snibgo's ImageMagick pages

Wrapping a pipe

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. 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.

This page gives two methods:

  1. Orthographic projection, which assumes a camera-like viewer at an infinite distance;
  2. Rectilinear projection, which assumes a camera-like viewer at a distance that may be close to the pipe.

Sample input

%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
wrp_src.pngjpg

Orthographic method

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:

wrp_diag.png

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, 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.

Orthographic scripts

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.

call %PICTBAT%setIm7Path

Orthographic examples

call %PICTBAT%wrapPipe.bat wrp_src.png wrp_examp1w.png 90
wrp_examp1w.pngjpg
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp1b.png 90
wrp_examp1b.pngjpg
%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
wrp_examp5.pngjpg
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp6.png 45
wrp_examp6.pngjpg
bash %PICTBAT%wrapPipe.sh wrp_src.png wrp_examp7.png 0.001
wrp_examp7.pngjpg

Orthographic animation

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%
wrp_anim.gif

The animation shows clearly that changing the angle has greatest effect at the top and botom, and no effect in the centre.

Rectlinear method

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 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°.

Vertical distortion

We first consider the vertical distortion.

Point V is not shown on the diagram. It is the point where ET and DH, extended, intersect.

wrp_diag2.png

So:

ED = r * sin(α)

As in the orthographic case:

d = α * r
α = d / r

So:

ED = r * sin(d / r)

So:

h = ED * VT/VE
  = ED * VT / (VT + r - r * cos(α))
  = r * sin(d/r) * VT / (VT + r - r * cos(d/r))

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.

Horizontal distortion

Now we consider the horizontal distortion.

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.)

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)

Rectilinear script

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 massively 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).

Rectilinear examples

bash %PICTBAT%wrapPipeRect.sh ^
  wrp_src.png wrp_rex1.png 2000 angleMax 60
wrp_rex1.pngjpg

A closer viewpoint:

bash %PICTBAT%wrapPipeRect.sh ^
  wrp_src.png wrp_rex2.png 500 angleMax 60
wrp_rex2.pngjpg

A smaller angle:

bash %PICTBAT%wrapPipeRect.sh ^
  wrp_src.png wrp_rex3.png 500 angleMax 45
wrp_rex3.pngjpg

Specifying the radius instead of the angle:

bash %PICTBAT%wrapPipeRect.sh ^
  wrp_src.png wrp_rex4.png 500 radius 180
wrp_rex4.pngjpg

Rectilinear animation

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%
wrp_anim_r.gif

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

setIm7Path.bat

@for %%I in (magick.exe) do @set IPATH=%%~$PATH:I

@if not "%IPATH%"=="" exit /B 0

set PATH=%IMG7%;%PATH%

wrapPipe.bat

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%

wrapPipe.sh

#! /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:

wrapPipeRect.sh

#! /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.0-4 Q16 x64 2021-07-18 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Visual C++: 192930038
Features: Cipher DPC HDRI Modules OpenCL OpenMP(2.0) 
Delegates (built-in): bzlib cairo flif freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib

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 11-Nov-2021 19:05:51.

Copyright © 2021 Alan Gibson.