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
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index dad280d..2e566da 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -978,7 +978,7 @@
         //     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)) {
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 236b100..1aaa176 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -472,7 +472,12 @@
             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 43ed90f..086caf2 100644
--- a/src/codec/SkAndroidCodec.cpp
+++ b/src/codec/SkAndroidCodec.cpp
@@ -53,6 +53,11 @@
         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 @@
         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 @@
         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 229555b..ed4eb7f 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 @@
     , fCodec(codec)
 {}
 
+SkISize SkSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const {
+    SkISize preSampledSize = fCodec->getInfo().dimensions();
+    int sampleSize = *sampleSizePtr;
+    SkASSERT(sampleSize > 1);
+
+    if (nativeSampleSize) {
+        *nativeSampleSize = 1;
+    }
+
+    // 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;
+            }
+        }
+    }
+
+    return preSampledSize;
+}
+
 SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const {
-    // Fast path for when we are not scaling.
-    if (1 == sampleSize) {
-        return fCodec->getInfo().dimensions();
-    }
-
-    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;
-    }
-
-    // Provide the scaling by sampling.
-    return SkISize::Make(get_scaled_dimension(width, sampleSize),
-            get_scaled_dimension(height, sampleSize));
+    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 @@
 
     // 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::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 = &subset;
     }
 
     // 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 277a50f..bbd7d35 100644
--- a/src/codec/SkSampledCodec.h
+++ b/src/codec/SkSampledCodec.h
@@ -33,6 +33,19 @@
             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 bd3e203..205bd49 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -581,7 +581,7 @@
     }
 
     // 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()