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_WIN32 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 + ' ';\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