image icon indicating copy to clipboard operation
image copied to clipboard

PNM does not seem to make maxval available

Open ndaniels opened this issue 4 years ago • 2 comments

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?
}

ndaniels avatar Sep 24 '21 02:09 ndaniels

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:

  • We add PnmDecoder::header(&self) -> &PnmHeader. Should be easy enough.
  • We modify the inner implementation of from_decoder to work with a mutable reference and then expose that as a separate method on DynamicImage.
  • More proper color space management of the decoding pipeline is planned for 0.24.

197g avatar Sep 24 '21 06:09 197g

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);
}

197g avatar Sep 24 '21 11:09 197g