image-rs / image

Encoding and decoding images in Rust
Apache License 2.0
4.86k stars 597 forks source link

JpegEncoder is very slow! #2240

Closed lanyeeee closed 3 months ago

lanyeeee commented 3 months ago

I needed to set the quality before save the jpg image, and I found the solution here https://github.com/image-rs/image/issues/1842#issuecomment-1683453765 (use image::codecs::jpeg::JpegEncoder), but I found it very slow, saving a jpg image with it took almost 10 times as long as ImageBuffer::save.

I also tried jpeg-encoder. In my test, it takes 1/2 the time of ImageBuffer::save to encode an image, here's my test code.

main.rs

use anyhow::{Ok, Result};
use image::codecs::jpeg::JpegEncoder;
use std::fs;

fn main() -> Result<()> {
    let in_img = image::open("1.jpg")
        .expect("Failed to open output image")
        .to_rgb8();

    // save with image::codecs::jpeg::JpegEncoder
    let timer = std::time::Instant::now();
    let mut writer = fs::File::create("encoder_save_out.jpg")?;
    let encoder = JpegEncoder::new_with_quality(&mut writer, 95);
    in_img.write_with_encoder(encoder)?;
    println!("write_with_encoder: {:?}", timer.elapsed());

    // save with ImageBuffer::save
    let timer = std::time::Instant::now();
    in_img.save("save_out.jpg")?;
    println!("ImageBuffer::save: {:?}", timer.elapsed());

    // save with jpeg_encoder
    let timer = std::time::Instant::now();
    let encoder = jpeg_encoder::Encoder::new_file("jpeg_encoder_out.jpg", 95)?;
    encoder.encode(
        &in_img.as_raw(),
        in_img.width() as u16,
        in_img.height() as u16,
        jpeg_encoder::ColorType::Rgb,
    )?;
    println!("jpeg_encoder: {:?}", timer.elapsed());

    Ok(())
}

Cargo.toml

[dependencies]
image = "0.25.1"
anyhow = "1.0"
jpeg-encoder = "0.6.0"

output

write_with_encoder: 8.3307498s
ImageBuffer::save: 706.4297ms
jpeg_encoder: 280.8474ms

So why is using image::codecs::jpeg::JpegEncoder so much slower than ImageBuffer::save, is there some bug?

fintelia commented 3 months ago

Save internally uses a BufWriter which performs better than a raw File object: https://github.com/image-rs/image/blob/d48c6a6ff310b356018ffd6b5daff0d73587c039/src/io/free_functions.rs#L51

It then internally creates a JpegEncoder, so that part is the same: https://github.com/image-rs/image/blob/d48c6a6ff310b356018ffd6b5daff0d73587c039/src/io/free_functions.rs#L72

lanyeeee commented 3 months ago

I'm really sorry for didn't consider the File is not buffered. Without buffering, each write is a system call, so it's very slow. Now I've changed the code like this, it works like a charm.

let file = fs::File::create("encoder_save_out.jpg")?;
let mut buffered_file_writer = &mut BufWriter::new(file);
let encoder = JpegEncoder::new_with_quality(&mut buffered_file_writer, 95);
in_img.write_with_encoder(encoder)?;