diff options
author | scroggo <scroggo@chromium.org> | 2015-11-03 07:55:11 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-03 07:55:12 -0800 |
commit | 501b7344f116ccc821d437d324aa7883d7ce97bf (patch) | |
tree | 6be104babfba2a62fc82d4645bc85bf9a7d948fb | |
parent | f7de08a52b5287cb16b2e89a8e3691676a4dbe5f (diff) |
Combine native sampling with sampling
In SkSampledCodec, allow the native codec to do its scaling first, then
sample on top of that. Since the only codec which can do native scaling
is JPEG, and we know what it can do, hard-code for JPEG. Check to see
if the sampleSize is something JPEG supports, or a multiple of
something it supports. If so, use JPEG directly or combine them.
BUG=skia:4320
Review URL: https://codereview.chromium.org/1417583009
-rw-r--r-- | bench/nanobench.cpp | 2 | ||||
-rw-r--r-- | dm/DM.cpp | 7 | ||||
-rw-r--r-- | src/codec/SkAndroidCodec.cpp | 18 | ||||
-rw-r--r-- | src/codec/SkSampledCodec.cpp | 100 | ||||
-rw-r--r-- | src/codec/SkSampledCodec.h | 13 | ||||
-rw-r--r-- | tests/CodexTest.cpp | 2 |
6 files changed, 109 insertions, 33 deletions
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp index dad280df0b..2e566da8e1 100644 --- a/bench/nanobench.cpp +++ b/bench/nanobench.cpp @@ -978,7 +978,7 @@ public: // All use cases we are aware of only scale by powers of two. // PNG decodes use the indicated sampling strategy regardless of the sample size, so // these tests are sufficient to provide good coverage of our scaling options. - const uint32_t sampleSizes[] = { 1, 2, 4, 8, 16 }; + const uint32_t sampleSizes[] = { 1, 2, 4, 8, 16, 32, 64 }; const uint32_t minOutputSize = 512; while (fCurrentBRDImage < fImages.count()) { while (fCurrentBRDStrategy < (int) SK_ARRAY_COUNT(strategies)) { @@ -472,7 +472,12 @@ static void push_brd_srcs(Path path) { SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy, }; - const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + // Test on a variety of sampleSizes, making sure to include: + // - 2, 4, and 8, which are natively supported by jpeg + // - multiples of 2 which are not divisible by 4 (analogous for 4) + // - larger powers of two, since BRD clients generally use powers of 2 + // We will only produce output for the larger sizes on large images. + const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 24, 32, 64 }; // We will only test to one backend (8888), but we will test all of the // color types that we need to decode to on this backend. diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp index 43ed90f6c0..086caf2d81 100644 --- a/src/codec/SkAndroidCodec.cpp +++ b/src/codec/SkAndroidCodec.cpp @@ -53,6 +53,11 @@ SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const { return SkISize::Make(0, 0); } + // Fast path for when we are not scaling. + if (1 == sampleSize) { + return fInfo.dimensions(); + } + return this->onGetSampledDimensions(sampleSize); } @@ -77,9 +82,9 @@ SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect return SkISize::Make(0, 0); } - // If the subset is the entire image, for consistency, use onGetSampledDimensions(). + // If the subset is the entire image, for consistency, use getSampledDimensions(). if (fInfo.dimensions() == subset.size()) { - return onGetSampledDimensions(sampleSize); + return this->getSampledDimensions(sampleSize); } // This should perhaps call a virtual function, but currently both of our subclasses @@ -104,6 +109,15 @@ SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* if (!is_valid_subset(*options->fSubset, fInfo.dimensions())) { return SkCodec::kInvalidParameters; } + + if (SkIRect::MakeSize(fInfo.dimensions()) == *options->fSubset) { + // The caller wants the whole thing, rather than a subset. Modify + // the AndroidOptions passed to onGetAndroidPixels to not specify + // a subset. + defaultOptions = *options; + defaultOptions.fSubset = nullptr; + options = &defaultOptions; + } } return this->onGetAndroidPixels(info, pixels, rowBytes, *options); diff --git a/src/codec/SkSampledCodec.cpp b/src/codec/SkSampledCodec.cpp index 229555b9bf..ed4eb7f02d 100644 --- a/src/codec/SkSampledCodec.cpp +++ b/src/codec/SkSampledCodec.cpp @@ -7,6 +7,7 @@ #include "SkCodec.h" #include "SkCodecPriv.h" +#include "SkMath.h" #include "SkSampledCodec.h" SkSampledCodec::SkSampledCodec(SkCodec* codec) @@ -14,30 +15,58 @@ SkSampledCodec::SkSampledCodec(SkCodec* codec) , fCodec(codec) {} -SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { - // Fast path for when we are not scaling. - if (1 == sampleSize) { - return fCodec->getInfo().dimensions(); +SkISize SkSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const { + SkISize preSampledSize = fCodec->getInfo().dimensions(); + int sampleSize = *sampleSizePtr; + SkASSERT(sampleSize > 1); + + if (nativeSampleSize) { + *nativeSampleSize = 1; } - const int width = fCodec->getInfo().width(); - const int height = fCodec->getInfo().height(); - - // Check if the codec can provide the scaling natively. - float scale = get_scale_from_sample_size(sampleSize); - SkSize idealSize = SkSize::Make(scale * (float) width, scale * (float) height); - SkISize nativeSize = fCodec->getScaledDimensions(scale); - float widthDiff = SkTAbs(((float) nativeSize.width()) - idealSize.width()); - float heightDiff = SkTAbs(((float) nativeSize.height()) - idealSize.height()); - // Native scaling is preferred to sampling. If we can scale natively to - // within one of the ideal value, we should choose to scale natively. - if (widthDiff < 1.0f && heightDiff < 1.0f) { - return nativeSize; + // Only JPEG supports native downsampling. + if (fCodec->getEncodedFormat() == kJPEG_SkEncodedFormat) { + // See if libjpeg supports this scale directly + switch (sampleSize) { + case 2: + case 4: + case 8: + // This class does not need to do any sampling. + *sampleSizePtr = 1; + return fCodec->getScaledDimensions(get_scale_from_sample_size(sampleSize)); + default: + break; + } + + // Check if sampleSize is a multiple of something libjpeg can support. + int remainder; + const int sampleSizes[] = { 8, 4, 2 }; + for (int supportedSampleSize : sampleSizes) { + int actualSampleSize; + SkTDivMod(sampleSize, supportedSampleSize, &actualSampleSize, &remainder); + if (0 == remainder) { + float scale = get_scale_from_sample_size(supportedSampleSize); + + // fCodec will scale to this size. + preSampledSize = fCodec->getScaledDimensions(scale); + + // And then this class will sample it. + *sampleSizePtr = actualSampleSize; + if (nativeSampleSize) { + *nativeSampleSize = supportedSampleSize; + } + break; + } + } } - // Provide the scaling by sampling. - return SkISize::Make(get_scaled_dimension(width, sampleSize), - get_scaled_dimension(height, sampleSize)); + return preSampledSize; +} + +SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { + const SkISize size = this->accountForNativeScaling(&sampleSize); + return SkISize::Make(get_scaled_dimension(size.width(), sampleSize), + get_scaled_dimension(size.height(), sampleSize)); } SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, @@ -59,7 +88,7 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void // We are performing a subset decode. int sampleSize = options.fSampleSize; - SkISize scaledSize = this->onGetSampledDimensions(sampleSize); + SkISize scaledSize = this->getSampledDimensions(sampleSize); if (!fCodec->dimensionsSupported(scaledSize)) { // If the native codec does not support the requested scale, scale by sampling. return this->sampledDecode(info, pixels, rowBytes, options); @@ -107,31 +136,46 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, AndroidOptions& options) { + // We should only call this function when sampling. + SkASSERT(options.fSampleSize > 1); + // Create options struct for the codec. SkCodec::Options sampledOptions; sampledOptions.fZeroInitialized = options.fZeroInitialized; + // FIXME: This was already called by onGetAndroidPixels. Can we reduce that? + int sampleSize = options.fSampleSize; + int nativeSampleSize; + SkISize nativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize); + // Check if there is a subset. SkIRect subset; int subsetY = 0; - int subsetWidth = fCodec->getInfo().width(); - int subsetHeight = fCodec->getInfo().height(); + int subsetWidth = nativeSize.width(); + int subsetHeight = nativeSize.height(); if (options.fSubset) { // We will need to know about subsetting in the y-dimension in order to use the // scanline decoder. + // Update the subset to account for scaling done by fCodec. SkIRect* subsetPtr = options.fSubset; - subsetY = subsetPtr->y(); - subsetWidth = subsetPtr->width(); - subsetHeight = subsetPtr->height(); + + // Do the divide ourselves, instead of calling get_scaled_dimension. If + // X and Y are 0, they should remain 0, rather than being upgraded to 1 + // due to being smaller than the sampleSize. + const int subsetX = subsetPtr->x() / nativeSampleSize; + subsetY = subsetPtr->y() / nativeSampleSize; + + subsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize); + subsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize); // The scanline decoder only needs to be aware of subsetting in the x-dimension. - subset.setXYWH(subsetPtr->x(), 0, subsetWidth, fCodec->getInfo().height()); + subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height()); sampledOptions.fSubset = ⊂ } // Start the scanline decode. SkCodec::Result result = fCodec->startScanlineDecode( - info.makeWH(fCodec->getInfo().width(), fCodec->getInfo().height()), &sampledOptions, + info.makeWH(nativeSize.width(), nativeSize.height()), &sampledOptions, options.fColorPtr, options.fColorCount); if (SkCodec::kSuccess != result) { return result; diff --git a/src/codec/SkSampledCodec.h b/src/codec/SkSampledCodec.h index 277a50f0ee..bbd7d3599c 100644 --- a/src/codec/SkSampledCodec.h +++ b/src/codec/SkSampledCodec.h @@ -33,6 +33,19 @@ protected: AndroidOptions& options) override; private: + /** + * Find the best way to account for native scaling. + * + * Return a size that fCodec can scale to, and adjust sampleSize to finish scaling. + * + * @param sampleSize As an input, the requested sample size. + * As an output, sampling needed after letting fCodec + * scale to the returned dimensions. + * @param nativeSampleSize Optional output parameter. Will be set to the + * effective sample size done by fCodec. + * @return SkISize The size that fCodec should scale to. + */ + SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr) const; /** * This fulfills the same contract as onGetAndroidPixels(). diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp index bd3e2031c9..205bd49e2b 100644 --- a/tests/CodexTest.cpp +++ b/tests/CodexTest.cpp @@ -581,7 +581,7 @@ static void test_dimensions(skiatest::Reporter* r, const char path[]) { } // Check that the decode is successful for a variety of scales - for (int sampleSize = 1; sampleSize < 10; sampleSize++) { + for (int sampleSize = 1; sampleSize < 32; sampleSize++) { // Scale the output dimensions SkISize scaledDims = codec->getSampledDimensions(sampleSize); SkImageInfo scaledInfo = codec->getInfo() |