Post

写真保存形式をjpgからwebpに切り替えた話

写真保存形式をjpgからwebpに切り替えた話

はじめに

これまで、写真の保存形式としては主にjpg(JPEG)を使っていました。扱いやすく、ほとんどの環境で問題なく開くことができるため、特に疑問も持たずに長年使い続けていたのです。

ところが最近、「webp」という画像形式を見かける機会が増えてきました。Googleが開発した形式で、主にWebの高速表示を目的としているそうです。少し気になったので、自分の環境でも試してみることにしました。

webp形式のメリット

調べてみたところ、webpにはいくつかの魅力的な特長があることがわかりました。

  • ファイルサイズが小さい:同じ画質で保存した場合でも、jpgより圧縮率が高く、容量がかなり抑えられます。
  • 画質も良好:圧縮による劣化が目立ちにくく、自然な見た目を保てます。
  • 透過対応:pngと同様に背景を透明にできるため、用途の幅が広がります。
  • アニメーション対応:gifの代替としても利用可能です。

対応状況も年々改善されており、主要なブラウザやモダンなOSではほとんど問題なく再生・表示が可能になってきています。

Macでのwebp対応状況

私が特に驚いたのは、macOSがwebp形式をいつの間にか標準でサポートしていたという点です。

Finderでのサムネイル表示やQuick Look(スペースキーによるプレビュー)も対応しており、Preview.appでも問題なく開けました。普段使っている画像ビューアと変わらない使い心地で、非常にスムーズでした。

jpgからwebpへの変換自動化

brewに入っているので、以下のコマンドでOKです。

1
2
brew install webp
cwebp -q 80 input.jpg -o output.webp

一つ一つやるのは大変なので既存のjpgファイルを一括でwebpに変換してみようと思い、自分で変換スクリプトを作成します。 Claude Codeでささっと。AIが作ると絵文字が多くなるのはなんでなんでしょうね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/bin/bash

# JPG/PNG/JPEGファイルをWebPに再帰的に変換するスクリプト
# 使用方法: ./image-to-webp-converter.sh /path/to/directory [quality]

