Update skdiff.

https://codereview.appspot.com/6850115/


git-svn-id: http://skia.googlecode.com/svn/trunk@6681 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
bungeman@google.com 2012-12-05 20:13:12 +00:00
parent b959a9cbd9
commit e3c8ddfd03
16 changed files with 1763 additions and 1013 deletions

View File

@ -28,7 +28,31 @@
'target_name': 'skdiff',
'type': 'executable',
'sources': [
'../tools/skdiff.cpp',
'../tools/skdiff.h',
'../tools/skdiff_html.cpp',
'../tools/skdiff_html.h',
'../tools/skdiff_main.cpp',
'../tools/skdiff_utils.cpp',
'../tools/skdiff_utils.h',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
'effects.gyp:effects',
'images.gyp:images',
],
},
{
'target_name': 'skimagediff',
'type': 'executable',
'sources': [
'../tools/skdiff.cpp',
'../tools/skdiff.h',
'../tools/skdiff_html.cpp',
'../tools/skdiff_html.h',
'../tools/skdiff_image.cpp',
'../tools/skdiff_utils.cpp',
'../tools/skdiff_utils.h',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',

221
tools/skdiff.cpp Normal file
View File

@ -0,0 +1,221 @@
/*
* 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;
}

265
tools/skdiff.h Normal file
View File

@ -0,0 +1,265 @@
/*
* 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)
, fAverageMismatchR(0)
, fAverageMismatchG(0)
, fAverageMismatchB(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 fAverageMismatchR;
float fAverageMismatchG;
float fAverageMismatchB;
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

300
tools/skdiff_html.cpp Normal file
View File

@ -0,0 +1,300 @@
/*
* 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 "skdiff_html.h"
#include "SkStream.h"
#include "SkTime.h"
/// Make layout more consistent by scaling image to 240 height, 360 width,
/// or natural size, whichever is smallest.
static int compute_image_height(int height, int width) {
int retval = 240;
if (height < retval) {
retval = height;
}
float scale = (float) retval / height;
if (width * scale > 360) {
scale = (float) 360 / width;
retval = static_cast<int>(height * scale);
}
return retval;
}
static void print_table_header(SkFILEWStream* stream,
const int matchCount,
const int colorThreshold,
const RecordArray& differences,
const SkString &baseDir,
const SkString &comparisonDir,
bool doOutputDate = false) {
stream->writeText("<table>\n");
stream->writeText("<tr><th>");
stream->writeText("select image</th>\n<th>");
if (doOutputDate) {
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(" diffs matched ");
if (colorThreshold == 0) {
stream->writeText("exactly");
} else {
stream->writeText("within ");
stream->writeDecAsText(colorThreshold);
stream->writeText(" color units per component");
}
stream->writeText(".<br>");
stream->writeText("</th>\n<th>");
stream->writeText("every different pixel shown in white");
stream->writeText("</th>\n<th>");
stream->writeText("color difference at each pixel");
stream->writeText("</th>\n<th>baseDir: ");
stream->writeText(baseDir.c_str());
stream->writeText("</th>\n<th>comparisonDir: ");
stream->writeText(comparisonDir.c_str());
stream->writeText("</th>\n");
stream->writeText("</tr>\n");
}
static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) {
stream->writeText("<br>(");
stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
diff.fBase.fBitmap.width() *
diff.fBase.fBitmap.height()));
stream->writeText(" pixels)");
/*
stream->writeDecAsText(diff.fWeightedFraction *
diff.fBaseWidth *
diff.fBaseHeight);
stream->writeText(" weighted pixels)");
*/
}
static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) {
stream->writeText("<td><input type=\"checkbox\" name=\"");
stream->writeText(diff.fBase.fFilename.c_str());
stream->writeText("\" checked=\"yes\"></td>");
}
static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) {
char metricBuf [20];
stream->writeText("<td><b>");
stream->writeText(diff.fBase.fFilename.c_str());
stream->writeText("</b><br>");
switch (diff.fResult) {
case DiffRecord::kEqualBits_Result:
SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
return;
case DiffRecord::kEqualPixels_Result:
SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
return;
case DiffRecord::kDifferentSizes_Result:
stream->writeText("Image sizes differ</td>");
return;
case DiffRecord::kDifferentPixels_Result:
sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference);
stream->writeText(metricBuf);
stream->writeText(" of pixels differ");
stream->writeText("\n (");
sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction);
stream->writeText(metricBuf);
stream->writeText(" weighted)");
// Write the actual number of pixels that differ if it's < 1%
if (diff.fFractionDifference < 0.01) {
print_pixel_count(stream, diff);
}
stream->writeText("<br>Average color mismatch ");
stream->writeDecAsText(static_cast<int>(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>");
break;
case DiffRecord::kCouldNotCompare_Result:
stream->writeText("Could not compare.<br>base: ");
stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus));
stream->writeText("<br>comparison: ");
stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus));
stream->writeText("</td>");
return;
default:
SkDEBUGFAIL("encountered DiffRecord with unknown result type");
return;
}
}
static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) {
stream->writeText("<td><a href=\"");
stream->writeText(path.c_str());
stream->writeText("\"><img src=\"");
stream->writeText(path.c_str());
stream->writeText("\" height=\"");
stream->writeDecAsText(height);
stream->writeText("px\"></a></td>");
}
static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) {
stream->writeText("<td><a href=\"");
stream->writeText(path.c_str());
stream->writeText("\">");
stream->writeText(text);
stream->writeText("</a></td>");
}
static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
const SkString& relativePath, bool local) {
if (resource.fBitmap.empty()) {
if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
if (local && !resource.fFilename.isEmpty()) {
print_link_cell(stream, resource.fFilename, "N/A");
return;
}
if (!resource.fFullPath.isEmpty()) {
if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
resource.fFullPath.prepend(relativePath);
}
print_link_cell(stream, resource.fFullPath, "N/A");
return;
}
}
stream->writeText("<td>N/A</td>");
return;
}
int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
if (local) {
print_image_cell(stream, resource.fFilename, height);
return;
}
if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
resource.fFullPath.prepend(relativePath);
}
print_image_cell(stream, resource.fFullPath, height);
}
static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
stream->writeText("<tr>\n");
print_checkbox_cell(stream, diff);
print_label_cell(stream, diff);
print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
print_diff_resource_cell(stream, diff.fBase, relativePath, false);
print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
stream->writeText("</tr>\n");
stream->flush();
}
void print_diff_page(const int matchCount,
const int colorThreshold,
const RecordArray& differences,
const SkString& baseDir,
const SkString& comparisonDir,
const SkString& outputDir) {
SkASSERT(!baseDir.isEmpty());
SkASSERT(!comparisonDir.isEmpty());
SkASSERT(!outputDir.isEmpty());
SkString outputPath(outputDir);
outputPath.append("index.html");
//SkFILEWStream outputStream ("index.html");
SkFILEWStream outputStream(outputPath.c_str());
// Need to convert paths from relative-to-cwd to relative-to-outputDir
// FIXME this doesn't work if there are '..' inside the outputDir
bool isPathAbsolute = false;
// On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
isPathAbsolute = true;
}
#ifdef SK_BUILD_FOR_WIN32
// On Windows, absolute paths can also start with "x:", where x is any
// drive letter.
if (outputDir.size() > 1 && ':' == outputDir[1]) {
isPathAbsolute = true;
}
#endif
SkString relativePath;
if (!isPathAbsolute) {
unsigned int ui;
for (ui = 0; ui < outputDir.size(); ui++) {
if (outputDir[ui] == PATH_DIV_CHAR) {
relativePath.append(".." PATH_DIV_STR);
}
}
}
outputStream.writeText(
"<html>\n<head>\n"
"<script src=\"https://ajax.googleapis.com/ajax/"
"libs/jquery/1.7.2/jquery.min.js\"></script>\n"
"<script type=\"text/javascript\">\n"
"function generateCheckedList() {\n"
"var boxes = $(\":checkbox:checked\");\n"
"var fileCmdLineString = '';\n"
"var fileMultiLineString = '';\n"
"for (var i = 0; i < boxes.length; i++) {\n"
"fileMultiLineString += boxes[i].name + '<br>';\n"
"fileCmdLineString += boxes[i].name + '&nbsp;';\n"
"}\n"
"$(\"#checkedList\").html(fileCmdLineString + "
"'<br><br>' + fileMultiLineString);\n"
"}\n"
"</script>\n</head>\n<body>\n");
print_table_header(&outputStream, matchCount, colorThreshold, differences,
baseDir, comparisonDir);
int i;
for (i = 0; i < differences.count(); i++) {
DiffRecord* diff = differences[i];
switch (diff->fResult) {
// Cases in which there is no diff to report.
case DiffRecord::kEqualBits_Result:
case DiffRecord::kEqualPixels_Result:
continue;
// Cases in which we want a detailed pixel diff.
case DiffRecord::kDifferentPixels_Result:
case DiffRecord::kDifferentSizes_Result:
case DiffRecord::kCouldNotCompare_Result:
print_diff_row(&outputStream, *diff, relativePath);
continue;
default:
SkDEBUGFAIL("encountered DiffRecord with unknown result type");
continue;
}
}
outputStream.writeText(
"</table>\n"
"<input type=\"button\" "
"onclick=\"generateCheckedList()\" "
"value=\"Create Rebaseline List\">\n"
"<div id=\"checkedList\"></div>\n"
"</body>\n</html>\n");
outputStream.flush();
}

