skia2/tests/ImageDecodingTest.cpp
scroggo 2a1208017d Qualify the return value of SkImageDecoder::decode
Add a new enum to differentiate between a complete decode and a
partial decode (with the third value being failure). Return this
value from SkImageDecoder::onDecode (in all subclasses, plus
SkImageDecoder_empty) and ::decode.

For convenience, if the enum is treated as a boolean, success and
partial success are both considered true.

Note that the static helper functions (DecodeFile etc) still return
true and false (for one thing, this allows us to continue to use
SkImageDecoder::DecodeMemory as an SkPicture::InstallPixelRefProc in
SkPicture::CreateFromStream).

Also correctly report failure in SkASTCImageDecoder::onDecode when
SkTextureCompressor::DecompressBufferFromFormat fails.

BUG=skia:3037
BUG:b/17419670

Review URL: https://codereview.chromium.org/647023006
2014-10-22 12:07:00 -07:00

820 lines
31 KiB
C++

/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Resources.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkData.h"
#include "SkDecodingImageGenerator.h"
#include "SkDiscardableMemoryPool.h"
#include "SkForceLinking.h"
#include "SkGradientShader.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkImageGeneratorPriv.h"
#include "SkImagePriv.h"
#include "SkOSFile.h"
#include "SkPoint.h"
#include "SkShader.h"
#include "SkStream.h"
#include "SkString.h"
#include "Test.h"
__SK_FORCE_IMAGE_DECODER_LINKING;
/**
* Interprets c as an unpremultiplied color, and returns the
* premultiplied equivalent.
*/
static SkPMColor premultiply_unpmcolor(SkPMColor c) {
U8CPU a = SkGetPackedA32(c);
U8CPU r = SkGetPackedR32(c);
U8CPU g = SkGetPackedG32(c);
U8CPU b = SkGetPackedB32(c);
return SkPreMultiplyARGB(a, r, g, b);
}
/**
* Return true if this stream format should be skipped, due
* to do being an opaque format or not a valid format.
*/
static bool skip_image_format(SkImageDecoder::Format format) {
switch (format) {
case SkImageDecoder::kPNG_Format:
case SkImageDecoder::kWEBP_Format:
return false;
// Skip unknown since it will not be decoded anyway.
case SkImageDecoder::kUnknown_Format:
// Technically ICO and BMP supports alpha channels, but our image
// decoders do not, so skip them as well.
case SkImageDecoder::kICO_Format:
case SkImageDecoder::kBMP_Format:
// KTX and ASTC are texture formats so it's not particularly clear how to
// decode the alpha from them.
case SkImageDecoder::kKTX_Format:
case SkImageDecoder::kASTC_Format:
// The rest of these are opaque.
case SkImageDecoder::kPKM_Format:
case SkImageDecoder::kWBMP_Format:
case SkImageDecoder::kGIF_Format:
case SkImageDecoder::kJPEG_Format:
return true;
}
SkASSERT(false);
return true;
}
/**
* Test decoding an image in premultiplied mode and unpremultiplied mode and compare
* them.
*/
static void compare_unpremul(skiatest::Reporter* reporter, const SkString& filename) {
// Decode a resource:
SkBitmap bm8888;
SkBitmap bm8888Unpremul;
SkFILEStream stream(filename.c_str());
SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&stream);
if (skip_image_format(format)) {
return;
}
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
if (NULL == decoder.get()) {
SkDebugf("couldn't decode %s\n", filename.c_str());
return;
}
bool success = decoder->decode(&stream, &bm8888, kN32_SkColorType,
SkImageDecoder::kDecodePixels_Mode) != SkImageDecoder::kFailure;
if (!success) {
return;
}
success = stream.rewind();
REPORTER_ASSERT(reporter, success);
if (!success) {
return;
}
decoder->setRequireUnpremultipliedColors(true);
success = decoder->decode(&stream, &bm8888Unpremul, kN32_SkColorType,
SkImageDecoder::kDecodePixels_Mode) != SkImageDecoder::kFailure;
if (!success) {
return;
}
bool dimensionsMatch = bm8888.width() == bm8888Unpremul.width()
&& bm8888.height() == bm8888Unpremul.height();
REPORTER_ASSERT(reporter, dimensionsMatch);
if (!dimensionsMatch) {
return;
}
// Only do the comparison if the two bitmaps are both 8888.
if (bm8888.colorType() != kN32_SkColorType || bm8888Unpremul.colorType() != kN32_SkColorType) {
return;
}
// Now compare the two bitmaps.
for (int i = 0; i < bm8888.width(); ++i) {
for (int j = 0; j < bm8888.height(); ++j) {
// "c0" is the color of the premultiplied bitmap at (i, j).
const SkPMColor c0 = *bm8888.getAddr32(i, j);
// "c1" is the result of premultiplying the color of the unpremultiplied
// bitmap at (i, j).
const SkPMColor c1 = premultiply_unpmcolor(*bm8888Unpremul.getAddr32(i, j));
// Compute the difference for each component.
int da = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
int dr = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
int dg = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
int db = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
// Alpha component must be exactly the same.
REPORTER_ASSERT(reporter, 0 == da);
// Color components may not match exactly due to rounding error.
REPORTER_ASSERT(reporter, dr <= 1);
REPORTER_ASSERT(reporter, dg <= 1);
REPORTER_ASSERT(reporter, db <= 1);
}
}
}
static void test_unpremul(skiatest::Reporter* reporter) {
// This test cannot run if there is no resource path.
SkString resourcePath = GetResourcePath();
if (resourcePath.isEmpty()) {
SkDebugf("Could not run unpremul test because resourcePath not specified.");
return;
}
SkOSFile::Iter iter(resourcePath.c_str());
SkString basename;
if (iter.next(&basename)) {
do {
SkString filename = SkOSPath::Join(resourcePath.c_str(), basename.c_str());
// SkDebugf("about to decode \"%s\"\n", filename.c_str());
compare_unpremul(reporter, filename);
} while (iter.next(&basename));
} else {
SkDebugf("Failed to find any files :(\n");
}
}
#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
// Test that the alpha type is what we expect.
static void test_alphaType(skiatest::Reporter* reporter, const SkString& filename,
bool requireUnpremul) {
SkBitmap bm;
SkFILEStream stream(filename.c_str());
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
if (NULL == decoder.get()) {
return;
}
decoder->setRequireUnpremultipliedColors(requireUnpremul);
// Decode just the bounds. This should always succeed.
bool success = decoder->decode(&stream, &bm, kN32_SkColorType,
SkImageDecoder::kDecodeBounds_Mode);
REPORTER_ASSERT(reporter, success);
if (!success) {
return;
}
// Keep track of the alpha type for testing later. If the full decode
// succeeds, the alpha type should be the same, unless the full decode
// determined that the alpha type should actually be opaque, which may
// not be known when only decoding the bounds.
const SkAlphaType boundsAlphaType = bm.alphaType();
// rewind should always succeed on SkFILEStream.
success = stream.rewind();
REPORTER_ASSERT(reporter, success);
if (!success) {
return;
}
success = decoder->decode(&stream, &bm, kN32_SkColorType, SkImageDecoder::kDecodePixels_Mode);
if (!success) {
// When the decoder is set to require unpremul, if it does not support
// unpremul it will fail. This is the only reason the decode should
// fail (since we know the files we are using to test can be decoded).
REPORTER_ASSERT(reporter, requireUnpremul);
return;
}
// The bounds decode should return with either the requested
// premul/unpremul or opaque, if that value could be determined when only
// decoding the bounds.
if (requireUnpremul) {
REPORTER_ASSERT(reporter, kUnpremul_SkAlphaType == boundsAlphaType
|| kOpaque_SkAlphaType == boundsAlphaType);
} else {
REPORTER_ASSERT(reporter, kPremul_SkAlphaType == boundsAlphaType
|| kOpaque_SkAlphaType == boundsAlphaType);
}
// When decoding the full image, the alpha type should match the one
// returned by the bounds decode, unless the full decode determined that
// the alpha type is actually opaque.
REPORTER_ASSERT(reporter, bm.alphaType() == boundsAlphaType
|| bm.alphaType() == kOpaque_SkAlphaType);
}
DEF_TEST(ImageDecoding_alphaType, reporter) {
SkString resourcePath = GetResourcePath();
if (resourcePath.isEmpty()) {
SkDebugf("Could not run alphaType test because resourcePath not specified.");
return;
}
SkOSFile::Iter iter(resourcePath.c_str());
SkString basename;
if (iter.next(&basename)) {
do {
SkString filename = SkOSPath::Join(resourcePath.c_str(), basename.c_str());
for (int truth = 0; truth <= 1; ++truth) {
test_alphaType(reporter, filename, SkToBool(truth));
}
} while (iter.next(&basename));
} else {
SkDebugf("Failed to find any files :(\n");
}
}
// Using known images, test that decoding into unpremul and premul behave as expected.
DEF_TEST(ImageDecoding_unpremul, reporter) {
SkString resourcePath = GetResourcePath();
if (resourcePath.isEmpty()) {
SkDebugf("Could not run unpremul test because resourcePath not specified.");
return;
}
const char* root = "half-transparent-white-pixel";
const char* suffixes[] = { ".png", ".webp" };
for (size_t i = 0; i < SK_ARRAY_COUNT(suffixes); ++i) {
SkString basename = SkStringPrintf("%s%s", root, suffixes[i]);
SkString fullName = SkOSPath::Join(resourcePath.c_str(), basename.c_str());
SkBitmap bm;
SkFILEStream stream(fullName.c_str());
if (!stream.isValid()) {
SkDebugf("file %s missing from resource directoy %s\n",
basename.c_str(), resourcePath.c_str());
continue;
}
// This should never fail since we know the images we're decoding.
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
REPORTER_ASSERT(reporter, decoder.get());
if (NULL == decoder.get()) {
continue;
}
// Test unpremultiplied. We know what color this should result in.
decoder->setRequireUnpremultipliedColors(true);
bool success = decoder->decode(&stream, &bm, kN32_SkColorType,
SkImageDecoder::kDecodePixels_Mode);
REPORTER_ASSERT(reporter, success);
if (!success) {
continue;
}
REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1);
{
SkAutoLockPixels alp(bm);
REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7fffffff);
}
success = stream.rewind();
REPORTER_ASSERT(reporter, success);
if (!success) {
continue;
}
// Test premultiplied. Once again, we know which color this should
// result in.
decoder->setRequireUnpremultipliedColors(false);
success = decoder->decode(&stream, &bm, kN32_SkColorType,
SkImageDecoder::kDecodePixels_Mode);
REPORTER_ASSERT(reporter, success);
if (!success) {
continue;
}
REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1);
{
SkAutoLockPixels alp(bm);
REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7f7f7f7f);
}
}
}
#endif // SK_BUILD_FOR_UNIX/ANDROID skbug.com/2388
#ifdef SK_DEBUG
// Create a stream containing a bitmap encoded to Type type.
static SkMemoryStream* create_image_stream(SkImageEncoder::Type type) {
SkBitmap bm;
const int size = 50;
bm.allocN32Pixels(size, size);
SkCanvas canvas(bm);
SkPoint points[2] = {
{ SkIntToScalar(0), SkIntToScalar(0) },
{ SkIntToScalar(size), SkIntToScalar(size) }
};
SkColor colors[2] = { SK_ColorWHITE, SK_ColorBLUE };
SkShader* shader = SkGradientShader::CreateLinear(points, colors, NULL,
SK_ARRAY_COUNT(colors),
SkShader::kClamp_TileMode);
SkPaint paint;
paint.setShader(shader)->unref();
canvas.drawPaint(paint);
// Now encode it to a stream.
SkAutoTUnref<SkData> data(SkImageEncoder::EncodeData(bm, type, 100));
if (NULL == data.get()) {
return NULL;
}
return SkNEW_ARGS(SkMemoryStream, (data.get()));
}
// For every format that supports tile based decoding, ensure that
// calling decodeSubset will not fail if the caller has unreffed the
// stream provided in buildTileIndex.
// Only runs in debug mode since we are testing for a crash.
static void test_stream_life() {
const SkImageEncoder::Type gTypes[] = {
#ifdef SK_BUILD_FOR_ANDROID
SkImageEncoder::kJPEG_Type,
SkImageEncoder::kPNG_Type,
#endif
SkImageEncoder::kWEBP_Type,
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gTypes); ++i) {
// SkDebugf("encoding to %i\n", i);
SkAutoTUnref<SkMemoryStream> stream(create_image_stream(gTypes[i]));
if (NULL == stream.get()) {
SkDebugf("no stream\n");
continue;
}
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(stream));
if (NULL == decoder.get()) {
SkDebugf("no decoder\n");
continue;
}
int width, height;
if (!decoder->buildTileIndex(stream.get(), &width, &height)) {
SkDebugf("could not build a tile index\n");
continue;
}
// Now unref the stream to make sure it survives
stream.reset(NULL);
SkBitmap bm;
decoder->decodeSubset(&bm, SkIRect::MakeWH(width, height), kN32_SkColorType);
}
}
// Test inside SkScaledBitmapSampler.cpp
extern void test_row_proc_choice();
#endif // SK_DEBUG
DEF_TEST(ImageDecoding, reporter) {
test_unpremul(reporter);
#ifdef SK_DEBUG
test_stream_life();
test_row_proc_choice();
#endif
}
// expected output for 8x8 bitmap
static const int kExpectedWidth = 8;
static const int kExpectedHeight = 8;
static const SkColor kExpectedPixels[] = {
0xffbba570, 0xff395f5d, 0xffe25c39, 0xff197666,
0xff3cba27, 0xffdefcb0, 0xffc13874, 0xfffa0093,
0xffbda60e, 0xffc01db6, 0xff2bd688, 0xff9362d4,
0xffc641b2, 0xffa5cede, 0xff606eba, 0xff8f4bf3,
0xff3bf742, 0xff8f02a8, 0xff5509df, 0xffc7027e,
0xff24aa8a, 0xff886c96, 0xff625481, 0xff403689,
0xffc52152, 0xff78ccd6, 0xffdcb4ab, 0xff09d27d,
0xffca00f3, 0xff605d47, 0xff446fb2, 0xff576e46,
0xff273df9, 0xffb41a83, 0xfff812c3, 0xffccab67,
0xff034218, 0xff7db9a7, 0xff821048, 0xfffe4ab4,
0xff6fac98, 0xff941d27, 0xff5fe411, 0xfffbb283,
0xffd86e99, 0xff169162, 0xff71128c, 0xff39cab4,
0xffa7fe63, 0xff4c956b, 0xffbc22e0, 0xffb272e4,
0xff129f4a, 0xffe34513, 0xff3d3742, 0xffbd190a,
0xffb07222, 0xff2e23f8, 0xfff089d9, 0xffb35738,
0xffa86022, 0xff3340fe, 0xff95fe71, 0xff6a71df
};
SK_COMPILE_ASSERT((kExpectedWidth * kExpectedHeight)
== SK_ARRAY_COUNT(kExpectedPixels), array_size_mismatch);
DEF_TEST(WebP, reporter) {
const unsigned char encodedWebP[] = {
0x52, 0x49, 0x46, 0x46, 0x2c, 0x01, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
0x56, 0x50, 0x38, 0x4c, 0x20, 0x01, 0x00, 0x00, 0x2f, 0x07, 0xc0, 0x01,
0x00, 0xff, 0x01, 0x45, 0x03, 0x00, 0xe2, 0xd5, 0xae, 0x60, 0x2b, 0xad,
0xd9, 0x68, 0x76, 0xb6, 0x8d, 0x6a, 0x1d, 0xc0, 0xe6, 0x19, 0xd6, 0x16,
0xb7, 0xb4, 0xef, 0xcf, 0xc3, 0x15, 0x6c, 0xb3, 0xbd, 0x77, 0x0d, 0x85,
0x6d, 0x1b, 0xa9, 0xb1, 0x2b, 0xdc, 0x3d, 0x83, 0xdb, 0x00, 0x00, 0xc8,
0x26, 0xe5, 0x01, 0x99, 0x8a, 0xd5, 0xdd, 0xfc, 0x82, 0xcd, 0xcd, 0x9a,
0x8c, 0x13, 0xcc, 0x1b, 0xba, 0xf5, 0x05, 0xdb, 0xee, 0x6a, 0xdb, 0x38,
0x60, 0xfe, 0x43, 0x2c, 0xd4, 0x6a, 0x99, 0x4d, 0xc6, 0xc0, 0xd3, 0x28,
0x1b, 0xc1, 0xb1, 0x17, 0x4e, 0x43, 0x0e, 0x3d, 0x27, 0xe9, 0xe4, 0x84,
0x4f, 0x24, 0x62, 0x69, 0x85, 0x43, 0x8d, 0xc2, 0x04, 0x00, 0x07, 0x59,
0x60, 0xfd, 0x8b, 0x4d, 0x60, 0x32, 0x72, 0xcf, 0x88, 0x0c, 0x2f, 0x2f,
0xad, 0x62, 0xbd, 0x27, 0x09, 0x16, 0x70, 0x78, 0x6c, 0xd9, 0x82, 0xef,
0x1a, 0xa2, 0xcc, 0xf0, 0xf1, 0x6f, 0xd8, 0x78, 0x2e, 0x39, 0xa1, 0xcf,
0x14, 0x4b, 0x89, 0xb4, 0x1b, 0x48, 0x15, 0x7c, 0x48, 0x6f, 0x8c, 0x20,
0xb7, 0x00, 0xcf, 0xfc, 0xdb, 0xd0, 0xe9, 0xe7, 0x42, 0x09, 0xa4, 0x03,
0x40, 0xac, 0xda, 0x40, 0x01, 0x00, 0x5f, 0xa1, 0x3d, 0x64, 0xe1, 0xf4,
0x03, 0x45, 0x29, 0xe0, 0xe2, 0x4a, 0xc3, 0xa2, 0xe8, 0xe0, 0x25, 0x12,
0x74, 0xc6, 0xe8, 0xfb, 0x93, 0x4f, 0x9f, 0x5e, 0xc0, 0xa6, 0x91, 0x1b,
0xa4, 0x24, 0x82, 0xc3, 0x61, 0x07, 0x4c, 0x49, 0x4f, 0x53, 0xae, 0x5f,
0x5d, 0x39, 0x36, 0xc0, 0x5b, 0x57, 0x54, 0x60, 0x10, 0x00, 0x00, 0xd1,
0x68, 0xb6, 0x6d, 0xdb, 0x36, 0x22, 0xfa, 0x1f, 0x35, 0x75, 0x22, 0xec,
0x31, 0xbc, 0x5d, 0x8f, 0x87, 0x53, 0xa2, 0x05, 0x8c, 0x2f, 0xcd, 0xa8,
0xa7, 0xf3, 0xa3, 0xbd, 0x83, 0x8b, 0x2a, 0xc8, 0x58, 0xf5, 0xac, 0x80,
0xe3, 0xfe, 0x66, 0xa4, 0x7c, 0x1b, 0x6c, 0xd1, 0xa9, 0xd8, 0x14, 0xd0,
0xc5, 0xb5, 0x39, 0x71, 0x97, 0x19, 0x19, 0x1b
};
SkAutoDataUnref encoded(SkData::NewWithCopy(encodedWebP,
sizeof(encodedWebP)));
SkBitmap bm;
bool success = SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(encoded,
SkDecodingImageGenerator::Options()), &bm);
REPORTER_ASSERT(reporter, success);
if (!success) {
return;
}
SkAutoLockPixels alp(bm);
bool rightSize = ((kExpectedWidth == bm.width())
&& (kExpectedHeight == bm.height()));
REPORTER_ASSERT(reporter, rightSize);
if (rightSize) {
bool error = false;
const SkColor* correctPixel = kExpectedPixels;
for (int y = 0; y < bm.height(); ++y) {
for (int x = 0; x < bm.width(); ++x) {
error |= (*correctPixel != bm.getColor(x, y));
++correctPixel;
}
}
REPORTER_ASSERT(reporter, !error);
}
}
////////////////////////////////////////////////////////////////////////////////
// example of how Android will do this inside their BitmapFactory
static SkPixelRef* install_pixel_ref(SkBitmap* bitmap,
SkStreamRewindable* stream,
int sampleSize, bool ditherImage) {
SkASSERT(bitmap != NULL);
SkASSERT(stream != NULL);
SkASSERT(stream->rewind());
SkASSERT(stream->unique());
SkColorType colorType = bitmap->colorType();
SkDecodingImageGenerator::Options opts(sampleSize, ditherImage, colorType);
if (SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(stream, opts), bitmap)) {
return bitmap->pixelRef();
}
return NULL;
}
/**
* A test for the SkDecodingImageGenerator::Create and
* SkInstallDiscardablePixelRef functions.
*/
DEF_TEST(ImprovedBitmapFactory, reporter) {
SkString pngFilename = GetResourcePath("randPixels.png");
SkAutoTUnref<SkStreamRewindable> stream(SkStream::NewFromFile(pngFilename.c_str()));
if (sk_exists(pngFilename.c_str())) {
SkBitmap bm;
SkAssertResult(bm.setInfo(SkImageInfo::MakeN32Premul(1, 1)));
REPORTER_ASSERT(reporter,
install_pixel_ref(&bm, stream.detach(), 1, true));
SkAutoLockPixels alp(bm);
REPORTER_ASSERT(reporter, bm.getPixels());
}
}
////////////////////////////////////////////////////////////////////////////////
#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
static inline bool check_rounding(int value, int dividend, int divisor) {
// returns true if the value is greater than floor(dividend/divisor)
// and less than SkNextPow2(ceil(dividend - divisor))
return (((divisor * value) > (dividend - divisor))
&& value <= SkNextPow2(((dividend - 1) / divisor) + 1));
}
#endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
#define kBackwards_SkColorType kRGBA_8888_SkColorType
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
#define kBackwards_SkColorType kBGRA_8888_SkColorType
#else
#error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"
#endif
static inline const char* SkColorType_to_string(SkColorType colorType) {
switch(colorType) {
case kAlpha_8_SkColorType: return "Alpha_8";
case kRGB_565_SkColorType: return "RGB_565";
case kARGB_4444_SkColorType: return "ARGB_4444";
case kN32_SkColorType: return "N32";
case kBackwards_SkColorType: return "Backwards";
case kIndex_8_SkColorType: return "Index_8";
default: return "ERROR";
}
}
static inline const char* options_colorType(
const SkDecodingImageGenerator::Options& opts) {
if (opts.fUseRequestedColorType) {
return SkColorType_to_string(opts.fRequestedColorType);
} else {
return "(none)";
}
}
static inline const char* yn(bool value) {
if (value) {
return "yes";
} else {
return "no";
}
}
/**
* Given either a SkStream or a SkData, try to decode the encoded
* image using the specified options and report errors.
*/
static void test_options(skiatest::Reporter* reporter,
const SkDecodingImageGenerator::Options& opts,
SkStreamRewindable* encodedStream,
SkData* encodedData,
bool useData,
const SkString& path) {
SkBitmap bm;
bool success = false;
if (useData) {
if (NULL == encodedData) {
return;
}
success = SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(encodedData, opts), &bm);
} else {
if (NULL == encodedStream) {
return;
}
success = SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts), &bm);
}
if (!success) {
if (opts.fUseRequestedColorType
&& (kARGB_4444_SkColorType == opts.fRequestedColorType)) {
return; // Ignore known conversion inabilities.
}
// If we get here, it's a failure and we will need more
// information about why it failed.
ERRORF(reporter, "Bounds decode failed [sampleSize=%d dither=%s "
"colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage),
options_colorType(opts), path.c_str());
return;
}
#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
// Android is the only system that use Skia's image decoders in
// production. For now, we'll only verify that samplesize works
// on systems where it already is known to work.
REPORTER_ASSERT(reporter, check_rounding(bm.height(), kExpectedHeight,
opts.fSampleSize));
REPORTER_ASSERT(reporter, check_rounding(bm.width(), kExpectedWidth,
opts.fSampleSize));
// The ImageDecoder API doesn't guarantee that SampleSize does
// anything at all, but the decoders that this test excercises all
// produce an output size in the following range:
// (((sample_size * out_size) > (in_size - sample_size))
// && out_size <= SkNextPow2(((in_size - 1) / sample_size) + 1));
#endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX
SkAutoLockPixels alp(bm);
if (bm.getPixels() == NULL) {
ERRORF(reporter, "Pixel decode failed [sampleSize=%d dither=%s "
"colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage),
options_colorType(opts), path.c_str());
return;
}
SkColorType requestedColorType = opts.fRequestedColorType;
REPORTER_ASSERT(reporter,
(!opts.fUseRequestedColorType)
|| (bm.colorType() == requestedColorType));
// Condition under which we should check the decoding results:
if ((kN32_SkColorType == bm.colorType())
&& (!path.endsWith(".jpg")) // lossy
&& (opts.fSampleSize == 1)) { // scaled
const SkColor* correctPixels = kExpectedPixels;
SkASSERT(bm.height() == kExpectedHeight);
SkASSERT(bm.width() == kExpectedWidth);
int pixelErrors = 0;
for (int y = 0; y < bm.height(); ++y) {
for (int x = 0; x < bm.width(); ++x) {
if (*correctPixels != bm.getColor(x, y)) {
++pixelErrors;
}
++correctPixels;
}
}
if (pixelErrors != 0) {
ERRORF(reporter, "Pixel-level mismatch (%d of %d) "
"[sampleSize=%d dither=%s colorType=%s %s]",
pixelErrors, kExpectedHeight * kExpectedWidth,
opts.fSampleSize, yn(opts.fDitherImage),
options_colorType(opts), path.c_str());
}
}
}
/**
* SkDecodingImageGenerator has an Options struct which lets the
* client of the generator set sample size, dithering, and bitmap
* config. This test loops through many possible options and tries
* them on a set of 5 small encoded images (each in a different
* format). We test both SkData and SkStreamRewindable decoding.
*/
DEF_TEST(ImageDecoderOptions, reporter) {
const char* files[] = {
"randPixels.bmp",
"randPixels.jpg",
"randPixels.png",
"randPixels.webp",
#if !defined(SK_BUILD_FOR_WIN)
// TODO(halcanary): Find out why this fails sometimes.
"randPixels.gif",
#endif
};
SkString resourceDir = GetResourcePath();
if (!sk_exists(resourceDir.c_str())) {
return;
}
int scaleList[] = {1, 2, 3, 4};
bool ditherList[] = {true, false};
SkColorType colorList[] = {
kAlpha_8_SkColorType,
kRGB_565_SkColorType,
kARGB_4444_SkColorType, // Most decoders will fail on 4444.
kN32_SkColorType
// Note that indexed color is left out of the list. Lazy
// decoding doesn't do indexed color.
};
const bool useDataList[] = {true, false};
for (size_t fidx = 0; fidx < SK_ARRAY_COUNT(files); ++fidx) {
SkString path = SkOSPath::Join(resourceDir.c_str(), files[fidx]);
if (!sk_exists(path.c_str())) {
continue;
}
SkAutoDataUnref encodedData(SkData::NewFromFileName(path.c_str()));
REPORTER_ASSERT(reporter, encodedData.get() != NULL);
SkAutoTUnref<SkStreamRewindable> encodedStream(
SkStream::NewFromFile(path.c_str()));
REPORTER_ASSERT(reporter, encodedStream.get() != NULL);
for (size_t i = 0; i < SK_ARRAY_COUNT(scaleList); ++i) {
for (size_t j = 0; j < SK_ARRAY_COUNT(ditherList); ++j) {
for (size_t m = 0; m < SK_ARRAY_COUNT(useDataList); ++m) {
for (size_t k = 0; k < SK_ARRAY_COUNT(colorList); ++k) {
SkDecodingImageGenerator::Options opts(scaleList[i],
ditherList[j],
colorList[k]);
test_options(reporter, opts, encodedStream, encodedData,
useDataList[m], path);
}
SkDecodingImageGenerator::Options options(scaleList[i],
ditherList[j]);
test_options(reporter, options, encodedStream, encodedData,
useDataList[m], path);
}
}
}
}
}
DEF_TEST(DiscardablePixelRef_SecondLockColorTableCheck, r) {
SkString resourceDir = GetResourcePath();
SkString path = SkOSPath::Join(resourceDir.c_str(), "randPixels.gif");
if (!sk_exists(path.c_str())) {
return;
}
SkAutoDataUnref encoded(SkData::NewFromFileName(path.c_str()));
SkBitmap bitmap;
if (!SkInstallDiscardablePixelRef(
SkDecodingImageGenerator::Create(
encoded, SkDecodingImageGenerator::Options()), &bitmap)) {
#ifndef SK_BUILD_FOR_WIN
ERRORF(r, "SkInstallDiscardablePixelRef [randPixels.gif] failed.");
#endif
return;
}
if (kIndex_8_SkColorType != bitmap.colorType()) {
return;
}
{
SkAutoLockPixels alp(bitmap);
REPORTER_ASSERT(r, bitmap.getColorTable() && "first pass");
}
{
SkAutoLockPixels alp(bitmap);
REPORTER_ASSERT(r, bitmap.getColorTable() && "second pass");
}
}
////////////////////////////////////////////////////////////////////////////////
namespace {
class SingleAllocator : public SkBitmap::Allocator {
public:
SingleAllocator(void* p, size_t s) : fPixels(p), fSize(s) { }
~SingleAllocator() {}
// If the pixels in fPixels are big enough, use them.
virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) SK_OVERRIDE {
SkASSERT(bm);
if (bm->info().getSafeSize(bm->rowBytes()) <= fSize) {
bm->setPixels(fPixels, ct);
fPixels = NULL;
fSize = 0;
return true;
}
return bm->tryAllocPixels(NULL, ct);
}
bool ready() { return fPixels != NULL; }
private:
void* fPixels;
size_t fSize;
};
} // namespace
/* This tests for a bug in libjpeg where INT32 is typedefed to long
and memory can be written to outside of the array. */
DEF_TEST(ImageDecoding_JpegOverwrite, r) {
SkString resourceDir = GetResourcePath();
SkString path = SkOSPath::Join(resourceDir.c_str(), "randPixels.jpg");
SkAutoTUnref<SkStreamAsset> stream(
SkStream::NewFromFile(path.c_str()));
if (!stream.get()) {
SkDebugf("\nPath '%s' missing.\n", path.c_str());
return;
}
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(stream));
if (NULL == decoder.get()) {
ERRORF(r, "\nSkImageDecoder::Factory failed.\n");
return;
}
SkAssertResult(stream->rewind());
static const uint16_t sentinal = 0xBEEF;
static const int pixelCount = 16;
SkAutoTMalloc<uint16_t> pixels(pixelCount + 1);
// pixels.get() should be 4-byte aligned.
// This is necessary to reproduce the bug.
pixels[pixelCount] = sentinal; // This value should not be changed.
SkAutoTUnref<SingleAllocator> allocator(
SkNEW_ARGS(SingleAllocator,
((void*)pixels.get(), sizeof(uint16_t) * pixelCount)));
decoder->setAllocator(allocator);
decoder->setSampleSize(2);
SkBitmap bitmap;
bool success = decoder->decode(stream, &bitmap, kRGB_565_SkColorType,
SkImageDecoder::kDecodePixels_Mode) != SkImageDecoder::kFailure;
REPORTER_ASSERT(r, success);
REPORTER_ASSERT(r, !allocator->ready()); // Decoder used correct memory
REPORTER_ASSERT(r, sentinal == pixels[pixelCount]);
}