Skip to content

Commit

Permalink
[rust png] Add a new SkCodec::isAnimated API.
Browse files Browse the repository at this point in the history
The new `SkCodec::isAnimated` API helps to disambiguate what is meant by
`codec->getRepetitionCount()` returning a zero.  This in turn supports
https://crrev.com/c/6271944 which tweaks Chromium's
`blink::SkiaImageDecoderBase::RepetitionCount` to correctly return
`kAnimationNone` for partial input of a static PNG image (rather than
returning `kAnimationLoopOnce`).  https://crbug.com/396192872#comment24
and https://crbug.com/396192872#comment26 describe why adding the new
API seems like the best way forward.

Bug: chromium:396192872
Change-Id: If425788aaafbdb2f4683390eccc2e08c40e9ea3e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/952050
Commit-Queue: Łukasz Anforowicz <[email protected]>
Reviewed-by: Florin Malita <[email protected]>
Auto-Submit: Łukasz Anforowicz <[email protected]>
  • Loading branch information
anforowicz authored and SkCQ committed Feb 19, 2025
1 parent 96521ff commit 1f2c440
Show file tree
Hide file tree
Showing 16 changed files with 113 additions and 1 deletion.
7 changes: 7 additions & 0 deletions experimental/rust_png/decoder/impl/SkPngRustCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,13 @@ int SkPngRustCodec::onGetRepetitionCount() {
return numPlays - 1;
}

SkCodec::IsAnimated SkPngRustCodec::onIsAnimated() {
if (fReader->has_actl_chunk() && fReader->get_actl_num_frames() > 1) {
return IsAnimated::kYes;
}
return IsAnimated::kNo;
}

