canvas->drawBitmap(bitmap, 0, 0);
break;
}
+ case kSubset_Mode: {
+ // Arbitrarily choose a divisor.
+ int divisor = 2;
+ // Total width/height of the image.
+ const int W = codec->getInfo().width();
+ const int H = codec->getInfo().height();
+ if (divisor > W || divisor > H) {
+ return Error::Nonfatal(SkStringPrintf("Cannot codec subset: divisor %d is too big "
+ "for %s with dimensions (%d x %d)", divisor,
+ fPath.c_str(), W, H));
+ }
+ // subset dimensions
+ // SkWebpCodec, the only one that supports subsets, requires even top/left boundaries.
+ const int w = SkAlign2(W / divisor);
+ const int h = SkAlign2(H / divisor);
+ SkIRect subset;
+ SkCodec::Options opts;
+ opts.fSubset = ⊂
+ SkBitmap subsetBm;
+ // We will reuse pixel memory from bitmap.
+ void* pixels = bitmap.getPixels();
+ // Keep track of left and top (for drawing subsetBm into canvas). We could use
+ // fScale * x and fScale * y, but we want integers such that the next subset will start
+ // where the last one ended. So we'll add decodeInfo.width() and height().
+ int left = 0;
+ for (int x = 0; x < W; x += w) {
+ int top = 0;
+ for (int y = 0; y < H; y+= h) {
+ // Do not make the subset go off the edge of the image.
+ const int preScaleW = SkTMin(w, W - x);
+ const int preScaleH = SkTMin(h, H - y);
+ subset.setXYWH(x, y, preScaleW, preScaleH);
+ // And scale
+ // FIXME: Should we have a version of getScaledDimensions that takes a subset
+ // into account?
+ decodeInfo = decodeInfo.makeWH(SkScalarRoundToInt(preScaleW * fScale),
+ SkScalarRoundToInt(preScaleH * fScale));
+ size_t rowBytes = decodeInfo.minRowBytes();
+ if (!subsetBm.installPixels(decodeInfo, pixels, rowBytes, colorTable.get(),
+ NULL, NULL)) {
+ return SkStringPrintf("could not install pixels for %s.", fPath.c_str());
+ }
+ const SkCodec::Result result = codec->getPixels(decodeInfo, pixels, rowBytes,
+ &opts, colorPtr, colorCountPtr);
+ switch (result) {
+ case SkCodec::kSuccess:
+ case SkCodec::kIncompleteInput:
+ break;
+ case SkCodec::kInvalidConversion:
+ if (0 == (x|y)) {
+ // First subset is okay to return unimplemented.
+ return Error::Nonfatal("Incompatible colortype conversion");
+ }
+ // If the first subset succeeded, a later one should not fail.
+ // fall through to failure
+ case SkCodec::kUnimplemented:
+ if (0 == (x|y)) {
+ // First subset is okay to return unimplemented.
+ return Error::Nonfatal("subset codec not supported");
+ }
+ // If the first subset succeeded, why would a later one fail?
+ // fall through to failure
+ default:
+ return SkStringPrintf("subset codec failed to decode (%d, %d, %d, %d) "
+ "from %s with dimensions (%d x %d)\t error %d",
+ x, y, decodeInfo.width(), decodeInfo.height(),
+ fPath.c_str(), W, H, result);
+ }
+ canvas->drawBitmap(subsetBm, SkIntToScalar(left), SkIntToScalar(top));
+ // translate by the scaled height.
+ top += decodeInfo.height();
+ }
+ // translate by the scaled width.
+ left += decodeInfo.width();
+ }
+ return "";
+ }
}
return "";
}
// is arbitrary.
static const size_t BUFFER_SIZE = 4096;
+bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
+ if (!desiredSubset) {
+ return false;
+ }
+
+ SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
+ if (!desiredSubset->intersect(bounds)) {
+ return false;
+ }
+
+ // As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we
+ // decode this exact subset.
+ // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested.
+ desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1;
+ desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1;
+ return true;
+}
+
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
- const Options&, SkPMColor*, int*) {
+ const Options& options, SkPMColor*, int*) {
switch (this->rewindIfNeeded()) {
case kCouldNotRewind_RewindState:
return kCouldNotRewind;
// Free any memory associated with the buffer. Must be called last, so we declare it first.
SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output));
- SkISize dimensions = dstInfo.dimensions();
- if (this->getInfo().dimensions() != dimensions) {
+ SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
+ if (options.fSubset) {
+ // Caller is requesting a subset.
+ if (!bounds.contains(*options.fSubset)) {
+ // The subset is out of bounds.
+ return kInvalidParameters;
+ }
+
+ bounds = *options.fSubset;
+
+ // This is tricky. libwebp snaps the top and left to even values. We could let libwebp
+ // do the snap, and return a subset which is a different one than requested. The problem
+ // with that approach is that the caller may try to stitch subsets together, and if we
+ // returned different subsets than requested, there would be artifacts at the boundaries.
+ // Instead, we report that we cannot support odd values for top and left..
+ if (!SkIsAlign2(bounds.fLeft) || !SkIsAlign2(bounds.fTop)) {
+ return kInvalidParameters;
+ }
+
+#ifdef SK_DEBUG
+ {
+ // Make a copy, since getValidSubset can change its input.
+ SkIRect subset(bounds);
+ // That said, getValidSubset should *not* change its input, in this case; otherwise
+ // getValidSubset does not match the actual subsets we can do.
+ SkASSERT(this->getValidSubset(&subset) && subset == bounds);
+ }
+#endif
+
+ config.options.use_cropping = 1;
+ config.options.crop_left = bounds.fLeft;
+ config.options.crop_top = bounds.fTop;
+ config.options.crop_width = bounds.width();
+ config.options.crop_height = bounds.height();
+ }
+
+ SkISize dstDimensions = dstInfo.dimensions();
+ if (bounds.size() != dstDimensions) {
// Caller is requesting scaling.
config.options.use_scaling = 1;
- config.options.scaled_width = dimensions.width();
- config.options.scaled_height = dimensions.height();
+ config.options.scaled_width = dstDimensions.width();
+ config.options.scaled_height = dstDimensions.height();
}
config.output.colorspace = webp_decode_mode(dstInfo.colorType(),
#include "SkBitmap.h"
#include "SkCodec.h"
#include "SkMD5.h"
+#include "SkRandom.h"
#include "SkScanlineDecoder.h"
#include "Test.h"
REPORTER_ASSERT(r, digest == goodDigest);
}
+SkIRect generate_random_subset(SkRandom* rand, int w, int h) {
+ SkIRect rect;
+ do {
+ rect.fLeft = rand->nextRangeU(0, w);
+ rect.fTop = rand->nextRangeU(0, h);
+ rect.fRight = rand->nextRangeU(0, w);
+ rect.fBottom = rand->nextRangeU(0, h);
+ rect.sort();
+ } while (rect.isEmpty());
+ return rect;
+}
+
static void check(skiatest::Reporter* r,
const char path[],
SkISize size,
- bool supportsScanlineDecoding) {
+ bool supportsScanlineDecoding,
+ bool supportsSubsetDecoding) {
SkAutoTDelete<SkStream> stream(resource(path));
if (!stream) {
SkDebugf("Missing resource '%s'\n", path);
} else {
REPORTER_ASSERT(r, !scanlineDecoder);
}
+
+ // The rest of this function tests decoding subsets, and will decode an arbitrary number of
+ // random subsets.
+ // Do not attempt to decode subsets of an image of only once pixel, since there is no
+ // meaningful subset.
+ if (size.width() * size.height() == 1) {
+ return;
+ }
+
+ SkRandom rand;
+ SkIRect subset;
+ SkCodec::Options opts;
+ opts.fSubset = ⊂
+ for (int i = 0; i < 5; i++) {
+ subset = generate_random_subset(&rand, size.width(), size.height());
+ SkASSERT(!subset.isEmpty());
+ const bool supported = codec->getValidSubset(&subset);
+ REPORTER_ASSERT(r, supported == supportsSubsetDecoding);
+
+ SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height());
+ SkBitmap bm;
+ bm.allocPixels(subsetInfo);
+ const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(),
+ &opts, NULL, NULL);
+
+ if (supportsSubsetDecoding) {
+ REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+ // Webp is the only codec that supports subsets, and it will have modified the subset
+ // to have even left/top.
+ REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop));
+ } else {
+ // No subsets will work.
+ REPORTER_ASSERT(r, result == SkCodec::kUnimplemented);
+ }
+ }
}
DEF_TEST(Codec, r) {
// WBMP
- check(r, "mandrill.wbmp", SkISize::Make(512, 512), false);
+ check(r, "mandrill.wbmp", SkISize::Make(512, 512), false, false);
// WEBP
- check(r, "baby_tux.webp", SkISize::Make(386, 395), false);
- check(r, "color_wheel.webp", SkISize::Make(128, 128), false);
- check(r, "yellow_rose.webp", SkISize::Make(400, 301), false);
+ check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true);
+ check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true);
+ check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true);
// BMP
- check(r, "randPixels.bmp", SkISize::Make(8, 8), false);
+ check(r, "randPixels.bmp", SkISize::Make(8, 8), false, false);
// ICO
// These two tests examine interestingly different behavior:
// Decodes an embedded BMP image
- check(r, "color_wheel.ico", SkISize::Make(128, 128), false);
+ check(r, "color_wheel.ico", SkISize::Make(128, 128), false, false);
// Decodes an embedded PNG image
- check(r, "google_chrome.ico", SkISize::Make(256, 256), false);
+ check(r, "google_chrome.ico", SkISize::Make(256, 256), false, false);
// GIF
- check(r, "box.gif", SkISize::Make(200, 55), false);
- check(r, "color_wheel.gif", SkISize::Make(128, 128), false);
- check(r, "randPixels.gif", SkISize::Make(8, 8), false);
+ check(r, "box.gif", SkISize::Make(200, 55), false, false);
+ check(r, "color_wheel.gif", SkISize::Make(128, 128), false, false);
+ check(r, "randPixels.gif", SkISize::Make(8, 8), false, false);
// JPG
- check(r, "CMYK.jpg", SkISize::Make(642, 516), true);
- check(r, "color_wheel.jpg", SkISize::Make(128, 128), true);
- check(r, "grayscale.jpg", SkISize::Make(128, 128), true);
- check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true);
- check(r, "randPixels.jpg", SkISize::Make(8, 8), true);
+ check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false);
+ check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false);
+ check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false);
+ check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false);
+ check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false);
// PNG
- check(r, "arrow.png", SkISize::Make(187, 312), true);
- check(r, "baby_tux.png", SkISize::Make(240, 246), true);
- check(r, "color_wheel.png", SkISize::Make(128, 128), true);
- check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true);
- check(r, "mandrill_128.png", SkISize::Make(128, 128), true);
- check(r, "mandrill_16.png", SkISize::Make(16, 16), true);
- check(r, "mandrill_256.png", SkISize::Make(256, 256), true);
- check(r, "mandrill_32.png", SkISize::Make(32, 32), true);
- check(r, "mandrill_512.png", SkISize::Make(512, 512), true);
- check(r, "mandrill_64.png", SkISize::Make(64, 64), true);
- check(r, "plane.png", SkISize::Make(250, 126), true);
- check(r, "randPixels.png", SkISize::Make(8, 8), true);
- check(r, "yellow_rose.png", SkISize::Make(400, 301), true);
+ check(r, "arrow.png", SkISize::Make(187, 312), true, false);
+ check(r, "baby_tux.png", SkISize::Make(240, 246), true, false);
+ check(r, "color_wheel.png", SkISize::Make(128, 128), true, false);
+ check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, false);
+ check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false);
+ check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false);
+ check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false);
+ check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false);
+ check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false);
+ check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false);
+ check(r, "plane.png", SkISize::Make(250, 126), true, false);
+ check(r, "randPixels.png", SkISize::Make(8, 8), true, false);
+ check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false);
}
static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {