snibgo's ImageMagick pages

Shape to shape

We can distort a shape to match another shape.

The general problem is to distort an arbitrary shape into another arbitrary shape.

Building blocks for this page are:

Some commands and scripts on this page use those process modules. Without those modules, the commands won't work.

Sample input

We create a sample source, with a yellow grid and red boundary to clearly show the distortions.

set SRC=s2s_src.png

%IMG7%magick ^
  %PICTLIB%20140430\GOPR0180.jpg ^
  -resize 400x400 ^
  %SRC%

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^
  %SRC%`) do set %%L

call %PICTBAT%grid %WW% %HH% 6 4 1 yellow none red

%IMG7%magick ^
  %SRC% ^
  grid.png ^
  -composite ^
  %SRC%
s2s_src.pngjpg

Using "-fx"

If the required shape is defined mathematically, we can distort it with "-fx". Fx is slow, but accurate and easy to use.

The examples below use a number of fx built-in variables or "symbols". See the official page Apply Special Effects to an Image with an Fx Expression.

Symbol Meaning
i column offset: 0, 1, ... w-1
j row offset: 0, 1, ... h-1
w width; number of columns
h height; number of rows

My own variables use capital letters. For efficiency, we pre-calculate w/2 and h/2:

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "W_2=%%[fx:w/2]\nH_2=%%[fx:h/2]" ^
  %SRC%`) do set %%L

Examples, with horizontal displacement only:

No-op

%IMG7%magick ^
  %SRC% ^
  -fx "p{i,j}" ^
  s2s_fx0.png
s2s_fx0.pngjpg

Triangle

%IMG7%magick ^
  %SRC% ^
  -fx ^
p{j==0?0:i*h/j,j} ^
  s2s_fx1.png
s2s_fx1.pngjpg

Triangle

%IMG7%magick ^
  %SRC% ^
  -fx ^
p{(j==0?0:((i/w-0.5)*h/j+0.5)*w),j} ^
  s2s_fx2.png
s2s_fx2.pngjpg

Diamond

%IMG7%magick ^
  %SRC% ^
  -fx ^
JH=(j^>%H_2%)?h-j-1:j;^
p{JH==0?0:((i/w-0.5)*h/JH+0.5)*w,j} ^
  s2s_fx3.png
s2s_fx3.pngjpg

Hexagon

%IMG7%magick ^
  %SRC% ^
  -fx ^
JH=(j^>%H_2%)?h-j-1:j;^
FF=h/(JH+%H_2%);^
p{((i/w-0.5)*FF+0.5)*w,j} ^
  s2s_fx4.png

If h/w = sqrt(3/4), the hexagon will be regular.

s2s_fx4.pngjpg

We can invert the hexagon transformation, or any other,
by dividing by F instead of multiplying.

%IMG7%magick ^
  s2s_fx4.png ^
  -fx ^
JH=(j^>%H_2%)?h-j-1:j;^
FF=h/(JH+%H_2%);^
p{((i/w-0.5)/FF+0.5)*w,j} ^
  s2s_fx4a.png
s2s_fx4a.pngjpg

Ellipse

%IMG7%magick ^
  %SRC% ^
  -fx ^
JH=(j^>%H_2%)?h-j-1:j;^
JHM=(%H_2%-JH)/h;^
FF=(JH==0)?0:1/(sqrt(1-4*JHM*JHM));^
p{((i/w-0.5)*FF+0.5)*w,j} ^
  s2s_fx5.png
s2s_fx5.pngjpg

Vertical displacements can be performed by rotation, or adapting the above examples by swapping i and j, and w and h:

Ellipse by vertical displacement

%IMG7%magick ^
  %SRC% ^
  -fx ^
IW=(i^>%W_2%)?w-i-1:i;^
IWM=(%W_2%-IW)/w;^
FF=(IW==0)?0:1/(sqrt(1-4*IWM*IWM));^
p{i,((j/h-0.5)*FF+0.5)*h} ^
  s2s_fx6.png
s2s_fx6.pngjpg

Some fancy displacements:

Ellipse, discontinuous at central vertical

%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RR=sqrt(DX*DX+DY*DY);^
FX=abs(cos(AA));^
FX=(FX==0)?0.00001:FX;^
p{((i/w-0.5)/FX+0.5)*w,j} ^
  s2s_fx7.png
s2s_fx7.pngjpg

Ellipse, discontinuous at central horizontal

%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RR=sqrt(DX*DX+DY*DY);^
FY=abs(sin(AA));^
FY=(FY==0)?0.00001:FY;^
p{i,((j/h-0.5)/FY+0.5)*h} ^
  s2s_fx8.png
s2s_fx8.pngjpg

Points on the ellipse have no distortion

%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RR=sqrt(DX*DX+DY*DY);^
FX=RR;^
FY=RR;^
p{((i/w-0.5)*FX+0.5)*w,((j/h-0.5)*FY+0.5)*h} ^
  s2s_fx9.png
s2s_fx9.pngjpg

Angle discontinuity at the diagonals.

%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RS=1/max(abs(cos(AA)),abs(sin(AA)));^
FX=RS;^
FY=RS;^
p{((i/w-0.5)*FX+0.5)*w,((j/h-0.5)*FY+0.5)*h} ^
  s2s_fx10.png
