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