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

以前 [Rust で jpeg をリサイズして mozjpeg で高圧縮化](https://qiita.com/benki/items/5303f5e3c5a498be2804) という記事を書きましたが、今見直すと色々とイケてないところがあったので見直し＋αです。

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

```rust
// 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 の```resize```、```unsharpen```関数を使います。

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

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

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

```rust
// 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```で保存するようにしました。

```rust
// 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)?;
```

## まとめ
<details><summary>コピペ用サンプルコード</summary><div>

```rust: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(())
}
```
</div></details>

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

```toml:/.cargo/config
[alias]
r = "rustc --release -- -Ctarget-feature=+sse"
```
すると```cargo r```とタイプするだけでビルドしてくれます。