xref: /aosp_15_r20/external/skia/experimental/rust_png/decoder/impl/SkPngRustCodec.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2024 Google LLC.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "experimental/rust_png/decoder/impl/SkPngRustCodec.h"
9 
10 #include <limits>
11 #include <memory>
12 #include <utility>
13 
14 #include "experimental/rust_png/ffi/FFI.rs.h"
15 #include "experimental/rust_png/ffi/UtilsForFFI.h"
16 #include "include/core/SkColorSpace.h"
17 #include "include/core/SkStream.h"
18 #include "include/private/SkEncodedInfo.h"
19 #include "include/private/base/SkAssert.h"
20 #include "include/private/base/SkSafe32.h"
21 #include "include/private/base/SkTemplates.h"
22 #include "modules/skcms/skcms.h"
23 #include "src/base/SkAutoMalloc.h"
24 #include "src/base/SkSafeMath.h"
25 #include "src/codec/SkFrameHolder.h"
26 #include "src/codec/SkSwizzler.h"
27 #include "src/core/SkRasterPipeline.h"
28 #include "src/core/SkRasterPipelineOpList.h"
29 #include "third_party/rust/cxx/v1/cxx.h"
30 
31 #ifdef __clang__
32 #pragma clang diagnostic error "-Wconversion"
33 #endif
34 
35 namespace {
36 
ToColor(rust_png::ColorType colorType)37 SkEncodedInfo::Color ToColor(rust_png::ColorType colorType) {
38     // TODO(https://crbug.com/359279096): Take `sBIT` chunk into account to
39     // sometimes return `kXAlpha_Color` or `k565_Color`.  This may require
40     // a small PR to expose `sBIT` chunk from the `png` crate.
41 
42     switch (colorType) {
43         case rust_png::ColorType::Grayscale:
44             return SkEncodedInfo::kGray_Color;
45         case rust_png::ColorType::Rgb:
46             return SkEncodedInfo::kRGB_Color;
47         case rust_png::ColorType::GrayscaleAlpha:
48             return SkEncodedInfo::kGrayAlpha_Color;
49         case rust_png::ColorType::Rgba:
50             return SkEncodedInfo::kRGBA_Color;
51         case rust_png::ColorType::Indexed:
52             return SkEncodedInfo::kPalette_Color;
53     }
54     SK_ABORT("Unexpected `rust_png::ColorType`: %d", static_cast<int>(colorType));
55 }
56 
ToAlpha(rust_png::ColorType colorType,const rust_png::Reader & reader)57 SkEncodedInfo::Alpha ToAlpha(rust_png::ColorType colorType, const rust_png::Reader& reader) {
58     switch (colorType) {
59         case rust_png::ColorType::Grayscale:
60         case rust_png::ColorType::Rgb:
61             return SkEncodedInfo::kOpaque_Alpha;
62         case rust_png::ColorType::GrayscaleAlpha:
63         case rust_png::ColorType::Rgba:
64             return SkEncodedInfo::kUnpremul_Alpha;
65         case rust_png::ColorType::Indexed:
66             if (reader.has_trns_chunk()) {
67                 return SkEncodedInfo::kUnpremul_Alpha;
68             } else {
69                 return SkEncodedInfo::kOpaque_Alpha;
70             }
71     }
72     SK_ABORT("Unexpected `rust_png::ColorType`: %d", static_cast<int>(colorType));
73 }
74 
ToDisposalMethod(rust_png::DisposeOp op)75 SkCodecAnimation::DisposalMethod ToDisposalMethod(rust_png::DisposeOp op) {
76     switch (op) {
77         case rust_png::DisposeOp::None:
78             return SkCodecAnimation::DisposalMethod::kKeep;
79         case rust_png::DisposeOp::Background:
80             return SkCodecAnimation::DisposalMethod::kRestoreBGColor;
81         case rust_png::DisposeOp::Previous:
82             return SkCodecAnimation::DisposalMethod::kRestorePrevious;
83     }
84     SK_ABORT("Unexpected `rust_png::DisposeOp`: %d", static_cast<int>(op));
85 }
86 
ToBlend(rust_png::BlendOp op)87 SkCodecAnimation::Blend ToBlend(rust_png::BlendOp op) {
88     switch (op) {
89         case rust_png::BlendOp::Source:
90             return SkCodecAnimation::Blend::kSrc;
91         case rust_png::BlendOp::Over:
92             return SkCodecAnimation::Blend::kSrcOver;
93     }
94     SK_ABORT("Unexpected `rust_png::BlendOp`: %d", static_cast<int>(op));
95 }
96 
CreateColorProfile(const rust_png::Reader & reader)97 std::unique_ptr<SkEncodedInfo::ICCProfile> CreateColorProfile(const rust_png::Reader& reader) {
98     // NOTE: This method is based on `read_color_profile` in
99     // `src/codec/SkPngCodec.cpp` but has been refactored to use Rust inputs
100     // instead of `libpng`.
101 
102     // Considering the `cICP` chunk first, because the spec at
103     // https://www.w3.org/TR/png-3/#cICP-chunk says: "This chunk, if understood
104     // by the decoder, is the highest-precedence color chunk."
105     uint8_t cicpPrimariesId = 0;
106     uint8_t cicpTransferId = 0;
107     uint8_t cicpMatrixId = 0;
108     bool cicpIsFullRange = false;
109     if (reader.try_get_cicp_chunk(cicpPrimariesId, cicpTransferId, cicpMatrixId, cicpIsFullRange)) {
110         // https://www.w3.org/TR/png-3/#cICP-chunk says "RGB is currently the
111         // only supported color model in PNG, and as such Matrix Coefficients
112         // shall be set to 0."
113         //
114         // According to SkColorSpace::MakeCICP narrow range images are rare and
115         // therefore not supported.
116         if (cicpMatrixId == 0 && cicpIsFullRange) {
117             sk_sp<SkColorSpace> colorSpace =
118                     SkColorSpace::MakeCICP(static_cast<SkNamedPrimaries::CicpId>(cicpPrimariesId),
119                                            static_cast<SkNamedTransferFn::CicpId>(cicpTransferId));
120             if (colorSpace) {
121                 skcms_ICCProfile colorProfile;
122                 skcms_Init(&colorProfile);
123                 colorSpace->toProfile(&colorProfile);
124                 return SkEncodedInfo::ICCProfile::Make(colorProfile);
125             }
126         }
127     }
128 
129     if (reader.has_iccp_chunk()) {
130         // `SkData::MakeWithCopy` is resilient against 0-sized inputs, so
131         // no need to check `rust_slice.empty()` here.
132         rust::Slice<const uint8_t> rust_slice = reader.get_iccp_chunk();
133         sk_sp<SkData> owned_data = SkData::MakeWithCopy(rust_slice.data(), rust_slice.size());
134         std::unique_ptr<SkEncodedInfo::ICCProfile> parsed_data =
135                 SkEncodedInfo::ICCProfile::Make(std::move(owned_data));
136         if (parsed_data) {
137             return parsed_data;
138         }
139     }
140 
141     if (reader.is_srgb()) {
142         // TODO(https://crbug.com/362304558): Consider the intent field from the
143         // `sRGB` chunk.
144         return nullptr;
145     }
146 
147     // Default to SRGB gamut.
148     skcms_Matrix3x3 toXYZD50 = skcms_sRGB_profile()->toXYZD50;
149 
150     // Next, check for chromaticities.
151     float rx = 0.0;
152     float ry = 0.0;
153     float gx = 0.0;
154     float gy = 0.0;
155     float bx = 0.0;
156     float by = 0.0;
157     float wx = 0.0;
158     float wy = 0.0;
159     if (reader.try_get_chrm(wx, wy, rx, ry, gx, gy, bx, by)) {
160         skcms_Matrix3x3 tmp;
161         if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &tmp)) {
162             toXYZD50 = tmp;
163         } else {
164             // Note that Blink simply returns nullptr in this case. We'll fall
165             // back to srgb.
166             //
167             // TODO(https://crbug.com/362306048): If this implementation ends up
168             // replacing the one from Blink, then we should 1) double-check that
169             // we are comfortable with the difference and 2) remove this comment
170             // (since the Blink code that it refers to will get removed).
171         }
172     }
173 
174     skcms_TransferFunction fn;
175     float gamma;
176     if (reader.try_get_gama(gamma)) {
177         fn.a = 1.0f;
178         fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
179         fn.g = 1.0f / gamma;
180     } else {
181         // Default to sRGB gamma if the image has color space information,
182         // but does not specify gamma.
183         // Note that Blink would again return nullptr in this case.
184         fn = *skcms_sRGB_TransferFunction();
185     }
186 
187     skcms_ICCProfile profile;
188     skcms_Init(&profile);
189     skcms_SetTransferFunction(&profile, &fn);
190     skcms_SetXYZD50(&profile, &toXYZD50);
191     return SkEncodedInfo::ICCProfile::Make(profile);
192 }
193 
CreateEncodedInfo(const rust_png::Reader & reader)194 SkEncodedInfo CreateEncodedInfo(const rust_png::Reader& reader) {
195     rust_png::ColorType rustColor = reader.output_color_type();
196     SkEncodedInfo::Color skColor = ToColor(rustColor);
197 
198     std::unique_ptr<SkEncodedInfo::ICCProfile> profile = CreateColorProfile(reader);
199     if (!SkPngCodecBase::isCompatibleColorProfileAndType(profile.get(), skColor)) {
200         profile = nullptr;
201     }
202 
203     static_assert(sizeof(int) >= sizeof(int32_t), "Is it ok to use Sk64_pin_to_s32 below?");
204     return SkEncodedInfo::Make(Sk64_pin_to_s32(reader.width()),
205                                Sk64_pin_to_s32(reader.height()),
206                                skColor,
207                                ToAlpha(rustColor, reader),
208                                reader.output_bits_per_component(),
209                                std::move(profile));
210 }
211 
ToSkCodecResult(rust_png::DecodingResult rustResult)212 SkCodec::Result ToSkCodecResult(rust_png::DecodingResult rustResult) {
213     switch (rustResult) {
214         case rust_png::DecodingResult::Success:
215             return SkCodec::kSuccess;
216         case rust_png::DecodingResult::FormatError:
217             return SkCodec::kErrorInInput;
218         case rust_png::DecodingResult::ParameterError:
219             return SkCodec::kInvalidParameters;
220         case rust_png::DecodingResult::LimitsExceededError:
221             return SkCodec::kInternalError;
222         case rust_png::DecodingResult::IncompleteInput:
223             return SkCodec::kIncompleteInput;
224     }
225     SK_ABORT("Unexpected `rust_png::DecodingResult`: %d", static_cast<int>(rustResult));
226 }
227 
228 // This helper class adapts `SkStream` to expose the API required by Rust FFI
229 // (i.e. the `ReadTrait` API).
230 class ReadTraitAdapterForSkStream final : public rust_png::ReadTrait {
231 public:
232     // SAFETY: The caller needs to guarantee that `stream` will be alive for
233     // as long as `ReadTraitAdapterForSkStream`.
ReadTraitAdapterForSkStream(SkStream * stream)234     explicit ReadTraitAdapterForSkStream(SkStream* stream) : fStream(stream) { SkASSERT(fStream); }
235 
236     ~ReadTraitAdapterForSkStream() override = default;
237 
238     // Non-copyable and non-movable (we want a stable `this` pointer, because we
239     // will be passing a `ReadTrait*` pointer over the FFI boundary and
240     // retaining it inside `png::Reader`).
241     ReadTraitAdapterForSkStream(const ReadTraitAdapterForSkStream&) = delete;
242     ReadTraitAdapterForSkStream& operator=(const ReadTraitAdapterForSkStream&) = delete;
243     ReadTraitAdapterForSkStream(ReadTraitAdapterForSkStream&&) = delete;
244     ReadTraitAdapterForSkStream& operator=(ReadTraitAdapterForSkStream&&) = delete;
245 
246     // Implementation of the `std::io::Read::read` method.  See `RustTrait`'s
247     // doc comments and
248     // https://doc.rust-lang.org/nightly/std/io/trait.Read.html#tymethod.read
249     // for guidance on the desired implementation and behavior of this method.
read(rust::Slice<uint8_t> buffer)250     size_t read(rust::Slice<uint8_t> buffer) override {
251         SkSpan<uint8_t> span = ToSkSpan(buffer);
252         return fStream->read(span.data(), span.size());
253     }
254 
255 private:
256     SkStream* fStream = nullptr;  // Non-owning pointer.
257 };
258 
blendRow(SkSpan<uint8_t> dstRow,SkSpan<const uint8_t> srcRow,SkColorType color,SkAlphaType alpha)259 void blendRow(SkSpan<uint8_t> dstRow,
260               SkSpan<const uint8_t> srcRow,
261               SkColorType color,
262               SkAlphaType alpha) {
263     SkASSERT(dstRow.size() >= srcRow.size());
264     SkRasterPipeline_<256> p;
265 
266     SkRasterPipeline_MemoryCtx dstCtx = {dstRow.data(), 0};
267     p.appendLoadDst(color, &dstCtx);
268     if (kUnpremul_SkAlphaType == alpha) {
269         p.append(SkRasterPipelineOp::premul_dst);
270     }
271 
272     SkRasterPipeline_MemoryCtx srcCtx = {const_cast<void*>(static_cast<const void*>(srcRow.data())),
273                                          0};
274     p.appendLoad(color, &srcCtx);
275     if (kUnpremul_SkAlphaType == alpha) {
276         p.append(SkRasterPipelineOp::premul);
277     }
278 
279     p.append(SkRasterPipelineOp::srcover);
280 
281     if (kUnpremul_SkAlphaType == alpha) {
282         p.append(SkRasterPipelineOp::unpremul);
283     }
284     p.appendStore(color, &dstCtx);
285 
286     SkSafeMath safe;
287     size_t bpp = safe.castTo<size_t>(SkColorTypeBytesPerPixel(color));
288     SkASSERT(safe.ok());
289 
290     size_t width = srcRow.size() / bpp;
291     p.run(0, 0, width, 1);
292 }
293 
blendAllRows(SkSpan<uint8_t> dstFrame,SkSpan<const uint8_t> srcFrame,size_t rowSize,size_t rowStride,SkColorType color,SkAlphaType alpha)294 void blendAllRows(SkSpan<uint8_t> dstFrame,
295                   SkSpan<const uint8_t> srcFrame,
296                   size_t rowSize,
297                   size_t rowStride,
298                   SkColorType color,
299                   SkAlphaType alpha) {
300     while (srcFrame.size() >= rowSize) {
301         blendRow(dstFrame, srcFrame.first(rowSize), color, alpha);
302 
303         dstFrame = dstFrame.subspan(rowStride);
304         srcFrame = srcFrame.subspan(rowStride);
305     }
306 }
307 
308 }  // namespace
309 
310 // static
MakeFromStream(std::unique_ptr<SkStream> stream,Result * result)311 std::unique_ptr<SkPngRustCodec> SkPngRustCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
312                                                                Result* result) {
313     SkASSERT(stream);
314     SkASSERT(result);
315 
316     auto readTraitAdapter = std::make_unique<ReadTraitAdapterForSkStream>(stream.get());
317     rust::Box<rust_png::ResultOfReader> resultOfReader =
318             rust_png::new_reader(std::move(readTraitAdapter));
319     *result = ToSkCodecResult(resultOfReader->err());
320     if (*result != kSuccess) {
321         return nullptr;
322     }
323     rust::Box<rust_png::Reader> reader = resultOfReader->unwrap();
324 
325     return std::make_unique<SkPngRustCodec>(
326             CreateEncodedInfo(*reader), std::move(stream), std::move(reader));
327 }
328 
SkPngRustCodec(SkEncodedInfo && encodedInfo,std::unique_ptr<SkStream> stream,rust::Box<rust_png::Reader> reader)329 SkPngRustCodec::SkPngRustCodec(SkEncodedInfo&& encodedInfo,
330                                std::unique_ptr<SkStream> stream,
331                                rust::Box<rust_png::Reader> reader)
332         : SkPngCodecBase(std::move(encodedInfo),
333                          // TODO(https://crbug.com/370522089): If/when `SkCodec` can
334                          // avoid unnecessary rewinding, then stop "hiding" our stream
335                          // from it.
336                          /* stream = */ nullptr)
337         , fReader(std::move(reader))
338         , fPrivStream(std::move(stream))
339         , fFrameHolder(encodedInfo.width(), encodedInfo.height()) {
340     SkASSERT(fPrivStream);
341 
342     bool idatIsNotPartOfAnimation = fReader->has_actl_chunk() && !fReader->has_fctl_chunk();
343     fFrameAtCurrentStreamPosition = idatIsNotPartOfAnimation ? -1 : 0;
344     fStreamIsPositionedAtStartOfFrameData = true;
345     if (!idatIsNotPartOfAnimation) {
346         // This `appendNewFrame` call should always succeed because:
347         // * `fFrameHolder.size()` is 0 at this point
348         // * Width and height are already capped when calling `SkEncodedInfo::Make`
349         // * `!fReader->has_fctl_chunk()` means that we don't need to worry
350         //   about validating other frame metadata.
351         Result result = fFrameHolder.appendNewFrame(*fReader, this->getEncodedInfo());
352         SkASSERT(result == kSuccess);
353     }
354 }
355 
356 SkPngRustCodec::~SkPngRustCodec() = default;
357 
readToStartOfNextFrame()358 SkCodec::Result SkPngRustCodec::readToStartOfNextFrame() {
359     SkASSERT(fFrameAtCurrentStreamPosition < this->getRawFrameCount());
360     Result result = ToSkCodecResult(fReader->next_frame_info());
361     if (result != kSuccess) {
362         fStreamIsPositionedAtStartOfFrameData = false;
363         return result;
364     }
365 
366     fStreamIsPositionedAtStartOfFrameData = true;
367     fFrameAtCurrentStreamPosition++;
368     if (fFrameAtCurrentStreamPosition == fFrameHolder.size()) {
369         result = fFrameHolder.appendNewFrame(*fReader, this->getEncodedInfo());
370     }
371 
372     return result;
373 }
374 
seekToStartOfFrame(int index)375 SkCodec::Result SkPngRustCodec::seekToStartOfFrame(int index) {
376     // Callers of this `private` method should provide a valid `index`.
377     //
378     // `index == fFrameHolder.size()` means that we are seeking to the next
379     // frame (i.e. to the first frame for which an `fcTL` chunk wasn't parsed
380     // yet).
381     SkASSERT((0 <= index) && (index <= fFrameHolder.size()));
382 
383     // TODO(https://crbug.com/371060427): Improve runtime performance by seeking
384     // directly to the right offset in the stream, rather than calling `rewind`
385     // here and moving one-frame-at-a-time via `readToStartOfNextFrame` below.
386     if ((index < fFrameAtCurrentStreamPosition) ||
387         (index == fFrameAtCurrentStreamPosition && !fStreamIsPositionedAtStartOfFrameData)) {
388         if (!fPrivStream->rewind()) {
389             return kCouldNotRewind;
390         }
391 
392         auto readTraitAdapter = std::make_unique<ReadTraitAdapterForSkStream>(fPrivStream.get());
393         rust::Box<rust_png::ResultOfReader> resultOfReader =
394                 rust_png::new_reader(std::move(readTraitAdapter));
395 
396         // `SkPngRustCodec` constructor must have run before, and the
397         // constructor got a successfully created reader - we therefore also
398         // expect success here.
399         SkASSERT(kSuccess == ToSkCodecResult(resultOfReader->err()));
400         fReader = resultOfReader->unwrap();
401 
402         bool idatIsNotPartOfAnimation = fReader->has_actl_chunk() && !fReader->has_fctl_chunk();
403         fFrameAtCurrentStreamPosition = idatIsNotPartOfAnimation ? -1 : 0;
404         fStreamIsPositionedAtStartOfFrameData = true;
405     }
406     while (fFrameAtCurrentStreamPosition < index) {
407         Result result = this->readToStartOfNextFrame();
408         if (result != kSuccess) {
409             return result;
410         }
411     }
412 
413     return kSuccess;
414 }
415 
getRawFrameCount() const416 int SkPngRustCodec::getRawFrameCount() const {
417     if (!fReader->has_actl_chunk()) {
418         return 1;
419     }
420 
421     static_assert(sizeof(int) >= sizeof(int32_t), "Is it ok to use Sk64_pin_to_s32 below?");
422     uint32_t num_frames = fReader->get_actl_num_frames();
423     return Sk64_pin_to_s32(num_frames);
424 }
425 
parseAdditionalFrameInfos()426 SkCodec::Result SkPngRustCodec::parseAdditionalFrameInfos() {
427     while (fFrameHolder.size() < this->getRawFrameCount()) {
428         int oldFrameCount = fFrameHolder.size();
429 
430         Result result = this->seekToStartOfFrame(fFrameHolder.size());
431         if (result != kSuccess) {
432             return result;
433         }
434         SkASSERT(fFrameHolder.size() == (oldFrameCount + 1));
435     }
436     return kSuccess;
437 }
438 
startDecoding(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const Options & options,DecodingState * decodingState)439 SkCodec::Result SkPngRustCodec::startDecoding(const SkImageInfo& dstInfo,
440                                               void* pixels,
441                                               size_t rowBytes,
442                                               const Options& options,
443                                               DecodingState* decodingState) {
444     // TODO(https://crbug.com/362830091): Consider handling `fSubset`.
445     if (options.fSubset) {
446         return kUnimplemented;
447     }
448 
449     if (options.fFrameIndex < 0 || options.fFrameIndex >= fFrameHolder.size()) {
450         return kInvalidParameters;
451     }
452     const SkFrame* frame = fFrameHolder.getFrame(options.fFrameIndex);
453     SkASSERT(frame);
454 
455     // https://www.w3.org/TR/png-3/#11PLTE says that for color type 3
456     // (indexed-color), the PLTE chunk is required.  OTOH, `Codec_InvalidImages`
457     // expects that we will succeed in this case and produce *some* output.
458     if (this->getEncodedInfo().color() == SkEncodedInfo::kPalette_Color &&
459         !fReader->has_plte_chunk()) {
460         return kInvalidInput;
461     }
462 
463     Result result = this->seekToStartOfFrame(options.fFrameIndex);
464     if (result != kSuccess) {
465         return result;
466     }
467 
468     result = this->initializeXforms(dstInfo, options, frame->width());
469     if (result != kSuccess) {
470         return result;
471     }
472 
473     {
474         SkSafeMath safe;
475         decodingState->fDstRowStride = rowBytes;
476 
477         uint8_t dstBytesPerPixel = safe.castTo<uint8_t>(dstInfo.bytesPerPixel());
478         if (dstBytesPerPixel >= 32u) {
479             return kInvalidParameters;
480         }
481 
482         size_t imageHeight = safe.castTo<size_t>(dstInfo.height());
483         size_t imageSize = safe.mul(rowBytes, imageHeight);
484 
485         size_t xPixelOffset = safe.castTo<size_t>(frame->xOffset());
486         size_t xByteOffset = safe.mul(dstBytesPerPixel, xPixelOffset);
487 
488         size_t yPixelOffset = safe.castTo<size_t>(frame->yOffset());
489         size_t yByteOffset = safe.mul(rowBytes, yPixelOffset);
490 
491         size_t frameWidth = safe.castTo<size_t>(frame->width());
492         size_t rowSize = safe.mul(dstBytesPerPixel, frameWidth);
493         size_t frameHeight = safe.castTo<size_t>(frame->height());
494         size_t frameHeightTimesRowStride = safe.mul(frameHeight, rowBytes);
495         decodingState->fDstRowSize = rowSize;
496 
497         if (!safe.ok()) {
498             return kErrorInInput;
499         }
500 
501         decodingState->fDst = SkSpan(static_cast<uint8_t*>(pixels), imageSize)
502                                       .subspan(xByteOffset)
503                                       .subspan(yByteOffset);
504         if (frameHeightTimesRowStride < decodingState->fDst.size()) {
505             decodingState->fDst = decodingState->fDst.first(frameHeightTimesRowStride);
506         }
507 
508         if (frame->getBlend() == SkCodecAnimation::Blend::kSrcOver) {
509             if (fReader->interlaced()) {
510                 decodingState->fPreblendBuffer.resize(imageSize, 0x00);
511             } else {
512                 decodingState->fPreblendBuffer.resize(rowSize, 0x00);
513             }
514         }
515     }
516 
517     return kSuccess;
518 }
519 
expandDecodedInterlacedRow(SkSpan<uint8_t> dstFrame,SkSpan<const uint8_t> srcRow,const DecodingState & decodingState)520 void SkPngRustCodec::expandDecodedInterlacedRow(SkSpan<uint8_t> dstFrame,
521                                                 SkSpan<const uint8_t> srcRow,
522                                                 const DecodingState& decodingState) {
523     SkASSERT(fReader->interlaced());
524     std::vector<uint8_t> decodedInterlacedFullWidthRow;
525     std::vector<uint8_t> xformedInterlacedRow;
526 
527     // Copy (potentially shorter for initial Adam7 passes) `srcRow` into a
528     // full-frame-width `decodedInterlacedFullWidthRow`.  This is needed because
529     // `applyXformRow` requires full-width rows as input (can't change
530     // `SkSwizzler::fSrcWidth` after `initializeXforms`).
531     //
532     // TODO(https://crbug.com/357876243): Having `Reader.read_row` API (see
533     // https://github.com/image-rs/image-png/pull/493) would help avoid
534     // an extra copy here.
535     decodedInterlacedFullWidthRow.resize(this->getEncodedRowBytes(), 0x00);
536     SkASSERT(decodedInterlacedFullWidthRow.size() >= srcRow.size());
537     memcpy(decodedInterlacedFullWidthRow.data(), srcRow.data(), srcRow.size());
538 
539     xformedInterlacedRow.resize(decodingState.fDstRowSize, 0x00);
540     this->applyXformRow(xformedInterlacedRow, decodedInterlacedFullWidthRow);
541 
542     SkSafeMath safe;
543     uint8_t dstBytesPerPixel = safe.castTo<uint8_t>(this->dstInfo().bytesPerPixel());
544     SkASSERT(safe.ok());               // Checked in `startDecoding`.
545     SkASSERT(dstBytesPerPixel < 32u);  // Checked in `startDecoding`.
546     fReader->expand_last_interlaced_row(rust::Slice<uint8_t>(dstFrame),
547                                         decodingState.fDstRowStride,
548                                         rust::Slice<const uint8_t>(xformedInterlacedRow),
549                                         dstBytesPerPixel * 8u);
550 }
551 
incrementalDecode(DecodingState & decodingState,int * rowsDecodedPtr)552 SkCodec::Result SkPngRustCodec::incrementalDecode(DecodingState& decodingState,
553                                                   int* rowsDecodedPtr) {
554     this->initializeXformParams();
555 
556     int rowsDecoded = 0;
557     bool interlaced = fReader->interlaced();
558     while (true) {
559         // TODO(https://crbug.com/357876243): Avoid an unconditional buffer hop
560         // through buffer owned by `fReader` (e.g. when we can decode directly
561         // into `dst`, because the pixel format received from `fReader` is
562         // similar enough to `dstInfo`).
563         rust::Slice<const uint8_t> decodedRow;
564 
565         fStreamIsPositionedAtStartOfFrameData = false;
566         Result result = ToSkCodecResult(fReader->next_interlaced_row(decodedRow));
567         if (result != kSuccess) {
568             if (result == kIncompleteInput && rowsDecodedPtr) {
569                 *rowsDecodedPtr = rowsDecoded;
570             }
571             return result;
572         }
573 
574         if (decodedRow.empty()) {  // This is how FFI layer says "no more rows".
575             if (interlaced && !decodingState.fPreblendBuffer.empty()) {
576                 blendAllRows(decodingState.fDst,
577                              decodingState.fPreblendBuffer,
578                              decodingState.fDstRowSize,
579                              decodingState.fDstRowStride,
580                              this->dstInfo().colorType(),
581                              this->dstInfo().alphaType());
582             }
583             if (!interlaced) {
584                 // All of the original `fDst` should be filled out at this point.
585                 SkASSERT(decodingState.fDst.empty());
586             }
587 
588             // `static_cast` is ok, because `startDecoding` already validated `fFrameIndex`.
589             fFrameHolder.markFrameAsFullyReceived(static_cast<size_t>(this->options().fFrameIndex));
590             fIncrementalDecodingState.reset();
591             return kSuccess;
592         }
593 
594         if (interlaced) {
595             if (decodingState.fPreblendBuffer.empty()) {
596                 this->expandDecodedInterlacedRow(decodingState.fDst, decodedRow, decodingState);
597             } else {
598                 this->expandDecodedInterlacedRow(
599                         decodingState.fPreblendBuffer, decodedRow, decodingState);
600             }
601             // `rowsDecoded` is not incremented, because full, contiguous rows
602             // are not decoded until pass 6 (or 7 depending on how you look) of
603             // Adam7 interlacing scheme.
604         } else {
605             if (decodingState.fPreblendBuffer.empty()) {
606                 this->applyXformRow(decodingState.fDst, decodedRow);
607             } else {
608                 this->applyXformRow(decodingState.fPreblendBuffer, decodedRow);
609                 blendRow(decodingState.fDst,
610                          decodingState.fPreblendBuffer,
611                          this->dstInfo().colorType(),
612                          this->dstInfo().alphaType());
613             }
614 
615             decodingState.fDst = decodingState.fDst.subspan(
616                     std::min(decodingState.fDstRowStride, decodingState.fDst.size()));
617             rowsDecoded++;
618         }
619     }
620 }
621 
onGetPixels(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const Options & options,int * rowsDecoded)622 SkCodec::Result SkPngRustCodec::onGetPixels(const SkImageInfo& dstInfo,
623                                             void* pixels,
624                                             size_t rowBytes,
625                                             const Options& options,
626                                             int* rowsDecoded) {
627     DecodingState decodingState;
628     Result result = this->startDecoding(dstInfo, pixels, rowBytes, options, &decodingState);
629     if (result != kSuccess) {
630         return result;
631     }
632 
633     return this->incrementalDecode(decodingState, rowsDecoded);
634 }
635 
onStartIncrementalDecode(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const Options & options)636 SkCodec::Result SkPngRustCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
637                                                          void* pixels,
638                                                          size_t rowBytes,
639                                                          const Options& options) {
640     DecodingState decodingState;
641     Result result = this->startDecoding(dstInfo, pixels, rowBytes, options, &decodingState);
642     if (result != kSuccess) {
643         return result;
644     }
645 
646     SkASSERT(!fIncrementalDecodingState.has_value());
647     fIncrementalDecodingState = decodingState;
648     return kSuccess;
649 }
650 
onIncrementalDecode(int * rowsDecoded)651 SkCodec::Result SkPngRustCodec::onIncrementalDecode(int* rowsDecoded) {
652     SkASSERT(fIncrementalDecodingState.has_value());
653     return this->incrementalDecode(*fIncrementalDecodingState, rowsDecoded);
654 }
655 
onGetFrameCount()656 int SkPngRustCodec::onGetFrameCount() {
657     do {
658         if (!fCanParseAdditionalFrameInfos || fIncrementalDecodingState.has_value()) {
659             break;
660         }
661 
662         if (fPrivStream->hasLength()) {
663             size_t currentLength = fPrivStream->getLength();
664             if (fStreamLengthDuringLastCallToParseAdditionalFrameInfos.has_value()) {
665                 size_t oldLength = *fStreamLengthDuringLastCallToParseAdditionalFrameInfos;
666                 if (oldLength == currentLength) {
667                     // Don't retry `parseAdditionalFrameInfos` if the input
668                     // didn't change.
669                     break;
670                 }
671                 // We expect the input stream's length to be monotonically
672                 // increasing (even though the code may not yet rely on that
673                 // expectation).
674                 SkASSERT(currentLength > oldLength);
675             }
676             fStreamLengthDuringLastCallToParseAdditionalFrameInfos = currentLength;
677         }
678 
679         switch (this->parseAdditionalFrameInfos()) {
680             case kIncompleteInput:
681                 fCanParseAdditionalFrameInfos = true;
682                 break;
683             case kSuccess:
684                 SkASSERT(fFrameHolder.size() == this->getRawFrameCount());
685                 fCanParseAdditionalFrameInfos = false;
686                 break;
687             default:
688                 fCanParseAdditionalFrameInfos = false;
689                 break;
690         }
691     } while (false);
692 
693     return fFrameHolder.size();
694 }
695 
onGetFrameInfo(int index,FrameInfo * info) const696 bool SkPngRustCodec::onGetFrameInfo(int index, FrameInfo* info) const {
697     return fFrameHolder.getFrameInfo(index, info);
698 }
699 
onGetRepetitionCount()700 int SkPngRustCodec::onGetRepetitionCount() {
701     if (!fReader->has_actl_chunk()) {
702         return 0;
703     }
704 
705     uint32_t numFrames = fReader->get_actl_num_frames();
706     if (numFrames <= 1) {
707         return 0;
708     }
709 
710     // APNG spec says that "`num_plays` indicates the number of times that this
711     // animation should play; if it is 0, the animation should play
712     // indefinitely."
713     SkSafeMath safe;
714     int numPlays = safe.castTo<int>(fReader->get_actl_num_plays());
715     if ((numPlays == 0) || !safe.ok()) {
716         return kRepetitionCountInfinite;
717     }
718 
719     // Subtracting 1, because `SkCodec::onGetRepetitionCount` doc comment says
720     // that "This number does not include the first play through of each frame.
721     // For example, a repetition count of 4 means that each frame is played 5
722     // times and then the animation stops."
723     return numPlays - 1;
724 }
725 
onTryGetPlteChunk()726 std::optional<SkSpan<const SkPngCodecBase::PaletteColorEntry>> SkPngRustCodec::onTryGetPlteChunk() {
727     if (fReader->output_color_type() != rust_png::ColorType::Indexed) {
728         return std::nullopt;
729     }
730 
731     SkASSERT(fReader->has_plte_chunk());  // Checked in `startDecoding`.
732     SkSpan<const uint8_t> bytes = ToSkSpan(fReader->get_plte_chunk());
733 
734     // Make sure that `bytes.size()` is a multiple of
735     // `sizeof(PaletteColorEntry)`.
736     constexpr size_t kEntrySize = sizeof(PaletteColorEntry);
737     bytes = bytes.first((bytes.size() / kEntrySize) * kEntrySize);
738 
739     // Alignment of `PaletteColorEntry` is 1, because its size is 3, and size
740     // has to be a multiple of alignment (every element of an array has to be
741     // aligned) + alignment is always a power of 2.  And this means that
742     // `bytes.data()` is already aligned.
743     static_assert(kEntrySize == 3, "");
744     static_assert(std::alignment_of<PaletteColorEntry>::value == 1, "");
745     static_assert(std::alignment_of<uint8_t>::value == 1, "");
746     SkSpan<const PaletteColorEntry> palette = SkSpan(
747             reinterpret_cast<const PaletteColorEntry*>(bytes.data()), bytes.size() / kEntrySize);
748 
749     return palette;
750 }
751 
onTryGetTrnsChunk()752 std::optional<SkSpan<const uint8_t>> SkPngRustCodec::onTryGetTrnsChunk() {
753     if (fReader->output_color_type() != rust_png::ColorType::Indexed) {
754         return std::nullopt;
755     }
756 
757     if (!fReader->has_trns_chunk()) {
758         return std::nullopt;
759     }
760 
761     return ToSkSpan(fReader->get_trns_chunk());
762 }
763 
764 class SkPngRustCodec::FrameHolder::PngFrame final : public SkFrame {
765 public:
PngFrame(int id,SkEncodedInfo::Alpha alpha)766     PngFrame(int id, SkEncodedInfo::Alpha alpha) : SkFrame(id), fReportedAlpha(alpha) {}
767 
isFullyReceived() const768     bool isFullyReceived() const { return fFullyReceived; }
markAsFullyReceived()769     void markAsFullyReceived() { fFullyReceived = true; }
770 
771 private:
onReportedAlpha() const772     SkEncodedInfo::Alpha onReportedAlpha() const override { return fReportedAlpha; }
773 
774     const SkEncodedInfo::Alpha fReportedAlpha;
775     bool fFullyReceived = false;
776 };
777 
FrameHolder(int width,int height)778 SkPngRustCodec::FrameHolder::FrameHolder(int width, int height) : SkFrameHolder() {
779     fScreenWidth = width;
780     fScreenHeight = height;
781 }
782 
getFrameHolder() const783 const SkFrameHolder* SkPngRustCodec::getFrameHolder() const { return &fFrameHolder; }
784 
785 // We cannot use the SkCodec implementation since we pass nullptr to the superclass out of
786 // an abundance of caution w/r to rewinding the stream.
787 //
788 // TODO(https://crbug.com/370522089): See if `SkCodec` can be tweaked to avoid
789 // the need to hide the stream from it.
getEncodedData() const790 std::unique_ptr<SkStream> SkPngRustCodec::getEncodedData() const {
791     SkASSERT(fPrivStream);
792     return fPrivStream->duplicate();
793 }
794 
795 SkPngRustCodec::FrameHolder::~FrameHolder() = default;
796 
onGetFrame(int unverifiedIndex) const797 const SkFrame* SkPngRustCodec::FrameHolder::onGetFrame(int unverifiedIndex) const {
798     SkSafeMath safe;
799     size_t index = safe.castTo<size_t>(unverifiedIndex);
800     if (safe.ok() && (index < fFrames.size())) {
801         return &fFrames[index];
802     }
803     return nullptr;
804 }
805 
size() const806 int SkPngRustCodec::FrameHolder::size() const {
807     // This invariant is maintained in `appendNewFrame`.
808     SkASSERT(SkTFitsIn<int>(fFrames.size()));
809     return static_cast<int>(fFrames.size());
810 }
811 
markFrameAsFullyReceived(size_t index)812 void SkPngRustCodec::FrameHolder::markFrameAsFullyReceived(size_t index) {
813     SkASSERT(index < fFrames.size());
814     fFrames[index].markAsFullyReceived();
815 }
816 
getFrameInfo(int index,FrameInfo * info) const817 bool SkPngRustCodec::FrameHolder::getFrameInfo(int index, FrameInfo* info) const {
818     const SkFrame* frame = this->getFrame(index);
819     if (frame && info) {
820         bool isFullyReceived = static_cast<const PngFrame*>(frame)->isFullyReceived();
821         frame->fillIn(info, isFullyReceived);
822     }
823     return !!frame;
824 }
825 
appendNewFrame(const rust_png::Reader & reader,const SkEncodedInfo & info)826 SkCodec::Result SkPngRustCodec::FrameHolder::appendNewFrame(const rust_png::Reader& reader,
827                                                             const SkEncodedInfo& info) {
828     // Ensure that `this->size()` fits into an `int`.  `+ 1u` is used to account
829     // for `push_back` / `emplace_back` below.
830     if (!SkTFitsIn<int>(fFrames.size() + 1u)) {
831         return kErrorInInput;
832     }
833     int id = static_cast<int>(fFrames.size());
834 
835     if (reader.has_fctl_chunk()) {
836         if (!fFrames.empty()) {
837             // Having `fcTL` for a new frame means that the previous frame has been
838             // fully received (since all of the previous frame's `fdAT` / `IDAT`
839             // chunks must have come before the new frame's `fcTL` chunk).
840             fFrames.back().markAsFullyReceived();
841         }
842 
843         PngFrame frame(id, info.alpha());
844         SkCodec::Result result = this->setFrameInfoFromCurrentFctlChunk(reader, &frame);
845         if (result == SkCodec::kSuccess) {
846             fFrames.push_back(std::move(frame));
847         }
848         return result;
849     }
850 
851     SkASSERT(!reader.has_actl_chunk());
852     SkASSERT(id == 0);
853     fFrames.emplace_back(id, info.alpha());
854     SkFrame& frame = fFrames.back();
855     frame.setXYWH(0, 0, info.width(), info.height());
856     frame.setBlend(SkCodecAnimation::Blend::kSrc);
857     this->setAlphaAndRequiredFrame(&frame);
858     return kSuccess;
859 }
860 
setFrameInfoFromCurrentFctlChunk(const rust_png::Reader & reader,PngFrame * frame)861 SkCodec::Result SkPngRustCodec::FrameHolder::setFrameInfoFromCurrentFctlChunk(
862         const rust_png::Reader& reader, PngFrame* frame) {
863     SkASSERT(reader.has_fctl_chunk());  // Caller should guarantee this
864     SkASSERT(frame);
865 
866     uint32_t width = 0;
867     uint32_t height = 0;
868     uint32_t xOffset = 0;
869     uint32_t yOffset = 0;
870     auto disposeOp = rust_png::DisposeOp::None;
871     auto blendOp = rust_png::BlendOp::Source;
872     uint32_t durationMs = 0;
873     reader.get_fctl_info(width, height, xOffset, yOffset, disposeOp, blendOp, durationMs);
874 
875     {
876         SkSafeMath safe;
877         frame->setXYWH(safe.castTo<int>(xOffset),
878                        safe.castTo<int>(yOffset),
879                        safe.castTo<int>(width),
880                        safe.castTo<int>(height));
881         frame->setDuration(safe.castTo<int>(durationMs));
882         if (!safe.ok()) {
883             return kErrorInInput;
884         }
885     }
886 
887     frame->setDisposalMethod(ToDisposalMethod(disposeOp));
888 
889     // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
890     // points out that "for the first frame the two blend modes are functionally
891     // equivalent" so we use `BlendOp::Source` because it has better performance
892     // characteristics.
893     if (frame->frameId() == 0) {
894         blendOp = rust_png::BlendOp::Source;
895     }
896     frame->setBlend(ToBlend(blendOp));
897 
898     // Note: `setAlphaAndRequiredFrame` needs to be called last, because it
899     // depends on the other properties set above.
900     this->setAlphaAndRequiredFrame(frame);
901     return kSuccess;
902 }
903