﻿

# Curving in 3D

We distort the image plane, like curving a piece of paper.

Curving an image in three-dimensional space is easily performed by ray-tracers such as POV-ray. The ray-tracer can create displacement maps and lighting masks for known surfaces so ImageMagick can apply them to any arbitrary input image.

However, some curved surfaces can be directly modelled with just ImageMagick. The method show here is not geometrically accurate for perspective views, but is good enough for some purposes.

## The goal

Given an input image and a line that represents the cross-section of a surface, make a 2-D image that resembles the input curved into the surface.

Imagine the input image is printed on a sheet of paper. We can curve this paper in a number of ways, and even fold it. However, we will not cut the paper, nor will we stretch it.

In addition, we will curve the input image along the x-dimension only. Straight horizontal lines will become curved, but vertical lines will remain straight.

## Sample inputs

We will distort this input image:

 ```set SRC=c3d_src.png call %PICTBAT%gridOver ^ toes.png %SRC% 8 8```

Images don't usually have grids, but this helps us see what is happening.

First, we need the profile of the curve we want to make. This is a cross-section of the 3-D curve. For a cylinder, it would be a circle. However, it must have two ends.

I use Gimp to draw a thin white line on a black background. The line can start and end anywhere, but it must not cross itself.

 c3d_line.png

The size of the image that contains the line is of no significance. The length of the line is significant, and so is the width it occupies within the image. We find the bounding rectangle of the line with the script colTrim.bat. An alternative method is to use "-connected-components".

```call %PICTBAT%colTrim c3d_line.png Black

echo ctRECT=%ctRECT%  ctW=%ctW%  ctH=%ctH%```
`ctRECT=239x137+22+49  ctW=239  ctH=137`

The script dp2Grad.bat finds the two ends of the line, and the length of the line in pixels. It also creates a gradient along the line that is black at one end, white at the other. Pixels that are not on the line are made transparent. (The morphology operation, and hence the entire script, is horribly slow.) We trim the result to just the line.

 ```call %PICTBAT%dp2grad ^ c3d_line.png ^ c3d_grad.png %IMG7%magick ^ c3d_grad.png ^ -crop %ctRECT% +repage ^ c3d_grad.png echo dp2gLINE_LEN=%dp2gLINE_LEN% dp2gERR=%dp2gERR% ``` `dp2gLINE_LEN= dp2gERR=0 `

dp2grad writes some properties to the file.

`%IMG7%magick identify -format %%[line:*] c3d_grad.png `
```line:len=368
line:x0=22
line:x1=260
line:y0=169
line:y1=185```

We can read these to environment variables. In Windows, variable names can't contain colons (:), so we change them to underscores.

```for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format %%[line:*] ^
set LINE=%%L
set !LINE::=_!
)

set line_ ```
```line_len=368
line_x0=22
line_x1=260
line_y0=169
line_y1=185```

The length of the line needs to be equal to the width of the input image. Currently, the line is too long. We could rectify this by shrinking the line. (For precision, we might then re-calculate the length, and repeat until the length is sufficiently close.) Instead, we will enlarge the input image. We also need the new dimensions.

 ```for /F "usebackq" %%L in (`%IMG7%magick ^ %SRC% ^ -resize %line_len%x ^ -format "INP_WW=%%w\nINP_HH=%%h" ^ +write info: ^ c3d_res_inp.png`) do set %%L echo INP_WW=%INP_WW% INP_HH=%INP_HH% ``` `INP_WW=368 INP_HH=321 `

We fill the transparent areas by filling vertically. (See Filling holes: fill by shift.)

 ```rem set sfFLAT_COL=None call %PICTBAT%shiftFill ^ c3d_grad.png 0x c3d_fill.png rem set sfFLAT_COL=```

If the line hasn't doubled back on itself, the rows will be equal. The top row represents a horizontal absolute displacement map, to transform an input image to the curved surface.

We crop the top row and scale it to the required height (INP_HH). It is the correct scale horizontally, but needs to be extended to the right to match the input.

 ```%IMG7%magick ^ c3d_fill.png ^ -crop 0x1+0+0 +repage ^ -scale "x%INP_HH%^!" ^ -gravity West ^ -background White ^ -extent %INP_WW%x%INP_HH% ^ c3d_xdisp.png```

## X-displacement

