| /* |
| * Copyright 2024 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| #ifndef SkPngRustCodec_DEFINED |
| #define SkPngRustCodec_DEFINED |
| |
| #include <memory> |
| #include <variant> |
| #include <vector> |
| |
| #include "include/core/SkSpan.h" |
| #include "rust/png/FFI.rs.h" |
| #include "src/codec/SkCodecPriv.h" |
| #include "src/codec/SkFrameHolder.h" |
| #include "src/codec/SkPngCodecBase.h" |
| #include "third_party/rust/cxx/v1/cxx.h" |
| |
| class SkFrame; |
| class SkStream; |
| struct SkEncodedInfo; |
| |
| // This class provides the Skia image decoding API (`SkCodec`) on top of: |
| // * The third-party `png` crate (PNG decompression and decoding implemented in |
| // Rust) |
| // * Skia's `SkSwizzler` and `skcms_Transform` (pixel format and color space |
| // transformations implemented in C++). |
| class SkPngRustCodec final : public SkPngCodecBase { |
| public: |
| static std::unique_ptr<SkPngRustCodec> MakeFromStream(std::unique_ptr<SkStream>, |
| Result*, |
| SkPngChunkReader* = nullptr); |
| |
| // `public` to support `std::make_unique<SkPngRustCodec>(...)`. |
| SkPngRustCodec(SkEncodedInfo&&, |
| std::unique_ptr<SkStream>, |
| rust::Box<rust_png::Reader>, |
| sk_sp<SkPngCompositeChunkReader>, |
| std::unique_ptr<SkStream> gainmapStream, |
| std::optional<SkGainmapInfo> gainmapInfo); |
| |
| ~SkPngRustCodec() override; |
| |
| private: |
| // Holds raw destination parameters captured in `startDecoding` before |
| // sampler/swizzler options are finalized. |
| struct UninitializedDstInfo { |
| // Pointer to the start of the caller's destination pixel buffer. |
| uint8_t* fDstPtr; |
| |
| // Size of a row (in bytes) in the full image. Based on `rowBytes` |
| // passed to `onGetPixels` or `onStartIncrementalDecode`. |
| size_t fDstRowStride; |
| }; |
| |
| // Holds validated and calculated destination spans after `initializeDecodingDst` |
| // has run. |
| struct DecodingDstInfo { |
| // Size of a row (in bytes) in the full image. |
| size_t fDstRowStride; |
| |
| // `fDst` is based on `fDstPtr` from `UninitializedDstInfo`. For interlaced |
| // and non-interlaced images, `initializeDecodingDst` initializes `fDst` |
| // to start at the (0,0) (top-left) pixel of the current frame (which may |
| // be offset from `fDstPtr` if the current frame is a sub-rect of the full |
| // image). After decoding a non-interlaced row this moves (by |
| // `fDstRowStride`) to the next row. |
| SkSpan<uint8_t> fDst; |
| |
| // Size of a row (in bytes) in the current frame. |
| size_t fDstRowSize; |
| |
| // Height (in rows) of the current frame. |
| size_t fDstRowCount; |
| |
| // Bytes per pixel of fDst. |
| uint8_t fDstBytesPerPixel; |
| }; |
| |
| struct DecodingState { |
| // Transitions from `UninitializedDstInfo` (set in `startDecoding`) -> |
| // `DecodingDstInfo` (populated by `initializeDecodingDst` once sampling |
| // and swizzler parameters are finalized). |
| std::variant<UninitializedDstInfo, DecodingDstInfo> fDst; |
| |
| DecodingDstInfo& dst() { return std::get<DecodingDstInfo>(fDst); } |
| |
| // Intermediate buffer that holds color-transformed pixels that are |
| // ready to be blended with the destination. Used only when this frame |
| // uses `SkCodecAnimation::Blend::kSrcOver`. For interlaced images this |
| // buffer holds the whole frame; otherwise it holds only a single row. |
| // |
| // This is also used in the case of subsets or sampling for interlaced |
| // images. We use this buffer as a full sized encoded image, which we then |
| // take the subset / sampled rows from. |
| // TODO: Subsets of APNG not supported, but if we need to, we would need |
| // a separate fInterlacedBuffer along with fPreblendBuffer. |
| SkCodecPriv::BudgetedBuffer fPreblendBuffer; |
| |
| int fFirstRow = 0; |
| int fLastRow = 0; |
| |
| // Tracks the current row index of the source image being processed. |
| // Needs to be persisted in DecodingState to support incremental decoding |
| // across multiple calls without resetting progress. |
| int fCurrentSourceRow = 0; |
| // Tracks the number of rows actually written to the destination buffer. |
| // Reported via `rowsDecoded` when decoding fails. Must never exceed the |
| // number of fully written rows, because callers such as |
| // `SkCodec::getPixels` only fill the rows after it. |
| int fRowsWrittenToOutput = 0; |
| |
| // The y offset for a subset in the encoded color type, not the dst color type. |
| // Only used for interlaced images. |
| size_t fYByteOffset = 0; |
| }; |
| |
| // Helper for validating parameters of `onGetPixels` and/or |
| // `onStartIncrementalDecode`. If `kSuccess` is returned then |
| // `decodingState` output parameter got populated. |
| Result startDecoding(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options& options, |
| std::optional<DecodingState>& decodingState); |
| |
| // Helper for initializing the destination span (`fDst`), row size, and |
| // preblend buffer once sampling/subsetting parameters are finalized. |
| Result initializeDecodingDst(DecodingState& decodingState); |
| |
| // Helper for taking a decoded interlaced `srcRow`, applying color |
| // transformations, and then expanding it into the `frame`. |
| void expandDecodedInterlacedRow(SkSpan<uint8_t> dstFrame, |
| SkSpan<const uint8_t> srcRow, |
| const DecodingDstInfo& decodingDst, |
| bool xFormNeeded); |
| |
| // Helper for row-by-row decoding which is used from `onGetPixels` and/or |
| // `onIncrementalDecode`. |
| Result incrementalDecode(DecodingState& decodingState); |
| // The same as incrementalDecode but uses `applyXFormRow()`. Should only |
| // be used if this->canReadRows() is false. |
| Result incrementalDecodeXForm(DecodingState& decodingState); |
| |
| // Reports the number of decoded rows via `rowsDecoded` after any failed |
| // decode (not just `kIncompleteInput`), matching `SkPngCodec`, so that |
| // callers keep the rows decoded before a `kErrorInInput`. |
| void reportRowsDecoded(Result result, |
| const DecodingState& decodingState, |
| int* rowsDecoded) const; |
| |
| // Helper for reading until the start of the next `fdAT` sequence. |
| Result readToStartOfNextFrame(); |
| |
| // Helper for seeking to the start of image data for the given frame. |
| Result seekToStartOfFrame(int index); |
| |
| // The number of frames calculated based on 1) the presence, and 2) the |
| // contents of an `acTL` chunk. "raw" in the sense that it reports all the |
| // frames, while `SkCodec::getFrameCount` and |
| // `SkPngRustCodec::onGetFrameCount` only report frames for which we have |
| // successfully populated `fFrameHolder` with frame info parsed from `IHDR` |
| // and/or `fcTL` chunks. |
| int getRawFrameCount() const; |
| |
| // Attempts to read through the input stream to parse the additional `fcTL` |
| // chunks. |
| Result parseAdditionalFrameInfos(); |
| |
| // Determines whether or not we can read from the rust decoder directly into dst. |
| bool canReadRow(); |
| void processUnknownChunks(); |
| bool isLastFrame(); |
| bool isSampling() const; |
| // Helper to query whether the image supports sampling or subsetting |
| // (only static full-canvas images are supported; APNG animations and |
| // offset frames are unsupported). |
| bool supportsSamplingOrSubsetting(); |
| // Leaves the stream just past `IEND` so that callers can keep reading it (see `Codec_end`). |
| // Like libpng, partial decodes skip this and stop early. Errors are ignored: the scanlines and |
| // the `IDAT` CRC are verified already, and libpng also tolerates a damaged tail (it checks the |
| // `IEND` CRC as if it were ancillary). No-op unless the codec is built `for_android`. |
| void finishStreamAfterLastFrame(); |
| |
| // SkCodec overrides: |
| Result onGetPixels(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options&, |
| int* rowsDecoded) override; |
| bool onSupportsIncrementalDecode(const SkImageInfo&) override { return true; } |
| Result onStartIncrementalDecode(const SkImageInfo& dstInfo, |
| void* pixels, |
| size_t rowBytes, |
| const Options&) override; |
| Result onIncrementalDecode(int* rowsDecoded) override; |
| int onGetFrameCount() override; |
| bool onGetFrameInfo(int, FrameInfo*) const override; |
| int onGetRepetitionCount() override; |
| IsAnimated onIsAnimated() override; |
| const SkFrameHolder* getFrameHolder() const override; |
| sk_sp<const SkData> getEncodedData() const override; |
| SkSampler* getSampler(bool createIfNecessary) final; |
| |
| // SkPngCodecBase overrides: |
| std::unique_ptr<SkCodec> onDecodeGainmap(std::unique_ptr<SkStream> stream, |
| SkCodec::Result* result) override; |
| std::optional<SkSpan<const PaletteColorEntry>> onTryGetPlteChunk() override; |
| std::optional<SkSpan<const uint8_t>> onTryGetTrnsChunk() override; |
| |
| rust::Box<rust_png::Reader> fReader; |
| |
| // `-1` means that `IDAT` is not part of animation and wasn't skipped yet. |
| int fFrameAtCurrentStreamPosition = -1; |
| bool fStreamIsPositionedAtStartOfFrameData = false; |
| const std::unique_ptr<SkStream> fPrivStream; |
| // TODO(https://crbug.com/371060427): Once fast seeking is available, we can |
| // remove the field that tracks the stream length. |
| std::optional<size_t> fMaxStreamLengthSeenWhenParsingAdditionalFrameInfos; |
| |
| std::optional<DecodingState> fIncrementalDecodingState; |
| |
| class FrameHolder final : public SkFrameHolder { |
| public: |
| FrameHolder(int width, int height); |
| ~FrameHolder() override; |
| |
| FrameHolder(const FrameHolder&) = delete; |
| FrameHolder(FrameHolder&&) = delete; |
| FrameHolder& operator=(const FrameHolder&) = delete; |
| FrameHolder& operator=(FrameHolder&&) = delete; |
| |
| // Returning an `int` (rather than `size_t`) for easier interop with |
| // other parts of the SkCodec API. |
| int size() const; |
| |
| Result appendNewFrame(const rust_png::Reader& reader, const SkEncodedInfo& info); |
| void markFrameAsFullyReceived(size_t index); |
| bool getFrameInfo(int index, FrameInfo* info) const; |
| |
| private: |
| class PngFrame; |
| |
| const SkFrame* onGetFrame(int unverifiedIndex) const override; |
| Result setFrameInfoFromCurrentFctlChunk(const rust_png::Reader& reader, |
| PngFrame* out_frame); |
| |
| std::vector<PngFrame> fFrames; |
| }; |
| FrameHolder fFrameHolder; |
| |
| // Whether there may still be additional `fcTL` chunks to discover and parse. |
| // |
| // `true` if the stream hasn't been fully received (i.e. only |
| // `kIncompleteInput` errors so far, no hard errors) and `fFrameHolder` |
| // doesn't yet contain frame info for all `num_frames` declared in an `acTL` |
| // chunk. |
| bool fCanParseAdditionalFrameInfos = true; |
| |
| // When decoding interlaced subsets or sampling, decode the full image into a |
| // buffer in the encoded colortype and extract/sample for the final dst using |
| // this function. This function will use applyXFormRow() for needed rows. |
| void getSubsetOrSampleFromFullImage(SkSpan<const uint8_t> fullImageBuffer, |
| SkSpan<uint8_t> dst, |
| size_t dstRowStride, |
| size_t offset, |
| int srcHeight, |
| int dstHeight, |
| size_t dstRowSize); |
| }; |
| |
| #endif // SkPngRustCodec_DEFINED |