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 "SkBitmap.h"
10 #include "SkColor.h"
11 #include "SkColorPriv.h"
12 #include "SkTypes.h"
13 
14 /*static*/ char const * const DiffRecord::ResultNames[DiffRecord::kResultCount] = {
15     "EqualBits",
16     "EqualPixels",
17     "DifferentPixels",
18     "DifferentSizes",
19     "CouldNotCompare",
20     "Unknown",
21 };
22 
getResultByName(const char * name)23 DiffRecord::Result DiffRecord::getResultByName(const char *name) {
24     for (int result = 0; result < DiffRecord::kResultCount; ++result) {
25         if (0 == strcmp(DiffRecord::ResultNames[result], name)) {
26             return static_cast<DiffRecord::Result>(result);
27         }
28     }
29     return DiffRecord::kResultCount;
30 }
31 
32 static char const * const ResultDescriptions[DiffRecord::kResultCount] = {
33     "contain exactly the same bits",
34     "contain the same pixel values, but not the same bits",
35     "have identical dimensions but some differing pixels",
36     "have differing dimensions",
37     "could not be compared",
38     "not compared yet",
39 };
40 
getResultDescription(DiffRecord::Result result)41 const char* DiffRecord::getResultDescription(DiffRecord::Result result) {
42     return ResultDescriptions[result];
43 }
44 
45 /*static*/ char const * const DiffResource::StatusNames[DiffResource::kStatusCount] = {
46     "Decoded",
47     "CouldNotDecode",
48 
49     "Read",
50     "CouldNotRead",
51 
52     "Exists",
53     "DoesNotExist",
54 
55     "Specified",
56     "Unspecified",
57 
58     "Unknown",
59 };
60 
getStatusByName(const char * name)61 DiffResource::Status DiffResource::getStatusByName(const char *name) {
62     for (int status = 0; status < DiffResource::kStatusCount; ++status) {
63         if (0 == strcmp(DiffResource::StatusNames[status], name)) {
64             return static_cast<DiffResource::Status>(status);
65         }
66     }
67     return DiffResource::kStatusCount;
68 }
69 
70 static char const * const StatusDescriptions[DiffResource::kStatusCount] = {
71     "decoded",
72     "could not be decoded",
73 
74     "read",
75     "could not be read",
76 
77     "found",
78     "not found",
79 
80     "specified",
81     "unspecified",
82 
83     "unknown",
84 };
85 
getStatusDescription(DiffResource::Status status)86 const char* DiffResource::getStatusDescription(DiffResource::Status status) {
87     return StatusDescriptions[status];
88 }
89 
isStatusFailed(DiffResource::Status status)90 bool DiffResource::isStatusFailed(DiffResource::Status status) {
91     return DiffResource::kCouldNotDecode_Status == status ||
92            DiffResource::kCouldNotRead_Status == status ||
93            DiffResource::kDoesNotExist_Status == status ||
94            DiffResource::kUnspecified_Status == status ||
95            DiffResource::kUnknown_Status == status;
96 }
97 
getMatchingStatuses(char * selector,bool statuses[kStatusCount])98 bool DiffResource::getMatchingStatuses(char* selector, bool statuses[kStatusCount]) {
99     if (!strcmp(selector, "any")) {
100         for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
101             statuses[statusIndex] = true;
102         }
103         return true;
104     }
105 
106     for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
107         statuses[statusIndex] = false;
108     }
109 
110     static const char kDelimiterChar = ',';
111     bool understood = true;
112     while (true) {
113         char* delimiterPtr = strchr(selector, kDelimiterChar);
114 
115         if (delimiterPtr) {
116             *delimiterPtr = '\0';
117         }
118 
119         if (!strcmp(selector, "failed")) {
120             for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
121                 Status status = static_cast<Status>(statusIndex);
122                 statuses[statusIndex] |= isStatusFailed(status);
123             }
124         } else {
125             Status status = getStatusByName(selector);
126             if (status == kStatusCount) {
127                 understood = false;
128             } else {
129                 statuses[status] = true;
130             }
131         }
132 
133         if (!delimiterPtr) {
134             break;
135         }
136 
137         *delimiterPtr = kDelimiterChar;
138         selector = delimiterPtr + 1;
139     }
140     return understood;
141 }
142 
colors_match_thresholded(SkPMColor c0,SkPMColor c1,const int threshold)143 static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1, const int threshold) {
144     int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
145     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
146     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
147     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
148 
149     return ((SkAbs32(da) <= threshold) &&
150             (SkAbs32(dr) <= threshold) &&
151             (SkAbs32(dg) <= threshold) &&
152             (SkAbs32(db) <= threshold));
153 }
154 
155 const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
156 const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
157 
compute_diff(DiffRecord * dr,DiffMetricProc diffFunction,const int colorThreshold)158 void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold) {
159     const int w = dr->fComparison.fBitmap.width();
160     const int h = dr->fComparison.fBitmap.height();
161     if (w != dr->fBase.fBitmap.width() || h != dr->fBase.fBitmap.height()) {
162         dr->fResult = DiffRecord::kDifferentSizes_Result;
163         return;
164     }
165 
166     SkAutoLockPixels alpDiff(dr->fDifference.fBitmap);
167     SkAutoLockPixels alpWhite(dr->fWhite.fBitmap);
168     int mismatchedPixels = 0;
169     int totalMismatchA = 0;
170     int totalMismatchR = 0;
171     int totalMismatchG = 0;
172     int totalMismatchB = 0;
173 
174     // Accumulate fractionally different pixels, then divide out
175     // # of pixels at the end.
176     dr->fWeightedFraction = 0;
177     for (int y = 0; y < h; y++) {
178         for (int x = 0; x < w; x++) {
179             SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y);
180             SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y);
181             SkPMColor outputDifference = diffFunction(c0, c1);
182             uint32_t thisA = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
183             uint32_t thisR = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
184             uint32_t thisG = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
185             uint32_t thisB = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
186             totalMismatchA += thisA;
187             totalMismatchR += thisR;
188             totalMismatchG += thisG;
189             totalMismatchB += thisB;
190             // In HSV, value is defined as max RGB component.
191             int value = MAX3(thisR, thisG, thisB);
192             dr->fWeightedFraction += ((float) value) / 255;
193             if (thisA > dr->fMaxMismatchA) {
194                 dr->fMaxMismatchA = thisA;
195             }
196             if (thisR > dr->fMaxMismatchR) {
197                 dr->fMaxMismatchR = thisR;
198             }
199             if (thisG > dr->fMaxMismatchG) {
200                 dr->fMaxMismatchG = thisG;
201             }
202             if (thisB > dr->fMaxMismatchB) {
203                 dr->fMaxMismatchB = thisB;
204             }
205             if (!colors_match_thresholded(c0, c1, colorThreshold)) {
206                 mismatchedPixels++;
207                 *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference;
208                 *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE;
209             } else {
210                 *dr->fDifference.fBitmap.getAddr32(x, y) = 0;
211                 *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK;
212             }
213         }
214     }
215     if (0 == mismatchedPixels) {
216         dr->fResult = DiffRecord::kEqualPixels_Result;
217         return;
218     }
219     dr->fResult = DiffRecord::kDifferentPixels_Result;
220     int pixelCount = w * h;
221     dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
222     dr->fWeightedFraction /= pixelCount;
223     dr->fTotalMismatchA = totalMismatchA;
224     dr->fAverageMismatchA = ((float) totalMismatchA) / pixelCount;
225     dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
226     dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
227     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
228 }
229