xref: /third_party/skia/tools/skdiff/skdiff.cpp (revision cb93a386)
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 "include/core/SkBitmap.h"
9#include "include/core/SkColor.h"
10#include "include/core/SkColorPriv.h"
11#include "include/core/SkTypes.h"
12#include "tools/skdiff/skdiff.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
23DiffRecord::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
32static 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
41const 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
61DiffResource::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
70static 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
86const char* DiffResource::getStatusDescription(DiffResource::Status status) {
87    return StatusDescriptions[status];
88}
89
90bool 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
98bool 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
143static 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
155const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
156const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
157
158void 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    int mismatchedPixels = 0;
167    int totalMismatchA = 0;
168    int totalMismatchR = 0;
169    int totalMismatchG = 0;
170    int totalMismatchB = 0;
171
172    // Accumulate fractionally different pixels, then divide out
173    // # of pixels at the end.
174    dr->fWeightedFraction = 0;
175    for (int y = 0; y < h; y++) {
176        for (int x = 0; x < w; x++) {
177            SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y);
178            SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y);
179            SkPMColor outputDifference = diffFunction(c0, c1);
180            uint32_t thisA = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
181            uint32_t thisR = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
182            uint32_t thisG = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
183            uint32_t thisB = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
184            totalMismatchA += thisA;
185            totalMismatchR += thisR;
186            totalMismatchG += thisG;
187            totalMismatchB += thisB;
188            // In HSV, value is defined as max RGB component.
189            int value = MAX3(thisR, thisG, thisB);
190            dr->fWeightedFraction += ((float) value) / 255;
191            if (thisA > dr->fMaxMismatchA) {
192                dr->fMaxMismatchA = thisA;
193            }
194            if (thisR > dr->fMaxMismatchR) {
195                dr->fMaxMismatchR = thisR;
196            }
197            if (thisG > dr->fMaxMismatchG) {
198                dr->fMaxMismatchG = thisG;
199            }
200            if (thisB > dr->fMaxMismatchB) {
201                dr->fMaxMismatchB = thisB;
202            }
203            if (!colors_match_thresholded(c0, c1, colorThreshold)) {
204                mismatchedPixels++;
205                *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference;
206                *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE;
207            } else {
208                *dr->fDifference.fBitmap.getAddr32(x, y) = 0;
209                *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK;
210            }
211        }
212    }
213    if (0 == mismatchedPixels) {
214        dr->fResult = DiffRecord::kEqualPixels_Result;
215        return;
216    }
217    dr->fResult = DiffRecord::kDifferentPixels_Result;
218    int pixelCount = w * h;
219    dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
220    dr->fWeightedFraction /= pixelCount;
221    dr->fTotalMismatchA = totalMismatchA;
222    dr->fAverageMismatchA = ((float) totalMismatchA) / pixelCount;
223    dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
224    dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
225    dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
226}
227