std::optional<SkSpan<const SkPngCodecBase::PaletteColorEntry>> SkPngRustCodec::onTryGetPlteChunk() {
if (fReader->output_color_type() != rust_png::ColorType::Indexed) {
return std::nullopt;
Expand Down
1 change: 1 addition & 0 deletions experimental/rust_png/decoder/impl/SkPngRustCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class SkPngRustCodec final : public SkPngCodecBase {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;
const SkFrameHolder* getFrameHolder() const override;
std::unique_ptr<SkStream> getEncodedData() const override;

Expand Down
40 changes: 39 additions & 1 deletion include/codec/SkCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -784,12 +784,46 @@ class SK_API SkCodec : SkNoncopyable {
*
* As such, future decoding calls may require a rewind.
*
* For still (non-animated) image codecs, this will return 0.
* `getRepetitionCount` will return `0` in two cases:
* 1. Still (non-animated) images.
* 2. Animated images that only play the animation once (i.e. that don't
* repeat the animation)
* `isAnimated` can be used to disambiguate between these two cases.
*/
int getRepetitionCount() {
return this->onGetRepetitionCount();
}

/**
* `isAnimated` returns whether the full input is expected to contain an
* animated image (i.e. more than 1 image frame). This can be used to
* disambiguate the meaning of `getRepetitionCount` returning `0` (see
* `getRepetitionCount`'s doc comment for more details).
*
* Note that in some codecs `getFrameCount()` only returns the number of
* frames for which all the metadata has been already successfully decoded.
* Therefore for a partial input `isAnimated()` may return "yes", even
* though `getFrameCount()` may temporarily return `1` until more of the
* input is available.
*
* When handling partial input, some codecs may not know until later (e.g.
* until encountering additional image frames) whether the given image has
* more than one frame. Such codecs may initially return
* `IsAnimated::kUnknown` and only later give a definitive "yes" or "no"
* answer. GIF format is one example where this may happen.
*
* Other codecs may be able to decode the information from the metadata
* present before the first image frame. Such codecs should be able to give
* a definitive "yes" or "no" answer as soon as they are constructed. PNG
* format is one example where this happens.
*/
enum class IsAnimated {
kYes,
kNo,
kUnknown,
};
IsAnimated isAnimated() { return this->onIsAnimated(); }

// Register a decoder at runtime by passing two function pointers:
// - peek() to return true if the span of bytes appears to be your encoded format;
// - make() to attempt to create an SkCodec from the given stream.
Expand Down Expand Up @@ -937,6 +971,10 @@ class SK_API SkCodec : SkNoncopyable {
return 0;
}

virtual IsAnimated onIsAnimated() {
return IsAnimated::kNo;
}

private:
const SkEncodedInfo fEncodedInfo;
XformFormat fSrcXformFormat;
Expand Down
2 changes: 2 additions & 0 deletions relnotes/codec_is_animated_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The `SkCodec` class has a new `isAnimated` method which helps to disambiguate
the meaning of `codec->getRepetitionCount()` returning `0`.
7 changes: 7 additions & 0 deletions src/codec/SkAvifCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ bool SkAvifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {

int SkAvifCodec::onGetRepetitionCount() { return kRepetitionCountInfinite; }

SkCodec::IsAnimated SkAvifCodec::onIsAnimated() {
if (!fUseAnimation || fAvifDecoder->imageCount <= 1) {
return IsAnimated::kNo;
}
return IsAnimated::kYes;
}

SkCodec::Result SkAvifCodec::onGetPixels(const SkImageInfo& dstInfo,
void* dst,
size_t dstRowBytes,
Expand Down
1 change: 1 addition & 0 deletions src/codec/SkAvifCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SkAvifCodec : public SkScalingCodec {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;
const SkFrameHolder* getFrameHolder() const override { return &fFrameHolder; }

private:
Expand Down
7 changes: 7 additions & 0 deletions src/codec/SkCrabbyAvifCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,13 @@ int SkCrabbyAvifCodec::onGetRepetitionCount() {
: fAvifDecoder->repetitionCount;
}

SkCodec::IsAnimated SkCrabbyAvifCodec::onIsAnimated() {
if (!fUseAnimation || fAvifDecoder->imageCount <= 1) {
return IsAnimated::kNo;
}
return IsAnimated::kYes;
}

bool SkCrabbyAvifCodec::conversionSupported(const SkImageInfo& dstInfo,
bool srcIsOpaque,
bool needsColorXform) {
Expand Down
1 change: 1 addition & 0 deletions src/codec/SkCrabbyAvifCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class SkCrabbyAvifCodec : public SkScalingCodec {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;
const SkFrameHolder* getFrameHolder() const override { return &fFrameHolder; }
bool conversionSupported(const SkImageInfo&, bool, bool) override;
bool onGetGainmapCodec(SkGainmapInfo* info, std::unique_ptr<SkCodec>* gainmapCodec) override;
Expand Down
14 changes: 14 additions & 0 deletions src/codec/SkHeifCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,20 @@ int SkHeifCodec::onGetRepetitionCount() {
return kRepetitionCountInfinite;
}

SkCodec::IsAnimated SkHeifCodec::onIsAnimated() {
if (!fUseAnimation) {
return IsAnimated::kNo;
}

size_t frameCount;
HeifFrameInfo frameInfo;
if (!fHeifDecoder->getSequenceInfo(&frameInfo, &frameCount)) {
return IsAnimated::kUnknown;
}

return (frameCount > 1) ? IsAnimated::kYes : IsAnimated::kNo;
}

/*
* Performs the heif decode
*/
Expand Down
1 change: 1 addition & 0 deletions src/codec/SkHeifCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SkHeifCodec : public SkCodec {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;
const SkFrameHolder* getFrameHolder() const override {
return &fFrameHolder;
}
Expand Down
5 changes: 5 additions & 0 deletions src/codec/SkJpegxlCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,11 @@ int SkJpegxlCodec::onGetRepetitionCount() {
return std::numeric_limits<int>::max();
}

SkCodec::IsAnimated SkJpegxlCodec::onIsAnimated() {
JxlBasicInfo& info = fCodec->fInfo;
return info.have_animation ? IsAnimated::kYes : IsAnimated::kNo;
}

const SkFrameHolder* SkJpegxlCodec::getFrameHolder() const {
return fCodec.get();
}
Expand Down
1 change: 1 addition & 0 deletions src/codec/SkJpegxlCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class SkJpegxlCodec : public SkScalingCodec {
bool onGetFrameInfo(int, FrameInfo*) const override;

int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;

private:
const SkFrameHolder* getFrameHolder() const override;
Expand Down
5 changes: 5 additions & 0 deletions src/codec/SkWebpCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ int SkWebpCodec::onGetRepetitionCount() {
return loopCount;
}

SkCodec::IsAnimated SkWebpCodec::onIsAnimated() {
auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
return (flags & ANIMATION_FLAG) != 0 ? IsAnimated::kYes : IsAnimated::kNo;
}

int SkWebpCodec::onGetFrameCount() {
auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
if (!(flags & ANIMATION_FLAG)) {
Expand Down
1 change: 1 addition & 0 deletions src/codec/SkWebpCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class SkWebpCodec final : public SkScalingCodec {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;

const SkFrameHolder* getFrameHolder() const override {
return &fFrameHolder;
Expand Down
11 changes: 11 additions & 0 deletions src/codec/SkWuffsCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class SkWuffsCodec final : public SkScalingCodec {
int onGetFrameCount() override;
bool onGetFrameInfo(int, FrameInfo*) const override;
int onGetRepetitionCount() override;
IsAnimated onIsAnimated() override;

// Two separate implementations of onStartIncrementalDecode and
// onIncrementalDecode, named "one pass" and "two pass" decoding. One pass
Expand Down Expand Up @@ -881,6 +882,16 @@ int SkWuffsCodec::onGetRepetitionCount() {
return n < INT_MAX ? n : INT_MAX;
}

SkCodec::IsAnimated SkWuffsCodec::onIsAnimated() {
if (fFrames.size() > 1) {
return IsAnimated::kYes;
}

// If we only have encounted a single image frame so far, then we have an
// ambiguous situation - maybe more frames will come, but maybe not.
return fFramesComplete ? IsAnimated::kNo : IsAnimated::kUnknown;
}

SkCodec::Result SkWuffsCodec::seekFrame(int frameIndex) {
if (fDecoderIsSuspended) {
SkCodec::Result res = this->resetDecoder();
Expand Down
10 changes: 10 additions & 0 deletions tests/CodecAnimTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,16 @@ DEF_TEST(Codec_frames, r) {
rec.fName, rec.fRepetitionCount, repetitionCount);
}

// When decoding the full, non-partial input, `isAnimated()` will
// just be a proxy for "is there just a single frame?".
const SkCodec::IsAnimated expectedIsAnimated =
rec.fFrameCount == 1 ? SkCodec::IsAnimated::kNo : SkCodec::IsAnimated::kYes;
const SkCodec::IsAnimated actualIsAnimated = codec->isAnimated();
if (expectedIsAnimated != actualIsAnimated) {
ERRORF(r, "%s isAnimated does not match! expected: %i\tactual: %i", rec.fName,
static_cast<int>(expectedIsAnimated), static_cast<int>(actualIsAnimated));
}

// From here on, we are only concerned with animated images.
if (1 == frameCount) {
continue;
Expand Down

0 comments on commit 1f2c440

Please sign in to comment.