| /* | |
| * Copyright 2012 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "skdiff.h" | |
| #include "SkBitmap.h" | |
| #include "SkColor.h" | |
| #include "SkColorPriv.h" | |
| #include "SkTypes.h" | |
| /*static*/ char const * const DiffRecord::ResultNames[DiffRecord::kResultCount] = { | |
| "EqualBits", | |
| "EqualPixels", | |
| "DifferentPixels", | |
| "DifferentSizes", | |
| "CouldNotCompare", | |
| "Unknown", | |
| }; | |
| DiffRecord::Result DiffRecord::getResultByName(const char *name) { | |
| for (int result = 0; result < DiffRecord::kResultCount; ++result) { | |
| if (0 == strcmp(DiffRecord::ResultNames[result], name)) { | |
| return static_cast<DiffRecord::Result>(result); | |
| } | |
| } | |
| return DiffRecord::kResultCount; | |
| } | |
| static char const * const ResultDescriptions[DiffRecord::kResultCount] = { | |
| "contain exactly the same bits", | |
| "contain the same pixel values, but not the same bits", | |
| "have identical dimensions but some differing pixels", | |
| "have differing dimensions", | |
| "could not be compared", | |
| "not compared yet", | |
| }; | |
| const char* DiffRecord::getResultDescription(DiffRecord::Result result) { | |
| return ResultDescriptions[result]; | |
| } | |
| /*static*/ char const * const DiffResource::StatusNames[DiffResource::kStatusCount] = { | |
| "Decoded", | |
| "CouldNotDecode", | |
| "Read", | |
| "CouldNotRead", | |
| "Exists", | |
| "DoesNotExist", | |
| "Specified", | |
| "Unspecified", | |
| "Unknown", | |
| }; | |
| DiffResource::Status DiffResource::getStatusByName(const char *name) { | |
| for (int status = 0; status < DiffResource::kStatusCount; ++status) { | |
| if (0 == strcmp(DiffResource::StatusNames[status], name)) { | |
| return static_cast<DiffResource::Status>(status); | |
| } | |
| } | |
| return DiffResource::kStatusCount; | |
| } | |
| static char const * const StatusDescriptions[DiffResource::kStatusCount] = { | |
| "decoded", | |
| "could not be decoded", | |
| "read", | |
| "could not be read", | |
| "found", | |
| "not found", | |
| "specified", | |
| "unspecified", | |
| "unknown", | |
| }; | |
| const char* DiffResource::getStatusDescription(DiffResource::Status status) { | |
| return StatusDescriptions[status]; | |
| } | |
| bool DiffResource::isStatusFailed(DiffResource::Status status) { | |
| return DiffResource::kCouldNotDecode_Status == status || | |
| DiffResource::kCouldNotRead_Status == status || | |
| DiffResource::kDoesNotExist_Status == status || | |
| DiffResource::kUnspecified_Status == status || | |
| DiffResource::kUnknown_Status == status; | |
| } | |
| bool DiffResource::getMatchingStatuses(char* selector, bool statuses[kStatusCount]) { | |
| if (!strcmp(selector, "any")) { | |
| for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { | |
| statuses[statusIndex] = true; | |
| } | |
| return true; | |
| } | |
| for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { | |
| statuses[statusIndex] = false; | |
| } | |
| static const char kDelimiterChar = ','; | |
| bool understood = true; | |
| while (true) { | |
| char* delimiterPtr = strchr(selector, kDelimiterChar); | |
| if (delimiterPtr) { | |
| *delimiterPtr = '\0'; | |
| } | |
| if (!strcmp(selector, "failed")) { | |
| for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) { | |
| Status status = static_cast<Status>(statusIndex); | |
| statuses[statusIndex] |= isStatusFailed(status); | |
| } | |
| } else { | |
| Status status = getStatusByName(selector); | |
| if (status == kStatusCount) { | |
| understood = false; | |
| } else { | |
| statuses[status] = true; | |
| } | |
| } | |
| if (!delimiterPtr) { | |
| break; | |
| } | |
| *delimiterPtr = kDelimiterChar; | |
| selector = delimiterPtr + 1; | |
| } | |
| return understood; | |
| } | |
| static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1, const int threshold) { | |
| int da = SkGetPackedA32(c0) - SkGetPackedA32(c1); | |
| int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1); | |
| int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1); | |
| int db = SkGetPackedB32(c0) - SkGetPackedB32(c1); | |
| return ((SkAbs32(da) <= threshold) && | |
| (SkAbs32(dr) <= threshold) && | |
| (SkAbs32(dg) <= threshold) && | |
| (SkAbs32(db) <= threshold)); | |
| } | |
| const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE); | |
| const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK); | |
| void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold) { | |
| const int w = dr->fComparison.fBitmap.width(); | |
| const int h = dr->fComparison.fBitmap.height(); | |
| if (w != dr->fBase.fBitmap.width() || h != dr->fBase.fBitmap.height()) { | |
| dr->fResult = DiffRecord::kDifferentSizes_Result; | |
| return; | |
| } | |
| SkAutoLockPixels alpDiff(dr->fDifference.fBitmap); | |
| SkAutoLockPixels alpWhite(dr->fWhite.fBitmap); | |
| int mismatchedPixels = 0; | |
| int totalMismatchA = 0; | |
| int totalMismatchR = 0; | |
| int totalMismatchG = 0; | |
| int totalMismatchB = 0; | |
| // Accumulate fractionally different pixels, then divide out | |
| // # of pixels at the end. | |
| dr->fWeightedFraction = 0; | |
| for (int y = 0; y < h; y++) { | |
| for (int x = 0; x < w; x++) { | |
| SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y); | |
| SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y); | |
| SkPMColor outputDifference = diffFunction(c0, c1); | |
| uint32_t thisA = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1)); | |
| uint32_t thisR = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1)); | |
| uint32_t thisG = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1)); | |
| uint32_t thisB = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1)); | |
| totalMismatchA += thisA; | |
| totalMismatchR += thisR; | |
| totalMismatchG += thisG; | |
| totalMismatchB += thisB; | |
| // In HSV, value is defined as max RGB component. | |
| int value = MAX3(thisR, thisG, thisB); | |
| dr->fWeightedFraction += ((float) value) / 255; | |
| if (thisA > dr->fMaxMismatchA) { | |
| dr->fMaxMismatchA = thisA; | |
| } | |
| if (thisR > dr->fMaxMismatchR) { | |
| dr->fMaxMismatchR = thisR; | |
| } | |
| if (thisG > dr->fMaxMismatchG) { | |
| dr->fMaxMismatchG = thisG; | |
| } | |
| if (thisB > dr->fMaxMismatchB) { | |
| dr->fMaxMismatchB = thisB; | |
| } | |
| if (!colors_match_thresholded(c0, c1, colorThreshold)) { | |
| mismatchedPixels++; | |
| *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference; | |
| *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE; | |
| } else { | |
| *dr->fDifference.fBitmap.getAddr32(x, y) = 0; | |
| *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK; | |
| } | |
| } | |
| } | |
| if (0 == mismatchedPixels) { | |
| dr->fResult = DiffRecord::kEqualPixels_Result; | |
| return; | |
| } | |
| dr->fResult = DiffRecord::kDifferentPixels_Result; | |
| int pixelCount = w * h; | |
| dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount; | |
| dr->fWeightedFraction /= pixelCount; | |
| dr->fTotalMismatchA = totalMismatchA; | |
| dr->fAverageMismatchA = ((float) totalMismatchA) / pixelCount; | |
| dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount; | |
| dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount; | |
| dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount; | |
| } |