s2s_fx10.pngjpg
%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RR=sqrt(DX*DX+DY*DY);^
RS=abs(cos(AA));^
RS=(RS==0)?0.00001:RS;^
FX=RS;^
FY=RS;^
p{((i/w-0.5)/FX+0.5)*w,((j/h-0.5)/FY+0.5)*h} ^
  s2s_fx11.png
s2s_fx11.pngjpg
%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
RS=(abs(cos(AA))+abs(sin(AA)))/2;^
RS=(RS==0)?0.00001:RS;^
FX=RS;^
FY=RS;^
p{((i/w-0.5)/FX+0.5)*w,((j/h-0.5)/FY+0.5)*h} ^
  s2s_fx13.png
s2s_fx13.pngjpg
%IMG7%magick ^
  %SRC% ^
  -fx ^
DX=i/%W_2%-1;^
DY=j/%H_2%-1;^
AA=atan2(DY,DX);^
FX=abs(sin(AA));^
FY=abs(cos(AA));^
FX=(FX==0)?0.00001:FX;^
FY=(FY==0)?0.00001:FY;^
p{((i/w-0.5)/FX+0.5)*w,((j/h-0.5)/FY+0.5)*h} ^
  s2s_fx15.png
s2s_fx15.pngjpg

Displacement maps: method

For an abritrary shape, we can make a displacement map from the mask.

For a mask, draw a white shape on a black background. For this example, we ensure the white shape extends to the top and bottom edges. Each horizontal line has contiguous white pixels.

s2s_cent_mask.png

s2s_cent_mask.png

We will distort the source horizontally to fit within the white shape. We do this by creating a relative displacement map that fits within the white shape. The map will be black at the left, white at the right, with a smooth gradient between. This will displace pixels from the left of the source rectangle to the left of the shape, and from the right of the source to the right of the shape.

We will also create the inverse map, so we can displace from the shape back to the source rectangle.

As a first step towards the relative displacement map, we create an absolute displacement map.

Creating the absolute displacement map with "pure" IM operations is messy, but is trivially simple with my cumulhisto process. (See Customising ImageMagick: cumulate histogram.)

Create the absolute and relative maps:

%IM7DEV%magick ^
  s2s_cent_mask.png ^
  +depth ^
  -process 'cumulhisto norm' ^
  -write s2s_cent_absmap.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White" ^
  ) ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  s2s_cent_relmap.png
s2s_cent_absmap.png s2s_cent_relmap.png

Apply the relative map,
and mask the result down to the original mask:

%IMG7%magick ^
  %SRC% ^
  s2s_cent_relmap.png ^
  -compose Displace ^
  -set option:compose:args %Wm1%x0 -composite ^
  s2s_cent_mask.png ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  s2s_cm.png

Horizontal lines remain straight; vertical lines are distorted.

s2s_cm.pngjpg

From the distorted image, we can restore the original rectangular image. We do this by taking the forwards absolute map, inverting it, and converting this to make the relative map:

Create the inverse absolute and relative maps:

%IM7DEV%magick ^
  s2s_cent_absmap.png ^
  +depth ^
  -process 'invclut' ^
  +write s2s_cent_absmapinv.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White" ^
  ) ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  s2s_cent_relmapinv.png
s2s_cent_absmapinv.png s2s_cent_relmapinv.png

Apply the inverse relative map:

%IMG7%magick ^
  s2s_cm.png ^
  s2s_cent_relmapinv.png ^
  -compose Displace ^
  -set option:compose:args %Wm1%x0 -composite ^
  s2s_cminv.png
s2s_cminv.pngjpg

The process modules cumulhisto and invclut operate horizontally, treating each line as a clut. If vertical displacement is wanted, we rotate the mask before and after cumulhisto, and we make a gradient from top to bottom instead of left to right.

Create the rotated absolute and relative maps:

%IM7DEV%magick ^
  s2s_cent_mask.png ^
  -rotate -90 ^
  -process 'cumulhisto norm' ^
  -rotate 90 ^
  +depth ^
  +write s2s_cent_absmap_r.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black 0,%%[fx:h-1] White" ^
  ) ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  s2s_cent_relmap_r.png
s2s_cent_absmap_r.png s2s_cent_relmap_r.png

Apply the rotated relative map as a vertical displacement:

%IMG7%magick ^
  %SRC% ^
  s2s_cent_relmap_r.png ^
  -virtual-pixel Black ^
  -compose Displace ^
  -set option:compose:args 0x%Hm1% -composite ^
  s2s_cm_r.png

Vertical lines remain straight; horizontal lines are distorted.

s2s_cm_r.pngjpg

This has trimmed the right and left sides because the mask doesn't extend that far. We can first shrink the source to the width of the mask, then apply the vertical displacement.

Find the mask dimensions:

for /F "usebackq tokens=1-6 delims=x+-," %%A ^
in (`%IMG7%magick ^
  s2s_cent_mask.png ^
  -bordercolor Black -border 1 ^
  -format "%%@,%%w,%%h" ^
  info:`) ^
do (
  set /A TW=%%A
  set /A TH=%%B
  set /A TX=%%C-1
  set /A TY=%%D-1
  set /A WW=%%E-2
  set /A HH=%%F-2
)
echo TW=%TW% TH=%TH% TX=%TX% TY=%TY% WW=%WW% HH=%HH%
TW=259 TH=300 TX=83 TY=0 WW=400 HH=300
%IMG7%magick ^
  %SRC% ^
  -resize "%TW%x%TH%^!" ^
  -background Blue ^
  -extent %WW%x%HH%-%TX%-%TY% ^
  s2s_cent_relmap_r.png ^
  -compose Displace ^
  -set option:compose:args 0x%Hm1% -composite ^
  s2s_cm_rz.png

