Reland "SkAndroidCodec: Support decoding all frames"

This is a reland of fc4fdc5b25

Original change's description:
> SkAndroidCodec: Support decoding all frames
>
> Bug: b/160984428
> Bug: b/163595585
>
> Add support to SkAndroidCodec for decoding all frames with an
> fSampleSize, so that an entire animation can be decoded to a smaller
> size.
>
> dm/:
> - Test scaled + animated decodes
>
> SkAndroidCodec:
> - Make AndroidOptions inherit from SkCodec::Options. This allows
>   SkAndroidCodec to use fFrameIndex. (It also combines the two versions
>   of fSubset, which is now const for both.)
> - Respect fFrameIndex, and call SkCodec::handleFrameIndex to decode
>   the required frame.
> - Disallow decoding with kRespect + fFrameIndex > 0 if there is a
>   non-default orientation. As currently written (except without
>   disabling this combination), SkPixmapPriv::Orient would draw the new
>   portion of the frame on top of uninitialized pixels, instead of the
>   prior frame. This could be fixed by
>   - If SkAndroidCodec needs to decode the required frame, it could do so
>     without applying the orientation, then decode fFrameIndex, and then
>     apply the orientation.
>   - If the client provided the required frame, SkAndroidCodec would need
>     to un-apply the orientation to get the proper starting state, then
>     decode and apply.
>   I think it is simpler to force the client to handle the orientation
>   externally.
>
> SkCodec:
> - Allow SkAndroidCodec to call its private method handleFrameIndex. This
>   method handles decoding a required frame, if necessary. When called by
>   SkAndroidCodec, it now uses the SkAndroidCodec to check for/decode the
>   required frame, so that it will scale properly.
> - Call rewindIfNeeded inside handleFrameIndex. handleFrameIndex calls a
>   virtual method which may set some state (e.g. in SkJpegCodec). Without
>   this change, that state would be reset by rewindIfNeeded.
> - Simplify handling a kRestoreBGColor frame. Whether provided or not,
>   take the same path to calling zero_rect.
> - Updates to zero_rect:
>   - Intersect after scaling, which will also check for empty.
>   - Round out instead of in - this ensures we don't under-erase
>   - Use kFill_ScaleToFit, which better matches the intent.
>
> Change-Id: Ibe1951980a0dca8f5b7b1f20192432d395681683
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/333225
> Commit-Queue: Leon Scroggins <scroggo@google.com>
> Reviewed-by: Derek Sollenberger <djsollen@google.com>

Bug: b/160984428
Bug: b/163595585
Change-Id: I7c1e79e0f92c75b4840eef65c8fc2b8497189e81
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/334842
Auto-Submit: Leon Scroggins <scroggo@google.com>
Commit-Queue: Derek Sollenberger <djsollen@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
This commit is contained in:
Leon Scroggins 2020-11-09 14:18:12 -05:00 committed by Skia Commit-Bot
parent 5a89ed542f
commit 1340dbde91
14 changed files with 254 additions and 149 deletions

View File

@ -1236,7 +1236,7 @@ component("skia") {
"src/sfnt/SkOTUtils.cpp",
]
defines = []
defines = [ "SK_HAS_ANDROID_CODEC" ]
libs = []
if (is_win) {

2
DEPS
View File

@ -24,7 +24,7 @@ deps = {
"third_party/externals/harfbuzz" : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@3a74ee528255cc027d84b204a87b5c25e47bff79",
"third_party/externals/icu" : "https://chromium.googlesource.com/chromium/deps/icu.git@dbd3825b31041d782c5b504c59dcfb5ac7dda08c",
"third_party/externals/imgui" : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@9418dcb69355558f70de260483424412c5ca2fce",
"third_party/externals/libgifcodec" : "https://skia.googlesource.com/libgifcodec@d06d2a6d42baf6c0c91cacc28df2542a911d05fe",
"third_party/externals/libgifcodec" : "https://skia.googlesource.com/libgifcodec@b89e0a4edd6c0158b24730845e0e498969e22a16",
"third_party/externals/libjpeg-turbo" : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@64fc43d52351ed52143208ce6a656c03db56462b",
"third_party/externals/libpng" : "https://skia.googlesource.com/third_party/libpng.git@386707c6d19b974ca2e3db7f5c61873813c6fe44",
"third_party/externals/libwebp" : "https://chromium.googlesource.com/webm/libwebp.git@55a080e50af655d1fbe0a5c22954835cdd59ff92",

View File

@ -764,6 +764,10 @@ static void push_codec_srcs(Path path) {
push_codec_src(path, CodecSrc::kAnimated_Mode, dstCT, at, 1.0f);
}
}
for (float scale : { .5f, .33f }) {
push_codec_src(path, CodecSrc::kAnimated_Mode, CodecSrc::kGetFromCanvas_DstColorType,
kPremul_SkAlphaType, scale);
}
}
}