21
tools/skdiff_html.h Normal file
View File

@ -0,0 +1,21 @@
/*
* 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_html_DEFINED
#define skdiff_html_DEFINED
#include "skdiff.h"
class SkString;
void print_diff_page(const int matchCount,
const int colorThreshold,
const RecordArray& differences,
const SkString& baseDir,
const SkString& comparisonDir,
const SkString& outputDir);
#endif

375
tools/skdiff_image.cpp Normal file
View File

@ -0,0 +1,375 @@
/*
* 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 "skdiff_utils.h"
#include "SkBitmap.h"
#include "SkData.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkOSFile.h"
#include "SkTDArray.h"
#include "SkTemplates.h"
#include "SkTypes.h"
/// If outputDir.isEmpty(), don't write out diff files.
static void create_diff_images (DiffMetricProc dmp,
const int colorThreshold,
const SkString& baseFile,
const SkString& comparisonFile,
const SkString& outputDir,
const SkString& outputFilename,
DiffRecord* drp) {
SkASSERT(!baseFile.isEmpty());
SkASSERT(!comparisonFile.isEmpty());
drp->fBase.fFilename = baseFile;
drp->fBase.fFullPath = baseFile;
drp->fBase.fStatus = DiffResource::kSpecified_Status;
drp->fComparison.fFilename = comparisonFile;
drp->fComparison.fFullPath = comparisonFile;
drp->fComparison.fStatus = DiffResource::kSpecified_Status;
SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
if (NULL != baseFileBits) {
drp->fBase.fStatus = DiffResource::kRead_Status;
}
SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
if (NULL != comparisonFileBits) {
drp->fComparison.fStatus = DiffResource::kRead_Status;
}
if (NULL == baseFileBits || NULL == comparisonFileBits) {
if (NULL == baseFileBits) {
drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
}
if (NULL == comparisonFileBits) {
drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
}
drp->fResult = DiffRecord::kCouldNotCompare_Result;
return;
}
if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
drp->fResult = DiffRecord::kEqualBits_Result;
return;
}
get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
get_bitmap(comparisonFileBits, drp->fComparison, SkImageDecoder::kDecodePixels_Mode);
if (DiffResource::kDecoded_Status != drp->fBase.fStatus ||
DiffResource::kDecoded_Status != drp->fComparison.fStatus)
{
drp->fResult = DiffRecord::kCouldNotCompare_Result;
return;
}
create_and_write_diff_image(drp, dmp, colorThreshold, outputDir, outputFilename);
//TODO: copy fBase.fFilename and fComparison.fFilename to outputDir
// svn and git often present tmp files to diff tools which are promptly deleted
//TODO: serialize drp to outputDir
// write a tool to deserialize them and call print_diff_page
SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
}
static void usage (char * argv0) {
SkDebugf("Skia image diff tool\n");
SkDebugf("\n"
"Usage: \n"
" %s <baseFile> <comparisonFile>\n" , argv0);
SkDebugf(
"\nArguments:"
"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
"\n return code (number of file pairs yielding this"
"\n result) if any file pairs yielded this result."
"\n This flag may be repeated, in which case the"
"\n return code will be the number of fail pairs"
"\n yielding ANY of these results."
"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
"\n code if any file pairs yeilded this status."
"\n --help: display this info"
"\n --listfilenames: list all filenames for each result type in stdout"
"\n --nodiffs: don't write out image diffs, just generate report on stdout"
"\n --outputdir: directory to write difference images"
"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
"\n -u: ignored. Recognized for compatibility with svn diff."
"\n -L: first occurrence label for base, second occurrence label for comparison."
"\n Labels must be of the form \"<filename>(\t<specifier>)?\"."
"\n The base <filename> will be used to create files in outputdir."
"\n"
"\n baseFile: baseline image file."
"\n comparisonFile: comparison image file"
"\n"
"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
"\n");
}
const int kNoError = 0;
const int kGenericError = -1;
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
DiffMetricProc diffProc = compute_diff_pmcolor;
// Maximum error tolerated in any one color channel in any one pixel before
// a difference is reported.
int colorThreshold = 0;
SkString baseFile;
SkString baseLabel;
SkString comparisonFile;
SkString comparisonLabel;
SkString outputDir;
bool listFilenames = false;
bool failOnResultType[DiffRecord::kResultCount];
for (int i = 0; i < DiffRecord::kResultCount; i++) {
failOnResultType[i] = false;
}
bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
for (int base = 0; base < DiffResource::kStatusCount; ++base) {
for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
failOnStatusType[base][comparison] = false;
}
}
int i;
int numUnflaggedArguments = 0;
int numLabelArguments = 0;
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--failonresult")) {
if (argc == ++i) {
SkDebugf("failonresult expects one argument.\n");
continue;
}
DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
if (type != DiffRecord::kResultCount) {
failOnResultType[type] = true;
} else {
SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
}
continue;
}
if (!strcmp(argv[i], "--failonstatus")) {
if (argc == ++i) {
SkDebugf("failonstatus missing base status.\n");
continue;
}
bool baseStatuses[DiffResource::kStatusCount];
if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
SkDebugf("unrecognized base status <%s>\n", argv[i]);
}
if (argc == ++i) {
SkDebugf("failonstatus missing comparison status.\n");
continue;
}
bool comparisonStatuses[DiffResource::kStatusCount];
if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
}
for (int base = 0; base < DiffResource::kStatusCount; ++base) {
for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
failOnStatusType[base][comparison] |=
baseStatuses[base] && comparisonStatuses[comparison];
}
}
continue;
}
if (!strcmp(argv[i], "--help")) {
usage(argv[0]);
return kNoError;
}
if (!strcmp(argv[i], "--listfilenames")) {
listFilenames = true;
continue;
}
if (!strcmp(argv[i], "--outputdir")) {
if (argc == ++i) {
SkDebugf("outputdir expects one argument.\n");
continue;
}
outputDir.set(argv[i]);
continue;
}
if (!strcmp(argv[i], "--threshold")) {
colorThreshold = atoi(argv[++i]);
continue;
}
if (!strcmp(argv[i], "-u")) {
//we don't produce unified diffs, ignore parameter to work with svn diff
continue;
}
if (!strcmp(argv[i], "-L")) {
if (argc == ++i) {
SkDebugf("label expects one argument.\n");
continue;
}
switch (numLabelArguments++) {
case 0:
baseLabel.set(argv[i]);
continue;
case 1:
comparisonLabel.set(argv[i]);
continue;
default:
SkDebugf("extra label argument <%s>\n", argv[i]);
usage(argv[0]);
return kGenericError;
}
continue;
}
if (argv[i][0] != '-') {
switch (numUnflaggedArguments++) {
case 0:
baseFile.set(argv[i]);
continue;
case 1:
comparisonFile.set(argv[i]);
continue;
default:
SkDebugf("extra unflagged argument <%s>\n", argv[i]);
usage(argv[0]);
return kGenericError;
}
}
SkDebugf("Unrecognized argument <%s>\n", argv[i]);
usage(argv[0]);
return kGenericError;
}
if (numUnflaggedArguments != 2) {
usage(argv[0]);
return kGenericError;
}
if (listFilenames) {
printf("Base file is [%s]\n", baseFile.c_str());
}
if (listFilenames) {
printf("Comparison file is [%s]\n", comparisonFile.c_str());
}
if (outputDir.isEmpty()) {
if (listFilenames) {
printf("Not writing any diffs. No output dir specified.\n");
}
} else {
if (!outputDir.endsWith(PATH_DIV_STR)) {
outputDir.append(PATH_DIV_STR);
}
if (listFilenames) {
printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str());
}
}
// Some obscure documentation about diff/patch labels:
//
// Posix says the format is: <filename><tab><date>
// It also states that if a filename contains <tab> or <newline>
// the result is implementation defined
//
// Svn diff --diff-cmd provides labels of the form: <filename><tab><revision>
//
// Git diff --ext-diff does not supply arguments compatible with diff.
// However, it does provide the filename directly.
// skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)"
//
// Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters.
// difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)"
//
// Diff will write any specified label verbatim. Without a specified label diff will write
// <filename><tab><date>
// However, diff will encode the filename as a cstring if the filename contains
// Any of <space> or <double quote>
// A char less than 32
// Any escapable character \a, \b, \t, \n, \v, \f, \r, \\
//
// Patch decodes:
// If first <non-white-space> is <double quote>, parse filename from cstring.
// If there is a <tab> after the first <non-white-space>, filename is
// [first <non-white-space>, the next run of <white-space> with an embedded <tab>).
// Otherwise the filename is [first <non-space>, the next <white-space>).
//
// The filename /dev/null means the file does not exist (used in adds and deletes).
// Considering the above, skimagediff will consider the contents of a -L parameter as
// <filename>(\t<specifier>)?
SkString outputFile;
if (baseLabel.isEmpty()) {
baseLabel.set(baseFile);
outputFile = baseLabel;
} else {
const char* baseLabelCstr = baseLabel.c_str();
const char* tab = strchr(baseLabelCstr, '\t');
if (NULL == tab) {
outputFile = baseLabel;
} else {
outputFile.set(baseLabelCstr, tab - baseLabelCstr);
}
}
if (comparisonLabel.isEmpty()) {
comparisonLabel.set(comparisonFile);
}
printf("Base: %s\n", baseLabel.c_str());
printf("Comparison: %s\n", comparisonLabel.c_str());
DiffRecord dr;
create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile,
&dr);
if (DiffResource::isStatusFailed(dr.fBase.fStatus)) {
printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus));
}
if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) {
printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus));
}
printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult));
if (DiffRecord::kDifferentPixels_Result == dr.fResult) {
printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference);
printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction);
if (dr.fFractionDifference < 0.01) {
printf(" %d pixels", static_cast<int>(dr.fFractionDifference *
dr.fBase.fBitmap.width() *
dr.fBase.fBitmap.height()));
}
printf("\nAverage color mismatch: ");
printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR,
dr.fAverageMismatchG,
dr.fAverageMismatchB)));
printf("\nMax color mismatch: ");
printf("%d", MAX3(dr.fMaxMismatchR,
dr.fMaxMismatchG,
dr.fMaxMismatchB));
printf("\n");
}
printf("\n");
int num_failing_results = 0;
if (failOnResultType[dr.fResult]) {
++num_failing_results;
}
if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) {
++num_failing_results;
}
return num_failing_results;
}
#if !defined SK_BUILD_FOR_IOS
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
#endif

File diff suppressed because it is too large Load Diff

186
tools/skdiff_utils.cpp Normal file
View File

@ -0,0 +1,186 @@
/*
* 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 "skdiff_utils.h"
#include "SkBitmap.h"
#include "SkData.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkTypes.h"
bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
if ((NULL == skdata1) || (NULL == skdata2)) {
return false;
}
if (skdata1->size() != skdata2->size()) {
return false;
}
return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
}
SkData* read_file(const char* file_path) {
SkFILEStream fileStream(file_path);
if (!fileStream.isValid()) {
SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
return NULL;
}
size_t bytesInFile = fileStream.getLength();
size_t bytesLeftToRead = bytesInFile;
void* bufferStart = sk_malloc_throw(bytesInFile);
char* bufferPointer = (char*)bufferStart;
while (bytesLeftToRead > 0) {
size_t bytesReadThisTime = fileStream.read(bufferPointer, bytesLeftToRead);
if (0 == bytesReadThisTime) {
SkDebugf("WARNING: error reading from <%s>\n", file_path);
sk_free(bufferStart);
return NULL;
}
bytesLeftToRead -= bytesReadThisTime;
bufferPointer += bytesReadThisTime;
}
return SkData::NewFromMalloc(bufferStart, bytesInFile);
}
bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode) {
SkMemoryStream stream(fileBits->data(), fileBits->size());
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
if (NULL == codec) {
SkDebugf("ERROR: no codec found for <%s>\n", resource.fFullPath.c_str());
resource.fStatus = DiffResource::kCouldNotDecode_Status;
return false;
}
// In debug, the DLL will automatically be unloaded when this is deleted,
// but that shouldn't be a problem in release mode.
SkAutoTDelete<SkImageDecoder> ad(codec);
stream.rewind();
if (!codec->decode(&stream, &resource.fBitmap, SkBitmap::kARGB_8888_Config, mode)) {
SkDebugf("ERROR: codec failed for basePath <%s>\n", resource.fFullPath.c_str());
resource.fStatus = DiffResource::kCouldNotDecode_Status;
return false;
}
resource.fStatus = DiffResource::kDecoded_Status;
return true;
}
/** 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);
}
}
}
bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
SkBitmap copy;
bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
force_all_opaque(copy);
return SkImageEncoder::EncodeFile(path.c_str(), copy,
SkImageEncoder::kPNG_Type, 100);
}
/// Return a copy of the "input" string, within which we have replaced all instances
/// of oldSubstring with newSubstring.
///
/// TODO: If we like this, we should move it into the core SkString implementation,
/// adding more checks and ample test cases, and paying more attention to efficiency.
static SkString replace_all(const SkString &input,
const char oldSubstring[], const char newSubstring[]) {
SkString output;
const char *input_cstr = input.c_str();
const char *first_char = input_cstr;
const char *match_char;
int oldSubstringLen = strlen(oldSubstring);
while (NULL != (match_char = strstr(first_char, oldSubstring))) {
output.append(first_char, (match_char - first_char));
output.append(newSubstring);
first_char = match_char + oldSubstringLen;
}
output.append(first_char);
return output;
}
static SkString filename_to_derived_filename(const SkString& filename, const char *suffix) {
SkString diffName (filename);
const char* cstring = diffName.c_str();
int dotOffset = strrchr(cstring, '.') - cstring;
diffName.remove(dotOffset, diffName.size() - dotOffset);
diffName.append(suffix);
// In case we recursed into subdirectories, replace slashes with something else
// so the diffs will all be written into a single flat directory.
diffName = replace_all(diffName, PATH_DIV_STR, "_");
return diffName;
}
SkString filename_to_diff_filename(const SkString& filename) {
return filename_to_derived_filename(filename, "-diff.png");
}
SkString filename_to_white_filename(const SkString& filename) {
return filename_to_derived_filename(filename, "-white.png");
}
void create_and_write_diff_image(DiffRecord* drp,
DiffMetricProc dmp,
const int colorThreshold,
const SkString& outputDir,
const SkString& filename) {
const int w = drp->fBase.fBitmap.width();
const int h = drp->fBase.fBitmap.height();
if (w != drp->fComparison.fBitmap.width() || h != drp->fComparison.fBitmap.height()) {
drp->fResult = DiffRecord::kDifferentSizes_Result;
} else {
drp->fDifference.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
drp->fDifference.fBitmap.allocPixels();
drp->fWhite.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
drp->fWhite.fBitmap.allocPixels();
SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
compute_diff(drp, dmp, colorThreshold);
SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
}
if (outputDir.isEmpty()) {
drp->fDifference.fStatus = DiffResource::kUnspecified_Status;
drp->fWhite.fStatus = DiffResource::kUnspecified_Status;
} else {
drp->fDifference.fFilename = filename_to_diff_filename(filename);
drp->fDifference.fFullPath = outputDir;
drp->fDifference.fFullPath.append(drp->fDifference.fFilename);
drp->fDifference.fStatus = DiffResource::kSpecified_Status;
drp->fWhite.fFilename = filename_to_white_filename(filename);
drp->fWhite.fFullPath = outputDir;
drp->fWhite.fFullPath.append(drp->fWhite.fFilename);
drp->fWhite.fStatus = DiffResource::kSpecified_Status;
if (DiffRecord::kDifferentPixels_Result == drp->fResult) {
if (write_bitmap(drp->fDifference.fFullPath, drp->fDifference.fBitmap)) {
drp->fDifference.fStatus = DiffResource::kExists_Status;
} else {
drp->fDifference.fStatus = DiffResource::kDoesNotExist_Status;
}
if (write_bitmap(drp->fWhite.fFullPath, drp->fWhite.fBitmap)) {
drp->fWhite.fStatus = DiffResource::kExists_Status;
} else {
drp->fWhite.fStatus = DiffResource::kDoesNotExist_Status;
}
}
}
}

53
tools/skdiff_utils.h Normal file
View File

@ -0,0 +1,53 @@
/*
* 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_utils_DEFINED
#define skdiff_utils_DEFINED
#include "skdiff.h"
#include "SkImageDecoder.h"
class SkBitmap;
class SkData;
class SkString;
/** Returns true if the two buffers passed in are both non-NULL,
* have the same length, and contain exactly the same byte values.
*/
bool are_buffers_equal(SkData* skdata1, SkData* skdata2);
/** Reads the file at the given path and returns its complete contents as an
* SkData object (or returns NULL on error).
*/
SkData* read_file(const char* file_path);
/** Decodes the fileBits into the resource.fBitmap. Returns false on failure. */
bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode);
/** Writes the bitmap as a PNG to the path specified. */
bool write_bitmap(const SkString& path, const SkBitmap& bitmap);
/** Given an image filename, returns the name of the file containing
* the associated difference image.
*/
SkString filename_to_diff_filename(const SkString& filename);
/** Given an image filename, returns the name of the file containing
* the "white" difference image.
*/
SkString filename_to_white_filename(const SkString& filename);
/** Calls compute_diff and handles the difference and white diff resources.
* If !outputDir.isEmpty(), writes out difference and white images.
*/
void create_and_write_diff_image(DiffRecord* drp,
DiffMetricProc dmp,
const int colorThreshold,
const SkString& outputDir,
const SkString& filename);
#endif

