diff options
-rw-r--r-- | dm/DM.cpp | 78 | ||||
-rw-r--r-- | dm/DMSrcSink.cpp | 238 | ||||
-rw-r--r-- | dm/DMSrcSink.h | 23 | ||||
-rw-r--r-- | gyp/codec.gyp | 2 | ||||
-rw-r--r-- | include/codec/SkAndroidCodec.h | 224 | ||||
-rw-r--r-- | include/codec/SkCodec.h | 2 | ||||
-rw-r--r-- | include/codec/SkScaledCodec.h | 59 | ||||
-rw-r--r-- | src/codec/SkAndroidCodec.cpp | 115 | ||||
-rw-r--r-- | src/codec/SkBmpCodec.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkBmpRLECodec.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkCodecPriv.h | 10 | ||||
-rw-r--r-- | src/codec/SkCodec_libpng.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkJpegCodec.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkMaskSwizzler.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkSampledCodec.h | 51 | ||||
-rw-r--r-- | src/codec/SkScaledCodec.cpp | 430 | ||||
-rw-r--r-- | src/codec/SkSwizzler.cpp | 1 | ||||
-rw-r--r-- | src/codec/SkWebpAdapterCodec.cpp | 46 | ||||
-rw-r--r-- | src/codec/SkWebpAdapterCodec.h | 41 | ||||
-rw-r--r-- | tests/CodexTest.cpp | 98 |
20 files changed, 994 insertions, 429 deletions
@@ -226,9 +226,6 @@ static void push_codec_src(Path path, CodecSrc::Mode mode, CodecSrc::DstColorTyp case CodecSrc::kCodec_Mode: folder.append("codec"); break; - case CodecSrc::kScaledCodec_Mode: - folder.append("scaled_codec"); - break; case CodecSrc::kScanline_Mode: folder.append("scanline"); break; @@ -262,6 +259,37 @@ static void push_codec_src(Path path, CodecSrc::Mode mode, CodecSrc::DstColorTyp push_src("image", folder, src); } +static void push_android_codec_src(Path path, AndroidCodecSrc::Mode mode, + CodecSrc::DstColorType dstColorType, int sampleSize) { + SkString folder; + switch (mode) { + case AndroidCodecSrc::kFullImage_Mode: + folder.append("scaled_codec"); + break; + case AndroidCodecSrc::kDivisor_Mode: + folder.append("scaled_codec_divisor"); + break; + } + + switch (dstColorType) { + case CodecSrc::kGrayscale_Always_DstColorType: + folder.append("_kGray8"); + break; + case CodecSrc::kIndex8_Always_DstColorType: + folder.append("_kIndex8"); + break; + default: + break; + } + + if (1 != sampleSize) { + folder.appendf("_%.3f", get_scale_from_sample_size(sampleSize)); + } + + AndroidCodecSrc* src = new AndroidCodecSrc(path, mode, dstColorType, sampleSize); + push_src("image", folder, src); +} + static void push_codec_srcs(Path path) { SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(path.c_str())); if (!encoded) { @@ -288,7 +316,9 @@ static void push_codec_srcs(Path path) { switch (codec->getInfo().colorType()) { case kGray_8_SkColorType: // FIXME: Is this a long term solution for testing wbmps decodes to kIndex8? - // Further discussion on this topic is at skbug.com/3683 + // Further discussion on this topic is at skbug.com/3683. + // This causes us to try to convert grayscale jpegs to kIndex8. We currently + // fail non-fatally in this case. colorTypes[0] = CodecSrc::kGetFromCanvas_DstColorType; colorTypes[1] = CodecSrc::kGrayscale_Always_DstColorType; colorTypes[2] = CodecSrc::kIndex8_Always_DstColorType; @@ -313,24 +343,34 @@ static void push_codec_srcs(Path path) { } } - if (path.endsWith(".ico") || path.endsWith(".ICO")) { - // FIXME: skbug.com/4404: ICO does not have the ability to decode scanlines, so we cannot - // use SkScaledCodec with it. + // skbug.com/4428 + static const char* const exts[] = { + "jpg", "jpeg", "png", "webp", + "JPG", "JPEG", "PNG", "WEBP", + }; + bool supported = false; + for (const char* ext : exts) { + if (path.endsWith(ext)) { + supported = true; + break; + } + } + if (!supported) { return; } - // SkScaledCodec Scales - // The native scales are included to make sure that SkScaledCodec defaults to the native - // scaling strategy when possible. - // 0.1, 0.16, 0.2 etc allow us to test SkScaledCodec with sampleSize 10, 6, 5, etc. - // 0.4, 0.7 etc allow to test what happens when the client requests a scale that - // does not exactly match a sampleSize or native scaling capability. - const float samplingScales[] = { 0.1f, 0.125f, 0.167f, 0.2f, 0.25f, 0.333f, 0.375f, 0.4f, 0.5f, - 0.6f, 0.625f, 0.750f, 0.8f, 0.875f, 1.0f }; - - for (float scale : samplingScales) { - for (uint32_t i = 0; i < numColorTypes; i++) { - push_codec_src(path, CodecSrc::kScaledCodec_Mode, colorTypes[i], scale); + const int sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + const AndroidCodecSrc::Mode androidModes[] = { + AndroidCodecSrc::kFullImage_Mode, + AndroidCodecSrc::kDivisor_Mode, + }; + + for (int sampleSize : sampleSizes) { + for (AndroidCodecSrc::Mode mode : androidModes) { + for (uint32_t i = 0; i < numColorTypes; i++) { + push_android_codec_src(path, mode, colorTypes[i], sampleSize); + } } } } diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 394b84fa7b..24ae2d6087 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -7,6 +7,7 @@ #include "DMSrcSink.h" #include "SamplePipeControllers.h" +#include "SkAndroidCodec.h" #include "SkCodec.h" #include "SkCodecTools.h" #include "SkCommonFlags.h" @@ -25,11 +26,9 @@ #include "SkRecorder.h" #include "SkRemote.h" #include "SkSVGCanvas.h" -#include "SkScaledCodec.h" #include "SkStream.h" #include "SkTLogic.h" #include "SkXMLWriter.h" -#include "SkScaledCodec.h" #include "SkSwizzler.h" DEFINE_bool(multiPage, false, "For document-type backends, render the source" @@ -244,46 +243,47 @@ bool CodecSrc::veto(SinkFlags flags) const { || flags.approach != SinkFlags::kDirect; } +bool get_decode_info(SkImageInfo* decodeInfo, const SkImageInfo& defaultInfo, + SkColorType canvasColorType, CodecSrc::DstColorType dstColorType) { + switch (dstColorType) { + case CodecSrc::kIndex8_Always_DstColorType: + if (kRGB_565_SkColorType == canvasColorType) { + return false; + } + *decodeInfo = defaultInfo.makeColorType(kIndex_8_SkColorType); + break; + case CodecSrc::kGrayscale_Always_DstColorType: + if (kRGB_565_SkColorType == canvasColorType) { + return false; + } + *decodeInfo = defaultInfo.makeColorType(kGray_8_SkColorType); + break; + default: + *decodeInfo = defaultInfo.makeColorType(canvasColorType); + break; + } + + // FIXME: Currently we cannot draw unpremultiplied sources. + if (decodeInfo->alphaType() == kUnpremul_SkAlphaType) { + decodeInfo->makeAlphaType(kPremul_SkAlphaType); + } + return true; +} + Error CodecSrc::draw(SkCanvas* canvas) const { SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str())); if (!encoded) { return SkStringPrintf("Couldn't read %s.", fPath.c_str()); } - SkAutoTDelete<SkCodec> codec(NULL); - if (kScaledCodec_Mode == fMode) { - codec.reset(SkScaledCodec::NewFromData(encoded)); - // TODO (msarett): This should fall through to a fatal error once we support scaled - // codecs for all image types. - if (nullptr == codec.get()) { - return Error::Nonfatal(SkStringPrintf("Couldn't create scaled codec for %s.", - fPath.c_str())); - } - } else { - codec.reset(SkCodec::NewFromData(encoded)); - } + SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(encoded)); if (nullptr == codec.get()) { return SkStringPrintf("Couldn't create codec for %s.", fPath.c_str()); } - // Choose the color type to decode to - SkImageInfo decodeInfo = codec->getInfo(); - SkColorType canvasColorType = canvas->imageInfo().colorType(); - switch (fDstColorType) { - case kIndex8_Always_DstColorType: - decodeInfo = codec->getInfo().makeColorType(kIndex_8_SkColorType); - if (kRGB_565_SkColorType == canvasColorType) { - return Error::Nonfatal("Testing non-565 to 565 is uninteresting."); - } - break; - case kGrayscale_Always_DstColorType: - decodeInfo = codec->getInfo().makeColorType(kGray_8_SkColorType); - if (kRGB_565_SkColorType == canvasColorType) { - return Error::Nonfatal("Testing non-565 to 565 is uninteresting."); - } - break; - default: - decodeInfo = decodeInfo.makeColorType(canvasColorType); - break; + SkImageInfo decodeInfo; + if (!get_decode_info(&decodeInfo, codec->getInfo(), canvas->imageInfo().colorType(), + fDstColorType)) { + return Error::Nonfatal("Testing non-565 to 565 is uninteresting."); } // Try to scale the image if it is desired @@ -311,11 +311,6 @@ Error CodecSrc::draw(SkCanvas* canvas) const { colorCountPtr = &maxColors; } - // FIXME: Currently we cannot draw unpremultiplied sources. - if (decodeInfo.alphaType() == kUnpremul_SkAlphaType) { - decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType); - } - SkBitmap bitmap; if (!bitmap.tryAllocPixels(decodeInfo, nullptr, colorTable.get())) { return SkStringPrintf("Image(%s) is too large (%d x %d)\n", fPath.c_str(), @@ -323,7 +318,6 @@ Error CodecSrc::draw(SkCanvas* canvas) const { } switch (fMode) { - case kScaledCodec_Mode: case kCodec_Mode: { switch (codec->getPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), nullptr, colorPtr, colorCountPtr)) { @@ -585,14 +579,7 @@ Error CodecSrc::draw(SkCanvas* canvas) const { SkISize CodecSrc::size() const { SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str())); - SkAutoTDelete<SkCodec> codec(nullptr); - - if (kScaledCodec_Mode == fMode) { - codec.reset(SkScaledCodec::NewFromData(encoded)); - } else { - codec.reset(SkCodec::NewFromData(encoded)); - } - + SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(encoded)); if (nullptr == codec) { return SkISize::Make(0, 0); } @@ -608,6 +595,163 @@ Name CodecSrc::name() const { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +AndroidCodecSrc::AndroidCodecSrc(Path path, Mode mode, CodecSrc::DstColorType dstColorType, + int sampleSize) + : fPath(path) + , fMode(mode) + , fDstColorType(dstColorType) + , fSampleSize(sampleSize) +{} + +bool AndroidCodecSrc::veto(SinkFlags flags) const { + // No need to test decoding to non-raster or indirect backend. + // TODO: Once we implement GPU paths (e.g. JPEG YUV), we should use a deferred decode to + // let the GPU handle it. + return flags.type != SinkFlags::kRaster + || flags.approach != SinkFlags::kDirect; +} + +Error AndroidCodecSrc::draw(SkCanvas* canvas) const { + SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str())); + if (!encoded) { + return SkStringPrintf("Couldn't read %s.", fPath.c_str()); + } + SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromData(encoded)); + if (nullptr == codec.get()) { + return SkStringPrintf("Couldn't create android codec for %s.", fPath.c_str()); + } + + SkImageInfo decodeInfo; + if (!get_decode_info(&decodeInfo, codec->getInfo(), canvas->imageInfo().colorType(), + fDstColorType)) { + return Error::Nonfatal("Testing non-565 to 565 is uninteresting."); + } + + // Scale the image if it is desired. + SkISize size = codec->getSampledDimensions(fSampleSize); + + // Visually inspecting very small output images is not necessary. We will + // cover these cases in unit testing. + if ((size.width() <= 10 || size.height() <= 10) && 1 != fSampleSize) { + return Error::Nonfatal("Scaling very small images is uninteresting."); + } + decodeInfo = decodeInfo.makeWH(size.width(), size.height()); + + // Construct a color table for the decode if necessary + SkAutoTUnref<SkColorTable> colorTable(nullptr); + SkPMColor* colorPtr = nullptr; + int* colorCountPtr = nullptr; + int maxColors = 256; + if (kIndex_8_SkColorType == decodeInfo.colorType()) { + SkPMColor colors[256]; + colorTable.reset(new SkColorTable(colors, maxColors)); + colorPtr = const_cast<SkPMColor*>(colorTable->readColors()); + colorCountPtr = &maxColors; + } + + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(decodeInfo, nullptr, colorTable.get())) { + return SkStringPrintf("Image(%s) is too large (%d x %d)\n", fPath.c_str(), + decodeInfo.width(), decodeInfo.height()); + } + + // Create options for the codec. + SkAndroidCodec::AndroidOptions options; + options.fColorPtr = colorPtr; + options.fColorCount = colorCountPtr; + options.fSampleSize = fSampleSize; + + switch (fMode) { + case kFullImage_Mode: { + switch (codec->getAndroidPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), + &options)) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + break; + case SkCodec::kInvalidConversion: + return Error::Nonfatal("Cannot convert to requested color type.\n"); + default: + return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str()); + } + canvas->drawBitmap(bitmap, 0, 0); + return ""; + } + case kDivisor_Mode: { + const int width = codec->getInfo().width(); + const int height = codec->getInfo().height(); + const int divisor = 2; + if (width < divisor || height < divisor) { + return Error::Nonfatal("Divisor is larger than image dimension.\n"); + } + + // Rounding the size of the subsets may leave some pixels uninitialized on the bottom + // and right edges of the bitmap. + bitmap.eraseColor(0); + for (int x = 0; x < divisor; x++) { + for (int y = 0; y < divisor; y++) { + // Calculate the subset dimensions + int subsetWidth = width / divisor; + int subsetHeight = height / divisor; + const int left = x * subsetWidth; + const int top = y * subsetHeight; + + // Increase the size of the last subset in each row or column, when the + // divisor does not divide evenly into the image dimensions + subsetWidth += (x + 1 == divisor) ? (width % divisor) : 0; + subsetHeight += (y + 1 == divisor) ? (height % divisor) : 0; + SkIRect subset = SkIRect::MakeXYWH(left, top, subsetWidth, subsetHeight); + if (!codec->getSupportedSubset(&subset)) { + return "Could not get supported subset to decode.\n"; + } + options.fSubset = ⊂ + void* pixels = bitmap.getAddr(subset.left() / fSampleSize, + subset.top() / fSampleSize); + SkISize scaledSubsetSize = codec->getSampledSubsetDimensions(fSampleSize, + subset); + SkImageInfo subsetDecodeInfo = decodeInfo.makeWH(scaledSubsetSize.width(), + scaledSubsetSize.height()); + + switch (codec->getAndroidPixels(subsetDecodeInfo, pixels, bitmap.rowBytes(), + &options)) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + break; + case SkCodec::kInvalidConversion: + return Error::Nonfatal("Cannot convert to requested color type.\n"); + default: + return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str()); + } + } + } + canvas->drawBitmap(bitmap, 0, 0); + return ""; + } + default: + SkASSERT(false); + return "Error: Should not be reached.\n"; + } +} + +SkISize AndroidCodecSrc::size() const { + SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str())); + SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromData(encoded)); + if (nullptr == codec) { + return SkISize::Make(0, 0); + } + return codec->getSampledDimensions(fSampleSize); +} + +Name AndroidCodecSrc::name() const { + // We will replicate the names used by CodecSrc so that images can + // be compared in Gold. + if (1 == fSampleSize) { + return SkOSPath::Basename(fPath.c_str()); + } + return get_scaled_name(fPath, get_scale_from_sample_size(fSampleSize)); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + ImageSrc::ImageSrc(Path path, int divisor) : fPath(path), fDivisor(divisor) {} bool ImageSrc::veto(SinkFlags flags) const { diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h index 1beaec9a69..a7cca8396a 100644 --- a/dm/DMSrcSink.h +++ b/dm/DMSrcSink.h @@ -104,7 +104,6 @@ private: class CodecSrc : public Src { public: enum Mode { - kScaledCodec_Mode, kCodec_Mode, kScanline_Mode, kScanline_Subset_Mode, @@ -129,6 +128,28 @@ private: float fScale; }; +class AndroidCodecSrc : public Src { +public: + enum Mode { + kFullImage_Mode, + // Splits the image into multiple subsets using a divisor and decodes the subsets + // separately. + kDivisor_Mode, + }; + + AndroidCodecSrc(Path, Mode, CodecSrc::DstColorType, int sampleSize); + + Error draw(SkCanvas*) const override; + SkISize size() const override; + Name name() const override; + bool veto(SinkFlags) const override; +private: + Path fPath; + Mode fMode; + CodecSrc::DstColorType fDstColorType; + int fSampleSize; +}; + // Allows for testing of various implementations of Android's BitmapRegionDecoder class BRDSrc : public Src { public: diff --git a/gyp/codec.gyp b/gyp/codec.gyp index 1f3287ab52..350410a665 100644 --- a/gyp/codec.gyp +++ b/gyp/codec.gyp @@ -32,6 +32,7 @@ '../src/core', ], 'sources': [ + '../src/codec/SkAndroidCodec.cpp', '../src/codec/SkBmpCodec.cpp', '../src/codec/SkBmpMaskCodec.cpp', '../src/codec/SkBmpRLECodec.cpp', @@ -49,6 +50,7 @@ '../src/codec/SkSampler.cpp', '../src/codec/SkScaledCodec.cpp', '../src/codec/SkSwizzler.cpp', + '../src/codec/SkWebpAdapterCodec.cpp', '../src/codec/SkWebpCodec.cpp', ], 'direct_dependent_settings': { diff --git a/include/codec/SkAndroidCodec.h b/include/codec/SkAndroidCodec.h new file mode 100644 index 0000000000..42e7fa8b9f --- /dev/null +++ b/include/codec/SkAndroidCodec.h @@ -0,0 +1,224 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAndroidCodec_DEFINED +#define SkAndroidCodec_DEFINED + +#include "SkCodec.h" +#include "SkEncodedFormat.h" +#include "SkStream.h" +#include "SkTypes.h" + +/** + * Abstract interface defining image codec functionality that is necessary for + * Android. + */ +class SkAndroidCodec : SkNoncopyable { +public: + /** + * If this stream represents an encoded image that we know how to decode, + * return an SkAndroidCodec that can decode it. Otherwise return NULL. + * + * If NULL is returned, the stream is deleted immediately. Otherwise, the + * SkCodec takes ownership of it, and will delete it when done with it. + */ + static SkAndroidCodec* NewFromStream(SkStream*); + + /** + * If this data represents an encoded image that we know how to decode, + * return an SkAndroidCodec that can decode it. Otherwise return NULL. + * + * Will take a ref if it returns a codec, else will not affect the data. + */ + static SkAndroidCodec* NewFromData(SkData*); + + virtual ~SkAndroidCodec() {} + + + const SkImageInfo& getInfo() const { return fInfo; } + + /** + * Format of the encoded data. + */ + SkEncodedFormat getEncodedFormat() const { return this->onGetEncodedFormat(); } + + /** + * Returns the dimensions of the scaled output image, for an input + * sampleSize. + * + * When the sample size divides evenly into the original dimensions, the + * scaled output dimensions will simply be equal to the original + * dimensions divided by the sample size. + * + * When the sample size does not divide even into the original + * dimensions, the codec may round up or down, depending on what is most + * efficient to decode. + * + * Finally, the codec will always recommend a non-zero output, so the output + * dimension will always be one if the sampleSize is greater than the + * original dimension. + */ + SkISize getSampledDimensions(int sampleSize) const; + + /** + * Return (via desiredSubset) a subset which can decoded from this codec, + * or false if the input subset is invalid. + * + * @param desiredSubset in/out parameter + * As input, a desired subset of the original bounds + * (as specified by getInfo). + * As output, if true is returned, desiredSubset may + * have been modified to a subset which is + * supported. Although a particular change may have + * been made to desiredSubset to create something + * supported, it is possible other changes could + * result in a valid subset. If false is returned, + * desiredSubset's value is undefined. + * @return true If the input desiredSubset is valid. + * desiredSubset may be modified to a subset + * supported by the codec. + * false If desiredSubset is invalid (NULL or not fully + * contained within the image). + */ + bool getSupportedSubset(SkIRect* desiredSubset) const; + // TODO: Rename SkCodec::getValidSubset() to getSupportedSubset() + + /** + * Returns the dimensions of the scaled, partial output image, for an + * input sampleSize and subset. + * + * @param sampleSize Factor to scale down by. + * @param subset Must be a valid subset of the original image + * dimensions and a subset supported by SkAndroidCodec. + * getSubset() can be used to obtain a subset supported + * by SkAndroidCodec. + * @return Size of the scaled partial image. Or zero size + * if either of the inputs is invalid. + */ + SkISize getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const; + + /** + * Additional options to pass to getAndroidPixels(). + */ + // FIXME: It's a bit redundant to name these AndroidOptions when this class is already + // called SkAndroidCodec. On the other hand, it's may be a bit confusing to call + // these Options when SkCodec has a slightly different set of Options. Maybe these + // should be DecodeOptions or SamplingOptions? + struct AndroidOptions { + AndroidOptions() + : fZeroInitialized(SkCodec::kNo_ZeroInitialized) + , fSubset(nullptr) + , fColorPtr(nullptr) + , fColorCount(nullptr) + , fSampleSize(1) + {} + + /** + * Indicates is destination pixel memory is zero initialized. + */ + SkCodec::ZeroInitialized fZeroInitialized; + + /** + * If not NULL, represents a subset of the original image to decode. + * + * Must be within the bounds returned by getInfo(). + * + * If the EncodedFormat is kWEBP_SkEncodedFormat, the top and left + * values must be even. + */ + SkIRect* fSubset; + + /** + * If the client has requested a decode to kIndex8_SkColorType + * (specified in the SkImageInfo), then the caller must provide + * storage for up to 256 SkPMColor values in fColorPtr. On success, + * the codec must copy N colors into that storage, (where N is the + * logical number of table entries) and set fColorCount to N. + * + * If the client does not request kIndex8_SkColorType, then the last + * two parameters may be NULL. If fColorCount is not null, it will be + * set to 0. + */ + SkPMColor* fColorPtr; + int* fColorCount; + + /** + * The client may provide an integer downscale factor for the decode. + * The codec may implement this downscaling by sampling or another + * method if it is more efficient. + */ + int fSampleSize; + }; + + /** + * Decode into the given pixels, a block of memory of size at + * least (info.fHeight - 1) * rowBytes + (info.fWidth * + * bytesPerPixel) + * + * Repeated calls to this function should give the same results, + * allowing the PixelRef to be immutable. + * + * @param info A description of the format (config, size) + * expected by the caller. This can simply be identical + * to the info returned by getInfo(). + * + * This contract also allows the caller to specify + * different output-configs, which the implementation can + * decide to support or not. + * + * A size that does not match getInfo() implies a request + * to scale or subset. If the codec cannot perform this + * scaling or subsetting, it will return an error code. + * + * If info is kIndex8_SkColorType, then the caller must provide storage for up to 256 + * SkPMColor values in options->fColorPtr. On success the codec must copy N colors into + * that storage, (where N is the logical number of table entries) and set + * options->fColorCount to N. + * + * If info is not kIndex8_SkColorType, options->fColorPtr and options->fColorCount may + * be nullptr. + * + * The AndroidOptions object is also used to specify any requested scaling or subsetting + * using options->fSampleSize and options->fSubset. + * + * @return Result kSuccess, or another value explaining the type of failure. + */ + // FIXME: It's a bit redundant to name this getAndroidPixels() when this class is already + // called SkAndroidCodec. On the other hand, it's may be a bit confusing to call + // this getPixels() when it is a slightly different API than SkCodec's getPixels(). + // Maybe this should be decode() or decodeSubset()? + SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + AndroidOptions* options); + + /** + * Simplified version of getAndroidPixels() where we supply the default AndroidOptions. + * + * This will return an error if the info is kIndex_8_SkColorType and also will not perform + * any scaling or subsetting. + */ + SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes); + +protected: + + SkAndroidCodec(const SkImageInfo&); + + virtual SkEncodedFormat onGetEncodedFormat() const = 0; + + virtual SkISize onGetSampledDimensions(int sampleSize) const = 0; + + virtual bool onGetSupportedSubset(SkIRect* desiredSubset) const = 0; + + virtual SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, AndroidOptions& options) = 0; + +private: + + // This will always be a reference to the info that is contained by the + // embedded SkCodec. + const SkImageInfo& fInfo; +}; +#endif // SkAndroidCodec_DEFINED diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h index 8b6e210167..5127a7cc41 100644 --- a/include/codec/SkCodec.h +++ b/include/codec/SkCodec.h @@ -584,6 +584,6 @@ private: */ virtual SkSampler* getSampler(bool createIfNecessary) { return nullptr; } - friend class SkScaledCodec; + friend class SkSampledCodec; }; #endif // SkCodec_DEFINED diff --git a/include/codec/SkScaledCodec.h b/include/codec/SkScaledCodec.h deleted file mode 100644 index 028706bc94..0000000000 --- a/include/codec/SkScaledCodec.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#ifndef SkScaledCodec_DEFINED -#define SkScaledCodec_DEFINED - -#include "SkCodec.h" - -class SkStream; - -/** - * This class implements scaling, by sampling scanlines in the y direction. - * x-wise sampling is implemented in the swizzler, when getScanlines() is called. - */ -class SkScaledCodec : public SkCodec { -public: - static SkCodec* NewFromStream(SkStream*); - static SkCodec* NewFromData(SkData*); - - virtual ~SkScaledCodec(); - - static void ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim, - int* sampleSizeX, int* sampleSizeY); - -protected: - bool onRewind() override; - - /** - * Recommend a set of destination dimensions given a requested scale - */ - SkISize onGetScaledDimensions(float desiredScale) const override; - bool onDimensionsSupported(const SkISize&) override; - - Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*) - override; - SkEncodedFormat onGetEncodedFormat() const override { - return fCodec->getEncodedFormat(); - } - - bool onReallyHasAlpha() const override { - return fCodec->reallyHasAlpha(); - } - - uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override; - - SkScanlineOrder onGetScanlineOrder() const override; - -private: - - SkAutoTDelete<SkCodec> fCodec; - - explicit SkScaledCodec(SkCodec*); - - typedef SkCodec INHERITED; -}; -#endif // SkScaledCodec_DEFINED diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp new file mode 100644 index 0000000000..43ed90f6c0 --- /dev/null +++ b/src/codec/SkAndroidCodec.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkAndroidCodec.h" +#include "SkCodec.h" +#include "SkCodecPriv.h" +#include "SkSampledCodec.h" +#include "SkWebpAdapterCodec.h" + +static bool is_valid_sample_size(int sampleSize) { + // FIXME: As Leon has mentioned elsewhere, surely there is also a maximum sampleSize? + return sampleSize > 0; +} + +SkAndroidCodec::SkAndroidCodec(const SkImageInfo& info) + : fInfo(info) +{} + +SkAndroidCodec* SkAndroidCodec::NewFromStream(SkStream* stream) { + SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream)); + if (nullptr == codec) { + return nullptr; + } + + switch (codec->getEncodedFormat()) { + case kWEBP_SkEncodedFormat: + return new SkWebpAdapterCodec((SkWebpCodec*) codec.detach()); + case kPNG_SkEncodedFormat: + case kJPEG_SkEncodedFormat: + return new SkSampledCodec(codec.detach()); + default: + // FIXME: SkSampledCodec is temporarily disabled for other formats + // while focusing on the formats that are supported by + // BitmapRegionDecoder. + return nullptr; + } +} + +SkAndroidCodec* SkAndroidCodec::NewFromData(SkData* data) { + if (!data) { + return nullptr; + } + + return NewFromStream(new SkMemoryStream(data)); +} + +SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const { + if (!is_valid_sample_size(sampleSize)) { + return SkISize::Make(0, 0); + } + + return this->onGetSampledDimensions(sampleSize); +} + +bool SkAndroidCodec::getSupportedSubset(SkIRect* desiredSubset) const { + if (!desiredSubset || !is_valid_subset(*desiredSubset, fInfo.dimensions())) { + return false; + } + + return this->onGetSupportedSubset(desiredSubset); +} + +SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const { + if (!is_valid_sample_size(sampleSize)) { + return SkISize::Make(0, 0); + } + + // We require that the input subset is a subset that is supported by SkAndroidCodec. + // We test this by calling getSupportedSubset() and verifying that no modifications + // are made to the subset. + SkIRect copySubset = subset; + if (!this->getSupportedSubset(©Subset) || copySubset != subset) { + return SkISize::Make(0, 0); + } + + // If the subset is the entire image, for consistency, use onGetSampledDimensions(). + if (fInfo.dimensions() == subset.size()) { + return onGetSampledDimensions(sampleSize); + } + + // This should perhaps call a virtual function, but currently both of our subclasses + // want the same implementation. + return SkISize::Make(get_scaled_dimension(subset.width(), sampleSize), + get_scaled_dimension(subset.height(), sampleSize)); +} + +SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, AndroidOptions* options) { + if (!pixels) { + return SkCodec::kInvalidParameters; + } + if (rowBytes < info.minRowBytes()) { + return SkCodec::kInvalidParameters; + } + + AndroidOptions defaultOptions; + if (!options) { + options = &defaultOptions; + } else if (options->fSubset) { + if (!is_valid_subset(*options->fSubset, fInfo.dimensions())) { + return SkCodec::kInvalidParameters; + } + } + + return this->onGetAndroidPixels(info, pixels, rowBytes, *options); +} + +SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes) { + return this->getAndroidPixels(info, pixels, rowBytes, nullptr); +} diff --git a/src/codec/SkBmpCodec.cpp b/src/codec/SkBmpCodec.cpp index 0222c8ce9f..191c2ad800 100644 --- a/src/codec/SkBmpCodec.cpp +++ b/src/codec/SkBmpCodec.cpp @@ -11,7 +11,6 @@ #include "SkBmpStandardCodec.h" #include "SkCodecPriv.h" #include "SkColorPriv.h" -#include "SkScaledCodec.h" #include "SkStream.h" /* diff --git a/src/codec/SkBmpRLECodec.cpp b/src/codec/SkBmpRLECodec.cpp index 67e262a704..32580c3fa5 100644 --- a/src/codec/SkBmpRLECodec.cpp +++ b/src/codec/SkBmpRLECodec.cpp @@ -8,7 +8,6 @@ #include "SkBmpRLECodec.h" #include "SkCodecPriv.h" #include "SkColorPriv.h" -#include "SkScaledCodec.h" #include "SkStream.h" /* diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h index 0442625dd2..1b6723fd4a 100644 --- a/src/codec/SkCodecPriv.h +++ b/src/codec/SkCodecPriv.h @@ -31,9 +31,19 @@ #define COMPUTE_RESULT_ALPHA \ SkSwizzler::GetResult(zeroAlpha, maxAlpha); +// FIXME: Consider sharing with dm, nanbench, and tools. +inline float get_scale_from_sample_size(int sampleSize) { + return 1.0f / ((float) sampleSize); +} + +inline bool is_valid_subset(const SkIRect& subset, const SkISize& imageDims) { + return SkIRect::MakeSize(imageDims).contains(subset); +} + /* * returns a scaled dimension based on the original dimension and the sampleSize * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder + * FIXME: I think we should call this get_sampled_dimension(). */ inline int get_scaled_dimension(int srcDimension, int sampleSize) { if (sampleSize > srcDimension) { diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp index f4d75561d8..54b57870a3 100644 --- a/src/codec/SkCodec_libpng.cpp +++ b/src/codec/SkCodec_libpng.cpp @@ -11,7 +11,6 @@ #include "SkColorTable.h" #include "SkBitmap.h" #include "SkMath.h" -#include "SkScaledCodec.h" #include "SkSize.h" #include "SkStream.h" #include "SkSwizzler.h" diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index cca71c1cad..9975748cf4 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -11,7 +11,6 @@ #include "SkJpegUtility_codec.h" #include "SkCodecPriv.h" #include "SkColorPriv.h" -#include "SkScaledCodec.h" #include "SkStream.h" #include "SkTemplates.h" #include "SkTypes.h" diff --git a/src/codec/SkMaskSwizzler.cpp b/src/codec/SkMaskSwizzler.cpp index 958df61cc7..e5facc1f0c 100644 --- a/src/codec/SkMaskSwizzler.cpp +++ b/src/codec/SkMaskSwizzler.cpp @@ -8,7 +8,6 @@ #include "SkCodecPriv.h" #include "SkColorPriv.h" #include "SkMaskSwizzler.h" -#include "SkScaledCodec.h" static SkSwizzler::ResultAlpha swizzle_mask16_to_n32_opaque( void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, diff --git a/src/codec/SkSampledCodec.h b/src/codec/SkSampledCodec.h new file mode 100644 index 0000000000..277a50f0ee --- /dev/null +++ b/src/codec/SkSampledCodec.h @@ -0,0 +1,51 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkSampledCodec_DEFINED +#define SkSampledCodec_DEFINED + +#include "SkAndroidCodec.h" +#include "SkCodec.h" + +/** + * This class implements the functionality of SkAndroidCodec. Scaling will + * be provided by sampling if it cannot be provided by fCodec. + */ +class SkSampledCodec : public SkAndroidCodec { +public: + + explicit SkSampledCodec(SkCodec*); + + virtual ~SkSampledCodec() {} + +protected: + + SkEncodedFormat onGetEncodedFormat() const override { return fCodec->getEncodedFormat(); }; + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override { return true; } + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + AndroidOptions& options) override; + +private: + + /** + * This fulfills the same contract as onGetAndroidPixels(). + * + * We call this function from onGetAndroidPixels() if we have determined + * that fCodec does not support the requested scale, and we need to + * provide the scale by sampling. + */ + SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, + AndroidOptions& options); + + SkAutoTDelete<SkCodec> fCodec; + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkSampledCodec_DEFINED diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp index 6b50a09f26..c2be2d6ede 100644 --- a/src/codec/SkScaledCodec.cpp +++ b/src/codec/SkScaledCodec.cpp @@ -5,344 +5,210 @@ * found in the LICENSE file. */ +#include "SkCodec.h" #include "SkCodecPriv.h" -#include "SkScaledCodec.h" -#include "SkStream.h" -#include "SkWebpCodec.h" +#include "SkSampledCodec.h" +// FIXME: Rename this file to SkSampledCodec.cpp -SkCodec* SkScaledCodec::NewFromStream(SkStream* stream) { - SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream)); - if (nullptr == codec) { - return nullptr; - } - - switch (codec->getEncodedFormat()) { - case kWEBP_SkEncodedFormat: - // Webp codec supports scaling and subsetting natively - return codec.detach(); - case kPNG_SkEncodedFormat: - case kJPEG_SkEncodedFormat: - // wrap in new SkScaledCodec - return new SkScaledCodec(codec.detach()); - default: - // FIXME: SkScaledCodec is temporarily disabled for other formats - // while focusing on the formats that are supported by - // BitmapRegionDecoder. - return nullptr; - } -} - -SkCodec* SkScaledCodec::NewFromData(SkData* data) { - if (!data) { - return nullptr; - } - return NewFromStream(new SkMemoryStream(data)); -} - -SkScaledCodec::SkScaledCodec(SkCodec* codec) - : INHERITED(codec->getInfo(), nullptr) +SkSampledCodec::SkSampledCodec(SkCodec* codec) + : INHERITED(codec->getInfo()) , fCodec(codec) {} -SkScaledCodec::~SkScaledCodec() {} - -bool SkScaledCodec::onRewind() { - return fCodec->onRewind(); -} - -static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& nativeDims, - const SkISize& scaledCodecDims, float desiredScale) { - if (nativeDims == scaledCodecDims) { - // does not matter which to return if equal. Return here to skip below calculations - return nativeDims; +SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { + // Fast path for when we are not scaling. + if (1 == sampleSize) { + return fCodec->getInfo().dimensions(); } - float idealWidth = origDims.width() * desiredScale; - float idealHeight = origDims.height() * desiredScale; - // calculate difference between native dimensions and ideal dimensions - float nativeWDiff = SkTAbs(idealWidth - nativeDims.width()); - float nativeHDiff = SkTAbs(idealHeight - nativeDims.height()); - float nativeDiff = nativeWDiff + nativeHDiff; + 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 (nativeWDiff < 1.0f && nativeHDiff < 1.0f) { - return nativeDims; + if (widthDiff < 1.0f && heightDiff < 1.0f) { + return nativeSize; } - // calculate difference between scaledCodec dimensions and ideal dimensions - float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width()); - float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height()); - float scaledCodecDiff = scaledCodecWDiff + scaledCodecHDiff; - - // return dimensions closest to ideal dimensions. - // If the differences are equal, return nativeDims, as native scaling is more efficient. - return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims; + // Provide the scaling by sampling. + return SkISize::Make(get_scaled_dimension(width, sampleSize), + get_scaled_dimension(height, sampleSize)); } -/* - * Return a valid set of output dimensions for this decoder, given an input scale - */ -SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const { - SkISize nativeDimensions = fCodec->getScaledDimensions(desiredScale); - // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ... - SkISize scaledCodecDimensions; - if (desiredScale > 0.5f) { - // sampleSize = 1 - scaledCodecDimensions = fCodec->getInfo().dimensions(); - } - // sampleSize determines the step size between samples - // Ex: sampleSize = 2, sample every second pixel in x and y directions - int sampleSize = int ((1.0f / desiredScale) + 0.5f); - - int scaledWidth = get_scaled_dimension(this->getInfo().width(), sampleSize); - int scaledHeight = get_scaled_dimension(this->getInfo().height(), sampleSize); - - // Return the calculated output dimensions for the given scale - scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight); - - return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions, - scaledCodecDimensions, desiredScale); -} - -// check if scaling to dstInfo size from srcInfo size using sampleSize is possible -static bool scaling_supported(const SkISize& dstDim, const SkISize& srcDim, - int* sampleX, int* sampleY) { - SkScaledCodec::ComputeSampleSize(dstDim, srcDim, sampleX, sampleY); - const int dstWidth = dstDim.width(); - const int dstHeight = dstDim.height(); - const int srcWidth = srcDim.width(); - const int srcHeight = srcDim.height(); - // only support down sampling, not up sampling - if (dstWidth > srcWidth || dstHeight > srcHeight) { - return false; - } - // check that srcWidth is scaled down by an integer value - if (get_scaled_dimension(srcWidth, *sampleX) != dstWidth) { - return false; - } - // check that src height is scaled down by an integer value - if (get_scaled_dimension(srcHeight, *sampleY) != dstHeight) { - return false; - } - // sampleX and sampleY should be equal unless the original sampleSize requested was larger - // than srcWidth or srcHeight. If so, the result of this is dstWidth or dstHeight = 1. - // This functionality allows for tall thin images to still be scaled down by scaling factors. - if (*sampleX != *sampleY){ - if (1 != dstWidth && 1 != dstHeight) { - return false; +SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, AndroidOptions& options) { + // Create an Options struct for the codec. + SkCodec::Options codecOptions; + codecOptions.fZeroInitialized = options.fZeroInitialized; + + SkIRect* subset = options.fSubset; + if (!subset || subset->size() == fCodec->getInfo().dimensions()) { + if (fCodec->dimensionsSupported(info.dimensions())) { + return fCodec->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr, + options.fColorCount); } - } - return true; -} -bool SkScaledCodec::onDimensionsSupported(const SkISize& dim) { - // Check with fCodec first. No need to call the non-virtual version, which - // just checks if it matches the original, since a match means this method - // will not be called. - if (fCodec->onDimensionsSupported(dim)) { - return true; + // If the native codec does not support the requested scale, scale by sampling. + return this->sampledDecode(info, pixels, rowBytes, options); } - // FIXME: These variables are unused, but are needed by scaling_supported. - // This class could also cache these values, and avoid calling this in - // onGetPixels (since getPixels already calls it). - int sampleX; - int sampleY; - return scaling_supported(dim, this->getInfo().dimensions(), &sampleX, &sampleY); -} - -// calculates sampleSize in x and y direction -void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim, - int* sampleXPtr, int* sampleYPtr) { - int srcWidth = srcDim.width(); - int dstWidth = dstDim.width(); - int srcHeight = srcDim.height(); - int dstHeight = dstDim.height(); - - int sampleX = srcWidth / dstWidth; - int sampleY = srcHeight / dstHeight; - - // only support down sampling, not up sampling - SkASSERT(dstWidth <= srcWidth); - SkASSERT(dstHeight <= srcHeight); + // We are performing a subset decode. + int sampleSize = options.fSampleSize; + SkISize scaledSize = this->onGetSampledDimensions(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); + } - // sampleX and sampleY should be equal unless the original sampleSize requested was - // larger than srcWidth or srcHeight. - // If so, the result of this is dstWidth or dstHeight = 1. This functionality - // allows for tall thin images to still be scaled down by scaling factors. + // Calculate the scaled subset bounds. + int scaledSubsetX = subset->x() / sampleSize; + int scaledSubsetY = subset->y() / sampleSize; + int scaledSubsetWidth = info.width(); + int scaledSubsetHeight = info.height(); - if (sampleX != sampleY){ - if (1 != dstWidth && 1 != dstHeight) { + // Start the scanline decode. + SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, + scaledSize.height()); + codecOptions.fSubset = &scanlineSubset; + SkCodec::Result result = fCodec->startScanlineDecode(info.makeWH(scaledSize.width(), + scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount); + if (SkCodec::kSuccess != result) { + return result; + } - // rounding during onGetScaledDimensions can cause different sampleSizes - // Ex: srcWidth = 79, srcHeight = 20, sampleSize = 10 - // dstWidth = 7, dstHeight = 2, sampleX = 79/7 = 11, sampleY = 20/2 = 10 - // correct for this rounding by comparing width to sampleY and height to sampleX + // At this point, we are only concerned with subsetting. Either no scale was + // requested, or the fCodec is handling the scale. + switch (fCodec->getScanlineOrder()) { + case SkCodec::kTopDown_SkScanlineOrder: + case SkCodec::kNone_SkScanlineOrder: { + if (!fCodec->skipScanlines(scaledSubsetY)) { + fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + scaledSubsetHeight, 0); + return SkCodec::kIncompleteInput; + } - if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) { - sampleX = sampleY; - } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) { - sampleY = sampleX; + int decodedLines = fCodec->getScanlines(pixels, scaledSubsetHeight, rowBytes); + if (decodedLines != scaledSubsetHeight) { + return SkCodec::kIncompleteInput; } + return SkCodec::kSuccess; } - } - - if (sampleXPtr) { - *sampleXPtr = sampleX; - } - if (sampleYPtr) { - *sampleYPtr = sampleY; + default: + SkASSERT(false); + return SkCodec::kUnimplemented; } } -// TODO: Implement subsetting in onGetPixels which works when and when not sampling -SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, - size_t rowBytes, const Options& options, - SkPMColor ctable[], int* ctableCount, - int* rowsDecoded) { +SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, + size_t rowBytes, AndroidOptions& options) { + // Create options struct for the codec. + SkCodec::Options sampledOptions; + sampledOptions.fZeroInitialized = options.fZeroInitialized; + // Check if there is a subset. + SkIRect subset; + int subsetY = 0; + int subsetWidth = fCodec->getInfo().width(); + int subsetHeight = fCodec->getInfo().height(); if (options.fSubset) { - // Subsets are not supported. - return kUnimplemented; - } - - if (fCodec->dimensionsSupported(requestedInfo.dimensions())) { - // Make sure that the parent class does not fill on an incomplete decode, since - // fCodec will take care of filling the uninitialized lines. - *rowsDecoded = requestedInfo.height(); - return fCodec->getPixels(requestedInfo, dst, rowBytes, &options, ctable, ctableCount); - } - - // scaling requested - int sampleX; - int sampleY; - if (!scaling_supported(requestedInfo.dimensions(), fCodec->getInfo().dimensions(), - &sampleX, &sampleY)) { - // onDimensionsSupported would have returned false, meaning we should never reach here. - SkASSERT(false); - return kInvalidScale; - } - - // set first sample pixel in y direction - const int Y0 = get_start_coord(sampleY); - - const int dstHeight = requestedInfo.height(); - const int srcWidth = fCodec->getInfo().width(); - const int srcHeight = fCodec->getInfo().height(); - - const SkImageInfo info = requestedInfo.makeWH(srcWidth, srcHeight); - - Result result = fCodec->startScanlineDecode(info, &options, ctable, ctableCount); - - if (kSuccess != result) { + // We will need to know about subsetting in the y-dimension in order to use the + // scanline decoder. + SkIRect* subsetPtr = options.fSubset; + subsetY = subsetPtr->y(); + subsetWidth = subsetPtr->width(); + subsetHeight = subsetPtr->height(); + + // The scanline decoder only needs to be aware of subsetting in the x-dimension. + subset.setXYWH(subsetPtr->x(), 0, subsetWidth, fCodec->getInfo().height()); + sampledOptions.fSubset = ⊂ + } + + // Start the scanline decode. + SkCodec::Result result = fCodec->startScanlineDecode( + info.makeWH(fCodec->getInfo().width(), fCodec->getInfo().height()), &sampledOptions, + options.fColorPtr, options.fColorCount); + if (SkCodec::kSuccess != result) { return result; } SkSampler* sampler = fCodec->getSampler(true); if (!sampler) { - return kUnimplemented; + return SkCodec::kUnimplemented; } - if (sampler->setSampleX(sampleX) != requestedInfo.width()) { - return kInvalidScale; + // Since we guarantee that output dimensions are always at least one (even if the sampleSize + // is greater than a given dimension), the input sampleSize is not always the sampleSize that + // we use in practice. + const int sampleX = subsetWidth / info.width(); + const int sampleY = subsetHeight / info.height(); + if (sampler->setSampleX(sampleX) != info.width()) { + return SkCodec::kInvalidScale; + } + if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { + return SkCodec::kInvalidScale; } + const int samplingOffsetY = get_start_coord(sampleY); + const int startY = samplingOffsetY + subsetY; + int dstHeight = info.height(); switch(fCodec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: { - if (!fCodec->skipScanlines(Y0)) { - *rowsDecoded = 0; - return kIncompleteInput; + if (!fCodec->skipScanlines(startY)) { + fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + dstHeight, 0); + return SkCodec::kIncompleteInput; } + void* pixelPtr = pixels; for (int y = 0; y < dstHeight; y++) { - if (1 != fCodec->getScanlines(dst, 1, rowBytes)) { - // The failed call to getScanlines() will take care of - // filling the failed row, so we indicate that we have - // decoded (y + 1) rows. - *rowsDecoded = y + 1; - return kIncompleteInput; - } - if (y < dstHeight - 1) { - if (!fCodec->skipScanlines(sampleY - 1)) { - *rowsDecoded = y + 1; - return kIncompleteInput; - } - } - dst = SkTAddOffset<void>(dst, rowBytes); - } - return kSuccess; - } - case SkCodec::kBottomUp_SkScanlineOrder: - case SkCodec::kOutOfOrder_SkScanlineOrder: { - Result result = kSuccess; - int y; - for (y = 0; y < srcHeight; y++) { - int srcY = fCodec->nextScanline(); - if (is_coord_necessary(srcY, sampleY, dstHeight)) { - void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY)); - if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) { - result = kIncompleteInput; - break; - } - } else { - if (!fCodec->skipScanlines(1)) { - result = kIncompleteInput; - break; - } + if (1 != fCodec->getScanlines(pixelPtr, 1, rowBytes)) { + fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + dstHeight, y + 1); + return SkCodec::kIncompleteInput; } - } - - // We handle filling uninitialized memory here instead of in the parent class. - // The parent class does not know that we are sampling. - if (kIncompleteInput == result) { - const uint32_t fillValue = fCodec->getFillValue(requestedInfo.colorType(), - requestedInfo.alphaType()); - for (; y < srcHeight; y++) { - int srcY = fCodec->outputScanline(y); - if (is_coord_necessary(srcY, sampleY, dstHeight)) { - void* dstRow = SkTAddOffset<void>(dst, - rowBytes * get_dst_coord(srcY, sampleY)); - SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow, - rowBytes, fillValue, options.fZeroInitialized); - } + int linesToSkip = SkTMin(sampleY - 1, dstHeight - y - 1); + if (!fCodec->skipScanlines(linesToSkip)) { + fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + dstHeight, y + 1); + return SkCodec::kIncompleteInput; } - *rowsDecoded = dstHeight; + pixelPtr = SkTAddOffset<void>(pixelPtr, rowBytes); } - return result; + return SkCodec::kSuccess; } case SkCodec::kNone_SkScanlineOrder: { - SkAutoMalloc storage(srcHeight * rowBytes); + const int linesNeeded = subsetHeight - samplingOffsetY; + SkAutoMalloc storage(linesNeeded * rowBytes); uint8_t* storagePtr = static_cast<uint8_t*>(storage.get()); - int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes); - storagePtr += Y0 * rowBytes; - scanlines -= Y0; - int y = 0; - while (y < dstHeight && scanlines > 0) { - memcpy(dst, storagePtr, rowBytes); + + if (!fCodec->skipScanlines(startY)) { + fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + dstHeight, 0); + return SkCodec::kIncompleteInput; + } + int scanlines = fCodec->getScanlines(storagePtr, linesNeeded, rowBytes); + + for (int y = 0; y < dstHeight; y++) { + memcpy(pixels, storagePtr, info.minRowBytes()); storagePtr += sampleY * rowBytes; - dst = SkTAddOffset<void>(dst, rowBytes); - scanlines -= sampleY; - y++; + pixels = SkTAddOffset<void>(pixels, rowBytes); } - if (y < dstHeight) { + + if (scanlines < dstHeight) { // fCodec has already handled filling uninitialized memory. - *rowsDecoded = dstHeight; - return kIncompleteInput; + return SkCodec::kIncompleteInput; } - return kSuccess; + return SkCodec::kSuccess; } default: SkASSERT(false); - return kUnimplemented; + return SkCodec::kUnimplemented; } } - -uint32_t SkScaledCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { - return fCodec->onGetFillValue(colorType, alphaType); -} - -SkCodec::SkScanlineOrder SkScaledCodec::onGetScanlineOrder() const { - return fCodec->onGetScanlineOrder(); -} diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp index de69124340..4eea8799c5 100644 --- a/src/codec/SkSwizzler.cpp +++ b/src/codec/SkSwizzler.cpp @@ -7,7 +7,6 @@ #include "SkCodecPriv.h" #include "SkColorPriv.h" -#include "SkScaledCodec.h" #include "SkSwizzler.h" #include "SkTemplates.h" diff --git a/src/codec/SkWebpAdapterCodec.cpp b/src/codec/SkWebpAdapterCodec.cpp new file mode 100644 index 0000000000..0804e748a2 --- /dev/null +++ b/src/codec/SkWebpAdapterCodec.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCodec.h" +#include "SkCodecPriv.h" +#include "SkWebpAdapterCodec.h" + +SkWebpAdapterCodec::SkWebpAdapterCodec(SkWebpCodec* codec) + : INHERITED(codec->getInfo()) + , fCodec(codec) +{} + +SkISize SkWebpAdapterCodec::onGetSampledDimensions(int sampleSize) const { + float scale = get_scale_from_sample_size(sampleSize); + return fCodec->getScaledDimensions(scale); +} + +bool SkWebpAdapterCodec::onGetSupportedSubset(SkIRect* desiredSubset) const { + return fCodec->getValidSubset(desiredSubset); +} + +SkCodec::Result SkWebpAdapterCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, AndroidOptions& options) { + // SkWebpCodec will support pretty much any dimensions that we provide, but we want + // to be stricter about the type of scaling that we allow, so we will add an extra + // check here. + SkISize supportedSize; + if (!options.fSubset) { + supportedSize = this->onGetSampledDimensions(options.fSampleSize); + } else { + supportedSize = this->getSampledSubsetDimensions(options.fSampleSize, *options.fSubset); + } + if (supportedSize != info.dimensions()) { + return SkCodec::kInvalidParameters; + } + + SkCodec::Options codecOptions; + codecOptions.fZeroInitialized = options.fZeroInitialized; + codecOptions.fSubset = options.fSubset; + return fCodec->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr, + options.fColorCount); +} diff --git a/src/codec/SkWebpAdapterCodec.h b/src/codec/SkWebpAdapterCodec.h new file mode 100644 index 0000000000..b48f39bb60 --- /dev/null +++ b/src/codec/SkWebpAdapterCodec.h @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkWebpAdapterCodec_DEFINED +#define SkWebpAdapterCodec_DEFINED + +#include "SkAndroidCodec.h" +#include "SkWebpCodec.h" + +/** + * This class implements the functionality of SkAndroidCodec. It uses an + * SkWebpCodec. + */ +class SkWebpAdapterCodec : public SkAndroidCodec { +public: + + explicit SkWebpAdapterCodec(SkWebpCodec*); + + virtual ~SkWebpAdapterCodec() {} + +protected: + + SkEncodedFormat onGetEncodedFormat() const override { return kWEBP_SkEncodedFormat; }; + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override; + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + AndroidOptions& options) override; + +private: + + SkAutoTDelete<SkWebpCodec> fCodec; + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkWebpAdapterCodec_DEFINED diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp index 5ef1f8a86e..b2743bfa1f 100644 --- a/tests/CodexTest.cpp +++ b/tests/CodexTest.cpp @@ -6,12 +6,12 @@ */ #include "Resources.h" +#include "SkAndroidCodec.h" #include "SkBitmap.h" #include "SkCodec.h" #include "SkData.h" #include "SkMD5.h" #include "SkRandom.h" -#include "SkScaledCodec.h" #include "Test.h" static SkStreamAsset* resource(const char path[]) { @@ -63,6 +63,20 @@ static void test_info(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& } } +static void test_android_info(skiatest::Reporter* r, SkAndroidCodec* codec, const SkImageInfo& info, + SkCodec::Result expectedResult, const SkMD5::Digest* goodDigest) { + SkBitmap bm; + bm.allocPixels(info); + SkAutoLockPixels autoLockPixels(bm); + + SkCodec::Result result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes()); + REPORTER_ASSERT(r, result == expectedResult); + + if (goodDigest) { + compare_to_good_digest(r, *goodDigest, bm); + } +} + SkIRect generate_random_subset(SkRandom* rand, int w, int h) { SkIRect rect; do { @@ -128,6 +142,59 @@ static void test_codec(skiatest::Reporter* r, SkCodec* codec, SkBitmap& bm, cons } } +static void test_android_codec(skiatest::Reporter* r, SkAndroidCodec* codec, SkBitmap& bm, + const SkImageInfo& info, const SkISize& size, bool supports565, + SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { + + REPORTER_ASSERT(r, info.dimensions() == size); + bm.allocPixels(info); + SkAutoLockPixels autoLockPixels(bm); + + SkCodec::Result result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes()); + REPORTER_ASSERT(r, result == expectedResult); + + md5(bm, digest); + if (goodDigest) { + REPORTER_ASSERT(r, *digest == *goodDigest); + } + + { + // Test decoding to 565 + SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); + SkCodec::Result expected565 = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ? + expectedResult : SkCodec::kInvalidConversion; + test_android_info(r, codec, info565, expected565, nullptr); + } + + // Verify that re-decoding gives the same result. It is interesting to check this after + // a decode to 565, since choosing to decode to 565 may result in some of the decode + // options being modified. These options should return to their defaults on another + // decode to kN32, so the new digest should match the old digest. + test_android_info(r, codec, info, expectedResult, digest); + + { + // Check alpha type conversions + if (info.alphaType() == kOpaque_SkAlphaType) { + test_android_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), + SkCodec::kInvalidConversion, nullptr); + test_android_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), + SkCodec::kInvalidConversion, nullptr); + } else { + // Decoding to opaque should fail + test_android_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), + SkCodec::kInvalidConversion, nullptr); + SkAlphaType otherAt = info.alphaType(); + if (kPremul_SkAlphaType == otherAt) { + otherAt = kUnpremul_SkAlphaType; + } else { + otherAt = kPremul_SkAlphaType; + } + // The other non-opaque alpha type should always succeed, but not match. + test_android_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); + } + } +} + // FIXME: SkScaledCodec is currently only supported for types used by BRD // skbug.com/4428 static bool supports_scaled_codec(const char path[]) { @@ -288,13 +355,13 @@ static void check(skiatest::Reporter* r, return; } - SkAutoTDelete<SkCodec> codec(nullptr); + SkAutoTDelete<SkAndroidCodec> codec(nullptr); if (isIncomplete) { size_t size = stream->getLength(); SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3))); - codec.reset(SkScaledCodec::NewFromData(data)); + codec.reset(SkAndroidCodec::NewFromData(data)); } else { - codec.reset(SkScaledCodec::NewFromStream(stream.detach())); + codec.reset(SkAndroidCodec::NewFromStream(stream.detach())); } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); @@ -303,8 +370,8 @@ static void check(skiatest::Reporter* r, SkBitmap bm; SkMD5::Digest scaledCodecDigest; - test_codec(r, codec, bm, info, size, supports565, expectedResult, &scaledCodecDigest, - &codecDigest); + test_android_codec(r, codec, bm, info, size, supports565, expectedResult, + &scaledCodecDigest, &codecDigest); } // If we've just tested incomplete decodes, let's run the same test again on full decodes. @@ -463,8 +530,9 @@ static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_ SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false)); REPORTER_ASSERT(r, !codec); - codec = SkScaledCodec::NewFromStream(new SkMemoryStream(stream, len, false)); - REPORTER_ASSERT(r, !codec); + SkAndroidCodec* androidCodec = + SkAndroidCodec::NewFromStream(new SkMemoryStream(stream, len, false)); + REPORTER_ASSERT(r, !androidCodec); } // Ensure that SkCodec::NewFromStream handles freeing the passed in SkStream, @@ -496,8 +564,8 @@ DEF_TEST(Codec_null, r) { SkCodec* codec = SkCodec::NewFromStream(nullptr); REPORTER_ASSERT(r, !codec); - codec = SkScaledCodec::NewFromStream(nullptr); - REPORTER_ASSERT(r, !codec); + SkAndroidCodec* androidCodec = SkAndroidCodec::NewFromStream(nullptr); + REPORTER_ASSERT(r, !androidCodec); } static void test_dimensions(skiatest::Reporter* r, const char path[]) { @@ -507,16 +575,16 @@ static void test_dimensions(skiatest::Reporter* r, const char path[]) { SkDebugf("Missing resource '%s'\n", path); return; } - SkAutoTDelete<SkCodec> codec(SkScaledCodec::NewFromStream(stream.detach())); + SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(stream.detach())); if (!codec) { ERRORF(r, "Unable to create codec '%s'", path); return; } // Check that the decode is successful for a variety of scales - for (float scale = 0.05f; scale < 2.0f; scale += 0.05f) { + for (int sampleSize = 1; sampleSize < 10; sampleSize++) { // Scale the output dimensions - SkISize scaledDims = codec->getScaledDimensions(scale); + SkISize scaledDims = codec->getSampledDimensions(sampleSize); SkImageInfo scaledInfo = codec->getInfo() .makeWH(scaledDims.width(), scaledDims.height()) .makeColorType(kN32_SkColorType); @@ -526,8 +594,10 @@ static void test_dimensions(skiatest::Reporter* r, const char path[]) { size_t totalBytes = scaledInfo.getSafeSize(rowBytes); SkAutoTMalloc<SkPMColor> pixels(totalBytes); + SkAndroidCodec::AndroidOptions options; + options.fSampleSize = sampleSize; SkCodec::Result result = - codec->getPixels(scaledInfo, pixels.get(), rowBytes, nullptr, nullptr, nullptr); + codec->getAndroidPixels(scaledInfo, pixels.get(), rowBytes, &options); REPORTER_ASSERT(r, SkCodec::kSuccess == result); } } |