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