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 "include/core/SkTime.h"
10#include "tools/skdiff/skdiff.h"
11#include "tools/skdiff/skdiff_html.h"
12
13/// Make layout more consistent by scaling image to 240 height, 360 width,
14/// or natural size, whichever is smallest.
15static 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
28static 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
79static 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
93static 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
99static 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
163static 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
173static 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
181static void print_diff_resource_cell(SkFILEWStream* stream, const DiffResource& resource,
182                                     const SkString& relativePath, bool local) {
183    SkString fullPath = resource.fFullPath;
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 (!fullPath.isEmpty()) {
191                if (!fullPath.startsWith(PATH_DIV_STR)) {
192                    fullPath.prepend(relativePath);
193                }
194                print_link_cell(stream, fullPath, "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 (!fullPath.startsWith(PATH_DIV_STR)) {
208        fullPath.prepend(relativePath);
209    }
210    print_image_cell(stream, fullPath, height);
211}
212
213static void print_diff_row(SkFILEWStream* stream, const 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    }
249#ifdef SK_BUILD_FOR_WIN
250    // 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        const 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}
315