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