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 + '&nbsp;';\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