1 /* 2 * Copyright 2013 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 "SkBitmap.h" 9 #include "SkImageDecoder.h" 10 #include "SkOSFile.h" 11 #include "SkStream.h" 12 #include "SkTDict.h" 13 14 #include "SkDiffContext.h" 15 #include "SkImageDiffer.h" 16 #include "skpdiff_util.h" 17 18 // Truncates the number of points of interests in JSON output to not freeze the parser 19 static const int kMaxPOI = 100; 20 21 SkDiffContext::SkDiffContext() { 22 fRecords = NULL; 23 fDiffers = NULL; 24 fDifferCount = 0; 25 } 26 27 SkDiffContext::~SkDiffContext() { 28 // Delete the record linked list 29 DiffRecord* currentRecord = fRecords; 30 while (NULL != currentRecord) { 31 DiffRecord* nextRecord = currentRecord->fNext; 32 SkDELETE(currentRecord); 33 currentRecord = nextRecord; 34 } 35 36 if (NULL != fDiffers) { 37 SkDELETE_ARRAY(fDiffers); 38 } 39 } 40 41 void SkDiffContext::setDiffers(const SkTDArray<SkImageDiffer*>& differs) { 42 // Delete whatever the last array of differs was 43 if (NULL != fDiffers) { 44 SkDELETE_ARRAY(fDiffers); 45 fDiffers = NULL; 46 fDifferCount = 0; 47 } 48 49 // Copy over the new differs 50 fDifferCount = differs.count(); 51 fDiffers = SkNEW_ARRAY(SkImageDiffer*, fDifferCount); 52 differs.copy(fDiffers); 53 } 54 55 void SkDiffContext::addDiff(const char* baselinePath, const char* testPath) { 56 // Load the images at the paths 57 SkBitmap baselineBitmap; 58 SkBitmap testBitmap; 59 if (!SkImageDecoder::DecodeFile(baselinePath, &baselineBitmap)) { 60 SkDebugf("Failed to load bitmap \"%s\"\n", baselinePath); 61 return; 62 } 63 if (!SkImageDecoder::DecodeFile(testPath, &testBitmap)) { 64 SkDebugf("Failed to load bitmap \"%s\"\n", testPath); 65 return; 66 } 67 68 // Setup a record for this diff 69 DiffRecord* newRecord = SkNEW(DiffRecord); 70 newRecord->fBaselinePath = baselinePath; 71 newRecord->fTestPath = testPath; 72 newRecord->fNext = fRecords; 73 fRecords = newRecord; 74 75 // Perform each diff 76 for (int differIndex = 0; differIndex < fDifferCount; differIndex++) { 77 SkImageDiffer* differ = fDiffers[differIndex]; 78 int diffID = differ->queueDiff(&baselineBitmap, &testBitmap); 79 if (diffID >= 0) { 80 81 // Copy the results into data for this record 82 DiffData& diffData = newRecord->fDiffs.push_back(); 83 84 diffData.fDiffName = differ->getName(); 85 diffData.fResult = differ->getResult(diffID); 86 87 int poiCount = differ->getPointsOfInterestCount(diffID); 88 SkIPoint* poi = differ->getPointsOfInterest(diffID); 89 diffData.fPointsOfInterest.append(poiCount, poi); 90 91 // Because we are doing everything synchronously for now, we are done with the diff 92 // after reading it. 93 differ->deleteDiff(diffID); 94 } 95 } 96 } 97 98 99 void SkDiffContext::diffDirectories(const char baselinePath[], const char testPath[]) { 100 // Get the files in the baseline, we will then look for those inside the test path 101 SkTArray<SkString> baselineEntries; 102 if (!get_directory(baselinePath, &baselineEntries)) { 103 SkDebugf("Unable to open path \"%s\"\n", baselinePath); 104 return; 105 } 106 107 for (int baselineIndex = 0; baselineIndex < baselineEntries.count(); baselineIndex++) { 108 SkDebugf("[%i/%i] ", baselineIndex, baselineEntries.count()); 109 const char* baseFilename = baselineEntries[baselineIndex].c_str(); 110 111 // Find the real location of each file to compare 112 SkString baselineFile = SkOSPath::SkPathJoin(baselinePath, baseFilename); 113 SkString testFile = SkOSPath::SkPathJoin(testPath, baseFilename); 114 115 // Check that the test file exists and is a file 116 if (sk_exists(testFile.c_str()) && !sk_isdir(testFile.c_str())) { 117 // Queue up the comparison with the differ 118 this->addDiff(baselineFile.c_str(), testFile.c_str()); 119 } else { 120 SkDebugf("Baseline file \"%s\" has no corresponding test file\n", baselineFile.c_str()); 121 } 122 } 123 } 124 125 126 void SkDiffContext::diffPatterns(const char baselinePattern[], const char testPattern[]) { 127 // Get the files in the baseline and test patterns. Because they are in sorted order, it's easy 128 // to find corresponding images by matching entry indices. 129 130 SkTArray<SkString> baselineEntries; 131 if (!glob_files(baselinePattern, &baselineEntries)) { 132 SkDebugf("Unable to get pattern \"%s\"\n", baselinePattern); 133 return; 134 } 135 136 SkTArray<SkString> testEntries; 137 if (!glob_files(testPattern, &testEntries)) { 138 SkDebugf("Unable to get pattern \"%s\"\n", testPattern); 139 return; 140 } 141 142 if (baselineEntries.count() != testEntries.count()) { 143 SkDebugf("Baseline and test patterns do not yield corresponding number of files\n"); 144 return; 145 } 146 147 for (int entryIndex = 0; entryIndex < baselineEntries.count(); entryIndex++) { 148 SkDebugf("[%i/%i] ", entryIndex, baselineEntries.count()); 149 const char* baselineFilename = baselineEntries[entryIndex].c_str(); 150 const char* testFilename = testEntries [entryIndex].c_str(); 151 152 this->addDiff(baselineFilename, testFilename); 153 } 154 } 155 156 void SkDiffContext::outputRecords(SkWStream& stream, bool useJSONP) { 157 DiffRecord* currentRecord = fRecords; 158 if (useJSONP) { 159 stream.writeText("var SkPDiffRecords = {\n"); 160 } else { 161 stream.writeText("{\n"); 162 } 163 stream.writeText(" \"records\": [\n"); 164 while (NULL != currentRecord) { 165 stream.writeText(" {\n"); 166 167 SkString baselineAbsPath = get_absolute_path(currentRecord->fBaselinePath); 168 SkString testAbsPath = get_absolute_path(currentRecord->fTestPath); 169 170 stream.writeText(" \"baselinePath\": \""); 171 stream.writeText(baselineAbsPath.c_str()); 172 stream.writeText("\",\n"); 173 174 stream.writeText(" \"testPath\": \""); 175 stream.writeText(testAbsPath.c_str()); 176 stream.writeText("\",\n"); 177 178 stream.writeText(" \"diffs\": [\n"); 179 for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { 180 DiffData& data = currentRecord->fDiffs[diffIndex]; 181 stream.writeText(" {\n"); 182 183 stream.writeText(" \"differName\": \""); 184 stream.writeText(data.fDiffName); 185 stream.writeText("\",\n"); 186 187 stream.writeText(" \"result\": "); 188 stream.writeScalarAsText((SkScalar)data.fResult); 189 stream.writeText(",\n"); 190 191 stream.writeText(" \"pointsOfInterest\": [\n"); 192 for (int poiIndex = 0; poiIndex < data.fPointsOfInterest.count() && 193 poiIndex < kMaxPOI; poiIndex++) { 194 SkIPoint poi = data.fPointsOfInterest[poiIndex]; 195 stream.writeText(" ["); 196 stream.writeDecAsText(poi.x()); 197 stream.writeText(","); 198 stream.writeDecAsText(poi.y()); 199 stream.writeText("]"); 200 201 // JSON does not allow trailing commas 202 if (poiIndex + 1 < data.fPointsOfInterest.count() && 203 poiIndex + 1 < kMaxPOI) { 204 stream.writeText(","); 205 } 206 stream.writeText("\n"); 207 } 208 stream.writeText(" ]\n"); 209 stream.writeText(" }"); 210 211 // JSON does not allow trailing commas 212 if (diffIndex + 1 < currentRecord->fDiffs.count()) { 213 stream.writeText(","); 214 } 215 stream.writeText(" \n"); 216 } 217 stream.writeText(" ]\n"); 218 219 stream.writeText(" }"); 220 221 // JSON does not allow trailing commas 222 if (NULL != currentRecord->fNext) { 223 stream.writeText(","); 224 } 225 stream.writeText("\n"); 226 currentRecord = currentRecord->fNext; 227 } 228 stream.writeText(" ]\n"); 229 if (useJSONP) { 230 stream.writeText("};\n"); 231 } else { 232 stream.writeText("}\n"); 233 } 234 } 235 236 void SkDiffContext::outputCsv(SkWStream& stream) { 237 SkTDict<int> columns(2); 238 int cntColumns = 0; 239 240 stream.writeText("key"); 241 242 DiffRecord* currentRecord = fRecords; 243 244 // Write CSV header and create a dictionary of all columns. 245 while (NULL != currentRecord) { 246 for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { 247 DiffData& data = currentRecord->fDiffs[diffIndex]; 248 if (!columns.find(data.fDiffName)) { 249 columns.set(data.fDiffName, cntColumns); 250 stream.writeText(", "); 251 stream.writeText(data.fDiffName); 252 cntColumns++; 253 } 254 } 255 currentRecord = currentRecord->fNext; 256 } 257 stream.writeText("\n"); 258 259 double values[100]; 260 SkASSERT(cntColumns < 100); // Make the array larger, if we ever have so many diff types. 261 262 currentRecord = fRecords; 263 while (NULL != currentRecord) { 264 for (int i = 0; i < cntColumns; i++) { 265 values[i] = -1; 266 } 267 268 for (int diffIndex = 0; diffIndex < currentRecord->fDiffs.count(); diffIndex++) { 269 DiffData& data = currentRecord->fDiffs[diffIndex]; 270 int index = -1; 271 SkAssertResult(columns.find(data.fDiffName, &index)); 272 SkASSERT(index >= 0 && index < cntColumns); 273 values[index] = data.fResult; 274 } 275 276 const char* filename = currentRecord->fBaselinePath.c_str() + 277 strlen(currentRecord->fBaselinePath.c_str()) - 1; 278 while (filename > currentRecord->fBaselinePath.c_str() && *(filename - 1) != '/') { 279 filename--; 280 } 281 282 stream.writeText(filename); 283 284 for (int i = 0; i < cntColumns; i++) { 285 SkString str; 286 str.printf(", %f", values[i]); 287 stream.writeText(str.c_str()); 288 } 289 stream.writeText("\n"); 290 291 currentRecord = currentRecord->fNext; 292 } 293 } 294