The entire source is contained within the mask.

Vertical lines remain straight; horizontal lines are distorted.

As before, the result could be masked down to the required shape.

s2s_cm_rz.pngjpg

Displacement maps: script

The above processes can be combined in a script, sh2shLinear.bat. For help, call it with no arguments.

call %PICTBAT%sh2shLinear 

cmd /c exit /B 0
@rem Given image %1
@rem and mask %2, white shape on black background,
@rem distorts image into white shape with horizontal or linear displacement.
@rem
@rem Optional arguments:
@rem   %3 direction of displacement
@rem        h=horizontal, v=vertical [h]
@rem   %4 proportion towards displacement (0 to 100) [100]
@rem   %5 reverse transformation (shape -> rectangle) NYI
@rem        f=forwards, r=reverse [f]
@rem   %6 output filename [*_s2sl.*]
@rem   %7 mask-down (make transparent where mask is black)
@rem        y=yes, n=no [y]
@rem   %8 filename for output relative displacement map [no file]
@rem
@rem Updated:
@rem   27-August-2022 for IM v7.
@rem

Horizontal:

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_cent_mask.png
s2s_src_s2sl.pngjpg

Vertical:

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_cent_mask.png v . . s2s_linscr2.png
s2s_linscr2.pngjpg

Horizontal, 75% of effect, without masking-down:

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_cent_mask.png h 75 . s2s_linscr3.png 0
s2s_linscr3.pngjpg

Horizontal, 75% of effect, reverse, without masking-down:

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_cent_mask.png h 75 r s2s_linscr4.png 0
s2s_linscr4.pngjpg

Horizontal, 100% of effect, reverse, without masking-down,
taking input from the forwards distortion:

call %PICTBAT%sh2shLinear ^
  s2s_src_s2sl.png s2s_cent_mask.png h . r s2s_linscr5.png 0

As expected, we are back where we started.

s2s_linscr5.pngjpg

Horizontal stretch method

If we have a shape that we want to stretch horizontally into a rectangle, here is a simple method that doesn't use process modules.

  1. Make an identity absolute displacement map the size of the rectangle.
  2. Mark as blue the pixels near the right and left edges that are outside the shape.
  3. Crop into lines. Trim any blue from the ends of each line. Resize all lines to the full width. Append the lines vertically.
  4. Now we have an absolute displacement map that transforms the shape into the bounding rectangle. Apply this map.

The script stretchHoriz.bat does this. Note that, unlike other scripts on this page, it stretches out only to the bounding rectangle of the white pixels in the mask. If the requirement is to stretch to the image rectangle, remove the %MASK_CROP% operations.

call %PICTBAT%stretchHoriz ^
  s2s_src_s2sl.png s2s_cent_mask.png s2s_strh.png

We are back where we started, but with a changed aspect ratio.

s2s_strh.pngjpg

Although designed for stretching horizontally, it can instead stretch vertically, by rotations.

The script has provision for supersampling the image and mask, and subsampling at the end, which slightly improves quality. However, a bigger improvement is often available by supersampling the image first and making the mask from that.

Polar displacement: method

We can unroll both source and mask with depolar, apply a vertical displacement, and roll up the result.

Unroll the source:

%IMG7%magick ^
  %SRC% ^
  -background Blue ^
  -virtual-pixel Background ^
  -distort depolar -1 ^
  s2s_src_dep.png
s2s_src_dep.png

Unroll the mask:

%IMG7%magick ^
  s2s_cent_mask.png ^
  -background Blue ^
  -virtual-pixel Background ^
  -distort depolar -1 ^
  s2s_mask_dep.png
s2s_mask_dep.png

Looking at the unrolled mask, we want a vertical displacement map that will move the upper edge of the blue area to the lower edge of the white area. We can do this in two stages. The first stage will expand the image downwards from the top of the blue area to the lower edge of the blue area (which is the bottom edge of the rectangle). The second stage will shrink the image upwards from the lower edge of the blue area to the lower edge of the white area. Each stage needs a relative displacement map.

Stage 1 distorts the source rectangle into a circle (in the same coordinate system). Stage 2 distorts the circle into the required shape.

We can either apply these two maps sequentially (Test stage 1 and Test stage 2 below), or we can combine the two maps, then apply this combined map.

Stage 1:

%IM7DEV%magick ^
  s2s_cent_mask.png ^
  +depth ^
  -fill White -colorize 100 ^
  -background Black ^
  -virtual-pixel Background ^
  -distort depolar -1 ^
  -rotate -90 ^
+write s2s_mask_dep_st1b.png ^
  -process 'cumulhisto norm' ^
  -process 'invclut' ^
  +write s2s_mask_dep_st1a.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White" ^
  ) ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  s2s_mask_dep_st1.png
s2s_mask_dep_st1b.png s2s_mask_dep_st1a.png s2s_mask_dep_st1.png

Test stage 1:

