cvr/
png.rs

1//! `png` contains routines that enable users to read and write `PNG` files. It wraps the [`png`](https://crates.io/crates/png)
2//! crate and returns errors directly from the library where further documentation can be found on
3//! the precise nature of the `DecodingError` and the `EncodingError`.
4//!
5
6extern crate png;
7
8/// `Error` wraps a decoding/encoding error directly from the underlying `png` crate dependency or
9/// conveys that the supplied `Reader` does not match the expected format.
10///
11#[derive(std::fmt::Debug)]
12pub enum Error {
13  /// Error during the PNG decoding process.
14  Decoding(::png::DecodingError),
15  /// Error in the PNG encoding process.
16  Encoding(::png::EncodingError),
17  /// When reading in the PNG image, the file's actual bit depth did not match the expected.
18  InvalidBitDepth,
19  /// When reading in the PNG image, the file's actual color type did not match the expected.
20  InvalidColorType,
21}
22
23impl std::convert::From<::png::DecodingError> for Error {
24  fn from(err: ::png::DecodingError) -> Self {
25    Error::Decoding(err)
26  }
27}
28
29impl std::convert::From<::png::EncodingError> for Error {
30  fn from(err: ::png::EncodingError) -> Self {
31    Error::Encoding(err)
32  }
33}
34
35fn into_raw_parts<T>(v: Vec<T>) -> (*mut T, usize, usize) {
36  let mut m = std::mem::ManuallyDrop::new(v);
37  (m.as_mut_ptr(), m.len(), m.capacity())
38}
39
40/// `read_rgba8` claims ownership of the supplied `std::io::Read` type and attempts to decode an
41/// 8-bit `RGBA` image.
42///
43/// # Errors
44///
45/// Returns a `Result` that's either the 8-bit `RGBA` data or a `cvr::png::Error` type.
46///
47pub fn read_rgba8<Reader>(r: Reader) -> Result<crate::rgba::Image<u8>, Error>
48where
49  Reader: std::io::Read,
50{
51  let (output_info, mut png_reader) = ::png::Decoder::new(r).read_info()?;
52
53  let ::png::OutputInfo {
54    height,
55    width,
56    color_type,
57    bit_depth,
58    ..
59  } = output_info;
60
61  if color_type != ::png::ColorType::RGBA {
62    return Err(Error::InvalidColorType);
63  }
64
65  if bit_depth != ::png::BitDepth::Eight {
66    return Err(Error::InvalidBitDepth);
67  }
68
69  let height = height as usize;
70  let width = width as usize;
71  let size = height * width;
72
73  let mut r = minivec::MiniVec::<u8>::with_capacity(size);
74  let mut g = minivec::MiniVec::<u8>::with_capacity(size);
75  let mut b = minivec::MiniVec::<u8>::with_capacity(size);
76  let mut a = minivec::MiniVec::<u8>::with_capacity(size);
77
78  let mut row_idx = 0;
79
80  let num_channels = 4;
81  let num_cols = width;
82  while let Some(row) = png_reader.next_row()? {
83    let idx = row_idx * num_cols;
84    let end_idx = idx + num_cols;
85
86    row
87      .chunks_exact(num_channels)
88      .zip(r.spare_capacity_mut()[idx..end_idx].iter_mut())
89      .zip(g.spare_capacity_mut()[idx..end_idx].iter_mut())
90      .zip(b.spare_capacity_mut()[idx..end_idx].iter_mut())
91      .zip(a.spare_capacity_mut()[idx..end_idx].iter_mut())
92      .for_each(|((((chunk, r), g), b), a)| {
93        *r = std::mem::MaybeUninit::new(chunk[0]);
94        *g = std::mem::MaybeUninit::new(chunk[1]);
95        *b = std::mem::MaybeUninit::new(chunk[2]);
96        *a = std::mem::MaybeUninit::new(chunk[3]);
97      });
98
99    row_idx += 1;
100  }
101
102  unsafe {
103    r.set_len(size);
104    g.set_len(size);
105    b.set_len(size);
106    a.set_len(size);
107  }
108
109  Ok(crate::rgba::Image {
110    r,
111    g,
112    b,
113    a,
114    h: height,
115    w: width,
116  })
117}
118
119/// `read_rgb8` claims ownership of the supplied `std::io::Read` type and attempts to decode an
120/// 8-bit `RGB` image.
121///
122/// # Errors
123///
124/// Returns a `Result` that's either the 8-bit `RGB` data or a `cvr::png::Error` type.
125///
126pub fn read_rgb8<Reader>(r: Reader) -> Result<crate::rgb::Image<u8>, Error>
127where
128  Reader: std::io::Read,
129{
130  let (output_info, mut png_reader) = ::png::Decoder::new(r).read_info()?;
131
132  let ::png::OutputInfo {
133    height,
134    width,
135    color_type,
136    bit_depth,
137    ..
138  } = output_info;
139
140  if color_type != ::png::ColorType::RGBA && color_type != ::png::ColorType::RGB {
141    return Err(Error::InvalidColorType);
142  }
143
144  if bit_depth != ::png::BitDepth::Eight {
145    return Err(Error::InvalidBitDepth);
146  }
147
148  let height = height as usize;
149  let width = width as usize;
150  let size = height * width;
151
152  let mut r = minivec::MiniVec::<u8>::with_capacity(size);
153  let mut g = minivec::MiniVec::<u8>::with_capacity(size);
154  let mut b = minivec::MiniVec::<u8>::with_capacity(size);
155
156  let mut row_idx = 0;
157
158  let num_channels = if color_type == ::png::ColorType::RGBA {
159    4
160  } else {
161    3
162  };
163
164  let num_cols = width;
165  while let Some(row) = png_reader.next_row()? {
166    let idx = row_idx * num_cols;
167    let end_idx = idx + num_cols;
168
169    row
170      .chunks_exact(num_channels)
171      .zip(r.spare_capacity_mut()[idx..end_idx].iter_mut())
172      .zip(g.spare_capacity_mut()[idx..end_idx].iter_mut())
173      .zip(b.spare_capacity_mut()[idx..end_idx].iter_mut())
174      .for_each(|(((chunk, r), g), b)| {
175        *r = std::mem::MaybeUninit::new(chunk[0]);
176        *g = std::mem::MaybeUninit::new(chunk[1]);
177        *b = std::mem::MaybeUninit::new(chunk[2]);
178      });
179
180    row_idx += 1;
181  }
182
183  unsafe {
184    r.set_len(size);
185    g.set_len(size);
186    b.set_len(size);
187  }
188
189  Ok(crate::rgb::Image {
190    r,
191    g,
192    b,
193    h: height,
194    w: width,
195  })
196}
197
198/// `write_rgba8` attempts to write the provided `RGBA` image to the supplied `std::io::Write`
199/// object using the specified width and height.
200///
201/// # Errors
202///
203/// Returns either a wrapped `::png::EncodingError` or a truthy `Result`.
204///
205#[allow(clippy::cast_possible_truncation)]
206pub fn write_rgba8<Writer, Iter>(
207  writer: Writer,
208  img: Iter,
209  width: usize,
210  height: usize,
211) -> Result<(), Error>
212where
213  Writer: std::io::Write,
214  Iter: std::iter::Iterator<Item = [u8; 4]>,
215{
216  let mut png_encoder = ::png::Encoder::new(writer, width as u32, height as u32);
217  png_encoder.set_color(::png::ColorType::RGBA);
218  png_encoder.set_depth(::png::BitDepth::Eight);
219  let mut png_writer = png_encoder.write_header()?;
220
221  let num_channels = 4;
222  let count = num_channels * width * height;
223
224  let mut buf = vec![std::mem::MaybeUninit::<u8>::uninit(); count];
225  buf
226    .chunks_exact_mut(num_channels)
227    .zip(img)
228    .for_each(|(chunk, [r, g, b, a])| {
229      chunk[0] = std::mem::MaybeUninit::<u8>::new(r);
230      chunk[1] = std::mem::MaybeUninit::<u8>::new(g);
231      chunk[2] = std::mem::MaybeUninit::<u8>::new(b);
232      chunk[3] = std::mem::MaybeUninit::<u8>::new(a);
233    });
234
235  let (ptr, len, cap) = into_raw_parts(buf);
236  let buf = unsafe { Vec::<u8>::from_raw_parts(ptr.cast::<u8>(), len, cap) };
237
238  Ok(png_writer.write_image_data(&buf)?)
239}
240
241/// `write_rgb8` attempts to write the provided `RGB` image to the supplied `std::io::Write`
242/// object using the specified width and height.
243///
244/// # Errors
245///
246/// Returns either a wrapped `::png::EncodingError` or a truthy `Result`.
247///
248#[allow(clippy::cast_possible_truncation)]
249pub fn write_rgb8<Writer, Iter>(
250  writer: Writer,
251  img: Iter,
252  width: usize,
253  height: usize,
254) -> Result<(), Error>
255where
256  Writer: std::io::Write,
257  Iter: std::iter::Iterator<Item = [u8; 3]>,
258{
259  let mut png_encoder = ::png::Encoder::new(writer, width as u32, height as u32);
260  png_encoder.set_color(::png::ColorType::RGB);
261  png_encoder.set_depth(::png::BitDepth::Eight);
262  let mut png_writer = png_encoder.write_header()?;
263
264  let num_channels = 3;
265  let count = num_channels * width * height;
266
267  let mut buf = vec![std::mem::MaybeUninit::<u8>::uninit(); count];
268  buf
269    .chunks_exact_mut(num_channels)
270    .zip(img)
271    .for_each(|(chunk, [r, g, b])| {
272      chunk[0] = std::mem::MaybeUninit::<u8>::new(r);
273      chunk[1] = std::mem::MaybeUninit::<u8>::new(g);
274      chunk[2] = std::mem::MaybeUninit::<u8>::new(b);
275    });
276
277  let (ptr, len, cap) = into_raw_parts(buf);
278  let buf = unsafe { Vec::<u8>::from_raw_parts(ptr.cast::<u8>(), len, cap) };
279
280  Ok(png_writer.write_image_data(&buf)?)
281}
282
283/// `write_grayalpha8` attempts to write the provided grayscale-alpha image to the supplied
284/// `std::io::Write` object using the specified width and height.
285///
286/// # Errors
287///
288/// Returns either a wrapped `::png::EncodingError` or a truthy `Result`.
289///
290#[allow(clippy::cast_possible_truncation)]
291pub fn write_grayalpha8<Writer, Iter>(
292  writer: Writer,
293  img: Iter,
294  width: usize,
295  height: usize,
296) -> Result<(), Error>
297where
298  Writer: std::io::Write,
299  Iter: std::iter::Iterator<Item = [u8; 2]>,
300{
301  let mut png_encoder = ::png::Encoder::new(writer, width as u32, height as u32);
302  png_encoder.set_color(::png::ColorType::GrayscaleAlpha);
303  png_encoder.set_depth(::png::BitDepth::Eight);
304  let mut png_writer = png_encoder.write_header()?;
305
306  let num_channels = 2;
307  let count = num_channels * width * height;
308
309  let mut buf = vec![std::mem::MaybeUninit::<u8>::uninit(); count];
310  buf
311    .chunks_exact_mut(num_channels)
312    .zip(img)
313    .for_each(|(chunk, [v, a])| {
314      chunk[0] = std::mem::MaybeUninit::<u8>::new(v);
315      chunk[1] = std::mem::MaybeUninit::<u8>::new(a);
316    });
317
318  let (ptr, len, cap) = into_raw_parts(buf);
319  let buf = unsafe { Vec::<u8>::from_raw_parts(ptr.cast::<u8>(), len, cap) };
320
321  Ok(png_writer.write_image_data(&buf)?)
322}