|  | /* | 
|  | * Copyright 2012 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "skdiff.h" | 
|  | #include "skdiff_html.h" | 
|  | #include "SkStream.h" | 
|  | #include "SkTime.h" | 
|  |  | 
|  | /// Make layout more consistent by scaling image to 240 height, 360 width, | 
|  | /// or natural size, whichever is smallest. | 
|  | static int compute_image_height(int height, int width) { | 
|  | int retval = 240; | 
|  | if (height < retval) { | 
|  | retval = height; | 
|  | } | 
|  | float scale = (float) retval / height; | 
|  | if (width * scale > 360) { | 
|  | scale = (float) 360 / width; | 
|  | retval = static_cast<int>(height * scale); | 
|  | } | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static void print_table_header(SkFILEWStream* stream, | 
|  | const int matchCount, | 
|  | const int colorThreshold, | 
|  | const RecordArray& differences, | 
|  | const SkString &baseDir, | 
|  | const SkString &comparisonDir, | 
|  | bool doOutputDate = false) { | 
|  | stream->writeText("<table>\n"); | 
|  | stream->writeText("<tr><th>"); | 
|  | stream->writeText("select image</th>\n<th>"); | 
|  | if (doOutputDate) { | 
|  | SkTime::DateTime dt; | 
|  | SkTime::GetDateTime(&dt); | 
|  | stream->writeText("SkDiff run at "); | 
|  | stream->writeDecAsText(dt.fHour); | 
|  | stream->writeText(":"); | 
|  | if (dt.fMinute < 10) { | 
|  | stream->writeText("0"); | 
|  | } | 
|  | stream->writeDecAsText(dt.fMinute); | 
|  | stream->writeText(":"); | 
|  | if (dt.fSecond < 10) { | 
|  | stream->writeText("0"); | 
|  | } | 
|  | stream->writeDecAsText(dt.fSecond); | 
|  | stream->writeText("<br>"); | 
|  | } | 
|  | stream->writeDecAsText(matchCount); | 
|  | stream->writeText(" of "); | 
|  | stream->writeDecAsText(differences.count()); | 
|  | stream->writeText(" diffs matched "); | 
|  | if (colorThreshold == 0) { | 
|  | stream->writeText("exactly"); | 
|  | } else { | 
|  | stream->writeText("within "); | 
|  | stream->writeDecAsText(colorThreshold); | 
|  | stream->writeText(" color units per component"); | 
|  | } | 
|  | stream->writeText(".<br>"); | 
|  | stream->writeText("</th>\n<th>"); | 
|  | stream->writeText("every different pixel shown in white"); | 
|  | stream->writeText("</th>\n<th>"); | 
|  | stream->writeText("color difference at each pixel"); | 
|  | stream->writeText("</th>\n<th>baseDir: "); | 
|  | stream->writeText(baseDir.c_str()); | 
|  | stream->writeText("</th>\n<th>comparisonDir: "); | 
|  | stream->writeText(comparisonDir.c_str()); | 
|  | stream->writeText("</th>\n"); | 
|  | stream->writeText("</tr>\n"); | 
|  | } | 
|  |  | 
|  | static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) { | 
|  | stream->writeText("<br>("); | 
|  | stream->writeDecAsText(static_cast<int>(diff.fFractionDifference * | 
|  | diff.fBase.fBitmap.width() * | 
|  | diff.fBase.fBitmap.height())); | 
|  | stream->writeText(" pixels)"); | 
|  | /* | 
|  | stream->writeDecAsText(diff.fWeightedFraction * | 
|  | diff.fBaseWidth * | 
|  | diff.fBaseHeight); | 
|  | stream->writeText(" weighted pixels)"); | 
|  | */ | 
|  | } | 
|  |  | 
|  | static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) { | 
|  | stream->writeText("<td><input type=\"checkbox\" name=\""); | 
|  | stream->writeText(diff.fBase.fFilename.c_str()); | 
|  | stream->writeText("\" checked=\"yes\"></td>"); | 
|  | } | 
|  |  | 
|  | static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) { | 
|  | char metricBuf [20]; | 
|  |  | 
|  | stream->writeText("<td><b>"); | 
|  | stream->writeText(diff.fBase.fFilename.c_str()); | 
|  | stream->writeText("</b><br>"); | 
|  | switch (diff.fResult) { | 
|  | case DiffRecord::kEqualBits_Result: | 
|  | SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here"); | 
|  | return; | 
|  | case DiffRecord::kEqualPixels_Result: | 
|  | SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here"); | 
|  | return; | 
|  | case DiffRecord::kDifferentSizes_Result: | 
|  | stream->writeText("Image sizes differ</td>"); | 
|  | return; | 
|  | case DiffRecord::kDifferentPixels_Result: | 
|  | sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference); | 
|  | stream->writeText(metricBuf); | 
|  | stream->writeText(" of pixels differ"); | 
|  | stream->writeText("\n  ("); | 
|  | sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction); | 
|  | stream->writeText(metricBuf); | 
|  | stream->writeText(" weighted)"); | 
|  | // Write the actual number of pixels that differ if it's < 1% | 
|  | if (diff.fFractionDifference < 0.01) { | 
|  | print_pixel_count(stream, diff); | 
|  | } | 
|  | stream->writeText("<br>"); | 
|  | if (SkScalarRoundToInt(diff.fAverageMismatchA) > 0) { | 
|  | stream->writeText("<br>Average alpha channel mismatch "); | 
|  | stream->writeDecAsText(SkScalarRoundToInt(diff.fAverageMismatchA)); | 
|  | } | 
|  |  | 
|  | stream->writeText("<br>Max alpha channel mismatch "); | 
|  | stream->writeDecAsText(SkScalarRoundToInt(diff.fMaxMismatchA)); | 
|  |  | 
|  | stream->writeText("<br>Total alpha channel mismatch "); | 
|  | stream->writeDecAsText(static_cast<int>(diff.fTotalMismatchA)); | 
|  |  | 
|  | stream->writeText("<br>"); | 
|  | stream->writeText("<br>Average color mismatch "); | 
|  | stream->writeDecAsText(SkScalarRoundToInt(MAX3(diff.fAverageMismatchR, | 
|  | diff.fAverageMismatchG, | 
|  | diff.fAverageMismatchB))); | 
|  | stream->writeText("<br>Max color mismatch "); | 
|  | stream->writeDecAsText(MAX3(diff.fMaxMismatchR, | 
|  | diff.fMaxMismatchG, | 
|  | diff.fMaxMismatchB)); | 
|  | stream->writeText("</td>"); | 
|  | break; | 
|  | case DiffRecord::kCouldNotCompare_Result: | 
|  | stream->writeText("Could not compare.<br>base: "); | 
|  | stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus)); | 
|  | stream->writeText("<br>comparison: "); | 
|  | stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus)); | 
|  | stream->writeText("</td>"); | 
|  | return; | 
|  | default: | 
|  | SkDEBUGFAIL("encountered DiffRecord with unknown result type"); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) { | 
|  | stream->writeText("<td><a href=\""); | 
|  | stream->writeText(path.c_str()); | 
|  | stream->writeText("\"><img src=\""); | 
|  | stream->writeText(path.c_str()); | 
|  | stream->writeText("\" height=\""); | 
|  | stream->writeDecAsText(height); | 
|  | stream->writeText("px\"></a></td>"); | 
|  | } | 
|  |  | 
|  | static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) { | 
|  | stream->writeText("<td><a href=\""); | 
|  | stream->writeText(path.c_str()); | 
|  | stream->writeText("\">"); | 
|  | stream->writeText(text); | 
|  | stream->writeText("</a></td>"); | 
|  | } | 
|  |  | 
|  | static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource, | 
|  | const SkString& relativePath, bool local) { | 
|  | if (resource.fBitmap.empty()) { | 
|  | if (DiffResource::kCouldNotDecode_Status == resource.fStatus) { | 
|  | if (local && !resource.fFilename.isEmpty()) { | 
|  | print_link_cell(stream, resource.fFilename, "N/A"); | 
|  | return; | 
|  | } | 
|  | if (!resource.fFullPath.isEmpty()) { | 
|  | if (!resource.fFullPath.startsWith(PATH_DIV_STR)) { | 
|  | resource.fFullPath.prepend(relativePath); | 
|  | } | 
|  | print_link_cell(stream, resource.fFullPath, "N/A"); | 
|  | return; | 
|  | } | 
|  | } | 
|  | stream->writeText("<td>N/A</td>"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width()); | 
|  | if (local) { | 
|  | print_image_cell(stream, resource.fFilename, height); | 
|  | return; | 
|  | } | 
|  | if (!resource.fFullPath.startsWith(PATH_DIV_STR)) { | 
|  | resource.fFullPath.prepend(relativePath); | 
|  | } | 
|  | print_image_cell(stream, resource.fFullPath, height); | 
|  | } | 
|  |  | 
|  | static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) { | 
|  | stream->writeText("<tr>\n"); | 
|  | print_checkbox_cell(stream, diff); | 
|  | print_label_cell(stream, diff); | 
|  | print_diff_resource_cell(stream, diff.fWhite, relativePath, true); | 
|  | print_diff_resource_cell(stream, diff.fDifference, relativePath, true); | 
|  | print_diff_resource_cell(stream, diff.fBase, relativePath, false); | 
|  | print_diff_resource_cell(stream, diff.fComparison, relativePath, false); | 
|  | stream->writeText("</tr>\n"); | 
|  | stream->flush(); | 
|  | } | 
|  |  | 
|  | void print_diff_page(const int matchCount, | 
|  | const int colorThreshold, | 
|  | const RecordArray& differences, | 
|  | const SkString& baseDir, | 
|  | const SkString& comparisonDir, | 
|  | const SkString& outputDir) { | 
|  |  | 
|  | SkASSERT(!baseDir.isEmpty()); | 
|  | SkASSERT(!comparisonDir.isEmpty()); | 
|  | SkASSERT(!outputDir.isEmpty()); | 
|  |  | 
|  | SkString outputPath(outputDir); | 
|  | outputPath.append("index.html"); | 
|  | //SkFILEWStream outputStream ("index.html"); | 
|  | SkFILEWStream outputStream(outputPath.c_str()); | 
|  |  | 
|  | // Need to convert paths from relative-to-cwd to relative-to-outputDir | 
|  | // FIXME this doesn't work if there are '..' inside the outputDir | 
|  |  | 
|  | bool isPathAbsolute = false; | 
|  | // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute. | 
|  | if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) { | 
|  | isPathAbsolute = true; | 
|  | } | 
|  | #ifdef SK_BUILD_FOR_WIN32 | 
|  | // On Windows, absolute paths can also start with "x:", where x is any | 
|  | // drive letter. | 
|  | if (outputDir.size() > 1 && ':' == outputDir[1]) { | 
|  | isPathAbsolute = true; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | SkString relativePath; | 
|  | if (!isPathAbsolute) { | 
|  | unsigned int ui; | 
|  | for (ui = 0; ui < outputDir.size(); ui++) { | 
|  | if (outputDir[ui] == PATH_DIV_CHAR) { | 
|  | relativePath.append(".." PATH_DIV_STR); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | outputStream.writeText( | 
|  | "<html>\n<head>\n" | 
|  | "<script src=\"https://ajax.googleapis.com/ajax/" | 
|  | "libs/jquery/1.7.2/jquery.min.js\"></script>\n" | 
|  | "<script type=\"text/javascript\">\n" | 
|  | "function generateCheckedList() {\n" | 
|  | "var boxes = $(\":checkbox:checked\");\n" | 
|  | "var fileCmdLineString = '';\n" | 
|  | "var fileMultiLineString = '';\n" | 
|  | "for (var i = 0; i < boxes.length; i++) {\n" | 
|  | "fileMultiLineString += boxes[i].name + '<br>';\n" | 
|  | "fileCmdLineString += boxes[i].name + ' ';\n" | 
|  | "}\n" | 
|  | "$(\"#checkedList\").html(fileCmdLineString + " | 
|  | "'<br><br>' + fileMultiLineString);\n" | 
|  | "}\n" | 
|  | "</script>\n</head>\n<body>\n"); | 
|  | print_table_header(&outputStream, matchCount, colorThreshold, differences, | 
|  | baseDir, comparisonDir); | 
|  | int i; | 
|  | for (i = 0; i < differences.count(); i++) { | 
|  | DiffRecord* diff = differences[i]; | 
|  |  | 
|  | switch (diff->fResult) { | 
|  | // Cases in which there is no diff to report. | 
|  | case DiffRecord::kEqualBits_Result: | 
|  | case DiffRecord::kEqualPixels_Result: | 
|  | continue; | 
|  | // Cases in which we want a detailed pixel diff. | 
|  | case DiffRecord::kDifferentPixels_Result: | 
|  | case DiffRecord::kDifferentSizes_Result: | 
|  | case DiffRecord::kCouldNotCompare_Result: | 
|  | print_diff_row(&outputStream, *diff, relativePath); | 
|  | continue; | 
|  | default: | 
|  | SkDEBUGFAIL("encountered DiffRecord with unknown result type"); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | outputStream.writeText( | 
|  | "</table>\n" | 
|  | "<input type=\"button\" " | 
|  | "onclick=\"generateCheckedList()\" " | 
|  | "value=\"Create Rebaseline List\">\n" | 
|  | "<div id=\"checkedList\"></div>\n" | 
|  | "</body>\n</html>\n"); | 
|  | outputStream.flush(); | 
|  | } |