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