1 // Copyright 2024 Google LLC
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! This crate provides C++ bindings for the `png` Rust crate.
6 //!
7 //! The public API of this crate is the C++ API declared by the `#[cxx::bridge]`
8 //! macro below and exposed through the auto-generated `FFI.rs.h` header.
9
10 use std::borrow::Cow;
11 use std::io::{BufReader, ErrorKind, Read, Seek, SeekFrom, Write};
12 use std::pin::Pin;
13
14 // No `use png::...` nor `use ffi::...` because we want the code to explicitly
15 // spell out if it means `ffi::ColorType` vs `png::ColorType` (or `Reader`
16 // vs `png::Reader`).
17
18 #[cxx::bridge(namespace = "rust_png")]
19 mod ffi {
20 /// FFI-friendly equivalent of `png::ColorType`.
21 enum ColorType {
22 Grayscale = 0,
23 Rgb = 2,
24 Indexed = 3,
25 GrayscaleAlpha = 4,
26 Rgba = 6,
27 }
28
29 /// FFI-friendly simplification of `Option<png::DecodingError>`.
30 enum DecodingResult {
31 Success,
32 FormatError,
33 ParameterError,
34 LimitsExceededError,
35 /// `IncompleteInput` is equivalent to `png::DecodingError::IoError(
36 /// std::io::ErrorKind::UnexpectedEof.into())`. It is named after
37 /// `SkCodec::Result::kIncompleteInput`.
38 IncompleteInput,
39 OtherIoError,
40 }
41
42 /// FFI-friendly equivalent of `png::DisposeOp`.
43 enum DisposeOp {
44 None,
45 Background,
46 Previous,
47 }
48
49 /// FFI-friendly equivalent of `png::BlendOp`.
50 enum BlendOp {
51 Source,
52 Over,
53 }
54
55 /// FFI-friendly simplification of `png::Compression`.
56 enum Compression {
57 Fastest,
58 Fast,
59 Balanced,
60 High,
61 }
62
63 /// FFI-friendly simplification of `Option<png::EncodingError>`.
64 enum EncodingResult {
65 Success,
66 IoError,
67 FormatError,
68 ParameterError,
69 LimitsExceededError,
70 }
71
72 unsafe extern "C++" {
73 include!("experimental/rust_png/ffi/FFI.h");
74
75 type ReadAndSeekTraits;
read(self: Pin<&mut ReadAndSeekTraits>, buffer: &mut [u8]) -> usize76 fn read(self: Pin<&mut ReadAndSeekTraits>, buffer: &mut [u8]) -> usize;
seek_from_start( self: Pin<&mut ReadAndSeekTraits>, requested_pos: u64, final_pos: &mut u64, ) -> bool77 fn seek_from_start(
78 self: Pin<&mut ReadAndSeekTraits>,
79 requested_pos: u64,
80 final_pos: &mut u64,
81 ) -> bool;
seek_from_end( self: Pin<&mut ReadAndSeekTraits>, requested_offset: i64, final_pos: &mut u64, ) -> bool82 fn seek_from_end(
83 self: Pin<&mut ReadAndSeekTraits>,
84 requested_offset: i64,
85 final_pos: &mut u64,
86 ) -> bool;
seek_relative( self: Pin<&mut ReadAndSeekTraits>, requested_offset: i64, final_pos: &mut u64, ) -> bool87 fn seek_relative(
88 self: Pin<&mut ReadAndSeekTraits>,
89 requested_offset: i64,
90 final_pos: &mut u64,
91 ) -> bool;
92
93 type WriteTrait;
write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool94 fn write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool;
flush(self: Pin<&mut WriteTrait>)95 fn flush(self: Pin<&mut WriteTrait>);
96 }
97
98 // Rust functions, types, and methods that are exposed through FFI.
99 //
100 // To avoid duplication, there are no doc comments inside the `extern "Rust"`
101 // section. The doc comments of these items can instead be found in the
102 // actual Rust code, outside of the `#[cxx::bridge]` manifest.
103 extern "Rust" {
new_reader(input: UniquePtr<ReadAndSeekTraits>) -> Box<ResultOfReader>104 fn new_reader(input: UniquePtr<ReadAndSeekTraits>) -> Box<ResultOfReader>;
105
106 type ResultOfReader;
err(self: &ResultOfReader) -> DecodingResult107 fn err(self: &ResultOfReader) -> DecodingResult;
unwrap(self: &mut ResultOfReader) -> Box<Reader>108 fn unwrap(self: &mut ResultOfReader) -> Box<Reader>;
109
110 type Reader;
height(self: &Reader) -> u32111 fn height(self: &Reader) -> u32;
width(self: &Reader) -> u32112 fn width(self: &Reader) -> u32;
interlaced(self: &Reader) -> bool113 fn interlaced(self: &Reader) -> bool;
is_srgb(self: &Reader) -> bool114 fn is_srgb(self: &Reader) -> bool;
try_get_chrm( self: &Reader, wx: &mut f32, wy: &mut f32, rx: &mut f32, ry: &mut f32, gx: &mut f32, gy: &mut f32, bx: &mut f32, by: &mut f32, ) -> bool115 fn try_get_chrm(
116 self: &Reader,
117 wx: &mut f32,
118 wy: &mut f32,
119 rx: &mut f32,
120 ry: &mut f32,
121 gx: &mut f32,
122 gy: &mut f32,
123 bx: &mut f32,
124 by: &mut f32,
125 ) -> bool;
try_get_cicp_chunk( self: &Reader, primaries_id: &mut u8, transfer_id: &mut u8, matrix_id: &mut u8, is_full_range: &mut bool, ) -> bool126 fn try_get_cicp_chunk(
127 self: &Reader,
128 primaries_id: &mut u8,
129 transfer_id: &mut u8,
130 matrix_id: &mut u8,
131 is_full_range: &mut bool,
132 ) -> bool;
try_get_gama(self: &Reader, gamma: &mut f32) -> bool133 fn try_get_gama(self: &Reader, gamma: &mut f32) -> bool;
has_exif_chunk(self: &Reader) -> bool134 fn has_exif_chunk(self: &Reader) -> bool;
get_exif_chunk(self: &Reader) -> &[u8]135 fn get_exif_chunk(self: &Reader) -> &[u8];
has_iccp_chunk(self: &Reader) -> bool136 fn has_iccp_chunk(self: &Reader) -> bool;
get_iccp_chunk(self: &Reader) -> &[u8]137 fn get_iccp_chunk(self: &Reader) -> &[u8];
has_trns_chunk(self: &Reader) -> bool138 fn has_trns_chunk(self: &Reader) -> bool;
get_trns_chunk(self: &Reader) -> &[u8]139 fn get_trns_chunk(self: &Reader) -> &[u8];
has_plte_chunk(self: &Reader) -> bool140 fn has_plte_chunk(self: &Reader) -> bool;
get_plte_chunk(self: &Reader) -> &[u8]141 fn get_plte_chunk(self: &Reader) -> &[u8];
has_actl_chunk(self: &Reader) -> bool142 fn has_actl_chunk(self: &Reader) -> bool;
get_actl_num_frames(self: &Reader) -> u32143 fn get_actl_num_frames(self: &Reader) -> u32;
get_actl_num_plays(self: &Reader) -> u32144 fn get_actl_num_plays(self: &Reader) -> u32;
has_fctl_chunk(self: &Reader) -> bool145 fn has_fctl_chunk(self: &Reader) -> bool;
get_fctl_info( self: &Reader, width: &mut u32, height: &mut u32, x_offset: &mut u32, y_offset: &mut u32, dispose_op: &mut DisposeOp, blend_op: &mut BlendOp, duration_ms: &mut u32, )146 fn get_fctl_info(
147 self: &Reader,
148 width: &mut u32,
149 height: &mut u32,
150 x_offset: &mut u32,
151 y_offset: &mut u32,
152 dispose_op: &mut DisposeOp,
153 blend_op: &mut BlendOp,
154 duration_ms: &mut u32,
155 );
output_buffer_size(self: &Reader) -> usize156 fn output_buffer_size(self: &Reader) -> usize;
output_color_type(self: &Reader) -> ColorType157 fn output_color_type(self: &Reader) -> ColorType;
output_bits_per_component(self: &Reader) -> u8158 fn output_bits_per_component(self: &Reader) -> u8;
next_frame_info(self: &mut Reader) -> DecodingResult159 fn next_frame_info(self: &mut Reader) -> DecodingResult;
next_interlaced_row<'a>( self: &'a mut Reader, row: &mut &'a [u8], ) -> DecodingResult160 unsafe fn next_interlaced_row<'a>(
161 self: &'a mut Reader,
162 row: &mut &'a [u8],
163 ) -> DecodingResult;
expand_last_interlaced_row( self: &Reader, img: &mut [u8], img_row_stride: usize, row: &[u8], bits_per_pixel: u8, )164 fn expand_last_interlaced_row(
165 self: &Reader,
166 img: &mut [u8],
167 img_row_stride: usize,
168 row: &[u8],
169 bits_per_pixel: u8,
170 );
171
new_writer( output: UniquePtr<WriteTrait>, width: u32, height: u32, color: ColorType, bits_per_component: u8, compression: Compression, icc_profile: &[u8], ) -> Box<ResultOfWriter>172 fn new_writer(
173 output: UniquePtr<WriteTrait>,
174 width: u32,
175 height: u32,
176 color: ColorType,
177 bits_per_component: u8,
178 compression: Compression,
179 icc_profile: &[u8],
180 ) -> Box<ResultOfWriter>;
181
182 type ResultOfWriter;
err(self: &ResultOfWriter) -> EncodingResult183 fn err(self: &ResultOfWriter) -> EncodingResult;
unwrap(self: &mut ResultOfWriter) -> Box<Writer>184 fn unwrap(self: &mut ResultOfWriter) -> Box<Writer>;
185
186 type Writer;
write_text_chunk(self: &mut Writer, keyword: &[u8], text: &[u8]) -> EncodingResult187 fn write_text_chunk(self: &mut Writer, keyword: &[u8], text: &[u8]) -> EncodingResult;
convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter>188 fn convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter>;
189
190 type ResultOfStreamWriter;
err(self: &ResultOfStreamWriter) -> EncodingResult191 fn err(self: &ResultOfStreamWriter) -> EncodingResult;
unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>192 fn unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>;
193
194 type StreamWriter;
write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult195 fn write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult;
finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult196 fn finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult;
197 }
198 }
199
200 impl From<png::ColorType> for ffi::ColorType {
from(value: png::ColorType) -> Self201 fn from(value: png::ColorType) -> Self {
202 match value {
203 png::ColorType::Grayscale => Self::Grayscale,
204 png::ColorType::Rgb => Self::Rgb,
205 png::ColorType::Indexed => Self::Indexed,
206 png::ColorType::GrayscaleAlpha => Self::GrayscaleAlpha,
207 png::ColorType::Rgba => Self::Rgba,
208 }
209 }
210 }
211
212 impl Into<png::ColorType> for ffi::ColorType {
into(self) -> png::ColorType213 fn into(self) -> png::ColorType {
214 match self {
215 Self::Grayscale => png::ColorType::Grayscale,
216 Self::Rgb => png::ColorType::Rgb,
217 Self::GrayscaleAlpha => png::ColorType::GrayscaleAlpha,
218 Self::Rgba => png::ColorType::Rgba,
219
220 // `SkPngRustEncoderImpl` only uses the color types above.
221 _ => unreachable!(),
222 }
223 }
224 }
225
226 impl From<png::DisposeOp> for ffi::DisposeOp {
from(value: png::DisposeOp) -> Self227 fn from(value: png::DisposeOp) -> Self {
228 match value {
229 png::DisposeOp::None => Self::None,
230 png::DisposeOp::Background => Self::Background,
231 png::DisposeOp::Previous => Self::Previous,
232 }
233 }
234 }
235
236 impl From<png::BlendOp> for ffi::BlendOp {
from(value: png::BlendOp) -> Self237 fn from(value: png::BlendOp) -> Self {
238 match value {
239 png::BlendOp::Source => Self::Source,
240 png::BlendOp::Over => Self::Over,
241 }
242 }
243 }
244
245 impl From<Option<&png::DecodingError>> for ffi::DecodingResult {
from(option: Option<&png::DecodingError>) -> Self246 fn from(option: Option<&png::DecodingError>) -> Self {
247 match option {
248 None => Self::Success,
249 Some(decoding_error) => match decoding_error {
250 png::DecodingError::IoError(e) => {
251 if e.kind() == ErrorKind::UnexpectedEof {
252 Self::IncompleteInput
253 } else {
254 Self::OtherIoError
255 }
256 }
257 png::DecodingError::Format(_) => Self::FormatError,
258 png::DecodingError::Parameter(_) => Self::ParameterError,
259 png::DecodingError::LimitsExceeded => Self::LimitsExceededError,
260 },
261 }
262 }
263 }
264
265 impl ffi::Compression {
apply<'a, W: Write>(&self, encoder: &mut png::Encoder<'a, W>)266 fn apply<'a, W: Write>(&self, encoder: &mut png::Encoder<'a, W>) {
267 match self {
268 &Self::Fastest => encoder.set_compression(png::Compression::Fastest),
269 &Self::Fast => encoder.set_compression(png::Compression::Fast),
270 &Self::Balanced => encoder.set_compression(png::Compression::Balanced),
271 &Self::High => encoder.set_compression(png::Compression::High),
272 _ => unreachable!(),
273 }
274 }
275 }
276
277 impl From<Option<&png::EncodingError>> for ffi::EncodingResult {
from(option: Option<&png::EncodingError>) -> Self278 fn from(option: Option<&png::EncodingError>) -> Self {
279 match option {
280 None => Self::Success,
281 Some(encoding_error) => match encoding_error {
282 png::EncodingError::IoError(_) => Self::IoError,
283 png::EncodingError::Format(_) => Self::FormatError,
284 png::EncodingError::Parameter(_) => Self::ParameterError,
285 png::EncodingError::LimitsExceeded => Self::LimitsExceededError,
286 },
287 }
288 }
289 }
290
291 impl<'a> Read for Pin<&'a mut ffi::ReadAndSeekTraits> {
read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>292 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
293 Ok(self.as_mut().read(buf))
294 }
295 }
296
297 impl<'a> Seek for Pin<&'a mut ffi::ReadAndSeekTraits> {
seek(&mut self, pos: SeekFrom) -> std::io::Result<u64>298 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
299 let mut new_pos = 0;
300 let success = match pos {
301 SeekFrom::Start(pos) => self.as_mut().seek_from_start(pos, &mut new_pos),
302 SeekFrom::End(offset) => self.as_mut().seek_from_end(offset, &mut new_pos),
303 SeekFrom::Current(offset) => self.as_mut().seek_relative(offset, &mut new_pos),
304 };
305 if success {
306 Ok(new_pos)
307 } else {
308 Err(ErrorKind::Other.into())
309 }
310 }
311 }
312
313 impl<'a> Write for Pin<&'a mut ffi::WriteTrait> {
write(&mut self, buf: &[u8]) -> std::io::Result<usize>314 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
315 if self.as_mut().write(buf) {
316 Ok(buf.len())
317 } else {
318 Err(ErrorKind::Other.into())
319 }
320 }
321
flush(&mut self) -> std::io::Result<()>322 fn flush(&mut self) -> std::io::Result<()> {
323 self.as_mut().flush();
324 Ok(())
325 }
326 }
327
328 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
329 /// generics, so we manually monomorphize here, but still expose a minimal,
330 /// somewhat tweaked API of the original type).
331 struct ResultOfReader(Result<Reader, png::DecodingError>);
332
333 impl ResultOfReader {
err(&self) -> ffi::DecodingResult334 fn err(&self) -> ffi::DecodingResult {
335 self.0.as_ref().err().into()
336 }
337
unwrap(&mut self) -> Box<Reader>338 fn unwrap(&mut self) -> Box<Reader> {
339 // Leaving `self` in a C++-friendly "moved-away" state.
340 let mut result = Err(png::DecodingError::LimitsExceeded);
341 std::mem::swap(&mut self.0, &mut result);
342
343 Box::new(result.unwrap())
344 }
345 }
346
compute_transformations(info: &png::Info) -> png::Transformations347 fn compute_transformations(info: &png::Info) -> png::Transformations {
348 // There are 2 scenarios where `EXPAND` transformation may be needed:
349 //
350 // * `SkSwizzler` can handle low-bit-depth `ColorType::Indexed`, but it may not
351 // support other inputs with low bit depth (e.g. `kGray_Color` with bpp=4). We
352 // use `EXPAND` to ask the `png` crate to expand such low-bpp images to at
353 // least 8 bits.
354 // * We may need to inject an alpha channel from the `tRNS` chunk if present.
355 // Note that we can't check `info.trns.is_some()` because at this point we
356 // have not yet read beyond the `IHDR` chunk.
357 //
358 // We avoid using `EXPAND` for `ColorType::Indexed` because this results in some
359 // performance gains - see https://crbug.com/356882657 for more details.
360 let mut result = match info.color_type {
361 // Work around bpp<8 limitations of `SkSwizzler`
362 png::ColorType::Rgba | png::ColorType::GrayscaleAlpha if (info.bit_depth as u8) < 8 => {
363 png::Transformations::EXPAND
364 }
365
366 // Handle `tRNS` expansion + work around bpp<8 limitations of `SkSwizzler`
367 png::ColorType::Rgb | png::ColorType::Grayscale => png::Transformations::EXPAND,
368
369 // Otherwise there is no need to `EXPAND`.
370 png::ColorType::Indexed | png::ColorType::Rgba | png::ColorType::GrayscaleAlpha => {
371 png::Transformations::IDENTITY
372 }
373 };
374
375 // We mimic how the `libpng`-based `SkPngCodec` handles G16 and GA16.
376 //
377 // TODO(https://crbug.com/359245096): Avoid stripping least signinficant 8 bits in G16 and
378 // GA16 images.
379 if info.bit_depth == png::BitDepth::Sixteen {
380 if matches!(info.color_type, png::ColorType::Grayscale | png::ColorType::GrayscaleAlpha) {
381 result = result | png::Transformations::STRIP_16;
382 }
383 }
384
385 result
386 }
387
388 /// FFI-friendly wrapper around `png::Reader<R>` (`cxx` can't handle arbitrary
389 /// generics, so we manually monomorphize here, but still expose a minimal,
390 /// somewhat tweaked API of the original type).
391 struct Reader {
392 reader: png::Reader<BufReader<cxx::UniquePtr<ffi::ReadAndSeekTraits>>>,
393 last_interlace_info: Option<png::InterlaceInfo>,
394 }
395
396 impl Reader {
new(input: cxx::UniquePtr<ffi::ReadAndSeekTraits>) -> Result<Self, png::DecodingError>397 fn new(input: cxx::UniquePtr<ffi::ReadAndSeekTraits>) -> Result<Self, png::DecodingError> {
398 // The magic value of `BUF_CAPACITY` is based on `CHUNK_BUFFER_SIZE` which was
399 // used in `BufReader::with_capacity` calls by `png` crate up to version
400 // 0.17.16 - see: https://github.com/image-rs/image-png/pull/558/files#diff-c28833b65510e37441203b4256b74068f191d29ea34b6e753442e644d3a316b8L28
401 // and
402 // https://github.com/image-rs/image-png/blob/eb9b5d7f371b88f15aaca6a8d21c58b86c400d76/src/decoder/stream.rs#L21
403 const BUF_CAPACITY: usize = 32 * 1024;
404 // TODO(https://crbug.com/399894620): Consider instead implementing `BufRead` on top of
405 // `SkStream` API when/if possible in the future.
406 let input = BufReader::with_capacity(BUF_CAPACITY, input);
407
408 // By default, the decoder is limited to using 64 Mib. If we ever need to change
409 // that, we can use `png::Decoder::new_with_limits`.
410 let mut decoder = png::Decoder::new(input);
411
412 let info = decoder.read_header_info()?;
413 let transformations = compute_transformations(info);
414 decoder.set_transformations(transformations);
415
416 Ok(Self { reader: decoder.read_info()?, last_interlace_info: None })
417 }
418
height(&self) -> u32419 fn height(&self) -> u32 {
420 self.reader.info().height
421 }
422
width(&self) -> u32423 fn width(&self) -> u32 {
424 self.reader.info().width
425 }
426
427 /// Returns whether the PNG image is interlaced.
interlaced(&self) -> bool428 fn interlaced(&self) -> bool {
429 self.reader.info().interlaced
430 }
431
432 /// Returns whether the decoded PNG image contained a `sRGB` chunk.
is_srgb(&self) -> bool433 fn is_srgb(&self) -> bool {
434 self.reader.info().srgb.is_some()
435 }
436
437 /// If the decoded PNG image contained a `cHRM` chunk then `try_get_chrm`
438 /// returns `true` and populates the out parameters (`wx`, `wy`, `rx`,
439 /// etc.). Otherwise, returns `false`.
try_get_chrm( &self, wx: &mut f32, wy: &mut f32, rx: &mut f32, ry: &mut f32, gx: &mut f32, gy: &mut f32, bx: &mut f32, by: &mut f32, ) -> bool440 fn try_get_chrm(
441 &self,
442 wx: &mut f32,
443 wy: &mut f32,
444 rx: &mut f32,
445 ry: &mut f32,
446 gx: &mut f32,
447 gy: &mut f32,
448 bx: &mut f32,
449 by: &mut f32,
450 ) -> bool {
451 fn copy_channel(channel: &(png::ScaledFloat, png::ScaledFloat), x: &mut f32, y: &mut f32) {
452 *x = png_u32_into_f32(channel.0);
453 *y = png_u32_into_f32(channel.1);
454 }
455
456 match self.reader.info().chrm_chunk.as_ref() {
457 None => false,
458 Some(chrm) => {
459 copy_channel(&chrm.white, wx, wy);
460 copy_channel(&chrm.red, rx, ry);
461 copy_channel(&chrm.green, gx, gy);
462 copy_channel(&chrm.blue, bx, by);
463 true
464 }
465 }
466 }
467
468 /// If the decoded PNG image contained a `cICP` chunk then
469 /// `try_get_cicp_chunk` returns `true` and populates the out
470 /// parameters. Otherwise, returns `false`.
try_get_cicp_chunk( &self, primaries_id: &mut u8, transfer_id: &mut u8, matrix_id: &mut u8, is_full_range: &mut bool, ) -> bool471 fn try_get_cicp_chunk(
472 &self,
473 primaries_id: &mut u8,
474 transfer_id: &mut u8,
475 matrix_id: &mut u8,
476 is_full_range: &mut bool,
477 ) -> bool {
478 match self.reader.info().coding_independent_code_points.as_ref() {
479 None => false,
480 Some(cicp) => {
481 *primaries_id = cicp.color_primaries;
482 *transfer_id = cicp.transfer_function;
483 *matrix_id = cicp.matrix_coefficients;
484 *is_full_range = cicp.is_video_full_range_image;
485 true
486 }
487 }
488 }
489
490 /// If the decoded PNG image contained a `gAMA` chunk then `try_get_gama`
491 /// returns `true` and populates the `gamma` out parameter. Otherwise,
492 /// returns `false`.
try_get_gama(&self, gamma: &mut f32) -> bool493 fn try_get_gama(&self, gamma: &mut f32) -> bool {
494 match self.reader.info().gama_chunk.as_ref() {
495 None => false,
496 Some(&scaled_float) => {
497 *gamma = png_u32_into_f32(scaled_float);
498 true
499 }
500 }
501 }
502
503 /// Returns whether the `eXIf` chunk exists.
has_exif_chunk(&self) -> bool504 fn has_exif_chunk(&self) -> bool {
505 self.reader.info().exif_metadata.is_some()
506 }
507
508 /// Returns contents of the `eXIf` chunk. Panics if there is no `eXIf`
509 /// chunk.
get_exif_chunk(&self) -> &[u8]510 fn get_exif_chunk(&self) -> &[u8] {
511 self.reader.info().exif_metadata.as_ref().unwrap().as_ref()
512 }
513
514 /// Returns whether the `iCCP` chunk exists.
has_iccp_chunk(&self) -> bool515 fn has_iccp_chunk(&self) -> bool {
516 self.reader.info().icc_profile.is_some()
517 }
518
519 /// Returns contents of the `iCCP` chunk. Panics if there is no `iCCP`
520 /// chunk.
get_iccp_chunk(&self) -> &[u8]521 fn get_iccp_chunk(&self) -> &[u8] {
522 self.reader.info().icc_profile.as_ref().unwrap().as_ref()
523 }
524
525 /// Returns whether the `tRNS` chunk exists.
has_trns_chunk(&self) -> bool526 fn has_trns_chunk(&self) -> bool {
527 self.reader.info().trns.is_some()
528 }
529
530 /// Returns contents of the `tRNS` chunk. Panics if there is no `tRNS`
531 /// chunk.
get_trns_chunk(&self) -> &[u8]532 fn get_trns_chunk(&self) -> &[u8] {
533 self.reader.info().trns.as_ref().unwrap().as_ref()
534 }
535
536 /// Returns whether the `PLTE` chunk exists.
has_plte_chunk(&self) -> bool537 fn has_plte_chunk(&self) -> bool {
538 self.reader.info().palette.is_some()
539 }
540
541 /// Returns contents of the `PLTE` chunk. Panics if there is no `PLTE`
542 /// chunk.
get_plte_chunk(&self) -> &[u8]543 fn get_plte_chunk(&self) -> &[u8] {
544 self.reader.info().palette.as_ref().unwrap().as_ref()
545 }
546
547 /// Returns whether the `acTL` chunk exists.
has_actl_chunk(&self) -> bool548 fn has_actl_chunk(&self) -> bool {
549 self.reader.info().animation_control.is_some()
550 }
551
552 /// Returns `num_frames` from the `acTL` chunk. Panics if there is no
553 /// `acTL` chunk.
554 ///
555 /// The returned value is equal the number of `fcTL` chunks. (Note that it
556 /// doesn't count `IDAT` nor `fdAT` chunks. In particular, if an `fcTL`
557 /// chunk doesn't appear before an `IDAT` chunk then `IDAT` is not part
558 /// of the animation.)
559 ///
560 /// See also
561 /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_frames(&self) -> u32562 fn get_actl_num_frames(&self) -> u32 {
563 self.reader.info().animation_control.as_ref().unwrap().num_frames
564 }
565
566 /// Returns `num_plays` from the `acTL` chunk. Panics if there is no `acTL`
567 /// chunk.
568 ///
569 /// `0` indicates that the animation should play indefinitely. See
570 /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_plays(&self) -> u32571 fn get_actl_num_plays(&self) -> u32 {
572 self.reader.info().animation_control.as_ref().unwrap().num_plays
573 }
574
575 /// Returns whether a `fcTL` chunk has been parsed (and can be read using
576 /// `get_fctl_info`).
has_fctl_chunk(&self) -> bool577 fn has_fctl_chunk(&self) -> bool {
578 self.reader.info().frame_control.is_some()
579 }
580
581 /// Returns `png::FrameControl` information.
582 ///
583 /// Panics if no `fcTL` chunk hasn't been parsed yet.
get_fctl_info( &self, width: &mut u32, height: &mut u32, x_offset: &mut u32, y_offset: &mut u32, dispose_op: &mut ffi::DisposeOp, blend_op: &mut ffi::BlendOp, duration_ms: &mut u32, )584 fn get_fctl_info(
585 &self,
586 width: &mut u32,
587 height: &mut u32,
588 x_offset: &mut u32,
589 y_offset: &mut u32,
590 dispose_op: &mut ffi::DisposeOp,
591 blend_op: &mut ffi::BlendOp,
592 duration_ms: &mut u32,
593 ) {
594 let frame_control = self.reader.info().frame_control.as_ref().unwrap();
595 *width = frame_control.width;
596 *height = frame_control.height;
597 *x_offset = frame_control.x_offset;
598 *y_offset = frame_control.y_offset;
599 *dispose_op = frame_control.dispose_op.into();
600 *blend_op = frame_control.blend_op.into();
601
602 // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
603 // says:
604 //
605 // > "The `delay_num` and `delay_den` parameters together specify a fraction
606 // > indicating the time to display the current frame, in seconds. If the
607 // > denominator is 0, it is to be treated as if it were 100 (that is,
608 // > `delay_num` then specifies 1/100ths of a second).
609 *duration_ms = if frame_control.delay_den == 0 {
610 10 * frame_control.delay_num as u32
611 } else {
612 1000 * frame_control.delay_num as u32 / frame_control.delay_den as u32
613 };
614 }
615
output_buffer_size(&self) -> usize616 fn output_buffer_size(&self) -> usize {
617 self.reader.output_buffer_size()
618 }
619
output_color_type(&self) -> ffi::ColorType620 fn output_color_type(&self) -> ffi::ColorType {
621 self.reader.output_color_type().0.into()
622 }
623
output_bits_per_component(&self) -> u8624 fn output_bits_per_component(&self) -> u8 {
625 self.reader.output_color_type().1 as u8
626 }
627
next_frame_info(&mut self) -> ffi::DecodingResult628 fn next_frame_info(&mut self) -> ffi::DecodingResult {
629 self.reader.next_frame_info().as_ref().err().into()
630 }
631
632 /// Decodes the next row - see
633 /// https://docs.rs/png/latest/png/struct.Reader.html#method.next_interlaced_row
634 ///
635 /// TODO(https://crbug.com/399891492): Consider using `read_row` to avoid an extra copy.
next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult636 fn next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult {
637 let result = self.reader.next_interlaced_row();
638 if let Ok(maybe_row) = result.as_ref() {
639 self.last_interlace_info = maybe_row.as_ref().map(|r| r.interlace()).copied();
640 *row = maybe_row.map(|r| r.data()).unwrap_or(&[]);
641 }
642 result.as_ref().err().into()
643 }
644
645 /// Expands the last decoded interlaced row - see
646 /// https://docs.rs/png/latest/png/fn.expand_interlaced_row
expand_last_interlaced_row( &self, img: &mut [u8], img_row_stride: usize, row: &[u8], bits_per_pixel: u8, )647 fn expand_last_interlaced_row(
648 &self,
649 img: &mut [u8],
650 img_row_stride: usize,
651 row: &[u8],
652 bits_per_pixel: u8,
653 ) {
654 let Some(png::InterlaceInfo::Adam7(ref adam7info)) = self.last_interlace_info.as_ref()
655 else {
656 panic!("This function should only be called after decoding an interlaced row");
657 };
658 png::expand_interlaced_row(img, img_row_stride, row, adam7info, bits_per_pixel);
659 }
660 }
661
png_u32_into_f32(v: png::ScaledFloat) -> f32662 fn png_u32_into_f32(v: png::ScaledFloat) -> f32 {
663 // This uses `0.00001_f32 * (v.into_scaled() as f32)` instead of just
664 // `v.into_value()` for compatibility with the legacy implementation
665 // of `ReadColorProfile` in
666 // `.../blink/renderer/platform/image-decoders/png/png_image_decoder.cc`.
667 0.00001_f32 * (v.into_scaled() as f32)
668 }
669
670 /// This provides a public C++ API for decoding a PNG image.
new_reader(input: cxx::UniquePtr<ffi::ReadAndSeekTraits>) -> Box<ResultOfReader>671 fn new_reader(input: cxx::UniquePtr<ffi::ReadAndSeekTraits>) -> Box<ResultOfReader> {
672 Box::new(ResultOfReader(Reader::new(input)))
673 }
674
675 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
676 /// generics, so we manually monomorphize here, but still expose a minimal,
677 /// somewhat tweaked API of the original type).
678 struct ResultOfWriter(Result<Writer, png::EncodingError>);
679
680 impl ResultOfWriter {
err(&self) -> ffi::EncodingResult681 fn err(&self) -> ffi::EncodingResult {
682 self.0.as_ref().err().into()
683 }
684
unwrap(&mut self) -> Box<Writer>685 fn unwrap(&mut self) -> Box<Writer> {
686 // Leaving `self` in a C++-friendly "moved-away" state.
687 let mut result = Err(png::EncodingError::LimitsExceeded);
688 std::mem::swap(&mut self.0, &mut result);
689
690 Box::new(result.unwrap())
691 }
692 }
693
694 /// FFI-friendly wrapper around `png::Writer` (`cxx` can't handle
695 /// arbitrary generics, so we manually monomorphize here, but still expose a
696 /// minimal, somewhat tweaked API of the original type).
697 struct Writer(png::Writer<cxx::UniquePtr<ffi::WriteTrait>>);
698
699 impl Writer {
new( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, compression: ffi::Compression, icc_profile: &[u8], ) -> Result<Self, png::EncodingError>700 fn new(
701 output: cxx::UniquePtr<ffi::WriteTrait>,
702 width: u32,
703 height: u32,
704 color: ffi::ColorType,
705 bits_per_component: u8,
706 compression: ffi::Compression,
707 icc_profile: &[u8],
708 ) -> Result<Self, png::EncodingError> {
709 let mut info = png::Info::with_size(width, height);
710 info.color_type = color.into();
711 info.bit_depth = match bits_per_component {
712 8 => png::BitDepth::Eight,
713 16 => png::BitDepth::Sixteen,
714
715 // `SkPngRustEncoderImpl` only encodes 8-bit or 16-bit images.
716 _ => unreachable!(),
717 };
718 if !icc_profile.is_empty() {
719 info.icc_profile = Some(Cow::Owned(icc_profile.to_owned()));
720 }
721 let mut encoder = png::Encoder::with_info(output, info)?;
722 compression.apply(&mut encoder);
723
724 let writer = encoder.write_header()?;
725 Ok(Self(writer))
726 }
727
728 /// FFI-friendly wrapper around `png::Writer::write_text_chunk`.
729 ///
730 /// `keyword` and `text` are treated as strings encoded as Latin-1 (i.e.
731 /// ISO-8859-1).
732 ///
733 /// `ffi::EncodingResult::Parameter` error will be returned if `keyword` or
734 /// `text` don't meet the requirements of the PNG spec. `text` may have
735 /// any length and contain any of the 191 Latin-1 characters (and/or the
736 /// linefeed character), but `keyword`'s length is restricted to at most
737 /// 79 characters and it can't contain a non-breaking space character.
738 ///
739 /// See also https://docs.rs/png/latest/png/struct.Writer.html#method.write_text_chunk
write_text_chunk(&mut self, keyword: &[u8], text: &[u8]) -> ffi::EncodingResult740 fn write_text_chunk(&mut self, keyword: &[u8], text: &[u8]) -> ffi::EncodingResult {
741 // https://www.w3.org/TR/png-3/#11tEXt says that "`text` is interpreted according to the
742 // Latin-1 character set [ISO_8859-1]. The text string may contain any Latin-1
743 // character."
744 let is_latin1_byte = |b| (0x20..=0x7E).contains(b) || (0xA0..=0xFF).contains(b);
745 let is_nbsp_byte = |&b: &u8| b == 0xA0;
746 let is_linefeed_byte = |&b: &u8| b == 10;
747 if !text.iter().all(|b| is_latin1_byte(b) || is_linefeed_byte(b)) {
748 return ffi::EncodingResult::ParameterError;
749 }
750 fn latin1_bytes_into_string(bytes: &[u8]) -> String {
751 bytes.iter().map(|&b| b as char).collect()
752 }
753 let text = latin1_bytes_into_string(text);
754
755 // https://www.w3.org/TR/png-3/#11keywords says that "keywords shall contain only printable
756 // Latin-1 [ISO_8859-1] characters and spaces; that is, only code points 0x20-7E
757 // and 0xA1-FF are allowed."
758 if !keyword.iter().all(|b| is_latin1_byte(b) && !is_nbsp_byte(b)) {
759 return ffi::EncodingResult::ParameterError;
760 }
761 let keyword = latin1_bytes_into_string(keyword);
762
763 let chunk = png::text_metadata::TEXtChunk { keyword, text };
764 let result = self.0.write_text_chunk(&chunk);
765 result.as_ref().err().into()
766 }
767 }
768
769 /// FFI-friendly wrapper around `png::Writer::into_stream_writer`.
770 ///
771 /// See also https://docs.rs/png/latest/png/struct.Writer.html#method.into_stream_writer
convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter>772 fn convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter> {
773 Box::new(ResultOfStreamWriter(writer.0.into_stream_writer().map(StreamWriter)))
774 }
775
776 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
777 /// generics, so we manually monomorphize here, but still expose a minimal,
778 /// somewhat tweaked API of the original type).
779 struct ResultOfStreamWriter(Result<StreamWriter, png::EncodingError>);
780
781 impl ResultOfStreamWriter {
err(&self) -> ffi::EncodingResult782 fn err(&self) -> ffi::EncodingResult {
783 self.0.as_ref().err().into()
784 }
785
unwrap(&mut self) -> Box<StreamWriter>786 fn unwrap(&mut self) -> Box<StreamWriter> {
787 // Leaving `self` in a C++-friendly "moved-away" state.
788 let mut result = Err(png::EncodingError::LimitsExceeded);
789 std::mem::swap(&mut self.0, &mut result);
790
791 Box::new(result.unwrap())
792 }
793 }
794
795 /// FFI-friendly wrapper around `png::StreamWriter` (`cxx` can't handle
796 /// arbitrary generics, so we manually monomorphize here, but still expose a
797 /// minimal, somewhat tweaked API of the original type).
798 struct StreamWriter(png::StreamWriter<'static, cxx::UniquePtr<ffi::WriteTrait>>);
799
800 impl StreamWriter {
801 /// FFI-friendly wrapper around `Write::write` implementation of
802 /// `png::StreamWriter`.
803 ///
804 /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.write
write(&mut self, data: &[u8]) -> ffi::EncodingResult805 pub fn write(&mut self, data: &[u8]) -> ffi::EncodingResult {
806 let io_result = self.0.write(data);
807 let encoding_result = io_result.map_err(|err| png::EncodingError::IoError(err));
808 encoding_result.as_ref().err().into()
809 }
810 }
811
812 /// This provides a public C++ API for encoding a PNG image.
813 ///
814 /// `icc_profile` set to an empty slice acts as null / `None`.
new_writer( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, compression: ffi::Compression, icc_profile: &[u8], ) -> Box<ResultOfWriter>815 fn new_writer(
816 output: cxx::UniquePtr<ffi::WriteTrait>,
817 width: u32,
818 height: u32,
819 color: ffi::ColorType,
820 bits_per_component: u8,
821 compression: ffi::Compression,
822 icc_profile: &[u8],
823 ) -> Box<ResultOfWriter> {
824 Box::new(ResultOfWriter(Writer::new(
825 output,
826 width,
827 height,
828 color,
829 bits_per_component,
830 compression,
831 icc_profile,
832 )))
833 }
834
835 /// FFI-friendly wrapper around `png::StreamWriter::finish`.
836 ///
837 /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.finish
finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult838 fn finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult {
839 stream_writer.0.finish().as_ref().err().into()
840 }
841