View File

@ -436,6 +436,13 @@ Result CodecSrc::draw(GrDirectContext*, SkCanvas* canvas) const {
// Try to scale the image if it is desired
SkISize size = codec->getScaledDimensions(fScale);
std::unique_ptr<SkAndroidCodec> androidCodec;
if (1.0f != fScale && fMode == kAnimated_Mode) {
androidCodec = SkAndroidCodec::MakeFromData(encoded);
size = androidCodec->getSampledDimensions(1 / fScale);
}
if (size == decodeInfo.dimensions() && 1.0f != fScale) {
return Result::Skip("Test without scaling is uninteresting.");
}
@ -467,7 +474,16 @@ Result CodecSrc::draw(GrDirectContext*, SkCanvas* canvas) const {
switch (fMode) {
case kAnimated_Mode: {
std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
SkAndroidCodec::AndroidOptions androidOptions;
if (fScale != 1.0f) {
SkASSERT(androidCodec);
androidOptions.fSampleSize = 1 / fScale;
auto dims = androidCodec->getSampledDimensions(androidOptions.fSampleSize);
decodeInfo = decodeInfo.makeDimensions(dims);
}
std::vector<SkCodec::FrameInfo> frameInfos = androidCodec
? androidCodec->codec()->getFrameInfo() : codec->getFrameInfo();
if (frameInfos.size() <= 1) {
return Result::Fatal("%s is not an animated image.", fPath.c_str());
}
@ -482,19 +498,21 @@ Result CodecSrc::draw(GrDirectContext*, SkCanvas* canvas) const {
SkAutoMalloc priorFramePixels;
int cachedFrame = SkCodec::kNoFrame;
for (int i = 0; static_cast<size_t>(i) < frameInfos.size(); i++) {
options.fFrameIndex = i;
androidOptions.fFrameIndex = i;
// Check for a prior frame
const int reqFrame = frameInfos[i].fRequiredFrame;
if (reqFrame != SkCodec::kNoFrame && reqFrame == cachedFrame
&& priorFramePixels.get()) {
// Copy into pixels
memcpy(pixels.get(), priorFramePixels.get(), safeSize);
options.fPriorFrame = reqFrame;
androidOptions.fPriorFrame = reqFrame;
} else {
options.fPriorFrame = SkCodec::kNoFrame;
androidOptions.fPriorFrame = SkCodec::kNoFrame;
}
SkCodec::Result result = codec->getPixels(decodeInfo, pixels.get(),
rowBytes, &options);
SkCodec::Result result = androidCodec
? androidCodec->getAndroidPixels(decodeInfo, pixels.get(), rowBytes,
&androidOptions)
: codec->getPixels(decodeInfo, pixels.get(), rowBytes, &androidOptions);
if (SkCodec::kInvalidInput == result && i > 0) {
// Some of our test images have truncated later frames. Treat that
// the same as incomplete.
@ -759,30 +777,33 @@ SkISize CodecSrc::size() const {
return {0, 0};
}
auto imageSize = codec->getScaledDimensions(fScale);
if (fMode == kAnimated_Mode) {
// We'll draw one of each frame, so make it big enough to hold them all
// in a grid. The grid will be roughly square, with "factor" frames per
// row and up to "factor" rows.
const size_t count = codec->getFrameInfo().size();
const float root = sqrt((float) count);
const int factor = sk_float_ceil2int(root);
imageSize.fWidth = imageSize.fWidth * factor;
imageSize.fHeight = imageSize.fHeight * sk_float_ceil2int((float) count / (float) factor);
if (fMode != kAnimated_Mode) {
return codec->getScaledDimensions(fScale);
}
// We'll draw one of each frame, so make it big enough to hold them all
// in a grid. The grid will be roughly square, with "factor" frames per
// row and up to "factor" rows.
const size_t count = codec->getFrameInfo().size();
const float root = sqrt((float) count);
const int factor = sk_float_ceil2int(root);
auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
auto imageSize = androidCodec->getSampledDimensions(1 / fScale);
imageSize.fWidth = imageSize.fWidth * factor;
imageSize.fHeight = imageSize.fHeight * sk_float_ceil2int((float) count / (float) factor);
return imageSize;
}
Name CodecSrc::name() const {
Name name = SkOSPath::Basename(fPath.c_str());
if (fMode == kAnimated_Mode) {
name.append("_animated");
}
if (1.0f == fScale) {
Name name = SkOSPath::Basename(fPath.c_str());
if (fMode == kAnimated_Mode) {
name.append("_animated");
}
return name;
}
SkASSERT(fMode != kAnimated_Mode);
return get_scaled_name(fPath, fScale);
return get_scaled_name(name.c_str(), fScale);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

View File

@ -195,32 +195,12 @@ public:
// 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 {
struct AndroidOptions : public SkCodec::Options {
AndroidOptions()
: fZeroInitialized(SkCodec::kNo_ZeroInitialized)
, fSubset(nullptr)
: SkCodec::Options()
, fSampleSize(1)
{}
/**
* Indicates is destination pixel memory is zero initialized.
*
* The default is SkCodec::kNo_ZeroInitialized.
*/
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 SkEncodedImageFormat::kWEBP, the top and left
* values must be even.
*
* The default is NULL, meaning a decode of the entire image.
*/
SkIRect* fSubset;
/**
* The client may provide an integer downscale factor for the decode.
* The codec may implement this downscaling by sampling or another

View File

@ -24,6 +24,7 @@
#include <vector>
class SkAndroidCodec;
class SkColorSpace;
class SkData;
class SkFrameHolder;
@ -854,6 +855,10 @@ private:
bool fStartedIncrementalDecode;
// Allows SkAndroidCodec to call handleFrameIndex (potentially decoding a prior frame and
// clearing to transparent) without SkCodec calling it, too.
bool fAndroidCodecHandlesFrameIndex;
bool initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha, bool srcIsOpaque);
/**
@ -878,8 +883,15 @@ private:
/**
* Check for a valid Options.fFrameIndex, and decode prior frames if necessary.
*
* If androidCodec is not null, that means this SkCodec is owned by an SkAndroidCodec. In that
* case, the Options will be treated as an AndroidOptions, and SkAndroidCodec will be used to
* decode a prior frame, if a prior frame is needed. When such an owned SkCodec calls
* handleFrameIndex, it will immediately return kSuccess, since SkAndroidCodec already handled
* it.
*/
Result handleFrameIndex(const SkImageInfo&, void* pixels, size_t rowBytes, const Options&);
Result handleFrameIndex(const SkImageInfo&, void* pixels, size_t rowBytes, const Options&,
SkAndroidCodec* androidCodec = nullptr);
// Methods for scanline decoding.
virtual Result onStartScanlineDecode(const SkImageInfo& /*dstInfo*/,

View File

@ -732,6 +732,7 @@ def base_defines(os_conditions):
# JPEG is in codec_limited
"SK_CODEC_DECODES_JPEG",
"SK_ENCODE_JPEG",
"SK_HAS_ANDROID_CODEC",
# Needed for some tests in dm
"SK_ENABLE_SKSL_INTERPRETER",
] + skia_select(

View File

@ -372,21 +372,35 @@ SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo,
AndroidOptions defaultOptions;
if (!options) {
options = &defaultOptions;
} else if (options->fSubset) {
if (!is_valid_subset(*options->fSubset, adjustedInfo.dimensions())) {
return SkCodec::kInvalidParameters;
} else {
if (options->fSubset) {
if (!is_valid_subset(*options->fSubset, adjustedInfo.dimensions())) {
return SkCodec::kInvalidParameters;
}
if (SkIRect::MakeSize(adjustedInfo.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;
}
}
if (SkIRect::MakeSize(adjustedInfo.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;
// To simplify frame compositing, force the client to use kIgnore and
// handle orientation themselves.
if (options->fFrameIndex != 0 && fOrientationBehavior == ExifOrientationBehavior::kRespect
&& fCodec->getOrigin() != kDefault_SkEncodedOrigin) {
return SkCodec::kInvalidParameters;
}
}
if (auto result = fCodec->handleFrameIndex(requestInfo, requestPixels, requestRowBytes,
*options, this); result != SkCodec::kSuccess) {
return result;
}
if (ExifOrientationBehavior::kIgnore == fOrientationBehavior) {
return this->onGetAndroidPixels(requestInfo, requestPixels, requestRowBytes, *options);
}

View File

@ -23,8 +23,5 @@ bool SkAndroidCodecAdapter::onGetSupportedSubset(SkIRect* desiredSubset) const {
SkCodec::Result SkAndroidCodecAdapter::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
size_t rowBytes, const AndroidOptions& options) {
SkCodec::Options codecOptions;
codecOptions.fZeroInitialized = options.fZeroInitialized;
codecOptions.fSubset = options.fSubset;
return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions);
return this->codec()->getPixels(info, pixels, rowBytes, &options);
}

View File

@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
#include "include/codec/SkAndroidCodec.h"
#include "include/codec/SkCodec.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
@ -165,6 +166,7 @@ SkCodec::SkCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr<Sk
, fOptions()
, fCurrScanline(-1)
, fStartedIncrementalDecode(false)
, fAndroidCodecHandlesFrameIndex(false)
{}
SkCodec::~SkCodec() {}
@ -245,25 +247,21 @@ static SkIRect frame_rect_on_screen(SkIRect frameRect,
bool zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
SkISize srcDimensions, SkIRect prevRect) {
prevRect = frame_rect_on_screen(prevRect, SkIRect::MakeSize(srcDimensions));
if (prevRect.isEmpty()) {
return true;
}
const auto dimensions = dstInfo.dimensions();
if (dimensions != srcDimensions) {
SkRect src = SkRect::Make(srcDimensions);
SkRect dst = SkRect::Make(dimensions);
SkMatrix map = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kCenter_ScaleToFit);
SkMatrix map = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
SkRect asRect = SkRect::Make(prevRect);
if (!map.mapRect(&asRect)) {
return false;
}
asRect.roundIn(&prevRect);
if (prevRect.isEmpty()) {
// Down-scaling shrank the empty portion to nothing,
// so nothing to zero.
return true;
}
asRect.roundOut(&prevRect);
}
if (!prevRect.intersect(SkIRect::MakeSize(dimensions))) {
// Nothing to zero, due to scaling or bad frame rect.
return true;
}
const SkImageInfo info = dstInfo.makeDimensions(prevRect.size());
@ -275,7 +273,19 @@ bool zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
}
SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels, size_t rowBytes,
const Options& options) {
const Options& options, SkAndroidCodec* androidCodec) {
if (androidCodec) {
// This is never set back to false. If SkAndroidCodec is calling this method, its fCodec
// should never call it directly.
fAndroidCodecHandlesFrameIndex = true;
} else if (fAndroidCodecHandlesFrameIndex) {
return kSuccess;
}
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
const int index = options.fFrameIndex;
if (0 == index) {
return this->initializeColorXform(info, fEncodedInfo.alpha(), fEncodedInfo.opaque())
@ -304,46 +314,55 @@ SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels,
const int requiredFrame = frame->getRequiredFrame();
if (requiredFrame != kNoFrame) {
if (options.fPriorFrame != kNoFrame) {
const SkFrame* preppedFrame = nullptr;
if (options.fPriorFrame == kNoFrame) {
Result result = kInternalError;
if (androidCodec) {
#ifdef SK_HAS_ANDROID_CODEC
SkAndroidCodec::AndroidOptions prevFrameOptions(
reinterpret_cast<const SkAndroidCodec::AndroidOptions&>(options));
prevFrameOptions.fFrameIndex = requiredFrame;
result = androidCodec->getAndroidPixels(info, pixels, rowBytes, &prevFrameOptions);
#endif
} else {
Options prevFrameOptions(options);
prevFrameOptions.fFrameIndex = requiredFrame;
result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions);
}
if (result != kSuccess) {
return result;
}
preppedFrame = frameHolder->getFrame(requiredFrame);
} else {
// Check for a valid frame as a starting point. Alternatively, we could
// treat an invalid frame as not providing one, but rejecting it will
// make it easier to catch the mistake.
if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) {
return kInvalidParameters;
}
const auto* prevFrame = frameHolder->getFrame(options.fPriorFrame);
switch (prevFrame->getDisposalMethod()) {
case SkCodecAnimation::DisposalMethod::kRestorePrevious:
return kInvalidParameters;
case SkCodecAnimation::DisposalMethod::kRestoreBGColor:
// If a frame after the required frame is provided, there is no
// need to clear, since it must be covered by the desired frame.
if (options.fPriorFrame == requiredFrame) {
SkIRect prevRect = prevFrame->frameRect();
if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) {
return kInternalError;
}
preppedFrame = frameHolder->getFrame(options.fPriorFrame);
}
SkASSERT(preppedFrame);
switch (preppedFrame->getDisposalMethod()) {
case SkCodecAnimation::DisposalMethod::kRestorePrevious:
SkASSERT(options.fPriorFrame != kNoFrame);
return kInvalidParameters;
case SkCodecAnimation::DisposalMethod::kRestoreBGColor:
// If a frame after the required frame is provided, there is no
// need to clear, since it must be covered by the desired frame.
// FIXME: If the required frame is kRestoreBGColor, we don't actually need to decode
// it, since we'll just clear it to transparent. Instead, we could decode *its*
// required frame and then clear.
if (preppedFrame->frameId() == requiredFrame) {
SkIRect preppedRect = preppedFrame->frameRect();
if (!zero_rect(info, pixels, rowBytes, this->dimensions(), preppedRect)) {
return kInternalError;
}
break;
default:
break;
}
} else {
Options prevFrameOptions(options);
prevFrameOptions.fFrameIndex = requiredFrame;
prevFrameOptions.fZeroInitialized = kNo_ZeroInitialized;
const Result result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions);
if (result != kSuccess) {
return result;
}
const auto* prevFrame = frameHolder->getFrame(requiredFrame);
const auto disposalMethod = prevFrame->getDisposalMethod();
if (disposalMethod == SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
auto prevRect = prevFrame->frameRect();
if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) {
return kInternalError;
}
}
break;
default:
break;
}
}
@ -363,10 +382,6 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
return kInvalidParameters;
}
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
// Default options.
Options optsStorage;
if (nullptr == options) {
@ -432,15 +447,6 @@ SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& info, void* p
return kInvalidParameters;
}
// FIXME: If the rows come after the rows of a previous incremental decode,
// we might be able to skip the rewind, but only the implementation knows
// that. (e.g. PNG will always need to rewind, since we called longjmp, but
// a bottom-up BMP could skip rewinding if the new rows are above the old
// rows.)
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
// Set options.
Options optsStorage;
if (nullptr == options) {
@ -495,10 +501,6 @@ SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
// Reset fCurrScanline in case of failure.
fCurrScanline = -1;
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
// Set options.
Options optsStorage;
if (nullptr == options) {
@ -538,6 +540,15 @@ SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
return result;
}
// FIXME: See startIncrementalDecode. That method set fNeedsRewind to false
// so that when onStartScanlineDecode calls rewindIfNeeded it would not
// rewind. But it also relies on that call to rewindIfNeeded to set
// fNeedsRewind to true for future decodes. When
// fAndroidCodecHandlesFrameIndex is true, that call to rewindIfNeeded is
// skipped, so this method sets it back to true.
SkASSERT(fAndroidCodecHandlesFrameIndex || fNeedsRewind);
fNeedsRewind = true;
fCurrScanline = 0;
fDstInfo = info;
fOptions = *options;

View File

@ -73,14 +73,10 @@ SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const {
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
size_t rowBytes, const AndroidOptions& options) {
// Create an Options struct for the codec.
SkCodec::Options codecOptions;
codecOptions.fZeroInitialized = options.fZeroInitialized;
SkIRect* subset = options.fSubset;
const SkIRect* subset = options.fSubset;
if (!subset || subset->size() == this->codec()->dimensions()) {
if (this->codec()->dimensionsSupported(info.dimensions())) {
return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions);
return this->codec()->getPixels(info, pixels, rowBytes, &options);
}
// If the native codec does not support the requested scale, scale by sampling.
@ -103,15 +99,17 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
const SkImageInfo scaledInfo = info.makeDimensions(scaledSize);
// Copy so we can use a different fSubset.
AndroidOptions subsetOptions = options;
{
// Although startScanlineDecode expects the bottom and top to match the
// SkImageInfo, startIncrementalDecode uses them to determine which rows to
// decode.
SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY,
scaledSubsetWidth, scaledSubsetHeight);
codecOptions.fSubset = &incrementalSubset;
subsetOptions.fSubset = &incrementalSubset;
const SkCodec::Result startResult = this->codec()->startIncrementalDecode(
scaledInfo, pixels, rowBytes, &codecOptions);
scaledInfo, pixels, rowBytes, &subsetOptions);
if (SkCodec::kSuccess == startResult) {
int rowsDecoded = 0;
const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
@ -128,17 +126,17 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
return startResult;
}
// Otherwise fall down to use the old scanline decoder.
// codecOptions.fSubset will be reset below, so it will not continue to
// subsetOptions.fSubset will be reset below, so it will not continue to
// point to the object that is no longer on the stack.
}
// Start the scanline decode.
SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
scaledSize.height());
codecOptions.fSubset = &scanlineSubset;
subsetOptions.fSubset = &scanlineSubset;
SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo,
&codecOptions);
&subsetOptions);
if (SkCodec::kSuccess != result) {
return result;
}
@ -167,10 +165,6 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
// 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;
@ -198,7 +192,6 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
// The scanline decoder only needs to be aware of subsetting in the x-dimension.
subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height());
sampledOptions.fSubset = &subset;
}
// Since we guarantee that output dimensions are always at least one (even if the sampleSize
@ -217,13 +210,13 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
// Although startScanlineDecode expects the bottom and top to match the
// SkImageInfo, startIncrementalDecode uses them to determine which rows to
// decode.
SkCodec::Options incrementalOptions = sampledOptions;
AndroidOptions incrementalOptions = options;
SkIRect incrementalSubset;
if (sampledOptions.fSubset) {
incrementalSubset.fTop = subsetY;
incrementalSubset.fBottom = subsetY + subsetHeight;
incrementalSubset.fLeft = sampledOptions.fSubset->fLeft;
incrementalSubset.fRight = sampledOptions.fSubset->fRight;
if (options.fSubset) {
incrementalSubset.fTop = subsetY;
incrementalSubset.fBottom = subsetY + subsetHeight;
incrementalSubset.fLeft = subset.fLeft;
incrementalSubset.fRight = subset.fRight;
incrementalOptions.fSubset = &incrementalSubset;
}
const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo,
@ -263,6 +256,10 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
}
// Start the scanline decode.
AndroidOptions sampledOptions = options;
if (options.fSubset) {
sampledOptions.fSubset = &subset;
}
SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo,
&sampledOptions);
if (SkCodec::kIncompleteInput == result || SkCodec::kErrorInInput == result) {

View File

@ -300,3 +300,61 @@ DEF_TEST(AndroidCodec_sampledOrientation, r) {
ERRORF(r, "got result \"%s\"\n", SkCodec::ResultToString(result));
}
}
DEF_TEST(AndroidCodec_animatedOrientation, r) {
if (GetResourcePath().isEmpty()) {
return;
}
static const struct {
const char* path;
SkEncodedOrigin origin;
} gRec[] = {
{ "images/stoplight.webp", kDefault_SkEncodedOrigin },
{ "images/stoplight_h.webp", kLeftBottom_SkEncodedOrigin } // Rotated 90 CCW
};
for (auto rec : gRec) {
auto data = GetResourceAsData(rec.path);
if (!data) {
ERRORF(r, "Failed to get resource %s", rec.path);
return;
}
// SkAndroidCodec now allows decoding frames beyond the first, but combining this with
// kRespect-ing a non-kDefault_SkEncodedOrigin is not supported. If a frame depends on
// a prior frame, we allow a client to provide that prior frame. But in order to respect
// the origin, we would need to transform the prior frame back to the original orientation
// in order to blend (and potentially erase, for a kRestoreBG frame) just to transform it
// back. Instead, force the client to handle the orientation after the fact.
for (auto behavior : { SkAndroidCodec::ExifOrientationBehavior::kRespect,
SkAndroidCodec::ExifOrientationBehavior::kIgnore }) {
auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data),
behavior);
REPORTER_ASSERT(r, androidCodec->codec()->getOrigin() == rec.origin);
auto info = androidCodec->getInfo();
SkBitmap bm;
bm.allocPixels(info);
auto result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes());
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
SkAndroidCodec::AndroidOptions options;
options.fFrameIndex = 1;
options.fPriorFrame = 0;
result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options);
switch (behavior) {
case SkAndroidCodec::ExifOrientationBehavior::kRespect:
if (rec.origin != kDefault_SkEncodedOrigin) {
REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters,
"Should not be able to decode frame 1 with exif orientation"
" directly! Result: %s", SkCodec::ResultToString(result));
break;
}
[[fallthrough]];
case SkAndroidCodec::ExifOrientationBehavior::kIgnore:
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
break;
}
}
}
}