%IMG7%magick ^
  s2s_mask_dep_st1.png ^
  ( +clone -sparse-color Bilinear ^
      "0,0 Red %%[fx:w-1],0 Lime" ) ^
  +swap ^
  -background Blue -virtual-pixel Background ^
  -compose Displace ^
  -set option:compose:args %Wm1%x0 -composite ^
  s2s_pol_test1a.png

%IMG7%magick ^
  s2s_src_dep.png ^
  -rotate -90 ^
  s2s_mask_dep_st1.png ^
  -compose Displace ^
  -set option:compose:args %Hm1%x0 -composite ^
  s2s_pol_test1b.png
s2s_pol_test1a.pngjpg s2s_pol_test1b.pngjpg

Stage 2:

%IM7DEV%magick ^
  s2s_cent_mask.png ^
  +depth ^
  -background Black ^
  -virtual-pixel Background ^
  -distort depolar -1 ^
  -rotate -90 ^
+write s2s_mask_dep_st2b.png ^
  -process 'cumulhisto norm' ^
  +write s2s_mask_dep_st2a.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White" ^
  ) ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  s2s_mask_dep_st2.png
s2s_mask_dep_st2b.png s2s_mask_dep_st2a.png s2s_mask_dep_st2.png

Test stage 2:

%IMG7%magick ^
  s2s_mask_dep_st2.png ^
  ( +clone -sparse-color Bilinear ^
      "0,0 Red %%[fx:w-1],0 Lime" ) ^
  +swap ^
  -background Blue -virtual-pixel Background ^
  -compose Displace ^
  -set option:compose:args %Hm1%x0 -composite ^
  s2s_pol_test2a.png

%IMG7%magick ^
  s2s_pol_test1b.png ^
  s2s_mask_dep_st2.png ^
  -compose Displace ^
  -set option:compose:args %Hm1%x0 -composite ^
  s2s_pol_test2b.png

%IMG7%magick ^
  s2s_pol_test2b.png ^
  -rotate 90 ^
  -distort polar -1 ^
  s2s_pol_test2b_out.png
s2s_pol_test2a.pngjpg s2s_pol_test2b.pngjpg s2s_pol_test2b_out.pngjpg

Combine the two maps, and rotate:

%IMG7%magick ^
  s2s_mask_dep_st1.png ^
  s2s_mask_dep_st2.png ^
  -compose Mathematics ^
    -define compose:args=0,1,1,-0.5 ^
    -composite ^
  -rotate 90 ^
  s2s_mask_comb12a.png

%IMG7%magick ^
  s2s_mask_dep_st1.png ^
  ( +clone ^
    -sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White" ^
    -write mpr:IDENT ^
  ) ^
  +swap ^
  -compose Displace ^
  -set option:compose:args %Hm1%x0 -composite ^
  s2s_mask_dep_st2.png ^
  -compose Displace ^
  -set option:compose:args %Hm1%x0 -composite ^
  mpr:IDENT ^
  -compose Mathematics ^
    -define compose:args=0,-0.5,0.5,0.5 ^
    -composite ^
  -rotate 90 ^
  s2s_mask_comb12.png
s2s_mask_comb12a.png s2s_mask_comb12.png

Apply this rotated combined relative map:

%IMG7%magick ^
  s2s_src_dep.png ^
  s2s_mask_comb12.png ^
  -compose Displace ^
  -set option:compose:args 0x%Hm1% -composite ^
  s2s_pol_out.png
s2s_pol_out.png

Polar the result:

%IMG7%magick ^
  s2s_pol_out.png ^
  -distort polar -1 ^
  s2s_pol_out2.png
s2s_pol_out2.png

One advantage of a combined displacement map is that it enables a simple transition between the source and the distortion, for example 75% towards the distortion.

:

%IMG7%magick ^
  s2s_mask_comb12.png ^
  -fill gray(50%%) -colorize 25 ^
  s2s_src_dep.png ^
  +swap ^
  -compose Displace ^
  -set option:compose:args 0x%Hm1% -composite ^
  -distort polar -1 ^
  s2s_pol_bl_out.png
s2s_pol_bl_out.png

Polar displacement: script

The above processes can be combined in a script, sh2shPolar.bat. For help, call it with no arguments.

call %PICTBAT%sh2shPolar 

cmd /c exit /B 0
@rem Given image %1
@rem and mask %2, white shape on black background,
@rem distorts image into white shape with horizontal or polar displacement.
@rem
@rem Optional arguments:
@rem   %3 proportion towards displacement (0 to 100) [100]
@rem   %4 reverse transformation (shape -> rectangle)
@rem        f=forwards, r=reverse (r is buggy; FIXME) [f]
@rem   %5 output filename [*_s2sp.*]
@rem   %6 mask-down (make transparent where mask is black)
@rem        1=yes, 0=no [1]
@rem   %7 filename for output relative displacement map [no file] (NYI)
@rem
@rem Also uses:
@rem   s2spSUP_SAMP Factor for supersampling [default 1].
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem
@rem FIXME: also need method of specifying central coord.
@rem        (If neither specified, leave blank in "-distort".)
@rem
rem IM bug? if %OUTFILE% is .png, we get corrupt file, unless we have "-depth 16".

Default:

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png
s2s_src_s2sp.png

75% of effect, no mask:

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png 75 . s2s_pol2.png 0
s2s_pol2.pngjpg

With supersampling:

set s2spSUP_SAMP=2

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png . . s2s_pol3.png
s2s_pol3.pngjpg

