Skip to content

Commit 75220b4

Browse files
authored
Extend PNG encoding and decoding support (#332)
* Extend PNG encoding and decoding support * Make png encoding error similar to png decoding error * Fix typos * Add test for rgb16 and reduce memory footprint by not creating image again from the buffer in encoding * Fix typo [no ci]
1 parent 8f94afc commit 75220b4

File tree

4 files changed

+250
-4
lines changed

4 files changed

+250
-4
lines changed

crates/kornia-io/src/error.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ pub enum IoError {
3434
#[error("Failed to decode the image")]
3535
ImageDecodeError(#[from] image::ImageError),
3636

37+
/// Error to encode the PNG image.
38+
#[error("Failed to encode the png image")]
39+
PngEncodingError(String),
40+
3741
/// Error to decode the PNG image.
38-
#[error("Failed to decode the image")]
42+
#[error("Failed to decode the png image")]
3943
PngDecodeError(String),
4044
}

crates/kornia-io/src/png.rs

Lines changed: 245 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{fs::File, path::Path};
22

3-
use kornia_image::Image;
4-
use png::Decoder;
3+
use kornia_image::{Image, ImageSize};
4+
use png::{BitDepth, ColorType, Decoder, Encoder};
55

66
use crate::error::IoError;
77

@@ -47,6 +47,48 @@ pub fn read_image_png_rgba8(file_path: impl AsRef<Path>) -> Result<Image<u8, 4>,
4747
Ok(Image::new(size.into(), buf)?)
4848
}
4949

50+
/// Read a PNG image with a three channels (rgb16).
51+
///
52+
/// # Arguments
53+
///
54+
/// * `file_path` - The path to the PNG file.
55+
///
56+
/// # Returns
57+
///
58+
/// A RGB image with three channels (rgb16).
59+
pub fn read_image_png_rgb16(file_path: impl AsRef<Path>) -> Result<Image<u16, 3>, IoError> {
60+
let (buf, size) = read_png_impl(file_path)?;
61+
62+
// convert the buffer to u16
63+
let mut buf_u16 = Vec::with_capacity(buf.len() / 2);
64+
for chunk in buf.chunks_exact(2) {
65+
buf_u16.push(u16::from_be_bytes([chunk[0], chunk[1]]));
66+
}
67+
68+
Ok(Image::new(size.into(), buf_u16)?)
69+
}
70+
71+
/// Read a PNG image with a four channels (rgba16).
72+
///
73+
/// # Arguments
74+
///
75+
/// * `file_path` - The path to the PNG file.
76+
///
77+
/// # Returns
78+
///
79+
/// A RGB image with four channels (rgb16).
80+
pub fn read_image_png_rgba16(file_path: impl AsRef<Path>) -> Result<Image<u16, 4>, IoError> {
81+
let (buf, size) = read_png_impl(file_path)?;
82+
83+
// convert the buffer to u16
84+
let mut buf_u16 = Vec::with_capacity(buf.len() / 2);
85+
for chunk in buf.chunks_exact(2) {
86+
buf_u16.push(u16::from_be_bytes([chunk[0], chunk[1]]));
87+
}
88+
89+
Ok(Image::new(size.into(), buf_u16)?)
90+
}
91+
5092
/// Read a PNG image with a single channel (mono16).
5193
///
5294
/// # Arguments
@@ -98,10 +140,172 @@ fn read_png_impl(file_path: impl AsRef<Path>) -> Result<(Vec<u8>, [usize; 2]), I
98140
Ok((buf, [info.width as usize, info.height as usize]))
99141
}
100142

