image-png icon indicating copy to clipboard operation
image-png copied to clipboard

Text chunks after image data

Open volks73 opened this issue 2 years ago • 2 comments

Hello, thank you for the excellent crate and related documentation. I am having trouble reading text chunks, specifically tEXt chunks, that appear after the image data chunks but before the image end chunk. According to the documentation for the text_metadata module:

They may appear either before or after the image data chunks.

Then, in the Reading text chunks section, there is the following example:

use std::fs::File;
use std::iter::FromIterator;
use std::path::PathBuf;

// Opening a png file that has a zTXt chunk
let decoder = png::Decoder::new(
    File::open(PathBuf::from_iter([
        "tests",
        "text_chunk_examples",
        "ztxt_example.png",
    ]))
    .unwrap(),
);
let mut reader = decoder.read_info().unwrap();
// If the text chunk is before the image data frames, `reader.info()` already contains the text.
for text_chunk in &reader.info().compressed_latin1_text {
    println!("{:?}", text_chunk.keyword); // Prints the keyword
    println!("{:#?}", text_chunk); // Prints out the text chunk.
    // To get the uncompressed text, use the `get_text` method.
    println!("{}", text_chunk.get_text().unwrap());
}

When I implement this example with the file name replaced with the two example images, the text metadata works for the image with the text chunks before the image data, but it does not work for the PNG with the text chunks after the image data, the text chunks STDOUT is empty.

The above example does have the following comment:

// If the text chunk is before the image data frames, `reader.info()` already contains the text.

and the read_info documentation:

Reads all meta data until the first IDAT chunk

The behavior for the PNG with the image data after the IDAT chunk is expected since this chunk has not been read. When I run the pngcheck example, the text chunks are found for both PNGs. The pngcheck example uses a StreamingDecoder, but this appears more complicated and lower level.

Sorry for the long-winded question, but do I need to implement StreamingDecoder in order to ensure all of the text chunks have been read, regardless of location relative to the IDAT chunk? Alternatively, how do I continue the decoding/reading after all frames have been read but there are post IDAT chunks/pre IEND chunks without using the StreamingDecoder?

I am more than willing to implement StreamingDecoder, but I just wanted to check if there was a higher level interface that I could use and if I am missing something.

text_after_IDAT text_before_IDAT

volks73 avatar Mar 17 '22 18:03 volks73

You'll need to use StreamDecoder for the moment. But that's a shame, so I'd keep this open as a reminder that we should offer functionality similar to StreamWriter::finish() that passes over all additional auxiliary chunks.

HeroicKatora avatar Mar 17 '22 19:03 HeroicKatora

Here is an example I created using the StreamingDecoder loosely based on the pngcheck example, in case it is useful for anyone:

fn main() {
    let mut reader = io::BufReader::new(File::open("./after_idat.png").unwrap());
    let mut decoder = StreamingDecoder::new();
    let data = &mut vec![0; 10 * 1024][..];
    let n = reader.read(data).unwrap();
    let mut buf = &data[..n];
    loop {
        if buf.len() == 0 {
            let n = reader.read(data).unwrap();
            if n == 0 {
                break;
            }
            buf = &data[..n];
        }
        let (n, decoded) = decoder.update(buf, &mut Vec::new()).unwrap();
        match decoded {
            png::Decoded::ImageEnd => break,
            _ => buf = &buf[n..],
        }
    }
    for text_chunk in &decoder.info().unwrap().uncompressed_latin1_text {
        println!("{}", text_chunk.text);
    }
}

volks73 avatar Mar 17 '22 20:03 volks73

@volks73 thanks for the example, I ran into the exact same issue. You can simplify the decoding a bit with std::fs::read if you're not dealing with very large files:

// error handling omitted
let mut decoder = png::StreamingDecoder::new();

let mut bytes_to_decode = &std::fs::read(target_file_path).unwrap()[..];

loop {
    let (num_bytes_decoded, last_decoded_result) =
        decoder.update(bytes_to_decode, &mut Vec::new()).unwrap();

    match last_decoded_result {
        png::Decoded::ImageEnd => break,
        _ => bytes_to_decode = &bytes_to_decode[num_bytes_decoded..],
    }
}

yamplum avatar Dec 10 '23 12:12 yamplum