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:
parent
5a89ed542f
commit
1340dbde91
2
BUILD.gn
2
BUILD.gn
@ -1236,7 +1236,7 @@ component("skia") {
|
||||
"src/sfnt/SkOTUtils.cpp",
|
||||
]
|
||||
|
||||
defines = []
|
||||
defines = [ "SK_HAS_ANDROID_CODEC" ]
|
||||
libs = []
|
||||
|
||||
if (is_win) {
|
||||
|
2
DEPS
2
DEPS
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
@ -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
|
||||
|
@ -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*/,
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 = ⊂
|
||||
}
|
||||
|
||||
// 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 = ⊂
|
||||
}
|
||||
SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo,
|
||||
&sampledOptions);
|
||||
if (SkCodec::kIncompleteInput == result || SkCodec::kErrorInInput == result) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 ,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user