#!/bin/bash
#
#  Script to strip ICC profiles from all png images and to recompress them
#  afterwards via optipng and advdef
#
#  Run-time requirements: ImageMagick, OptiPNG, Advdef (part of AdvanceCOMP)
#
#  Copyright (C) 2004 by Crossbow/Miyo <miyo@iki.fi>
#  Copyright (C) 2005 by Isaac Clerencia <isaac@warp.es>
#  * (Ruby-ification and some features)
#  Copyright (C) 2008 by Iris Morelle <shadowm2006@gmail.com>
#                     and Fabio Pedretti <fabio.ped@libero.it>
#  * (De-ruby-ification and some other features)
#  * (Added advdef support)
#  * (Don't use .stripped files anymore)
#  * (Improved final statistics)
#  Copyright (C) 2009 by Nils Kneuper <crazy-ivanovic@gmx.net>
#  * (Added support for parallization)
#
#  Part of the Battle for Wesnoth Project <https://www.wesnoth.org>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 2 or,
#  at your option any later version. This program is distributed in the
#  hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING
#  file for more details.
#

####### VARIABLES #######
current_file=""
# Default optimization process nice
opti_nice=19
max_number_threads=1
total_original_size=0
total_savings_size=0
total_savings_filecount=0
new_file_list="new_file_list.tmp"
old_file_list="old_file_list.tmp"
broken_file_list="broken_file_list.tmp"
stat_args="-c %s"

if [ -e "/proc/cpuinfo" ] && [ `grep processor /proc/cpuinfo | wc -l` -gt 1 ]; then
		max_number_threads=`grep processor /proc/cpuinfo | wc -l`
fi

if [ `man stat | grep 'BSD' | wc -l` -gt 0 ]; then
	stat_args="-f %z"
fi

####### PROCEDURES #######

report_absent_tool()
{
	echo "$1 is not present in PATH. $(basename ${0}) requires it in order to work properly."
	if [ -n "$2" ]; then
		echo "You can obtain $1 at <${2}>."
	fi
	exit -1
}

user_int()
{
	if [ -n "${current_file}" ]; then
		#echo "Removing temp file ${current_file}.new..."
		rm -f ${current_file}.new
	fi

	echo
	echo "Stopped by user request."
	echo

	print_statistics

	exit
}

# this part will *only* compress and not remove the temp files
# those have to be removed by calling get_statistics_and_delete_temp_files
optimize_imgfile()
{
	current_file=${1}

	if ! [ -e ${1} ]; then
		echo "ERROR: image file ${1} not found"
		return -1
	fi

	echo "* processing ${1} (nice = ${2})..."

	nice -n $2 convert -strip -define png:color-type=6 ${1} ${1}.new
	nice -n $2 optipng -q -o5 -nb -nc -np ${1}.new
	# Sometimes lower compression level gives a better compression
	nice -n $2 advdef -z -4 ${1}.new > /dev/null
	nice -n $2 advdef -z -3 ${1}.new > /dev/null
	nice -n $2 advdef -z -2 ${1}.new > /dev/null
	nice -n $2 advdef -z -1 ${1}.new > /dev/null

	echo ${1}.new >> ${new_file_list}
	echo ${1} >> ${old_file_list}
}

# gather statistics and remove the temp files
get_statistics_and_delete_temp_files()
{
	current_file=${1}

	if ! [ -e ${1} ]; then
		echo "ERROR: image file ${1} not found"
		return -1
	fi

	echo "* processing ${1} (nice = ${2})..."

	if [ ! -z `cat ${broken_file_list} | cut -f1 -d " " | grep ${1}` ]; then
		echo "ERROR: temporary file ${1}.new has pixel differences compared to the original, not using this optimized file."
		rm -f ${1}.new
	elif [ -e ${1} ] && [ -e ${1}.new ]; then
		# Compare output size with original and print per-file statistics
		local old_png_size=`stat ${stat_args} ${1} 2> /dev/null`
		local new_png_size=`stat ${stat_args} ${1}.new 2> /dev/null`

		if [ "$old_png_size" -gt "$new_png_size" ]; then
			local png_savings_size=$((${old_png_size}-${new_png_size}))
			total_original_size=$((${total_original_size}+${old_png_size}))
			total_savings_size=$((${total_savings_size}+${png_savings_size}))
			total_savings_filecount=$((1+${total_savings_filecount}))
			mv -f ${1}.new ${1}
			echo "  saved: ${png_savings_size} bytes, total saved: $((${total_savings_size}/1024)) KiB"
		else
			rm -f ${1}.new
			#no need to mention it when nothing actually changed...
			#echo "  already optimized"
		fi
	else
		echo "ERROR: temporary file ${1}.new disappeared mysteriously"
	fi
}

