LoginSignup
6
4

More than 3 years have passed since last update.

Rust で jpeg をリサイズして mozjpeg で高圧縮化(その2)

Posted at

以前 Rust で jpeg をリサイズして mozjpeg で高圧縮化 という記事を書きましたが、今見直すと色々とイケてないところがあったので見直し+αです。

デコード

まずデコード処理です。以前は image crate のopenで処理していましたが、mozjpeg のDecompressを使った方が若干早いようです。

// linux なら Decompress::with_markers(ALL_MARKERS)
//            .from_path("/home/benki/Downloads/0.jpg")?; が使える
let raw_data = fs::read("/home/benki/Downloads/0.jpg")?;
let decomp = Decompress::with_markers(ALL_MARKERS).from_mem(&raw_data)?;

// markers の中に Exif 情報がある
let markers: Vec<(Marker, Vec<u8>)> = decomp
    .markers()
    .into_iter()
    .map(|m| (m.marker, m.data.to_owned()))
    .collect();

// RGB 形式でデコード開始
let mut decomp_started = decomp.rgb()?;

// 幅・高さ取得
let width = decomp_started.width();
let height = decomp_started.height();

// デコードされたデータの取得
let data = decomp_started
    .read_scanlines::<[u8; 3]>()
    .ok_or(anyhow!("read_scanlines error"))?
    .iter()
    .flatten()
    .cloned()
    .collect();

// デコードの終了処理
decomp_started.finish_decompress();

リサイズ・シャープ処理

リサイズとシャープは image crate のresizeunsharpen関数を使います。

まず、Decompressで取得した RGB データを image crate のDynamicImageに渡す必要があります。

次にリサイズに関して、以前の記事はアスペクトレシオを自分で計算していましたが、resize関数が計算してくれます。長い方の辺の大きさを指定すれば良いだけです。

シャープについてはunsharpenという名前なので「ぼかし」かと思いますが、シャープにする処理です。これは以前の記事には書いていませんでした。unsharpen関数の引数は大きさを変えながら画像に合わせて調整が必要です。

// image crate の DynamicImage に変換
let image_buffer =
    RgbImage::from_raw(width as u32, height as u32, data).ok_or(anyhow!("from_raw error"))?;
let img = DynamicImage::ImageRgb8(image_buffer);

// リサイズとシャープ処理
// 1) resize はアスペクトレシオを保持する
// 2) unshrpen の一つ目の引数はどの程度ぼかしを入れるか(0.5~5.0 ぐらい?)
//   二つ目の引数はしきい値(1~10 ぐらい?)
//   どのぐらいの数値が良いかは画像によって変わる
let img = img.resize(1024, 1024, Lanczos3).unsharpen(0.5, 10);

// リサイズ後の幅・高さ取得
let width = img.width() as usize;
let height = img.height() as usize;

// 変換後の RGB データ取得
let data = img.to_rgb().to_vec();

圧縮

mozjpeg のCompressで圧縮します。以前の記事は Exif 情報の保存処理をしていませんでしたが、今回はwrite_markerで保存するようにしました。

// mozjpeg での圧縮処理
let mut comp = Compress::new(ColorSpace::JCS_RGB);
comp.set_scan_optimization_mode(ScanMode::AllComponentsTogether);
comp.set_quality(70.0);
comp.set_size(width, height);
comp.set_mem_dest();
comp.start_compress();

// Exif 情報を書き込む
markers.into_iter().for_each(|m| {
    comp.write_marker(m.0, &m.1);
});

// RGB データを書き込む
let mut line = 0;
loop {
    if line > height - 1 {
        break;
    }
    let buf = unsafe { data.get_unchecked(line * width * 3..(line + 1) * width * 3) };
    comp.write_scanlines(buf);
    line += 1;
}

// 圧縮の終了処理
comp.finish_compress();

// ファイルに保存
let buf = comp.data_to_vec().map_err(|e| anyhow!("{:?}", e))?;
fs::write("/home/benki/Downloads/1.jpg", &buf)?;

まとめ

コピペ用サンプルコード
main.rs
use anyhow::{anyhow, Result};
use image::{self, imageops::FilterType::Lanczos3, DynamicImage, GenericImageView, RgbImage};
use mozjpeg::{ColorSpace, Compress, Decompress, Marker, ScanMode, ALL_MARKERS};
use std::fs;

fn main() -> Result<()> {
    // linux なら Decompress::with_markers(ALL_MARKERS)
    //            .from_path("/home/benki/Downloads/0.jpg")?; が使える
    let raw_data = fs::read("/home/tbenki/Downloads/0.jpg")?;
    let decomp = Decompress::with_markers(ALL_MARKERS).from_mem(&raw_data)?;

    // markers の中に Exif 情報がある
    let markers: Vec<(Marker, Vec<u8>)> = decomp
        .markers()
        .into_iter()
        .map(|m| (m.marker, m.data.to_owned()))
        .collect();

    // RGB 形式でデコード開始
    let mut decomp_started = decomp.rgb()?;

    // 幅・高さ取得
    let width = decomp_started.width();
    let height = decomp_started.height();

    // デコードされたデータの取得
    let data = decomp_started
        .read_scanlines::<[u8; 3]>()
        .ok_or(anyhow!("read_scanlines error"))?
        .iter()
        .flatten()
        .cloned()
        .collect();

    // デコードの終了処理
    decomp_started.finish_decompress();

    // image crate の DynamicImage に変換
    let image_buffer =
        RgbImage::from_raw(width as u32, height as u32, data).ok_or(anyhow!("from_raw error"))?;
    let img = DynamicImage::ImageRgb8(image_buffer);

    // リサイズとシャープ処理
    // 1) resize はアスペクトレシオを保持する
    // 2) unshrpen の一つ目の引数はどの程度ぼかしを入れるか(0.5~5.0 ぐらい?)
    //   二つ目の引数はしきい値(1~10 ぐらい?)
    //   どのぐらいの数値が良いかは画像によって変わる
    let img = img.resize(1024, 1024, Lanczos3).unsharpen(0.5, 10);

    // リサイズ後の幅・高さ取得
    let width = img.width() as usize;
    let height = img.height() as usize;

    // 変換後の RGB データ取得
    let data = img.to_rgb().to_vec();

    // mozjpeg での圧縮処理
    let mut comp = Compress::new(ColorSpace::JCS_RGB);
    comp.set_scan_optimization_mode(ScanMode::AllComponentsTogether);
    comp.set_quality(70.0);
    comp.set_size(width, height);
    comp.set_mem_dest();
    comp.start_compress();

    // Exif 情報を書き込む
    markers.into_iter().for_each(|m| {
        comp.write_marker(m.0, &m.1);
    });

    // RGB データを書き込む
    let mut line = 0;
    loop {
        if line > height - 1 {
            break;
        }
        let buf = unsafe { data.get_unchecked(line * width * 3..(line + 1) * width * 3) };
        comp.write_scanlines(buf);
        line += 1;
    }

    // 圧縮の終了処理
    comp.finish_compress();

    // ファイルに保存
    let buf = comp.data_to_vec().map_err(|e| anyhow!("{:?}", e))?;
    fs::write("/home/benki/Downloads/1.jpg", &buf)?;
    Ok(())
}

あとリリースビルドするときは rustc に-Ctarget-feature=+sseオプションを渡すと処理が早くなります。Cargo.toml があるディレクトリに.cargoディレクトリを作って、その下にconfigファイルを作ります。configファイルに下記を記述。

/.cargo/config
[alias]
r = "rustc --release -- -Ctarget-feature=+sse"

するとcargo rとタイプするだけでビルドしてくれます。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4