From c3d_xdisp.png, we make a complete absolute displacement map.

 Make the map. ```%IMG7%magick ^ c3d_xdisp.png ^ ( -clone 0 ^ -sparse-color bilinear ^ "0,0 Black 0,%%[fx:h-1] White" ^ ) ^ ( -clone 0 ^ -fill gray(50%%) -colorize 100 ^ ) ^ -combine ^ -set colorspace sRGB ^ c3d_x_abs.png``` Apply the map, and crop. ```%IMG7%magick ^ c3d_res_inp.png ^ c3d_x_abs.png ^ -compose Distort ^ -set option:compose:args 100%%x100%% ^ -composite ^ -crop %ctW%x+0+0 +repage ^ c3d_xdisp_trial.png```

If we took a photo from an infinite distance of our input image wrapped onto the surface, looking directly at the surface, this is what the resulting photo would look like.

## Y-displacement

Instead of looking directly at the surface, we might view it from an angle. This is equivalent to rotating the surface around the x-axis by an angle α. This has two effects:

1. The y-axis is fore-shortened, by a factor of cos(α).
2. We will see the profile of the surface, as defined by the line image. This is also fore-shortened, by a factor of sin(α).

The angle is between -90° and +90°. If the angle is zero, we have no y-displacement. At either extreme, we see the exact profile, with none of the input image.

The total height of the output will be the sum of the foreshortened height of the input, plus the foreshortened height of the profile. This is INP_HH * cos(α) + ctH * |sin(α)| where INP_HH and ctH are given above.

```set ALPHA=30

for /F "useback" %%L in (`%IMG7%magick identify ^
xc:`) do set %%L

for /F "useback" %%L in (`%IMG7%magick identify ^
-format "OUT_HH=%%[fx:%inHcosA% + %ctHsinA%]\nctHsinA_2=%%[fx:%ctHsinA%/2]"
xc:`) do set %%L

echo inHcosA=%inHcosA% ctHsinA=%ctHsinA% ctHsinA_2=%ctHsinA_2% OUT_HH=%OUT_HH% ```
`inHcosA=277.994 ctHsinA=68.5 ctHsinA_2=34.25 OUT_HH=346.494 `

We can get a height field from the cross section by trimming to the line, turning the pixels beneath it white, and scaling to a single row. This will be used for any y-displacement.

 ```%IMG7%magick ^ c3d_line.png ^ -crop %ctRECT% +repage ^ -morphology dilate rectangle:1x%ctH%+0+0 ^ -scale "x1^!" ^ c3d_ht_fld.png```

From c3d_ht_fld.png, we can make a relative displacement map.

 Make the map. ```%IMG7%magick ^ c3d_ht_fld.png ^ ( -clone 0 ^ -fill gray(50%%) -colorize 100 ^ ) ^ ( -clone 1 ) ^ -swap 0,1 ^ -combine ^ -set colorspace sRGB ^ -scale "x%OUT_HH%^!" ^ c3d_y_rel.png``` Apply the map. ```%IMG7%magick ^ c3d_xdisp_trial.png ^ -scale "x%inHcosA%^!" ^ -gravity Center -background None ^ -extent "x%OUT_HH%^!" ^ c3d_y_rel.png ^ -virtual-pixel None ^ -compose Displace ^ -set option:compose:args 0x%ctHsinA_2% ^ -composite ^ c3d_ydisp_trial.png```

This is like taking a photo of the curved image from an infinite distance, with no perspective ("parallel projection").

## Y-displacement with perspective

We can simulate perspective by:

1. reducing the height of the profile at the top of the image;
2. reducing the distance between the horizontal parallels at the top of the image;
3. narrowing the width at the top of the image.

For effect (1) and (2), we "+level" the map, to push values towards 50%. We apply this to a clone of the entire map, and blend this with the original map by composing "over" with a mask.

 Revise the map. ```%IMG7%magick ^ c3d_y_rel.png ^ ( +clone ^ +level 10%% ^ ) ^ ( +clone ^ -sparse-color bilinear "0,0,White 0,%%[fx:h-1],Black" ^ ) ^ -composite ^ c3d_y_persp.png```

