| /* |
| * 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 "include/core/SkStream.h" |
| #include "tools/skdiff/skdiff.h" |
| #include "tools/skdiff/skdiff_html.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) { |
| stream->writeText("<table>\n"); |
| stream->writeText("<tr><th>"); |
| stream->writeText("select image</th>\n<th>"); |
| stream->writeDecAsText(matchCount); |
| stream->writeText(" of "); |
| stream->writeDecAsText(differences.size()); |
| 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, const DiffResource& resource, |
| const SkString& relativePath, bool local) { |
| SkString fullPath = resource.fFullPath; |
| 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 (!fullPath.isEmpty()) { |
| if (!fullPath.startsWith(PATH_DIV_STR)) { |
| fullPath.prepend(relativePath); |
| } |
| print_link_cell(stream, fullPath, "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 (!fullPath.startsWith(PATH_DIV_STR)) { |
| fullPath.prepend(relativePath); |
| } |
| print_image_cell(stream, fullPath, height); |
| } |
| |
| static void print_diff_row(SkFILEWStream* stream, const 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_WIN |
| // 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 type=\"text/javascript\">\n" |
| "function generateCheckedList() {\n" |
| " const boxes = document.querySelectorAll('input[type=checkbox]:checked');\n" |
| " let fileCmdLineString = '';\n" |
| " let fileMultiLineString = '';\n" |
| " for (let i = 0; i < boxes.length; i++) {\n" |
| " fileMultiLineString += boxes[i].name + '<br>';\n" |
| " fileCmdLineString += boxes[i].name + ' ';\n" |
| " }\n" |
| " const checkedList = document.querySelector('#checkedList');\n" |
| " checkedList.innerHTML = 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.size(); i++) { |
| const 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(); |
| } |