PNG形式とJPEG形式の画像ファイルは画像としての情報を完全に保持しながらファイルサイズをある程度減らすことができる場合がある。
画像のファイルサイズを減らすと、容量が節約できる他、Webページに貼り付けてある場合は表示速度を高速化でき、データ転送量やサーバ負荷を減らすことにもつながる。
コマンドは作業を自動化するのに便利なので、大量の画像を一括で処理するのも楽にできる。記事の最後では、扱ったツールを用いて指定ディレクトリ以下の画像をまとめて最適化する、複数CPU(コア含む)対応スクリプトも貼り付けている。
PNGファイルの最適化
PNG形式のファイルはOptiPNGと呼ばれるツールを用いて簡単に最適化を行うことができる。[1]Debian/Ubuntuでは “optipng” という名前のパッケージを選択してインストールすることで導入できる。
このツールは処理対象のファイルを直接変更(上書き)して最適化し、ファイルサイズを削減する。
PNGファイルの最適化例
(高レベルの最適化) $ optipng -o7 /path/to/input.png (最大限の最適化・非常に時間がかかる) $ optipng -o7 -zm1-9 /path/to/input.png
どの程度ファイルサイズが削減できるかは処理対象のファイルが最適化されていない度合いによるため、数値で表現することは難しいが、出力内に削減率が表示されるので、実行後に確認することはできる。
下の例ではGIMPで適当にスクリーンショットを撮って圧縮レベルを最高の “9” にしてエクスポートしたファイルを長時間かけて最大限に最適化しているが、元々1.6MiB強のファイルサイズが7.3KiB程度しか減っていない。
$ optipng -o7 -zm1-9 test.png ** Processing: test.png 1920x1080 pixels, 3x8 bits/pixel, RGB Input IDAT size = 1682354 bytes Input file size = 1684871 bytes Trying: zc = 9 zm = 9 zs = 1 f = 4 IDAT size = 1677293 Selecting parameters: zc = 9 zm = 9 zs = 1 f = 4 IDAT size = 1677293 Output IDAT size = 1677293 bytes (5061 bytes decrease) Output file size = 1677350 bytes (7521 bytes = 0.45% decrease)
下の例では同じスクリーンショットをGIMPで圧縮レベルを “1” [2]にして保存したものをOptiPNGの既定(省略可)の最適化レベルである-o2
指定で最適化した場合(処理時間は短い)。
$ optipng -o2 test2.png ** Processing: test2.png 1920x1080 pixels, 3x8 bits/pixel, RGB Input IDAT size = 1898435 bytes Input file size = 1901264 bytes Trying: zc = 9 zm = 8 zs = 0 f = 5 IDAT size = 1704003 zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 1682354 Selecting parameters: zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 1682354 Output IDAT size = 1682354 bytes (216081 bytes decrease) Output file size = 1682411 bytes (218853 bytes = 11.51% decrease)
JPEGファイルの最適化
JPEGライブラリとツール
JPEG画像を扱うライブラリには “libjpeg” と呼ばれるものがあるが、x86,x86_64,ARMのアーキテクチャ上でCPUのSIMD命令を活用して効率良く(高速に)処理を行うための “libjpeg-turbo” というものがあり、本家版と互換性があるためにGNU/Linuxでも広く使われている。
この “libjpeg-turbo” をMozillaプロジェクトが独自に改善し、既存のJPEGデコードプログラムで正しくデコードできる形で圧縮率を向上させるための “Mozilla JPEG Encoder Project (mozjpeg)” プロジェクトが存在し、バージョン3.0時点ではlibjpeg-turboとのABI互換性もある。[3]
libjpeg系のライブラリ(libjpeg-turboやmozjpegも含む)にはライブラリの他に幾つかのツール(コマンド)群が付属し、JPEG形式と他形式との変換や、既存のJPEGファイルの最適化などが行える。
mozjpegの導入例
mozjpegは2015年春時点ではディストリのパッケージとしては利用できないことが多いが、libjpeg-turboとのABI互換性などから、今後ディストリのパッケージがこちらに置き換えられていくことは考えられる。
以下はソースの取得から(手動)インストールまでの例で、バージョン3.1時点では標準で/opt/mozjpeg/
以下にインストールされる。SIMD命令を用いる場合はyasmもしくはnasmも必要。[4]これらのソフトウェアはGNU/Linuxでは同名のディストリのパッケージとして利用可能なことが多い(Debian/Ubuntuでは両方とも利用できる)。
(2015/6/6)mozjpeg-[バージョン]-release-source.tar.gz
をダウンロードする形に修正し、バージョン部分も3.1に更新
$ wget https://github.com/mozilla/mozjpeg/releases/download/v3.1/mozjpeg-3.1-release-source.tar.gz -O - | tar zxf - $ cd mozjpeg/ [mozjpeg]$ ./configure [mozjpeg]$ make [mozjpeg]$ sudo make install-strip
(2015/6/6)“Source code” と書かれたファイルをダウンロードする場合はconfigure
スクリプトはautoreconf
コマンドで生成する必要があるのでautoconfとautomakeに加えlibtool(パッケージ名も同じ)もインストールされている必要があり、libtoolが未インストールだと以下のエラーで失敗する。
[mozjpeg]$ autoreconf --install configure.ac:22: error: possibly undefined macro: AC_PROG_LIBTOOL If this token and others are legitimate, please use m4_pattern_allow. See the Autoconf documentation. autoreconf: /usr/bin/autoconf failed with exit status: 1
mozjpegを用いたJPEGファイルの最適化例
付属ツールに含まれるjpegtran
というコマンドを用いることで既存のJPEGファイルを無劣化[5]で最適化し、ファイルサイズを小さくできる。こちらもどの程度縮むかは処理対象のファイルが最適化されていない度合いによる[6]が、例えばデジタルカメラで撮影した写真などが沢山ある場合などにはmozjpegのjpegtran
を用いて全て最適化していくことで合計でそれなりの効果は得られる。
(2015/5/10)脚注の圧縮率に関する記述を修正
オリジナルのlibjpeg-turboでも同様の操作が可能だが、最適化に-optimize
オプションが必要で、効果はmozjpegより弱い。
下はjpegtran
で最適化を行う書式。将来mozjpegがディストリのパッケージとしてlibjpeg-turboを置き換えるようになった場合は “/opt/mozjpeg/bin/” の部分は不要になる。
(コメントとその他の情報を全て保持/ファイルサイズが最も大きい) $ /opt/mozjpeg/bin/jpegtran -copy all -outfile /path/to/outfile.jpg /path/to/infile.jpg (コメント以外の情報を削除) $ /opt/mozjpeg/bin/jpegtran -outfile /path/to/outfile.jpg /path/to/infile.jpg (コメントとその他の情報を全て削除/ファイルサイズが最も小さい) $ /opt/mozjpeg/bin/jpegtran -copy none -outfile /path/to/outfile.jpg /path/to/infile.jpg
コメントに関する既定の動作は “コメント以外の情報を削除” [7]となっており、デジタルカメラなどが保存するEXIFの情報は消えてしまう。EXIFデータを残したいのか、捨てたい(ファイルサイズ削減目的含む)のか、目的に応じて指定を選択する。
指定ディレクトリ以下のPNG/JPEG画像を全て最適化するスクリプト
下はoptipng
とjpegtran
を用いて引数に指定したディレクトリ以下の画像ファイルを全て最適化するスクリプト(CPU数に応じた並列化対応)。これらがインストールされている前提なので注意。最終更新日時は保持する。
optipngjpeg-recursive.py
ライセンス:zlib#! /usr/bin/python
# optipngjpeg-recursive.py - optimize JPEG and PNG files
# (C) 2015 kakurasan
# Licensed under zlib
from __future__ import print_function
import multiprocessing as mp
try:
import queue
except:
import Queue as queue
import subprocess
import shutil
import sys
import os
class Config:
jpegtran = '/opt/mozjpeg/bin/jpegtran' # path to jpegtran
jpegtran_copy_markers = 'all' # 'none', 'comments', or 'all'
optipng = '/usr/bin/optipng' # path to optipng
optipng_opts = ('-o2',)
# optipng_opts = ('-o7',)
# optipng_opts = ('-o7', '-zm1-9')
def do_work (f, root):
f_lower = f.lower ()
if f_lower.endswith ('.jpg') or f_lower.endswith ('.jpeg') or f_lower.endswith ('.jpe'):
dotpos = f.rfind ('.')
name, ext = f[:dotpos], f[dotpos:]
infile = os.path.join (root, f)
tmpfile = os.path.join (root, '{0}-min{1}'.format (name, ext))
try:
subprocess.check_call ((Config.jpegtran, '-copy', Config.jpegtran_copy_markers, '-outfile', tmpfile, infile), stderr = subprocess.STDOUT)
except subprocess.CalledProcessError:
print ('FAILED "{0}"'.format (infile))
return
st_before = os.stat (infile)
st_after = os.stat (tmpfile)
os.utime (tmpfile, (st_before.st_atime, st_before.st_mtime))
os.unlink (infile)
shutil.move (tmpfile, infile)
if st_after.st_size == st_before.st_size:
print ('"{0}" is already optimized.'.format (infile))
else:
print ('optimized "{0}" ({1} bytes = {2:.2f}% decrease)'.format (infile, st_before.st_size - st_after.st_size, (st_before.st_size - st_after.st_size) * 100.0 / st_before.st_size))
elif f_lower.endswith ('.png'):
infile = os.path.join (root, f)
args = [Config.optipng, '-quiet']
for a in Config.optipng_opts:
args.append (a)
args.append (infile)
st_before = os.stat (infile)
try:
subprocess.check_call (args, stderr = subprocess.STDOUT)
except subprocess.CalledProcessError:
print ('FAILED "{0}"'.format (infile))
return
st_after = os.stat (infile)
os.utime (infile, (st_before.st_atime, st_before.st_mtime))
if st_after.st_size == st_before.st_size:
print ('"{0}" is already optimized.'.format (infile))
else:
print ('optimized "{0}" ({1} bytes = {2:.2f}% decrease)'.format (infile, st_before.st_size - st_after.st_size, (st_before.st_size - st_after.st_size) * 100.0 / st_before.st_size))
def worker (q):
while True:
try:
f, root = q.get (True, 1)
do_work (f, root)
q.task_done ()
except queue.Empty:
break
if __name__ == '__main__':
if len (sys.argv) < 2:
sys.exit ('USAGE: {0} [DIR...]'.format (__file__))
q = mp.JoinableQueue ()
for topdir in sys.argv[1:]:
for root, _, files in os.walk (topdir):
for f in files:
q.put ((f, root))
for i in range (mp.cpu_count ()):
p = mp.Process (target = worker, args = (q,))
p.start ()
q.join ()
使用例$ /path/to/optipngjpeg-recursive.py mozjpeg-3.0/ optimized "mozjpeg-3.0/testimages/testorig.jpg" (286 bytes = 4.96% decrease) optimized "mozjpeg-3.0/doc/html/tab_s.png" (60 bytes = 32.61% decrease) optimized "mozjpeg-3.0/doc/html/sync_on.png" (29 bytes = 3.43% decrease) "mozjpeg-3.0/doc/html/nav_f.png" is already optimized. optimized "mozjpeg-3.0/doc/html/ftv2pnode.png" (66 bytes = 28.82% decrease) optimized "mozjpeg-3.0/doc/html/ftv2ns.png" (106 bytes = 27.32% decrease) optimized "mozjpeg-3.0/doc/html/ftv2link.png" (202 bytes = 27.08% decrease) optimized "mozjpeg-3.0/doc/html/ftv2folderclosed.png" (113 bytes = 18.34% decrease) optimized "mozjpeg-3.0/doc/html/doxygen.png" (1409 bytes = 37.28% decrease) optimized "mozjpeg-3.0/doc/html/search/search_r.png" (3 bytes = 0.49% decrease) optimized "mozjpeg-3.0/doc/html/search/mag_sel.png" (136 bytes = 24.16% decrease) Unsupported JPEG data precision 12 FAILED "mozjpeg-3.0/testimages/testorig12.jpg" "mozjpeg-3.0/testimages/testimgari.jpg" is already optimized. ... optimized "mozjpeg-3.0/doc/html/bdwn.png" (28 bytes = 19.05% decrease) "mozjpeg-3.0/doc/html/search/search_m.png" is already optimized. optimized "mozjpeg-3.0/doc/html/search/close.png" (55 bytes = 20.15% decrease)
- OptiPNG 0.7.5
- mozjpeg 3.0, 3.1
- autoconf 2.69
- automake 1.14.1
- Python 2.7.8, 3.4.2
djpeg
に最適化前後それぞれのファイルを指定すると(デコードの)出力は同じものになる-copy comments
オプション相当