# 引数チェック
if [ $# -lt 1 ]; then
    echo "使用方法: $(basename "$0") <ディレクトリパス> [品質(0-100、デフォルト:70)]"
    echo "例: $(basename "$0") '/home/user/pictures with spaces' 85"
    echo "例: $(basename "$0") /home/user/pictures 85"
    exit 1
fi

TARGET_DIR="$1"
QUALITY="${2:-70}"

# ディレクトリの存在チェック
if [ ! -d "$TARGET_DIR" ]; then
    echo "エラー: ディレクトリ '$TARGET_DIR' が存在しません"
    exit 1
fi

# cwebpコマンドの存在チェック
if ! command -v cwebp &> /dev/null; then
    echo "エラー: cwebpコマンドが見つかりません"
    echo "WebPツールをインストールしてください"
    exit 1
fi

# 品質の範囲チェック
if ! [[ "$QUALITY" =~ ^[0-9]+$ ]] || [ "$QUALITY" -lt 0 ] || [ "$QUALITY" -gt 100 ]; then
    echo "エラー: 品質は0-100の数値で指定してください"
    exit 1
fi

# ディレクトリ全体のサイズを計算する関数
get_directory_size() {
    local dir="$1"
    if command -v du >/dev/null 2>&1; then
        # macOSとLinux両対応
        local size_kb=$(du -sk "$dir" 2>/dev/null | cut -f1)
        if [ -n "$size_kb" ] && [ "$size_kb" -gt 0 ]; then
            echo $((size_kb * 1024))  # KBをBに変換
        else
            echo "0"
        fi
    else
        echo "0"
    fi
}

# ファイルサイズを人間が読みやすい形式に変換する関数
human_readable_size() {
    local size=$1
    if [ "$size" -ge 1073741824 ]; then
        echo "$(echo "scale=2; $size / 1073741824" | bc -l) GB"
    elif [ "$size" -ge 1048576 ]; then
        echo "$(echo "scale=2; $size / 1048576" | bc -l) MB"
    elif [ "$size" -ge 1024 ]; then
        echo "$(echo "scale=2; $size / 1024" | bc -l) KB"
    else
        echo "${size} B"
    fi
}

# 圧縮率を計算する関数
calculate_compression_ratio() {
    local original=$1
    local compressed=$2
    if [ "$original" -gt 0 ]; then
        echo "$(echo "scale=1; (($original - $compressed) * 100) / $original" | bc -l)"
    else
        echo "0"
    fi
}

echo "=== 画像 to WebP 変換スクリプト ==="
echo "対象ディレクトリ: $TARGET_DIR"
echo "品質設定: $QUALITY"
echo "対応形式: JPG, JPEG, PNG"
echo "==============================="

# 指定されたディレクトリに移動して処理を実行
cd "$TARGET_DIR" || {
    echo "エラー: ディレクトリ '$TARGET_DIR' に移動できません"
    exit 1
}

# 変換前のディレクトリサイズを取得
echo ""
echo "📁 変換前のディレクトリサイズを計算中..."
initial_dir_size=$(get_directory_size ".")
echo "変換前ディレクトリサイズ: $(human_readable_size $initial_dir_size)"
echo ""

# 一時ファイルでカウンターとサイズを管理
temp_dir=$(mktemp -d)
trap "rm -rf '$temp_dir'" EXIT  # スクリプト終了時に一時ディレクトリを削除

converted_count_file="$temp_dir/converted_count"
failed_count_file="$temp_dir/failed_count"
total_original_size_file="$temp_dir/total_original_size"
total_converted_size_file="$temp_dir/total_converted_size"

echo "0" > "$converted_count_file"
echo "0" > "$failed_count_file"
echo "0" > "$total_original_size_file"
echo "0" > "$total_converted_size_file"

# findコマンドで再帰的に検索・変換(JPG、JPEG、PNGに対応)
while IFS= read -r -d '' file; do
    # 出力ファイル名を生成(拡張子をwebpに変更)
    output="${file%.*}.webp"
    
    # 元ファイルのサイズを取得
    if [ -f "$file" ]; then
        original_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0")
    else
        original_size=0
    fi
    
    # ファイル形式を判定してcwebpのオプションを調整
    file_ext=$(echo "${file##*.}" | tr '[:upper:]' '[:lower:]')
    
    # PNGの場合は可逆圧縮オプションも提供
    if [ "$file_ext" = "png" ]; then
        # PNGの場合、透明度がある可能性があるのでアルファチャンネルを保持
        cwebp_options="-q $QUALITY -alpha_cleanup"
        # 可逆圧縮を試してみて、サイズが小さくなるかチェック
        temp_lossless="${output}.temp_lossless"
        cwebp -lossless "$file" -o "$temp_lossless" 2>/dev/null
        
        if [ -f "$temp_lossless" ]; then
            lossless_size=$(stat -f%z "$temp_lossless" 2>/dev/null || stat -c%s "$temp_lossless" 2>/dev/null || echo "0")
        else
            lossless_size=999999999  # 失敗した場合は非常に大きい値
        fi
    else
        cwebp_options="-q $QUALITY"
        lossless_size=999999999
    fi
    
    echo "変換中: $file ($(human_readable_size $original_size))"
    
    # WebPに変換
    if cwebp $cwebp_options "$file" -o "$output" 2>/dev/null; then
        # 変換後ファイルのサイズを取得
        if [ -f "$output" ]; then
            converted_size=$(stat -f%z "$output" 2>/dev/null || stat -c%s "$output" 2>/dev/null || echo "0")
        else
            converted_size=0
        fi
        
        # PNGの場合、可逆圧縮の方が小さければそちらを使用
        if [ "$file_ext" = "png" ] && [ "$lossless_size" -lt "$converted_size" ] && [ -f "$temp_lossless" ]; then
            mv "$temp_lossless" "$output"
            converted_size=$lossless_size
            compression_type="可逆圧縮"
        else
            compression_type="品質$QUALITY"
            [ -f "$temp_lossless" ] && rm "$temp_lossless"
        fi
        
        # 圧縮率を計算
        compression_ratio=$(calculate_compression_ratio $original_size $converted_size)
        
        # 変換成功時は元ファイルを削除
        if rm "$file"; then
            echo "✓ 変換完了: $file -> $output ($compression_type)"
            echo "  サイズ: $(human_readable_size $original_size) -> $(human_readable_size $converted_size) (${compression_ratio}% 圧縮)"
            
            # カウンターを一時ファイルに記録
            converted_count=$(cat "$converted_count_file")
            total_original_size=$(cat "$total_original_size_file")
            total_converted_size=$(cat "$total_converted_size_file")
            
            echo $((converted_count + 1)) > "$converted_count_file"
            echo $((total_original_size + original_size)) > "$total_original_size_file"
            echo $((total_converted_size + converted_size)) > "$total_converted_size_file"
        else
            echo "✗ 元ファイルの削除に失敗: $file"
            # 削除に失敗した場合は変換ファイルも削除
            [ -f "$output" ] && rm "$output"
            [ -f "$temp_lossless" ] && rm "$temp_lossless"
            
            failed_count=$(cat "$failed_count_file")
            echo $((failed_count + 1)) > "$failed_count_file"
        fi
    else
        echo "✗ 変換失敗: $file"
        # 失敗時は不完全なWebPファイルがあれば削除
        [ -f "$output" ] && rm "$output"
        [ -f "$temp_lossless" ] && rm "$temp_lossless"
        
        failed_count=$(cat "$failed_count_file")
        echo $((failed_count + 1)) > "$failed_count_file"
    fi
    
    echo "" # 空行で区切り
done < <(find . -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \) -print0)

