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