• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::io::{ErrorKind, Read, Write};
11 use std::pin::Pin;
12 
13 // No `use png::...` nor `use ffi::...` because we want the code to explicitly
14 // spell out if it means `ffi::ColorType` vs `png::ColorType` (or `Reader`
15 // vs `png::Reader`).
16 
17 #[cxx::bridge(namespace = "rust_png")]
18 mod ffi {
19     /// FFI-friendly equivalent of `png::ColorType`.
20     enum ColorType {
21         Grayscale = 0,
22         Rgb = 2,
23         Indexed = 3,
24         GrayscaleAlpha = 4,
25         Rgba = 6,
26     }
27 
28     /// FFI-friendly simplification of `Option<png::DecodingError>`.
29     enum DecodingResult {
30         Success,
31         FormatError,
32         ParameterError,
33         LimitsExceededError,
34         /// `IncompleteInput` is equivalent to `png::DecodingError::IoError(
35         /// std::io::ErrorKind::UnexpectedEof.into())`.  It is named after
36         /// `SkCodec::Result::kIncompleteInput`.
37         ///
38         /// `ReadTrait` is infallible and therefore we provide no generic
39         /// equivalent of the `png::DecodingError::IoError` variant
40         /// (other than the special case of `IncompleteInput`).
41         IncompleteInput,
42     }
43 
44     /// FFI-friendly equivalent of `png::DisposeOp`.
45     enum DisposeOp {
46         None,
47         Background,
48         Previous,
49     }
50 
51     /// FFI-friendly equivalent of `png::BlendOp`.
52     enum BlendOp {
53         Source,
54         Over,
55     }
56 
57     /// FFI-friendly simplification of `png::CompressionLevel`.
58     enum Compression {
59         Default,
60         Fast,
61         Best,
62     }
63 
64     /// FFI-friendly simplification of `Option<png::EncodingError>`.
65     enum EncodingResult {
66         Success,
67         IoError,
68         FormatError,
69         ParameterError,
70         LimitsExceededError,
71     }
72 
73     unsafe extern "C++" {
74         include!("experimental/rust_png/ffi/FFI.h");
75 
76         type ReadTrait;
read(self: Pin<&mut ReadTrait>, buffer: &mut [u8]) -> usize77         fn read(self: Pin<&mut ReadTrait>, buffer: &mut [u8]) -> usize;
78 
79         type WriteTrait;
write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool80         fn write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool;
flush(self: Pin<&mut WriteTrait>)81         fn flush(self: Pin<&mut WriteTrait>);
82     }
83 
84     // Rust functions, types, and methods that are exposed through FFI.
85     //
86     // To avoid duplication, there are no doc comments inside the `extern "Rust"`
87     // section. The doc comments of these items can instead be found in the
88     // actual Rust code, outside of the `#[cxx::bridge]` manifest.
89     extern "Rust" {
new_reader(input: UniquePtr<ReadTrait>) -> Box<ResultOfReader>90         fn new_reader(input: UniquePtr<ReadTrait>) -> Box<ResultOfReader>;
91 
92         type ResultOfReader;
err(self: &ResultOfReader) -> DecodingResult93         fn err(self: &ResultOfReader) -> DecodingResult;
unwrap(self: &mut ResultOfReader) -> Box<Reader>94         fn unwrap(self: &mut ResultOfReader) -> Box<Reader>;
95 
96         type Reader;
height(self: &Reader) -> u3297         fn height(self: &Reader) -> u32;
width(self: &Reader) -> u3298         fn width(self: &Reader) -> u32;
interlaced(self: &Reader) -> bool99         fn interlaced(self: &Reader) -> bool;
is_srgb(self: &Reader) -> bool100         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, ) -> bool101         fn try_get_chrm(
102             self: &Reader,
103             wx: &mut f32,
104             wy: &mut f32,
105             rx: &mut f32,
106             ry: &mut f32,
107             gx: &mut f32,
108             gy: &mut f32,
109             bx: &mut f32,
110             by: &mut f32,
111         ) -> bool;
try_get_cicp_chunk( self: &Reader, primaries_id: &mut u8, transfer_id: &mut u8, matrix_id: &mut u8, is_full_range: &mut bool, ) -> bool112         fn try_get_cicp_chunk(
113             self: &Reader,
114             primaries_id: &mut u8,
115             transfer_id: &mut u8,
116             matrix_id: &mut u8,
117             is_full_range: &mut bool,
118         ) -> bool;
try_get_gama(self: &Reader, gamma: &mut f32) -> bool119         fn try_get_gama(self: &Reader, gamma: &mut f32) -> bool;
has_iccp_chunk(self: &Reader) -> bool120         fn has_iccp_chunk(self: &Reader) -> bool;
get_iccp_chunk(self: &Reader) -> &[u8]121         fn get_iccp_chunk(self: &Reader) -> &[u8];
has_trns_chunk(self: &Reader) -> bool122         fn has_trns_chunk(self: &Reader) -> bool;
get_trns_chunk(self: &Reader) -> &[u8]123         fn get_trns_chunk(self: &Reader) -> &[u8];
has_plte_chunk(self: &Reader) -> bool124         fn has_plte_chunk(self: &Reader) -> bool;
get_plte_chunk(self: &Reader) -> &[u8]125         fn get_plte_chunk(self: &Reader) -> &[u8];
has_actl_chunk(self: &Reader) -> bool126         fn has_actl_chunk(self: &Reader) -> bool;
get_actl_num_frames(self: &Reader) -> u32127         fn get_actl_num_frames(self: &Reader) -> u32;
get_actl_num_plays(self: &Reader) -> u32128         fn get_actl_num_plays(self: &Reader) -> u32;
has_fctl_chunk(self: &Reader) -> bool129         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, )130         fn get_fctl_info(
131             self: &Reader,
132             width: &mut u32,
133             height: &mut u32,
134             x_offset: &mut u32,
135             y_offset: &mut u32,
136             dispose_op: &mut DisposeOp,
137             blend_op: &mut BlendOp,
138             duration_ms: &mut u32,
139         );
output_buffer_size(self: &Reader) -> usize140         fn output_buffer_size(self: &Reader) -> usize;
output_color_type(self: &Reader) -> ColorType141         fn output_color_type(self: &Reader) -> ColorType;
output_bits_per_component(self: &Reader) -> u8142         fn output_bits_per_component(self: &Reader) -> u8;
next_frame_info(self: &mut Reader) -> DecodingResult143         fn next_frame_info(self: &mut Reader) -> DecodingResult;
next_interlaced_row<'a>( self: &'a mut Reader, row: &mut &'a [u8], ) -> DecodingResult144         unsafe fn next_interlaced_row<'a>(
145             self: &'a mut Reader,
146             row: &mut &'a [u8],
147         ) -> DecodingResult;
expand_last_interlaced_row( self: &Reader, img: &mut [u8], img_row_stride: usize, row: &[u8], bits_per_pixel: u8, )148         fn expand_last_interlaced_row(
149             self: &Reader,
150             img: &mut [u8],
151             img_row_stride: usize,
152             row: &[u8],
153             bits_per_pixel: u8,
154         );
155 
new_writer( output: UniquePtr<WriteTrait>, width: u32, height: u32, color: ColorType, bits_per_component: u8, compression: Compression, ) -> Box<ResultOfWriter>156         fn new_writer(
157             output: UniquePtr<WriteTrait>,
158             width: u32,
159             height: u32,
160             color: ColorType,
161             bits_per_component: u8,
162             compression: Compression,
163         ) -> Box<ResultOfWriter>;
164 
165         type ResultOfWriter;
err(self: &ResultOfWriter) -> EncodingResult166         fn err(self: &ResultOfWriter) -> EncodingResult;
unwrap(self: &mut ResultOfWriter) -> Box<Writer>167         fn unwrap(self: &mut ResultOfWriter) -> Box<Writer>;
168 
169         type Writer;
write_text_chunk(self: &mut Writer, keyword: &[u8], text: &[u8]) -> EncodingResult170         fn write_text_chunk(self: &mut Writer, keyword: &[u8], text: &[u8]) -> EncodingResult;
convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter>171         fn convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter>;
172 
173         type ResultOfStreamWriter;
err(self: &ResultOfStreamWriter) -> EncodingResult174         fn err(self: &ResultOfStreamWriter) -> EncodingResult;
unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>175         fn unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>;
176 
177         type StreamWriter;
write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult178         fn write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult;
finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult179         fn finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult;
180     }
181 }
182 
183 impl From<png::ColorType> for ffi::ColorType {
from(value: png::ColorType) -> Self184     fn from(value: png::ColorType) -> Self {
185         match value {
186             png::ColorType::Grayscale => Self::Grayscale,
187             png::ColorType::Rgb => Self::Rgb,
188             png::ColorType::Indexed => Self::Indexed,
189             png::ColorType::GrayscaleAlpha => Self::GrayscaleAlpha,
190             png::ColorType::Rgba => Self::Rgba,
191         }
192     }
193 }
194 
195 impl Into<png::ColorType> for ffi::ColorType {
into(self) -> png::ColorType196     fn into(self) -> png::ColorType {
197         match self {
198             Self::Grayscale => png::ColorType::Grayscale,
199             Self::Rgb => png::ColorType::Rgb,
200             Self::GrayscaleAlpha => png::ColorType::GrayscaleAlpha,
201             Self::Rgba => png::ColorType::Rgba,
202 
203             // `SkPngRustEncoderImpl` only uses the color types above.
204             _ => unreachable!(),
205         }
206     }
207 }
208 
209 impl From<png::DisposeOp> for ffi::DisposeOp {
from(value: png::DisposeOp) -> Self210     fn from(value: png::DisposeOp) -> Self {
211         match value {
212             png::DisposeOp::None => Self::None,
213             png::DisposeOp::Background => Self::Background,
214             png::DisposeOp::Previous => Self::Previous,
215         }
216     }
217 }
218 
219 impl From<png::BlendOp> for ffi::BlendOp {
from(value: png::BlendOp) -> Self220     fn from(value: png::BlendOp) -> Self {
221         match value {
222             png::BlendOp::Source => Self::Source,
223             png::BlendOp::Over => Self::Over,
224         }
225     }
226 }
227 
228 impl From<Option<&png::DecodingError>> for ffi::DecodingResult {
from(option: Option<&png::DecodingError>) -> Self229     fn from(option: Option<&png::DecodingError>) -> Self {
230         match option {
231             None => Self::Success,
232             Some(decoding_error) => match decoding_error {
233                 png::DecodingError::IoError(e) => {
234                     if e.kind() == ErrorKind::UnexpectedEof {
235                         Self::IncompleteInput
236                     } else {
237                         // `ReadTrait` is infallible => we expect no other kind of
238                         // `png::DecodingError::IoError`.
239                         unreachable!()
240                     }
241                 }
242                 png::DecodingError::Format(_) => Self::FormatError,
243                 png::DecodingError::Parameter(_) => Self::ParameterError,
244                 png::DecodingError::LimitsExceeded => Self::LimitsExceededError,
245             },
246         }
247     }
248 }
249 
250 impl Into<png::Compression> for ffi::Compression {
into(self) -> png::Compression251     fn into(self) -> png::Compression {
252         match self {
253             Self::Default => png::Compression::Default,
254             Self::Fast => png::Compression::Fast,
255             Self::Best => png::Compression::Best,
256             _ => unreachable!(),
257         }
258     }
259 }
260 
261 impl From<Option<&png::EncodingError>> for ffi::EncodingResult {
from(option: Option<&png::EncodingError>) -> Self262     fn from(option: Option<&png::EncodingError>) -> Self {
263         match option {
264             None => Self::Success,
265             Some(encoding_error) => match encoding_error {
266                 png::EncodingError::IoError(_) => Self::IoError,
267                 png::EncodingError::Format(_) => Self::FormatError,
268                 png::EncodingError::Parameter(_) => Self::ParameterError,
269                 png::EncodingError::LimitsExceeded => Self::LimitsExceededError,
270             },
271         }
272     }
273 }
274 
275 impl<'a> Read for Pin<&'a mut ffi::ReadTrait> {
read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>276     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
277         Ok(self.as_mut().read(buf))
278     }
279 }
280 
281 impl<'a> Write for Pin<&'a mut ffi::WriteTrait> {
write(&mut self, buf: &[u8]) -> std::io::Result<usize>282     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
283         if self.as_mut().write(buf) {
284             Ok(buf.len())
285         } else {
286             Err(ErrorKind::Other.into())
287         }
288     }
289 
flush(&mut self) -> std::io::Result<()>290     fn flush(&mut self) -> std::io::Result<()> {
291         self.as_mut().flush();
292         Ok(())
293     }
294 }
295 
296 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
297 /// generics, so we manually monomorphize here, but still expose a minimal,
298 /// somewhat tweaked API of the original type).
299 struct ResultOfReader(Result<Reader, png::DecodingError>);
300 
301 impl ResultOfReader {
err(&self) -> ffi::DecodingResult302     fn err(&self) -> ffi::DecodingResult {
303         self.0.as_ref().err().into()
304     }
305 
unwrap(&mut self) -> Box<Reader>306     fn unwrap(&mut self) -> Box<Reader> {
307         // Leaving `self` in a C++-friendly "moved-away" state.
308         let mut result = Err(png::DecodingError::LimitsExceeded);
309         std::mem::swap(&mut self.0, &mut result);
310 
311         Box::new(result.unwrap())
312     }
313 }
314 
compute_transformations(info: &png::Info) -> png::Transformations315 fn compute_transformations(info: &png::Info) -> png::Transformations {
316     // There are 2 scenarios where `EXPAND` transformation may be needed:
317     //
318     // * `SkSwizzler` can handle low-bit-depth `ColorType::Indexed`, but it may not
319     //   support other inputs with low bit depth (e.g. `kGray_Color` with bpp=4). We
320     //   use `EXPAND` to ask the `png` crate to expand such low-bpp images to at
321     //   least 8 bits.
322     // * We may need to inject an alpha channel from the `tRNS` chunk if present.
323     //   Note that we can't check `info.trns.is_some()` because at this point we
324     //   have not yet read beyond the `IHDR` chunk.
325     //
326     // We avoid using `EXPAND` for `ColorType::Indexed` because this results in some
327     // performance gains - see https://crbug.com/356882657 for more details.
328     let mut result = match info.color_type {
329         // Work around bpp<8 limitations of `SkSwizzler`
330         png::ColorType::Rgba | png::ColorType::GrayscaleAlpha if (info.bit_depth as u8) < 8 => {
331             png::Transformations::EXPAND
332         }
333 
334         // Handle `tRNS` expansion + work around bpp<8 limitations of `SkSwizzler`
335         png::ColorType::Rgb | png::ColorType::Grayscale => png::Transformations::EXPAND,
336 
337         // Otherwise there is no need to `EXPAND`.
338         png::ColorType::Indexed | png::ColorType::Rgba | png::ColorType::GrayscaleAlpha => {
339             png::Transformations::IDENTITY
340         }
341     };
342 
343     // We mimic how the `libpng`-based `SkPngCodec` handles G16 and GA16.
344     //
345     // TODO(https://crbug.com/359245096): Avoid stripping least signinficant 8 bits in G16 and
346     // GA16 images.
347     if info.bit_depth == png::BitDepth::Sixteen {
348         if matches!(info.color_type, png::ColorType::Grayscale | png::ColorType::GrayscaleAlpha) {
349             result = result | png::Transformations::STRIP_16;
350         }
351     }
352 
353     result
354 }
355 
356 /// FFI-friendly wrapper around `png::Reader<R>` (`cxx` can't handle arbitrary
357 /// generics, so we manually monomorphize here, but still expose a minimal,
358 /// somewhat tweaked API of the original type).
359 struct Reader {
360     reader: png::Reader<cxx::UniquePtr<ffi::ReadTrait>>,
361     last_interlace_info: Option<png::InterlaceInfo>,
362 }
363 
364 impl Reader {
new(input: cxx::UniquePtr<ffi::ReadTrait>) -> Result<Self, png::DecodingError>365     fn new(input: cxx::UniquePtr<ffi::ReadTrait>) -> Result<Self, png::DecodingError> {
366         // By default, the decoder is limited to using 64 Mib. If we ever need to change
367         // that, we can use `png::Decoder::new_with_limits`.
368         let mut decoder = png::Decoder::new(input);
369 
370         let info = decoder.read_header_info()?;
371         let transformations = compute_transformations(info);
372         decoder.set_transformations(transformations);
373 
374         Ok(Self { reader: decoder.read_info()?, last_interlace_info: None })
375     }
376 
height(&self) -> u32377     fn height(&self) -> u32 {
378         self.reader.info().height
379     }
380 
width(&self) -> u32381     fn width(&self) -> u32 {
382         self.reader.info().width
383     }
384 
385     /// Returns whether the PNG image is interlaced.
interlaced(&self) -> bool386     fn interlaced(&self) -> bool {
387         self.reader.info().interlaced
388     }
389 
390     /// Returns whether the decoded PNG image contained a `sRGB` chunk.
is_srgb(&self) -> bool391     fn is_srgb(&self) -> bool {
392         self.reader.info().srgb.is_some()
393     }
394 
395     /// If the decoded PNG image contained a `cHRM` chunk then `try_get_chrm`
396     /// returns `true` and populates the out parameters (`wx`, `wy`, `rx`,
397     /// 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, ) -> bool398     fn try_get_chrm(
399         &self,
400         wx: &mut f32,
401         wy: &mut f32,
402         rx: &mut f32,
403         ry: &mut f32,
404         gx: &mut f32,
405         gy: &mut f32,
406         bx: &mut f32,
407         by: &mut f32,
408     ) -> bool {
409         fn copy_channel(channel: &(png::ScaledFloat, png::ScaledFloat), x: &mut f32, y: &mut f32) {
410             // This uses `0.00001_f32 * (foo.into_scaled() as f32)` instead of just
411             // `foo.into_value()` for compatibility with the legacy implementation
412             // of `ReadColorProfile` in
413             // `//third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc`.
414             *x = 0.00001_f32 * (channel.0.into_scaled() as f32);
415             *y = 0.00001_f32 * (channel.1.into_scaled() as f32);
416         }
417 
418         match self.reader.info().chrm_chunk.as_ref() {
419             None => false,
420             Some(chrm) => {
421                 copy_channel(&chrm.white, wx, wy);
422                 copy_channel(&chrm.red, rx, ry);
423                 copy_channel(&chrm.green, gx, gy);
424                 copy_channel(&chrm.blue, bx, by);
425                 true
426             }
427         }
428     }
429 
430     /// If the decoded PNG image contained a `cICP` chunk then
431     /// `try_get_cicp_chunk` returns `true` and populates the out
432     /// 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, ) -> bool433     fn try_get_cicp_chunk(
434         &self,
435         primaries_id: &mut u8,
436         transfer_id: &mut u8,
437         matrix_id: &mut u8,
438         is_full_range: &mut bool,
439     ) -> bool {
440         match self.reader.info().coding_independent_code_points.as_ref() {
441             None => false,
442             Some(cicp) => {
443                 *primaries_id = cicp.color_primaries;
444                 *transfer_id = cicp.transfer_function;
445                 *matrix_id = cicp.matrix_coefficients;
446                 *is_full_range = cicp.is_video_full_range_image;
447                 true
448             }
449         }
450     }
451 
452     /// If the decoded PNG image contained a `gAMA` chunk then `try_get_gama`
453     /// returns `true` and populates the `gamma` out parameter.  Otherwise,
454     /// returns `false`.
try_get_gama(&self, gamma: &mut f32) -> bool455     fn try_get_gama(&self, gamma: &mut f32) -> bool {
456         match self.reader.info().gama_chunk.as_ref() {
457             None => false,
458             Some(scaled_float) => {
459                 *gamma = scaled_float.into_value();
460                 true
461             }
462         }
463     }
464 
465     /// Returns whether the `iCCP` chunk exists.
has_iccp_chunk(&self) -> bool466     fn has_iccp_chunk(&self) -> bool {
467         self.reader.info().icc_profile.is_some()
468     }
469 
470     /// Returns contents of the `iCCP` chunk.  Panics if there is no `iCCP`
471     /// chunk.
get_iccp_chunk(&self) -> &[u8]472     fn get_iccp_chunk(&self) -> &[u8] {
473         self.reader.info().icc_profile.as_ref().unwrap().as_ref()
474     }
475 
476     /// Returns whether the `tRNS` chunk exists.
has_trns_chunk(&self) -> bool477     fn has_trns_chunk(&self) -> bool {
478         self.reader.info().trns.is_some()
479     }
480 
481     /// Returns contents of the `tRNS` chunk.  Panics if there is no `tRNS`
482     /// chunk.
get_trns_chunk(&self) -> &[u8]483     fn get_trns_chunk(&self) -> &[u8] {
484         self.reader.info().trns.as_ref().unwrap().as_ref()
485     }
486 
487     /// Returns whether the `PLTE` chunk exists.
has_plte_chunk(&self) -> bool488     fn has_plte_chunk(&self) -> bool {
489         self.reader.info().palette.is_some()
490     }
491 
492     /// Returns contents of the `PLTE` chunk.  Panics if there is no `PLTE`
493     /// chunk.
get_plte_chunk(&self) -> &[u8]494     fn get_plte_chunk(&self) -> &[u8] {
495         self.reader.info().palette.as_ref().unwrap().as_ref()
496     }
497 
498     /// Returns whether the `acTL` chunk exists.
has_actl_chunk(&self) -> bool499     fn has_actl_chunk(&self) -> bool {
500         self.reader.info().animation_control.is_some()
501     }
502 
503     /// Returns `num_frames` from the `acTL` chunk.  Panics if there is no
504     /// `acTL` chunk.
505     ///
506     /// The returned value is equal the number of `fcTL` chunks.  (Note that it
507     /// doesn't count `IDAT` nor `fdAT` chunks.  In particular, if an `fcTL`
508     /// chunk doesn't appear before an `IDAT` chunk then `IDAT` is not part
509     /// of the animation.)
510     ///
511     /// See also
512     /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_frames(&self) -> u32513     fn get_actl_num_frames(&self) -> u32 {
514         self.reader.info().animation_control.as_ref().unwrap().num_frames
515     }
516 
517     /// Returns `num_plays` from the `acTL` chunk.  Panics if there is no `acTL`
518     /// chunk.
519     ///
520     /// `0` indicates that the animation should play indefinitely. See
521     /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_plays(&self) -> u32522     fn get_actl_num_plays(&self) -> u32 {
523         self.reader.info().animation_control.as_ref().unwrap().num_plays
524     }
525 
526     /// Returns whether a `fcTL` chunk has been parsed (and can be read using
527     /// `get_fctl_info`).
has_fctl_chunk(&self) -> bool528     fn has_fctl_chunk(&self) -> bool {
529         self.reader.info().frame_control.is_some()
530     }
531 
532     /// Returns `png::FrameControl` information.
533     ///
534     /// 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, )535     fn get_fctl_info(
536         &self,
537         width: &mut u32,
538         height: &mut u32,
539         x_offset: &mut u32,
540         y_offset: &mut u32,
541         dispose_op: &mut ffi::DisposeOp,
542         blend_op: &mut ffi::BlendOp,
543         duration_ms: &mut u32,
544     ) {
545         let frame_control = self.reader.info().frame_control.as_ref().unwrap();
546         *width = frame_control.width;
547         *height = frame_control.height;
548         *x_offset = frame_control.x_offset;
549         *y_offset = frame_control.y_offset;
550         *dispose_op = frame_control.dispose_op.into();
551         *blend_op = frame_control.blend_op.into();
552 
553         // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
554         // says:
555         //
556         // > "The `delay_num` and `delay_den` parameters together specify a fraction
557         // > indicating the time to display the current frame, in seconds. If the
558         // > denominator is 0, it is to be treated as if it were 100 (that is,
559         // > `delay_num` then specifies 1/100ths of a second).
560         *duration_ms = if frame_control.delay_den == 0 {
561             10 * frame_control.delay_num as u32
562         } else {
563             1000 * frame_control.delay_num as u32 / frame_control.delay_den as u32
564         };
565     }
566 
output_buffer_size(&self) -> usize567     fn output_buffer_size(&self) -> usize {
568         self.reader.output_buffer_size()
569     }
570 
output_color_type(&self) -> ffi::ColorType571     fn output_color_type(&self) -> ffi::ColorType {
572         self.reader.output_color_type().0.into()
573     }
574 
output_bits_per_component(&self) -> u8575     fn output_bits_per_component(&self) -> u8 {
576         self.reader.output_color_type().1 as u8
577     }
578 
next_frame_info(&mut self) -> ffi::DecodingResult579     fn next_frame_info(&mut self) -> ffi::DecodingResult {
580         self.reader.next_frame_info().as_ref().err().into()
581     }
582 
583     /// Decodes the next row - see
584     /// https://docs.rs/png/latest/png/struct.Reader.html#method.next_interlaced_row
585     ///
586     /// TODO(https://crbug.com/357876243): Consider using `read_row` to avoid an extra copy.
587     /// See also https://github.com/image-rs/image-png/pull/493
next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult588     fn next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult {
589         let result = self.reader.next_interlaced_row();
590         if let Ok(maybe_row) = result.as_ref() {
591             self.last_interlace_info = maybe_row.as_ref().map(|r| r.interlace()).copied();
592             *row = maybe_row.map(|r| r.data()).unwrap_or(&[]);
593         }
594         result.as_ref().err().into()
595     }
596 
597     /// Expands the last decoded interlaced row - see
598     /// 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, )599     fn expand_last_interlaced_row(
600         &self,
601         img: &mut [u8],
602         img_row_stride: usize,
603         row: &[u8],
604         bits_per_pixel: u8,
605     ) {
606         let Some(png::InterlaceInfo::Adam7(ref adam7info)) = self.last_interlace_info.as_ref()
607         else {
608             panic!("This function should only be called after decoding an interlaced row");
609         };
610         png::expand_interlaced_row(img, img_row_stride, row, adam7info, bits_per_pixel);
611     }
612 }
613 
614 /// This provides a public C++ API for decoding a PNG image.
new_reader(input: cxx::UniquePtr<ffi::ReadTrait>) -> Box<ResultOfReader>615 fn new_reader(input: cxx::UniquePtr<ffi::ReadTrait>) -> Box<ResultOfReader> {
616     Box::new(ResultOfReader(Reader::new(input)))
617 }
618 
619 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
620 /// generics, so we manually monomorphize here, but still expose a minimal,
621 /// somewhat tweaked API of the original type).
622 struct ResultOfWriter(Result<Writer, png::EncodingError>);
623 
624 impl ResultOfWriter {
err(&self) -> ffi::EncodingResult625     fn err(&self) -> ffi::EncodingResult {
626         self.0.as_ref().err().into()
627     }
628 
unwrap(&mut self) -> Box<Writer>629     fn unwrap(&mut self) -> Box<Writer> {
630         // Leaving `self` in a C++-friendly "moved-away" state.
631         let mut result = Err(png::EncodingError::LimitsExceeded);
632         std::mem::swap(&mut self.0, &mut result);
633 
634         Box::new(result.unwrap())
635     }
636 }
637 
638 /// FFI-friendly wrapper around `png::Writer` (`cxx` can't handle
639 /// arbitrary generics, so we manually monomorphize here, but still expose a
640 /// minimal, somewhat tweaked API of the original type).
641 struct Writer(png::Writer<cxx::UniquePtr<ffi::WriteTrait>>);
642 
643 impl Writer {
new( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, compression: ffi::Compression, ) -> Result<Self, png::EncodingError>644     fn new(
645         output: cxx::UniquePtr<ffi::WriteTrait>,
646         width: u32,
647         height: u32,
648         color: ffi::ColorType,
649         bits_per_component: u8,
650         compression: ffi::Compression,
651     ) -> Result<Self, png::EncodingError> {
652         let mut encoder = png::Encoder::new(output, width, height);
653         encoder.set_color(color.into());
654         encoder.set_depth(match bits_per_component {
655             8 => png::BitDepth::Eight,
656             16 => png::BitDepth::Sixteen,
657 
658             // `SkPngRustEncoderImpl` only encodes 8-bit or 16-bit images.
659             _ => unreachable!(),
660         });
661         encoder.set_compression(compression.into());
662         encoder.set_adaptive_filter(match compression {
663             ffi::Compression::Fast => png::AdaptiveFilterType::NonAdaptive,
664             ffi::Compression::Default | ffi::Compression::Best => png::AdaptiveFilterType::Adaptive,
665             _ => unreachable!(),
666         });
667 
668         let writer = encoder.write_header()?;
669         Ok(Self(writer))
670     }
671 
672     /// FFI-friendly wrapper around `png::Writer::write_text_chunk`.
673     ///
674     /// `keyword` and `text` are treated as strings encoded as Latin-1 (i.e.
675     /// ISO-8859-1).
676     ///
677     /// `ffi::EncodingResult::Parameter` error will be returned if `keyword` or
678     /// `text` don't meet the requirements of the PNG spec.  `text` may have
679     /// any length and contain any of the 191 Latin-1 characters (and/or the
680     /// linefeed character), but `keyword`'s length is restricted to at most
681     /// 79 characters and it can't contain a non-breaking space character.
682     ///
683     /// 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::EncodingResult684     fn write_text_chunk(&mut self, keyword: &[u8], text: &[u8]) -> ffi::EncodingResult {
685         // https://www.w3.org/TR/png-3/#11tEXt says that "`text` is interpreted according to the
686         // Latin-1 character set [ISO_8859-1]. The text string may contain any Latin-1
687         // character."
688         let is_latin1_byte = |b| (0x20..=0x7E).contains(b) || (0xA0..=0xFF).contains(b);
689         let is_nbsp_byte = |&b: &u8| b == 0xA0;
690         let is_linefeed_byte = |&b: &u8| b == 10;
691         if !text.iter().all(|b| is_latin1_byte(b) || is_linefeed_byte(b)) {
692             return ffi::EncodingResult::ParameterError;
693         }
694         fn latin1_bytes_into_string(bytes: &[u8]) -> String {
695             bytes.iter().map(|&b| b as char).collect()
696         }
697         let text = latin1_bytes_into_string(text);
698 
699         // https://www.w3.org/TR/png-3/#11keywords says that "keywords shall contain only printable
700         // Latin-1 [ISO_8859-1] characters and spaces; that is, only code points 0x20-7E
701         // and 0xA1-FF are allowed."
702         if !keyword.iter().all(|b| is_latin1_byte(b) && !is_nbsp_byte(b)) {
703             return ffi::EncodingResult::ParameterError;
704         }
705         let keyword = latin1_bytes_into_string(keyword);
706 
707         let chunk = png::text_metadata::TEXtChunk { keyword, text };
708         let result = self.0.write_text_chunk(&chunk);
709         result.as_ref().err().into()
710     }
711 }
712 
713 /// FFI-friendly wrapper around `png::Writer::into_stream_writer`.
714 ///
715 /// 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>716 fn convert_writer_into_stream_writer(writer: Box<Writer>) -> Box<ResultOfStreamWriter> {
717     Box::new(ResultOfStreamWriter(writer.0.into_stream_writer().map(StreamWriter)))
718 }
719 
720 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
721 /// generics, so we manually monomorphize here, but still expose a minimal,
722 /// somewhat tweaked API of the original type).
723 struct ResultOfStreamWriter(Result<StreamWriter, png::EncodingError>);
724 
725 impl ResultOfStreamWriter {
err(&self) -> ffi::EncodingResult726     fn err(&self) -> ffi::EncodingResult {
727         self.0.as_ref().err().into()
728     }
729 
unwrap(&mut self) -> Box<StreamWriter>730     fn unwrap(&mut self) -> Box<StreamWriter> {
731         // Leaving `self` in a C++-friendly "moved-away" state.
732         let mut result = Err(png::EncodingError::LimitsExceeded);
733         std::mem::swap(&mut self.0, &mut result);
734 
735         Box::new(result.unwrap())
736     }
737 }
738 
739 /// FFI-friendly wrapper around `png::StreamWriter` (`cxx` can't handle
740 /// arbitrary generics, so we manually monomorphize here, but still expose a
741 /// minimal, somewhat tweaked API of the original type).
742 struct StreamWriter(png::StreamWriter<'static, cxx::UniquePtr<ffi::WriteTrait>>);
743 
744 impl StreamWriter {
745     /// FFI-friendly wrapper around `Write::write` implementation of
746     /// `png::StreamWriter`.
747     ///
748     /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.write
write(&mut self, data: &[u8]) -> ffi::EncodingResult749     pub fn write(&mut self, data: &[u8]) -> ffi::EncodingResult {
750         let io_result = self.0.write(data);
751         let encoding_result = io_result.map_err(|err| png::EncodingError::IoError(err));
752         encoding_result.as_ref().err().into()
753     }
754 }
755 
756 /// This provides a public C++ API for encoding a PNG image.
new_writer( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, compression: ffi::Compression, ) -> Box<ResultOfWriter>757 fn new_writer(
758     output: cxx::UniquePtr<ffi::WriteTrait>,
759     width: u32,
760     height: u32,
761     color: ffi::ColorType,
762     bits_per_component: u8,
763     compression: ffi::Compression,
764 ) -> Box<ResultOfWriter> {
765     Box::new(ResultOfWriter(Writer::new(
766         output,
767         width,
768         height,
769         color,
770         bits_per_component,
771         compression,
772     )))
773 }
774 
775 /// FFI-friendly wrapper around `png::StreamWriter::finish`.
776 ///
777 /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.finish
finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult778 fn finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult {
779     stream_writer.0.finish().as_ref().err().into()
780 }
781