aboutsummaryrefslogtreecommitdiff
path: root/tools/spherize
blob: 23ed4a8c3bd4e4935a8b87a2ea139c4b719ead92 (plain)
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#!/usr/bin/env bash
# 
# Developed by Fred Weinhaus 6/20/2009 .......... revised 4/25/2015
#
# ------------------------------------------------------------------------------
# 
# Licensing:
# 
# Copyright © Fred Weinhaus
# 
# My scripts are available free of charge for non-commercial use, ONLY.
# 
# For use of my scripts in commercial (for-profit) environments or 
# non-free applications, please contact me (Fred Weinhaus) for 
# licensing arrangements. My email address is fmw at alink dot net.
# 
# If you: 1) redistribute, 2) incorporate any of these scripts into other 
# free applications or 3) reprogram them in another scripting language, 
# then you must contact me for permission, especially if the result might 
# be used in a commercial or for-profit environment.
# 
# My scripts are also subject, in a subordinate manner, to the ImageMagick 
# license, which can be found at: http://www.imagemagick.org/script/license.php
# 
# ------------------------------------------------------------------------------
# 
####
#
# USAGE: spherize [-d diameter(s)] [-a amp] [-z zoom] [-b bcolor ] [-s] [-t] infile outfile
# USAGE: spherize [-h or -help]
# 
# OPTIONS:
# 
# -d        diameter(s)         diameters; comma separate x and y diameters;
#                               integers>0 and less than or equal to image
#                               dimensions; if only one value provided, 
#                               it will be used for both; default is the 
#                               image "width,height"
# -a        amp                 distortion amplification factor; float>=0;
#                               default=0
# -z        zoom                zoom factor; scales the input image on the 
#                               sphere; float>=0; default=1
# -b        bcolor              background color; any valid IM color or 
#                               "none" for transparent or "image" for input 
#                               image background; default=black
# -s                            make result with both diameters equal to the  
#                               smaller of the x or y diameters
# -t                            trim image to diameters
# 
###
# 
# NAME: SPHERIZE 
#  
# PURPOSE: To warp an image onto a (hemi-)sphere.
# 
# DESCRIPTION: UNROTATE warps an image onto a (hemi-)sphere and views it 
# orthographically. The x and y diameters can be specified along with the 
# distortion amplification and input image zoom. The background around  
# the sphere is also user controlled. This is a limited version of my 
# bubblewarp script that is not limited by the use of the IM -fx function 
# and therefore is very fast.
# 
# 
# Arguments: 
# 
# -d diameter(s) --- DIAMETER(S) is a comma separate list of the desired 
# x and y diameters of the sphere. If only one value is provided, then it 
# will be used for both diameters. Values are integers greater than zero 
# and equal to or smaller than the dimensions of the image. The default 
# values are the size of the image. Thus if the image is square, then a  
# spherical effect will be produced. But if the image is not square, then 
# elliptical effect will be produced.
# 
# -a amp --- AMP is the distortion amplification factor. Values are floats 
# greater than or equal to zero. The default=1. Values larger than one will 
# make more distortion and values less than one will make less distortion.
#
# -z zoom --- ZOOM is the zoom factor for the input image before warping it 
# onto the sphere. Values are floats greater than or equal to zero. The 
# default=1. Values larger than one will magnify the input image on the sphere. 
# Values less than one will minify the input image on the sphere.
# 
# -b bcolor ... BCOLOR is the color for the background outside the sphere. 
# Any valid IM color is allowed or one may specify "none" for a transparent 
# background or "image" to have the input image as the background. The default 
# is black.
# 
# -s ... Make the result with both diameters equal to the smaller of the 
# x or y diameters.
# 
# -t ... Trim the output image to the diameters.
# 
# NOTE: This script is a limited version of my bubblewarp script, but is 
# much faster, since it does not use -fx.
# 
# NOTE: This script requires IM 6.5.3-9 or higher due to the use of 
# -compose distort. Many thanks to Anthony Thyssen for his hard work 
# developing it.
# 
# CAVEAT: No guarantee that this script will work on all platforms, 
# nor that trapping of inconsistent parameters is complete and 
# foolproof. Use At Your Own Risk. 
# 
######
#
# set default values; 
dx=""					# x diameter
dy=""					# y diameter
amp=1					# distortion amplification
zoom=1					# input image zoom factor
bcolor="black"			# background color; none for transparency; or "image"
symmetry="no"			# sphere with both diameters equal to min of x and y diameters
trim="no"				# trim output to diameters