With more supersampling:

set s2spSUP_SAMP=4

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png . . s2s_pol4.png

set s2spSUP_SAMP=
s2s_pol4.pngjpg

With supersampling 2, no mask:

set s2spSUP_SAMP=2

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png . . s2s_pol5.png 0

set s2spSUP_SAMP=
s2s_pol5.pngjpg

Reverse transformation of the source image:

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask.png . r s2s_pol6.png 0
s2s_pol6.pngjpg

Reverse transformation of the forward transformation:

set s2spSUP_SAMP=

call %PICTBAT%sh2shPolar ^
  s2s_pol5.png s2s_cent_mask.png . r s2s_pol7.png 0

set s2spSUP_SAMP=

(This is clearly wrong.)

s2s_pol7.pngjpg

Supersampling factor 2 seems to work well. Processing time is proportional to the square of the supersampling factor.

Reentrant masks

The mask used above have the property that each white line (horizontal, vertical or radial, depending on the method) is contiguous. We can break this condition. When the mask is not contiguous, source pixels are spread across that part of the mask.

s2s_cent_mask2.png:

s2s_cent_mask2.png

Horizontal linear, so we get horizontal spreading of pixels:

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_cent_mask2.png . . . s2s_lin_nc.png 0
s2s_lin_nc.png

Polar, so we get radial spreading of pixels:

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_cent_mask2.png . . s2s_pol_nc.png 0
s2s_pol_nc.png

Mathematical shapes with the scripts

Mathematical shapes can be used with these scripts. Examples below show the mask, a horizontal linear transformation, and a polar transformation.

Rectangle:

set s2spSUP_SAMP=2

%IMG7%magick ^
  %SRC% ^
  -fill #000 -colorize 100 ^
  -fill #fff ^
  -draw "rectangle 100,75 300,225" ^
  s2s_ms_m1.png

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_ms_m1.png . . . s2s_ms_l1.png 0

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_ms_m1.png . . s2s_ms_p1.png 0
s2s_ms_m1.png s2s_ms_l1.png s2s_ms_p1.png

Off-centre rectangle:

%IMG7%magick ^
  %SRC% ^
  -fill #000 -colorize 100 ^
  -fill #fff ^
  -draw "rectangle 50,25 250,175" ^
  s2s_ms_m2.png

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_ms_m2.png . . . s2s_ms_l2.png 0

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_ms_m2.png . . s2s_ms_p2.png 0
s2s_ms_m2.png s2s_ms_l2.png s2s_ms_p2.png

Circle:

%IMG7%magick ^
  %SRC% ^
  -fill #000 -colorize 100 ^
  -fill #fff ^
  -draw "circle 200,150 200,0" ^
  s2s_ms_m3.png

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_ms_m3.png . . . s2s_ms_l3.png 0

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_ms_m3.png . . s2s_ms_p3.png 0
s2s_ms_m3.png s2s_ms_l3.png s2s_ms_p3.png

Off-center circle:

%IMG7%magick ^
  %SRC% ^
  -fill #000 -colorize 100 ^
  -fill #fff ^
  -draw "circle 100,100 100,0" ^
  s2s_ms_m4.png

call %PICTBAT%sh2shLinear ^
  %SRC% s2s_ms_m4.png . . . s2s_ms_l4.png 0

call %PICTBAT%sh2shPolar ^
  %SRC% s2s_ms_m4.png . . s2s_ms_p4.png 0

set s2spSUP_SAMP=
s2s_ms_m4.png s2s_ms_l4.png s2s_ms_p4.png

Future

Techniques shown here might be used to morph between shapes, possibly with animation.

Scripts

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

grid.bat

rem Makes a grid with specified number of intervals.
rem %1,%2 are width and height.
rem %3 and %4 are number of gaps between horizontal and vertical grid lines, >=1 (default 4)
rem %5 is whether to draw diagonal (default 0)
rem %6 is grid colour (default white)
rem %7 is background colour, can be "none" (default black)
rem %8 is colour for boundary
rem %9 is output file (default grid.png)
@rem
@rem Also uses:
@rem   gridSTROKE_WIDTH default 1
@rem
@rem
@rem Updated:
@rem   5-August-2022 for IM v7.
@rem

rem No required arguments.
rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

set WW=%1
if "%WW%"=="." set WW=
if "%WW%"=="" set WW=150
set HH=%2
if "%HH%"=="." set HH=
if "%HH%"=="" set HH=100

set nX=%3
if "%nX%"=="." set nX=
if "%nX%"=="" set nX=4
set nY=%4
if "%nY%"=="." set nY=
if "%nY%"=="" set nY=4

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

set GRID_COL=%6
if "%GRID_COL%"=="." set GRID_COL=
if "%GRID_COL%"=="" set GRID_COL=White

set BACK_COL=%7
if "%BACK_COL%"=="." set BACK_COL=
if "%BACK_COL%"=="" set BACK_COL=Black

set BOUND_COL=%8
if "%BOUND_COL%"=="." set BOUND_COL=

set OUTFILE=%9
if "%OUTFILE%"=="." set OUTFILE=
if "%OUTFILE%"=="" set OUTFILE=grid.png

if "%gridSTROKE_WIDTH%"=="" set gridSTROKE_WIDTH=0


FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify ^
  -format ^
