/
cjpeg-dssim
executable file
·143 lines (117 loc) · 5.8 KB
/
cjpeg-dssim
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
#!/usr/bin/env bash
###############################################################################
#
# Intro:
# Automatically find the ideal JPEG quality setting for a JPEG image by
# calculating the output images dissimilarity from the input JPEG. This
# frees us from having to rely on the unstandardized quality integer.
#
# Installation instructions:
# Place this script anywhere in your $PATH. Requires Bash >= 4.x
#
# Required tools:
# * ImageMagick >= v.6
# * dssim - https://github.com/pornel/dssim
# * jpegoptim - https://github.com/tjko/jpegoptim
# * mozjpeg - https://github.com/mozilla/mozjpeg
#
# CLI usage example:
# cjpeg-dssim jpegoptim /path/to/input-image.jpg
#
# Supported encoders:
# * jpegoptim
# * mozjpeg
#
###############################################################################
###############################################################################
# SANITY CHECKS
###############################################################################
# Set locales to C (raw uninterpreted byte sequence) to avoid illegal byte sequence errors and invalid number errors
export LANG=C LC_NUMERIC=C LC_COLLATE=C
# Check for proper input parameters
# Filename and selected JPEG compressor should both be set
if [ -z $1 ] || [ -z $2 ]; then
echo "Please select a JPEG compression method and input JPEG"
elif [ -n $1 ] && [ -n $2 ]; then
JPEG_COMPRESSION_SELECTION="$1"
INPUTFILE="$2"
else
exit 1
fi
# Path and filename retrieval to save the output image
CLEANFILENAME=${INPUTFILE%.jp*g}
FILEEXTENSION=${INPUTFILE##*.}
CLEANPATH="${INPUTFILE%/*}"
# If the JPEG is in the same direcctory, empty the path variable
# Or if it is set, make sure the path has a trailing slash
if [ "$CLEANPATH" == "$INPUTFILE" ]; then
CLEANPATH=""
else
CLEANPATH="$CLEANPATH/"
fi
###############################################################################
# CONFIGURABLE RUNTIME VARIABLES
###############################################################################
# Initial JPEG quality and summand/subtrahend settings to start from
INITIAL_JPEG_QUALITY="80"
JPEG_QUALITY_SUMMAND_SUBTRAHEND="20"
# The dissimilarity range in which we accept a recompressed JPEG image
DSSIM_LOWER_BOUND="0.014250"
DSSIM_UPPER_BOUND="0.016500"
# How should the output image name be amended - Leave empty to overwrite
OUTPUTFILESUFFIX="_cjpeg_dssim"
###############################################################################
# MAIN PROGRAM
###############################################################################
main () {
define_encoder_toolbelt
local -i __current_jpeg_quality=$(optimize_quality_level ${INITIAL_JPEG_QUALITY} ${JPEG_QUALITY_SUMMAND_SUBTRAHEND})
$(eval ${JPEG_COMPRESSION_COMMAND} > "${CLEANPATH}${CLEANFILENAME##*/}${OUTPUTFILESUFFIX}".${FILEEXTENSION});
}
###############################################################################
# FUNCTIONS
###############################################################################
function define_encoder_toolbelt () {
# Define our toolbelt of JPEG compression commands and make them selectable from the CLI
case ${JPEG_COMPRESSION_SELECTION} in
"jpegoptim") JPEG_COMPRESSION_COMMAND="jpegoptim -q -p -f --max=\${__current_jpeg_quality} --strip-all --all-progressive --stdout \${INPUTFILE}";;
"mozjpeg") JPEG_COMPRESSION_COMMAND="mozjpeg -quality \${__current_jpeg_quality} \${INPUTFILE}";;
*) echo "Supported JPEG compression methods: jpegoptim | mozjpeg"; exit 1;;
esac
}
function optimize_quality_level () {
local -i __current_jpeg_quality=$1
local -i __current_jpeg_quality_summand_subtrahend=$2
local -i __iteration_counter="0"
local __current_dssim_score="0"
# While the result of the current run is either too similar (ecouraging stronger compression) or too different (already too many artifacts visible), run the compression again while homing in on a proper quality setting for the current encoder
while ( (( $(echo "${__current_dssim_score} >= ${DSSIM_UPPER_BOUND}" | bc -l) )) || (( $(echo "${__current_dssim_score} < ${DSSIM_LOWER_BOUND}" | bc -l) )) ) && (( ${__iteration_counter}<7 )); do
# Retrieve the current dissimilarity score
__current_dssim_score=$(calculate_dissimilarity "${__current_jpeg_quality}")
# Define if we need to add or substract from the current JPEG quality (DRY)
if (( $(echo "${__current_dssim_score} < ${DSSIM_LOWER_BOUND}" | bc -l) )); then
local __arithmetic_operator="-"
elif (( $(echo "${__current_dssim_score} >= ${DSSIM_UPPER_BOUND}" | bc -l) )); then
local __arithmetic_operator="+"
fi
# Binary-search-esque approach to home in on an acceptable JPEG quality by halfing the summand/subtrahend
__current_jpeg_quality_summand_subtrahend=$(echo "scale=0; ${__current_jpeg_quality_summand_subtrahend}/2" | bc -l)
if (( ${__current_jpeg_quality_summand_subtrahend}==0 )); then
__current_jpeg_quality_summand_subtrahend="1"
fi
# Set the JPEG quality to the newly calculated value
__current_jpeg_quality=$(echo "scale=0; ${__current_jpeg_quality}${__arithmetic_operator}${__current_jpeg_quality_summand_subtrahend}" | bc -l)
(( __iteration_counter++ ))
done
echo ${__current_jpeg_quality}
}
function calculate_dissimilarity () {
local __current_jpeg_quality=$1
# Convert the original JPEG to PNG for DSSIM comparison
# Also base64 it so we can safely store its result in a variable without needing to write the file to disk
local __original_image_png_base64=$(convert "${INPUTFILE}" png:- | base64)
# Run the JPEG compressor, pipe its output to convert, create a PNG from the newly compressed JPEG and hand it to DSSIM for comparison - all without creating a file on disk to increase runtime performance
local __current_dissimilarity=$(eval ${JPEG_COMPRESSION_COMMAND} | convert - png:- | dssim <(echo "${__original_image_png_base64}" | base64 --decode) /dev/stdin | awk '{print $1}')
echo ${__current_dissimilarity}
}
main