# set directory for temporary files
dir="."    # suggestions are dir="." or dir="/tmp"

# set up functions to report Usage and Usage with Description
PROGNAME=`type $0 | awk '{print $3}'`  # search for executable on path
PROGDIR=`dirname $PROGNAME`            # extract directory of program
PROGNAME=`basename $PROGNAME`          # base name of program
usage1() 
	{
	echo >&2 ""
	echo >&2 "$PROGNAME:" "$@"
	sed >&2 -e '1,/^####/d;  /^###/g;  /^#/!q;  s/^#//;  s/^ //;  4,$p' "$PROGDIR/$PROGNAME"
	}
usage2() 
	{
	echo >&2 ""
	echo >&2 "$PROGNAME:" "$@"
	sed >&2 -e '1,/^####/d;  /^######/g;  /^#/!q;  s/^#*//;  s/^ //;  4,$p' "$PROGDIR/$PROGNAME"
	}

# function to report error messages
errMsg()
	{
	echo ""
	echo $1
	echo ""
	usage1
	exit 1
	}

# function to test for minus at start of value of second part of option 1 or 2
checkMinus()
	{
	test=`echo "$1" | grep -c '^-.*$'`   # returns 1 if match; 0 otherwise
    [ $test -eq 1 ] && errMsg "$errorMsg"
	}

# test for correct number of arguments and get values
if [ $# -eq 0 ]
	then
	# help information
	echo ""
	usage2
	exit 0
elif [ $# -gt 12 ]
	then
	errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---"
else
	while [ $# -gt 0 ]
		do
		# get parameters
		case "$1" in
	  -h|-help)    # help information
				   echo ""
				   usage2
				   ;;
			-d)    # diameters
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID DIAMETERS SPECIFICATION ---"
				   checkMinus "$1"
				   diameters=$1
				   test=`echo "$1" | tr "," " " | wc -w`
				   [ $test -eq 1 -o $test -gt 2 ] && errMsg "--- INCORRECT NUMBER OF DIAMETERS SUPPLIED ---"
				   diameters=`expr "$1" : '\([0-9]*,[0-9]*\)'`
				   [ "$diameters" = "" ] && errMsg "--- DIAMETERS=$diameters MUST BE A PAIR OF NON-NEGATIVE INTEGERS SEPARATED BY A COMMA ---"
				   diameters="$1,"
				   dx=`echo "$diameters" | cut -d, -f1`
				   dy=`echo "$diameters" | cut -d, -f2`
				   # further testing done later
				   ;;
			-a)    # amp
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID AMPLIFICATION SPECIFICATION ---"
				   checkMinus "$1"
				   amp=`expr "$1" : '\([.0-9]*\)'`
				   [ "$amp" = "" ] && errMsg "--- AMPLIFICATION=$amp MUST BE A NON-NEGATIVE FLOATING POINT VALUE (with no sign) ---"
				   ;;
			-z)    # zoom
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID ZOOM SPECIFICATION ---"
				   checkMinus "$1"
				   zoom=`expr "$1" : '\([.0-9]*\)'`
				   [ "$zoom" = "" ] && errMsg "--- ZOOM=$zoom MUST BE A NON-NEGATIVE FLOATING POINT VALUE (with no sign) ---"
				   ;;
			-b)    # get bcolor
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID BACKGROUND COLOR SPECIFICATION ---"
				   checkMinus "$1"
				   bcolor="$1"
				   ;;
			-s)    # set symmetry
				   symmetry="yes"
				   ;;
			-t)    # set trim
				   trim="yes"
				   ;;
			 -)    # STDIN and end of arguments
				   break
				   ;;
			-*)    # any other - argument
				   errMsg "--- UNKNOWN OPTION ---"
				   ;;
			*)     # end of arguments
				   break
				   ;;
		esac
		shift   # next option
	done
	#
	# get infile and outfile
	infile="$1"
	outfile="$2"
