| /* |
| * Copyright 2024 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "experimental/rust_png/impl/SkPngRustCodec.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "experimental/rust_png/ffi/FFI.rs.h" |
| #include "include/core/SkStream.h" |
| #include "include/private/SkEncodedInfo.h" |
| #include "include/private/base/SkAssert.h" |
| #include "include/private/base/SkSafe32.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "modules/skcms/skcms.h" |
| #include "src/base/SkAutoMalloc.h" |
| #include "src/base/SkSafeMath.h" |
| #include "src/codec/SkFrameHolder.h" |
| #include "src/codec/SkSwizzler.h" |
| #include "third_party/rust/cxx/v1/cxx.h" |
| |
| #ifdef __clang__ |
| #pragma clang diagnostic error "-Wconversion" |
| #endif |
| |
| namespace { |
| |
| SkEncodedInfo::Color ToColor(rust_png::ColorType colorType) { |
| // TODO(https://crbug.com/359279096): Take `sBIT` chunk into account to |
| // sometimes return `kXAlpha_Color` or `k565_Color`. This may require |
| // a small PR to expose `sBIT` chunk from the `png` crate. |
| |
| switch (colorType) { |
| case rust_png::ColorType::Grayscale: |
| return SkEncodedInfo::kGray_Color; |
| case rust_png::ColorType::Rgb: |
| return SkEncodedInfo::kRGB_Color; |
| case rust_png::ColorType::GrayscaleAlpha: |
| return SkEncodedInfo::kGrayAlpha_Color; |
| case rust_png::ColorType::Rgba: |
| return SkEncodedInfo::kRGBA_Color; |
| // `Indexed` is impossible, because of `png::Transformations::EXPAND`. |
| case rust_png::ColorType::Indexed: |
| break; |
| } |
| SK_ABORT("Unexpected `rust_png::ColorType`: %d", static_cast<int>(colorType)); |
| } |
| |
| SkEncodedInfo::Alpha ToAlpha(rust_png::ColorType colorType) { |
| switch (colorType) { |
| case rust_png::ColorType::Grayscale: |
| case rust_png::ColorType::Rgb: |
| return SkEncodedInfo::kOpaque_Alpha; |
| case rust_png::ColorType::GrayscaleAlpha: |
| case rust_png::ColorType::Rgba: |
| return SkEncodedInfo::kUnpremul_Alpha; |
| // `Indexed` is impossible, because of `png::Transformations::EXPAND`. |
| case rust_png::ColorType::Indexed: |
| break; |
| } |
| SK_ABORT("Unexpected `rust_png::ColorType`: %d", static_cast<int>(colorType)); |
| } |
| |
| SkCodecAnimation::DisposalMethod ToDisposalMethod(rust_png::DisposeOp op) { |
| switch (op) { |
| case rust_png::DisposeOp::None: |
| return SkCodecAnimation::DisposalMethod::kKeep; |
| case rust_png::DisposeOp::Background: |
| return SkCodecAnimation::DisposalMethod::kRestoreBGColor; |
| case rust_png::DisposeOp::Previous: |
| return SkCodecAnimation::DisposalMethod::kRestorePrevious; |
| } |
| SK_ABORT("Unexpected `rust_png::DisposeOp`: %d", static_cast<int>(op)); |
| } |
| |
| SkCodecAnimation::Blend ToBlend(rust_png::BlendOp op) { |
| switch (op) { |
| case rust_png::BlendOp::Source: |
| return SkCodecAnimation::Blend::kSrc; |
| case rust_png::BlendOp::Over: |
| return SkCodecAnimation::Blend::kSrcOver; |
| } |
| SK_ABORT("Unexpected `rust_png::BlendOp`: %d", static_cast<int>(op)); |
| } |
| |
| std::unique_ptr<SkEncodedInfo::ICCProfile> CreateColorProfile(const rust_png::Reader& reader) { |
| // NOTE: This method is based on `read_color_profile` in |
| // `src/codec/SkPngCodec.cpp` but has been refactored to use Rust inputs |
| // instead of `libpng`. |
| |
| rust::Slice<const uint8_t> iccp; |
| if (reader.try_get_iccp(iccp)) { |
| skcms_ICCProfile profile; |
| skcms_Init(&profile); |
| if (skcms_Parse(iccp.data(), iccp.size(), &profile)) { |
| return SkEncodedInfo::ICCProfile::Make(profile); |
| } |
| } |
| |
| if (reader.is_srgb()) { |
| // TODO(https://crbug.com/362304558): Consider the intent field from the |
| // `sRGB` chunk. |
| return nullptr; |
| } |
| |
| // Default to SRGB gamut. |
| skcms_Matrix3x3 toXYZD50 = skcms_sRGB_profile()->toXYZD50; |
| |
| // Next, check for chromaticities. |
| float rx = 0.0; |
| float ry = 0.0; |
| float gx = 0.0; |
| float gy = 0.0; |
| float bx = 0.0; |
| float by = 0.0; |
| float wx = 0.0; |
| float wy = 0.0; |
| if (reader.try_get_chrm(wx, wy, rx, ry, gx, gy, bx, by)) { |
| skcms_Matrix3x3 tmp; |
| if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &tmp)) { |
| toXYZD50 = tmp; |
| } else { |
| // Note that Blink simply returns nullptr in this case. We'll fall |
| // back to srgb. |
| // |
| // TODO(https://crbug.com/362306048): If this implementation ends up |
| // replacing the one from Blink, then we should 1) double-check that |
| // we are comfortable with the difference and 2) remove this comment |
| // (since the Blink code that it refers to will get removed). |
| } |
| } |
| |
| skcms_TransferFunction fn; |
| float gamma; |
| if (reader.try_get_gama(gamma)) { |
| fn.a = 1.0f; |
| fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f; |
| fn.g = 1.0f / gamma; |
| } else { |
| // Default to sRGB gamma if the image has color space information, |
| // but does not specify gamma. |
| // Note that Blink would again return nullptr in this case. |
| fn = *skcms_sRGB_TransferFunction(); |
| } |
| |
| skcms_ICCProfile profile; |
| skcms_Init(&profile); |
| skcms_SetTransferFunction(&profile, &fn); |
| skcms_SetXYZD50(&profile, &toXYZD50); |
| return SkEncodedInfo::ICCProfile::Make(profile); |
| } |
| |
| SkEncodedInfo CreateEncodedInfo(const rust_png::Reader& reader) { |
| rust_png::ColorType rustColor = reader.output_color_type(); |
| SkEncodedInfo::Color skColor = ToColor(rustColor); |
| |
| std::unique_ptr<SkEncodedInfo::ICCProfile> profile = CreateColorProfile(reader); |
| if (!SkPngCodecBase::isCompatibleColorProfileAndType(profile.get(), skColor)) { |
| profile = nullptr; |
| } |
| |
| static_assert(sizeof(int) >= sizeof(int32_t), "Is it ok to use Sk64_pin_to_s32 below?"); |
| return SkEncodedInfo::Make(Sk64_pin_to_s32(reader.width()), |
| Sk64_pin_to_s32(reader.height()), |
| skColor, |
| ToAlpha(rustColor), |
| reader.output_bits_per_component(), |
| std::move(profile)); |
| } |
| |
| SkCodec::Result ToSkCodecResult(rust_png::DecodingResult rustResult) { |
| switch (rustResult) { |
| case rust_png::DecodingResult::Success: |
| return SkCodec::kSuccess; |
| case rust_png::DecodingResult::FormatError: |
| return SkCodec::kErrorInInput; |
| case rust_png::DecodingResult::ParameterError: |
| return SkCodec::kInvalidParameters; |
| case rust_png::DecodingResult::LimitsExceededError: |
| return SkCodec::kInternalError; |
| case rust_png::DecodingResult::IncompleteInput: |
| return SkCodec::kIncompleteInput; |
| } |
| SK_ABORT("Unexpected `rust_png::DecodingResult`: %d", static_cast<int>(rustResult)); |
| } |
| |
| // This helper class adapts `SkStream` to expose the API required by Rust FFI |
| // (i.e. the `ReadTrait` API). |
| class ReadTraitAdapterForSkStream final : public rust_png::ReadTrait { |
| public: |
| // SAFETY: The caller needs to guarantee that `stream` will be alive for |
| // as long as `ReadTraitAdapterForSkStream`. |
| explicit ReadTraitAdapterForSkStream(SkStream* stream) : fStream(stream) {} |
| |
| ~ReadTraitAdapterForSkStream() override = default; |
| |
| // Non-copyable and non-movable (we want a stable `this` pointer, because we |
| // will be passing a `ReadTrait*` pointer over the FFI boundary and |
| // retaining it inside `png::Reader`). |
| ReadTraitAdapterForSkStream(const ReadTraitAdapterForSkStream&) = delete; |
| ReadTraitAdapterForSkStream& operator=(const ReadTraitAdapterForSkStream&) = delete; |
| ReadTraitAdapterForSkStream(ReadTraitAdapterForSkStream&&) = delete; |
| ReadTraitAdapterForSkStream& operator=(ReadTraitAdapterForSkStream&&) = delete; |
| |
| // Implementation of the `std::io::Read::read` method. See `RustTrait`'s |
| // doc comments and |
| // https://doc.rust-lang.org/nightly/std/io/trait.Read.html#tymethod.read |
| // for guidance on the desired implementation and behavior of this method. |
| size_t read(rust::Slice<uint8_t> buffer) override { |
| // Avoiding operating on `buffer.data()` if the slice is empty helps to avoid |
| // UB risk described at https://davidben.net/2024/01/15/empty-slices.html. |
| if (buffer.empty()) { |
| return 0; |
| } |
| |
| return fStream->read(buffer.data(), buffer.size()); |
| } |
| |
| private: |
| SkStream* fStream = nullptr; // Non-owning pointer. |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<SkPngRustCodec> SkPngRustCodec::MakeFromStream(std::unique_ptr<SkStream> stream, |
| Result* result) { |
| SkASSERT(stream); |
| SkASSERT(result); |
| |
| auto readTraitAdapter = std::make_unique<ReadTraitAdapterForSkStream>(stream.get()); |
| rust::Box<rust_png::ResultOfReader> resultOfReader = |
| rust_png::new_reader(std::move(readTraitAdapter)); |
| *result = ToSkCodecResult(resultOfReader->err()); |
| if (*result != kSuccess) { |
| return nullptr; |
| } |
| rust::Box<rust_png::Reader> reader = resultOfReader->unwrap(); |
| |
| return std::make_unique<SkPngRustCodec>( |
| CreateEncodedInfo(*reader), std::move(stream), std::move(reader)); |
| } |
| |
| SkPngRustCodec::SkPngRustCodec(SkEncodedInfo&& encodedInfo, |
| std::unique_ptr<SkStream> stream, |
| rust::Box<rust_png::Reader> reader) |
| : SkPngCodecBase(std::move(encodedInfo), |
| // TODO(https://crbug.com/370522089): If/when `SkCodec` can |
| // avoid unnecessary rewinding, then stop "hiding" our stream |
| // from it. |
| /* stream = */ nullptr) |
| , fReader(std::move(reader)) |
| , fPrivStream(std::move(stream)) |
| , fFrameHolder(encodedInfo.width(), encodedInfo.height()) { |
| SkASSERT(fPrivStream); |
| |
| bool idatIsNotPartOfAnimation = fReader->has_actl_chunk() && !fReader->has_fctl_chunk(); |
| fFrameAtCurrentStreamPosition = idatIsNotPartOfAnimation ? -1 : 0; |
| if (!idatIsNotPartOfAnimation) { |
| // This `appendNewFrame` call should always succeed because: |
| // * `fFrameHolder.size()` is 0 at this point |
| // * Width and height are already capped when calling `SkEncodedInfo::Make` |
| // * `!fReader->has_fctl_chunk()` means that we don't need to worry |
| // about validating other frame metadata. |
| Result result = fFrameHolder.appendNewFrame(*fReader, this->getEncodedInfo()); |
| SkASSERT(result == kSuccess); |
| } |
| } |
| |
| SkPngRustCodec::~SkPngRustCodec() = default; |
| |
| SkCodec::Result SkPngRustCodec::readToStartOfNextFrame() { |
| SkASSERT(fFrameAtCurrentStreamPosition < this->getRawFrameCount()); |
| Result result = ToSkCodecResult(fReader->next_frame_info()); |
| if (result != kSuccess) { |
| return result; |
| } |
| |
| fFrameAtCurrentStreamPosition++; |
| if (fFrameAtCurrentStreamPosition == fFrameHolder.size()) { |
| result = fFrameHolder.appendNewFrame(*fReader, this->getEncodedInfo()); |
| } |
| |
| return result; |
| } |
| |
| SkCodec::Result SkPngRustCodec::seekToStartOfFrame(int index) { |
| // Callers of this `private` method should provide a valid `index`. |
| // |
| // `index == fFrameHolder.size()` means that we are seeking to the next |
| // frame (i.e. to the first frame for which an `fcTL` chunk wasn't parsed |
| // yet). |
| SkASSERT((0 <= index) && (index <= fFrameHolder.size())); |
| |
| // TODO(https://crbug.com/371060427): Improve runtime performance by seeking |
| // directly to the right offset in the stream, rather than calling `rewind` |
| // here and moving one-frame-at-a-time via `readToStartOfNextFrame` below. |
| if (index < fFrameAtCurrentStreamPosition) { |
| if (!fPrivStream->rewind()) { |
| return kCouldNotRewind; |
| } |
| |
| auto readTraitAdapter = std::make_unique<ReadTraitAdapterForSkStream>(fPrivStream.get()); |
| rust::Box<rust_png::ResultOfReader> resultOfReader = |
| rust_png::new_reader(std::move(readTraitAdapter)); |
| |
| // `SkPngRustCodec` constructor must have run before, and the |
| // constructor got a successfully created reader - we therefore also |
| // expect success here. |
| SkASSERT(kSuccess == ToSkCodecResult(resultOfReader->err())); |
| fReader = resultOfReader->unwrap(); |
| |
| bool idatIsNotPartOfAnimation = fReader->has_actl_chunk() && !fReader->has_fctl_chunk(); |
| fFrameAtCurrentStreamPosition = idatIsNotPartOfAnimation ? -1 : 0; |
| } |
| while (fFrameAtCurrentStreamPosition < index) { |
| Result result = this->readToStartOfNextFrame(); |
| if (result != kSuccess) { |
| return result; |
| } |
| } |
| |
| return kSuccess; |
| } |
| |
| int SkPngRustCodec::getRawFrameCount() const { |
| if (!fReader->has_actl_chunk()) { |
| return 1; |
| } |
| |
| static_assert(sizeof(int) >= sizeof(int32_t), "Is it ok to use Sk64_pin_to_s32 below?"); |
| uint32_t num_frames = fReader->get_actl_num_frames(); |
| return Sk64_pin_to_s32(num_frames); |
| } |
| |
| SkCodec::Result SkPngRustCodec::parseAdditionalFrameInfos() { |
| while (fFrameHolder.size() < this->getRawFrameCount()) { |
| int oldFrameCount = fFrameHolder.size(); |
| |
| Result result = this->seekToStartOfFrame(fFrameHolder.size()); |
| if (result != kSuccess) { |
| return result; |
| } |
| SkASSERT(fFrameHolder.size() == (oldFrameCount + 1)); |
| } |
| return kSuccess; |
| } |
| |
| SkCodec::Result SkPngRustCodec::startDecoding(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options& options, |
| DecodingState* decodingState) { |
| if (options.fFrameIndex < 0 || options.fFrameIndex >= fFrameHolder.size()) { |
| return kInvalidParameters; |
| } |
| const SkFrame* frame = fFrameHolder.getFrame(options.fFrameIndex); |
| SkASSERT(frame); |
| |
| Result result = this->seekToStartOfFrame(options.fFrameIndex); |
| if (result != kSuccess) { |
| return result; |
| } |
| |
| // TODO(https://crbug.com/362830091): Consider handling `fSubset`. |
| if (options.fSubset) { |
| return kUnimplemented; |
| } |
| |
| result = this->initializeXforms(dstInfo, options, frame->width()); |
| if (result != kSuccess) { |
| return result; |
| } |
| |
| { |
| SkSafeMath safe; |
| decodingState->fDstRowSize = rowBytes; |
| decodingState->fFrameIndex = safe.castTo<size_t>(options.fFrameIndex); |
| |
| decodingState->fBytesPerPixel = safe.castTo<uint8_t>(dstInfo.bytesPerPixel()); |
| if (decodingState->fBytesPerPixel >= 32u) { |
| return kInvalidParameters; |
| } |
| |
| size_t imageHeight = safe.castTo<size_t>(dstInfo.height()); |
| size_t imageSize = safe.mul(rowBytes, imageHeight); |
| |
| size_t xPixelOffset = safe.castTo<size_t>(frame->xOffset()); |
| size_t xByteOffset = safe.mul(decodingState->fBytesPerPixel, xPixelOffset); |
| |
| size_t yPixelOffset = safe.castTo<size_t>(frame->yOffset()); |
| size_t yByteOffset = safe.mul(rowBytes, yPixelOffset); |
| |
| if (!safe.ok()) { |
| return kErrorInInput; |
| } |
| decodingState->fDst = SkSpan(static_cast<uint8_t*>(pixels), imageSize) |
| .subspan(xByteOffset) |
| .subspan(yByteOffset); |
| } |
| |
| return kSuccess; |
| } |
| |
| SkCodec::Result SkPngRustCodec::incrementalDecode(DecodingState& decodingState, |
| int* rowsDecodedPtr) { |
| this->initializeXformParams(); |
| |
| int rowsDecoded = 0; |
| bool interlaced = fReader->interlaced(); |
| std::vector<uint8_t> decodedInterlacedFullWidthRow; |
| std::vector<uint8_t> xformedInterlacedRow; |
| while (true) { |
| // TODO(https://crbug.com/357876243): Avoid an unconditional buffer hop |
| // through buffer owned by `fReader` (e.g. when we can decode directly |
| // into `dst`, because the pixel format received from `fReader` is |
| // similar enough to `dstInfo`). |
| rust::Slice<const uint8_t> decodedRow; |
| |
| Result result = ToSkCodecResult(fReader->next_interlaced_row(decodedRow)); |
| if (result != kSuccess) { |
| if (result == kIncompleteInput && rowsDecodedPtr) { |
| *rowsDecodedPtr = rowsDecoded; |
| } |
| return result; |
| } |
| |
| if (decodedRow.empty()) { // This is how FFI layer says "no more rows". |
| fFrameHolder.markFrameAsFullyReceived(decodingState.fFrameIndex); |
| fIncrementalDecodingState.reset(); |
| return kSuccess; |
| } |
| |
| if (interlaced) { |
| // Copy (potentially shorter for initial Adam7 passes) `decodedRow` |
| // into a full-frame-width `decodedInterlacedFullWidthRow`. This is |
| // needed becxause `applyXformRow` requires full-width rows as input |
| // (can't change `SkSwizzler::fSrcWidth` after `initializeXforms`). |
| // |
| // TODO(https://crbug.com/357876243): Having `Reader.read_row` API (see |
| // https://github.com/image-rs/image-png/pull/493) would help avoid |
| // an extra copy here. |
| decodedInterlacedFullWidthRow.resize(this->getEncodedRowBytes(), 0x00); |
| SkASSERT(decodedInterlacedFullWidthRow.size() >= decodedRow.size()); |
| memcpy(decodedInterlacedFullWidthRow.data(), decodedRow.data(), decodedRow.size()); |
| |
| xformedInterlacedRow.resize(decodingState.fDstRowSize, 0x00); |
| this->applyXformRow(xformedInterlacedRow, decodedInterlacedFullWidthRow); |
| |
| SkASSERT(decodingState.fBytesPerPixel < 32u); // Checked in `startDecoding`. |
| fReader->expand_last_interlaced_row(rust::Slice<uint8_t>(decodingState.fDst), |
| decodingState.fDstRowSize, |
| rust::Slice<const uint8_t>(xformedInterlacedRow), |
| decodingState.fBytesPerPixel * 8u); |
| // `rowsDecoded` is not incremented, because full, contiguous rows |
| // are not decoded until pass 6 (or 7 depending on how you look) of |
| // Adam7 interlacing scheme. |
| } else { |
| this->applyXformRow(decodingState.fDst, decodedRow); |
| |
| decodingState.fDst = decodingState.fDst.subspan( |
| std::min(decodingState.fDstRowSize, decodingState.fDst.size())); |
| rowsDecoded++; |
| } |
| } |
| } |
| |
| SkCodec::Result SkPngRustCodec::onGetPixels(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options& options, |
| int* rowsDecoded) { |
| DecodingState decodingState; |
| Result result = this->startDecoding(dstInfo, pixels, rowBytes, options, &decodingState); |
| if (result != kSuccess) { |
| return result; |
| } |
| |
| return this->incrementalDecode(decodingState, rowsDecoded); |
| } |
| |
| SkCodec::Result SkPngRustCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options& options) { |
| DecodingState decodingState; |
| Result result = this->startDecoding(dstInfo, pixels, rowBytes, options, &decodingState); |
| if (result != kSuccess) { |
| return result; |
| } |
| |
| SkASSERT(!fIncrementalDecodingState.has_value()); |
| fIncrementalDecodingState = decodingState; |
| return kSuccess; |
| } |
| |
| SkCodec::Result SkPngRustCodec::onIncrementalDecode(int* rowsDecoded) { |
| SkASSERT(fIncrementalDecodingState.has_value()); |
| return this->incrementalDecode(*fIncrementalDecodingState, rowsDecoded); |
| } |
| |
| int SkPngRustCodec::onGetFrameCount() { |
| if (fCanParseAdditionalFrameInfos) { |
| switch (this->parseAdditionalFrameInfos()) { |
| case kIncompleteInput: |
| fCanParseAdditionalFrameInfos = true; |
| break; |
| case kSuccess: |
| SkASSERT(fFrameHolder.size() == this->getRawFrameCount()); |
| fCanParseAdditionalFrameInfos = false; |
| break; |
| default: |
| fCanParseAdditionalFrameInfos = false; |
| break; |
| } |
| } |
| |
| return fFrameHolder.size(); |
| } |
| |
| bool SkPngRustCodec::onGetFrameInfo(int index, FrameInfo* info) const { |
| return fFrameHolder.getFrameInfo(index, info); |
| } |
| |
| int SkPngRustCodec::onGetRepetitionCount() { |
| if (!fReader->has_actl_chunk()) { |
| return 0; |
| } |
| |
| uint32_t numFrames = fReader->get_actl_num_frames(); |
| if (numFrames <= 1) { |
| return 0; |
| } |
| |
| // APNG spec says that "`num_plays` indicates the number of times that this |
| // animation should play; if it is 0, the animation should play |
| // indefinitely." |
| SkSafeMath safe; |
| int numPlays = safe.castTo<int>(fReader->get_actl_num_plays()); |
| if ((numPlays == 0) || !safe.ok()) { |
| return kRepetitionCountInfinite; |
| } |
| |
| // Subtracting 1, because `SkCodec::onGetRepetitionCount` doc comment says |
| // that "This number does not include the first play through of each frame. |
| // For example, a repetition count of 4 means that each frame is played 5 |
| // times and then the animation stops." |
| return numPlays - 1; |
| } |
| |
| std::optional<SkSpan<const SkPngCodecBase::PaletteColorEntry>> SkPngRustCodec::onTryGetPlteChunk() { |
| if (fReader->output_color_type() != rust_png::ColorType::Indexed) { |
| return std::nullopt; |
| } |
| |
| // We shouldn't get here because we always use |
| // `png::Transformations::EXPAND`. |
| // |
| // TODO(https://crbug.com/356882657): Handle pLTE and tRNS inside |
| // `SkPngRustCodec` rather than via `png::Transformations::EXPAND`. |
| SkUNREACHABLE; |
| } |
| |
| std::optional<SkSpan<const uint8_t>> SkPngRustCodec::onTryGetTrnsChunk() { |
| if (fReader->output_color_type() != rust_png::ColorType::Indexed) { |
| return std::nullopt; |
| } |
| |
| // We shouldn't get here because we always use |
| // `png::Transformations::EXPAND`. |
| // |
| // TODO(https://crbug.com/356882657): Handle pLTE and tRNS inside |
| // `SkPngRustCodec` rather than via `png::Transformations::EXPAND`. |
| SkUNREACHABLE; |
| } |
| |
| class SkPngRustCodec::FrameHolder::PngFrame final : public SkFrame { |
| public: |
| PngFrame(int id, SkEncodedInfo::Alpha alpha) : SkFrame(id), fReportedAlpha(alpha) {} |
| |
| bool isFullyReceived() const { return fFullyReceived; } |
| void markAsFullyReceived() { fFullyReceived = true; } |
| |
| private: |
| SkEncodedInfo::Alpha onReportedAlpha() const override { return fReportedAlpha; } |
| |
| const SkEncodedInfo::Alpha fReportedAlpha; |
| bool fFullyReceived = false; |
| }; |
| |
| SkPngRustCodec::FrameHolder::FrameHolder(int width, int height) : SkFrameHolder() { |
| fScreenWidth = width; |
| fScreenHeight = height; |
| } |
| |
| const SkFrameHolder* SkPngRustCodec::getFrameHolder() const { return &fFrameHolder; } |
| |
| // We cannot use the SkCodec implementation since we pass nullptr to the superclass out of |
| // an abundance of caution w/r to rewinding the stream. |
| // |
| // TODO(https://crbug.com/370522089): See if `SkCodec` can be tweaked to avoid |
| // the need to hide the stream from it. |
| std::unique_ptr<SkStream> SkPngRustCodec::getEncodedData() const { |
| SkASSERT(fPrivStream); |
| return fPrivStream->duplicate(); |
| } |
| |
| SkPngRustCodec::FrameHolder::~FrameHolder() = default; |
| |
| const SkFrame* SkPngRustCodec::FrameHolder::onGetFrame(int unverifiedIndex) const { |
| SkSafeMath safe; |
| size_t index = safe.castTo<size_t>(unverifiedIndex); |
| if (safe.ok() && (index < fFrames.size())) { |
| return &fFrames[index]; |
| } |
| return nullptr; |
| } |
| |
| int SkPngRustCodec::FrameHolder::size() const { |
| // This invariant is maintained in `appendNewFrame`. |
| SkASSERT(SkTFitsIn<int>(fFrames.size())); |
| return static_cast<int>(fFrames.size()); |
| } |
| |
| void SkPngRustCodec::FrameHolder::markFrameAsFullyReceived(size_t index) { |
| SkASSERT(index < fFrames.size()); |
| fFrames[index].markAsFullyReceived(); |
| } |
| |
| bool SkPngRustCodec::FrameHolder::getFrameInfo(int index, FrameInfo* info) const { |
| const SkFrame* frame = this->getFrame(index); |
| if (frame && info) { |
| bool isFullyReceived = static_cast<const PngFrame*>(frame)->isFullyReceived(); |
| frame->fillIn(info, isFullyReceived); |
| } |
| return !!frame; |
| } |
| |
| SkCodec::Result SkPngRustCodec::FrameHolder::appendNewFrame(const rust_png::Reader& reader, |
| const SkEncodedInfo& info) { |
| // Ensure that `this->size()` fits into an `int`. `+ 1u` is used to account |
| // for `push_back` / `emplace_back` below. |
| if (!SkTFitsIn<int>(fFrames.size() + 1u)) { |
| return kErrorInInput; |
| } |
| int id = static_cast<int>(fFrames.size()); |
| |
| if (reader.has_fctl_chunk()) { |
| PngFrame frame(id, info.alpha()); |
| SkCodec::Result result = this->setFrameInfoFromCurrentFctlChunk(reader, &frame); |
| if (result == SkCodec::kSuccess) { |
| fFrames.push_back(std::move(frame)); |
| } |
| return result; |
| } |
| |
| SkASSERT(!reader.has_actl_chunk()); |
| SkASSERT(id == 0); |
| fFrames.emplace_back(id, info.alpha()); |
| SkFrame& frame = fFrames.back(); |
| frame.setXYWH(0, 0, info.width(), info.height()); |
| this->setAlphaAndRequiredFrame(&frame); |
| return kSuccess; |
| } |
| |
| SkCodec::Result SkPngRustCodec::FrameHolder::setFrameInfoFromCurrentFctlChunk( |
| const rust_png::Reader& reader, PngFrame* frame) { |
| SkASSERT(reader.has_fctl_chunk()); // Caller should guarantee this |
| SkASSERT(frame); |
| |
| uint32_t width = 0; |
| uint32_t height = 0; |
| uint32_t xOffset = 0; |
| uint32_t yOffset = 0; |
| auto disposeOp = rust_png::DisposeOp::None; |
| auto blendOp = rust_png::BlendOp::Source; |
| uint32_t durationMs = 0; |
| reader.get_fctl_info(width, height, xOffset, yOffset, disposeOp, blendOp, durationMs); |
| |
| { |
| SkSafeMath safe; |
| frame->setXYWH(safe.castTo<int>(xOffset), |
| safe.castTo<int>(yOffset), |
| safe.castTo<int>(width), |
| safe.castTo<int>(height)); |
| frame->setDuration(safe.castTo<int>(durationMs)); |
| if (!safe.ok()) { |
| return kErrorInInput; |
| } |
| } |
| |
| frame->setDisposalMethod(ToDisposalMethod(disposeOp)); |
| frame->setBlend(ToBlend(blendOp)); |
| this->setAlphaAndRequiredFrame(frame); |
| |
| return kSuccess; |
| } |