print_statistics()
{
	# Print overall statistics
	if [ "$total_savings_filecount" -gt 0 ]; then
		echo "Overall statistics (only for files with a smaller recompressed size):"
		echo "  Original size: $((${total_original_size}/1024)) KiB on ${total_savings_filecount} files"
		echo " Optimized size: $(((${total_original_size}-${total_savings_size})/1024)) KiB"
		echo "   Total saving: $((${total_savings_size}/1024)) KiB = $((${total_savings_size}*100/${total_original_size}))% decrease"
	else
		echo "No files were recompressed."
	fi
}

####### ACTUAL SCRIPT #######

# Parse command line parameters
while [ "${1}" != "" ]; do
	if [ "${1}" = "--nice" ] || [ "${1}" = "-n" ]; then
		opti_nice=$2
		shift 2
	elif [ "${1}" = "--threads" ]; then
		max_number_threads=$2
		shift 2
	elif [ "${1}" = "--help" ] || [ "${1}" = "-h" ]; then
		cat << EOSTREAM
Wesnoth automatic PNG optimization helper script
Part of the Battle for Wesnoth project <www.wesnoth.org>

Usage:
  $(basename ${0}) [{--nice|n} <value>] [{--threads} <value>] [{--help|-h}] [<file>...]

If no files are given on command line, all PNG files within current working
directory and below are processed.

Switches:
  --help / -h   Displays this help text.

  --nice / -n   Sets the process nice <value> under which the optimization
				shall be done. If not specified, 19 (the lowest priority) is
				assumed by default.

  --threads     Run script with <value> worker threads. If not specified, the
				amount of CPUs detected will be used.

This tool requires OptiPNG (http://optipng.sourceforge.net/),
Advdef (http://advancemame.sourceforge.net/comp-readme.html) and ImageMagick's
convert utility (http://www.imagemagick.org/script/convert.php) to be installed
and available in PATH.
EOSTREAM
		exit 0
	else
		filelist="${filelist} ${1}"
		shift
	fi
done

# FIXME: disabled because it isn't working for Ivanovic.
# Probe optipng
which optipng &> /dev/null ||
	report_absent_tool optipng 'http://optipng.sourceforge.net/'
# Probe IM's convert
which convert &>/dev/null ||
	report_absent_tool convert 'http://www.imagemagick.org'
# Probe advdef
which advdef &> /dev/null ||
	report_absent_tool advdef 'http://advancemame.sourceforge.net/comp-readme.html'

# Set-up a trap to avoid leaving orphan tempfiles behind.
trap user_int HUP INT TERM

if test -z "$filelist"; then
	filelist=$(find -iname "*.png")
fi

# work with a parallized joblist
echo "*************************"
echo "* compressing the files *"
echo "*************************"
for f in $filelist
do
	while [ $(jobs -pr | wc -l) -ge $max_number_threads ]
	do
		# max number of threads reached, wait for a quarter second...
		sleep 0.25
	done
	#when here, we can do our normal optimization run
	optimize_imgfile $f $opti_nice &
done
# wait till all threads are done before going on in the script
wait

# compare compressed images pixel-by-pixel to detect possible corruption
echo -ne "\n"
echo "****************************************************"
echo "* comparing original and new images pixel-by-pixel *"
echo "****************************************************"
$(dirname ${0})/compare_images.py ${old_file_list} ${new_file_list} > ${broken_file_list}

if [ $? -ne 0 ]; then
	echo "****************************************************"
	echo "* Unable to compare compressed and original images *"
	echo "****************************************************"
else
	echo "list of broken files saved to: ${broken_file_list}"
fi

# collect statistics and remove temp files
# this is in a seperate part because the stats would not be correct when
# run with several threads
echo -ne "\n"
echo "********************************************"
echo "* creating statistics and moving files now *"
echo "********************************************"
for f in $filelist
do
	get_statistics_and_delete_temp_files $f $opti_nice
done

rm -f ${old_file_list}
rm -f ${new_file_list}
rm -f ${broken_file_list}

print_statistics

exit 0