fi

# test that infile provided
[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED"

# test that outfile provided
[ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED"

# setup temporaries
tmpA1="$dir/spherize_A_$$.mpc"
tmpA2="$dir/spherize_A_$$.cache"
tmpB1="$dir/spherize_B_$$.mpc"
tmpB2="$dir/spherize_B_$$.cache"
tmpF1="$dir/spherize_T_$$.mpc"
tmpF2="$dir/spherize_T_$$.cache"
tmpM1="$dir/spherize_M_$$.mpc"
tmpM2="$dir/spherize_M_$$.cache"
tmpX1="$dir/spherize_X_$$.mpc"
tmpX2="$dir/spherize_X_$$.cache"
tmpY1="$dir/spherize_Y_$$.mpc"
tmpY2="$dir/spherize_Y_$$.cache"
trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpF1 $tmpF2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2;" 0
trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2; exit 1" 1 2 3 15
trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2; exit 1" ERR


# read the input image into the TMP cached image.
convert -quiet "$infile" +repage "$tmpA1" ||
	errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---"

# test for minimum IM version required
# IM 6.5.3.9 or higher to conform to new -compose distort
im_version=`convert -list configure | \
	sed '/^LIB_VERSION_NUMBER */!d; s//,/;  s/,/,0/g;  s/,0*\([0-9][0-9]\)/\1/g' | head -n 1`
[ "$im_version" -lt "06050309" ] && errMsg "--- REQUIRES IM VERSION 6.5.3-9 OR HIGHER ---"

# get image dimensions
ww=`convert $tmpA1 -ping -format "%w" info:`
hh=`convert $tmpA1 -ping -format "%h" info:`

# set default diameters
if [ "$dx" = "" ]; then
	dx=$ww
fi
if [ "$dy" = "" ]; then
	dy=$hh
fi

# compute min diameter
dm=`convert xc: -format "%[fx:min($dx,$dy)]" info:`
if [ "$symmetry" = "yes" ]; then
	dx=$dm
	dy=$dm
fi

# test diameters
[ $dx -eq 0 -o $dx -gt $ww ] && errMsg "--- X DIAMETER MUST BE GREATER THAN 0 AND LESS OR EQUAL TO IMAGE WIDTH ---"
[ $dy -eq 0 -o $dy -gt $hh ] && errMsg "--- Y DIAMETER MUST BE GREATER THAN 0 AND LESS OR EQUAL TO IMAGE HEIGHT ---"


# scale radius so that that image fits circle or ellipse at any diameter with zoom=1
# invert zoom so increases with larger values
# use absolute coord displacement
# xamount=(rx/zoom)*(ww/dx)=ww/(2*zoom); where rx = x radius
xamount=`convert xc: -format "%[fx:$ww/(2*$zoom+quantumscale)]" info:`
yamount=`convert xc: -format "%[fx:$hh/(2*$zoom+quantumscale)]" info:`


# create radial gradient
convert -size ${dm}x${dm} radial-gradient: -negate $tmpF1

# convert circular gradient to elliptical
if [ $dx -ne $dy ]; then
	convert $tmpF1 -resize ${dx}x${dy}! $tmpF1
fi

# create mask
convert $tmpF1 -negate -gravity center -background black -extent ${ww}x${hh} -threshold 0 $tmpM1

# set up amplify for distortion
if [ "$amp" = "1" ]; then
	amplify=""
else
	amplify="-evaluate pow $amp"
fi

# compute spherize function F=(2/pi)*asin(rd)/rd
convert $tmpF1 \( +clone -function arcsin '2,0,2,0' $amplify \) \
	-compose divide -composite $tmpF1

# set up x displacement
# compute x gradient increasing with i
convert -size ${dy}x${dx} gradient: -rotate 90 $tmpX1

# compute u*v=xd*asin(rd)/rd; u=gradient 0,1; v=asin(rd)/rd 
# but where want xd in range -1,1; so must use (2*u-1)
# then modify to 0.5*u*v+0.5 to recenter at mid gray
# old fx method: 0.5*((2*u-1)*v)+0.5 = u*v - 0.5*v + 0.5
# convert $tmpX1 $tmpF1 -monitor -fx "u*v-0.5*v+0.5" $tmpX1

if [ "$im_version" -ge "06050403" ]; then
	convert $tmpX1 $tmpF1 +swap \
	-compose Mathematics -set option:compose:args "1,0,-0.5,0.5" \
	-composite $tmpX1
else

	# multiply arcsin(r)/(r) by abs of gradient relative to midgray as zero
	# abs is computed as -solarize 50% -level 50,0%
	# abs has V shape and ranges 0 to 1
	# then scale so that range is 0.5 to 1
	#
	# convert gradient to sign of gradient 
	# sign is computed as -threshold 50%
	# thus zero (for neg) on left and one (for pos) on right
	# use sign as mask to separate the right (pos) side, 
	# then negate mask and arcsin-absgrad product 
	# to get the left (neg) side and then combine with right side
	convert $tmpX1 $tmpF1 \
		\( -clone 0 -solarize 50% -level 50,0% \) \
		\( -clone 1,2 -compose multiply -composite -function polynomial "0.5,0.5" \) \
		\( -clone 0 -threshold 50% \) \
		\( -clone 3,4 -compose multiply -composite \) \
		\( -clone 3,4 -negate -compose multiply -composite \) \
		-delete 0-4 -compose plus -composite \
		$tmpX1
fi

# y displacement
if [ $dx -eq $dy ]; then
	# map is symmetric so just rotate x displacement
	convert $tmpX1 -rotate 90 $tmpY1
else
	# compute y gradient increasing with j
	convert -size ${dx}x${dy} gradient: -negate $tmpY1
	
	if [ "$im_version" -ge "06050403" ]; then
		convert $tmpY1 $tmpF1 +swap \
		-compose Mathematics -set option:compose:args "1,0,-0.5,0.5" \
		-composite $tmpY1
	else
		convert $tmpY1 $tmpF1 \
			\( -clone 0 -solarize 50% -level 50,0% \) \
			\( -clone 1,2 -compose multiply -composite -function polynomial "0.5,0.5" \) \
			\( -clone 0 -threshold 50% \) \
			\( -clone 3,4 -compose multiply -composite \) \
			\( -clone 3,4 -negate -compose multiply -composite \) \
			-delete 0-4 -compose plus -composite \
			$tmpY1
	fi
fi

# displace image (center relative)
# absolute coord displacement
convert $tmpA1 $tmpX1 $tmpY1 \
	-gravity center -compose distort \
	-set option:compose:args ${xamount}x${yamount} -composite \
	$tmpB1

# set up trim
if [ "$trim" = "yes" ]; then
	trimming="-gravity center -crop ${dx}x${dy}+0+0 +repage"
else
	trimming=""
fi

# composite mask
if [ "$bcolor" = "none" ]; then
	convert $tmpB1 $tmpM1 \
		-alpha off -compose copy_opacity -composite $trimming "$outfile"
elif [ "$bcolor" = "image" -a "$symmetry" = "no" ]; then
	convert $tmpB1 $trimming "$outfile"
elif [ "$bcolor" = "image" -a "$symmetry" = "yes" ]; then
	convert $tmpA1 $tmpB1 $tmpM1 -compose over -composite $trimming "$outfile"
else
	convert \( $tmpB1 $tmpM1 \
		-gravity center -alpha off -compose copy_opacity -composite \) \
		\( -size ${ww}x${hh} xc:"$bcolor" \) +swap \
		-gravity center -compose over -composite $trimming "$outfile"
fi

exit 0