We use this revised map, and apply the narrowing process.

 Apply perspective. ```set NARROW_PC=10 set dNarrow=(w*%NARROW_PC%/100) set PERSP=^ 0,0,%%[fx:%dNarrow%],0,^ %%[fx:w-1],0,%%[fx:w-1-%dNarrow%],0,^ 0,%%[fx:h-1],0,%%[fx:h-1],^ %%[fx:w-1],%%[fx:h-1],%%[fx:w-1],%%[fx:h-1] %IMG7%magick ^ c3d_xdisp_trial.png ^ -scale "x%inHcosA%^!" ^ -gravity Center -background None ^ -extent "x%OUT_HH%^!" ^ c3d_y_persp.png ^ -virtual-pixel None ^ -compose Displace ^ -set option:compose:args 0x%ctHsinA_2% ^ -composite ^ -virtual-pixel None ^ -distort Perspective "%PERSP%" ^ c3d_ydisp_persp.png```

Because the profile height has reduced at the top (the back of the 3-D object), the overall result is shorter than OUT_HH.

The shortening of the profile height is not quite accurate (it varies at each cross-section), but visually appears realistic. Similarly, for correct 3-D, the distance between parallels on the ridge should be greater than the distance in the valleys, but this has reduced the distance. For ordinary photographs without grids, these defects should not be noticable.

## The script

We encapsulate the above in the script curve3d.bat.

Make
line file
Line file Run script Output

c3d_line.png

```call %PICTBAT%curve3d ^
%SRC% c3d_line.png ^
c3d_s1.png```

c3d_line.png

```call %PICTBAT%curve3d ^
%SRC% c3d_line.png ^
c3d_s2.png ^
20 20 20```

c3d_line.png

```call %PICTBAT%curve3d ^
%SRC% c3d_line.png ^
c3d_s3.png ^
-20 20 20```
```%IMG7%magick ^
-size 300x200 ^
xc:Black ^
+antialias ^
-stroke White -fill None ^
-draw ^
"arc 20,190 280,20 180,0" ^
c3d_arc.png```
```call %PICTBAT%curve3d ^
%SRC% c3d_arc.png ^
c3d_s4.png ^
-20 20 20```
```%IMG7%magick ^
-size 300x200 ^
xc:Black ^
+antialias ^
-stroke White -fill None ^
-draw ^
"ellipse 150,100 130,80 0,360" ^
-stroke Black ^
-draw "line 150,100 150,199" ^
c3d_ellip.png```

The black line breaks the circle
to make the two ends for the line.

The output is twice the size of the arc example,
but the back half is hidden.

```call %PICTBAT%curve3d ^
%SRC% c3d_ellip.png ^
c3d_s5.png ^
20 20 20```

## Future

The methods shown here could be extended:

• To create a lighting map.
• To invert the process, so a photograph of curved paper could be de-curved.
• To morph between given cross-sections.

## Scripts

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

### colTrim.bat

```rem Find trim rectangle for border of given colour.
@rem
@rem Updated:
@rem   21-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 ct

set BORD_COL=%2
if "%BORD_COL%"=="." set BORD_COL=
if "%BORD_COL%"=="" set BORD_COL=Black

if not "%3"=="" set OUTFILE=%3

for /F "usebackq tokens=1-4 delims=x+" %%A in (`%IMG7%magick ^
%INFILE% ^
-bordercolor %BORD_COL% ^
-border 1 ^
-format "%%@" ^
info:`) do (
set /A BW=%%A
set /A BH=%%B
set /A BX=%%C-1
set /A BY=%%D-1
)

call echoRestore

endlocal & set ctW=%BW%& set ctH=%BH%& set ctX=%BX%& set ctY=%BY%& set ctRECT=%BW%x%BH%+%BX%+%BY%```

