skdiff_html.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * Copyright 2012 Google Inc.
  3. *
  4. * Use of this source code is governed by a BSD-style license that can be
  5. * found in the LICENSE file.
  6. */
  7. #include "include/core/SkStream.h"
  8. #include "include/core/SkTime.h"
  9. #include "tools/skdiff/skdiff.h"
  10. #include "tools/skdiff/skdiff_html.h"
  11. /// Make layout more consistent by scaling image to 240 height, 360 width,
  12. /// or natural size, whichever is smallest.
  13. static int compute_image_height(int height, int width) {
  14. int retval = 240;
  15. if (height < retval) {
  16. retval = height;
  17. }
  18. float scale = (float) retval / height;
  19. if (width * scale > 360) {
  20. scale = (float) 360 / width;
  21. retval = static_cast<int>(height * scale);
  22. }
  23. return retval;
  24. }
  25. static void print_table_header(SkFILEWStream* stream,
  26. const int matchCount,
  27. const int colorThreshold,
  28. const RecordArray& differences,
  29. const SkString &baseDir,
  30. const SkString &comparisonDir,
  31. bool doOutputDate = false) {
  32. stream->writeText("<table>\n");
  33. stream->writeText("<tr><th>");
  34. stream->writeText("select image</th>\n<th>");
  35. if (doOutputDate) {
  36. SkTime::DateTime dt;
  37. SkTime::GetDateTime(&dt);
  38. stream->writeText("SkDiff run at ");
  39. stream->writeDecAsText(dt.fHour);
  40. stream->writeText(":");
  41. if (dt.fMinute < 10) {
  42. stream->writeText("0");
  43. }
  44. stream->writeDecAsText(dt.fMinute);
  45. stream->writeText(":");
  46. if (dt.fSecond < 10) {
  47. stream->writeText("0");
  48. }
  49. stream->writeDecAsText(dt.fSecond);
  50. stream->writeText("<br>");
  51. }
  52. stream->writeDecAsText(matchCount);
  53. stream->writeText(" of ");
  54. stream->writeDecAsText(differences.count());
  55. stream->writeText(" diffs matched ");
  56. if (colorThreshold == 0) {
  57. stream->writeText("exactly");
  58. } else {
  59. stream->writeText("within ");
  60. stream->writeDecAsText(colorThreshold);
  61. stream->writeText(" color units per component");
  62. }
  63. stream->writeText(".<br>");
  64. stream->writeText("</th>\n<th>");
  65. stream->writeText("every different pixel shown in white");
  66. stream->writeText("</th>\n<th>");
  67. stream->writeText("color difference at each pixel");
  68. stream->writeText("</th>\n<th>baseDir: ");
  69. stream->writeText(baseDir.c_str());
  70. stream->writeText("</th>\n<th>comparisonDir: ");
  71. stream->writeText(comparisonDir.c_str());
  72. stream->writeText("</th>\n");
  73. stream->writeText("</tr>\n");
  74. }
  75. static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) {
  76. stream->writeText("<br>(");
  77. stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
  78. diff.fBase.fBitmap.width() *
  79. diff.fBase.fBitmap.height()));
  80. stream->writeText(" pixels)");
  81. /*
  82. stream->writeDecAsText(diff.fWeightedFraction *
  83. diff.fBaseWidth *
  84. diff.fBaseHeight);
  85. stream->writeText(" weighted pixels)");
  86. */
  87. }
  88. static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) {
  89. stream->writeText("<td><input type=\"checkbox\" name=\"");
  90. stream->writeText(diff.fBase.fFilename.c_str());
  91. stream->writeText("\" checked=\"yes\"></td>");
  92. }
  93. static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) {
  94. char metricBuf [20];
  95. stream->writeText("<td><b>");
  96. stream->writeText(diff.fBase.fFilename.c_str());
  97. stream->writeText("</b><br>");
  98. switch (diff.fResult) {
  99. case DiffRecord::kEqualBits_Result:
  100. SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
  101. return;
  102. case DiffRecord::kEqualPixels_Result:
  103. SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
  104. return;
  105. case DiffRecord::kDifferentSizes_Result:
  106. stream->writeText("Image sizes differ</td>");
  107. return;
  108. case DiffRecord::kDifferentPixels_Result:
  109. sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference);
  110. stream->writeText(metricBuf);
  111. stream->writeText(" of pixels differ");
  112. stream->writeText("\n (");
  113. sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction);
  114. stream->writeText(metricBuf);
  115. stream->writeText(" weighted)");
  116. // Write the actual number of pixels that differ if it's < 1%
  117. if (diff.fFractionDifference < 0.01) {
  118. print_pixel_count(stream, diff);
  119. }
  120. stream->writeText("<br>");
  121. if (SkScalarRoundToInt(diff.fAverageMismatchA) > 0) {
  122. stream->writeText("<br>Average alpha channel mismatch ");
  123. stream->writeDecAsText(SkScalarRoundToInt(diff.fAverageMismatchA));
  124. }
  125. stream->writeText("<br>Max alpha channel mismatch ");
  126. stream->writeDecAsText(SkScalarRoundToInt(diff.fMaxMismatchA));
  127. stream->writeText("<br>Total alpha channel mismatch ");
  128. stream->writeDecAsText(static_cast<int>(diff.fTotalMismatchA));
  129. stream->writeText("<br>");
  130. stream->writeText("<br>Average color mismatch ");
  131. stream->writeDecAsText(SkScalarRoundToInt(MAX3(diff.fAverageMismatchR,
  132. diff.fAverageMismatchG,
  133. diff.fAverageMismatchB)));
  134. stream->writeText("<br>Max color mismatch ");
  135. stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
  136. diff.fMaxMismatchG,
  137. diff.fMaxMismatchB));
  138. stream->writeText("</td>");
  139. break;
  140. case DiffRecord::kCouldNotCompare_Result:
  141. stream->writeText("Could not compare.<br>base: ");
  142. stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus));
  143. stream->writeText("<br>comparison: ");
  144. stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus));
  145. stream->writeText("</td>");
  146. return;
  147. default:
  148. SkDEBUGFAIL("encountered DiffRecord with unknown result type");
  149. return;
  150. }
  151. }
  152. static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) {
  153. stream->writeText("<td><a href=\"");
  154. stream->writeText(path.c_str());
  155. stream->writeText("\"><img src=\"");
  156. stream->writeText(path.c_str());
  157. stream->writeText("\" height=\"");
  158. stream->writeDecAsText(height);
  159. stream->writeText("px\"></a></td>");
  160. }
  161. static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) {
  162. stream->writeText("<td><a href=\"");
  163. stream->writeText(path.c_str());
  164. stream->writeText("\">");
  165. stream->writeText(text);
  166. stream->writeText("</a></td>");
  167. }
  168. static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
  169. const SkString& relativePath, bool local) {
  170. if (resource.fBitmap.empty()) {
  171. if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
  172. if (local && !resource.fFilename.isEmpty()) {
  173. print_link_cell(stream, resource.fFilename, "N/A");
  174. return;
  175. }
  176. if (!resource.fFullPath.isEmpty()) {
  177. if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
  178. resource.fFullPath.prepend(relativePath);
  179. }
  180. print_link_cell(stream, resource.fFullPath, "N/A");
  181. return;
  182. }
  183. }
  184. stream->writeText("<td>N/A</td>");
  185. return;
  186. }
  187. int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
  188. if (local) {
  189. print_image_cell(stream, resource.fFilename, height);
  190. return;
  191. }
  192. if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
  193. resource.fFullPath.prepend(relativePath);
  194. }
  195. print_image_cell(stream, resource.fFullPath, height);
  196. }
  197. static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
  198. stream->writeText("<tr>\n");
  199. print_checkbox_cell(stream, diff);
  200. print_label_cell(stream, diff);
  201. print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
  202. print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
  203. print_diff_resource_cell(stream, diff.fBase, relativePath, false);
  204. print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
  205. stream->writeText("</tr>\n");
  206. stream->flush();
  207. }
  208. void print_diff_page(const int matchCount,
  209. const int colorThreshold,
  210. const RecordArray& differences,
  211. const SkString& baseDir,
  212. const SkString& comparisonDir,
  213. const SkString& outputDir) {
  214. SkASSERT(!baseDir.isEmpty());
  215. SkASSERT(!comparisonDir.isEmpty());
  216. SkASSERT(!outputDir.isEmpty());
  217. SkString outputPath(outputDir);
  218. outputPath.append("index.html");
  219. //SkFILEWStream outputStream ("index.html");
  220. SkFILEWStream outputStream(outputPath.c_str());
  221. // Need to convert paths from relative-to-cwd to relative-to-outputDir
  222. // FIXME this doesn't work if there are '..' inside the outputDir
  223. bool isPathAbsolute = false;
  224. // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
  225. if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
  226. isPathAbsolute = true;
  227. }
  228. #ifdef SK_BUILD_FOR_WIN
  229. // On Windows, absolute paths can also start with "x:", where x is any
  230. // drive letter.
  231. if (outputDir.size() > 1 && ':' == outputDir[1]) {
  232. isPathAbsolute = true;
  233. }
  234. #endif
  235. SkString relativePath;
  236. if (!isPathAbsolute) {
  237. unsigned int ui;
  238. for (ui = 0; ui < outputDir.size(); ui++) {
  239. if (outputDir[ui] == PATH_DIV_CHAR) {
  240. relativePath.append(".." PATH_DIV_STR);
  241. }
  242. }
  243. }
  244. outputStream.writeText(
  245. "<html>\n<head>\n"
  246. "<script src=\"https://ajax.googleapis.com/ajax/"
  247. "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
  248. "<script type=\"text/javascript\">\n"
  249. "function generateCheckedList() {\n"
  250. "var boxes = $(\":checkbox:checked\");\n"
  251. "var fileCmdLineString = '';\n"
  252. "var fileMultiLineString = '';\n"
  253. "for (var i = 0; i < boxes.length; i++) {\n"
  254. "fileMultiLineString += boxes[i].name + '<br>';\n"
  255. "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
  256. "}\n"
  257. "$(\"#checkedList\").html(fileCmdLineString + "
  258. "'<br><br>' + fileMultiLineString);\n"
  259. "}\n"
  260. "</script>\n</head>\n<body>\n");
  261. print_table_header(&outputStream, matchCount, colorThreshold, differences,
  262. baseDir, comparisonDir);
  263. int i;
  264. for (i = 0; i < differences.count(); i++) {
  265. DiffRecord* diff = differences[i];
  266. switch (diff->fResult) {
  267. // Cases in which there is no diff to report.
  268. case DiffRecord::kEqualBits_Result:
  269. case DiffRecord::kEqualPixels_Result:
  270. continue;
  271. // Cases in which we want a detailed pixel diff.
  272. case DiffRecord::kDifferentPixels_Result:
  273. case DiffRecord::kDifferentSizes_Result:
  274. case DiffRecord::kCouldNotCompare_Result:
  275. print_diff_row(&outputStream, *diff, relativePath);
  276. continue;
  277. default:
  278. SkDEBUGFAIL("encountered DiffRecord with unknown result type");
  279. continue;
  280. }
  281. }
  282. outputStream.writeText(
  283. "</table>\n"
  284. "<input type=\"button\" "
  285. "onclick=\"generateCheckedList()\" "
  286. "value=\"Create Rebaseline List\">\n"
  287. "<div id=\"checkedList\"></div>\n"
  288. "</body>\n</html>\n");
  289. outputStream.flush();
  290. }