画像を圧縮してサイズを減らす

同梱画像は実行ファイルへの埋め込み時にほぼ縮まない。WebP への変換と縮小、src/assets・public・bundle.resources の使い分けと convertFileSrc での表示、アイコンの注意点を示す。

パフォーマンス 対象: Tauri 2.x 更新日: 読了目安: 約8分 perf-003
目次
  1. 前提条件
  2. 1. 画像そのものを小さくする
  3. 2. 置き場所と読み込み方を選ぶ (TypeScript)
  4. 3. アイコンとトレイ画像で膨らませない (Rust)
  5. 動作確認
  6. よくあるエラーと対処法
  7. OS ごとの違いと注意点
  8. 関連レシピ

Tauri はフロントエンドを brotli で圧縮して実行ファイルに埋め込みますが、JPEG・PNG・WebP はもともと圧縮済みなので、この段階ではほとんど縮みません。手元では 1.5MB の JPEG が 1.5MB のまま入りました。画像は自分で小さくしてから入れる必要があります。ここでは変換と縮小の手順、Tauri で画像を同梱する 3 つの置き場所と読み込み方、アイコンやトレイ画像で実行ファイルが膨らむ落とし穴を扱います。どの画像が大きいかの調べ方は アプリのバンドルサイズを分析する、画像以外の削減は アプリを軽量化する を参照してください。

前提条件

変換には sharp-cli(画像処理ライブラリ sharp のコマンド版)を開発用に入れます。配布物には入りません。

npm install -D sharp-cli

bundle.resources に置いた画像を表示する場合だけ、asset プロトコルを有効にします(2 章)。プラグインは不要で、パスの解決に使う resolveResource() は core:default の範囲で呼べます。3 章の Rust の例は tauri の機能を 2 つ使います。

[dependencies]
tauri = { version = "2", features = ["tray-icon", "image-png"] }

1. 画像そのものを小さくする

効くのは寸法と形式の 2 つです。アプリのウィンドウは広くても 1920px 程度なので、4K の写真はそのままでは無駄が大きく、拡大率 200% の画面を考えても「表示する大きさの 2 倍」で足ります。形式は写真なら WebP の非可逆、透過のあるロゴやイラストなら WebP の可逆が扱いやすく、線画のアイコンは SVG にすると埋め込み時の圧縮も効きます。

# 元画像は images/ に置き、幅 1920 を超えるものだけ縮めて WebP(品質 80)で書き出す
npx sharp -i "images/*.jpg" -o "src/assets/{name}.webp" -q 80 resize 1920 --withoutEnlargement
# 透過のあるロゴは可逆の WebP にする
npx sharp -i images/logo.png -o src/assets/ -f webp --lossless

3840×2400 の写真(高画質の JPEG)で試した結果です。元の JPEG の画質が高いほど差が大きく出ます。

変換大きさ
元の JPEG(3840×2400)1,565 KB
WebP 品質 80177 KB
AVIF 品質 5089 KB
幅 1920 に縮小 + JPEG 品質 80116 KB
幅 1920 に縮小 + WebP 品質 8075 KB

変換はビルドのたびに行わず、変換した結果をリポジトリに入れます。元画像を public/ に残すと、使っていなくても実行ファイルに埋め込まれるので、images/ のような dist/ に出ない場所に置きます。

2. 置き場所と読み込み方を選ぶ (TypeScript)

置き場所配布物での扱い読み込み方
src/assets/ から importハッシュ付きの名前で dist/ に出力され、実行ファイルに埋め込まれる。4KB 未満は JS の中に base64 で入るimport した URL
public/そのまま dist/ にコピーされ、埋め込まれる。Vite の出力表に出ない/photo.webp のような絶対パス
bundle.resources実行ファイルの外に別ファイルとして同梱されるconvertFileSrc(await resolveResource(...))

画面の部品として使う少数の画像は import、ギャラリーの写真や動画のように数が多い・大きいものは bundle.resources に出すのが目安です。インストーラーの大きさはどちらでもほぼ同じですが、埋め込んだファイルは読み込むたびに全体を展開して渡されるのに対し、asset プロトコルはファイルを直接読み、部分的な読み込みにも応じます。

import { convertFileSrc } from '@tauri-apps/api/core';
import { resolveResource } from '@tauri-apps/api/path';
import hero from './assets/hero.webp'; // ビルド時に dist/ へ出力される URL

document.querySelector<HTMLImageElement>('#hero')?.setAttribute('src', hero);