```rem Given image %1 a white line on black background,
rem finds coords of the ends,
rem and makes output image %2 that has gradient for the line,
rem and transparent elsewhere.
rem Sets properties "line:*" in output.
@rem
@rem Updated:
@rem   20-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

rem @call echoOffSave

call %PICTBAT%setInOut %1 dp2g

if not "%2"=="" set OUTFILE=%2

set TMP_2ENDS=l2g_two_ends.miff

set ERR=0

set X0=
for /F "usebackq tokens=2,3 delims=:, " %%X in (`%IM7DEV%magick ^
%INFILE% ^
-virtual-pixel Black ^
-morphology HMT LineEnds ^
-process onewhite ^
%TMP_2ENDS% 2^>^&1`) do (
set X0=%%X
set Y0=%%Y
)
if "%X0%"=="" exit /B 1

if "%X0%"=="none" (
echo %0: no line ends [%INFILE%]
set ERR=1
goto end
)

for /F "usebackq tokens=2,3 delims=:, " %%X in (`%IM7DEV%magick ^
%TMP_2ENDS% ^
-fill Black -draw "point %X0%,%Y0%" ^
-process onewhite ^
NULL: 2^>^&1`) do (
set X1=%%X
set Y1=%%Y
)

if "%X1%"=="none" (
echo %0: no second line end [%INFILE%]
set ERR=1
goto end
)

set nInPath=
for /F "usebackq tokens=1-2 delims=: " %%L in (`%IM7DEV%magick ^
%INFILE% ^
-negate ^
-evaluate Subtract 1 -clamp -evaluate Add 1 ^
-set colorspace sRGB ^
-process ^
"darkestpntpnt s %X0%,%Y0% e %X1%,%Y1% t 0.5 data v" ^
-channel G -separate +channel ^
-clamp ^
-auto-level ^
+depth ^
%OUTFILE% 2^>^&1`) do (
if /I "%%L"=="nInPath" set nInPath=%%M
if /I "%%L"=="maxDist" set MaxDist=%%M
if /I "%%L"=="Bug:" set ERR=1
)
if "%nInPath%"=="" exit /B 1

%IMG7%magick ^
%OUTFILE% ^
%INFILE% ^
-compose CopyOpacity -composite ^
-background gray(50%%) -alpha Background ^
-auto-level ^
-alpha Background ^
-set line:len %nInPath% ^
-set line:x0 %X0% ^
-set line:y0 %Y0% ^
-set line:x1 %X1% ^
-set line:y1 %Y1% ^
%OUTFILE%

:end

call echoRestore

endlocal & set dp2gOUTFILE=%OUTFILE%& set dp2gERR=%ERR%```

```rem Given image %1 a white line on black background,
rem finds coords of the ends,
rem and makes output image %2 that has gradient for the line,
rem and transparent elsewhere.
rem Sets properties "line:*" in output.
@rem
@rem Updated:
@rem   3-September-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 l2g

if not "%2"=="" set OUTFILE=%2

set TMP_2ENDS=l2g_two_ends.miff

set APPROX_LEN_PC=90

set ERR=0

set WW=

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "WW=%%w\nHH=%%h\nDIAG=%%[fx:int(hypot(w,h)+0.5)]" ^
%INFILE%`) do set %%L

if "%WW%"=="" (
echo Can't open [%INFILE%]
exit /B 1
)

for /F "usebackq tokens=2,3 delims=:, " %%X in (`%IM7DEV%magick ^
%INFILE% ^
-virtual-pixel Black ^
-morphology HMT LineEnds ^
-process onewhite ^
%TMP_2ENDS% 2^>^&1`) do (
set X0=%%X
set Y0=%%Y
)

if "%X0%"=="none" (
echo %0: no line ends [%INFILE%]
set ERR=1
goto end
)

for /F "usebackq tokens=2,3 delims=:, " %%X in (`%IM7DEV%magick ^
%TMP_2ENDS% ^
fill Black -draw "point %X0%,%Y0%" ^
-process onewhite ^
NULL: 2^>^&1`) do (
set X1=%%X
set Y1=%%Y
)

if "%X1%"=="none" (
echo %0: no second line end [%INFILE%]
set ERR=1
goto end
)

:: FIXME:
:: If X1 < X0, swap the ends.
:: If X1==X0 and Y1 < Y0, swap the ends.

rem echo X0=%X0% Y0=%Y0% X1=%X1% Y1=%Y1%

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-format "APPROX_LEN=%%[fx:int(max(hypot(%X1%-%X0%,%Y1%-%Y0%),mean*w*h+0.5))]" ^
INFO:`) do set %%L

rem echo APPROX_LEN=%APPROX_LEN%

set /A KNL_SCALE_V=65535*%APPROX_LEN_PC%/%APPROX_LEN%/100

rem echo KNL_SCALE_V=%KNL_SCALE_V%

%IMG7%magick ^
%INFILE% ^
-fill White -colorize 100 ^
-fill Black -draw "point %X0%,%Y0%" ^
-morphology ^
IterativeDistance:%DIAG% ^
Euclidean:7,%KNL_SCALE_V% ^
+depth ^

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-compose Darken -composite ^
-format "MAX_01=%%[fx:maxima]\nMAX_GRAY=%%[fx:maxima*QuantumRange]\nLINE_LEN=%%[fx:maxima*QuantumRange/%KNL_SCALE_V%]" ^
info:`) do set %%L

