Allow offsets in blobs larger than 2GB on 32 bit Chromium builds.
[chromium-blink-merge.git] / tools / resources / optimize-png-files.sh
blob69e0be6fd00ad27832eef78db6108f4fc2c73562
1 #!/bin/bash -i
2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 # The optimization code is based on pngslim (http://goo.gl/a0XHg)
7 # and executes a similar pipleline to optimize the png file size.
8 # The steps that require pngoptimizercl/pngrewrite/deflopt are omitted,
9 # but this runs all other processes, including:
10 # 1) various color-dependent optimizations using optipng.
11 # 2) optimize the number of huffman blocks.
12 # 3) randomize the huffman table.
13 # 4) Further optimize using optipng and advdef (zlib stream).
14 # Due to the step 3), each run may produce slightly different results.
16 # Note(oshima): In my experiment, advdef didn't reduce much. I'm keeping it
17 # for now as it does not take much time to run.
19 readonly ALL_DIRS="
20 ash/resources
21 chrome/android/java/res
22 chrome/app/theme
23 chrome/browser/resources
24 chrome/renderer/resources
25 component/resources
26 content/public/android/java/res
27 content/app/resources
28 content/renderer/resources
29 content/shell/resources
30 remoting/resources
31 ui/android/java/res
32 ui/resources
33 ui/chromeos/resources
34 ui/webui/resources/images
35 win8/metro_driver/resources
38 # Files larger than this file size (in bytes) will
39 # use the optimization parameters tailored for large files.
40 LARGE_FILE_THRESHOLD=3000
42 # Constants used for optimization
43 readonly DEFAULT_MIN_BLOCK_SIZE=128
44 readonly DEFAULT_LIMIT_BLOCKS=256
45 readonly DEFAULT_RANDOM_TRIALS=100
46 # Taken from the recommendation in the pngslim's readme.txt.
47 readonly LARGE_MIN_BLOCK_SIZE=1
48 readonly LARGE_LIMIT_BLOCKS=2
49 readonly LARGE_RANDOM_TRIALS=1
51 # Global variables for stats
52 TOTAL_OLD_BYTES=0
53 TOTAL_NEW_BYTES=0
54 TOTAL_FILE=0
55 CORRUPTED_FILE=0
56 PROCESSED_FILE=0
58 declare -a THROBBER_STR=('-' '\\' '|' '/')
59 THROBBER_COUNT=0
61 VERBOSE=false
63 # Echo only if verbose option is set.
64 function info {
65 if $VERBOSE ; then
66 echo $@
70 # Show throbber character at current cursor position.
71 function throbber {
72 info -ne "${THROBBER_STR[$THROBBER_COUNT]}\b"
73 let THROBBER_COUNT=$THROBBER_COUNT+1
74 let THROBBER_COUNT=$THROBBER_COUNT%4
77 # Usage: pngout_loop <file> <png_out_options> ...
78 # Optimize the png file using pngout with the given options
79 # using various block split thresholds and filter types.
80 function pngout_loop {
81 local file=$1
82 shift
83 local opts=$*
84 if [ $OPTIMIZE_LEVEL == 1 ]; then
85 for j in $(eval echo {0..5}); do
86 throbber
87 pngout -q -k1 -s1 -f$j $opts $file
88 done
89 else
90 for i in 0 128 256 512; do
91 for j in $(eval echo {0..5}); do
92 throbber
93 pngout -q -k1 -s1 -b$i -f$j $opts $file
94 done
95 done
99 # Usage: get_color_depth_list
100 # Returns the list of color depth options for current optimization level.
101 function get_color_depth_list {
102 if [ $OPTIMIZE_LEVEL == 1 ]; then
103 echo "-d0"
104 else
105 echo "-d1 -d2 -d4 -d8"
109 # Usage: process_grayscale <file>
110 # Optimize grayscale images for all color bit depths.
112 # TODO(oshima): Experiment with -d0 w/o -c0.
113 function process_grayscale {
114 info -ne "\b\b\b\b\b\b\b\bgray...."
115 for opt in $(get_color_depth_list); do
116 pngout_loop $file -c0 $opt
117 done
120 # Usage: process_grayscale_alpha <file>
121 # Optimize grayscale images with alpha for all color bit depths.
122 function process_grayscale_alpha {
123 info -ne "\b\b\b\b\b\b\b\bgray-a.."
124 pngout_loop $file -c4
125 for opt in $(get_color_depth_list); do
126 pngout_loop $file -c3 $opt
127 done
130 # Usage: process_rgb <file>
131 # Optimize rgb images with or without alpha for all color bit depths.
132 function process_rgb {
133 info -ne "\b\b\b\b\b\b\b\brgb....."
134 for opt in $(get_color_depth_list); do
135 pngout_loop $file -c3 $opt
136 done
137 pngout_loop $file -c2
138 pngout_loop $file -c6
141 # Usage: huffman_blocks <file>
142 # Optimize the huffman blocks.
143 function huffman_blocks {
144 info -ne "\b\b\b\b\b\b\b\bhuffman."
145 local file=$1
146 local size=$(stat -c%s $file)
147 local min_block_size=$DEFAULT_MIN_BLOCK_SIZE
148 local limit_blocks=$DEFAULT_LIMIT_BLOCKS
150 if [ $size -gt $LARGE_FILE_THRESHOLD ]; then
151 min_block_size=$LARGE_MIN_BLOCK_SIZE
152 limit_blocks=$LARGE_LIMIT_BLOCKS
154 let max_blocks=$size/$min_block_size
155 if [ $max_blocks -gt $limit_blocks ]; then
156 max_blocks=$limit_blocks
159 for i in $(eval echo {2..$max_blocks}); do
160 throbber
161 pngout -q -k1 -ks -s1 -n$i $file
162 done
165 # Usage: random_huffman_table_trial <file>
166 # Try compressing by randomizing the initial huffman table.
168 # TODO(oshima): Try adjusting different parameters for large files to
169 # reduce runtime.
170 function random_huffman_table_trial {
171 info -ne "\b\b\b\b\b\b\b\brandom.."
172 local file=$1
173 local old_size=$(stat -c%s $file)
174 local trials_count=$DEFAULT_RANDOM_TRIALS
176 if [ $old_size -gt $LARGE_FILE_THRESHOLD ]; then
177 trials_count=$LARGE_RANDOM_TRIALS
179 for i in $(eval echo {1..$trials_count}); do
180 throbber
181 pngout -q -k1 -ks -s0 -r $file
182 done
183 local new_size=$(stat -c%s $file)
184 if [ $new_size -lt $old_size ]; then
185 random_huffman_table_trial $file
189 # Usage: final_comprssion <file>
190 # Further compress using optipng and advdef.
191 # TODO(oshima): Experiment with 256.
192 function final_compression {
193 info -ne "\b\b\b\b\b\b\b\bfinal..."
194 local file=$1
195 if [ $OPTIMIZE_LEVEL == 2 ]; then
196 for i in 32k 16k 8k 4k 2k 1k 512; do
197 throbber
198 optipng -q -nb -nc -zw$i -zc1-9 -zm1-9 -zs0-3 -f0-5 $file
199 done
201 for i in $(eval echo {1..4}); do
202 throbber
203 advdef -q -z -$i $file
204 done
206 # Clear the current line.
207 if $VERBOSE ; then
208 printf "\033[0G\033[K"
212 # Usage: get_color_type <file>
213 # Returns the color type name of the png file. Here is the list of names
214 # for each color type codes.
215 # 0: grayscale
216 # 2: RGB
217 # 3: colormap
218 # 4: gray+alpha
219 # 6: RGBA
220 # See http://en.wikipedia.org/wiki/Portable_Network_Graphics#Color_depth
221 # for details about the color type code.
222 function get_color_type {
223 local file=$1
224 echo $(file $file | awk -F, '{print $3}' | awk '{print $2}')
227 # Usage: optimize_size <file>
228 # Performs png file optimization.
229 function optimize_size {
230 # Print filename, trimmed to ensure it + status don't take more than 1 line
231 local filename_length=${#file}
232 local -i allowed_length=$COLUMNS-11
233 local -i trimmed_length=$filename_length-$COLUMNS+14
234 if [ "$filename_length" -lt "$allowed_length" ]; then
235 info -n "$file|........"
236 else
237 info -n "...${file:$trimmed_length}|........"
240 local file=$1
242 advdef -q -z -4 $file
244 pngout -q -s4 -c0 -force $file $file.tmp.png
245 if [ -f $file.tmp.png ]; then
246 rm $file.tmp.png
247 process_grayscale $file
248 process_grayscale_alpha $file
249 else
250 pngout -q -s4 -c4 -force $file $file.tmp.png
251 if [ -f $file.tmp.png ]; then
252 rm $file.tmp.png
253 process_grayscale_alpha $file
254 else
255 process_rgb $file
259 info -ne "\b\b\b\b\b\b\b\bfilter.."
260 local old_color_type=$(get_color_type $file)
261 optipng -q -zc9 -zm8 -zs0-3 -f0-5 $file -out $file.tmp.png
262 local new_color_type=$(get_color_type $file.tmp.png)
263 # optipng may corrupt a png file when reducing the color type
264 # to grayscale/grayscale+alpha. Just skip such cases until
265 # the bug is fixed. See crbug.com/174505, crbug.com/174084.
266 # The issue is reported in
267 # https://sourceforge.net/tracker/?func=detail&aid=3603630&group_id=151404&atid=780913
268 if [[ $old_color_type == "RGBA" && $new_color_type == gray* ]] ; then
269 rm $file.tmp.png
270 else
271 mv $file.tmp.png $file
273 pngout -q -k1 -s1 $file
275 huffman_blocks $file
277 # TODO(oshima): Experiment with strategy 1.
278 info -ne "\b\b\b\b\b\b\b\bstrategy"
279 if [ $OPTIMIZE_LEVEL == 2 ]; then
280 for i in 3 2 0; do
281 pngout -q -k1 -ks -s$i $file
282 done
283 else
284 pngout -q -k1 -ks -s1 $file
287 if [ $OPTIMIZE_LEVEL == 2 ]; then
288 random_huffman_table_trial $file
291 final_compression $file
294 # Usage: process_file <file>
295 function process_file {
296 local file=$1
297 local name=$(basename $file)
298 # -rem alla removes all ancillary chunks except for tRNS
299 pngcrush -d $TMP_DIR -brute -reduce -rem alla $file > /dev/null 2>&1
301 if [ -f $TMP_DIR/$name -a $OPTIMIZE_LEVEL != 0 ]; then
302 optimize_size $TMP_DIR/$name
306 # Usage: optimize_file <file>
307 function optimize_file {
308 local file=$1
309 if $using_cygwin ; then
310 file=$(cygpath -w $file)
313 local name=$(basename $file)
314 local old=$(stat -c%s $file)
315 local tmp_file=$TMP_DIR/$name
316 let TOTAL_FILE+=1
318 process_file $file
320 if [ ! -e $tmp_file ] ; then
321 let CORRUPTED_FILE+=1
322 echo "$file may be corrupted; skipping\n"
323 return
326 local new=$(stat -c%s $tmp_file)
327 let diff=$old-$new
328 let percent=$diff*100
329 let percent=$percent/$old
331 if [ $new -lt $old ]; then
332 info "$file: $old => $new ($diff bytes: $percent%)"
333 cp "$tmp_file" "$file"
334 let TOTAL_OLD_BYTES+=$old
335 let TOTAL_NEW_BYTES+=$new
336 let PROCESSED_FILE+=1
337 else
338 if [ $OPTIMIZE_LEVEL == 0 ]; then
339 info "$file: Skipped"
340 else
341 info "$file: Unable to reduce size"
343 rm $tmp_file
347 function optimize_dir {
348 local dir=$1
349 if $using_cygwin ; then
350 dir=$(cygpath -w $dir)
353 for f in $(find $dir -name "*.png"); do
354 optimize_file $f
355 done
358 function install_if_not_installed {
359 local program=$1
360 local package=$2
361 which $program > /dev/null 2>&1
362 if [ "$?" != "0" ]; then
363 if $using_cygwin ; then
364 echo "Couldn't find $program. " \
365 "Please run cygwin's setup.exe and install the $package package."
366 exit 1
367 else
368 read -p "Couldn't find $program. Do you want to install? (y/n)"
369 [ "$REPLY" == "y" ] && sudo apt-get install $package
370 [ "$REPLY" == "y" ] || exit
375 function fail_if_not_installed {
376 local program=$1
377 local url=$2
378 which $program > /dev/null 2>&1
379 if [ $? != 0 ]; then
380 echo "Couldn't find $program. Please download and install it from $url ."
381 exit 1
385 # Check pngcrush version and exit if the version is in bad range.
386 # See crbug.com/404893.
387 function exit_if_bad_pngcrush_version {
388 local version=$(pngcrush -v 2>&1 | awk "/pngcrush 1.7./ {print \$3}")
389 local version_num=$(echo $version | sed "s/\.//g")
390 if [[ (1748 -lt $version_num && $version_num -lt 1773) ]] ; then
391 echo "Your pngcrush ($version) has a bug that exists from " \
392 "1.7.49 to 1.7.72 (see crbug.com/404893 for details)."
393 echo "Please upgrade pngcrush and try again"
394 exit 1;
398 function show_help {
399 local program=$(basename $0)
400 echo \
401 "Usage: $program [options] <dir> ...
403 $program is a utility to reduce the size of png files by removing
404 unnecessary chunks and compressing the image.
406 Options:
407 -o<optimize_level> Specify optimization level: (default is 1)
408 0 Just run pngcrush. It removes unnecessary chunks and perform basic
409 optimization on the encoded data.
410 1 Optimize png files using pngout/optipng and advdef. This can further
411 reduce addtional 5~30%. This is the default level.
412 2 Aggressively optimize the size of png files. This may produce
413 addtional 1%~5% reduction. Warning: this is *VERY*
414 slow and can take hours to process all files.
415 -r<revision> If this is specified, the script processes only png files
416 changed since this revision. The <dir> options will be used
417 to narrow down the files under specific directories.
418 -v Shows optimization process for each file.
419 -h Print this help text."
420 exit 1
423 if [ ! -e ../.gclient ]; then
424 echo "$0 must be run in src directory"
425 exit 1
428 if [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then
429 using_cygwin=true
430 else
431 using_cygwin=false
434 # The -i in the shebang line should result in $COLUMNS being set on newer
435 # versions of bash. If it's not set yet, attempt to set it.
436 if [ -z $COLUMNS ]; then
437 which tput > /dev/null 2>&1
438 if [ "$?" == "0" ]; then
439 COLUMNS=$(tput cols)
440 else
441 # No tput either... give up and just guess 80 columns.
442 COLUMNS=80
444 export COLUMNS
447 OPTIMIZE_LEVEL=1
448 # Parse options
449 while getopts o:r:h:v opts
451 case $opts in
453 COMMIT=$(git svn find-rev r$OPTARG | tail -1) || exit
454 if [ -z "$COMMIT" ] ; then
455 echo "Revision $OPTARG not found"
456 show_help
460 if [[ "$OPTARG" != 0 && "$OPTARG" != 1 && "$OPTARG" != 2 ]] ; then
461 show_help
463 OPTIMIZE_LEVEL=$OPTARG
466 VERBOSE=true
468 [h?])
469 show_help;;
470 esac
471 done
473 # Remove options from argument list.
474 shift $(($OPTIND -1))
476 # Make sure we have all necessary commands installed.
477 install_if_not_installed pngcrush pngcrush
478 exit_if_bad_pngcrush_version
480 if [ $OPTIMIZE_LEVEL -ge 1 ]; then
481 install_if_not_installed optipng optipng
483 if $using_cygwin ; then
484 fail_if_not_installed advdef "http://advancemame.sourceforge.net/comp-readme.html"
485 else
486 install_if_not_installed advdef advancecomp
489 if $using_cygwin ; then
490 pngout_url="http://www.advsys.net/ken/utils.htm"
491 else
492 pngout_url="http://www.jonof.id.au/kenutils"
494 fail_if_not_installed pngout $pngout_url
497 # Create tmp directory for crushed png file.
498 TMP_DIR=$(mktemp -d)
499 if $using_cygwin ; then
500 TMP_DIR=$(cygpath -w $TMP_DIR)
503 # Make sure we cleanup temp dir
504 #trap "rm -rf $TMP_DIR" EXIT
506 # If no directories are specified, optimize all directories.
507 DIRS=$@
508 set ${DIRS:=$ALL_DIRS}
510 info "Optimize level=$OPTIMIZE_LEVEL"
512 if [ -n "$COMMIT" ] ; then
513 ALL_FILES=$(git diff --name-only $COMMIT HEAD $DIRS | grep "png$")
514 ALL_FILES_LIST=( $ALL_FILES )
515 echo "Processing ${#ALL_FILES_LIST[*]} files"
516 for f in $ALL_FILES; do
517 if [ -f $f ] ; then
518 optimize_file $f
519 else
520 echo "Skipping deleted file: $f";
522 done
523 else
524 for d in $DIRS; do
525 if [ -d $d ] ; then
526 info "Optimizing png files in $d"
527 optimize_dir $d
528 info ""
529 elif [ -f $d ] ; then
530 optimize_file $d
531 else
532 echo "Not a file or directory: $d";
534 done
537 # Print the results.
538 echo "Optimized $PROCESSED_FILE/$TOTAL_FILE files in" \
539 "$(date -d "0 + $SECONDS sec" +%Ts)"
540 if [ $PROCESSED_FILE != 0 ]; then
541 let diff=$TOTAL_OLD_BYTES-$TOTAL_NEW_BYTES
542 let percent=$diff*100/$TOTAL_OLD_BYTES
543 echo "Result: $TOTAL_OLD_BYTES => $TOTAL_NEW_BYTES bytes" \
544 "($diff bytes: $percent%)"
546 if [ $CORRUPTED_FILE != 0 ]; then
547 echo "Warning: corrupted files found: $CORRUPTED_FILE"
548 echo "Please contact the author of the CL that landed corrupted png files"