Add Options to SkDecodingImageGenerator, simplify API.
Motivation: We want to remove redundant classes from Skia. To
that end we want to remove SkImageRef and its subclasses and
replace their uses with SkDiscardablePixelRef +
SkDecodingImageGenerator. Since Android uses SkImageRef, we need
to make sure that SkDecodingImageGenerator allows all of the
settings that Android exposes in BitmapFactory.Options.
To that end, we have created an Options struct for the
SkDecodingImageGenerator which lets the client of the generator set
sample size, dithering, and bitmap config.
We have made the SkDecodingImageGenerator constructor private
and replaced the SkDecodingImageGenerator::Install functions
with a SkDecodingImageGenerator::Create functions (one for
SkData and one for SkStream) which now take a
SkDecodingImageGenerator::Options struct.
Also added a ImageDecoderOptions test which loops through a list
of sets of options and tries them on a set of 5 small encoded
images.
Also updated several users of SkDecodingImageGenerator::Install to
follow new call signature - gm/factory.cpp, LazyDecodeBitmap.cpp,
and PictureTest.cpp, CachedDecodingPixelRefTest.cpp.
We also added a new ImprovedBitmapFactory Test which simulates the
exact function that Android will need to modify to use this,
installPixelRef() in BitmapFactory.
R=reed@google.com, scroggo@google.com
Committed: https://code.google.com/p/skia/source/detail?r=12744
Review URL: https://codereview.chromium.org/93703004
git-svn-id: http://skia.googlecode.com/svn/trunk@12855 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tests/ImageDecodingTest.cpp b/tests/ImageDecodingTest.cpp
index bbc31cc..d39a51a 100644
--- a/tests/ImageDecodingTest.cpp
+++ b/tests/ImageDecodingTest.cpp
@@ -13,10 +13,13 @@
#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 "SkImageGenerator.h"
+#include "SkImagePriv.h"
#include "SkOSFile.h"
#include "SkPoint.h"
#include "SkShader.h"
@@ -153,7 +156,7 @@
if (iter.next(&basename)) {
do {
SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str());
- //SkDebugf("about to decode \"%s\"\n", filename.c_str());
+ // SkDebugf("about to decode \"%s\"\n", filename.c_str());
compare_unpremul(reporter, filename);
} while (iter.next(&basename));
} else {
@@ -201,7 +204,7 @@
SkImageEncoder::kWEBP_Type,
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gTypes); ++i) {
- //SkDebugf("encoding to %i\n", i);
+ // SkDebugf("encoding to %i\n", i);
SkAutoTUnref<SkMemoryStream> stream(create_image_stream(gTypes[i]));
if (NULL == stream.get()) {
SkDebugf("no stream\n");
@@ -228,7 +231,7 @@
// Test inside SkScaledBitmapSampler.cpp
extern void test_row_proc_choice();
-#endif // SK_DEBUG
+#endif // SK_DEBUG
DEF_TEST(ImageDecoding, reporter) {
test_unpremul(reporter);
@@ -239,6 +242,31 @@
}
////////////////////////////////////////////////////////////////////////////////
+namespace {
+// expected output for 8x8 bitmap
+const int kExpectedWidth = 8;
+const int kExpectedHeight = 8;
+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);
+} // namespace
DEF_TEST(WebP, reporter) {
const unsigned char encodedWebP[] = {
@@ -269,38 +297,26 @@
0xe3, 0xfe, 0x66, 0xa4, 0x7c, 0x1b, 0x6c, 0xd1, 0xa9, 0xd8, 0x14, 0xd0,
0xc5, 0xb5, 0x39, 0x71, 0x97, 0x19, 0x19, 0x1b
};
- const SkColor thePixels[] = {
- 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
- };
SkAutoDataUnref encoded(SkData::NewWithCopy(encodedWebP,
sizeof(encodedWebP)));
SkBitmap bm;
- bool success = SkDecodingImageGenerator::Install(encoded, &bm, NULL);
+
+ bool success = SkInstallDiscardablePixelRef(
+ SkDecodingImageGenerator::Create(encoded,
+ SkDecodingImageGenerator::Options()), &bm, NULL);
+
REPORTER_ASSERT(reporter, success);
if (!success) {
return;
}
SkAutoLockPixels alp(bm);
- bool rightSize = SK_ARRAY_COUNT(thePixels) == bm.width() * bm.height();
+
+ bool rightSize = ((kExpectedWidth == bm.width())
+ && (kExpectedHeight == bm.height()));
REPORTER_ASSERT(reporter, rightSize);
if (rightSize) {
bool error = false;
- const SkColor* correctPixel = thePixels;
+ 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));
@@ -310,3 +326,256 @@
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;
+ if (!SkBitmapConfigToColorType(bitmap->config(), &colorType)) {
+ return NULL;
+ }
+ SkDecodingImageGenerator::Options opts(sampleSize, ditherImage, colorType);
+ SkAutoTDelete<SkImageGenerator> gen(
+ SkDecodingImageGenerator::Create(stream, opts));
+ SkImageInfo info;
+ if ((NULL == gen.get()) || !gen->getInfo(&info)) {
+ return NULL;
+ }
+ SkDiscardableMemory::Factory* factory = NULL;
+ if (info.getSafeSize(info.minRowBytes()) < (32 * 1024)) {
+ // only use ashmem for large images, since mmaps come at a price
+ factory = SkGetGlobalDiscardableMemoryPool();
+ }
+ if (SkInstallDiscardablePixelRef(gen.detach(), bitmap, factory)) {
+ return bitmap->pixelRef();
+ }
+ return NULL;
+}
+/**
+ * A test for the SkDecodingImageGenerator::Create and
+ * SkInstallDiscardablePixelRef functions.
+ */
+DEF_TEST(ImprovedBitmapFactory, reporter) {
+ SkString resourcePath = skiatest::Test::GetResourcePath();
+ SkString directory = SkOSPath::SkPathJoin(resourcePath.c_str(), "encoding");
+ SkString path = SkOSPath::SkPathJoin(directory.c_str(), "randPixels.png");
+ SkAutoTUnref<SkStreamRewindable> stream(
+ SkStream::NewFromFile(path.c_str()));
+ if (sk_exists(path.c_str())) {
+ SkBitmap bm;
+ SkAssertResult(bm.setConfig(SkBitmap::kARGB_8888_Config, 1, 1));
+ REPORTER_ASSERT(reporter,
+ NULL != install_pixel_ref(&bm, stream.detach(), 1, true));
+ SkAutoLockPixels alp(bm);
+ REPORTER_ASSERT(reporter, NULL != 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 (dividend/divisor) rounds up OR down to value
+ return (((divisor * value) > (dividend - divisor))
+ && ((divisor * value) < (dividend + divisor)));
+}
+#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 kPMColor_SkColorType: return "PMColor";
+ case kBackwards_SkColorType: return "Backwards";
+ case kIndex_8_SkColorType: return "Index_8";
+ default: return "ERROR";
+ }
+}
+
+/**
+ * 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, NULL);
+ } else {
+ if (NULL == encodedStream) {
+ return;
+ }
+ success = SkInstallDiscardablePixelRef(
+ SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts),
+ &bm, NULL);
+ }
+ 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.
+ reporter->reportFailed(SkStringPrintf(
+ "Bounds decode failed "
+ "[sampleSize=%d dither=%s colorType=%s %s] %s:%d",
+ opts.fSampleSize, (opts.fDitherImage ? "yes" : "no"),
+ (opts.fUseRequestedColorType
+ ? SkColorType_to_string(opts.fRequestedColorType) : "(none)"),
+ path.c_str(), __FILE__, __LINE__));
+ 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));
+ #endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX
+ SkAutoLockPixels alp(bm);
+ if (bm.getPixels() == NULL) {
+ reporter->reportFailed(SkStringPrintf(
+ "Pixel decode failed "
+ "[sampleSize=%d dither=%s colorType=%s %s] %s:%d",
+ opts.fSampleSize, (opts.fDitherImage ? "yes" : "no"),
+ (opts.fUseRequestedColorType
+ ? SkColorType_to_string(opts.fRequestedColorType) : "(none)"),
+ path.c_str(), __FILE__, __LINE__));
+ return;
+ }
+
+ SkBitmap::Config requestedConfig
+ = SkColorTypeToBitmapConfig(opts.fRequestedColorType);
+ REPORTER_ASSERT(reporter,
+ (!opts.fUseRequestedColorType)
+ || (bm.config() == requestedConfig));
+
+ // Condition under which we should check the decoding results:
+ if ((SkBitmap::kARGB_8888_Config == bm.config())
+ && (!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) {
+ reporter->reportFailed(SkStringPrintf(
+ "Pixel-level mismatch (%d of %d) [sampleSize=%d "
+ "dither=%s colorType=%s %s] %s:%d",
+ pixelErrors, kExpectedHeight * kExpectedWidth,
+ opts.fSampleSize, (opts.fDitherImage ? "yes" : "no"),
+ (opts.fUseRequestedColorType
+ ? SkColorType_to_string(opts.fRequestedColorType)
+ : "(none)"), path.c_str(), __FILE__, __LINE__));
+ }
+ }
+}
+
+/**
+ * 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 = skiatest::Test::GetResourcePath();
+ SkString directory = SkOSPath::SkPathJoin(resourceDir.c_str(), "encoding");
+ if (!sk_exists(directory.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.
+ kPMColor_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::SkPathJoin(directory.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);
+ }
+ }
+ }
+ }
+}
+////////////////////////////////////////////////////////////////////////////////
+