blob: e508275eeb12cbc3ba0ecacd7edf282f0993bf41 [file] [log] [blame]
/*
* 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 defined(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)
, fAverageMismatchA(0)
, fAverageMismatchR(0)
, fAverageMismatchG(0)
, fAverageMismatchB(0)
, fTotalMismatchA(0)
, fMaxMismatchA(0)
, 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;
float fAverageMismatchA;
float fAverageMismatchR;
float fAverageMismatchG;
float fAverageMismatchB;
uint32_t fTotalMismatchA;
uint32_t fMaxMismatchA;
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