# 一時ファイルから最終結果を読み取り
converted_count=$(cat "$converted_count_file")
failed_count=$(cat "$failed_count_file")
total_original_size=$(cat "$total_original_size_file")
total_converted_size=$(cat "$total_converted_size_file")

echo "==============================="
echo "変換完了: ${converted_count}ファイル"
echo "変換失敗: ${failed_count}ファイル"

# 変換後のディレクトリサイズを取得
echo ""
echo "📁 変換後のディレクトリサイズを計算中..."
final_dir_size=$(get_directory_size ".")

echo ""
echo "=== 最終結果 ==="
echo "変換前ディレクトリサイズ: $(human_readable_size $initial_dir_size)"
echo "変換後ディレクトリサイズ: $(human_readable_size $final_dir_size)"

if [ $converted_count -gt 0 ]; then
    echo ""
    echo "=== 詳細なサイズ比較・圧縮効果 ==="
    echo "【変換対象ファイルのみ】"
    echo "変換前総サイズ: $(human_readable_size $total_original_size)"
    echo "変換後総サイズ: $(human_readable_size $total_converted_size)"
    
    if [ $total_original_size -gt 0 ]; then
        saved_size=$((total_original_size - total_converted_size))
        total_compression_ratio=$(calculate_compression_ratio $total_original_size $total_converted_size)
        
        # ディレクトリ全体での節約サイズ
        if [ $initial_dir_size -gt 0 ] && [ $final_dir_size -gt 0 ]; then
            dir_saved_size=$((initial_dir_size - final_dir_size))
            dir_compression_ratio=$(calculate_compression_ratio $initial_dir_size $final_dir_size)
        else
            dir_saved_size=0
            dir_compression_ratio="0"
        fi
        
        echo ""
        echo "🎉 圧縮効果 🎉"
        echo "【変換ファイルのみ】"
        echo "  節約されたサイズ: $(human_readable_size $saved_size)"
        echo "  圧縮率: ${total_compression_ratio}%"
        
        echo "【ディレクトリ全体】"
        echo "  ディレクトリ容量削減: $(human_readable_size $dir_saved_size)"
        echo "  ディレクトリ圧縮率: ${dir_compression_ratio}%"
        echo "  🎯 合計ストレージ節約効果: $(human_readable_size $dir_saved_size)"
        
        # パーセンテージでの節約効果も表示
        if [ $total_original_size -gt 0 ]; then
            savings_percentage=$(echo "scale=1; ($saved_size * 100) / $total_original_size" | bc -l)
            echo "  変換ファイル削減率: ${savings_percentage}%"
        fi
        
        if [ $initial_dir_size -gt 0 ] && [ $dir_saved_size -gt 0 ]; then
            dir_savings_percentage=$(echo "scale=1; ($dir_saved_size * 100) / $initial_dir_size" | bc -l)
            echo "  ディレクトリ全体削減率: ${dir_savings_percentage}%"
        fi
    fi
fi
echo "=============================="

使い方

1
2
chmod u+x jpg2webp.sh
./jpg2webp ./images

圧縮率や画質の検証

実際に、100枚ほどのjpg画像をwebp形式に変換して比較してみたところ:

  • 平均で40〜60%ほどのファイルサイズ削減が見られました。
  • 肉眼で見た限り、ほとんど画質の違いは感じませんでした。
  • 特に風景写真や物撮り画像などは、webpとの相性がとても良い印象です。

という感じで圧縮効果がかなりあります。古い端末だと見えないかもしれないので、まだ完全に置き換えるのは難しそうですが、、、

画像圧縮ツールの一つであるjpeg mini proとの併用も検討しています。たとえば、まずjpgをjpeg mini proで圧縮してからwebpに変換すると、さらに無駄のないファイルサイズが実現できそうです。

まとめ

今回、jpgからwebpへの移行を実際に行ってみて、非常に満足しています。

  • 表示速度が向上し、Webページのパフォーマンスにも好影響
  • ストレージ容量の節約にもつながる
  • 自動化スクリプトの作成によって画像管理の効率化も実現

まだすべての用途に適しているとは言い切れませんが、「画像を軽量化したい」「でも画質は保ちたい」と考えている方には、webpはとてもおすすめの形式だと思います。

This post is licensed under CC BY 4.0 by the author.