blob: 11299c68581812d318b55a68a00254f65bede14e [file] [log] [blame]
bungeman@google.come3c8ddf2012-12-05 20:13:12 +00001/*
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
8#include "skdiff.h"
9#include "skdiff_html.h"
10#include "SkStream.h"
Hal Canary8b681102018-09-05 22:32:41 -040011#include "SkStreamPriv.h"
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000012#include "SkTime.h"
13
14/// Make layout more consistent by scaling image to 240 height, 360 width,
15/// or natural size, whichever is smallest.
16static int compute_image_height(int height, int width) {
17 int retval = 240;
18 if (height < retval) {
19 retval = height;
20 }
21 float scale = (float) retval / height;
22 if (width * scale > 360) {
23 scale = (float) 360 / width;
24 retval = static_cast<int>(height * scale);
25 }
26 return retval;
27}
28
29static void print_table_header(SkFILEWStream* stream,
30 const int matchCount,
31 const int colorThreshold,
32 const RecordArray& differences,
33 const SkString &baseDir,
34 const SkString &comparisonDir,
35 bool doOutputDate = false) {
36 stream->writeText("<table>\n");
37 stream->writeText("<tr><th>");
38 stream->writeText("select image</th>\n<th>");
39 if (doOutputDate) {
40 SkTime::DateTime dt;
41 SkTime::GetDateTime(&dt);
42 stream->writeText("SkDiff run at ");
Hal Canary8b681102018-09-05 22:32:41 -040043 SkWStreamWriteDecAsText(stream, dt.fHour);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000044 stream->writeText(":");
45 if (dt.fMinute < 10) {
46 stream->writeText("0");
47 }
Hal Canary8b681102018-09-05 22:32:41 -040048 SkWStreamWriteDecAsText(stream, dt.fMinute);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000049 stream->writeText(":");
50 if (dt.fSecond < 10) {
51 stream->writeText("0");
52 }
Hal Canary8b681102018-09-05 22:32:41 -040053 SkWStreamWriteDecAsText(stream, dt.fSecond);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000054 stream->writeText("<br>");
55 }
Hal Canary8b681102018-09-05 22:32:41 -040056 SkWStreamWriteDecAsText(stream, matchCount);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000057 stream->writeText(" of ");
Hal Canary8b681102018-09-05 22:32:41 -040058 SkWStreamWriteDecAsText(stream, differences.count());
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000059 stream->writeText(" diffs matched ");
60 if (colorThreshold == 0) {
61 stream->writeText("exactly");
62 } else {
63 stream->writeText("within ");
Hal Canary8b681102018-09-05 22:32:41 -040064 SkWStreamWriteDecAsText(stream, colorThreshold);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000065 stream->writeText(" color units per component");
66 }
67 stream->writeText(".<br>");
68 stream->writeText("</th>\n<th>");
69 stream->writeText("every different pixel shown in white");
70 stream->writeText("</th>\n<th>");
71 stream->writeText("color difference at each pixel");
72 stream->writeText("</th>\n<th>baseDir: ");
73 stream->writeText(baseDir.c_str());
74 stream->writeText("</th>\n<th>comparisonDir: ");
75 stream->writeText(comparisonDir.c_str());
76 stream->writeText("</th>\n");
77 stream->writeText("</tr>\n");
78}
79
80static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) {
81 stream->writeText("<br>(");
Hal Canary8b681102018-09-05 22:32:41 -040082 SkWStreamWriteDecAsText(stream, static_cast<int>(diff.fFractionDifference *
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000083 diff.fBase.fBitmap.width() *
84 diff.fBase.fBitmap.height()));
85 stream->writeText(" pixels)");
86/*
Hal Canary8b681102018-09-05 22:32:41 -040087 SkWStreamWriteDecAsText(stream, diff.fWeightedFraction *
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000088 diff.fBaseWidth *
89 diff.fBaseHeight);
90 stream->writeText(" weighted pixels)");
91*/
92}
93
94static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) {
95 stream->writeText("<td><input type=\"checkbox\" name=\"");
96 stream->writeText(diff.fBase.fFilename.c_str());
97 stream->writeText("\" checked=\"yes\"></td>");
98}
99
100static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) {
101 char metricBuf [20];
102
103 stream->writeText("<td><b>");
104 stream->writeText(diff.fBase.fFilename.c_str());
105 stream->writeText("</b><br>");
106 switch (diff.fResult) {
107 case DiffRecord::kEqualBits_Result:
108 SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
109 return;
110 case DiffRecord::kEqualPixels_Result:
111 SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
112 return;
113 case DiffRecord::kDifferentSizes_Result:
114 stream->writeText("Image sizes differ</td>");
115 return;
116 case DiffRecord::kDifferentPixels_Result:
117 sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference);
118 stream->writeText(metricBuf);
119 stream->writeText(" of pixels differ");
120 stream->writeText("\n (");
121 sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction);
122 stream->writeText(metricBuf);
123 stream->writeText(" weighted)");
124 // Write the actual number of pixels that differ if it's < 1%
125 if (diff.fFractionDifference < 0.01) {
126 print_pixel_count(stream, diff);
127 }
rmistry@google.comee5a5ee2013-01-03 19:23:22 +0000128 stream->writeText("<br>");
reed@google.com89d15a22013-01-07 22:26:05 +0000129 if (SkScalarRoundToInt(diff.fAverageMismatchA) > 0) {
rmistry@google.comee5a5ee2013-01-03 19:23:22 +0000130 stream->writeText("<br>Average alpha channel mismatch ");
Hal Canary8b681102018-09-05 22:32:41 -0400131 SkWStreamWriteDecAsText(stream, SkScalarRoundToInt(diff.fAverageMismatchA));
rmistry@google.comee5a5ee2013-01-03 19:23:22 +0000132 }
133
134 stream->writeText("<br>Max alpha channel mismatch ");
Hal Canary8b681102018-09-05 22:32:41 -0400135 SkWStreamWriteDecAsText(stream, SkScalarRoundToInt(diff.fMaxMismatchA));
rmistry@google.comee5a5ee2013-01-03 19:23:22 +0000136
137 stream->writeText("<br>Total alpha channel mismatch ");
Hal Canary8b681102018-09-05 22:32:41 -0400138 SkWStreamWriteDecAsText(stream, static_cast<int>(diff.fTotalMismatchA));
rmistry@google.comee5a5ee2013-01-03 19:23:22 +0000139
140 stream->writeText("<br>");
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000141 stream->writeText("<br>Average color mismatch ");
Hal Canary8b681102018-09-05 22:32:41 -0400142 SkWStreamWriteDecAsText(stream, SkScalarRoundToInt(MAX3(diff.fAverageMismatchR,
reed@google.com89d15a22013-01-07 22:26:05 +0000143 diff.fAverageMismatchG,
144 diff.fAverageMismatchB)));
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000145 stream->writeText("<br>Max color mismatch ");
Hal Canary8b681102018-09-05 22:32:41 -0400146 SkWStreamWriteDecAsText(stream, MAX3(diff.fMaxMismatchR,
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000147 diff.fMaxMismatchG,
148 diff.fMaxMismatchB));
149 stream->writeText("</td>");
150 break;
151 case DiffRecord::kCouldNotCompare_Result:
152 stream->writeText("Could not compare.<br>base: ");
153 stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus));
154 stream->writeText("<br>comparison: ");
155 stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus));
156 stream->writeText("</td>");
157 return;
158 default:
159 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
160 return;
161 }
162}
163
164static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) {
165 stream->writeText("<td><a href=\"");
166 stream->writeText(path.c_str());
167 stream->writeText("\"><img src=\"");
168 stream->writeText(path.c_str());
169 stream->writeText("\" height=\"");
Hal Canary8b681102018-09-05 22:32:41 -0400170 SkWStreamWriteDecAsText(stream, height);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000171 stream->writeText("px\"></a></td>");
172}
173
174static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) {
175 stream->writeText("<td><a href=\"");
176 stream->writeText(path.c_str());
177 stream->writeText("\">");
178 stream->writeText(text);
179 stream->writeText("</a></td>");
180}
181
182static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
183 const SkString& relativePath, bool local) {
184 if (resource.fBitmap.empty()) {
185 if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
186 if (local && !resource.fFilename.isEmpty()) {
187 print_link_cell(stream, resource.fFilename, "N/A");
188 return;
189 }
190 if (!resource.fFullPath.isEmpty()) {
191 if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
192 resource.fFullPath.prepend(relativePath);
193 }
194 print_link_cell(stream, resource.fFullPath, "N/A");
195 return;
196 }
197 }
198 stream->writeText("<td>N/A</td>");
199 return;
200 }
201
202 int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
203 if (local) {
204 print_image_cell(stream, resource.fFilename, height);
205 return;
206 }
207 if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
208 resource.fFullPath.prepend(relativePath);
209 }
210 print_image_cell(stream, resource.fFullPath, height);
211}
212
213static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
214 stream->writeText("<tr>\n");
215 print_checkbox_cell(stream, diff);
216 print_label_cell(stream, diff);
217 print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
218 print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
219 print_diff_resource_cell(stream, diff.fBase, relativePath, false);
220 print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
221 stream->writeText("</tr>\n");
222 stream->flush();
223}
224
225void print_diff_page(const int matchCount,
226 const int colorThreshold,
227 const RecordArray& differences,
228 const SkString& baseDir,
229 const SkString& comparisonDir,
230 const SkString& outputDir) {
231
232 SkASSERT(!baseDir.isEmpty());
233 SkASSERT(!comparisonDir.isEmpty());
234 SkASSERT(!outputDir.isEmpty());
235
236 SkString outputPath(outputDir);
237 outputPath.append("index.html");
238 //SkFILEWStream outputStream ("index.html");
239 SkFILEWStream outputStream(outputPath.c_str());
240
241 // Need to convert paths from relative-to-cwd to relative-to-outputDir
242 // FIXME this doesn't work if there are '..' inside the outputDir
243
244 bool isPathAbsolute = false;
245 // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
246 if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
247 isPathAbsolute = true;
248 }
Mike Klein8f11d4d2018-01-24 12:42:55 -0500249#ifdef SK_BUILD_FOR_WIN
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000250 // On Windows, absolute paths can also start with "x:", where x is any
251 // drive letter.
252 if (outputDir.size() > 1 && ':' == outputDir[1]) {
253 isPathAbsolute = true;
254 }
255#endif
256
257 SkString relativePath;
258 if (!isPathAbsolute) {
259 unsigned int ui;
260 for (ui = 0; ui < outputDir.size(); ui++) {
261 if (outputDir[ui] == PATH_DIV_CHAR) {
262 relativePath.append(".." PATH_DIV_STR);
263 }
264 }
265 }
266
267 outputStream.writeText(
268 "<html>\n<head>\n"
269 "<script src=\"https://ajax.googleapis.com/ajax/"
270 "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
271 "<script type=\"text/javascript\">\n"
272 "function generateCheckedList() {\n"
273 "var boxes = $(\":checkbox:checked\");\n"
274 "var fileCmdLineString = '';\n"
275 "var fileMultiLineString = '';\n"
276 "for (var i = 0; i < boxes.length; i++) {\n"
277 "fileMultiLineString += boxes[i].name + '<br>';\n"
278 "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
279 "}\n"
280 "$(\"#checkedList\").html(fileCmdLineString + "
281 "'<br><br>' + fileMultiLineString);\n"
282 "}\n"
283 "</script>\n</head>\n<body>\n");
284 print_table_header(&outputStream, matchCount, colorThreshold, differences,
285 baseDir, comparisonDir);
286 int i;
287 for (i = 0; i < differences.count(); i++) {
288 DiffRecord* diff = differences[i];
289
290 switch (diff->fResult) {
291 // Cases in which there is no diff to report.
292 case DiffRecord::kEqualBits_Result:
293 case DiffRecord::kEqualPixels_Result:
294 continue;
295 // Cases in which we want a detailed pixel diff.
296 case DiffRecord::kDifferentPixels_Result:
297 case DiffRecord::kDifferentSizes_Result:
298 case DiffRecord::kCouldNotCompare_Result:
299 print_diff_row(&outputStream, *diff, relativePath);
300 continue;
301 default:
302 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
303 continue;
304 }
305 }
306 outputStream.writeText(
307 "</table>\n"
308 "<input type=\"button\" "
309 "onclick=\"generateCheckedList()\" "
310 "value=\"Create Rebaseline List\">\n"
311 "<div id=\"checkedList\"></div>\n"
312 "</body>\n</html>\n");
313 outputStream.flush();
314}