Add the ability to decode a subset to SkCodec

This allows codecs that support subsets natively (i.e. WEBP) to do so.

Add a field on SkCodec::Options representing the subset.

Add a method on SkCodec to find a valid subset which approximately
matches a desired subset.

Implement subset decodes in SkWebpCodec.

Add a test in DM for decoding subsets.
Notice that we only start on even boundaries. This is due to the
way libwebp's API works. SkWEBPImageDecoder does not take this into
account, which results in visual artifacts.

FIXME: Subsets with scaling are not pixel identical, but close. (This
may be fine, though - they are not perceptually different. We'll just
need to mark another set of images in gold as valid, once
https://skbug.com/4038 is fixed, so we can tests scaled webp without
generating new images on each run.)

Review URL: https://codereview.chromium.org/1240143002
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index 32a8b78..fea557d 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -125,8 +125,26 @@
 // 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;
@@ -153,12 +171,48 @@
     // 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(),