• 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::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