Wm1^=%%[fx:%WW%-1]\n^
Hm1^=%%[fx:%HH%-1]\n^
dX^=%%[fx:%WW%/%nX%]\n^
dY^=%%[fx:%HH%/%nY%]\n^
nXm1^=%%[fx:%nX%-1]\n^
nYm1^=%%[fx:%nY%-1] ^
  xc:`) ^
DO set %%L

set LINESFILE=%TEMP%\gridlines.txt
del %LINESFILE% 2>nul

if %nXm1% GTR 0 for /L %%i in (1, 1, %nXm1%) do (
  FOR /F %%L IN ('%IMG7%magick identify ^
    -format "v=%%[fx:int(%dX%*%%i+0.5)]" xc:') DO @set %%L
  echo line !v!,0 !v!,%Hm1% >>%LINESFILE%
)

if %nYm1% GTR 0 for /L %%i in (1, 1, %nYm1%) do (
  FOR /F %%L IN ('%IMG7%magick identify ^
    -format "v=%%[fx:int(%dY%*%%i+0.5)]" xc:') DO @set %%L
  echo line 0,!v! %Wm1%,!v! >>%LINESFILE%
)

if %DO_DIAG%==1 (
  echo line %Wm1%,0 0,%Hm1% >>%LINESFILE%
  echo line 0,0 %Wm1%,%Hm1% >>%LINESFILE%
)

if not "%BOUND_COL%"=="" (
  echo fill %BOUND_COL% >>%LINESFILE%
  echo line 0,0 %Wm1%,0 >>%LINESFILE%
  echo line 0,0 0,%Hm1% >>%LINESFILE%
  echo line %Wm1%,0 %Wm1%,%Hm1% >>%LINESFILE%
  echo line 0,%Hm1% %Wm1%,%Hm1% >>%LINESFILE%
)

if %gridSTROKE_WIDTH%==1 (
  set sSTROKEW=
) else (
  set sSTROKEW=-strokewidth %gridSTROKE_WIDTH%
)


%IMG7%magick ^
  -size %WW%x%HH% ^
  xc:%BACK_COL% ^
  -fill None ^
  -stroke %GRID_COL% ^
  %sSTROKEW% ^
  -draw "@%LINESFILE%" ^
  %OUTFILE%

call echoRestore

sh2shLinear.bat

@rem Given image %1
@rem and mask %2, white shape on black background,
@rem distorts image into white shape with horizontal or linear displacement.
@rem
@rem Optional arguments:
@rem   %3 direction of displacement
@rem        h=horizontal, v=vertical [h]
@rem   %4 proportion towards displacement (0 to 100) [100]
@rem   %5 reverse transformation (shape -> rectangle) NYI
@rem        f=forwards, r=reverse [f]
@rem   %6 output filename [*_s2sl.*]
@rem   %7 mask-down (make transparent where mask is black)
@rem        y=yes, n=no [y]
@rem   %8 filename for output relative displacement map [no file]
@rem
@rem Updated:
@rem   27-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 s2sl

set MASK_FILE=%2

set DIRN=%3
if "%DIRN%"=="." set DIRN=
if "%DIRN%"=="" set DIRN=h

set PROP_DIST=%4
if "%PROP_DIST%"=="." set PROP_DIST=
if "%PROP_DIST%"=="" set PROP_DIST=100

set FOR_REV=%5
if "%FOR_REV%"=="." set FOR_REV=
if "%FOR_REV%"=="" set FOR_REV=f

set OUTF=%6
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set MASK_DOWN=%7
if "%MASK_DOWN%"=="." set MASK_DOWN=
if "%MASK_DOWN%"=="" set MASK_DOWN=y

set OUT_MAP=%8
if "%OUT_MAP%"=="." set OUT_MAP=
if "%OUT_MAP%"=="" set OUT_MAP=


for /F "usebackq tokens=1-6 delims=x+-," %%A ^
in (`%IMG7%magick ^
  %MASK_FILE% ^
  -bordercolor Black -border 1 ^
  -format "%%@,%%w,%%h" ^
  info:`) ^
do (
  set /A MTW=%%A
  set /A MTH=%%B
  set /A MTX=%%C-1
  set /A MTY=%%D-1
  set /A MWW=%%E-2
  set /A MHH=%%F-2
)
echo Mask: MTW=%MTW% MTH=%MTH% MTX=%MTX% MTY=%MTY% MWW=%MWW% MHH=%MHH%


set sSHRINK=
if /I "%DIRN%"=="h" if not %MTH%==%MHH% (
  echo Shrink height

  set sSHRINK=-resize "%TW%x%TH%^^^!" ^
  -background Blue ^
  -extent %WW%x%HH%-%TX%-%TY%
)

if /I "%DIRN%"=="v" if not %MTW%==%MWW% (
  echo Shrink width

  set sSHRINK=-resize "%TW%x%TH%^^^!" ^
  -background Blue ^
  -extent %WW%x%HH%-%TX%-%TY%
)


if /I "%DIRN%"=="h" (
  set sSPARSE=-sparse-color Bilinear ^
      "0,0 Black %%[fx:w-1],0 White"
  set ROTR=
  set ROTF=

  set sDISP=-set option:compose:args %MWW%x0
)

if /I "%DIRN%"=="v" (
  set sSPARSE=-sparse-color Bilinear ^
      0,0,#000,0,%%[fx:h-1],#fff
  set ROTR=-rotate -90
  set ROTF=-rotate 90

  set sDISP=-set option:compose:args 0x%Hm1%
)

if "%PROP_DIST%"=="100" (
  set sPROP=
  set sMASK=mpr:MASK
) else (
  for /F "usebackq" %%L in (`%IMG7%magick identify ^
    -format="%%[fx:100-%PROP_DIST%]" ^
    xc:`) do set sPROP=-fill gray^(50%%^) -colorize %%L

  set sMASK=^^^( +clone -fill #fff -colorize 100 ^
    -virtual-pixel Black mpr:DISP ^
    -compose Displace ^
    %sDISP% -composite -write s.png ^^^)
)

echo sPROP=%sPROP%
echo sDISP=%sDISP%
echo sMASK=%sMASK%

if /I "%FOR_REV%"=="r" (
  set sINVERSE=-process invclut
) else (
  set sINVERSE=
)

if /I "%MASK_DOWN%"=="y" (
  set sMASK_DOWN=%sMASK% -alpha off -compose CopyOpacity -composite
) else (
  set sMASK_DOWN=
)

%IM7DEV%magick ^
  %INFILE% ^
  %sSHRINK% ^
  %MASK_FILE% ^
  +depth ^
  ( -clone 1 ^
    -write mpr:MASK ^
    %ROTR% ^
    -process 'cumulhisto norm' ^
    %sINVERSE% ^
    %ROTF% ^
    ( +clone ^
      %sSPARSE% ^
    ) ^
    -compose Mathematics ^
      -define compose:args=0,-0.5,0.5,0.5 ^
      -composite ^
    %sPROP% ^
    -write mpr:DISP ^
    +write sh2sh_map.png ^
  ) ^
  -delete 1 ^
  -compose Displace ^
  %sDISP% -composite ^
  %sMASK_DOWN% ^
  %OUTFILE%

call echoRestore

sh2shPolar.bat

@rem Given image %1
@rem and mask %2, white shape on black background,
@rem distorts image into white shape with horizontal or polar displacement.
@rem
@rem Optional arguments:
@rem   %3 proportion towards displacement (0 to 100) [100]
@rem   %4 reverse transformation (shape -> rectangle)
@rem        f=forwards, r=reverse (r is buggy; FIXME) [f]
@rem   %5 output filename [*_s2sp.*]
@rem   %6 mask-down (make transparent where mask is black)
@rem        1=yes, 0=no [1]
@rem   %7 filename for output relative displacement map [no file] (NYI)
@rem
@rem Also uses:
@rem   s2spSUP_SAMP Factor for supersampling [default 1].
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem
@rem FIXME: also need method of specifying central coord.
@rem        (If neither specified, leave blank in "-distort".)
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 s2sp

set MASK_FILE=%2

set PROP_DIST=%3
if "%PROP_DIST%"=="." set PROP_DIST=
if "%PROP_DIST%"=="" set PROP_DIST=100

set FOR_REV=%4
if "%FOR_REV%"=="." set FOR_REV=
if "%FOR_REV%"=="" set FOR_REV=f

set OUTF=%5
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set MASK_DOWN=%6
if "%MASK_DOWN%"=="." set MASK_DOWN=
if "%MASK_DOWN%"=="" set MASK_DOWN=1

set OUT_MAP=%7
if "%OUT_MAP%"=="." set OUT_MAP=
if "%OUT_MAP%"=="" set OUT_MAP=

set SUP_SAMP=%s2spSUP_SAMP%
if "%SUP_SAMP%"=="" set SUP_SAMP=1


for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -precision 15 ^
  -format "Wm1=%%[fx:%SUP_SAMP%*w-1]\nHm1=%%[fx:%SUP_SAMP%*h-1]\nSUP_SAMPinvPc=%%[fx:100/%SUP_SAMP%]\nSUP_SAMPinv=%%[fx:1/%SUP_SAMP%]" ^
  %INFILE%`) do set %%L

