Make an image that, when physically printed and bent into a cylinder, looks like a reference image.
This page is a response to a query on an ImageMagick forum: Stretch the top and bottom of an image.
The problem is to create an image that, when printed on paper and wrapped on the surface of a horizontal cylindrical pipe, would then, from a distance, look like a reference image. For example, if a poster is taped to a cyclindrical lamp post and then photographed, we want a process that will take the photograph as input and make a replica of the original poster as output. The output will have the same width as the input reference, but will be taller, with most stretch at the top and bottom, and zero stretch at the centre.
This is the opposite of a more common problem, which is to make an image of what a cylinder (such as a coffee mug) would look like if a reference image were wrapped around it.
Methods on this page could be used to distort an image so that, when the distorted image is printed on a coffee mug that is viewed from the correct distance, it looks like the original undistorted image. A coffee mug has a vertical axis, so this would need the input and output to be rotated.
We assume the camera is rectilinear. If it has barrel or pincushion distortion, this should be corrected before applying a process shown here.
This page gives two methods:
goto skip %IMG7%magick toes.png -crop 267x232+0+0 +repage x.png call %PICTBAT%gridOver x.png wrp_src.png 8 8 call %PICTBAT%gridOver toes.png wrp_src.png 8 8 |
|
The script applies a 3-channel distortion map to an extended version of the input image. How do we calculate the distortion map? First, some assumptions:
Under these assumptions, there will be no horizontal distortion. There will be vertical distortion, with greatest stretching at the top and bottom, with no distortion at the centre.
To illustrate the maths, here is a diagram of a cross-section through the pipe, showing one column of the input image and one column of the output image:
The "input image" represents the photographed image that will be the input to our process, and h is a distance along that image from its centre. The "output image" represents an image that has been wrapped around a pipe, and this is the required output from our process. d is a distance along the output image from its centre.
When we write an output pixel, it is at location with a known value of d, and we need to calculate the value of h so we can look up the input colour.
The pipe, and the output image, are shown as a circle centre O with radius r. We are concerned only with the top half. Suppose we are not given the value of r, but are given the angle α at the top (or bottom) of the image. We will see that if we are given one of these, we can calculate the other.
The centres of the input and output images are at point T.
H is some point on the input image, at distance h from T.
The projection of input point H is where the perpendicular of the input image meets the circle, at point D, at distance along the circle of d from T. Output D corresponds to input H. The angle TOD is α (alpha), in radians. Line DE is drawn parallel to HT, so they have the same lengths.
From trigonometry of triangle OED and sector OTD:
h/r = sin(α) d = α * r
At each location in the output image, we know d, and r is a constant, so we can calculate:
α = d / r h = r * sin(α) = r * sin(d / r)
This is the required transformation. The output at distance d comes from input distance h=r*sin(d/r). We will see, a couple of paragraphs down, how r is calculated. The distances are in pixels from the centres of the images. As IM numbers rows from zero at the top, and "-fx" should have its output normalised to [0..1], we need some extra juggling to create the 1xN absolute displacement map for each column of the output image.
We need to extend the input image because the output image and the displacement map will be taller than the input. We use "-extent" rather than "-resize" because we want no distortion at the centre. What is the new height?
We know the height of the input image, and half of this is hmax which is the maximum value of h. We are given alphaMax, the angle at the centre of the pipe for the top (or bottom) of the image, hence we can calculate:
r = hmax / sin(alphaMax) dmax = alphaMax * r dmax = alphaMax * hmax / sin(alphaMax)
dmax is half the required height of the output.
When alphaMax is very close to zero (because the pipe radius is much larger than the image), alphaMax=sin(alphaMax), so dmax=hmax, so there is no distortion.
The inputs to the script are the input image and the value of alphaMax. Instead of alphaMax, from which the script calculates r, the script could take r as an input from which to calculate alphaMax.
All distances must be in the same units. Mathematically, it doesn't matter what the units are: inches, metres, miles, or pixels. In the scripts, all distances are in pixels.
Mathematically, dmax is a floating-point number, and the calculation is simple. But images have heights in integers. A simple implementation of the maths in ImageMagick can cause obvious asymmetry for certain image heights and certain angles. We do some fiddling to reduce this problem:
One consequence of this fiddling is that odd-numbered height inputs will always have odd-numbered height outputs, and even-numbered height inputs will always have even-numbered height outputs. For animations with varying angles, this creates jerky transitions.
If an application needs the same transformation for many input files of the same size, the map can be created once, then used in a simpler command many times.
I have implemented this as a Windows BAT script wrapPipe.bat and a bash script wrapPipe.sh. They should make the same image.
For bash, the script setIm7Path.bat ensures that "magick.exe" is on the system path.
rem call %PICTBAT%setIm7Path
call %PICTBAT%wrapPipe.bat wrp_src.png wrp_examp1w.png 90 |
|
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp1b.png 90 |
|
%IMG7%magick compare -metric RMSE wrp_examp1w.png wrp_examp1b.png NULL:
0 (0)
The results are identical.
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp5.png 60 |
|
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp6.png 45 |
|
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp7.png 0.001 |
|
We can animate the effect of changing the angle. To reduce jerkiness, we operate at double size.
(Between optimizing and writing the GIF, we flatten the first frame. If we don't do this, the first frame is smaller than the page size, which messes the image-size calculation in the process that builds the web page.)
set FILES=
%IMG7%magick ^
wrp_src.png ^
-resize 200%% ^
wrp_tmp.miff
for /L %%I in (1,1,90) do (
echo %%I
bash %PICTBAT%wrapPipe.sh wrp_tmp.miff wrp_tmp2.miff %%I
%IMG7%magick ^
wrp_tmp2.miff ^
-resize 50%% ^
wrp_frm_%%I.miff
set FILES=!FILES! wrp_frm_%%I.miff
)
%IMG7%magick ^
%FILES% ^
-duplicate 10 ^
( +clone ^
-set option:MYSIZE %%[fx:w]x%%[fx:h] ^
+delete ^
) ^
-gravity Center -background None -extent %%[MYSIZE] ^
-layers optimize ^
( -clone 0 -background None -layers Flatten ) ^
-swap 0,-1 +delete ^
wrp_anim.gif
del %FILES%
The animation shows clearly that changing the angle has greatest effect at the top and botom, and no effect in the centre.
Above, we assume orthographic projection. This is reasonable when the distance from the viewpoint (such as a human eye or camera) to the pipe is much greater than the pipe radius. One consequence of orthographic projection is that a horizontal distance at the centre of the top of the pipe appears the same size as the same horizontal distance at the edge of the pipe, despite being closer. So all the lines DH are parallel to OT, and we have only vertical distortion, with no horizontal distortion.
Instead, we can assume rectilinear ("perspective") projection. Then all the lines DH intersect each other at point V, at some given distance VT above point T. So line DH is not vertical, and is not the same length as line ET. The "horizon", beyond which the cylinder is hidden, is less than 180°.
We first consider the vertical distortion.
Point V is not shown on the diagram. It is the point where ET and DH, extended, intersect.
So:
ED = r * sin(α)
As in the orthographic case:
d = α * r α = d / r
So:
ED = r * sin(d / r)
Also:
VE = VT + TE TE = r - OE OE = r * cos(α)
So:
VE = VT + r - r * cos(α) h = ED * VT/VE = ED * VT / (VT + r - r * cos(α)) = r * sin(d/r) * VT / (VT + r - r * cos(d/r))
This is the required transformation. The output at distance d, with known r and VT, is calculated from input distance h.
How do we calculate r from alphaMax, hmax and VT? At the maximum height:
Angle VDE = VHT = atan(VT/hmax) Angle OVD = TVH = atan(hmax/VT)
We know alphaMax so we know the angle ODE = 90°-alphaMax. Angle VDO = VDE + ODE = atan(VT/hmax) + 90°-alphaMax.
By the sine rule:
sin(OVD) = sin(VDO) r r + VT
Rearrange to get an expression for r:
sin(OVD)*r + sin(OVD)*VT = sin(VDO)*r r * (sin(VDO) - sin(OVD)) = sin(OVD)*VT r = sin(OVD)*VT / (sin(VDO) - sin(OVD))
So, given alphaMax and hmax and VT, we can calculate r.
Alternatively, perhaps we know r (and hmax and VT) and need to calculate alphaMax. As before:
Angle VDE = VHT = atan(VT/hmax) Angle OVD = TVH = atan(hmax/VT)
We don't know angle VDO, but we do know r, so we use the sine rule to find VDO and hence ODE.
sin(OVD) = sin(VDO)
r r + VT
sin(VDO) = sin(OVD) * (r+VT)
r
We use asin() to get VDO. But asin() returns an angle between 0 and pi/2 (0° to 90°). Unless the viewpoint is very close to the pipe, the angle should be between pi and pi/2 (180° to 90°), so we use pi-asin().
Then:
ODE = VDO - VDE
= VDO - VHT
= VDO - atan(VT/hmax)
alphaMax = 90° - ODE
= 90° - (VDO - atan(VT/hmax))
= 90° - VDO + atan(VT/hmax)
Now we know r and alphaMax, calculating dmax is simple:
dmax = r * alphaMax
Now we need an expression for h in terms of d.
Triangles VED and VTH are similar, so:
VE/VT = ED/TH h = TH = ED * VT / VE OE = r * cos(α) TE = r - OE VE = VT + TE = VT + r - OE = VT + r - r * cos(α) h = ED * VT/VE = ED * VT / (VT + r - r * cos(α)) = r * sin(d/r) * VT / (VT + r - r * cos(d/r))
This is the expression we need for the vertical displacement map. VT is a constant for the entire image. VE is a function of the distance from the centre. Note that when VT is very large, r - r * cos(d/r) is insignificant, and h = r * sin(d/r), as for the orthographic projection.
Aside: The Horizon
When V is at infinity, we have orthographic projection, and 180° of the cylinder is visible. When V is closer to the cylinder, less than 180° is visible. How much? At the horizon, angle VDO is a right-angle (90°). So:
cos(α) = OD/OV = r/(r+VT)
For example, if VT = 3*r, then cos(α) = r/(4*r) = 0.25 so α=75.52°. This is the semi-angle, so a total of 151.04° is visible.
Now we consider the horizontal distortion, which is much simpler.
When printed and wrapped around a pipe, horizontal rows at the top and bottom are further from the viewpoint than those at the centre, by a factor of VE/VT, so they appear smaller. We need to enlarge them by a factor of VE/VT. Hence the output image will be wider at the top and bottom than at the centre. It will be stretched outwards at the top and bottom, with no stretch in the centre, an "hourglass" shape. (Recall that VT is a constant over the image, but VE is a function of the vertical distance from the centre.)
At a vertical distance
So the input image has to be extended to a new width of ww * VEmax / VT, where ww is the input width and:
VEmax = VT + r - r * cos(alphaMax)
This is implemented in wrapPipeRect.sh.
Script parameters are:
| Number | Description |
|---|---|
| 1 | Input image |
| 2 | Output image |
| 3 | Distance of viewpoint above pipe in pixels |
| 4 | Describes next parameter: either angleMax or radius. |
| 5 | Either the maximum semi-angle in degrees (0..90],
or pipe radius in pixels. |
Units of distance, including radius, are pixels. For example, if the image is to be printed at 300 dpi and wrapped around a pipe with radius 4 inches, and viewed from a height of 10 inches, then radius will be 1200 and viewpoint distance will be 3000.
For the green channel, which controls displacement in the x-direction, the value at every pixel is different. The script could use -fx for every pixel, but that would be slow. Instead, we calculate the green channel for just the first and last columns, then interpolate linearly between them with "-morph" and "append", with some juggling so IM works with Nx1 rather than 1xN images for performance.
The script checks for some error conditions:
The script echos and returns isBad as 0 (no error) or 1 (error).
bash %PICTBAT%wrapPipeRect.sh ^ wrp_src.png wrp_rex1.png 2000 angleMax 60 |
|
|
A closer viewpoint: bash %PICTBAT%wrapPipeRect.sh ^ wrp_src.png wrp_rex2.png 500 angleMax 60 |
|
|
A smaller angle: bash %PICTBAT%wrapPipeRect.sh ^ wrp_src.png wrp_rex3.png 500 angleMax 45 |
|
|
Specifying the radius instead of the angle: bash %PICTBAT%wrapPipeRect.sh ^ wrp_src.png wrp_rex4.png 500 radius 180 |
|
As before, we can animate the effect of changing the angle.
set FILES=
%IMG7%magick ^
wrp_src.png ^
-resize 200%% ^
wrp_tmp_r.miff
for /L %%I in (1,1,65) do (
echo %%I
bash %PICTBAT%wrapPipeRect.sh wrp_tmp_r.miff wrp_tmp_r2.miff 500 angleMax %%I
%IMG7%magick ^
wrp_tmp_r2.miff ^
-resize 50%% ^
wrp_frm_%%I.miff
set FILES=!FILES! wrp_frm_%%I.miff
)
%IMG7%magick ^
%FILES% ^
-duplicate 10 ^
( +clone ^
-set option:MYSIZE %%[fx:w]x%%[fx:h] ^
+delete ^
) ^
-gravity Center -background None -extent %%[MYSIZE] ^
-layers optimize ^
( -clone 0 -background None -layers Flatten ) ^
-swap 0,-1 +delete ^
wrp_anim_r.gif
del %FILES%
I took an A4 (29.7cm by 21.0cm) sheet of squared paper and drew some grid lines on it. The pre-printed faint lines are 5mm apart, and my added lines are 25mm apart.
I fastened it to a telephone pole with a circumference of 65cm. So its radius is calculated by:
2 * π * r = 65cm r = 65cm / (2 * π) = 10.35 cm
I photographed this from a distance of about 58cm, measured from the front of the pole to the middle of the lens. (The lens had a focal length of 20mm, on a full-frame camera, but this makes no difference to anything.)
For internet bandwidth reasons, I shrink the image.
set PRACT_SRC=%PICTLIB%20250227\AGA_5727.JPG %IMG7%magick ^ %PRACT_SRC% ^ -strip ^ -resize 600 ^ -crop 598x288+0+69 +repage ^ -quality 40 ^ wrp_pract_sm.jpg |
|
On the image, the width of the paper at its widest point is 370 pixels. So the scale at the front of the pole is 370 pixels / 21.0cm = 17.619 pixels per cm.
The pole radius is about 10.35 cm, so this would be 10.35 * 17.619 = 182.357 pixels.
The distance from the camera to the pole is 58cm, so that would be 58 * 17.619 = 1021.9 pixels.
bash %PICTBAT%wrapPipeRect.sh ^ wrp_pract_sm.jpg ^ wrp_pract_sm_out.jpg ^ 1021.9 radius 182.357 |
|
set PRACT_SRC=%PICTLIB%20250227\AGA_5727.JPG %IMG7%magick ^ %PRACT_SRC% ^ -crop 4525x3537+1153+825 +repage ^ -quality 40 ^ wrp_pract.jpg |
|
bash %PICTBAT%wrapPipeRect.sh ^ wrp_pract.jpg ^ wrp_pract_out.jpg ^ 12497.619 radius 2230.179 |
|
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
goto skip @for %%I in (magick.exe) do @set IPATH=%%~$PATH:I @if not "%IPATH%"=="" exit /B 0 :skip set ModPath=!PATH:%IMG7%=xxxx! if not [%ModPath%]==[%PATH%] exit /B 0 :doit set PATH=%IMG7%;%PATH%
rem Distort the input so that,
rem when printed and wrapped around a pipe,
rem that would look like the input image.
rem $1 is input file
rem $2 is output file
rem $3 is half the angle of the pipe we can see, in degrees, more than 0, less than or equal to 90.
rem The script does NOT test for valid angles.
rem See also wrapPipe.sh.
@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1
@setlocal enabledelayedexpansion
rem @call echoOffSave
call %PICTBAT%setInOut %1 wp
if not "%2"=="" if not "%2"=="." set OUTFILE=%2
set amaxDeg=%3
if "%amaxDeg%"=="." set amaxDeg=
if "%amaxDeg%"=="" set amaxDeg=45
if "%amaxDeg%"=="0" set amaxDeg=0.0001
set ww=%%[w]
set hh=%%[h]
set hmax=%%[fx:h/2]
set halfh=%%[fx:int(h/2)]
set amax=%%[fx:%amaxDeg%*pi/180]
for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-precision 15 ^
-format "ww=%ww%\nhh=%hh%\nhmax=%hmax%\nhalfh=%halfh%\namax=%amax%\n" ^
info:`) do set %%L
for /F "usebackq" %%L in (`%IMG7%magick ^
xc: ^
-precision 15 ^
-format "rr=%%[fx:%hmax% / sin(%amax%)]\n" ^
info:`) do set %%L
for /F "usebackq" %%L in (`%IMG7%magick ^
xc: ^
-precision 15 ^
-format "ex=%%[fx:int((%rr% * (%amax%) * 2 - %hh%) / 2)]\n" ^
info:`) do set %%L
for /F "usebackq" %%L in (`%IMG7%magick ^
xc: ^
-precision 15 ^
-format "dx=%%[fx:%halfh% + %ex% - (%halfh%-%hh%/2)]\n" ^
info:`) do set %%L
for /F "usebackq" %%L in (`%IMG7%magick ^
xc: ^
-precision 15 ^
-format "dx2=%%[fx:%dx% * 2]\n" ^
info:`) do set %%L
echo ww=%ww% hh=%hh% hmax=%hmax% amax=%amax% halfh=%halfh% rr=%rr% ex=%ex% dx=%dx% dx2=%dx2%
%IMG7%magick ^
( %INFILE% ^
-background "#88f" ^
-gravity Center ^
-extent %ww%x%dx2% ^
) ^
( ^
-size %dx2%x1 xc: ^
-fx "(%rr%*sin((i-%dx%)/%rr%)+(%dx%))/w" ^
-rotate 90 ^
( -size 1x%ww% gradient:Black-White -rotate -90 ) ^
+swap ^
( -size 1x1 xc:"gray(50%%)" ) ^
-scale "%ww%x%dx2%^!" ^
-combine ^
) ^
-compose Distort -composite ^
%OUTFILE%
call echoRestore
endlocal & set wpOUTFILE=%OUTFILE%& ^
set wpDX2=%dx2%
#! /bin/bash
# Distort the input so that,
# when printed and wrapped around a pipe,
# that would look like the input image.
# $1 is input file
# $2 is output file
# $3 is half the angle of the pipe we can see, in degrees, more than 0, less than or equal to 90.
# The script does NOT test for valid angles.
# See also wrapPipe.bat.
# Updated:
# 25-October-2021
# 27-October-2021 Fixed (?) asymmetrical problem on certain heights and angles.
if [ $# != 3 ]; then
echo "Usage: $0 input output angle"
exit 1
fi
infile=$1
outfile=$2
amaxDeg=$3
MAGICK=magick.exe
ww=%[w]
hh=%[h]
hmax=%[fx:h/2]
halfh="%[fx:int(h/2)]"
amax="%[fx:$amaxDeg*pi/180]"
declare $( ${MAGICK} \
$infile \
-precision 15 \
-format "ww=${ww}\nhh=${hh}\nhmax=${hmax}\nhalfh=${halfh}\namax=${amax}\n" \
info: )
rr=$(${MAGICK} xc: -precision 15 -format "%[fx:$hmax / sin($amax)]" info: )
ex=$(${MAGICK} xc: -precision 15 -format "%[fx:int(($rr * ($amax) * 2 - $hh) / 2)]" info: )
dx=$(${MAGICK} xc: -precision 15 -format "%[fx:${halfh} + $ex - (${halfh}-${hh}/2)]" info: )
dx2=$(${MAGICK} xc: -precision 15 -format "%[fx:${dx}*2]" info: )
echo ww=$ww hh=$hh hmax=$hmax amax=$amax halfh=$halfh rr=$rr ex=$ex dx=$dx dx2=$dx2
${MAGICK} \
\( $infile \
-background "#88f" \
-gravity Center \
-extent ${ww}x${dx2} \
\) \
\( \
-size ${dx2}x1 xc: \
-fx "($rr *sin((i-${dx})/$rr)+${dx})/w" \
+write e.png \
-rotate 90 \
\( -size 1x$ww gradient:Black-White -rotate -90 \) \
+swap \
\( -size 1x1 xc:"gray(50%)" \) \
-scale "${ww}x${dx2}!" \
-combine \
+write mymap.miff \
\) \
-compose Distort -composite \
$outfile
magick e.png -format "%[fx:minima] %[fx:maxima] %[fx:1-minima]\n" info:
#! /bin/bash
# Distort the input so that,
# when printed and wrapped around a pipe,
# that would look like the input image.
# $1 is input file
# $2 is output file
# $3 distance of viewpoint above pipe
# $4 is either "angleMax" or "radius", describing the next argument.
# $5 is either the half the angle of the pipe we can see, or the radius of the pipe (in pixels)
# If the angle: in degrees, more than 0, less than or equal to 90.
# The script does NOT test for valid angles.
# See also wrapPipe.sh.
if [ $# != 5 ]; then
echo "Usage: $0 input output VT paramStr param"
exit 1
fi
infile=$1
outfile=$2
VT=$3
paramStr=$4
paramNum=$5
MAGICK=magick.exe
isBad=0
ww=%[w]
hh=%[h]
hmax=%[fx:h/2]
halfh="%[fx:int(h/2)]"
halfw="%[fx:int(w/2)]"
declare $( ${MAGICK} \
$infile \
-precision 15 \
-format "ww=${ww}\nhh=${hh}\nhmax=${hmax}\nhalfh=${halfh}\nhalfw=${halfw}\n" \
info: )
OVD="%[fx:atan(${hmax}/${VT})]"
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "OVD=${OVD}\n" \
info: )
echo VT=$VT ww=$ww hh=$hh hmax=$hmax halfh=$halfh OVD=$OVD
if [ "${paramStr}" = "angleMax" ]; then
echo "Param is maxAngle: ${paramNum}"
angleMax=%[fx:${paramNum}*pi/180]
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "angleMax=${angleMax}\n" \
info: )
VDO="%[fx:atan(${VT}/${hmax}) + pi/2 - ${angleMax}]"
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "VDO=${VDO}\n" \
info: )
rr="%[fx:sin(${OVD})*${VT} / (sin(${VDO}) - sin(${OVD}))]"
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "rr=${rr}\n" \
info: )
echo "Calculated angleMax=${angleMax} OVD=${OVD} VDO=${VDO} rr=${rr}"
elif [ "${paramStr}" = "radius" ]; then
echo "Param is radius: ${paramNum}"
rr=${paramNum}
VDO="%[fx:pi-asin(min(1,sin(${OVD})*(${rr}+${VT})/${rr}))]"
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "VDO=${VDO}\n" \
info: )
angleMax="%[fx:pi/2-${VDO}+atan(${VT}/${hmax})]"
declare $( ${MAGICK} \
xc: \
-precision 15 \
-format "angleMax=${angleMax}\n" \
info: )
echo "Calculated angleMax=${angleMax} OVD=${OVD} VDO=${VDO} rr=${rr}"
else
echo "Unknown paramStr [${paramStr}]"
exit 1
fi
horizonAng=$(${MAGICK} \
xc: \
-precision 15 \
-format "%[fx:acos(${rr}/(${rr}+${VT}))]\n" \
info: )
isBad=$(${MAGICK} \
xc: \
-precision 15 \
-format "%[fx:${angleMax}>=${horizonAng}||${rr}<=0||${VDO}<=pi/2?1:${isBad}]\n" \
info: )
echo isBad=$isBad
${MAGICK} \
xc: \
-precision 15 \
-format "VDOdeg=%[fx:${VDO}*180/pi]\nangleMaxdeg=%[fx:${angleMax}*180/pi]\nhorizonAngdeg=%[fx:${horizonAng}*180/pi]\n" \
info:
amax=$angleMax
ex=$(${MAGICK} xc: -precision 15 -format "%[fx:int(($rr * ($amax) * 2 - $hh) / 2)]" info: )
dx=$(${MAGICK} xc: -precision 15 -format "%[fx:${halfh} + $ex - (${halfh}-${hh}/2)]" info: )
dx2=$(${MAGICK} xc: -precision 15 -format "%[fx:${dx}*2]" info: )
echo horizonAng=$horizonAng ww=$ww hh=$hh hmax=$hmax amax=$amax halfh=$halfh rr=$rr ex=$ex dx=$dx dx2=$dx2
exh=$(${MAGICK} xc: -precision 15 -format "%[fx:int(($ww*($VT + $rr - $rr * cos($angleMax))/$VT-$ww)/2)]" info: )
wwNewHlf=$(${MAGICK} xc: -precision 15 -format "%[fx:${halfw} + $exh - (${halfw}-${ww}/2)]" info: )
wwNew=$(${MAGICK} xc: -precision 15 -format "%[fx:$wwNewHlf * 2]" info: )
echo exh=$exh wwNewHlf=$wwNewHlf wwNew=$wwNew
${MAGICK} \
\( $infile \
-background None \
-gravity Center \
-extent ${wwNew}x${dx2} \
\) \
\( \
-size ${dx2}x1 xc: \
-fx "dr=(i-${dx})/${rr}; ($rr*sin(dr)*$VT/($VT+$rr-$rr*cos(dr))+${dx})/w" \
-rotate 90 \
-scale "${wwNew}x${dx2}!" \
\( -size ${dx2}x1 xc: \
-fx "dh=(0-$wwNewHlf)/${wwNew}; al=(i-$dx)/${rr}; ${wwNew}/(${wwNew}-2) * dh * $VT/($VT + $rr - $rr * cos(al))+0.5" \
\( xc: \
-fx "dh=(${wwNew}-1-$wwNewHlf)/${wwNew}; al=(i-$dx)/${rr}; ${wwNew}/(${wwNew}-2) * dh * $VT/($VT + $rr - $rr * cos(al))+0.5" \
\) \
-morph %[fx:${wwNew}-2] \
-reverse -append \
-rotate 90 \
-alpha off \
\) \
+swap \
\( -size ${wwNew}x${dx2} xc:"gray(50%)" \) \
-combine \
-alpha off \
\) \
-compose Distort -composite \
$outfile
exit $isBad
All images on this page were created by the commands shown, using:
%IMG7%magick identify -version
Version: ImageMagick 7.1.1-20 Q16-HDRI x86 98bb1d4:20231008 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL OpenMP(2.0) Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193532217)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
Source file for this web page is wrappipe.h1. To re-create this web page, execute "procH1 wrappipe".
This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.
Anyone is permitted to re-publish this page, but only for non-commercial use.
Anyone is permitted to link to this page, including for commercial use.
Page version v1.0 3-November-2021.
Page created 01-Mar-2025 16:01:13.
Copyright © 2025 Alan Gibson.