image-rs / image-png

PNG decoding and encoding library in pure Rust
https://docs.rs/png
Apache License 2.0
347 stars 139 forks source link

How to set the DPI of an image before saving it? #482

Open xYx-c opened 1 month ago

xYx-c commented 1 month ago
pub fn build_dpi_chunk(dpi: u32) -> Vec<u8> {
    let dpm = 39.370_08 * dpi as f32;
    let rounded_dpm = dpm.round() as u32;

    let mut data = Vec::new();
    data.extend_from_slice(&rounded_dpm.to_be_bytes());
    data.extend_from_slice(&rounded_dpm.to_be_bytes());

    data.push(1);
    data
}

pub fn png_with_dpi<P>(imgbuf: ImageBuffer<P, Vec<u8>>, dpi: u32) -> Result<ImageBuffer<P, Vec<u8>>, std::io::Error>
where
    P: Pixel<Subpixel = u8>,
{
    let (width, height) = imgbuf.dimensions();
    let mut encoder = png::Encoder::new(imgbuf.clone().into_raw(), width, height);

    match <P as Pixel>::CHANNEL_COUNT {
        4 => encoder.set_color(png::ColorType::Rgba),
        3 => encoder.set_color(png::ColorType::Rgb),
        _ => {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Incorrect color channel count / format.",
            ))
        },
    }
    encoder.set_depth(png::BitDepth::Eight);
    encoder.set_compression(png::Compression::Best);
    let data = build_dpi_chunk(dpi);

    let mut writer = encoder.write_header()?;

    writer.write_chunk(png::chunk::pHYs, data.as_slice())?;
    writer.write_image_data(&imgbuf)?;

    Ok(imgbuf)
}

I have tried using like this but it doesn't work correctly.

fintelia commented 1 month ago

The first argument to Encoder::new is the writer you want to encode the PNG data into. Often this will be an empty Vec:

let mut encoded = Vec::new();
let mut encoder = png::Encoder::new(&mut encoded, width, height);

And then at the end you need to finish the encoding and return a copy of the encoded data rather than the initial image:

writer.finish()?;
Ok(encoded)
xYx-c commented 1 month ago

Thanks for the help!

call method:

pub fn png_with_dpi<P>(imgbuf: ImageBuffer<P, Vec<u8>>, dpi: u32) -> Result<Vec<u8>, std::io::Error>
where
    P: Pixel<Subpixel = u8>,
{
    let (width, height) = imgbuf.dimensions();
    let mut encoded = Vec::new();
    let mut encoder = png::Encoder::new(&mut encoded, width, height);

    match <P as Pixel>::CHANNEL_COUNT {
        4 => encoder.set_color(png::ColorType::Rgba),
        3 => encoder.set_color(png::ColorType::Rgb),
        _ => {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Incorrect color channel count / format.",
            ))
        },
    }
    encoder.set_depth(png::BitDepth::Eight);
    encoder.set_compression(png::Compression::Best);
    let data = build_dpi_chunk(dpi);

    let mut writer = encoder.write_header()?;

    writer.write_chunk(png::chunk::pHYs, data.as_slice())?;
    writer.write_image_data(&imgbuf)?;

    writer.finish()?;
    Ok(encoded)
}

png_with_dpi(merge_image, 300)?;

result:

x@26707:~/Downloads$ identify -verbose ff3a0f48613d4a529c06d8d5c94b2a4d.png
Image:
  Filename: ff3a0f48613d4a529c06d8d5c94b2a4d.png
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: DirectClass
  Geometry: 1504x2028+0+0
  Resolution: 118.11x118.11
  ...

Resolution: Doesn't seem to meet expectations?

I used imagemagick to verify the results.

fintelia commented 1 month ago

300 DPI = 118.11 pixels/cm, are you sure this isn't the output you expect?

xYx-c commented 1 month ago

thank you very much for your help. does meet the expected results. But when I use software like PhtotShow to open the picture, it shows 900ppi. After conversion, it is 354.331px/cm.

After that, I used a third-party software to set it to 118.11, and the correct reading on the PS was 300ppi.

x@26707:~/Downloads$ convert 030226201d6c4653a9ad492218582d5a.png -density 118.11 118_11.png
x@26707:~/Downloads$ identify -verbose 118_11.png 
Image:
  Filename: 118_11.png
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: DirectClass
  Geometry: 1504x2028+0+0
  Resolution: 118.11x118.11
  ...