rem echo MAX_01=%MAX_01% MAX_GRAY=%MAX_GRAY% LINE_LEN=%LINE_LEN%

if "%MAX_01%"=="1" (
echo %0: Bad first MAX_01 == %MAX_01%
set ERR=1
goto end
)

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-compose Darken -composite ^
-format "KNL_SCALE_V=%%[fx:%KNL_SCALE_V%/maxima)]" ^
info:`) do set %%L

rem echo KNL_SCALE_V=%KNL_SCALE_V%

%IMG7%magick ^
%INFILE% ^
-fill White -colorize 100 ^
-fill Black -draw "point %X0%,%Y0%" ^
-morphology ^
IterativeDistance:%DIAG% ^
Euclidean:7,%KNL_SCALE_V% ^
+depth ^

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-compose Darken -composite ^
-format "MAX_01=%%[fx:maxima]\nMAX_GRAY=%%[fx:maxima*QuantumRange]\nLINE_LEN=%%[fx:maxima*QuantumRange/%KNL_SCALE_V%]" ^
info:`) do set %%L

rem echo MAX_01=%MAX_01% MAX_GRAY=%MAX_GRAY% LINE_LEN=%LINE_LEN%

:: "%MAX_01%" should be "1", more or less.
:: MAX_GRAY should be >= QuantumRange - KNL_SCALE_V

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "GRAY_OK=%%[fx:%MAX_GRAY%>QuantumRange-%KNL_SCALE_V%?1:0]"
xc:`) do set %%L

if not "%GRAY_OK%"=="1" (
echo %0: Bad second MAX_01 = %MAX_01% MAX_GRAY = %MAX_GRAY%
set ERR=1
goto end
)

%IMG7%magick ^
%INFILE% ^
-compose CopyOpacity -composite ^
-background gray(50%%) -alpha Background ^
-auto-level ^
-alpha Background ^
-set line:len %LINE_LEN% ^
-set line:x0 %X0% ^
-set line:y0 %Y0% ^
-set line:x1 %X1% ^
-set line:y1 %Y1% ^
%OUTFILE%

:end

call echoRestore

endlocal & set l2gOUTFILE=%OUTFILE%& set l2gLINE_LEN=%LINE_LEN%&set l2gERR=%ERR%```

### curve3d.bat

```rem From image %1
rem and profile %2 (white line on black background),
rem creates output %3, curving the image around the extruded profile.
rem %4 is tilt of object. >0 tilts up (ie camera tilt down).
rem %5 is percentage perspective narrowing (0=no perspective).
rem %6 is percentage perspective shortening
@rem
@rem  Updated:
@rem    20-August-2022 Upgraded for IM v7.
@rem

@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 c3d

set LINE_FILE=%2
set LINE_BASE=%~n2

if not "%3"=="" if not "%3"=="." set OUTFILE=%3

set ALPHA=%4
if "%ALPHA%"=="." set ALPHA=
if "%ALPHA%"=="" set ALPHA=0

set NARROW_PC=%5
if "%NARROW_PC%"=="." set NARROW_PC=
if "%NARROW_PC%"=="" set NARROW_PC=0

set SHORTEN_PC=%6
if "%SHORTEN_PC%"=="." set SHORTEN_PC=
if "%SHORTEN_PC%"=="" set SHORTEN_PC=0

:: FIXME if alpha less than 0, different perspective.

call %PICTBAT%colTrim %LINE_FILE% Black

echo ctRECT=%ctRECT%  ctW=%ctW%  ctH=%ctH%

:: Grad file is named after the line, not the first input file.

set TMP_INPUT=c3d_tmp_inp.miff
set FILL_FILE=c3d_fill.miff
set XDISP_TRIAL=c3d_xdisp_trial.miff
set Y_PERSP=c3d_y_persp.miff

:: For better performance, don't delete GRAD_FILE.
::

set line_len=

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format %%[line:*] ^
set LINE=%%L
set !LINE::=_!
)
)

if "%line_len%"=="" (
%LINE_FILE% ^

%IMG7%magick ^
-crop %ctRECT% +repage ^

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format %%[line:*] ^
set LINE=%%L
set !LINE::=_!
)
)

echo line_len=%line_len% dp2gERR=%dp2gERR%
if "%line_len%"=="" exit /B 1

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-resize %line_len%x ^
-format "INP_WW=%%w\nINP_HH=%%h" ^
+write info: ^
%TMP_INPUT%`) do set %%L

