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