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