echo INP_WW=%INP_WW% INP_HH=%INP_HH%

call %PICTBAT%shiftFill ^

if ERRORLEVEL 1 exit /B 1

%IMG7%magick ^
%FILL_FILE% ^
-crop 0x1+0+0 +repage ^
-scale "x%INP_HH%^!" ^
-gravity West ^
-background White ^
-extent %INP_WW%x%INP_HH% ^
( -clone 0 ^
-sparse-color bilinear ^
"0,0 Black 0,%%[fx:h-1] White" ^
) ^
( -clone 0 ^
-fill gray(50%%) -colorize 100 ^
) ^
-combine ^
-set colorspace sRGB ^
%TMP_INPUT% ^
+swap ^
-compose Distort ^
-set option:compose:args 100%%x100%% ^
-composite ^
-crop %ctW%x+0+0 +repage ^
%XDISP_TRIAL%

for /F "useback" %%L in (`%IMG7%magick identify ^
xc:`) do set %%L

for /F "useback" %%L in (`%IMG7%magick identify ^
-format "OUT_HH=%%[fx:%inHcosA% + %ctHsinA%]\nctHsinA_2=%%[fx:%ctHsinA%/2]\nIS_NEG=%%[fx:%ALPHA%<0?1:0]"
xc:`) do set %%L

echo inHcosA=%inHcosA% ctHsinA=%ctHsinA% ctHsinA_2=%ctHsinA_2% OUT_HH=%OUT_HH% IS_NEG=%IS_NEG%

set dNarrow=w*%NARROW_PC%/100

if %IS_NEG%==1 (

echo Angle is negative: %ANGLE%

set sNEG_Y=-negate

set PERSP=^
0,0,0,0,^
%%[fx:w-1],0,%%[fx:w-1],0,^
0,%%[fx:h-1],%%[fx:%dNarrow%],%%[fx:h-1],^
%%[fx:w-1],%%[fx:h-1],%%[fx:w-1-%dNarrow%],%%[fx:h-1]

) else (

echo Angle is positive: %ANGLE%

set sNEG_Y=

set PERSP=^
0,0,%%[fx:%dNarrow%],0,^
%%[fx:w-1],0,%%[fx:w-1-%dNarrow%],0,^
0,%%[fx:h-1],0,%%[fx:h-1],^
%%[fx:w-1],%%[fx:h-1],%%[fx:w-1],%%[fx:h-1]

)

%IMG7%magick ^
%LINE_FILE% ^
-crop %ctRECT% +repage ^
-morphology dilate rectangle:1x%ctH%+0+0 ^
-scale "x1^!" ^
( -clone 0 ^
-fill gray(50%%) -colorize 100 ^
) ^
( -clone 1 ) ^
-swap 0,1 ^
-combine ^
-set colorspace sRGB ^
-scale "x%OUT_HH%^!" ^
%sNEG_Y% ^
( +clone ^
+level %SHORTEN_PC%%% ^
) ^
( +clone ^
-sparse-color bilinear "0,0,White 0,%%[fx:h-1],Black" ^
) ^
-composite ^
%Y_PERSP%

%IMG7%magick ^
%XDISP_TRIAL% ^
-scale "x%inHcosA%^!" ^
-gravity Center -background None ^
-extent "x%OUT_HH%^!" ^
%Y_PERSP% ^
-virtual-pixel None ^
-compose Displace ^
-set option:compose:args 0x%ctHsinA_2% ^
-composite ^
-virtual-pixel None ^
-distort Perspective "%PERSP%" ^
%OUTFILE%

call echoRestore

endlocal & set c3dOUTFILE=%OUTFILE%```

All images on this page were created by the commands shown, using:

`%IMG7%magick -version`
```Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
Features: Cipher DPC HDRI OpenCL
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 (193231332)```

Source file for this web page is curve3d.h1. To re-create this web page, execute "procH1 curve3d".

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.