Home | History | Annotate | Download | only in skpdiff
      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