Cykooz / libheif-rs

Safe wrapper to libheif-sys for parsing heif/heic files
MIT License
34 stars 11 forks source link

Failing to convert HEIC to JPEG #27

Closed vincent-herlemont closed 3 months ago

vincent-herlemont commented 3 months ago

I am trying to convert a HEIC file to JPEG using the library. Here is my test:

#[test]
fn convert_to_jpg() -> Result<()> {
    let read_ctx = HeifContext::read_from_file("./data/IMG_0832.HEIC")?;
    let handle = read_ctx.primary_image_handle()?;

    let lib_heif = LibHeif::new();
    let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;

    let mut write_context = HeifContext::new()?;
    let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Jpeg)?;
    encoder.set_quality(EncoderQuality::LossLess)?;
    let encoding_options = EncodingOptions::new().unwrap();
    write_context.encode_image(&image, &mut encoder, Some(encoding_options))?;

    let buf = write_context.write_to_bytes()?;

    let mut file = File::create("./data/IMG_0832.jpg").unwrap();
    file.write_all(&buf).unwrap();

    Ok(())
}

Here is the error I encountered:

Error: HeifError { code: EncodingError, sub_code: EncoderEncoding, message: "Error during encoding or writing output file: Encoding problem: JPEG encoding error" }

By adding logs in the C++ binder library, I discovered that the logs stop at this line jpeg_create_compress(&cinfo); encoder_jpeg.cc#L369, then the error is returned here encoder_jpeg.cc#L354.

I might be configuring the encoder incorrectly. Do you have any suggestions for solutions or investigations?


Note that the same image converts very well with similar code using Av1 by utilizing CompressionFormat::Av1.

Cykooz commented 3 months ago

I think it will be more easy if you use image crate to encode and save an image as JPEG-file. Or do you need a heif container with a JPEG-encoded image inside? libheif doesn't create JPEG-files. It creates HEIF-files with somehow encoded images inside.

vincent-herlemont commented 3 months ago

I think it will be more easy if you use image crate to encode and save an image as JPEG-file.

Hmm, I do understand that if it is possible to do that, it would be better.

But once I have retrieved Image after calling the decode function, how do I put the data into the image crate?

Cykooz commented 3 months ago

You can get &[u8] buffer with pixels data from corresponding plane(s). For ColorSpace::Rgb(RgbChroma::Rgb) it is interleaved plane:

let planes = image.planes();
let interleaved_plane = planes.interleaved.unwrap();
assert_eq!(interleaved_plane.width, 1024);
assert_eq!(interleaved_plane.height, 800);
assert_eq!(interleaved_plane.stride, 0);
let buffer: &[u8] = interleaved_plane.data;

In simple case (stride == 0) DynamicImage is created easy:

let rgb_image = image::RgbImage::from_raw(
    interleaved_plane.width, 
    interleaved_plane.height, 
    buffer.to_vec(),
).unwrap();
let dyn_image = image::DynamicImage::ImageRgb8(rgb_image);

I'm not sure if the image crate supports images with stride > width * pixel_size. So in that case you have to copy image rows one by one (without extra bytes at the end of each) from buffer to Vec<u8>.

vincent-herlemont commented 3 months ago

Thank you for your suggestions! It works perfectly (I adapted width, height, stride) to the image I have locally.

Here is the final code:

#[test]
fn convert_to_jpg_with_image_crate() -> Result<()> {
    let read_ctx = HeifContext::read_from_file("./data/IMG_0832.HEIC")?;
    let handle = read_ctx.primary_image_handle()?;

    let lib_heif = LibHeif::new();
    let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
    let planes = image.planes();
    let interleaved_plane = planes.interleaved.unwrap();
    assert_eq!(interleaved_plane.width, 4032);
    assert_eq!(interleaved_plane.height, 3024);
    assert_eq!(interleaved_plane.stride, 12096);

    let buffer: &[u8] = interleaved_plane.data;
    let rgb_image = image::RgbImage::from_raw(
        interleaved_plane.width, 
        interleaved_plane.height, 
        buffer.to_vec(),
    ).unwrap();
    let dyn_image = image::DynamicImage::ImageRgb8(rgb_image);  

    let mut file = File::create("./data/IMG_0832.jpg").unwrap();
    dyn_image.write_to(&mut file, image::ImageFormat::Jpeg).unwrap();

    Ok(())
}

I'm not sure if the image crate supports images with stride > 0. So in that case you have to copy image rows one by one (without extra bytes at the end of each) from buffer to Vec<u8>.

I feel like the image library does not take stride into account; the image result does not seem to be affected.