set sUNROLL=-virtual-pixel Background ^
  -distort depolar -1

set sROLL=-distort polar -1

set sCOMPMATH=-compose Mathematics ^
  -define compose:args=0,-0.5,0.5,0.5 ^
  -composite

if "%PROP_DIST%"=="100" (
  set sPROP=
) else (
  for /F "usebackq" %%L in (`%IMG7%magick identify ^
    -format="%%[fx:100-%PROP_DIST%]" ^
    xc:`) do set sPROP=-fill gray^(50%%^) -colorize %%L
)

if /I "%FOR_REV%"=="r" (
  set sREVERSE=-rotate -90 -process invclut -rotate 90

  set sREVERSE=-rotate -90 ^
+write z0.png ^
    mpr:IDENT ^
    -compose Mathematics -define compose:args=0,1,1,-0.5 -composite ^
+write z1.png ^
    -process invclut ^
    mpr:IDENT ^
    -compose Mathematics -define compose:args=0,-1,1,0.5 -composite ^
+write z2.png ^
    -rotate 90

) else (
  set sREVERSE=
)

if "%MASK_DOWN%"=="1" (
  set sMASK_DOWN=mpr:MASK -alpha off -compose CopyOpacity -composite
) else (
  set sMASK_DOWN=
)

%IM7DEV%magick ^
  -define distort:scale=%SUP_SAMP% ^
  %INFILE% ^
  %sUNROLL% ^
  ( %MASK_FILE% -write mpr:MASK ) ^
  +depth ^
  ( -clone 1 ^
    ( -clone 0 ^
      -fill White -colorize 100 ^
      -background Black ^
      %sUNROLL% ^
      -rotate -90 ^
      -process 'cumulhisto norm' ^
      -process 'invclut' ^
      ( +clone ^
        -sparse-color Bilinear ^
          "0,0 Black %%[fx:w-1],0 White" ^
        -write mpr:IDENT ^
      ) ^
      %sCOMPMATH% ^
+write x0.png ^
      mpr:IDENT ^
      +swap ^
      -compose Displace ^
      -set option:compose:args %Hm1%x0 -composite ^
    ) ^
    ( -clone 0 ^
+write x1b.png ^
      -background Black ^
      %sUNROLL% ^
      -rotate -90 ^
      -process 'cumulhisto norm' ^
+write x1a.png ^
      mpr:IDENT ^
      %sCOMPMATH% ^
+write x1.png ^
    ) ^
    -delete 0 ^
-write info: ^
    -compose Displace ^
    -set option:compose:args %Hm1%x0 -composite ^
    mpr:IDENT ^
    %sCOMPMATH% ^
    -rotate 90 ^
    %sPROP% ^
    %sREVERSE% ^
+write x2.png ^
  ) ^
  -delete 1 ^
  -compose Displace ^
  -set option:compose:args 0x%Hm1% -composite ^
  -define distort:scale=%SUP_SAMPinv% ^
