222 lines
7.0 KiB
C++
222 lines
7.0 KiB
C++
|
/*
|
||
|
* 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 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 trueDifference = compute_diff_pmcolor(c0, c1);
|
||
|
SkPMColor outputDifference = diffFunction(c0, c1);
|
||
|
uint32_t thisR = SkGetPackedR32(trueDifference);
|
||
|
uint32_t thisG = SkGetPackedG32(trueDifference);
|
||
|
uint32_t thisB = SkGetPackedB32(trueDifference);
|
||
|
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 (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->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
|
||
|
dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
|
||
|
dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
|
||
|
}
|