143+
/// Writes the given PNG _(rgb8)_ data to the given file path.
144+
///
145+
/// # Arguments
146+
///
147+
/// - `file_path` - The path to the PNG image.
148+
/// - `image` - The tensor containing the PNG image data.
149+
pub fn write_image_png_rgb8(
150+
file_path: impl AsRef<Path>,
151+
image: &Image<u8, 3>,
152+
) -> Result<(), IoError> {
153+
write_png_impl(
154+
file_path,
155+
image.as_slice(),
156+
image.size(),
157+
BitDepth::Eight,
158+
ColorType::Rgb,
159+
)
160+
}
161+
162+
/// Writes the given PNG _(rgba8)_ data to the given file path.
163+
///
164+
/// # Arguments
165+
///
166+
/// - `file_path` - The path to the PNG image.
167+
/// - `image` - The tensor containing the PNG image data.
168+
pub fn write_image_png_rgba8(
169+
file_path: impl AsRef<Path>,
170+
image: &Image<u8, 4>,
171+
) -> Result<(), IoError> {
172+
write_png_impl(
173+
file_path,
174+
image.as_slice(),
175+
image.size(),
176+
BitDepth::Eight,
177+
ColorType::Rgba,
178+
)
179+
}
180+
181+
/// Writes the given PNG _(grayscale 8-bit)_ data to the given file path.
182+
///
183+
/// # Arguments
184+
///
185+
/// - `file_path` - The path to the PNG image.
186+
/// - `image` - The tensor containing the PNG image data.
187+
pub fn write_image_png_gray8(
188+
file_path: impl AsRef<Path>,
189+
image: &Image<u8, 1>,
190+
) -> Result<(), IoError> {
191+
write_png_impl(
192+
file_path,
193+
image.as_slice(),
194+
image.size(),
195+
BitDepth::Eight,
196+
ColorType::Grayscale,
197+
)
198+
}
199+
200+
/// Writes the given PNG _(rgb16)_ data to the given file path.
201+
///
202+
/// # Arguments
203+
///
204+
/// - `file_path` - The path to the PNG image.
205+
/// - `image` - The tensor containing the PNG image data.
206+
pub fn write_image_png_rgb16(
207+
file_path: impl AsRef<Path>,
208+
image: &Image<u16, 3>,
209+
) -> Result<(), IoError> {
210+
let image_size = image.size();
211+
let mut image_buf: Vec<u8> = Vec::with_capacity(image_size.width * image_size.height * 2);
212+
213+
for buf in image.as_slice() {
214+
let be_bytes = buf.to_be_bytes();
215+
image_buf.extend_from_slice(&be_bytes);
216+
}
217+
218+
write_png_impl(
219+
file_path,
220+
&image_buf,
221+
image_size,
222+
BitDepth::Sixteen,
223+
ColorType::Rgb,
224+
)
225+
}
226+
227+
/// Writes the given PNG _(rgba16)_ data to the given file path.
228+
///
229+
/// # Arguments
230+
///
231+
/// - `file_path` - The path to the PNG image.
232+
/// - `image` - The tensor containing the PNG image data.
233+
pub fn write_image_png_rgba16(
234+
file_path: impl AsRef<Path>,
235+
image: &Image<u16, 4>,
236+
) -> Result<(), IoError> {
237+
let image_size = image.size();
238+
let mut image_buf: Vec<u8> = Vec::with_capacity(image_size.width * image_size.height * 2);
239+
240+
for buf in image.as_slice() {
241+
let be_bytes = buf.to_be_bytes();
242+
image_buf.extend_from_slice(&be_bytes);
243+
}
244+
245+
write_png_impl(
246+
file_path,
247+
&image_buf,
248+
image_size,
249+
BitDepth::Sixteen,
250+
ColorType::Rgba,
251+
)
252+
}
253+
254+
/// Writes the given PNG _(grayscale 16-bit)_ data to the given file path.
255+
///
256+
/// # Arguments
257+
///
258+
/// - `file_path` - The path to the PNG image.
259+
/// - `image` - The tensor containing the PNG image data.
260+
pub fn write_image_png_gray16(
261+
file_path: impl AsRef<Path>,
262+
image: &Image<u16, 1>,
263+
) -> Result<(), IoError> {
264+
let image_size = image.size();
265+
let mut image_buf: Vec<u8> = Vec::with_capacity(image_size.width * image_size.height * 2);
266+
267+
for buf in image.as_slice() {
268+
let bug_be = buf.to_be_bytes();
269+
image_buf.extend_from_slice(&bug_be);
270+
}
271+
272+
write_png_impl(
273+
file_path,
274+
&image_buf,
275+
image_size,
276+
BitDepth::Sixteen,
277+
ColorType::Grayscale,
278+
)
279+
}
280+
281+
fn write_png_impl(
282+
file_path: impl AsRef<Path>,
283+
image_data: &[u8],
284+
image_size: ImageSize,
285+
// Make sure you set `depth` correctly
286+
depth: BitDepth,
287+
color_type: ColorType,
288+
) -> Result<(), IoError> {
289+
let file = File::create(file_path)?;
290+
291+
let mut encoder = Encoder::new(file, image_size.width as u32, image_size.height as u32);
292+
encoder.set_color(color_type);
293+
encoder.set_depth(depth);
294+
295+
let mut writer = encoder
296+
.write_header()
297+
.map_err(|e| IoError::PngEncodingError(e.to_string()))?;
298+
writer
299+
.write_image_data(image_data)
300+
.map_err(|e| IoError::PngEncodingError(e.to_string()))?;
301+
Ok(())
302+
}
303+
101304
#[cfg(test)]
102305
mod tests {
306+
use super::*;
103307
use crate::error::IoError;
104-
use crate::png::read_image_png_mono8;
308+
use std::fs::create_dir_all;
105309

106310
#[test]
107311
fn read_png_mono8() -> Result<(), IoError> {
@@ -110,4 +314,42 @@ mod tests {
110314
assert_eq!(image.size().height, 195);
111315
Ok(())
112316
}
317+
318+
#[test]
319+
fn read_write_png_rgb8() -> Result<(), IoError> {
320+
let tmp_dir = tempfile::tempdir()?;
321+
create_dir_all(tmp_dir.path())?;
322+
323+
let file_path = tmp_dir.path().join("dog-rgb8.png");
324+
let image_data = read_image_png_rgb8("../../tests/data/dog-rgb8.png")?;
325+
write_image_png_rgb8(&file_path, &image_data)?;
326+
327+
let image_data_back = read_image_png_rgb8(&file_path)?;
328+
assert!(file_path.exists(), "File does not exist: {:?}", file_path);
329+
330+
assert_eq!(image_data_back.cols(), 258);
331+
assert_eq!(image_data_back.rows(), 195);
332+
assert_eq!(image_data_back.num_channels(), 3);
333+
334+
Ok(())
335+
}
336+
337+
#[test]
338+
fn read_write_png_rgb16() -> Result<(), IoError> {
339+
let tmp_dir = tempfile::tempdir()?;
340+
create_dir_all(tmp_dir.path())?;
341+
342+
let file_path = tmp_dir.path().join("rgb16.png");
343+
let image_data = read_image_png_rgb16("../../tests/data/rgb16.png")?;
344+
write_image_png_rgb16(&file_path, &image_data)?;
345+
346+
let image_data_back = read_image_png_rgb16(&file_path)?;
347+
assert!(file_path.exists(), "File does not exist: {:?}", file_path);
348+
349+
assert_eq!(image_data_back.cols(), 32);
350+
assert_eq!(image_data_back.rows(), 32);
351+
assert_eq!(image_data_back.num_channels(), 3);
352+
353+
Ok(())
354+
}
113355
}

tests/data/dog-rgb8.png

57.9 KB
Loading

tests/data/rgb16.png

595 Bytes
Loading

0 commit comments

Comments
 (0)