pconvert_rust/
utils.rs

1//! PNG decode/encode and read/write functions, external crate type
2//! conversions and other utility functions.
3
4use crate::blending::demultiply_image;
5use crate::errors::PConvertError;
6use image::codecs::png::{CompressionType, FilterType, PngDecoder, PngEncoder};
7use image::ImageDecoder;
8use image::ImageEncoder;
9use image::{ColorType, ImageBuffer, Rgba};
10use std::fs::File;
11use std::io::{BufWriter, Read, Write};
12
13/// Decodes and returns a PNG.
14///
15/// # Arguments
16///
17/// * `readable_stream` - Any structure that implements the `Read` trait.
18/// * `demultiply` - Whether or not to demultiply the PNG.
19pub fn decode_png(
20    readable_stream: impl Read,
21    demultiply: bool,
22) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, PConvertError> {
23    let decoder = PngDecoder::new(readable_stream)?;
24    let (width, height) = decoder.dimensions();
25
26    let mut reader = decoder.into_reader()?;
27
28    let mut bytes = Vec::<u8>::new();
29    reader.read_to_end(&mut bytes)?;
30
31    let mut img = ImageBuffer::from_vec(width, height, bytes).unwrap();
32
33    if demultiply {
34        demultiply_image(&mut img)
35    }
36
37    Ok(img)
38}
39
40/// Reads a PNG from the local file system.
41///
42/// # Arguments
43///
44/// * `file_in` - Local file system path to the PNG file.
45/// * `demultiply` - Whether or not to demultiply the PNG.
46pub fn read_png_from_file(
47    file_in: String,
48    demultiply: bool,
49) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, PConvertError> {
50    let file = File::open(file_in)?;
51    decode_png(file, demultiply)
52}
53
54/// Encodes a PNG and writes it to a buffer.
55///
56/// # Arguments
57///
58/// * `writable_buff` - Any buffer structure that implements the `Write` trait.
59/// * `png` - A byte buffer with the image data.
60/// * `compression` - Compression type to use in the encoding.
61/// * `filter` - Filter type to use in the encoding.
62pub fn encode_png(
63    writable_buff: impl Write,
64    png: &ImageBuffer<Rgba<u8>, Vec<u8>>,
65    compression: CompressionType,
66    filter: FilterType,
67) -> Result<(), PConvertError> {
68    let buff = BufWriter::new(writable_buff);
69    let encoder = PngEncoder::new_with_quality(buff, compression, filter);
70    Ok(encoder.write_image(png, png.width(), png.height(), ColorType::Rgba8)?)
71}
72
73/// Writes a PNG to the local file system using the provided compression
74/// and filter definitions.
75///
76/// # Arguments
77///
78/// * `file_out` - Local file system path where to write the PNG file.
79/// * `png` - A byte buffer with the image data.
80/// * `compression` - Compression type to use in the encoding.
81/// * `filter` - Filter type to use in the encoding.
82pub fn write_png_to_file(
83    file_out: String,
84    png: &ImageBuffer<Rgba<u8>, Vec<u8>>,
85    compression: CompressionType,
86    filter: FilterType,
87) -> Result<(), PConvertError> {
88    let file = File::create(file_out)?;
89    encode_png(file, png, compression, filter)
90}
91
92/// Writes a PNG to the local file system using the default
93/// compression and filter settings.
94///
95/// Avoid the usage of external enumeration values.
96///
97/// # Arguments
98///
99/// * `file_out` - Local file system path where to write the PNG file.
100/// * `png` - A byte buffer with the image data.
101pub fn write_png_to_file_d(
102    file_out: String,
103    png: &ImageBuffer<Rgba<u8>, Vec<u8>>,
104) -> Result<(), PConvertError> {
105    let file = File::create(file_out)?;
106    encode_png(file, png, CompressionType::Fast, FilterType::NoFilter)
107}
108
109/// [NOT SUPPORTED IN WASM] Multi-threaded write version of a
110/// PNG to the local file system.
111///
112/// # Arguments
113///
114/// * `file_out` - Local file system path where to write the PNG file.
115/// * `png` - A byte buffer with the image data.
116/// * `compression` - Compression type to use in the encoding.
117/// * `filter` - Filter type to use in the encoding.
118#[cfg(not(feature = "wasm-extension"))]
119pub fn write_png_parallel(
120    file_out: String,
121    png: &ImageBuffer<Rgba<u8>, Vec<u8>>,
122    compression: CompressionType,
123    filter: FilterType,
124) -> Result<(), PConvertError> {
125    let writer = File::create(file_out)?;
126
127    let mut header = mtpng::Header::new();
128    header.set_size(png.width(), png.height())?;
129    header.set_color(mtpng::ColorType::TruecolorAlpha, 8)?;
130
131    let mut options = mtpng::encoder::Options::new();
132    options.set_compression_level(mtpng_compression_from(compression))?;
133    options.set_filter_mode(mtpng::Mode::Fixed(mtpng_filter_from(filter)))?;
134
135    let mut encoder = mtpng::encoder::Encoder::new(writer, &options);
136    encoder.write_header(&header)?;
137    encoder.write_image_rows(png)?;
138    encoder.finish()?;
139
140    Ok(())
141}
142
143/// [SUPPORTED IN WASM] WASM stub; single-threaded write PNG to the
144/// local file system.
145///
146/// # Arguments
147///
148/// * `file_out` - Local file system path where to write the PNG file.
149/// * `png` - A byte buffer with the image data.
150/// * `compression` - Compression type to use in the encoding.
151/// * `filter` - Filter type to use in the encoding.
152#[cfg(feature = "wasm-extension")]
153pub fn write_png_parallel(
154    file_out: String,
155    png: &ImageBuffer<Rgba<u8>, Vec<u8>>,
156    compression: CompressionType,
157    filter: FilterType,
158) -> Result<(), PConvertError> {
159    write_png_to_file(file_out, png, compression, filter)
160}
161
162/// Converts a `String` to a `image::codecs::png::CompressionType`.
163/// This can not be done by implementing the trait `From<String> for CompressionType` due to Rust's.
164/// [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type).
165pub fn image_compression_from(compression: String) -> CompressionType {
166    match compression.trim().to_lowercase().as_str() {
167        "best" => CompressionType::Best,
168        "default" => CompressionType::Default,
169        "fast" => CompressionType::Fast,
170        _ => CompressionType::Fast,
171    }
172}
173
174/// Converts a `String` to a `image::codecs::png::FilterType`.
175/// This can not be done by implementing the trait `From<String> for FilterType` due to Rust's
176/// [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type).
177pub fn image_filter_from(filter: String) -> FilterType {
178    match filter.trim().to_lowercase().as_str() {
179        "avg" => FilterType::Avg,
180        "nofilter" => FilterType::NoFilter,
181        "paeth" => FilterType::Paeth,
182        "sub" => FilterType::Sub,
183        "up" => FilterType::Up,
184        _ => FilterType::NoFilter,
185    }
186}
187
188#[cfg(not(feature = "wasm-extension"))]
189fn mtpng_compression_from(compression: CompressionType) -> mtpng::CompressionLevel {
190    match compression {
191        CompressionType::Default => mtpng::CompressionLevel::Default,
192        CompressionType::Best => mtpng::CompressionLevel::High,
193        CompressionType::Fast => mtpng::CompressionLevel::Fast,
194        _ => mtpng::CompressionLevel::Fast,
195    }
196}
197
198#[cfg(not(feature = "wasm-extension"))]
199fn mtpng_filter_from(filter: FilterType) -> mtpng::Filter {
200    match filter {
201        FilterType::Avg => mtpng::Filter::Average,
202        FilterType::Paeth => mtpng::Filter::Paeth,
203        FilterType::Sub => mtpng::Filter::Sub,
204        FilterType::Up => mtpng::Filter::Up,
205        FilterType::NoFilter => mtpng::Filter::None,
206        _ => mtpng::Filter::None,
207    }
208}
209
210/// Maximum of two values that implement the `PartialOrd` trait.
211pub fn max<T: PartialOrd>(x: T, y: T) -> T {
212    if x > y {
213        x
214    } else {
215        y
216    }
217}
218
219/// Minimum of two values that implement the `PartialOrd` trait.
220pub fn min<T: PartialOrd>(x: T, y: T) -> T {
221    if x < y {
222        x
223    } else {
224        y
225    }
226}