-virtual-pixel None ^
  -distort polar -1 ^
  %sMASK_DOWN% ^
  -depth 16 ^
  %OUTFILE%

rem IM bug? if %OUTFILE% is .png, we get corrupt file, unless we have "-depth 16".


call echoRestore

@endlocal & set sh2shOUTFILE=%OUTFILE%

stretchHoriz.bat

rem Given %1 is an image,
rem %2 is a mask, mostly white, with black at left and right edges,
rem makes %3 that has image stretched to fill the bounding rectangle of the white pixels.
rem %4 direction: h=horizontally, v=vertically. Default h.
rem %5 supersample percentage eg 400. Default 100 (no supersampling).
@rem
@rem Updated:
@rem   27-August-2022 for IM v7.
@rem

setlocal

set INFILE=%1
set MASK=%2
set OUTFILE=%3

set TMP_IN=sh_tmp.miff

set DIRN=%4
if "%DIRN%"=="." set DIRN=
if "%DIRN%"=="" set DIRN=h

set RESAMP_PC=%5
if "%RESAMP_PC%"=="." set RESAMP_PC=
if "%RESAMP_PC%"=="" set RESAMP_PC=100

if /I %DIRN%==h (
  set ROT_START=
  set ROT_END=
) else if /I %DIRN%==v (
  set ROT_START=-rotate -90
  set ROT_END=-rotate 90
) else (
  echo %0: Invalid direction [%DIRN%]
  exit /B 1
)

for /F "usebackq tokens=1-4 delims=x+" %%A in (`%IMG7%magick ^
  %MASK% ^
  -bordercolor Black -border 1 ^
  -format "%%@" ^
  info:`) do (
  set MW=%%A
  set MH=%%B
  set /A MX=%%C-1
  set /A MY=%%D-1
)

set MASK_CROP=-crop %MW%x%MH%+%MX%+%MY% +repage

if %RESAMP_PC%==100 (
  set RESAMP_START=
  set RESAMP_END=
) else (
  set RESAMP_START=-resize %RESAMP_PC%%%
  set RESAMP_END=-resize "%MW%x%MH%^!"
)

set UNUSED_COL=Blue

set WW=
for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  %MASK_CROP% ^
  %ROT_START% ^
  %RESAMP_START% ^
  -format "WW=%%w\nHH=%%h" ^
  +write info: ^
  +depth ^
  %%TMP_IN%%`) do set %%L
if "%WW%"=="" exit /B 1

set EDGE_COL=blue

%IMG7%magick ^
  %MASK% ^
  +depth ^
  %MASK_CROP% ^
  %ROT_START% ^
  %RESAMP_START% ^
  -threshold 50%% ^
  +repage ^
  ( -clone 0 ^
    -sparse-color bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
  ) ^
  ( -clone 0 ^
    -fill %EDGE_COL% -colorize 100 ^
  ) ^
  -swap 0,2 ^
  -compose Over -composite ^
  -crop x1 +repage ^
  -bordercolor %EDGE_COL% -border 1 ^
  -trim +repage ^
  -resize "%WW%x1^!" ^
  -append ^
  %TMP_IN% ^
  +swap ^
  -compose Distort ^
  -set option:compose:args 100%%x100%% -composite ^
  %ROT_END% ^
  %RESAMP_END% ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

endlocal

All images on this page were created by the commands shown.

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.

My usual version of IM is:

%IMG7%magick -version
Version: ImageMagick 7.1.1-15 Q16-HDRI x64 a0a5f3d:20230730 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)

This customised development version is:

%IM7DEV%magick -version
Version: ImageMagick 7.1.1-5 (Beta) Q32-HDRI x86_64 852a723f1:20230319 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(4.5) 
Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib
Compiler: gcc (11.3)

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


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 8-October-2014.

Page created 20-Sep-2023 11:21:09.

Copyright © 2023 Alan Gibson.