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