Hello! I'm trying to update the DPI setting of a PNG image by reading an existing file, updating the pHYs chunk, and writing the output to a new file. For example, I'd update the pHYs chunk to x_res=11811, y_res=11811, units=1
, with x_res and y_res values depending on what DPI I want.
I've been messing around with the png crate but it seems really complicated and difficult to grasp for what seems like it should be a minor edit. So far I've been trying to figure out how to decode the image, then manually rebuild it row by row and re-encode it. Is there a simpler way to do this?
I looked at the image crate but I couldn't find any way to change the pHYs chunk or DPI settings with that.
Okay, I spent a lot of time reading the png crate documentation and examples and came up with this code that can update the DPI / pHYs chunk.
At first I was manually getting each property from the input image Info
and setting each property in the output image, but then I realized I could clone it entirely, then pass it to the Encoder
when creating it using Encoder::with_info()
instead of Encoder::new()
.
Here's the code, hopefully this helps someone else stumbling across this thread! :3
Cargo.toml dependencies:
[dependencies]
png = "0.17.16"
Program:
// For reading and opening files
use std::fs::File;
use std::io::BufWriter;
use std::path::Path;
fn main() {
// sets desired DPI (Dots per inch)
// (hard-coded to 300 DPI for example)
let dpi = 300.0;
// calculates pixels per meter from DPI
// (there are 39.37 inches in a meter)
let ppm = dpi * 39.37;
let ppm = ppm as u32;
// reads existing image from file into a Reader with Decoder
let decoder = png::Decoder::new(File::open("input.png").unwrap());
let mut reader = decoder.read_info().unwrap();
// Allocates the output buffer.
let mut buf = vec![0; reader.output_buffer_size()];
// Reads the next frame. An APNG might contain multiple frames.
let info = reader.next_frame(&mut buf).unwrap();
// Grabs the bytes of the image.
let bytes = &buf[..info.buffer_size()];
// Creates a new file to write output to
let path = Path::new("output.png");
let file = File::create(path).unwrap();
let w = &mut BufWriter::new(file);
// Copies Info from input file to reuse for output file
let mut output_info = reader.info().clone();
// Sets pixel dimensions (as pixels per meter)
output_info.pixel_dims = Some(png::PixelDimensions {
xppu: ppm,
yppu: ppm,
unit: png::Unit::Meter,
});
// Creates encoder with output_info as the Info
let encoder = png::Encoder::with_info(w, output_info).unwrap();
// Saves image header and data to output file
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(bytes).unwrap();
}
3 Likes