2011-04-27 19:39:30 +04:00
|
|
|
#include "SkColorPriv.h"
|
|
|
|
#include "SkImageDecoder.h"
|
|
|
|
#include "SkImageEncoder.h"
|
|
|
|
#include "SkOSFile.h"
|
|
|
|
#include "SkStream.h"
|
|
|
|
#include "SkTDArray.h"
|
|
|
|
#include "SkTemplates.h"
|
|
|
|
#include "SkTime.h"
|
|
|
|
#include "SkTSearch.h"
|
|
|
|
#include "SkTypes.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* skdiff
|
|
|
|
*
|
|
|
|
* Given three directory names, expects to find identically-named files in
|
|
|
|
* each of the first two; the first are treated as a set of baseline,
|
|
|
|
* the second a set of variant images, and a diff image is written into the
|
|
|
|
* third directory for each pair.
|
2011-07-14 17:15:55 +04:00
|
|
|
* Creates an index.html in the current third directory to compare each
|
2011-04-27 19:39:30 +04:00
|
|
|
* pair that does not match exactly.
|
|
|
|
* Does *not* recursively descend directories.
|
2011-07-14 17:15:55 +04:00
|
|
|
*
|
|
|
|
* With the --chromium flag, *does* recursively descend the first directory
|
|
|
|
* named, comparing *-expected.png with *-actual.png and writing diff
|
|
|
|
* images into the second directory, also writing index.html there.
|
2011-04-27 19:39:30 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct DiffRecord {
|
2011-07-13 21:42:46 +04:00
|
|
|
DiffRecord (const SkString filename,
|
|
|
|
const SkString basePath,
|
|
|
|
const SkString comparisonPath)
|
2011-04-27 19:39:30 +04:00
|
|
|
: fFilename (filename)
|
2011-07-13 21:42:46 +04:00
|
|
|
, fBasePath (basePath)
|
|
|
|
, fComparisonPath (comparisonPath)
|
2011-06-09 19:47:10 +04:00
|
|
|
, fFractionDifference (0)
|
|
|
|
, fWeightedFraction (0)
|
2011-04-27 19:39:30 +04:00
|
|
|
, fAverageMismatchR (0)
|
|
|
|
, fAverageMismatchG (0)
|
|
|
|
, fAverageMismatchB (0)
|
|
|
|
, fMaxMismatchR (0)
|
|
|
|
, fMaxMismatchG (0)
|
2011-07-13 21:42:46 +04:00
|
|
|
, fMaxMismatchB (0) {
|
2011-07-14 17:15:55 +04:00
|
|
|
// These asserts are valid for GM, but not for --chromium
|
|
|
|
//SkASSERT(basePath.endsWith(filename.c_str()));
|
|
|
|
//SkASSERT(comparisonPath.endsWith(filename.c_str()));
|
2011-07-13 21:42:46 +04:00
|
|
|
};
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
SkString fFilename;
|
2011-07-13 21:42:46 +04:00
|
|
|
SkString fBasePath;
|
|
|
|
SkString fComparisonPath;
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
SkBitmap fBaseBitmap;
|
|
|
|
SkBitmap fComparisonBitmap;
|
|
|
|
SkBitmap fDifferenceBitmap;
|
|
|
|
|
|
|
|
/// 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.
|
2011-06-09 19:47:10 +04:00
|
|
|
float fFractionDifference;
|
|
|
|
float fWeightedFraction;
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
float fAverageMismatchR;
|
|
|
|
float fAverageMismatchG;
|
|
|
|
float fAverageMismatchB;
|
|
|
|
|
|
|
|
uint32_t fMaxMismatchR;
|
|
|
|
uint32_t fMaxMismatchG;
|
|
|
|
uint32_t fMaxMismatchB;
|
|
|
|
};
|
|
|
|
|
2011-06-09 19:47:10 +04:00
|
|
|
#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
|
|
|
|
#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
|
|
|
|
|
|
|
|
struct DiffSummary {
|
|
|
|
DiffSummary ()
|
|
|
|
: fNumMatches (0)
|
|
|
|
, fNumMismatches (0)
|
|
|
|
, fMaxMismatchV (0)
|
|
|
|
, fMaxMismatchPercent (0) { };
|
|
|
|
|
|
|
|
uint32_t fNumMatches;
|
|
|
|
uint32_t fNumMismatches;
|
|
|
|
uint32_t fMaxMismatchV;
|
|
|
|
float fMaxMismatchPercent;
|
|
|
|
|
|
|
|
void print () {
|
|
|
|
printf("%d of %d images matched.\n", fNumMatches,
|
|
|
|
fNumMatches + fNumMismatches);
|
|
|
|
if (fNumMismatches > 0) {
|
|
|
|
printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
|
|
|
|
printf("Largest area mismatch was %.2f%% of pixels\n",
|
|
|
|
fMaxMismatchPercent);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void add (DiffRecord* drp) {
|
|
|
|
if (0 == drp->fFractionDifference) {
|
|
|
|
fNumMatches++;
|
|
|
|
} else {
|
|
|
|
fNumMismatches++;
|
|
|
|
if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
|
|
|
|
fMaxMismatchPercent = drp->fFractionDifference * 100;
|
|
|
|
}
|
2011-06-09 22:54:01 +04:00
|
|
|
uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
|
|
|
|
drp->fMaxMismatchB);
|
2011-06-09 19:47:10 +04:00
|
|
|
if (value > fMaxMismatchV) {
|
|
|
|
fMaxMismatchV = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-04-27 19:39:30 +04:00
|
|
|
typedef SkTDArray<DiffRecord*> RecordArray;
|
|
|
|
|
2011-06-09 19:47:10 +04:00
|
|
|
/// Comparison routine for qsort; sorts by fFractionDifference
|
2011-04-27 19:39:30 +04:00
|
|
|
/// from largest to smallest.
|
|
|
|
static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
|
2011-06-09 19:47:10 +04:00
|
|
|
if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
|
2011-04-27 19:39:30 +04:00
|
|
|
return 1;
|
|
|
|
}
|
2011-06-09 19:47:10 +04:00
|
|
|
if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
|
2011-05-24 23:41:13 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
|
2011-06-09 19:47:10 +04:00
|
|
|
if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
|
2011-05-24 23:41:13 +04:00
|
|
|
return 1;
|
|
|
|
}
|
2011-06-09 19:47:10 +04:00
|
|
|
if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
|
2011-04-27 19:39:30 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
|
|
|
|
/// from largest to smallest.
|
|
|
|
static int compare_diff_mean_mismatches (DiffRecord** lhs, 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.
|
|
|
|
static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
|
|
|
|
float leftValue = MAX3((*lhs)->fMaxMismatchR,
|
|
|
|
(*lhs)->fMaxMismatchG,
|
|
|
|
(*lhs)->fMaxMismatchB);
|
|
|
|
float rightValue = MAX3((*rhs)->fMaxMismatchR,
|
|
|
|
(*rhs)->fMaxMismatchG,
|
|
|
|
(*rhs)->fMaxMismatchB);
|
|
|
|
if (leftValue < rightValue) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (rightValue < leftValue) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return compare_diff_mean_mismatches(lhs, rhs);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Parameterized routine to compute the color of a pixel in a difference image.
|
|
|
|
typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
|
|
|
|
|
2011-07-13 21:42:46 +04:00
|
|
|
static bool get_bitmaps (DiffRecord* diffRecord) {
|
|
|
|
SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
if (!compareStream.isValid()) {
|
|
|
|
SkDebugf("WARNING: couldn't open comparison file <%s>\n",
|
2011-07-13 21:42:46 +04:00
|
|
|
diffRecord->fComparisonPath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-07-13 21:42:46 +04:00
|
|
|
SkFILEStream baseStream(diffRecord->fBasePath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
if (!baseStream.isValid()) {
|
|
|
|
SkDebugf("ERROR: couldn't open base file <%s>\n",
|
2011-07-13 21:42:46 +04:00
|
|
|
diffRecord->fBasePath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
|
|
|
|
if (NULL == codec) {
|
|
|
|
SkDebugf("ERROR: no codec found for <%s>\n",
|
2011-07-13 21:42:46 +04:00
|
|
|
diffRecord->fBasePath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkAutoTDelete<SkImageDecoder> ad(codec);
|
|
|
|
|
|
|
|
baseStream.rewind();
|
|
|
|
if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
|
|
|
|
SkBitmap::kARGB_8888_Config,
|
|
|
|
SkImageDecoder::kDecodePixels_Mode)) {
|
|
|
|
SkDebugf("ERROR: codec failed for <%s>\n",
|
2011-07-13 21:42:46 +04:00
|
|
|
diffRecord->fBasePath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
|
|
|
|
SkBitmap::kARGB_8888_Config,
|
|
|
|
SkImageDecoder::kDecodePixels_Mode)) {
|
|
|
|
SkDebugf("ERROR: codec failed for <%s>\n",
|
2011-07-13 21:42:46 +04:00
|
|
|
diffRecord->fComparisonPath.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// from gm - thanks to PNG, we need to force all pixels 100% opaque
|
|
|
|
static void force_all_opaque(const SkBitmap& bitmap) {
|
|
|
|
SkAutoLockPixels lock(bitmap);
|
|
|
|
for (int y = 0; y < bitmap.height(); y++) {
|
|
|
|
for (int x = 0; x < bitmap.width(); x++) {
|
|
|
|
*bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// from gm
|
|
|
|
static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
|
|
|
|
SkBitmap copy;
|
|
|
|
bitmap.copyTo(©, SkBitmap::kARGB_8888_Config);
|
|
|
|
force_all_opaque(copy);
|
|
|
|
return SkImageEncoder::EncodeFile(path.c_str(), copy,
|
|
|
|
SkImageEncoder::kPNG_Type, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns white on every pixel so that differences jump out at you;
|
|
|
|
/// makes it easy to spot areas of difference that are in the least-significant
|
|
|
|
/// bits.
|
|
|
|
static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
|
|
|
|
return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
// based on gm
|
|
|
|
static void compute_diff(DiffRecord* dr,
|
|
|
|
DiffMetricProc diffFunction,
|
|
|
|
const int colorThreshold) {
|
|
|
|
SkAutoLockPixels alp(dr->fDifferenceBitmap);
|
|
|
|
|
|
|
|
const int w = dr->fComparisonBitmap.width();
|
|
|
|
const int h = dr->fComparisonBitmap.height();
|
|
|
|
int mismatchedPixels = 0;
|
|
|
|
int totalMismatchR = 0;
|
|
|
|
int totalMismatchG = 0;
|
|
|
|
int totalMismatchB = 0;
|
2011-05-24 23:41:13 +04:00
|
|
|
// Accumulate fractionally different pixels, then divide out
|
|
|
|
// # of pixels at the end.
|
2011-06-09 19:47:10 +04:00
|
|
|
dr->fWeightedFraction = 0;
|
2011-04-27 19:39:30 +04:00
|
|
|
for (int y = 0; y < h; y++) {
|
|
|
|
for (int x = 0; x < w; x++) {
|
|
|
|
SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
|
|
|
|
SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
|
2011-06-09 19:47:10 +04:00
|
|
|
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);
|
2011-05-24 23:41:13 +04:00
|
|
|
totalMismatchR += thisR;
|
|
|
|
totalMismatchG += thisG;
|
|
|
|
totalMismatchB += thisB;
|
|
|
|
// In HSV, value is defined as max RGB component.
|
|
|
|
int value = MAX3(thisR, thisG, thisB);
|
2011-06-09 19:47:10 +04:00
|
|
|
dr->fWeightedFraction += ((float) value) / 255;
|
2011-05-24 23:41:13 +04:00
|
|
|
if (thisR > dr->fMaxMismatchR) {
|
|
|
|
dr->fMaxMismatchR = thisR;
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
2011-05-24 23:41:13 +04:00
|
|
|
if (thisG > dr->fMaxMismatchG) {
|
|
|
|
dr->fMaxMismatchG = thisG;
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
2011-05-24 23:41:13 +04:00
|
|
|
if (thisB > dr->fMaxMismatchB) {
|
|
|
|
dr->fMaxMismatchB = thisB;
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
if (!colors_match_thresholded(c0, c1, colorThreshold)) {
|
|
|
|
mismatchedPixels++;
|
2011-06-09 19:47:10 +04:00
|
|
|
*dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
|
2011-04-27 19:39:30 +04:00
|
|
|
} else {
|
|
|
|
*dr->fDifferenceBitmap.getAddr32(x, y) = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-05-24 23:41:13 +04:00
|
|
|
int pixelCount = w * h;
|
2011-06-09 19:47:10 +04:00
|
|
|
dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
|
|
|
|
dr->fWeightedFraction /= pixelCount;
|
2011-05-24 23:41:13 +04:00
|
|
|
dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
|
|
|
|
dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
|
|
|
|
dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
|
2011-06-09 19:47:10 +04:00
|
|
|
/// Given a image filename, returns the name of the file containing the
|
|
|
|
/// associated difference image.
|
|
|
|
static SkString filename_to_diff_filename (const SkString& filename) {
|
|
|
|
SkString diffName (filename);
|
|
|
|
const char* cstring = diffName.c_str();
|
|
|
|
int dotOffset = strrchr(cstring, '.') - cstring;
|
|
|
|
diffName.remove(dotOffset, diffName.size() - dotOffset);
|
|
|
|
diffName.append("-diff.png");
|
|
|
|
return diffName;
|
|
|
|
}
|
|
|
|
|
2011-07-14 17:15:55 +04:00
|
|
|
/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
|
|
|
|
static SkString chrome_expected_path_to_actual (const SkString& expected) {
|
|
|
|
SkString actualPath (expected);
|
|
|
|
actualPath.remove(actualPath.size() - 13, 13);
|
|
|
|
actualPath.append("-actual.png");
|
|
|
|
return actualPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
|
|
|
|
static SkString chrome_expected_name_to_short (const SkString& expected) {
|
|
|
|
SkString shortName (expected);
|
|
|
|
shortName.remove(shortName.size() - 13, 13);
|
|
|
|
shortName.append(".png");
|
|
|
|
return shortName;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void create_and_write_diff_image(DiffRecord* drp,
|
|
|
|
DiffMetricProc dmp,
|
|
|
|
const int colorThreshold,
|
|
|
|
const SkString& outputDir,
|
|
|
|
const SkString& filename) {
|
2011-07-13 21:42:46 +04:00
|
|
|
const int w = drp->fBaseBitmap.width();
|
|
|
|
const int h = drp->fBaseBitmap.height();
|
|
|
|
drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
|
|
|
|
drp->fDifferenceBitmap.allocPixels();
|
|
|
|
compute_diff(drp, dmp, colorThreshold);
|
2011-07-14 17:15:55 +04:00
|
|
|
|
|
|
|
SkString outPath (outputDir);
|
|
|
|
outPath.append(filename_to_diff_filename(filename));
|
|
|
|
write_bitmap(outPath, drp->fDifferenceBitmap);
|
2011-07-13 21:42:46 +04:00
|
|
|
}
|
|
|
|
|
2011-04-27 19:39:30 +04:00
|
|
|
/// Creates difference images, returns the number that have a 0 metric.
|
2011-06-09 19:47:10 +04:00
|
|
|
static void create_diff_images (DiffMetricProc dmp,
|
|
|
|
const int colorThreshold,
|
|
|
|
RecordArray* differences,
|
|
|
|
const SkString& baseDir,
|
|
|
|
const SkString& comparisonDir,
|
|
|
|
const SkString& outputDir,
|
|
|
|
DiffSummary* summary) {
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
//@todo thudson 19 Apr 2011
|
|
|
|
// this lets us know about files in baseDir not in compareDir, but it
|
|
|
|
// doesn't detect files in compareDir not in baseDir. Doing that
|
|
|
|
// efficiently seems to imply iterating through both directories to
|
|
|
|
// create a merged list, and then attempting to process every entry
|
|
|
|
// in that list?
|
|
|
|
|
|
|
|
SkOSFile::Iter baseIterator (baseDir.c_str());
|
|
|
|
SkString filename;
|
|
|
|
while (baseIterator.next(&filename)) {
|
2011-05-24 23:41:13 +04:00
|
|
|
if (filename.endsWith(".pdf")) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-07-13 21:42:46 +04:00
|
|
|
SkString basePath (baseDir);
|
|
|
|
basePath.append(filename);
|
2011-07-14 17:15:55 +04:00
|
|
|
SkString comparisonPath (comparisonDir);
|
2011-07-13 21:42:46 +04:00
|
|
|
comparisonPath.append(filename);
|
|
|
|
DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
|
|
|
|
if (!get_bitmaps(drp)) {
|
2011-04-27 19:39:30 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-07-14 17:15:55 +04:00
|
|
|
create_and_write_diff_image(drp, dmp, colorThreshold,
|
|
|
|
outputDir, filename);
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
differences->push(drp);
|
2011-06-09 19:47:10 +04:00
|
|
|
summary->add(drp);
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
}
|
2011-04-27 19:39:30 +04:00
|
|
|
|
2011-07-14 17:15:55 +04:00
|
|
|
static void create_diff_images_chromium (DiffMetricProc dmp,
|
|
|
|
const int colorThreshold,
|
|
|
|
RecordArray* differences,
|
|
|
|
const SkString& dirname,
|
|
|
|
const SkString& outputDir,
|
|
|
|
DiffSummary* summary) {
|
|
|
|
SkOSFile::Iter baseIterator (dirname.c_str());
|
|
|
|
SkString filename;
|
|
|
|
while (baseIterator.next(&filename)) {
|
|
|
|
if (filename.endsWith(".pdf")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (filename.endsWith("-expected.png")) {
|
|
|
|
SkString expectedPath (dirname);
|
|
|
|
expectedPath.append(filename);
|
|
|
|
SkString shortName (chrome_expected_name_to_short(filename));
|
|
|
|
SkString actualPath (chrome_expected_path_to_actual(expectedPath));
|
|
|
|
DiffRecord * drp =
|
|
|
|
new DiffRecord (shortName, expectedPath, actualPath);
|
|
|
|
if (!get_bitmaps(drp)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
create_and_write_diff_image(drp, dmp, colorThreshold,
|
|
|
|
outputDir, shortName);
|
|
|
|
|
|
|
|
differences->push(drp);
|
|
|
|
summary->add(drp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void analyze_chromium(DiffMetricProc dmp,
|
|
|
|
const int colorThreshold,
|
|
|
|
RecordArray* differences,
|
|
|
|
const SkString& dirname,
|
|
|
|
const SkString& outputDir,
|
|
|
|
DiffSummary* summary) {
|
|
|
|
create_diff_images_chromium(dmp, colorThreshold, differences,
|
|
|
|
dirname, outputDir, summary);
|
|
|
|
SkOSFile::Iter dirIterator(dirname.c_str());
|
|
|
|
SkString newdirname;
|
|
|
|
while (dirIterator.next(&newdirname, true)) {
|
|
|
|
if (newdirname.startsWith(".")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
SkString fullname (dirname);
|
|
|
|
fullname.append(newdirname);
|
|
|
|
if (!fullname.endsWith("/")) {
|
|
|
|
fullname.append("/");
|
|
|
|
}
|
|
|
|
analyze_chromium(dmp, colorThreshold, differences,
|
|
|
|
fullname, outputDir, summary);
|
|
|
|
}
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Make layout more consistent by scaling image to 240 height, 360 width,
|
|
|
|
/// or natural size, whichever is smallest.
|
|
|
|
static int compute_image_height (const SkBitmap& bmp) {
|
|
|
|
int retval = 240;
|
|
|
|
if (bmp.height() < retval) {
|
|
|
|
retval = bmp.height();
|
|
|
|
}
|
|
|
|
float scale = (float) retval / bmp.height();
|
|
|
|
if (bmp.width() * scale > 360) {
|
|
|
|
scale = (float) 360 / bmp.width();
|
|
|
|
retval = bmp.height() * scale;
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_page_header (SkFILEWStream* stream,
|
|
|
|
const int matchCount,
|
|
|
|
const int colorThreshold,
|
|
|
|
const RecordArray& differences) {
|
|
|
|
SkTime::DateTime dt;
|
|
|
|
SkTime::GetDateTime(&dt);
|
|
|
|
stream->writeText("SkDiff run at ");
|
|
|
|
stream->writeDecAsText(dt.fHour);
|
|
|
|
stream->writeText(":");
|
|
|
|
if (dt.fMinute < 10) {
|
|
|
|
stream->writeText("0");
|
|
|
|
}
|
|
|
|
stream->writeDecAsText(dt.fMinute);
|
|
|
|
stream->writeText(":");
|
|
|
|
if (dt.fSecond < 10) {
|
|
|
|
stream->writeText("0");
|
|
|
|
}
|
|
|
|
stream->writeDecAsText(dt.fSecond);
|
|
|
|
stream->writeText("<br>");
|
|
|
|
stream->writeDecAsText(matchCount);
|
|
|
|
stream->writeText(" of ");
|
|
|
|
stream->writeDecAsText(differences.count());
|
|
|
|
stream->writeText(" images matched ");
|
|
|
|
if (colorThreshold == 0) {
|
|
|
|
stream->writeText("exactly");
|
|
|
|
} else {
|
|
|
|
stream->writeText("within ");
|
|
|
|
stream->writeDecAsText(colorThreshold);
|
|
|
|
stream->writeText(" color units per component");
|
|
|
|
}
|
|
|
|
stream->writeText(".<br>");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_pixel_count (SkFILEWStream* stream,
|
|
|
|
const DiffRecord& diff) {
|
|
|
|
stream->writeText("<br>(");
|
2011-06-09 19:47:10 +04:00
|
|
|
stream->writeDecAsText(diff.fFractionDifference *
|
2011-04-27 19:39:30 +04:00
|
|
|
diff.fBaseBitmap.width() *
|
|
|
|
diff.fBaseBitmap.height());
|
|
|
|
stream->writeText(" pixels)");
|
2011-05-24 23:41:13 +04:00
|
|
|
/*
|
2011-06-09 19:47:10 +04:00
|
|
|
stream->writeDecAsText(diff.fWeightedFraction *
|
2011-05-24 23:41:13 +04:00
|
|
|
diff.fBaseBitmap.width() *
|
|
|
|
diff.fBaseBitmap.height());
|
|
|
|
stream->writeText(" weighted pixels)");
|
|
|
|
*/
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void print_label_cell (SkFILEWStream* stream,
|
|
|
|
const DiffRecord& diff) {
|
|
|
|
stream->writeText("<td>");
|
|
|
|
stream->writeText(diff.fFilename.c_str());
|
|
|
|
stream->writeText("<br>");
|
|
|
|
char metricBuf [20];
|
2011-06-09 19:47:10 +04:00
|
|
|
sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
|
2011-04-27 19:39:30 +04:00
|
|
|
stream->writeText(metricBuf);
|
|
|
|
stream->writeText(" of pixels differ");
|
2011-05-24 23:41:13 +04:00
|
|
|
stream->writeText("\n (");
|
2011-06-09 19:47:10 +04:00
|
|
|
sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
|
2011-05-24 23:41:13 +04:00
|
|
|
stream->writeText(metricBuf);
|
|
|
|
stream->writeText(" weighted)");
|
2011-04-27 19:39:30 +04:00
|
|
|
// Write the actual number of pixels that differ if it's < 1%
|
2011-06-09 19:47:10 +04:00
|
|
|
if (diff.fFractionDifference < 0.01) {
|
2011-04-27 19:39:30 +04:00
|
|
|
print_pixel_count(stream, diff);
|
|
|
|
}
|
|
|
|
stream->writeText("<br>Average color mismatch ");
|
|
|
|
stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
|
|
|
|
diff.fAverageMismatchG,
|
|
|
|
diff.fAverageMismatchB));
|
|
|
|
stream->writeText("<br>Max color mismatch ");
|
|
|
|
stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
|
|
|
|
diff.fMaxMismatchG,
|
|
|
|
diff.fMaxMismatchB));
|
|
|
|
stream->writeText("</td>");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_image_cell (SkFILEWStream* stream,
|
2011-07-13 21:42:46 +04:00
|
|
|
const SkString& path,
|
2011-04-27 19:39:30 +04:00
|
|
|
int height) {
|
|
|
|
stream->writeText("<td><a href=\"");
|
2011-07-13 21:42:46 +04:00
|
|
|
stream->writeText(path.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
stream->writeText("\"><img src=\"");
|
2011-07-13 21:42:46 +04:00
|
|
|
stream->writeText(path.c_str());
|
2011-04-27 19:39:30 +04:00
|
|
|
stream->writeText("\" height=\"");
|
|
|
|
stream->writeDecAsText(height);
|
|
|
|
stream->writeText("px\"></a></td>");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_diff_page (const int matchCount,
|
|
|
|
const int colorThreshold,
|
|
|
|
const RecordArray& differences,
|
|
|
|
const SkString& baseDir,
|
|
|
|
const SkString& comparisonDir,
|
|
|
|
const SkString& outputDir) {
|
|
|
|
|
2011-05-24 23:41:13 +04:00
|
|
|
SkString outputPath (outputDir);
|
|
|
|
outputPath.append("index.html");
|
|
|
|
//SkFILEWStream outputStream ("index.html");
|
|
|
|
SkFILEWStream outputStream (outputPath.c_str());
|
|
|
|
|
2011-07-13 21:42:46 +04:00
|
|
|
// Need to convert paths from relative-to-cwd to relative-to-outputDir
|
2011-05-24 23:41:13 +04:00
|
|
|
// FIXME this doesn't work if there are '..' inside the outputDir
|
|
|
|
unsigned int ui;
|
|
|
|
SkString relativePath;
|
|
|
|
for (ui = 0; ui < outputDir.size(); ui++) {
|
|
|
|
if (outputDir[ui] == '/') {
|
|
|
|
relativePath.append("../");
|
|
|
|
}
|
|
|
|
}
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
outputStream.writeText("<html>\n<body>\n");
|
|
|
|
print_page_header(&outputStream, matchCount, colorThreshold, differences);
|
|
|
|
|
|
|
|
outputStream.writeText("<table>\n");
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < differences.count(); i++) {
|
|
|
|
DiffRecord* diff = differences[i];
|
2011-06-09 19:47:10 +04:00
|
|
|
if (0 == diff->fFractionDifference) {
|
2011-04-27 19:39:30 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-07-13 21:42:46 +04:00
|
|
|
if (!diff->fBasePath.startsWith("/")) {
|
|
|
|
diff->fBasePath.prepend(relativePath);
|
|
|
|
}
|
|
|
|
if (!diff->fComparisonPath.startsWith("/")) {
|
|
|
|
diff->fComparisonPath.prepend(relativePath);
|
|
|
|
}
|
2011-04-27 19:39:30 +04:00
|
|
|
int height = compute_image_height(diff->fBaseBitmap);
|
|
|
|
outputStream.writeText("<tr>\n");
|
|
|
|
print_label_cell(&outputStream, *diff);
|
2011-07-13 21:42:46 +04:00
|
|
|
print_image_cell(&outputStream, diff->fBasePath, height);
|
|
|
|
print_image_cell(&outputStream,
|
2011-06-09 19:47:10 +04:00
|
|
|
filename_to_diff_filename(diff->fFilename), height);
|
2011-07-13 21:42:46 +04:00
|
|
|
print_image_cell(&outputStream, diff->fComparisonPath, height);
|
2011-04-27 19:39:30 +04:00
|
|
|
outputStream.writeText("</tr>\n");
|
|
|
|
outputStream.flush();
|
|
|
|
}
|
|
|
|
outputStream.writeText("</table>\n");
|
|
|
|
outputStream.writeText("</body>\n</html>\n");
|
|
|
|
outputStream.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void usage (char * argv0) {
|
|
|
|
SkDebugf("Skia baseline image diff tool\n");
|
2011-06-09 19:47:10 +04:00
|
|
|
SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
|
2011-04-27 19:39:30 +04:00
|
|
|
SkDebugf(
|
2011-07-14 17:15:55 +04:00
|
|
|
" %s --chromium --release|--debug baseDir outputDir\n", argv0);
|
|
|
|
SkDebugf(
|
|
|
|
" --white: force all difference pixels to white\n"
|
|
|
|
" --threshold n: only report differences > n (in one channel) [default 0]\n"
|
|
|
|
" --sortbymismatch: sort by average color channel mismatch\n");
|
2011-04-27 19:39:30 +04:00
|
|
|
SkDebugf(
|
2011-07-14 17:15:55 +04:00
|
|
|
" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
|
2011-04-27 19:39:30 +04:00
|
|
|
" break ties with -sortbymismatch,\n"
|
|
|
|
" [default by fraction of pixels mismatching]\n");
|
2011-05-24 23:41:13 +04:00
|
|
|
SkDebugf(
|
2011-07-14 17:15:55 +04:00
|
|
|
" --weighted: sort by # pixels different weighted by color difference\n");
|
|
|
|
SkDebugf(
|
|
|
|
" --chromium-release: process Webkit LayoutTests results instead of gm\n"
|
|
|
|
" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
|
|
|
|
SkDebugf(
|
|
|
|
" baseDir: directory to read baseline images from,\n"
|
|
|
|
" or chromium/src directory for --chromium.\n");
|
2011-04-27 19:39:30 +04:00
|
|
|
SkDebugf(" comparisonDir: directory to read comparison images from\n");
|
2011-06-09 19:47:10 +04:00
|
|
|
SkDebugf(
|
|
|
|
" outputDir: directory to write difference images to; defaults to\n"
|
2011-07-14 17:15:55 +04:00
|
|
|
" comparisonDir when not running --chromium\n");
|
|
|
|
SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int main (int argc, char ** argv) {
|
|
|
|
DiffMetricProc diffProc = compute_diff_pmcolor;
|
|
|
|
SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
|
|
|
|
|
|
|
|
// Maximum error tolerated in any one color channel in any one pixel before
|
|
|
|
// a difference is reported.
|
|
|
|
int colorThreshold = 0;
|
|
|
|
SkString baseDir;
|
|
|
|
SkString comparisonDir;
|
|
|
|
SkString outputDir;
|
|
|
|
|
2011-07-14 17:15:55 +04:00
|
|
|
bool analyzeChromium = false;
|
|
|
|
bool chromiumDebug = false;
|
|
|
|
bool chromiumRelease = false;
|
|
|
|
|
2011-04-27 19:39:30 +04:00
|
|
|
RecordArray differences;
|
2011-06-09 19:47:10 +04:00
|
|
|
DiffSummary summary;
|
2011-04-27 19:39:30 +04:00
|
|
|
|
|
|
|
int i, j;
|
|
|
|
for (i = 1, j = 0; i < argc; i++) {
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--help")) {
|
2011-04-27 19:39:30 +04:00
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--white")) {
|
2011-04-27 19:39:30 +04:00
|
|
|
diffProc = compute_diff_white;
|
|
|
|
continue;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--sortbymismatch")) {
|
2011-04-27 19:39:30 +04:00
|
|
|
sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
|
|
|
|
continue;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--sortbymaxmismatch")) {
|
2011-04-27 19:39:30 +04:00
|
|
|
sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
|
|
|
|
continue;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--weighted")) {
|
2011-05-24 23:41:13 +04:00
|
|
|
sortProc = (SkQSortCompareProc) compare_diff_weighted;
|
|
|
|
continue;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (!strcmp(argv[i], "--chromium-release")) {
|
|
|
|
analyzeChromium = true;
|
|
|
|
chromiumRelease = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(argv[i], "--chromium-debug")) {
|
|
|
|
analyzeChromium = true;
|
|
|
|
chromiumDebug = true;
|
2011-04-27 19:39:30 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (argv[i][0] != '-') {
|
|
|
|
switch (j++) {
|
|
|
|
case 0:
|
|
|
|
baseDir.set(argv[i]);
|
|
|
|
continue;
|
|
|
|
case 1:
|
|
|
|
comparisonDir.set(argv[i]);
|
|
|
|
continue;
|
|
|
|
case 2:
|
|
|
|
outputDir.set(argv[i]);
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SkDebugf("Unrecognized argument <%s>\n", argv[i]);
|
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
2011-07-14 17:15:55 +04:00
|
|
|
if (analyzeChromium) {
|
|
|
|
if (j != 2) {
|
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (chromiumRelease && chromiumDebug) {
|
|
|
|
SkDebugf(
|
|
|
|
"--chromium must be either -release or -debug, not both!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-09 19:47:10 +04:00
|
|
|
if (j == 2) {
|
|
|
|
outputDir = comparisonDir;
|
|
|
|
} else if (j != 3) {
|
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
2011-04-27 19:39:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!baseDir.endsWith("/")) {
|
|
|
|
baseDir.append("/");
|
|
|
|
}
|
|
|
|
if (!comparisonDir.endsWith("/")) {
|
|
|
|
comparisonDir.append("/");
|
|
|
|
}
|
|
|
|
if (!outputDir.endsWith("/")) {
|
|
|
|
outputDir.append("/");
|
|
|
|
}
|
|
|
|
|
2011-07-14 17:15:55 +04:00
|
|
|
if (analyzeChromium) {
|
|
|
|
baseDir.append("webkit/");
|
|
|
|
if (chromiumRelease) {
|
|
|
|
baseDir.append("Release/");
|
|
|
|
}
|
|
|
|
if (chromiumDebug) {
|
|
|
|
baseDir.append("Debug/");
|
|
|
|
}
|
|
|
|
baseDir.append("layout-test-results/");
|
|
|
|
analyze_chromium(diffProc, colorThreshold, &differences,
|
|
|
|
baseDir, outputDir, &summary);
|
|
|
|
} else {
|
|
|
|
create_diff_images(diffProc, colorThreshold, &differences,
|
|
|
|
baseDir, comparisonDir, outputDir, &summary);
|
|
|
|
}
|
2011-06-09 19:47:10 +04:00
|
|
|
summary.print();
|
2011-07-14 17:15:55 +04:00
|
|
|
|
|
|
|
if (differences.count()) {
|
|
|
|
SkQSort(differences.begin(), differences.count(),
|
|
|
|
sizeof(DiffRecord*), sortProc);
|
|
|
|
}
|
2011-06-09 19:47:10 +04:00
|
|
|
print_diff_page(summary.fNumMatches, colorThreshold, differences,
|
2011-04-27 19:39:30 +04:00
|
|
|
baseDir, comparisonDir, outputDir);
|
|
|
|
|
|
|
|
for (i = 0; i < differences.count(); i++) {
|
|
|
|
delete differences[i];
|
|
|
|
}
|
|
|
|
}
|