Created
September 22, 2011 12:40
-
-
Save slivero/1234664 to your computer and use it in GitHub Desktop.
Cylinderize an image (Saved here as a backup of original http://www.fmwconcepts.com/imagemagick/cylinderize/index.php)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Developed by Fred Weinhaus 5/1/2009 .......... revised 4/12/2010 | |
# | |
# USAGE: cylinderize [-m mode] [-r radius] [-l length] [-w wrap] [-f fcolor] [-a angle] [-p pitch] [-n narrow] [-v vpmethod] [-b bgcolor] [-t] infile outfile | |
# USAGE: cylinderize [-h or -help] | |
# | |
# OPTIONS: | |
# | |
# -m mode mode of orientation for cylinder axis; options are | |
# horizontal (or h) or vertical (or v); default=vertical | |
# -r radius radius of cylinder; float>0; default is one quarter | |
# of image width or height depending upon mode. | |
# -l length length of cylinder; lenght>0; default=width or height | |
# depending upon mode and adjusted for the cylinder pitch angle. | |
# -w wrap percentage of image to wrap about the cylinder; float; | |
# 10<=wrap<=100; default=50 | |
# -f fcolor fill color for portion of cylinder not covered by the | |
# image; default=none (transparent) | |
# -a angle rotation angle in degrees about cylinder axis; | |
# best used when wrap=full; float; -360<=angle<=360; | |
# default=0 | |
# -p pitch pitch (tilt) angle of cylinder; float; -90<pitch<90; | |
# default=0 | |
# -n narrow narrow is the percent ratio of the bottom-to-top or | |
# right-to-left radii used to simulate perspective tapering; | |
# floats>=0; default=100 means same size radii | |
# -v vpmethod virtual-pixel method; default=black | |
# -b bgcolor background color for the case when vpmethod=background; | |
# default=black | |
# -t trim background | |
# | |
### | |
# | |
# NAME: CYLINDERIZE | |
# | |
# PURPOSE: To apply a cylinder distortion to an image. | |
# | |
# DESCRIPTION: CYLINDERIZE applies a cylinder distortion to an image so | |
# that the image is wrapped about the cylinder. The image can be wrapped | |
# about any percentage of the cylinder from 10 to 100 percent. If the wrap | |
# is less than 100%, then the cylinder will be colored to fill the remaining | |
# amount. The cylinder can also be pitched (tilted). | |
# | |
# | |
# ARGUMENTS: | |
# | |
# -m mode ... MODE specifies the orientation for the cylinder axis. The | |
# choices are horizontal (or h) or vertical (or v). The default is vertical. | |
# | |
# -r radius ... RADIUS is the radius of the cylinder in pixels. The values are | |
# floats>0. The default is one quarter of the image width or height depending | |
# upon the mode. | |
# | |
# -l length ... Length is the length of the cylinder along its axis in pixels. | |
# The values are floats with length>0. The default is either the width or height | |
# depending upon mode and adjusted for the pitch of the cylinder. If a length | |
# is provided, then the cylinder length will not be adjusted for pitch, but | |
# the ends will still be adjusted for pitch. | |
# | |
# -w wrap ... WRAP is the percentage of the cylinder circumference that is wrapped | |
# with the image. Values are floats such that 10<=wrap<=100. Default=50. | |
# | |
# -f fcolor ... FCOLOR is the fill color to put on the remainder of the cylinder | |
# that is not covered by the image. Any valid IM color is allowed. The default | |
# is none (for transparent). | |
# | |
# -a angle ... ANGLE is the rotation the cylinder about its axis. This is | |
# best used when wrap=full. The values are floats with -360<=angle<=360. | |
# The default=0. | |
# | |
# -p pitch ... PITCH (tilt) angle of the cylinder. Values are floats with | |
# -90<pitch<90. Positive values move the top or left side towards the user | |
# depending upon mode. The default=0. | |
# | |
# -n narrow ... NARROW is the percent ratio of the bottom-to-top or | |
# right-to-left radii used to simulate perspective tapering. Values are | |
# floats>=0. The default=100 means same size radii. | |
# | |
# -v vpmethod ... VPMETHOD is the virtual-pixel method to use. Any valid IM | |
# virtual-pixel may be used. The background will be transparent if | |
# vpmethod=transparent. The default is black. | |
# | |
# -b bgcolor ... BGCOLOR is the background color to use when vpmethod=background. | |
# Any valid IM color is allowed. The background will be transparent if | |
# bgcolor=none and vpmethod=background. The default is black. | |
# | |
# -t ... TRIM the background. | |
# | |
# NOTE: Thanks to Anthony Thyssen for the concept and basic equation for | |
# achieving the tilted cylinder effect. Thanks to Glen Fiebich for the | |
# suggestion to allow any amount of wrap and to be able to fill the | |
# remainder with color or transparency and still rotate the cylinder. | |
# | |
# 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 | |
mode="vertical" # vertical or horizontal | |
wrap=50 # percentage of cylinder to wrap with image | |
fcolor="none" # fill color for remainder of cylinder | |
radius="" # default=1/4 width or height | |
length="" # default=width or height | |
angle=0 # cylinder rotation | |
pitch=0 # cylinder pitch (tilt) | |
narrow=100 # tapering due to perspective effect | |
vpmethod="black" # virtual pixel method | |
bgcolor="black" # background color for vp method=background | |
trim="no" # trim background: yes or no | |
# 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 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" | |
} | |
usage2() | |
{ | |
echo >&2 "" | |
echo >&2 "$PROGNAME:" "$@" | |
sed >&2 -n '/^######/q; /^#/!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 23 ] | |
then | |
errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" | |
else | |
while [ $# -gt 0 ] | |
do | |
# get parameter values | |
case "$1" in | |
-h|-help) # help information | |
echo "" | |
usage2 | |
exit 0 | |
;; | |
-m) # get mode | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID MODE SPECIFICATION ---" | |
checkMinus "$1" | |
mode="$1" | |
mode=`echo "$mode" | tr "[:upper:]" "[:lower:]"` | |
case "$mode" in | |
horizontal) mode="horizontal" ;; | |
h) mode="horizontal" ;; | |
vertical) mode="vertical" ;; | |
v) mode="vertical" ;; | |
*) errMsg "--- MODE=$mode IS NOT A VALID VALUE ---" ;; | |
esac | |
;; | |
-r) # get radius | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID RADIUS SPECIFICATION ---" | |
checkMinus "$1" | |
radius=`expr "$1" : '\([.0-9]*\)'` | |
[ "$radius" = "" ] && errMsg "--- RADIUS=$radius MUST BE A NON-NEGATIVE FLOAT ---" | |
radtestA=`echo "$radius <= 0" | bc` | |
[ $radtestA -eq 1 ] && errMsg "--- RADIUS=$radius MUST BE A FLOAT GREATER THAN 0 ---" | |
;; | |
-l) # get length | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID LENGTH SPECIFICATION ---" | |
checkMinus "$1" | |
length=`expr "$1" : '\([.0-9]*\)'` | |
[ "$length" = "" ] && errMsg "--- LENGTH=$length MUST BE A NON-NEGATIVE FLOAT ---" | |
lengthtestA=`echo "$length <= 0" | bc` | |
[ $lengthtestA -eq 1 ] && errMsg "--- LENGTH=$length MUST BE A FLOAT GREATER THAN 0 ---" | |
;; | |
-w) # get wrap | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID WRAP SPECIFICATION ---" | |
#checkMinus "$1" | |
wrap=`expr "$1" : '\([.0-9]*\)'` | |
[ "$wrap" = "" ] && errMsg "--- WRAP=$wrap MUST BE A NON-NEGATIVE FLOAT ---" | |
wraptestA=`echo "$wrap < 10" | bc` | |
wraptestB=`echo "$wrap > 100" | bc` | |
[ $wraptestA -eq 1 -o $wraptestB -eq 1 ] && errMsg "--- WRAP=$wrap MUST BE A FLOAT BETWEEN 10 AND 100 ---" | |
;; | |
-f) # get fcolor | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID FILL COLOR SPECIFICATION ---" | |
checkMinus "$1" | |
fcolor="$1" | |
;; | |
-a) # get angle | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID ANGLE SPECIFICATION ---" | |
#checkMinus "$1" | |
angle=`expr "$1" : '\([-.0-9]*\)'` | |
[ "$angle" = "" ] && errMsg "--- ANGLE=$angle MUST BE A NON-NEGATIVE FLOAT ---" | |
angtestA=`echo "$angle < -360" | bc` | |
angtestB=`echo "$angle > 360" | bc` | |
[ $angtestA -eq 1 -o $angtestB -eq 1 ] && errMsg "--- ANGLE=$angle MUST BE A FLOAT BETWEEN -360 AND 360 ---" | |
;; | |
-p) # get pitch | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID PITCH SPECIFICATION ---" | |
#checkMinus "$1" | |
pitch=`expr "$1" : '\([-.0-9]*\)'` | |
[ "$pitch" = "" ] && errMsg "--- PITCH=$pitch MUST BE A NON-NEGATIVE FLOAT ---" | |
pitchtestA=`echo "$pitch <= -90" | bc` | |
pitchtestB=`echo "$pitch >= 90" | bc` | |
[ $pitchtestA -eq 1 -o $pitchtestB -eq 1 ] && errMsg "--- PITCH=$pitch MUST BE A FLOAT BETWEEN -90 AND 90 ---" | |
;; | |
-n) # get narrow | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID NARROW SPECIFICATION ---" | |
checkMinus "$1" | |
narrow=`expr "$1" : '\([.0-9]*\)'` | |
[ "$narrow" = "" ] && errMsg "--- NARROW=$narrow MUST BE A NON-NEGATIVE FLOAT ---" | |
;; | |
-v) # get vpmethod | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID VIRTUAL-PIXEL METHOD SPECIFICATION ---" | |
checkMinus "$1" | |
vpmethod="$1" | |
;; | |
-b) # get bgcolor | |
shift # to get the next parameter | |
# test if parameter starts with minus sign | |
errorMsg="--- INVALID BACKGROUND COLOR SPECIFICATION ---" | |
checkMinus "$1" | |
bgcolor="$1" | |
;; | |
-t) # get 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" | |
# set directory for temporary files | |
dir="." # suggestions are dir="." or dir="/tmp" | |
# set up temporary images | |
tmpA="$dir/cylinderize_$$.mpc" | |
tmpB="$dir/cylinderize_$$.cache" | |
tmp0="$dir/cylinderize_0_$$.png" | |
tmp1="$dir/cylinder_1_$$.png" | |
trap "rm -f $tmpA $tmpB $tmp0 $tmp1; exit 0" 0 | |
trap "rm -f $tmpA $tmpB $tmp0 $tmp1; exit 1" 1 2 3 15 | |
# get image dimensions | |
width=`identify -ping -format %w $infile` | |
height=`identify -ping -format %h $infile` | |
if [ "$mode" = "vertical" ]; then | |
pwidth=`convert xc: -format "%[fx:100*$width/$wrap]" info:` | |
pheight=$height | |
elif [ "$mode" = "horizontal" ]; then | |
pwidth=$width | |
pheight=`convert xc: -format "%[fx:100*$height/$wrap]" info:` | |
fi | |
# set sign of angle for use below in sign of -roll arguments | |
sign=`convert xc: -format "%[fx:sign($angle)]" info:` | |
[ $sign -lt 0 ] && sign="-" || sign="+" | |
# read the input image into the TMP cached image. | |
if [ "$mode" = "vertical" ]; then | |
rollx=`convert xc: -format "%[fx:abs($angle)*$pwidth/360]" info:` | |
convert -quiet -regard-warnings "$infile" +repage \ | |
-gravity center -background ${fcolor} -extent ${pwidth}x${pheight} \ | |
-roll ${sign}${rollx}+0 "$tmpA" || | |
errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---" | |
elif [ "$mode" = "horizontal" ]; then | |
rolly=`convert xc: -format "%[fx:abs($angle)*$pheight/360]" info:` | |
convert -quiet -regard-warnings "$infile" +repage \ | |
-gravity center -background ${fcolor} -extent ${pwidth}x${pheight} \ | |
-roll +0${sign}${rolly} "$tmpA" || | |
errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---" | |
fi | |
: ' | |
# test that length<=width or height | |
if [ "$mode" = "vertical" -a "$length" != "" ]; then | |
test=`echo "$length <= $height" | bc` | |
[ $test -ne 1 ] && errMsg "--- LENGTH MUST NOT BE GREATER THAN HEIGHT ---" | |
elif [ "$mode" = "horizontal" -a "$length" != "" ]; then | |
test=`echo "$length <= $width" | bc` | |
[ $test -ne 1 ] && errMsg "--- LENGTH MUST NOT BE GREATER THAN WIDTH ---" | |
fi | |
' | |
# compute center coords | |
xc=`convert xc: -format "%[fx:($pwidth)/2]" info:` | |
yc=`convert xc: -format "%[fx:($pheight)/2]" info:` | |
# get arcsin factor depending upon wrap | |
factor=`convert xc: -format "%[fx:(1/pi)]" info:` | |
# get default radius and length | |
if [ "$mode" = "vertical" -a "$radius" = "" ]; then | |
radius=`convert xc: -format "%[fx:$width/4]" info:` | |
elif [ "$mode" = "horizontal" -a "$radius" = "" ]; then | |
radius=`convert xc: -format "%[fx:$height/4]" info:` | |
fi | |
if [ "$mode" = "vertical" -a "$length" = "" ]; then | |
length1=`convert xc: -format "%[fx:$height]" info:` | |
elif [ "$mode" = "horizontal" -a "$length" = "" ]; then | |
length1=`convert xc: -format "%[fx:$width]" info:` | |
else | |
length1=$length | |
fi | |
# get tilted dimensions | |
if [ "$length" = "" ]; then | |
length1=`convert xc: -format "%[fx:$length1*cos(pi*$pitch/180)]" info:` | |
fi | |
radius1=`convert xc: -format "%[fx:$radius*sin(pi*$pitch/180)]" info:` | |
if [ "$mode" = "vertical" ]; then | |
height1=`convert xc: -format "%[fx:$length1+$radius1]" info:` | |
else | |
width1=`convert xc: -format "%[fx:$length1+$radius1]" info:` | |
fi | |
# set up for transparency | |
if [ "$vpmethod" = "transparent" -o "$bgcolor" = "none" ]; then | |
channels="-channel rgba -alpha on" | |
else | |
channels="" | |
fi | |
# set up for background color | |
if [ "$vpmethod" = "background" ]; then | |
backgroundcolor="-background $bgcolor" | |
elif [ "$vpmethod" = "black" ]; then | |
backgroundcolor="-background black" | |
elif [ "$vpmethod" = "white" ]; then | |
backgroundcolor="-background white" | |
elif [ "$vpmethod" = "gray" ]; then | |
backgroundcolor="-background gray" | |
elif [ "$vpmethod" = "transparent" ]; then | |
backgroundcolor="-background none" | |
else | |
backgroundcolor="" | |
fi | |
# process image | |
if [ "$mode" = "vertical" ]; then | |
# Slow method using fx | |
# convert $tmpA -virtual-pixel $vpmethod -fx \ | |
# "xd=(i-$xc)/$radius; $ffx xs=$xc*ffx+$xc; u.p{xs,j}" \ | |
# $outfile | |
# Equivalent displace map must be relative to i and range from 0 to 1, | |
# but slow fx method above is relative to $xc and | |
# ffx ranges from -1 to 1 (for wrap=half) | |
# Thus we must modify the conversion to a displament map | |
# from simply 0.5*(ffx-0.5) [which scales ffx from -1,1 to 0,1] | |
# to 0.5*(ffx+($xc-i)/$xc)+0.5 [to account for the change from i to $xc] | |
# create horizontal cylinder map | |
ffx="ffx=$factor*asin(xd);" | |
convert -size ${pwidth}x1 xc: -virtual-pixel black -fx \ | |
"xd=(i-$xc)/$radius; $ffx xs=0.5*(ffx+($xc-i)/($xc))+0.5; xd>1?1:xs" \ | |
-scale ${pwidth}x${height1}! \ | |
$tmp0 | |
# create vertical tilted map | |
ffx="ffx=-sqrt(1-(xd)^2);" | |
convert -size ${pwidth}x1 xc: -virtual-pixel black -fx \ | |
"xd=(i-$xc)/$radius; $ffx xs=0.5*(ffx)+0.5; abs(xd)>1?0.5:xs" \ | |
-scale ${pwidth}x${height1}! \ | |
$tmp1 | |
# apply displacement | |
# convert length1 to percentage of height | |
length2=`convert xc: -format "%[fx:100*($length1)/$height]" info:` | |
convert $tmpA -resize 100x${length2}% \ | |
$backgroundcolor -gravity north -extent ${pwidth}x${height1} $tmpA | |
composite $tmp0 $tmpA $tmp1 $channels -virtual-pixel $vpmethod \ | |
$backgroundcolor -displace ${xc}x${radius1} $tmpA | |
if [ "$trim" = "yes" ]; then | |
convert $tmpA -fill $bgcolor -trim $tmpA | |
else | |
convert $tmpA -gravity center -crop ${width}x${height1}+0+0 +repage $tmpA | |
fi | |
# apply narrowing | |
if [ "$narrow" = "100" ]; then | |
convert $tmpA $outfile | |
else | |
ww=`convert $tmpA -ping -format "%[fx:w-1]" info:` | |
hh=`convert $tmpA -ping -format "%[fx:h-1]" info:` | |
offset=`convert xc: -format "%[fx:($ww/2)*(1-$narrow/100)]" info:` | |
www=`convert xc: -format "%[fx:$ww-$offset]" info:` | |
x1=0 | |
y1=0 | |
x2=$ww | |
y2=0 | |
x3=$www | |
y3=$hh | |
x4=$offset | |
y4=$hh | |
coords="0,0 $x1,$y1 $ww,0 $x2,$y2 $ww,$hh $x3,$y3 0,$hh $x4,$y4" | |
convert $tmpA -virtual-pixel $vpmethod $backgroundcolor \ | |
-distort perspective "$coords" $outfile | |
fi | |
elif [ "$mode" = "horizontal" ]; then | |
# create vertical cylinder map | |
ffy="ffy=$factor*asin(yd);" | |
convert -size 1x${pheight} xc: -virtual-pixel black -fx \ | |
"yd=(j-$yc)/$radius; $ffy ys=0.5*(ffy+($yc-j)/($yc))+0.5; yd>1?1:ys" \ | |
-scale ${width1}x${pheight}! \ | |
$tmp0 | |
# create horizontal tilted map | |
ffy="ffy=-sqrt(1-(yd)^2);" | |
convert -size 1x${pheight} xc: -virtual-pixel black -fx \ | |
"yd=(j-$yc)/$radius; $ffy ys=0.5*(ffy)+0.5; abs(yd)>1?0.5:ys" \ | |
-scale ${width1}x${pheight}! \ | |
$tmp1 | |
# apply displacement | |
# convert length1 to percentage of height | |
length2=`convert xc: -format "%[fx:100*($length1)/$width]" info:` | |
convert $tmpA -resize ${length2}x100% \ | |
$backgroundcolor -gravity west -extent ${width1}x${pheight} $tmpA | |
composite $tmp1 $tmpA $tmp0 $channels -virtual-pixel $vpmethod \ | |
$backgroundcolor -displace ${radius1}x${yc} $tmpA | |
if [ "$trim" = "yes" ]; then | |
convert $tmpA -fill $bgcolor -trim $tmpA | |
else | |
convert $tmpA -gravity center -crop ${width1}x${height}+0+0 +repage $tmpA | |
fi | |
# apply narrowing | |
if [ "$narrow" = "100" ]; then | |
convert $tmpA $outfile | |
else | |
ww=`convert $tmpA -ping -format "%[fx:w-1]" info:` | |
hh=`convert $tmpA -ping -format "%[fx:h-1]" info:` | |
offset=`convert xc: -format "%[fx:($hh/2)*(1-$narrow/100)]" info:` | |
hhh=`convert xc: -format "%[fx:$hh-$offset]" info:` | |
x1=0 | |
y1=0 | |
x2=$ww | |
y2=$offset | |
x3=$ww | |
y3=$hhh | |
x4=0 | |
y4=$hh | |
coords="0,0 $x1,$y1 $ww,0 $x2,$y2 $ww,$hh $x3,$y3 0,$hh $x4,$y4" | |
convert $tmpA -virtual-pixel $vpmethod $backgroundcolor \ | |
-distort perspective "$coords" $outfile | |
fi | |
fi | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment