546f70c823
src/images/SkImageDecoder_libpng.cpp: A8 images are not opaque, so do not set the opaque flag. This fixes a bug where copyTo does not work as expected (when copying an A8 decoded image to ARGB_8888), leading to a bitmap hash that does not represent the image correctly (in skimage). tools/skimage_main.cpp: In write_bitmap, which is creating the image for visual comparison, copy A8 to 8888, since A8 cannot be encoded. In the section that tests reencoding, do not test reencoding A8, which is known to not work. R=mtklein@google.com, djsollen@google.com Author: scroggo@google.com Review URL: https://codereview.chromium.org/25726004 git-svn-id: http://skia.googlecode.com/svn/trunk@11589 2bbb7eff-a529-9590-31e7-b0007b416f81
722 lines
29 KiB
C++
722 lines
29 KiB
C++
/*
|
|
* Copyright 2011 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "gm_expectations.h"
|
|
#include "SkBitmap.h"
|
|
#include "SkColorPriv.h"
|
|
#include "SkCommandLineFlags.h"
|
|
#include "SkData.h"
|
|
#include "SkForceLinking.h"
|
|
#include "SkGraphics.h"
|
|
#include "SkImageDecoder.h"
|
|
#include "SkImageEncoder.h"
|
|
#include "SkOSFile.h"
|
|
#include "SkRandom.h"
|
|
#include "SkStream.h"
|
|
#include "SkTArray.h"
|
|
#include "SkTemplates.h"
|
|
|
|
__SK_FORCE_IMAGE_DECODER_LINKING;
|
|
|
|
DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A8]");
|
|
DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations.");
|
|
DEFINE_string(mismatchPath, "", "Folder to write mismatched images to.");
|
|
DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
|
|
DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
|
|
DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
|
|
DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
|
|
DEFINE_string2(writePath, w, "", "Write rendered images into this directory.");
|
|
DEFINE_bool(skip, false, "Skip writing zeroes.");
|
|
|
|
struct Format {
|
|
SkImageEncoder::Type fType;
|
|
SkImageDecoder::Format fFormat;
|
|
const char* fSuffix;
|
|
};
|
|
|
|
static const Format gFormats[] = {
|
|
{ SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" },
|
|
{ SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" },
|
|
{ SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" },
|
|
{ SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" },
|
|
{ SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" },
|
|
{ SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" },
|
|
{ SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" }
|
|
};
|
|
|
|
static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) {
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
|
|
if (gFormats[i].fFormat == format) {
|
|
return gFormats[i].fType;
|
|
}
|
|
}
|
|
return SkImageEncoder::kUnknown_Type;
|
|
}
|
|
|
|
static const char* suffix_for_type(SkImageEncoder::Type type) {
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
|
|
if (gFormats[i].fType == type) {
|
|
return gFormats[i].fSuffix;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) {
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
|
|
if (strcmp(suffix, gFormats[i].fSuffix) == 0) {
|
|
return gFormats[i].fFormat;
|
|
}
|
|
}
|
|
return SkImageDecoder::kUnknown_Format;
|
|
}
|
|
|
|
static void make_outname(SkString* dst, const char outDir[], const char src[],
|
|
const char suffix[]) {
|
|
SkString basename = SkOSPath::SkBasename(src);
|
|
dst->set(SkOSPath::SkPathJoin(outDir, basename.c_str()));
|
|
if (!dst->endsWith(suffix)) {
|
|
const char* cstyleDst = dst->c_str();
|
|
const char* dot = strrchr(cstyleDst, '.');
|
|
if (dot != NULL) {
|
|
int32_t index = SkToS32(dot - cstyleDst);
|
|
dst->remove(index, dst->size() - index);
|
|
}
|
|
dst->append(suffix);
|
|
}
|
|
}
|
|
|
|
// Store the names of the filenames to report later which ones failed, succeeded, and were
|
|
// invalid.
|
|
static SkTArray<SkString, false> gInvalidStreams;
|
|
static SkTArray<SkString, false> gMissingCodecs;
|
|
static SkTArray<SkString, false> gDecodeFailures;
|
|
static SkTArray<SkString, false> gEncodeFailures;
|
|
static SkTArray<SkString, false> gSuccessfulDecodes;
|
|
static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
|
|
static SkTArray<SkString, false> gFailedSubsetDecodes;
|
|
// Files/subsets that do not have expectations. Not reported as a failure of the test so
|
|
// the bots will not turn red with each new image test.
|
|
static SkTArray<SkString, false> gMissingExpectations;
|
|
static SkTArray<SkString, false> gMissingSubsetExpectations;
|
|
|
|
static SkBitmap::Config gPrefConfig(SkBitmap::kNo_Config);
|
|
|
|
// Expections read from a file specified by readExpectationsPath. The expectations must have been
|
|
// previously written using createExpectationsPath.
|
|
SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations;
|
|
|
|
static bool write_bitmap(const char outName[], const SkBitmap& bm) {
|
|
const SkBitmap* bmPtr;
|
|
SkBitmap bm8888;
|
|
if (bm.config() == SkBitmap::kA8_Config) {
|
|
// Copy A8 into ARGB_8888, since our image encoders do not currently
|
|
// support A8.
|
|
if (!bm.copyTo(&bm8888, SkBitmap::kARGB_8888_Config)) {
|
|
return false;
|
|
}
|
|
bmPtr = &bm8888;
|
|
} else {
|
|
bmPtr = &bm;
|
|
}
|
|
return SkImageEncoder::EncodeFile(outName, *bmPtr, SkImageEncoder::kPNG_Type, 100);
|
|
}
|
|
|
|
/**
|
|
* Return a random SkIRect inside the range specified.
|
|
* @param rand Random number generator.
|
|
* @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be
|
|
* in the range [0, maxX)
|
|
* @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be
|
|
* in the range [0, maxY)
|
|
* @return SkIRect Non-empty, non-degenerate rectangle.
|
|
*/
|
|
static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) {
|
|
SkASSERT(maxX > 1 && maxY > 1);
|
|
int32_t left = rand->nextULessThan(maxX);
|
|
int32_t right = rand->nextULessThan(maxX);
|
|
int32_t top = rand->nextULessThan(maxY);
|
|
int32_t bottom = rand->nextULessThan(maxY);
|
|
SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom);
|
|
rect.sort();
|
|
// Make sure rect is not empty.
|
|
if (rect.fLeft == rect.fRight) {
|
|
if (rect.fLeft > 0) {
|
|
rect.fLeft--;
|
|
} else {
|
|
rect.fRight++;
|
|
// This branch is only taken if 0 == rect.fRight, and
|
|
// maxX must be at least 2, so it must still be in
|
|
// range.
|
|
SkASSERT(rect.fRight < maxX);
|
|
}
|
|
}
|
|
if (rect.fTop == rect.fBottom) {
|
|
if (rect.fTop > 0) {
|
|
rect.fTop--;
|
|
} else {
|
|
rect.fBottom++;
|
|
// Again, this must be in range.
|
|
SkASSERT(rect.fBottom < maxY);
|
|
}
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
// Stored expectations to be written to a file if createExpectationsPath is specified.
|
|
static Json::Value gExpectationsToWrite;
|
|
|
|
/**
|
|
* If expectations are to be recorded, record the bitmap expectations into global
|
|
* expectations array.
|
|
*/
|
|
static void write_expectations(const SkBitmap& bitmap, const char* filename) {
|
|
if (!FLAGS_createExpectationsPath.isEmpty()) {
|
|
// Creates an Expectations object, and add it to the list to write.
|
|
skiagm::Expectations expectation(bitmap);
|
|
Json::Value value = expectation.asJsonValue();
|
|
gExpectationsToWrite[filename] = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if this filename is a known failure, and therefore a failure
|
|
* to decode should be ignored.
|
|
*/
|
|
static bool expect_to_fail(const char* filename) {
|
|
if (NULL == gJsonExpectations.get()) {
|
|
return false;
|
|
}
|
|
skiagm::Expectations jsExpectations = gJsonExpectations->get(filename);
|
|
return jsExpectations.ignoreFailure();
|
|
}
|
|
|
|
/**
|
|
* Compare against an expectation for this filename, if there is one.
|
|
* @param digest GmResultDigest, computed from the decoded bitmap, to compare to the
|
|
* expectation.
|
|
* @param filename String used to find the expected value.
|
|
* @param failureArray Array to add a failure message to on failure.
|
|
* @param missingArray Array to add missing expectation to on failure.
|
|
* @return bool True in any of these cases:
|
|
* - the bitmap matches the expectation.
|
|
* False in any of these cases:
|
|
* - there is no expectations file.
|
|
* - there is an expectations file, but no expectation for this bitmap.
|
|
* - there is an expectation for this bitmap, but it did not match.
|
|
* - expectation could not be computed from the bitmap.
|
|
*/
|
|
static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest,
|
|
const char* filename,
|
|
SkTArray<SkString, false>* failureArray,
|
|
SkTArray<SkString, false>* missingArray) {
|
|
if (!digest.isValid()) {
|
|
if (failureArray != NULL) {
|
|
failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.",
|
|
filename);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (NULL == gJsonExpectations.get()) {
|
|
return false;
|
|
}
|
|
|
|
skiagm::Expectations jsExpectation = gJsonExpectations->get(filename);
|
|
if (jsExpectation.empty()) {
|
|
if (missingArray != NULL) {
|
|
missingArray->push_back().printf("decoded %s, but could not find expectation.",
|
|
filename);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (jsExpectation.match(digest)) {
|
|
return true;
|
|
}
|
|
|
|
if (failureArray != NULL) {
|
|
failureArray->push_back().printf("decoded %s, but the result does not match "
|
|
"expectations.",
|
|
filename);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper function to write a bitmap subset to a file. Only called if subsets were created
|
|
* and a writePath was provided. Creates a subdirectory called 'subsets' and writes a PNG to
|
|
* that directory. Also creates a subdirectory called 'extracted' and writes a bitmap created
|
|
* using extractSubset to a PNG in that directory. Both files will represent the same
|
|
* subrectangle and have the same name for comparison.
|
|
* @param writePath Parent directory to hold the folders for the PNG files to write. Must
|
|
* not be NULL.
|
|
* @param filename Basename of the original file. Used to name the new files. Must not be
|
|
* NULL.
|
|
* @param subsetDim String representing the dimensions of the subset. Used to name the new
|
|
* files. Must not be NULL.
|
|
* @param bitmapFromDecodeSubset Pointer to SkBitmap created by SkImageDecoder::DecodeSubset,
|
|
* using rect as the area to decode.
|
|
* @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call
|
|
* extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as
|
|
* bitmapFromDecodeSubset (assuming decodeSubset worked properly).
|
|
* @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset,
|
|
* using SkImageDecoder::decode to get the entire image. Used to create a PNG file for
|
|
* comparison to the PNG created by bitmapFromDecodeSubset.
|
|
* @return bool Whether the function succeeded at drawing the decoded subset and the extracted
|
|
* subset to files.
|
|
*/
|
|
static bool write_subset(const char* writePath, const char* filename, const char* subsetDim,
|
|
SkBitmap* bitmapFromDecodeSubset, SkIRect rect,
|
|
const SkBitmap& originalBitmap) {
|
|
// All parameters must be valid.
|
|
SkASSERT(writePath != NULL);
|
|
SkASSERT(filename != NULL);
|
|
SkASSERT(subsetDim != NULL);
|
|
SkASSERT(bitmapFromDecodeSubset != NULL);
|
|
|
|
// Create a subdirectory to hold the results of decodeSubset.
|
|
SkString dir = SkOSPath::SkPathJoin(writePath, "subsets");
|
|
if (!sk_mkdir(dir.c_str())) {
|
|
gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
|
|
"create a directory to write to.", subsetDim,
|
|
filename);
|
|
return false;
|
|
}
|
|
|
|
// Write the subset to a file whose name includes the dimensions.
|
|
SkString suffix = SkStringPrintf("_%s.png", subsetDim);
|
|
SkString outPath;
|
|
make_outname(&outPath, dir.c_str(), filename, suffix.c_str());
|
|
SkAssertResult(write_bitmap(outPath.c_str(), *bitmapFromDecodeSubset));
|
|
gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", outPath.c_str());
|
|
|
|
// Also use extractSubset from the original for visual comparison.
|
|
// Write the result to a file in a separate subdirectory.
|
|
SkBitmap extractedSubset;
|
|
if (!originalBitmap.extractSubset(&extractedSubset, rect)) {
|
|
gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
|
|
"extract a similar subset for comparison.",
|
|
subsetDim, filename);
|
|
return false;
|
|
}
|
|
|
|
SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted");
|
|
if (!sk_mkdir(dirExtracted.c_str())) {
|
|
gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
|
|
"create a directory for extractSubset comparison.",
|
|
subsetDim, filename);
|
|
return false;
|
|
}
|
|
|
|
make_outname(&outPath, dirExtracted.c_str(), filename, suffix.c_str());
|
|
SkAssertResult(write_bitmap(outPath.c_str(), extractedSubset));
|
|
return true;
|
|
}
|
|
|
|
// FIXME: This test could be run on windows/mac once we remove their dependence on
|
|
// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
|
|
#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
|
|
|
|
/**
|
|
* Dummy class for testing to ensure that a stream without a length decodes the same
|
|
* as a stream with a length.
|
|
*/
|
|
class FILEStreamWithoutLength : public SkFILEStream {
|
|
public:
|
|
FILEStreamWithoutLength(const char path[])
|
|
: INHERITED(path) {}
|
|
|
|
virtual bool hasLength() const SK_OVERRIDE {
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
typedef SkFILEStream INHERITED;
|
|
};
|
|
|
|
/**
|
|
* Test that decoding a stream which reports to not have a length still results in the
|
|
* same image as if it did report to have a length. Assumes that codec was used to
|
|
* successfully decode the file using SkFILEStream.
|
|
* @param srcPath The path to the file, for recreating the length-less stream.
|
|
* @param codec The SkImageDecoder originally used to decode srcPath, which will be used
|
|
* again to decode the length-less stream.
|
|
* @param digest GmResultDigest computed from decoding the stream the first time.
|
|
* Decoding the length-less stream is expected to result in a matching digest.
|
|
*/
|
|
static void test_stream_without_length(const char srcPath[], SkImageDecoder* codec,
|
|
const skiagm::GmResultDigest& digest) {
|
|
if (!digest.isValid()) {
|
|
// An error was already reported.
|
|
return;
|
|
}
|
|
SkASSERT(srcPath);
|
|
SkASSERT(codec);
|
|
FILEStreamWithoutLength stream(srcPath);
|
|
// This will only be called after a successful decode. Creating a stream from the same
|
|
// path should never fail.
|
|
SkASSERT(stream.isValid());
|
|
SkBitmap bm;
|
|
if (!codec->decode(&stream, &bm, gPrefConfig, SkImageDecoder::kDecodePixels_Mode)) {
|
|
gDecodeFailures.push_back().appendf("Without using getLength, %s failed to decode\n",
|
|
srcPath);
|
|
return;
|
|
}
|
|
skiagm::GmResultDigest lengthLessDigest(bm);
|
|
if (!lengthLessDigest.isValid()) {
|
|
gDecodeFailures.push_back().appendf("Without using getLength, %s failed to build "
|
|
"a digest\n", srcPath);
|
|
return;
|
|
}
|
|
if (!lengthLessDigest.equals(digest)) {
|
|
gDecodeFailures.push_back().appendf("Without using getLength, %s did not match digest "
|
|
"that uses getLength\n", srcPath);
|
|
}
|
|
}
|
|
#endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
|
|
|
|
static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
|
|
SkBitmap bitmap;
|
|
SkFILEStream stream(srcPath);
|
|
if (!stream.isValid()) {
|
|
gInvalidStreams.push_back().set(srcPath);
|
|
return;
|
|
}
|
|
|
|
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
|
|
if (NULL == codec) {
|
|
gMissingCodecs.push_back().set(srcPath);
|
|
return;
|
|
}
|
|
|
|
SkAutoTDelete<SkImageDecoder> ad(codec);
|
|
|
|
codec->setSkipWritingZeroes(FLAGS_skip);
|
|
stream.rewind();
|
|
|
|
// Create a string representing just the filename itself, for use in json expectations.
|
|
SkString basename = SkOSPath::SkBasename(srcPath);
|
|
const char* filename = basename.c_str();
|
|
|
|
if (!codec->decode(&stream, &bitmap, gPrefConfig,
|
|
SkImageDecoder::kDecodePixels_Mode)) {
|
|
if (expect_to_fail(filename)) {
|
|
gSuccessfulDecodes.push_back().appendf(
|
|
"failed to decode %s, which is a known failure.", srcPath);
|
|
} else {
|
|
gDecodeFailures.push_back().set(srcPath);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Test decoding just the bounds. The bounds should always match.
|
|
{
|
|
stream.rewind();
|
|
SkBitmap dim;
|
|
if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) {
|
|
SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath);
|
|
gDecodeFailures.push_back() = failure;
|
|
} else {
|
|
// Now check that the bounds match:
|
|
if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) {
|
|
SkString failure = SkStringPrintf("bounds do not match for %s", srcPath);
|
|
gDecodeFailures.push_back() = failure;
|
|
}
|
|
}
|
|
}
|
|
|
|
skiagm::GmResultDigest digest(bitmap);
|
|
if (compare_to_expectations_if_necessary(digest, filename,
|
|
&gDecodeFailures,
|
|
&gMissingExpectations)) {
|
|
gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
|
|
bitmap.height());
|
|
} else if (!FLAGS_mismatchPath.isEmpty()) {
|
|
SkString outPath;
|
|
make_outname(&outPath, FLAGS_mismatchPath[0], srcPath, ".png");
|
|
if (write_bitmap(outPath.c_str(), bitmap)) {
|
|
gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
|
|
} else {
|
|
gEncodeFailures.push_back().set(outPath);
|
|
}
|
|
}
|
|
|
|
// FIXME: This test could be run on windows/mac once we remove their dependence on
|
|
// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
|
|
#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
|
|
test_stream_without_length(srcPath, codec, digest);
|
|
#endif
|
|
|
|
if (writePath != NULL) {
|
|
SkString outPath;
|
|
make_outname(&outPath, writePath->c_str(), srcPath, ".png");
|
|
if (write_bitmap(outPath.c_str(), bitmap)) {
|
|
gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
|
|
} else {
|
|
gEncodeFailures.push_back().set(outPath);
|
|
}
|
|
}
|
|
|
|
write_expectations(bitmap, filename);
|
|
|
|
if (FLAGS_testSubsetDecoding) {
|
|
SkDEBUGCODE(bool couldRewind =) stream.rewind();
|
|
SkASSERT(couldRewind);
|
|
int width, height;
|
|
// Build the tile index for decoding subsets. If the image is 1x1, skip subset
|
|
// decoding since there are no smaller subsets.
|
|
if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
|
|
SkASSERT(bitmap.width() == width && bitmap.height() == height);
|
|
// Call decodeSubset multiple times:
|
|
SkRandom rand(0);
|
|
for (int i = 0; i < 5; i++) {
|
|
SkBitmap bitmapFromDecodeSubset;
|
|
// FIXME: Come up with a more representative set of rectangles.
|
|
SkIRect rect = generate_random_rect(&rand, width, height);
|
|
SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
|
|
rect.fRight, rect.fBottom);
|
|
if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefConfig)) {
|
|
SkString subsetName = SkStringPrintf("%s_%s", filename, subsetDim.c_str());
|
|
skiagm::GmResultDigest subsetDigest(bitmapFromDecodeSubset);
|
|
if (compare_to_expectations_if_necessary(subsetDigest,
|
|
subsetName.c_str(),
|
|
&gFailedSubsetDecodes,
|
|
&gMissingSubsetExpectations)) {
|
|
gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
|
|
subsetDim.c_str(), srcPath);
|
|
} else if (!FLAGS_mismatchPath.isEmpty()) {
|
|
write_subset(FLAGS_mismatchPath[0], filename, subsetDim.c_str(),
|
|
&bitmapFromDecodeSubset, rect, bitmap);
|
|
}
|
|
|
|
write_expectations(bitmapFromDecodeSubset, subsetName.c_str());
|
|
if (writePath != NULL) {
|
|
write_subset(writePath->c_str(), filename, subsetDim.c_str(),
|
|
&bitmapFromDecodeSubset, rect, bitmap);
|
|
}
|
|
} else {
|
|
gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s",
|
|
subsetDim.c_str(), srcPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do not attempt to re-encode A8, since our image encoders do not support encoding to A8.
|
|
if (FLAGS_reencode && bitmap.config() != SkBitmap::kA8_Config) {
|
|
// Encode to the format the file was originally in, or PNG if the encoder for the same
|
|
// format is unavailable.
|
|
SkImageDecoder::Format format = codec->getFormat();
|
|
if (SkImageDecoder::kUnknown_Format == format) {
|
|
if (stream.rewind()) {
|
|
format = SkImageDecoder::GetStreamFormat(&stream);
|
|
}
|
|
if (SkImageDecoder::kUnknown_Format == format) {
|
|
const char* dot = strrchr(srcPath, '.');
|
|
if (NULL != dot) {
|
|
format = guess_format_from_suffix(dot);
|
|
}
|
|
if (SkImageDecoder::kUnknown_Format == format) {
|
|
SkDebugf("Could not determine type for '%s'\n", srcPath);
|
|
format = SkImageDecoder::kPNG_Format;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
|
|
}
|
|
SkImageEncoder::Type type = format_to_type(format);
|
|
// format should never be kUnknown_Format, so type should never be kUnknown_Type.
|
|
SkASSERT(type != SkImageEncoder::kUnknown_Type);
|
|
|
|
SkImageEncoder* encoder = SkImageEncoder::Create(type);
|
|
if (NULL == encoder) {
|
|
type = SkImageEncoder::kPNG_Type;
|
|
encoder = SkImageEncoder::Create(type);
|
|
SkASSERT(encoder);
|
|
}
|
|
SkAutoTDelete<SkImageEncoder> ade(encoder);
|
|
// Encode to a stream.
|
|
SkDynamicMemoryWStream wStream;
|
|
if (!encoder->encodeStream(&wStream, bitmap, 100)) {
|
|
gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
|
|
suffix_for_type(type));
|
|
return;
|
|
}
|
|
|
|
SkAutoTUnref<SkData> data(wStream.copyToData());
|
|
if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
|
|
// Write the encoded data to a file. Do not write to PNG, which was already written.
|
|
SkString outPath;
|
|
make_outname(&outPath, writePath->c_str(), srcPath, suffix_for_type(type));
|
|
SkFILEWStream file(outPath.c_str());
|
|
if(file.write(data->data(), data->size())) {
|
|
gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
|
|
} else {
|
|
gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
|
|
}
|
|
}
|
|
// Ensure that the reencoded data can still be decoded.
|
|
SkMemoryStream memStream(data);
|
|
SkBitmap redecodedBitmap;
|
|
SkImageDecoder::Format formatOnSecondDecode;
|
|
if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefConfig,
|
|
SkImageDecoder::kDecodePixels_Mode,
|
|
&formatOnSecondDecode)) {
|
|
SkASSERT(format_to_type(formatOnSecondDecode) == type);
|
|
} else {
|
|
gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
|
|
srcPath, suffix_for_type(type));
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// If strings is not empty, print title, followed by each string on its own line starting
|
|
// with a tab.
|
|
// @return bool True if strings had at least one entry.
|
|
static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
|
|
if (strings.count() > 0) {
|
|
SkDebugf("%s:\n", title);
|
|
for (int i = 0; i < strings.count(); i++) {
|
|
SkDebugf("\t%s\n", strings[i].c_str());
|
|
}
|
|
SkDebugf("\n");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* If directory is non null and does not end with a path separator, append one.
|
|
* @param directory SkString representing the path to a directory. If the last character is not a
|
|
* path separator (specific to the current OS), append one.
|
|
*/
|
|
static void append_path_separator_if_necessary(SkString* directory) {
|
|
if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
|
|
directory->appendf("%c", SkPATH_SEPARATOR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the filename represents an image.
|
|
*/
|
|
static bool is_image_file(const char* filename) {
|
|
const char* gImageExtensions[] = {
|
|
".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP",
|
|
".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF"
|
|
};
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) {
|
|
if (SkStrEndsWith(filename, gImageExtensions[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int tool_main(int argc, char** argv);
|
|
int tool_main(int argc, char** argv) {
|
|
SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
|
|
SkCommandLineFlags::Parse(argc, argv);
|
|
|
|
if (FLAGS_readPath.count() < 1) {
|
|
SkDebugf("Folder(s) or image(s) to decode are required.\n");
|
|
return -1;
|
|
}
|
|
|
|
|
|
SkAutoGraphics ag;
|
|
|
|
if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) {
|
|
gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
|
|
(FLAGS_readExpectationsPath[0])));
|
|
}
|
|
|
|
SkString outDir;
|
|
SkString* outDirPtr;
|
|
|
|
if (FLAGS_writePath.count() == 1) {
|
|
outDir.set(FLAGS_writePath[0]);
|
|
append_path_separator_if_necessary(&outDir);
|
|
outDirPtr = &outDir;
|
|
} else {
|
|
outDirPtr = NULL;
|
|
}
|
|
|
|
if (FLAGS_config.count() == 1) {
|
|
// Only consider the first config specified on the command line.
|
|
const char* config = FLAGS_config[0];
|
|
if (0 == strcmp(config, "8888")) {
|
|
gPrefConfig = SkBitmap::kARGB_8888_Config;
|
|
} else if (0 == strcmp(config, "565")) {
|
|
gPrefConfig = SkBitmap::kRGB_565_Config;
|
|
} else if (0 == strcmp(config, "A8")) {
|
|
gPrefConfig = SkBitmap::kA8_Config;
|
|
} else if (0 != strcmp(config, "None")) {
|
|
SkDebugf("Invalid preferred config\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < FLAGS_readPath.count(); i++) {
|
|
const char* readPath = FLAGS_readPath[i];
|
|
if (strlen(readPath) < 1) {
|
|
break;
|
|
}
|
|
if (sk_isdir(readPath)) {
|
|
const char* dir = readPath;
|
|
SkOSFile::Iter iter(dir);
|
|
SkString filename;
|
|
while (iter.next(&filename)) {
|
|
if (!is_image_file(filename.c_str())) {
|
|
continue;
|
|
}
|
|
SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str());
|
|
decodeFileAndWrite(fullname.c_str(), outDirPtr);
|
|
}
|
|
} else if (sk_exists(readPath) && is_image_file(readPath)) {
|
|
decodeFileAndWrite(readPath, outDirPtr);
|
|
}
|
|
}
|
|
|
|
if (!FLAGS_createExpectationsPath.isEmpty()) {
|
|
// Use an empty value for everything besides expectations, since the reader only cares
|
|
// about the expectations.
|
|
Json::Value nullValue;
|
|
Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
|
|
nullValue, nullValue);
|
|
std::string jsonStdString = root.toStyledString();
|
|
SkFILEWStream stream(FLAGS_createExpectationsPath[0]);
|
|
stream.write(jsonStdString.c_str(), jsonStdString.length());
|
|
}
|
|
// Add some space, since codecs may print warnings without newline.
|
|
SkDebugf("\n\n");
|
|
|
|
bool failed = print_strings("Invalid files", gInvalidStreams);
|
|
failed |= print_strings("Missing codec", gMissingCodecs);
|
|
failed |= print_strings("Failed to decode", gDecodeFailures);
|
|
failed |= print_strings("Failed to encode", gEncodeFailures);
|
|
print_strings("Decoded", gSuccessfulDecodes);
|
|
print_strings("Missing expectations", gMissingExpectations);
|
|
|
|
if (FLAGS_testSubsetDecoding) {
|
|
failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
|
|
print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
|
|
print_strings("Missing subset expectations", gMissingSubsetExpectations);
|
|
}
|
|
|
|
return failed ? -1 : 0;
|
|
}
|
|
|
|
#if !defined SK_BUILD_FOR_IOS
|
|
int main(int argc, char * const argv[]) {
|
|
return tool_main(argc, (char**) argv);
|
|
}
|
|
#endif
|