xref: /aosp_15_r20/external/skia/experimental/rust_png/ffi/FFI.rs (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 `Option<png::EncodingError>`.
58     enum EncodingResult {
59         Success,
60         IoError,
61         FormatError,
62         ParameterError,
63         LimitsExceededError,
64     }
65 
66     unsafe extern "C++" {
67         include!("experimental/rust_png/ffi/FFI.h");
68 
69         type ReadTrait;
read(self: Pin<&mut ReadTrait>, buffer: &mut [u8]) -> usize70         fn read(self: Pin<&mut ReadTrait>, buffer: &mut [u8]) -> usize;
71 
72         type WriteTrait;
write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool73         fn write(self: Pin<&mut WriteTrait>, buffer: &[u8]) -> bool;
flush(self: Pin<&mut WriteTrait>)74         fn flush(self: Pin<&mut WriteTrait>);
75     }
76 
77     // Rust functions, types, and methods that are exposed through FFI.
78     //
79     // To avoid duplication, there are no doc comments inside the `extern "Rust"`
80     // section. The doc comments of these items can instead be found in the
81     // actual Rust code, outside of the `#[cxx::bridge]` manifest.
82     extern "Rust" {
new_reader(input: UniquePtr<ReadTrait>) -> Box<ResultOfReader>83         fn new_reader(input: UniquePtr<ReadTrait>) -> Box<ResultOfReader>;
84 
85         type ResultOfReader;
err(self: &ResultOfReader) -> DecodingResult86         fn err(self: &ResultOfReader) -> DecodingResult;
unwrap(self: &mut ResultOfReader) -> Box<Reader>87         fn unwrap(self: &mut ResultOfReader) -> Box<Reader>;
88 
89         type Reader;
height(self: &Reader) -> u3290         fn height(self: &Reader) -> u32;
width(self: &Reader) -> u3291         fn width(self: &Reader) -> u32;
interlaced(self: &Reader) -> bool92         fn interlaced(self: &Reader) -> bool;
is_srgb(self: &Reader) -> bool93         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, ) -> bool94         fn try_get_chrm(
95             self: &Reader,
96             wx: &mut f32,
97             wy: &mut f32,
98             rx: &mut f32,
99             ry: &mut f32,
100             gx: &mut f32,
101             gy: &mut f32,
102             bx: &mut f32,
103             by: &mut f32,
104         ) -> bool;
try_get_cicp_chunk( self: &Reader, primaries_id: &mut u8, transfer_id: &mut u8, matrix_id: &mut u8, is_full_range: &mut bool, ) -> bool105         fn try_get_cicp_chunk(
106             self: &Reader,
107             primaries_id: &mut u8,
108             transfer_id: &mut u8,
109             matrix_id: &mut u8,
110             is_full_range: &mut bool,
111         ) -> bool;
try_get_gama(self: &Reader, gamma: &mut f32) -> bool112         fn try_get_gama(self: &Reader, gamma: &mut f32) -> bool;
has_iccp_chunk(self: &Reader) -> bool113         fn has_iccp_chunk(self: &Reader) -> bool;
get_iccp_chunk(self: &Reader) -> &[u8]114         fn get_iccp_chunk(self: &Reader) -> &[u8];
has_trns_chunk(self: &Reader) -> bool115         fn has_trns_chunk(self: &Reader) -> bool;
get_trns_chunk(self: &Reader) -> &[u8]116         fn get_trns_chunk(self: &Reader) -> &[u8];
has_plte_chunk(self: &Reader) -> bool117         fn has_plte_chunk(self: &Reader) -> bool;
get_plte_chunk(self: &Reader) -> &[u8]118         fn get_plte_chunk(self: &Reader) -> &[u8];
has_actl_chunk(self: &Reader) -> bool119         fn has_actl_chunk(self: &Reader) -> bool;
get_actl_num_frames(self: &Reader) -> u32120         fn get_actl_num_frames(self: &Reader) -> u32;
get_actl_num_plays(self: &Reader) -> u32121         fn get_actl_num_plays(self: &Reader) -> u32;
has_fctl_chunk(self: &Reader) -> bool122         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, )123         fn get_fctl_info(
124             self: &Reader,
125             width: &mut u32,
126             height: &mut u32,
127             x_offset: &mut u32,
128             y_offset: &mut u32,
129             dispose_op: &mut DisposeOp,
130             blend_op: &mut BlendOp,
131             duration_ms: &mut u32,
132         );
output_buffer_size(self: &Reader) -> usize133         fn output_buffer_size(self: &Reader) -> usize;
output_color_type(self: &Reader) -> ColorType134         fn output_color_type(self: &Reader) -> ColorType;
output_bits_per_component(self: &Reader) -> u8135         fn output_bits_per_component(self: &Reader) -> u8;
next_frame_info(self: &mut Reader) -> DecodingResult136         fn next_frame_info(self: &mut Reader) -> DecodingResult;
next_interlaced_row<'a>( self: &'a mut Reader, row: &mut &'a [u8], ) -> DecodingResult137         unsafe fn next_interlaced_row<'a>(
138             self: &'a mut Reader,
139             row: &mut &'a [u8],
140         ) -> DecodingResult;
expand_last_interlaced_row( self: &Reader, img: &mut [u8], img_row_stride: usize, row: &[u8], bits_per_pixel: u8, )141         fn expand_last_interlaced_row(
142             self: &Reader,
143             img: &mut [u8],
144             img_row_stride: usize,
145             row: &[u8],
146             bits_per_pixel: u8,
147         );
148 
new_stream_writer( output: UniquePtr<WriteTrait>, width: u32, height: u32, color: ColorType, bits_per_component: u8, ) -> Box<ResultOfStreamWriter>149         fn new_stream_writer(
150             output: UniquePtr<WriteTrait>,
151             width: u32,
152             height: u32,
153             color: ColorType,
154             bits_per_component: u8,
155         ) -> Box<ResultOfStreamWriter>;
156 
157         type ResultOfStreamWriter;
err(self: &ResultOfStreamWriter) -> EncodingResult158         fn err(self: &ResultOfStreamWriter) -> EncodingResult;
unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>159         fn unwrap(self: &mut ResultOfStreamWriter) -> Box<StreamWriter>;
160 
161         type StreamWriter;
write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult162         fn write(self: &mut StreamWriter, data: &[u8]) -> EncodingResult;
finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult163         fn finish_encoding(stream_writer: Box<StreamWriter>) -> EncodingResult;
164     }
165 }
166 
167 impl From<png::ColorType> for ffi::ColorType {
from(value: png::ColorType) -> Self168     fn from(value: png::ColorType) -> Self {
169         match value {
170             png::ColorType::Grayscale => Self::Grayscale,
171             png::ColorType::Rgb => Self::Rgb,
172             png::ColorType::Indexed => Self::Indexed,
173             png::ColorType::GrayscaleAlpha => Self::GrayscaleAlpha,
174             png::ColorType::Rgba => Self::Rgba,
175         }
176     }
177 }
178 
179 impl Into<png::ColorType> for ffi::ColorType {
into(self) -> png::ColorType180     fn into(self) -> png::ColorType {
181         match self {
182             Self::Grayscale => png::ColorType::Grayscale,
183             Self::Rgb => png::ColorType::Rgb,
184             Self::GrayscaleAlpha => png::ColorType::GrayscaleAlpha,
185             Self::Rgba => png::ColorType::Rgba,
186 
187             // `SkPngRustEncoderImpl` only uses the color types above.
188             _ => unreachable!(),
189         }
190     }
191 }
192 
193 impl From<png::DisposeOp> for ffi::DisposeOp {
from(value: png::DisposeOp) -> Self194     fn from(value: png::DisposeOp) -> Self {
195         match value {
196             png::DisposeOp::None => Self::None,
197             png::DisposeOp::Background => Self::Background,
198             png::DisposeOp::Previous => Self::Previous,
199         }
200     }
201 }
202 
203 impl From<png::BlendOp> for ffi::BlendOp {
from(value: png::BlendOp) -> Self204     fn from(value: png::BlendOp) -> Self {
205         match value {
206             png::BlendOp::Source => Self::Source,
207             png::BlendOp::Over => Self::Over,
208         }
209     }
210 }
211 
212 impl From<Option<&png::DecodingError>> for ffi::DecodingResult {
from(option: Option<&png::DecodingError>) -> Self213     fn from(option: Option<&png::DecodingError>) -> Self {
214         match option {
215             None => Self::Success,
216             Some(decoding_error) => match decoding_error {
217                 png::DecodingError::IoError(e) => {
218                     if e.kind() == ErrorKind::UnexpectedEof {
219                         Self::IncompleteInput
220                     } else {
221                         // `ReadTrait` is infallible => we expect no other kind of
222                         // `png::DecodingError::IoError`.
223                         unreachable!()
224                     }
225                 }
226                 png::DecodingError::Format(_) => Self::FormatError,
227                 png::DecodingError::Parameter(_) => Self::ParameterError,
228                 png::DecodingError::LimitsExceeded => Self::LimitsExceededError,
229             },
230         }
231     }
232 }
233 
234 impl From<Option<&png::EncodingError>> for ffi::EncodingResult {
from(option: Option<&png::EncodingError>) -> Self235     fn from(option: Option<&png::EncodingError>) -> Self {
236         match option {
237             None => Self::Success,
238             Some(encoding_error) => match encoding_error {
239                 png::EncodingError::IoError(_) => Self::IoError,
240                 png::EncodingError::Format(_) => Self::FormatError,
241                 png::EncodingError::Parameter(_) => Self::ParameterError,
242                 png::EncodingError::LimitsExceeded => Self::LimitsExceededError,
243             },
244         }
245     }
246 }
247 
248 impl<'a> Read for Pin<&'a mut ffi::ReadTrait> {
read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>249     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
250         Ok(self.as_mut().read(buf))
251     }
252 }
253 
254 impl<'a> Write for Pin<&'a mut ffi::WriteTrait> {
write(&mut self, buf: &[u8]) -> std::io::Result<usize>255     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
256         if self.as_mut().write(buf) {
257             Ok(buf.len())
258         } else {
259             Err(ErrorKind::Other.into())
260         }
261     }
262 
flush(&mut self) -> std::io::Result<()>263     fn flush(&mut self) -> std::io::Result<()> {
264         self.as_mut().flush();
265         Ok(())
266     }
267 }
268 
269 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
270 /// generics, so we manually monomorphize here, but still expose a minimal,
271 /// somewhat tweaked API of the original type).
272 struct ResultOfReader(Result<Reader, png::DecodingError>);
273 
274 impl ResultOfReader {
err(&self) -> ffi::DecodingResult275     fn err(&self) -> ffi::DecodingResult {
276         self.0.as_ref().err().into()
277     }
278 
unwrap(&mut self) -> Box<Reader>279     fn unwrap(&mut self) -> Box<Reader> {
280         // Leaving `self` in a C++-friendly "moved-away" state.
281         let mut result = Err(png::DecodingError::LimitsExceeded);
282         std::mem::swap(&mut self.0, &mut result);
283 
284         Box::new(result.unwrap())
285     }
286 }
287 
compute_transformations(info: &png::Info) -> png::Transformations288 fn compute_transformations(info: &png::Info) -> png::Transformations {
289     // There are 2 scenarios where `EXPAND` transformation may be needed:
290     //
291     // * `SkSwizzler` can handle low-bit-depth `ColorType::Indexed`, but it may not
292     //   support other inputs with low bit depth (e.g. `kGray_Color` with bpp=4). We
293     //   use `EXPAND` to ask the `png` crate to expand such low-bpp images to at
294     //   least 8 bits.
295     // * We may need to inject an alpha channel from the `tRNS` chunk if present.
296     //   Note that we can't check `info.trns.is_some()` because at this point we
297     //   have not yet read beyond the `IHDR` chunk.
298     //
299     // We avoid using `EXPAND` for `ColorType::Indexed` because this results in some
300     // performance gains - see https://crbug.com/356882657 for more details.
301     let mut result = match info.color_type {
302         // Work around bpp<8 limitations of `SkSwizzler`
303         png::ColorType::Rgba | png::ColorType::GrayscaleAlpha if (info.bit_depth as u8) < 8 => {
304             png::Transformations::EXPAND
305         }
306 
307         // Handle `tRNS` expansion + work around bpp<8 limitations of `SkSwizzler`
308         png::ColorType::Rgb | png::ColorType::Grayscale => png::Transformations::EXPAND,
309 
310         // Otherwise there is no need to `EXPAND`.
311         png::ColorType::Indexed | png::ColorType::Rgba | png::ColorType::GrayscaleAlpha => {
312             png::Transformations::IDENTITY
313         }
314     };
315 
316     // We mimic how the `libpng`-based `SkPngCodec` handles G16 and GA16.
317     //
318     // TODO(https://crbug.com/359245096): Avoid stripping least signinficant 8 bits in G16 and
319     // GA16 images.
320     if info.bit_depth == png::BitDepth::Sixteen {
321         if matches!(info.color_type, png::ColorType::Grayscale | png::ColorType::GrayscaleAlpha) {
322             result = result | png::Transformations::STRIP_16;
323         }
324     }
325 
326     result
327 }
328 
329 /// FFI-friendly wrapper around `png::Reader<R>` (`cxx` can't handle arbitrary
330 /// generics, so we manually monomorphize here, but still expose a minimal,
331 /// somewhat tweaked API of the original type).
332 struct Reader {
333     reader: png::Reader<cxx::UniquePtr<ffi::ReadTrait>>,
334     last_interlace_info: Option<png::InterlaceInfo>,
335 }
336 
337 impl Reader {
new(input: cxx::UniquePtr<ffi::ReadTrait>) -> Result<Self, png::DecodingError>338     fn new(input: cxx::UniquePtr<ffi::ReadTrait>) -> Result<Self, png::DecodingError> {
339         // By default, the decoder is limited to using 64 Mib. If we ever need to change
340         // that, we can use `png::Decoder::new_with_limits`.
341         let mut decoder = png::Decoder::new(input);
342 
343         let info = decoder.read_header_info()?;
344         let transformations = compute_transformations(info);
345         decoder.set_transformations(transformations);
346 
347         Ok(Self { reader: decoder.read_info()?, last_interlace_info: None })
348     }
349 
height(&self) -> u32350     fn height(&self) -> u32 {
351         self.reader.info().height
352     }
353 
width(&self) -> u32354     fn width(&self) -> u32 {
355         self.reader.info().width
356     }
357 
358     /// Returns whether the PNG image is interlaced.
interlaced(&self) -> bool359     fn interlaced(&self) -> bool {
360         self.reader.info().interlaced
361     }
362 
363     /// Returns whether the decoded PNG image contained a `sRGB` chunk.
is_srgb(&self) -> bool364     fn is_srgb(&self) -> bool {
365         self.reader.info().srgb.is_some()
366     }
367 
368     /// If the decoded PNG image contained a `cHRM` chunk then `try_get_chrm`
369     /// returns `true` and populates the out parameters (`wx`, `wy`, `rx`,
370     /// 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, ) -> bool371     fn try_get_chrm(
372         &self,
373         wx: &mut f32,
374         wy: &mut f32,
375         rx: &mut f32,
376         ry: &mut f32,
377         gx: &mut f32,
378         gy: &mut f32,
379         bx: &mut f32,
380         by: &mut f32,
381     ) -> bool {
382         fn copy_channel(channel: &(png::ScaledFloat, png::ScaledFloat), x: &mut f32, y: &mut f32) {
383             *x = channel.0.into_value();
384             *y = channel.1.into_value();
385         }
386 
387         match self.reader.info().chrm_chunk.as_ref() {
388             None => false,
389             Some(chrm) => {
390                 copy_channel(&chrm.white, wx, wy);
391                 copy_channel(&chrm.red, rx, ry);
392                 copy_channel(&chrm.green, gx, gy);
393                 copy_channel(&chrm.blue, bx, by);
394                 true
395             }
396         }
397     }
398 
399     /// If the decoded PNG image contained a `cICP` chunk then
400     /// `try_get_cicp_chunk` returns `true` and populates the out
401     /// 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, ) -> bool402     fn try_get_cicp_chunk(
403         &self,
404         primaries_id: &mut u8,
405         transfer_id: &mut u8,
406         matrix_id: &mut u8,
407         is_full_range: &mut bool,
408     ) -> bool {
409         match self.reader.info().coding_independent_code_points.as_ref() {
410             None => false,
411             Some(cicp) => {
412                 *primaries_id = cicp.color_primaries;
413                 *transfer_id = cicp.transfer_function;
414                 *matrix_id = cicp.matrix_coefficients;
415                 *is_full_range = cicp.is_video_full_range_image;
416                 true
417             }
418         }
419     }
420 
421     /// If the decoded PNG image contained a `gAMA` chunk then `try_get_gama`
422     /// returns `true` and populates the `gamma` out parameter.  Otherwise,
423     /// returns `false`.
try_get_gama(&self, gamma: &mut f32) -> bool424     fn try_get_gama(&self, gamma: &mut f32) -> bool {
425         match self.reader.info().gama_chunk.as_ref() {
426             None => false,
427             Some(scaled_float) => {
428                 *gamma = scaled_float.into_value();
429                 true
430             }
431         }
432     }
433 
434     /// Returns whether the `iCCP` chunk exists.
has_iccp_chunk(&self) -> bool435     fn has_iccp_chunk(&self) -> bool {
436         self.reader.info().icc_profile.is_some()
437     }
438 
439     /// Returns contents of the `iCCP` chunk.  Panics if there is no `iCCP`
440     /// chunk.
get_iccp_chunk(&self) -> &[u8]441     fn get_iccp_chunk(&self) -> &[u8] {
442         self.reader.info().icc_profile.as_ref().unwrap().as_ref()
443     }
444 
445     /// Returns whether the `tRNS` chunk exists.
has_trns_chunk(&self) -> bool446     fn has_trns_chunk(&self) -> bool {
447         self.reader.info().trns.is_some()
448     }
449 
450     /// Returns contents of the `tRNS` chunk.  Panics if there is no `tRNS`
451     /// chunk.
get_trns_chunk(&self) -> &[u8]452     fn get_trns_chunk(&self) -> &[u8] {
453         self.reader.info().trns.as_ref().unwrap().as_ref()
454     }
455 
456     /// Returns whether the `PLTE` chunk exists.
has_plte_chunk(&self) -> bool457     fn has_plte_chunk(&self) -> bool {
458         self.reader.info().palette.is_some()
459     }
460 
461     /// Returns contents of the `PLTE` chunk.  Panics if there is no `PLTE`
462     /// chunk.
get_plte_chunk(&self) -> &[u8]463     fn get_plte_chunk(&self) -> &[u8] {
464         self.reader.info().palette.as_ref().unwrap().as_ref()
465     }
466 
467     /// Returns whether the `acTL` chunk exists.
has_actl_chunk(&self) -> bool468     fn has_actl_chunk(&self) -> bool {
469         self.reader.info().animation_control.is_some()
470     }
471 
472     /// Returns `num_frames` from the `acTL` chunk.  Panics if there is no
473     /// `acTL` chunk.
474     ///
475     /// The returned value is equal the number of `fcTL` chunks.  (Note that it
476     /// doesn't count `IDAT` nor `fdAT` chunks.  In particular, if an `fcTL`
477     /// chunk doesn't appear before an `IDAT` chunk then `IDAT` is not part
478     /// of the animation.)
479     ///
480     /// See also
481     /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_frames(&self) -> u32482     fn get_actl_num_frames(&self) -> u32 {
483         self.reader.info().animation_control.as_ref().unwrap().num_frames
484     }
485 
486     /// Returns `num_plays` from the `acTL` chunk.  Panics if there is no `acTL`
487     /// chunk.
488     ///
489     /// `0` indicates that the animation should play indefinitely. See
490     /// <https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk>.
get_actl_num_plays(&self) -> u32491     fn get_actl_num_plays(&self) -> u32 {
492         self.reader.info().animation_control.as_ref().unwrap().num_plays
493     }
494 
495     /// Returns whether a `fcTL` chunk has been parsed (and can be read using
496     /// `get_fctl_info`).
has_fctl_chunk(&self) -> bool497     fn has_fctl_chunk(&self) -> bool {
498         self.reader.info().frame_control.is_some()
499     }
500 
501     /// Returns `png::FrameControl` information.
502     ///
503     /// 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, )504     fn get_fctl_info(
505         &self,
506         width: &mut u32,
507         height: &mut u32,
508         x_offset: &mut u32,
509         y_offset: &mut u32,
510         dispose_op: &mut ffi::DisposeOp,
511         blend_op: &mut ffi::BlendOp,
512         duration_ms: &mut u32,
513     ) {
514         let frame_control = self.reader.info().frame_control.as_ref().unwrap();
515         *width = frame_control.width;
516         *height = frame_control.height;
517         *x_offset = frame_control.x_offset;
518         *y_offset = frame_control.y_offset;
519         *dispose_op = frame_control.dispose_op.into();
520         *blend_op = frame_control.blend_op.into();
521 
522         // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
523         // says:
524         //
525         // > "The `delay_num` and `delay_den` parameters together specify a fraction
526         // > indicating the time to display the current frame, in seconds. If the
527         // > denominator is 0, it is to be treated as if it were 100 (that is,
528         // > `delay_num` then specifies 1/100ths of a second).
529         *duration_ms = if frame_control.delay_den == 0 {
530             10 * frame_control.delay_num as u32
531         } else {
532             1000 * frame_control.delay_num as u32 / frame_control.delay_den as u32
533         };
534     }
535 
output_buffer_size(&self) -> usize536     fn output_buffer_size(&self) -> usize {
537         self.reader.output_buffer_size()
538     }
539 
output_color_type(&self) -> ffi::ColorType540     fn output_color_type(&self) -> ffi::ColorType {
541         self.reader.output_color_type().0.into()
542     }
543 
output_bits_per_component(&self) -> u8544     fn output_bits_per_component(&self) -> u8 {
545         self.reader.output_color_type().1 as u8
546     }
547 
next_frame_info(&mut self) -> ffi::DecodingResult548     fn next_frame_info(&mut self) -> ffi::DecodingResult {
549         self.reader.next_frame_info().as_ref().err().into()
550     }
551 
552     /// Decodes the next row - see
553     /// https://docs.rs/png/latest/png/struct.Reader.html#method.next_interlaced_row
554     ///
555     /// TODO(https://crbug.com/357876243): Consider using `read_row` to avoid an extra copy.
556     /// See also https://github.com/image-rs/image-png/pull/493
next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult557     fn next_interlaced_row<'a>(&'a mut self, row: &mut &'a [u8]) -> ffi::DecodingResult {
558         let result = self.reader.next_interlaced_row();
559         if let Ok(maybe_row) = result.as_ref() {
560             self.last_interlace_info = maybe_row.as_ref().map(|r| r.interlace()).copied();
561             *row = maybe_row.map(|r| r.data()).unwrap_or(&[]);
562         }
563         result.as_ref().err().into()
564     }
565 
566     /// Expands the last decoded interlaced row - see
567     /// 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, )568     fn expand_last_interlaced_row(
569         &self,
570         img: &mut [u8],
571         img_row_stride: usize,
572         row: &[u8],
573         bits_per_pixel: u8,
574     ) {
575         let Some(png::InterlaceInfo::Adam7(ref adam7info)) = self.last_interlace_info.as_ref()
576         else {
577             panic!("This function should only be called after decoding an interlaced row");
578         };
579         png::expand_interlaced_row(img, img_row_stride, row, adam7info, bits_per_pixel);
580     }
581 }
582 
583 /// This provides a public C++ API for decoding a PNG image.
new_reader(input: cxx::UniquePtr<ffi::ReadTrait>) -> Box<ResultOfReader>584 fn new_reader(input: cxx::UniquePtr<ffi::ReadTrait>) -> Box<ResultOfReader> {
585     Box::new(ResultOfReader(Reader::new(input)))
586 }
587 
588 /// FFI-friendly wrapper around `Result<T, E>` (`cxx` can't handle arbitrary
589 /// generics, so we manually monomorphize here, but still expose a minimal,
590 /// somewhat tweaked API of the original type).
591 struct ResultOfStreamWriter(Result<StreamWriter, png::EncodingError>);
592 
593 impl ResultOfStreamWriter {
err(&self) -> ffi::EncodingResult594     fn err(&self) -> ffi::EncodingResult {
595         self.0.as_ref().err().into()
596     }
597 
unwrap(&mut self) -> Box<StreamWriter>598     fn unwrap(&mut self) -> Box<StreamWriter> {
599         // Leaving `self` in a C++-friendly "moved-away" state.
600         let mut result = Err(png::EncodingError::LimitsExceeded);
601         std::mem::swap(&mut self.0, &mut result);
602 
603         Box::new(result.unwrap())
604     }
605 }
606 
607 /// FFI-friendly wrapper around `png::StreamWriter` (`cxx` can't handle
608 /// arbitrary generics, so we manually monomorphize here, but still expose a
609 /// minimal, somewhat tweaked API of the original type).
610 struct StreamWriter(png::StreamWriter<'static, cxx::UniquePtr<ffi::WriteTrait>>);
611 
612 impl StreamWriter {
new( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, ) -> Result<Self, png::EncodingError>613     fn new(
614         output: cxx::UniquePtr<ffi::WriteTrait>,
615         width: u32,
616         height: u32,
617         color: ffi::ColorType,
618         bits_per_component: u8,
619     ) -> Result<Self, png::EncodingError> {
620         let mut encoder = png::Encoder::new(output, width, height);
621         encoder.set_color(color.into());
622         encoder.set_depth(match bits_per_component {
623             8 => png::BitDepth::Eight,
624             16 => png::BitDepth::Sixteen,
625 
626             // `SkPngRustEncoderImpl` only encodes 8-bit or 16-bit images.
627             _ => unreachable!(),
628         });
629 
630         let writer = encoder.write_header()?;
631         let stream_writer = writer.into_stream_writer()?;
632         Ok(Self(stream_writer))
633     }
634 
635     /// FFI-friendly wrapper around `Write::write` implementation of
636     /// `png::StreamWriter`.
637     ///
638     /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.write
write(&mut self, data: &[u8]) -> ffi::EncodingResult639     pub fn write(&mut self, data: &[u8]) -> ffi::EncodingResult {
640         let io_result = self.0.write(data);
641         let encoding_result = io_result.map_err(|err| png::EncodingError::IoError(err));
642         encoding_result.as_ref().err().into()
643     }
644 }
645 
646 /// This provides a public C++ API for encoding a PNG image.
new_stream_writer( output: cxx::UniquePtr<ffi::WriteTrait>, width: u32, height: u32, color: ffi::ColorType, bits_per_component: u8, ) -> Box<ResultOfStreamWriter>647 fn new_stream_writer(
648     output: cxx::UniquePtr<ffi::WriteTrait>,
649     width: u32,
650     height: u32,
651     color: ffi::ColorType,
652     bits_per_component: u8,
653 ) -> Box<ResultOfStreamWriter> {
654     Box::new(ResultOfStreamWriter(StreamWriter::new(
655         output,
656         width,
657         height,
658         color,
659         bits_per_component,
660     )))
661 }
662 
663 /// FFI-friendly wrapper around `png::StreamWriter::finish`.
664 ///
665 /// See also https://docs.rs/png/latest/png/struct.StreamWriter.html#method.finish
finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult666 fn finish_encoding(stream_writer: Box<StreamWriter>) -> ffi::EncodingResult {
667     stream_writer.0.finish().as_ref().err().into()
668 }
669