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