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 #ifndef skdiff_DEFINED
9 #define skdiff_DEFINED
10 
11 #include "SkBitmap.h"
12 #include "SkColor.h"
13 #include "SkColorPriv.h"
14 #include "SkString.h"
15 #include "SkTDArray.h"
16 
17 #if defined(SK_BUILD_FOR_WIN32)
18     #define PATH_DIV_STR "\\"
19     #define PATH_DIV_CHAR '\\'
20 #else
21     #define PATH_DIV_STR "/"
22     #define PATH_DIV_CHAR '/'
23 #endif
24 
25 #define MAX2(a,b) (((b) < (a)) ? (a) : (b))
26 #define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
27 
28 
29 struct DiffResource {
30     enum Status {
31         /** The resource was specified, exists, read, and decoded. */
32         kDecoded_Status,
33         /** The resource was specified, exists, read, but could not be decoded. */
34         kCouldNotDecode_Status,
35 
36         /** The resource was specified, exists, and read. */
37         kRead_Status,
38         /** The resource was specified, exists, but could not be read. */
39         kCouldNotRead_Status,
40 
41         /** The resource was specified and exists. */
42         kExists_Status,
43         /** The resource was specified, but does not exist. */
44         kDoesNotExist_Status,
45 
46         /** The resource was specified. */
47         kSpecified_Status,
48         /** The resource was not specified. */
49         kUnspecified_Status,
50 
51         /** Nothing is yet known about the resource. */
52         kUnknown_Status,
53 
54         /** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */
55         kStatusCount
56     };
57     static char const * const StatusNames[DiffResource::kStatusCount];
58 
59     /** Returns the Status with this name.
60      *  If there is no Status with this name, returns kStatusCount.
61      */
62     static Status getStatusByName(const char *name);
63 
64     /** Returns a text description of the given Status type. */
65     static const char *getStatusDescription(Status status);
66 
67     /** Returns true if the Status indicates some kind of failure. */
68     static bool isStatusFailed(Status status);
69 
70     /** Sets statuses[i] if it is implied by selector, unsets it if not.
71      *  Selector may be a comma delimited list of status names, "any", or "failed".
72      *  Returns true if the selector was entirely understood, false otherwise.
73      */
74     static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]);
75 
DiffResourceDiffResource76     DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { };
77 
78     /** If isEmpty() indicates no filename available. */
79     SkString fFilename;
80     /** If isEmpty() indicates no path available. */
81     SkString fFullPath;
82     /** If empty() indicates the bitmap could not be created. */
83     SkBitmap fBitmap;
84     Status fStatus;
85 };
86 
87 struct DiffRecord {
88 
89     // Result of comparison for each pair of files.
90     // Listed from "better" to "worse", for sorting of results.
91     enum Result {
92         kEqualBits_Result,
93         kEqualPixels_Result,
94         kDifferentPixels_Result,
95         kDifferentSizes_Result,
96         kCouldNotCompare_Result,
97         kUnknown_Result,
98 
99         kResultCount  // NOT A VALID VALUE--used to set up arrays. Must be last.
100     };
101     static char const * const ResultNames[DiffRecord::kResultCount];
102 
103     /** Returns the Result with this name.
104      *  If there is no Result with this name, returns kResultCount.
105      */
106     static Result getResultByName(const char *name);
107 
108     /** Returns a text description of the given Result type. */
109     static const char *getResultDescription(Result result);
110 
DiffRecordDiffRecord111     DiffRecord()
112         : fBase()
113         , fComparison()
114         , fDifference()
115         , fWhite()
116         , fFractionDifference(0)
117         , fWeightedFraction(0)
118         , fAverageMismatchA(0)
119         , fAverageMismatchR(0)
120         , fAverageMismatchG(0)
121         , fAverageMismatchB(0)
122         , fTotalMismatchA(0)
123         , fMaxMismatchA(0)
124         , fMaxMismatchR(0)
125         , fMaxMismatchG(0)
126         , fMaxMismatchB(0)
127         , fResult(kUnknown_Result) {
128     };
129 
130     DiffResource fBase;
131     DiffResource fComparison;
132     DiffResource fDifference;
133     DiffResource fWhite;
134 
135     /// Arbitrary floating-point metric to be used to sort images from most
136     /// to least different from baseline; values of 0 will be omitted from the
137     /// summary webpage.
138     float fFractionDifference;
139     float fWeightedFraction;
140 
141     float fAverageMismatchA;
142     float fAverageMismatchR;
143     float fAverageMismatchG;
144     float fAverageMismatchB;
145 
146     uint32_t fTotalMismatchA;
147 
148     uint32_t fMaxMismatchA;
149     uint32_t fMaxMismatchR;
150     uint32_t fMaxMismatchG;
151     uint32_t fMaxMismatchB;
152 
153     /// Which category of diff result.
154     Result fResult;
155 };
156 
157 typedef SkTDArray<DiffRecord*> RecordArray;
158 
159 /// A wrapper for any sortProc (comparison routine) which applies a first-order
160 /// sort beforehand, and a tiebreaker if the sortProc returns 0.
compare(const void * untyped_lhs,const void * untyped_rhs)161 template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) {
162     const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs);
163     const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs);
164 
165     // First-order sort... these comparisons should be applied before comparing
166     // pixel values, no matter what.
167     if (lhs->fResult != rhs->fResult) {
168         return (lhs->fResult < rhs->fResult) ? 1 : -1;
169     }
170 
171     // Passed first-order sort, so call the pixel comparison routine.
172     int result = T::comparePixels(lhs, rhs);
173     if (result != 0) {
174         return result;
175     }
176 
177     // Tiebreaker... if we got to this point, we don't really care
178     // which order they are sorted in, but let's at least be consistent.
179     return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str());
180 }
181 
182 /// Comparison routine for qsort; sorts by fFractionDifference
183 /// from largest to smallest.
184 class CompareDiffMetrics {
185 public:
comparePixels(const DiffRecord * lhs,const DiffRecord * rhs)186     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
187         if (lhs->fFractionDifference < rhs->fFractionDifference) {
188           return 1;
189         }
190         if (rhs->fFractionDifference < lhs->fFractionDifference) {
191           return -1;
192         }
193         return 0;
194     }
195 };
196 
197 class CompareDiffWeighted {
198 public:
comparePixels(const DiffRecord * lhs,const DiffRecord * rhs)199     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
200         if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
201             return 1;
202         }
203         if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
204             return -1;
205         }
206         return 0;
207     }
208 };
209 
210 /// Comparison routine for qsort;  sorts by max(fAverageMismatch{RGB})
211 /// from largest to smallest.
212 class CompareDiffMeanMismatches {
213 public:
comparePixels(const DiffRecord * lhs,const DiffRecord * rhs)214     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
215         float leftValue = MAX3(lhs->fAverageMismatchR,
216                                lhs->fAverageMismatchG,
217                                lhs->fAverageMismatchB);
218         float rightValue = MAX3(rhs->fAverageMismatchR,
219                                 rhs->fAverageMismatchG,
220                                 rhs->fAverageMismatchB);
221         if (leftValue < rightValue) {
222             return 1;
223         }
224         if (rightValue < leftValue) {
225             return -1;
226         }
227         return 0;
228     }
229 };
230 
231 /// Comparison routine for qsort;  sorts by max(fMaxMismatch{RGB})
232 /// from largest to smallest.
233 class CompareDiffMaxMismatches {
234 public:
comparePixels(const DiffRecord * lhs,const DiffRecord * rhs)235     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
236         uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
237                                   lhs->fMaxMismatchG,
238                                   lhs->fMaxMismatchB);
239         uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
240                                    rhs->fMaxMismatchG,
241                                    rhs->fMaxMismatchB);
242         if (leftValue < rightValue) {
243             return 1;
244         }
245         if (rightValue < leftValue) {
246             return -1;
247         }
248 
249         return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
250     }
251 };
252 
253 
254 /// Parameterized routine to compute the color of a pixel in a difference image.
255 typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
256 
257 // from gm
compute_diff_pmcolor(SkPMColor c0,SkPMColor c1)258 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
259     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
260     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
261     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
262 
263     return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
264 }
265 
266 /** When finished, dr->fResult should have some value other than kUnknown_Result.
267  *  Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as
268  *  dr->fBase.fBitmap and have a valid pixelref.
269  */
270 void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold);
271 
272 #endif
273