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