image-rs / image

Encoding and decoding images in Rust
Apache License 2.0
4.88k stars 606 forks source link

PNM does not seem to make maxval available #1558

Open ndaniels opened 3 years ago

ndaniels commented 3 years ago

This happens in ,...

Open a pnm (e.g. ppm or pgm) file with the PnmDecoder. Pixel values are integers, but they need not be u8 by the PNM spec. They can have denominators as large as 65535.

Expected

I see that the maxval is read in from the Pnm header, but it doesn't seem to be available in any public interface. So, if I read a Pgm with a maxval of 15, for instance, or 65525, how is the denominator available for (for instance) computing the brightness of a pixel?

use image::io::{Reader};
use image::{GenericImageView};
use std::env;
use std::fs;
use std::io::{self, BufReader, BufRead, Cursor};

fn main() {
    let input = env::args().nth(1);
    let mut raw_reader: Box<dyn BufRead> = match input {
        None => Box::new(BufReader::new(io::stdin())),
        Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap()))
    };
    let mut buffer = Vec::new();
    // read the whole contents
    raw_reader.read_to_end(&mut buffer).unwrap();
    let reader = Reader::new(Cursor::new(buffer))
    .with_guessed_format()
    .expect("Failed to read image format");

    println!("{:?}", reader.format().unwrap());
    let img = reader.decode().unwrap();
    println!("{:?}", img);
    println!("{:?}", img.get_pixel(1,1));
    let sum = img.pixels().fold(0_usize, |acc, pixel| acc + (pixel.2[0] as usize)) as f64;
    let denom = img.get_pixel(0,0).0[3] as u32;
    println!{"{:.3}", sum / (img.width() * img.height() * denom) as f64}
    // so how to get correct maxval? how do they compute brightness?
}
HeroicKatora commented 3 years ago

If you the header data is needed (or any format specific information for that matter) then it's best to use skip the Reader and instead utilize the corresponding codec directly. Sadly, as it turns out, this isn't quite as straightforward either.

let reader = codecs::pnm::PnmDecoder::new(Cursor::new(buffer))
    .expect("Failed to read image format");
// I understand your confusion, there is no `reader.header()`
let img = DynamicImage::from_decoder(reader).unwrap();

This isn't impossible but requires quite a bit of tedium. Basically, from_decoder consumes the reader but you'd want the value to call PnmDecoder::into_inner—which contains the header in the second component. There is no magic in from_decoder and the method could work with a &mut but this use case was not considered to be as relevant in the design of that method.

Ways forward:

HeroicKatora commented 3 years ago

Since you have a Cursor you can do this via a multi-pass approach as a workaround:

use image::codecs::pnm;
use image::{DynamicImage, GenericImageView};
use std::env;
use std::fs;
use std::io::{self, BufReader, BufRead, Cursor};

fn main() {
    let input = env::args().nth(1);

    let mut raw_reader: Box<dyn BufRead> = match input {
        None => Box::new(BufReader::new(io::stdin())),
        Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap()))
    };

    let mut buffer = Vec::new();
    // read the whole contents
    raw_reader.read_to_end(&mut buffer).unwrap();
    // !! Changed
    let (mut cursor, header) = pnm::PnmDecoder::new(Cursor::new(&buffer[..]))
        .expect("Failed to read image format")
        .into_inner();
    // Rewind.
    cursor.set_position(0);
    let reader = image::codecs::pnm::PnmDecoder::new(cursor)
        .expect("Failed to read image format");
    let img = DynamicImage::from_decoder(reader).unwrap();
    println!("{:?}", img);
    println!("{:?}", img.get_pixel(1,1));

    let sum = img.pixels().fold(0_usize, |acc, pixel| acc + (pixel.2[0] as usize)) as f64;
    let denom = header.maximal_sample();
    println!("{:.3}", sum / (img.width() * img.height() * denom) as f64);
}