View File

@ -59,7 +59,7 @@ skdiff_test "$SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TES
# baseDir or comparisonDir)
# - list filenames with each result type to stdout
# - don't generate HTML output files
skdiff_test "--failonresult DifferentPixels --failonresult DifferentSizes --failonresult DifferentOther --failonresult Unknown --listfilenames --nodiffs $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test2"
skdiff_test "--failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test2"
# Run skdiff over just the files that have identical bits.
skdiff_test "--nodiffs --match identical-bits $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/identical-bits"

View File

@ -7,9 +7,7 @@ compared 3 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits
[_] 0 file pairs have identical dimensions but some differing pixels
[_] 0 file pairs have differing dimensions
[_] 0 file pairs contain different bits and are not parsable images
[_] 0 file pairs missing from comparisonDir
[_] 0 file pairs missing from baseDir
[_] 0 file pairs could not be compared
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)

View File

@ -7,9 +7,7 @@ compared 2 file pairs:
[_] 0 file pairs contain the same pixel values, but not the same bits
[_] 0 file pairs have identical dimensions but some differing pixels
[_] 0 file pairs have differing dimensions
[_] 0 file pairs contain different bits and are not parsable images
[_] 0 file pairs missing from comparisonDir
[_] 0 file pairs missing from baseDir
[_] 0 file pairs could not be compared
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)