View File

@ -389,16 +389,15 @@ DEF_TEST(Codec_frames, r) {
}
}
// Verify that a webp image can be animated scaled down. This image has a
// kRestoreBG frame, so it is an interesting image to test. After decoding that
// Verify that an image can be animated scaled down. These images have a
// kRestoreBG frame, so they are interesting to test. After decoding that
// frame, we have to erase its rectangle. The rectangle has to be adjusted
// based on the scaled size.
DEF_TEST(AndroidCodec_animated, r) {
static void test_animated_AndroidCodec(skiatest::Reporter* r, const char* file) {
if (GetResourcePath().isEmpty()) {
return;
}
const char* file = "images/required.webp";
sk_sp<SkData> data(GetResourceAsData(file));
if (!data) {
ERRORF(r, "Missing %s", file);
@ -456,6 +455,14 @@ DEF_TEST(AndroidCodec_animated, r) {
}
}
DEF_TEST(AndroidCodec_animated, r) {
test_animated_AndroidCodec(r, "images/required.webp");
}
DEF_TEST(AndroidCodec_animated_gif, r) {
test_animated_AndroidCodec(r, "images/required.gif");
}
DEF_TEST(EncodedOriginToMatrixTest, r) {
// SkAnimCodecPlayer relies on the fact that these matrices are invertible.
for (auto origin : { kTopLeft_SkEncodedOrigin ,

View File

@ -610,7 +610,10 @@ static void test_dimensions(skiatest::Reporter* r, const char path[]) {
options.fSampleSize = sampleSize;
SkCodec::Result result =
codec->getAndroidPixels(scaledInfo, pixels.get(), rowBytes, &options);
REPORTER_ASSERT(r, SkCodec::kSuccess == result);
if (result != SkCodec::kSuccess) {
ERRORF(r, "Failed to decode %s with sample size %i; error: %s", path, sampleSize,
SkCodec::ResultToString(result));
}
}
}