2012-12-06 00:13:12 +04:00
|
|
|
/*
|
|
|
|
* Copyright 2012 Google Inc.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
|
|
* found in the LICENSE file.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef skdiff_DEFINED
|
|
|
|
#define skdiff_DEFINED
|
|
|
|
|
|
|
|
#include "SkBitmap.h"
|
|
|
|
#include "SkColor.h"
|
|
|
|
#include "SkColorPriv.h"
|
|
|
|
#include "SkString.h"
|
|
|
|
#include "SkTDArray.h"
|
|
|
|
|
|
|
|
#if SK_BUILD_FOR_WIN32
|
|
|
|
#define PATH_DIV_STR "\\"
|
|
|
|
#define PATH_DIV_CHAR '\\'
|
|
|
|
#else
|
|
|
|
#define PATH_DIV_STR "/"
|
|
|
|
#define PATH_DIV_CHAR '/'
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
|
|
|
|
#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
|
|
|
|
|
|
|
|
|
|
|
|
struct DiffResource {
|
|
|
|
enum Status {
|
|
|
|
/** The resource was specified, exists, read, and decoded. */
|
|
|
|
kDecoded_Status,
|
|
|
|
/** The resource was specified, exists, read, but could not be decoded. */
|
|
|
|
kCouldNotDecode_Status,
|
|
|
|
|
|
|
|
/** The resource was specified, exists, and read. */
|
|
|
|
kRead_Status,
|
|
|
|
/** The resource was specified, exists, but could not be read. */
|
|
|
|
kCouldNotRead_Status,
|
|
|
|
|
|
|
|
/** The resource was specified and exists. */
|
|
|
|
kExists_Status,
|
|
|
|
/** The resource was specified, but does not exist. */
|
|
|
|
kDoesNotExist_Status,
|
|
|
|
|
|
|
|
/** The resource was specified. */
|
|
|
|
kSpecified_Status,
|
|
|
|
/** The resource was not specified. */
|
|
|
|
kUnspecified_Status,
|
|
|
|
|
|
|
|
/** Nothing is yet known about the resource. */
|
|
|
|
kUnknown_Status,
|
|
|
|
|
|
|
|
/** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */
|
|
|
|
kStatusCount
|
|
|
|
};
|
|
|
|
static char const * const StatusNames[DiffResource::kStatusCount];
|
|
|
|
|
|
|
|
/** Returns the Status with this name.
|
|
|
|
* If there is no Status with this name, returns kStatusCount.
|
|
|
|
*/
|
|
|
|
static Status getStatusByName(const char *name);
|
|
|
|
|
|
|
|
/** Returns a text description of the given Status type. */
|
|
|
|
static const char *getStatusDescription(Status status);
|
|
|
|
|
|
|
|
/** Returns true if the Status indicates some kind of failure. */
|
|
|
|
static bool isStatusFailed(Status status);
|
|
|
|
|
|
|
|
/** Sets statuses[i] if it is implied by selector, unsets it if not.
|
|
|
|
* Selector may be a comma delimited list of status names, "any", or "failed".
|
|
|
|
* Returns true if the selector was entirely understood, false otherwise.
|
|
|
|
*/
|
|
|
|
static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]);
|
|
|
|
|
|
|
|
DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { };
|
|
|
|
|
|
|
|
/** If isEmpty() indicates no filename available. */
|
|
|
|
SkString fFilename;
|
|
|
|
/** If isEmpty() indicates no path available. */
|
|
|
|
SkString fFullPath;
|
|
|
|
/** If empty() indicates the bitmap could not be created. */
|
|
|
|
SkBitmap fBitmap;
|
|
|
|
Status fStatus;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct DiffRecord {
|
|
|
|
|
|
|
|
// Result of comparison for each pair of files.
|
|
|
|
// Listed from "better" to "worse", for sorting of results.
|
|
|
|
enum Result {
|
|
|
|
kEqualBits_Result,
|
|
|
|
kEqualPixels_Result,
|
|
|
|
kDifferentPixels_Result,
|
|
|
|
kDifferentSizes_Result,
|
|
|
|
kCouldNotCompare_Result,
|
|
|
|
kUnknown_Result,
|
|
|
|
|
|
|
|
kResultCount // NOT A VALID VALUE--used to set up arrays. Must be last.
|
|
|
|
};
|
|
|
|
static char const * const ResultNames[DiffRecord::kResultCount];
|
|
|
|
|
|
|
|
/** Returns the Result with this name.
|
|
|
|
* If there is no Result with this name, returns kResultCount.
|
|
|
|
*/
|
|
|
|
static Result getResultByName(const char *name);
|
|
|
|
|
|
|
|
/** Returns a text description of the given Result type. */
|
|
|
|
static const char *getResultDescription(Result result);
|
|
|
|
|
|
|
|
DiffRecord()
|
|
|
|
: fBase()
|
|
|
|
, fComparison()
|
|
|
|
, fDifference()
|
|
|
|
, fWhite()
|
|
|
|
, fFractionDifference(0)
|
|
|
|
, fWeightedFraction(0)
|
2013-01-03 23:23:22 +04:00
|
|
|
, fAverageMismatchA(0)
|
2012-12-06 00:13:12 +04:00
|
|
|
, fAverageMismatchR(0)
|
|
|
|
, fAverageMismatchG(0)
|
|
|
|
, fAverageMismatchB(0)
|
2013-01-03 23:23:22 +04:00
|
|
|
, fTotalMismatchA(0)
|
|
|
|
, fMaxMismatchA(0)
|
2012-12-06 00:13:12 +04:00
|
|
|
, fMaxMismatchR(0)
|
|
|
|
, fMaxMismatchG(0)
|
|
|
|
, fMaxMismatchB(0)
|
|
|
|
, fResult(kUnknown_Result) {
|
|
|
|
};
|
|
|
|
|
|
|
|
DiffResource fBase;
|
|
|
|
DiffResource fComparison;
|
|
|
|
DiffResource fDifference;
|
|
|
|
DiffResource fWhite;
|
|
|
|
|
|
|
|
/// Arbitrary floating-point metric to be used to sort images from most
|
|
|
|
/// to least different from baseline; values of 0 will be omitted from the
|
|
|
|
/// summary webpage.
|
|
|
|
float fFractionDifference;
|
|
|
|
float fWeightedFraction;
|
|
|
|
|
2013-01-03 23:23:22 +04:00
|
|
|
float fAverageMismatchA;
|
2012-12-06 00:13:12 +04:00
|
|
|
float fAverageMismatchR;
|
|
|
|
float fAverageMismatchG;
|
|
|
|
float fAverageMismatchB;
|
|
|
|
|
2013-01-03 23:23:22 +04:00
|
|
|
uint32_t fTotalMismatchA;
|
|
|
|
|
|
|
|
uint32_t fMaxMismatchA;
|
2012-12-06 00:13:12 +04:00
|
|
|
uint32_t fMaxMismatchR;
|
|
|
|
uint32_t fMaxMismatchG;
|
|
|
|
uint32_t fMaxMismatchB;
|
|
|
|
|
|
|
|
/// Which category of diff result.
|
|
|
|
Result fResult;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef SkTDArray<DiffRecord*> RecordArray;
|
|
|
|
|
|
|
|
/// A wrapper for any sortProc (comparison routine) which applies a first-order
|
|
|
|
/// sort beforehand, and a tiebreaker if the sortProc returns 0.
|
|
|
|
template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) {
|
|
|
|
const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs);
|
|
|
|
const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs);
|
|
|
|
|
|
|
|
// First-order sort... these comparisons should be applied before comparing
|
|
|
|
// pixel values, no matter what.
|
|
|
|
if (lhs->fResult != rhs->fResult) {
|
|
|
|
return (lhs->fResult < rhs->fResult) ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Passed first-order sort, so call the pixel comparison routine.
|
|
|
|
int result = T::comparePixels(lhs, rhs);
|
|
|
|
if (result != 0) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tiebreaker... if we got to this point, we don't really care
|
|
|
|
// which order they are sorted in, but let's at least be consistent.
|
|
|
|
return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Comparison routine for qsort; sorts by fFractionDifference
|
|
|
|
/// from largest to smallest.
|
|
|
|
class CompareDiffMetrics {
|
|
|
|
public:
|
|
|
|
static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
|
|
|
|
if (lhs->fFractionDifference < rhs->fFractionDifference) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (rhs->fFractionDifference < lhs->fFractionDifference) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class CompareDiffWeighted {
|
|
|
|
public:
|
|
|
|
static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
|
|
|
|
if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
|
|
|
|
/// from largest to smallest.
|
|
|
|
class CompareDiffMeanMismatches {
|
|
|
|
public:
|
|
|
|
static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
|
|
|
|
float leftValue = MAX3(lhs->fAverageMismatchR,
|
|
|
|
lhs->fAverageMismatchG,
|
|
|
|
lhs->fAverageMismatchB);
|
|
|
|
float rightValue = MAX3(rhs->fAverageMismatchR,
|
|
|
|
rhs->fAverageMismatchG,
|
|
|
|
rhs->fAverageMismatchB);
|
|
|
|
if (leftValue < rightValue) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (rightValue < leftValue) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
|
|
|
|
/// from largest to smallest.
|
|
|
|
class CompareDiffMaxMismatches {
|
|
|
|
public:
|
|
|
|
static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
|
|
|
|
uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
|
|
|
|
lhs->fMaxMismatchG,
|
|
|
|
lhs->fMaxMismatchB);
|
|
|
|
uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
|
|
|
|
rhs->fMaxMismatchG,
|
|
|
|
rhs->fMaxMismatchB);
|
|
|
|
if (leftValue < rightValue) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (rightValue < leftValue) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// Parameterized routine to compute the color of a pixel in a difference image.
|
|
|
|
typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
|
|
|
|
|
|
|
|
// from gm
|
|
|
|
static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
|
|
|
|
int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
|
|
|
|
int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
|
|
|
|
int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
|
|
|
|
|
|
|
|
return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** When finished, dr->fResult should have some value other than kUnknown_Result.
|
|
|
|
* Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as
|
|
|
|
* dr->fBase.fBitmap and have a valid pixelref.
|
|
|
|
*/
|
|
|
|
void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold);
|
|
|
|
|
|
|
|
#endif
|