Home | History | Annotate | Download | only in skdiff
      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 "skdiff.h"
      9 #include "skdiff_html.h"
     10 #include "SkStream.h"
     11 #include "SkTime.h"
     12 
     13 /// Make layout more consistent by scaling image to 240 height, 360 width,
     14 /// or natural size, whichever is smallest.
     15 static 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 
     28 static 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 
     79 static 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 
     93 static 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 
     99 static 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 
    163 static 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 
    173 static 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 
    181 static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
    182                                      const SkString& relativePath, bool local) {
    183     if (resource.fBitmap.empty()) {
    184         if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
    185             if (local && !resource.fFilename.isEmpty()) {
    186                 print_link_cell(stream, resource.fFilename, "N/A");
    187                 return;
    188             }
    189             if (!resource.fFullPath.isEmpty()) {
    190                 if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
    191                     resource.fFullPath.prepend(relativePath);
    192                 }
    193                 print_link_cell(stream, resource.fFullPath, "N/A");
    194                 return;
    195             }
    196         }
    197         stream->writeText("<td>N/A</td>");
    198         return;
    199     }
    200 
    201     int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
    202     if (local) {
    203         print_image_cell(stream, resource.fFilename, height);
    204         return;
    205     }
    206     if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
    207         resource.fFullPath.prepend(relativePath);
    208     }
    209     print_image_cell(stream, resource.fFullPath, height);
    210 }
    211 
    212 static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
    213     stream->writeText("<tr>\n");
    214     print_checkbox_cell(stream, diff);
    215     print_label_cell(stream, diff);
    216     print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
    217     print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
    218     print_diff_resource_cell(stream, diff.fBase, relativePath, false);
    219     print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
    220     stream->writeText("</tr>\n");
    221     stream->flush();
    222 }
    223 
    224 void print_diff_page(const int matchCount,
    225                      const int colorThreshold,
    226                      const RecordArray& differences,
    227                      const SkString& baseDir,
    228                      const SkString& comparisonDir,
    229                      const SkString& outputDir) {
    230 
    231     SkASSERT(!baseDir.isEmpty());
    232     SkASSERT(!comparisonDir.isEmpty());
    233     SkASSERT(!outputDir.isEmpty());
    234 
    235     SkString outputPath(outputDir);
    236     outputPath.append("index.html");
    237     //SkFILEWStream outputStream ("index.html");
    238     SkFILEWStream outputStream(outputPath.c_str());
    239 
    240     // Need to convert paths from relative-to-cwd to relative-to-outputDir
    241     // FIXME this doesn't work if there are '..' inside the outputDir
    242 
    243     bool isPathAbsolute = false;
    244     // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
    245     if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
    246         isPathAbsolute = true;
    247     }
    248 #ifdef SK_BUILD_FOR_WIN
    249     // On Windows, absolute paths can also start with "x:", where x is any
    250     // drive letter.
    251     if (outputDir.size() > 1 && ':' == outputDir[1]) {
    252         isPathAbsolute = true;
    253     }
    254 #endif
    255 
    256     SkString relativePath;
    257     if (!isPathAbsolute) {
    258         unsigned int ui;
    259         for (ui = 0; ui < outputDir.size(); ui++) {
    260             if (outputDir[ui] == PATH_DIV_CHAR) {
    261                 relativePath.append(".." PATH_DIV_STR);
    262             }
    263         }
    264     }
    265 
    266     outputStream.writeText(
    267         "<html>\n<head>\n"
    268         "<script src=\"https://ajax.googleapis.com/ajax/"
    269         "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
    270         "<script type=\"text/javascript\">\n"
    271         "function generateCheckedList() {\n"
    272         "var boxes = $(\":checkbox:checked\");\n"
    273         "var fileCmdLineString = '';\n"
    274         "var fileMultiLineString = '';\n"
    275         "for (var i = 0; i < boxes.length; i++) {\n"
    276         "fileMultiLineString += boxes[i].name + '<br>';\n"
    277         "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
    278         "}\n"
    279         "$(\"#checkedList\").html(fileCmdLineString + "
    280         "'<br><br>' + fileMultiLineString);\n"
    281         "}\n"
    282         "</script>\n</head>\n<body>\n");
    283     print_table_header(&outputStream, matchCount, colorThreshold, differences,
    284                        baseDir, comparisonDir);
    285     int i;
    286     for (i = 0; i < differences.count(); i++) {
    287         DiffRecord* diff = differences[i];
    288 
    289         switch (diff->fResult) {
    290           // Cases in which there is no diff to report.
    291           case DiffRecord::kEqualBits_Result:
    292           case DiffRecord::kEqualPixels_Result:
    293             continue;
    294           // Cases in which we want a detailed pixel diff.
    295           case DiffRecord::kDifferentPixels_Result:
    296           case DiffRecord::kDifferentSizes_Result:
    297           case DiffRecord::kCouldNotCompare_Result:
    298             print_diff_row(&outputStream, *diff, relativePath);
    299             continue;
    300           default:
    301             SkDEBUGFAIL("encountered DiffRecord with unknown result type");
    302             continue;
    303         }
    304     }
    305     outputStream.writeText(
    306         "</table>\n"
    307         "<input type=\"button\" "
    308         "onclick=\"generateCheckedList()\" "
    309         "value=\"Create Rebaseline List\">\n"
    310         "<div id=\"checkedList\"></div>\n"
    311         "</body>\n</html>\n");
    312     outputStream.flush();
    313 }
    314