View File

@ -17,32 +17,32 @@ $("#checkedList").html(fileCmdLineString + '<br><br>' + fileMultiLineString);
<body>
<table>
<tr><th>select image</th>
<th>3 of 12 images matched exactly.<br></th>
<th>3 of 12 diffs matched exactly.<br></th>
<th>every different pixel shown in white</th>
<th>color difference at each pixel</th>
<th>baseDir: tools/tests/skdiff/baseDir/</th>
<th>comparisonDir: tools/tests/skdiff/comparisonDir/</th>
</tr>
<tr>
<td><input type="checkbox" name="missing-files/missing-from-baseDir.png" checked="yes"></td><td><b>missing-files/missing-from-baseDir.png</b><br>Missing from baseDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png" height="240px"></a></td></tr>
<td><input type="checkbox" name="different-bits/different-bits-unknown-format.xyz" checked="yes"></td><td><b>different-bits/different-bits-unknown-format.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td></tr>
<tr>
<td><input type="checkbox" name="missing-files/missing-from-baseDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-baseDir.xyz</b><br>Missing from baseDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
<td><input type="checkbox" name="missing-files/missing-from-baseDir.png" checked="yes"></td><td><b>missing-files/missing-from-baseDir.png</b><br>Could not compare.<br>base: not found<br>comparison: decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png" height="240px"></a></td></tr>
<tr>
<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.png" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.png</b><br>Missing from comparisonDir</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png"><img src="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png" height="240px"></a></td><td>N/A</td></tr>
<td><input type="checkbox" name="missing-files/missing-from-baseDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-baseDir.xyz</b><br>Could not compare.<br>base: not found<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz">N/A</a></td></tr>
<tr>
<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.xyz</b><br>Missing from comparisonDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.png" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.png</b><br>Could not compare.<br>base: decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png"><img src="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png" height="240px"></a></td><td>N/A</td></tr>
<tr>
<td><input type="checkbox" name="different-bits/different-bits-unknown-format.xyz" checked="yes"></td><td><b>different-bits/different-bits-unknown-format.xyz</b><br>Files differ; unable to parse one or both files</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz">N/A</a></td><td>N/A</td></tr>
<tr>
<td><input type="checkbox" name="different-bits/slightly-different-sizes.png" checked="yes"></td><td><b>different-bits/slightly-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png" height="240px"></a></td></tr>
<tr>
<td><input type="checkbox" name="different-bits/very-different-sizes.png" checked="yes"></td><td><b>different-bits/very-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png" height="128px"></a></td></tr>
<tr>
<td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br> 97.9926% of pixels differ
( 42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
<td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br>97.9926% of pixels differ
(42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
<tr>
<td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br> 0.6630% of pixels differ
( 0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
<td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br>0.6630% of pixels differ
(0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
</table>
<input type="button" onclick="generateCheckedList()" value="Create Rebaseline List">
<div id="checkedList"></div>

View File

@ -1,8 +1,9 @@
ERROR: no codec found for basePath <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/identical-bits/identical-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/identical-bits/identical-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz>
baseDir is [tools/tests/skdiff/baseDir/]
comparisonDir is [tools/tests/skdiff/comparisonDir/]
writing diffs to outputDir is [tools/tests/skdiff/test1/output-actual/]
@ -12,9 +13,12 @@ compared 12 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits
[_] 2 file pairs have identical dimensions but some differing pixels
[_] 2 file pairs have differing dimensions
[_] 1 file pairs contain different bits and are not parsable images
[_] 2 file pairs missing from comparisonDir
[_] 2 file pairs missing from baseDir
[_] 5 file pairs could not be compared
[_] 1 file pairs decoded in baseDir and not found in comparisonDir
[_] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir
[_] 1 file pairs could not be decoded in baseDir and not found in comparisonDir
[_] 1 file pairs not found in baseDir and decoded in comparisonDir
[_] 1 file pairs not found in baseDir and could not be decoded in comparisonDir
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)

View File

@ -1 +1 @@
out/Debug/skdiff --failonresult DifferentPixels --failonresult DifferentSizes --failonresult DifferentOther --failonresult Unknown --listfilenames --nodiffs tools/tests/skdiff/baseDir tools/tests/skdiff/comparisonDir tools/tests/skdiff/test2/output-actual
out/Debug/skdiff --failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs tools/tests/skdiff/baseDir tools/tests/skdiff/comparisonDir tools/tests/skdiff/test2/output-actual

View File

@ -1,4 +1,5 @@
ERROR: no codec found for basePath <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
baseDir is [tools/tests/skdiff/baseDir/]
comparisonDir is [tools/tests/skdiff/comparisonDir/]
not writing any diffs to outputDir [tools/tests/skdiff/test2/output-actual/]
@ -8,9 +9,10 @@ compared 12 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits: different-bits/different-bits-identical-pixels.png
[*] 2 file pairs have identical dimensions but some differing pixels: different-bits/slightly-different-pixels-same-size.png different-bits/very-different-pixels-same-size.png
[*] 2 file pairs have differing dimensions: different-bits/slightly-different-sizes.png different-bits/very-different-sizes.png
[*] 1 file pairs contain different bits and are not parsable images: different-bits/different-bits-unknown-format.xyz
[_] 2 file pairs missing from comparisonDir: missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
[_] 2 file pairs missing from baseDir: missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz
[_] 5 file pairs could not be compared: different-bits/different-bits-unknown-format.xyz missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
[*] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir: different-bits/different-bits-unknown-format.xyz
[_] 2 file pairs found in baseDir and not found in comparisonDir: missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
[_] 2 file pairs not found in baseDir and found in comparisonDir: missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz
[*] 0 file pairs not compared yet:
(results marked with [*] will cause nonzero return value)