// bundle.resources の photos/ に同梱した画像を表示する
export async function showPhoto(img: HTMLImageElement, name: string) {
  const path = await resolveResource(`photos/${name}`); // インストール先の絶対パス
  img.src = convertFileSrc(path); // WebView から読める asset プロトコルの URL にする
}

tauri.conf.json では同梱と asset プロトコルを設定します。scope は同梱したフォルダーだけに絞ります。CSP を設定しているなら img-src に asset: と http://asset.localhost を足します。

{
  "app": {
    "security": {
      "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
      "assetProtocol": { "enable": true, "scope": ["$RESOURCE/photos/**"] }
    }
  },
  "bundle": {
    "resources": { "resources/photos/": "photos/" }
  }
}

assetProtocol.enable を true にすると tauri クレートの protocol-asset 機能が必要になり、tauri dev / tauri build が Cargo.toml に自動で足します。

3. アイコンとトレイ画像で膨らませない (Rust)

Rust 側の tauri::include_image! は、PNG を展開した画素のまま(1 ピクセル 4 バイト)実行ファイルに入れます。32×32 なら 4KB ですが、512×512 だと約 1MB です。トレイ用には小さい PNG を使い、大きな画像が要るときは PNG のまま埋め込んで実行時に展開する Image::from_bytes() にします(image-png 機能のぶんコードは増えます)。

use tauri::image::Image;
use tauri::tray::TrayIconBuilder;

// 展開後 32 x 32 x 4 = 4KB。パスは src-tauri からの相対
const TRAY_ICON: Image<'static> = tauri::include_image!("icons/32x32.png");

// PNG のまま埋め込み、使うときに展開する(features = ["image-png"])
fn about_logo() -> tauri::Result<Image<'static>> {
    Image::from_bytes(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/icons/128x128@2x.png")))
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            TrayIconBuilder::new().icon(TRAY_ICON).build(app)?;
            let _logo = about_logo()?; // 例: 「このアプリについて」の画面で使う
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

ウィンドウの既定のアイコンも同じ仕組みで埋め込まれます。Windows では bundle.icon の最初の .ico の先頭の画像、それ以外の OS では最初の .png が使われるので、配列の先頭は icons/32x32.png のような小さいものにしておきます。アイコン一式の作り方は アプリアイコンを画像から生成する を参照してください。

動作確認

npm run tauri build の前後で実行ファイルの大きさを比べます(測り方は perf-001)。手元の例では、3840×2400 の JPEG(1.5MB)を dist/ に入れると実行ファイルが約 1.5MB 増え、幅 1920 の WebP(75KB)に替えると増加分も約 75KB になりました。include_image! を 32px から 512px の画像に替えると、約 1MB 増えます。

bundle.resources の画像は、npm run tauri dev で開発者ツールの Network タブを開くと、Windows では次のような URL で読み込まれているのが見えます。

http://asset.localhost/C%3A%5Cwork%5Cmy-app%5Csrc-tauri%5Ctarget%5Cdebug%5Cphotos%5Cbeach.webp

よくあるエラーと対処法

  • resources の画像が表示されない(Network タブで 403): assetProtocol.enable が false か、scope が同梱先を含んでいません。$RESOURCE/photos/** のように同梱先に合わせます。CSP を設定しているなら img-src も確かめます。
  • 「The tauri dependency features on the Cargo.toml file does not match the allowlist defined under tauri.conf.json.」: asset プロトコルを有効にしたのに cargo build を直接実行しています。tauri build を使うか、Cargo.toml の tauri に protocol-asset を足します。
  • 「icon …/icon.png is not RGBA」: include_image! にパレット形式(減色した)PNG を渡しています。圧縮ツールで減色した PNG はここでは使えないので、RGBA で書き出したものを使います。
  • 「Provided Image path "…" doesn't exists」: include_image! のパスは src-tauri を基準に書きます。

OS ごとの違いと注意点

  • Windows: WebView2 は WebP・AVIF を表示できます。asset プロトコルの URL は http://asset.localhost/ で始まります。
  • macOS: WebView の対応は OS の版に従い、WebP は macOS 11 以降、AVIF は macOS 13 以降が目安です。bundle.macOS.minimumSystemVersion の既定は 10.13 なので、古い macOS も対象にするなら <picture> で PNG や JPEG を控えに置くか、最低バージョンを上げます。URL は asset://localhost/ です。
  • Linux: 対応形式はディストリビューションが提供する WebView の版で変わります。配布先と同じ環境で表示を確かめます。
  • .ico に入るのは 256px までで、.icns は大きめになりがちです。アイコンは 1 回生成すれば済むので、元画像の画質は落とさずに作ります。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する