snibgo's ImageMagick pages

Pin or push

We can push pixels to new locations while pinning others in place.

This page does not consider how the user might specify the displacement. A trimap can specify which pixels are pinned, or pushed to new locations, or are free to move or not. I can't see an elegant graphical method for specifying new locations for pushed pixels. This could be simple text coordinates, of course.

Sample inputs

We demonstrate with a photograph that has an overlaid grid.

call %PICTBAT%gridOver ^
  toes.png pop_src.png 10 10

set SRC=pop_src.png
pop_src.pngjpg

We also make an identity absolute displacement map.

set IDENT=^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0

%IMG7%magick ^
  %SRC% ^
  -strip ^
  -sparse-color bilinear "%IDENT%" ^
  pop_id_abs_map.png
pop_id_abs_map.pngjpg

On this page, ordinary absolute displacement maps are called "pull" maps, because the value at a pixel in the map determines where the corresponding pixel in the final image comes from. We use the process module invdispmap to invert the map, making a "push" map, which determines where the corresponding pixel in the final image will be pushed to. The process invdispmap also does the opposite, converting a push map into a pull map. The inversion process leaves holes that we fill by relaxation.

The identity absolute displacement map creates no displacement, so can be regarded as either a pull map or a push map.

Move one point, pinning an outline

Suppose we have a shape, defined by a mask that is white on a black background. In an image of the same size, we want to move a point at (x1,y1) to another location (x2,y2), where both locations are within the white area defined by the mask. However, we want to pin all the pixels that are outside the shape, so they don't move. Intermediate pixels should move in proportion.

This is the more general case of pinning the four edges of the image.

We can create a gradient that is white at (x2,y2) and black at outside the white area defined by the mask. This can be used as a blend for two displacement maps. One displacement map is the required translation from (x1,y1) to (x2,y2), and the other is the identity displacement map.

For example, the locations are:

set X1=100
set Y1=100
set X2=130
set Y2=120

We show each step of the process:

Make a mask with white shape on black background

%IMG7%magick ^
  -size 267x233 xc:Black ^
  -fill White -stroke None ^
  -draw "polygon 50,50 210,75 250,125 230,200 75,175" ^
  pop_poly.png
pop_poly.png

Make the gradient

call %PICTBAT%shp2grad ^
  pop_poly.png ^
  pop_poly_grad.miff ^
  %X2%x%Y2% -negate

%IMG7%magick ^
  pop_poly_grad.miff pop_poly_grad.png

The MIFF version has negative values outside the shape.

We also save, and show, a (clamped) PNG version.

pop_poly_grad.png

Make the identity displacement map

%IMG7%magick -size 267x233 xc: ^
  -sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
  pop_poly_ident.png
pop_poly_ident.png

Modify the map for movement.
See Power curve below.

%IMG7%magick ^
  pop_poly_ident.png ^
  -channel R ^
  -evaluate pow ^
    %%[fx:log(%X1%/(w-1))/log(%X2%/(w-1))] ^
  -channel G ^
  -evaluate pow ^
    %%[fx:log(%Y1%/(h-1))/log(%Y2%/(h-1))] ^
  +channel ^
  pop_poly_move.png
pop_poly_move.png

Blend the maps

%IMG7%magick ^
  pop_poly_ident.png ^
  pop_poly_move.png ^
  pop_poly_grad.png ^
  -alpha off ^
  -compose Over -composite ^
  pop_poly_blnd.png
pop_poly_blnd.png

Apply the blended map

%IMG7%magick ^
  %SRC% ^
  pop_poly_blnd.png ^
  -compose Distort -composite ^
  pop_poly_out.png
pop_poly_out.png

The broken grid lines (second order discontinuity) can be fixed by modifying the blending function. "-sigmoidal-contrast" smooths the start and end of the transition, so the grid lines mostly have curves instead of breaks, and "-blur" smooths minor problems.

Blend the maps, with modified blend

%IMG7%magick ^
  pop_poly_ident.png ^
  pop_poly_move.png ^
  ( pop_poly_grad.png ^
    -sigmoidal-contrast 5,50%% ^
    -blur 0x5 ^
  ) ^
  -alpha off ^
  -compose Over -composite ^
  pop_poly_blnd2.png
pop_poly_blnd2.png

Apply the modified blended map

%IMG7%magick ^
  %SRC% ^
  pop_poly_blnd2.png ^
  -compose Distort -composite ^
  pop_poly_out2.png
pop_poly_out2.pngjpg

If we want the distortion to continue outside the shape (but in the opposite direction) we need to use the floating-point version of the gradient pop_poly_grad.miff, and we can't -blur it as that clamps:

Blend the maps, with modified blend

%IMG7%magick ^
  pop_poly_ident.png ^
  pop_poly_move.png ^
  -define compose:clamp=off ^
  pop_poly_grad.miff ^
  -alpha off ^
  -compose Over -composite ^
  -depth 32 -define quantum:format=floating-point ^
  -write pop_poly_blnd3.miff ^
  pop_poly_blnd3.png
pop_poly_blnd3.png

Apply the modified blended map

%IMG7%magick ^
  %SRC% ^
  pop_poly_blnd3.miff ^
  -compose Distort -composite ^
  pop_poly_out3.png

The boundary of the shape is not obvious.

pop_poly_out3.pngjpg

Move one point, pinning image edges

This is a specialisation of the above. Instead of finding the gradient within a shape, we find it within the image as a whole. We do the work in a script move1PinEdge.bat.

call %PICTBAT%move1PinEdge ^
  %SRC% pop_move1.png ^
  %X1%x%Y1% %X2%x%Y2%
pop_move1.pngjpg
call %PICTBAT%move1PinEdge ^
  %SRC% pop_move1b.png ^
  %X1%x%Y1% %X2%x%Y2% ^
  "-sigmoidal-contrast 5,50%%% -blur 0x5"
pop_move1b.pngjpg

Move points

We first consider a special case: we move coordinate (x1,y1) to (x2,y2), by moving an entire column from x1 to x2, and an entire row from y1 to y2. All other pixels should move in a "reasonable" way. Pixels at the top and bottom edge will move left or right. Pixels at the left or right edge will move up or down. Corner pixels won't move at all.

set X1=100
set Y1=100
set X2=130
set Y2=120

Linear interpolation

We can crop into two pieces horizontally at X1, then resize each, and append them together. Then we do the same vertically.

set sFMT=^
WW=%%w\n^
HH=%%h\n^
WR1=%%[fx:w-%X1%]\n^
WR2=%%[fx:w-%X2%]\n^
HB1=%%[fx:h-%Y1%]\n^
HB2=%%[fx:h-%Y2%]

for /F "usebackq" %%L in (`%IMG7%magick ^
  identify ^
  -format "%sFMT%"
  %SRC%`) do set %%L

%IMG7%magick ^
  %SRC% ^
  ( -clone 0 ^
    -crop %X1%x+0+0 +repage ^
    -resize "%X2%x^!" ^
  ) ^
  ( -clone 0 ^
    -crop %WR1%x+%X1%+0 +repage ^
    -resize "%WR2%x^!" ^
  ) ^
  -delete 0 ^
  +append +repage ^
  ( -clone 0 ^
    -crop x%Y1%+0+0 +repage ^
    -resize "x%Y2%^!" ^
  ) ^
  ( -clone 0 ^
    -crop x%HB1%+0+%Y1% +repage ^
    -resize "x%HB2%^!" ^
  ) ^
  -delete 0 ^
  -append +repage ^
  pop_li1.png
pop_li1.pngjpg

The abrupt change of scale is obvious in the "broken" diagonal lines.

Power curve

A single point can be moved by applying a power (gamma) function to the gradients of the identity map. This is a pull map, so the value at coordinate (x2,y2) should be x1 in the red channel and y1 in the green channel.

We raise to the power k such that:

v' = v^k

log(v') = log(v^k)  (where log is to any base)
        = k * log(v)

k = log(v') / log(v)

We use v7, so the calculation can be done within the "-evaluate pow".

Make a displacement map.

%IMG7%magick ^
  pop_id_abs_map.png ^
  -channel R ^
  -evaluate pow ^
    %%[fx:log(%X1%/(w-1))/log(%X2%/(w-1))] ^
  -channel G ^
  -evaluate pow ^
    %%[fx:log(%Y1%/(h-1))/log(%Y2%/(h-1))] ^
  +channel ^
  pop_pc_map.png
pop_pc_map.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pc_map.png ^
  -compose Distort -composite ^
  pop_pc1.png
pop_pc1.pngjpg

There is no abrupt change of scale, but stretching at the left and top edges is severe.

Pushing multiple points

Iterating the above won't move multiple points, because moving any one point uniquely determines the movement of all the others. A power (gamma) curve is determined by a single point.

The obvious solution is a Bézier curve, which is determined by however many points we want. An external program, cBezCurve.exe, calculates values along a Bézier curve, creating an Nx1 clut image. We use this twice, for the two dimensions.

(I don't publish the source or binary of cBezCurve.exe. The program calculates "reasonable" coordinates for the required extra control points.)

Get the dimensions.

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

echo WW=%WW% HH=%HH% 
WW=267 HH=233 

Make a WWx1 clut for the horizontal displacement. This is really a slightly distorted gradient. At ordinate X2, we want value X1. We also pin the first and last ordinates, 0 and %Wm1%.

cBezCurve /p0 /C /a1 /m%Wm1% /I"0,0; %X2%,%X1%; %Wm1%,%Wm1%" /n%WW% /T1 /F0 /Lpop_dx.ppm

Similarly, a HHx1 clut for the vertical displacement.

cBezCurve /p0 /C /a1 /m%Hm1% /I"0,0; %Y2%,%Y1%; %Hm1%,%Hm1%" /n%HH% /T1 /F0 /Lpop_dy.ppm

Use these two cluts to make a displacement map. We have told cBezCurve.exe to create just the red channel, so combining them is slightly complex. We need the red channel of the first PPM to go into the red channel of the map, and red channel of the second PPM to go into the green channel of the map.

Make a displacement map.

%IMG7%magick ^
  pop_dx.ppm ^
  ( pop_dy.ppm -rotate 90 ) ^
  xc:Black ^
  -channel R ^
  -scale "%WW%x%HH%^!" ^
  -separate ^
  +channel ^
  -combine ^
  pop_pmp_map.png
pop_pmp_map.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_map.png ^
  -compose Distort -composite ^
  pop_pmp1.png
pop_pmp1.pngjpg

The result is similar to the one from the power (gamma) curve, but with less severe stretching at the left and top edges.

cBezCurve.exe has other interpolation methods, such as linear:

cBezCurve /p0 /C /a1 /m%Wm1% /I"0,0; %X2%,%X1%; %Wm1%,%Wm1%" /n%WW% /T0 /F0 /Lpop_dx1a.ppm

cBezCurve /p0 /C /a1 /m%Hm1% /I"0,0; %Y2%,%Y1%; %Hm1%,%Hm1%" /n%HH% /T0 /F0 /Lpop_dy1a.ppm

Make a displacement map.

%IMG7%magick ^
  pop_dx1a.ppm ^
  ( pop_dy1a.ppm -rotate 90 ) ^
  xc:Black ^
  -channel R ^
  -scale "%WW%x%HH%^!" ^
  -separate ^
  +channel ^
  -combine ^
  pop_pmp_map1a.png
pop_pmp_map1a.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_map1a.png ^
  -compose Distort -composite ^
  pop_pmp1a.png

The result is essentially the same as the crop-resize method above.

pop_pmp1a.pngjpg

With Bézier curves, we can push as many pixels as we like. Morever, the same mechanism can pin pixels; we simply give the same coordinates for input and output.

set pinX=30
set pinY=25

set X3=50
set Y3=200
set X4=55
set Y4=185

set X5=200
set Y5=50
set X6=210
set Y6=60

set NewX=%pinX%,%pinX%; %X4%,%X3%; %X6%,%X5%

set NewY=%pinY%,%pinY%; %Y4%,%Y3%; %Y6%,%Y5%

Make the cluts:

cBezCurve /p0 /C /a1 /m%Wm1% /I"0,0; %X2%,%X1%; %NewX%; %Wm1%,%Wm1%" /n%WW% /T1 /F0 /Lpop_dx2.ppm

cBezCurve /p0 /C /a1 /m%Hm1% /I"0,0; %Y2%,%Y1%; %NewY%; %Hm1%,%Hm1%" /n%HH% /T1 /F0 /Lpop_dy2.ppm

Make a displacement map.

%IMG7%magick ^
  pop_dx2.ppm ^
  ( pop_dy2.ppm -rotate 90 ) ^
  xc:Black ^
  -channel R ^
  -scale "%WW%x%HH%^!" ^
  -separate ^
  +channel ^
  -combine ^
  pop_pmp_map2.png
pop_pmp_map2.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_map2.png ^
  -compose Distort -composite ^
  pop_pmp2.png
pop_pmp2.pngjpg

When we move X1,Y1 to X2,Y2, all pixels on row Y1 will move to row Y2, and all pixels on column X1 will move to column X2. Hence, input lines that were vertical or horizontal will be vertical or horizontal in the output.

We could invert the map, getting a push (instead of pull) map. This could be modulated, eg reduced except in the vicinity of points, and converted back to a pull map. For example, we modulate with a gradient so we have no displacement at the top:

Invert, to get a push map.

%IM7DEV%magick ^
  pop_pmp_map2.png ^
  -process invdispmap ^
  pop_pmp_map2i.png
pop_pmp_map2i.png

Fill by relaxation.

set rfCOMP_METRIC=PAE
set THRESH=1e-4
set NUM_ITER=1000

call %PICTBAT%relaxFillMS ^
  pop_pmp_map2i.png . pop_pmp_maprf.png ^
  %THRESH% %NUM_ITER%

We will use this push map a few times.

pop_pmp_maprf.pngjpg

Blend with identity map, to get zero displacement at the top and full displacement at the bottom.

%IMG7%magick ^
  pop_pmp_maprf.png ^
  pop_id_abs_map.png ^
  -size %WW%x%HH% gradient: ^
  -compose Over -composite ^
  pop_pmp_mapbld.png
pop_pmp_mapbld.pngjpg

Invert, to get a pull map.

%IM7DEV%magick ^
  pop_pmp_mapbld.png ^
  -process invdispmap ^
  pop_pmp_mapbldi.png
pop_pmp_mapbldi.png

Fill by relaxation.

call %PICTBAT%relaxFillMS ^
  pop_pmp_mapbldi.png . pop_pmp_mapbirf.png ^
  %THRESH% %NUM_ITER%
pop_pmp_mapbirf.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_mapbirf.png ^
  -compose Distort -composite ^
  pop_pmp_bld.png
pop_pmp_bld.pngjpg

In the next example, we get the full displacement at the required points, and decreasing displacement further from any points. We are modifying the push map, so we want a mask that is black at the original locations of pinned or pushed pixels.

Black where we have a point; otherwise white.

%IMG7%magick ^
  -size %WW%x%HH% ^
  xc:White ^
  -fill Black ^
  -draw ^
    "point %pinX%,%pinY% point %X1%,%Y1% point %X3%,%Y3% point %X5%,%Y5% " ^
  pop_pnt_mask.png
pop_pnt_mask.png
%IMG7%magick ^
  pop_pnt_mask.png ^
  -morphology Distance Euclidean:7,100 ^
  -auto-level ^
  pop_pnt_mask2.png
pop_pnt_mask2.pngjpg

Blend with identity map.

%IMG7%magick ^
  pop_pmp_maprf.png ^
  pop_id_abs_map.png ^
  pop_pnt_mask2.png ^
  -compose Over -composite ^
  pop_pmp_mapbld2.png
pop_pmp_mapbld2.pngjpg

Invert, to get a pull map.

%IM7DEV%magick ^
  pop_pmp_mapbld2.png ^
  -process invdispmap ^
  pop_pmp_mapbld2i.png
pop_pmp_mapbld2i.png

Fill by relaxation.

call %PICTBAT%relaxFillMS ^
  pop_pmp_mapbld2i.png . pop_pmp_maprf2.png ^
  %THRESH% %NUM_ITER%
pop_pmp_maprf2.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_maprf2.png ^
  -compose Distort -composite ^
  pop_pmp_bld2.png
pop_pmp_bld2.pngjpg

Another possibility: apply the full effect only in a circle around each point. The radius at each point probably needs to be sufficient to cover the displacement. For simplicity, we use the same radius and blur for all the circles.

Draw circles, and blur them.

set sCIRC=circle 0,0 0,60

%IMG7%magick ^
  -size %WW%x%HH% ^
  xc:White ^
  -fill Black ^
  -draw "translate %pinX%,%pinY% %sCIRC%" ^
  -draw "translate %X1%,%Y1% %sCIRC%" ^
  -draw "translate %X3%,%Y3% %sCIRC%" ^
  -draw "translate %X5%,%Y5% %sCIRC%" ^
  -blur 0x25 ^
  pop_pnt_mask3.png
pop_pnt_mask3.pngjpg

Blend with identity map.

%IMG7%magick ^
  pop_pmp_maprf.png ^
  pop_id_abs_map.png ^
  pop_pnt_mask3.png ^
  -compose Over -composite ^
  pop_pmp_mapbld3.png
pop_pmp_mapbld3.pngjpg

Invert, to get a pull map.

%IM7DEV%magick ^
  pop_pmp_mapbld3.png ^
  -process invdispmap ^
  pop_pmp_mapbld3i.png
pop_pmp_mapbld3i.png

Fill by relaxation.

call %PICTBAT%relaxFillMS ^
  pop_pmp_mapbld3i.png . pop_pmp_maprf3.png ^
  %THRESH% %NUM_ITER%
pop_pmp_maprf3.pngjpg

Apply the map.

%IMG7%magick ^
  %SRC% ^
  pop_pmp_maprf3.png ^
  -compose Distort -composite ^
  pop_pmp_bld3.png
pop_pmp_bld3.pngjpg

Move areas

Above, we have pinned or pushed individual pixels. (Strictly speaking, individual coordinates.) Instead, we can pin or push groups of pixels. All the pixels in each group will be displaced by the same amount. The method shown here is in general quite complex, but simple enough when each area is a rectangle (so this also includes horizontal and vertical lines).

The method is to either construct an identity absolute displacement map and move the areas, making the other pixels transparent, or to make a relative displacement map with areas of the appropriate shade of gray.

Each pixel in the displacement map is one of:

Transparent pixels in the displacement map are then filled by the relaxation method, and the map is used to displace the input image.

Absolute displacement maps

IM v6 can't have %[fx:] in -crop, so we use v7. For the edges of the map, early versions of v7 don't have "-region" so we do the job with four crops.

set sEDGES=^
  ( -clone 0 -crop %%[fx:w]x1+0+0 ) ^
  ( -clone 0 -crop %%[fx:w]x1+0+%%[fx:h-1] ) ^
  ( -clone 0 -crop 1x%%[fx:h]+0+0 ) ^
  ( -clone 0 -crop 1x%%[fx:h]+%%[fx:w-1]+0 )
%IMG7%magick ^
  pop_id_abs_map.png ^
  ( -clone 0 -alpha transparent ) ^
  %sEDGES% ^
  ( -clone 0 ^
    -crop %%[fx:w*0.3]x%%[fx:h*0.2]+%%[fx:w*0.4]+%%[fx:h*0.4] ^
  ) ^
  -delete 0 ^
  -background None ^
  -layers flatten ^
  pop_map1.png
pop_map1.png
set rfCOMP_METRIC=PAE
set THRESH=1e-4
set NUM_ITER=1000

rem set rfCOMP_METRIC=RMSE
rem set THRESH=1e-6

call %PICTBAT%relaxFillMS ^
  pop_map1.png . . %THRESH% %NUM_ITER%
pop_map1_rfms.pngjpg

We should have no displacement:

%IMG7%magick ^
  %SRC% ^
  pop_map1_rfms.png ^
  -compose Distort -composite ^
  pop_m1.png
pop_m1.pngjpg

Displace a central block:

%IMG7%magick ^
  pop_id_abs_map.png ^
  ( -clone 0 -alpha transparent ) ^
  %sEDGES% ^
  ( -clone 0 ^
    -set DW %%[fx:w*0.1] ^
    -set DH %%[fx:h*0.1] ^
    -crop %%[fx:w*0.3]x%%[fx:h*0.2]+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    -repage "+%%[DW]+%%[DH]^!" ^
  ) ^
  -delete 0 ^
  -background None ^
  -layers flatten ^
  pop_map2.png
pop_map2.png
call %PICTBAT%relaxFillMS ^
  pop_map2.png . . %THRESH% %NUM_ITER%
pop_map2_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_map2_rfms.png ^
  -compose Distort -composite ^
  pop_m2.png
pop_m2.pngjpg

Displace the four corners of a central block:

set sREPAGE=-repage "+%%[DW]+%%[DH]^^^!"

%IMG7%magick ^
  pop_id_abs_map.png ^
  -set DW %%[fx:w*0.1] ^
  -set DH %%[fx:h*0.1] ^
  ( -clone 0 -alpha transparent ) ^
  %sEDGES% ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
  -set DW %%[fx:w*0.1] ^
  -set DH %%[fx:h*0.1] ^
    -crop 1x1+%%[fx:w*0.7]+%%[fx:h*0.4] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.4]+%%[fx:h*0.6] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.7]+%%[fx:h*0.6] ^
    %sREPAGE% ^
  ) ^
  -delete 0 ^
  -background None ^
  -layers flatten ^
  pop_map3.png
pop_map3.png
call %PICTBAT%relaxFillMS ^
  pop_map3.png . . %THRESH% %NUM_ITER%
pop_map3_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_map3_rfms.png ^
  -compose Distort -composite ^
  pop_m3.png
pop_m3.pngjpg

The displacement is not as far as we wanted. Repeat the previous, but without multi-scale:

call %PICTBAT%relaxFill ^
  pop_map3.png . pop_map3a_rfms.png ^
 %THRESH% %NUM_ITER%
pop_map3a_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_map3a_rfms.png ^
  -compose Distort -composite ^
  pop_m3a.png
pop_m3a.pngjpg

This hasn't cured the problem.

Sadly, the process doesn't work well when we have isolated pixels in the holed image. The pixel at (50%,50%) has the correct value of RG=(40%,40%), but moving away from this pixel, the values swiftly rise.

Try moving small squares, one at each location:

set rfCOMP_METRIC=PAE
set THRESH=1e-4
set NUM_ITER=1000

set sREPAGE=-repage "+%%[DW]+%%[DH]^^^!"

set SW=10
set SH=10

%IMG7%magick ^
  pop_id_abs_map.png ^
  -set DW %%[fx:w*0.1] ^
  -set DH %%[fx:h*0.1] ^
  ( -clone 0 -alpha transparent ) ^
  %sEDGES% ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
  -set DW %%[fx:w*0.1] ^
  -set DH %%[fx:h*0.1] ^
    -crop %SW%x%SH%+%%[fx:w*0.7]+%%[fx:h*0.4] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.4]+%%[fx:h*0.6] ^
    %sREPAGE% ^
  ) ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.7]+%%[fx:h*0.6] ^
    %sREPAGE% ^
  ) ^
  -delete 0 ^
  -background None ^
  -layers flatten ^
  pop_map4.png
pop_map4.png
call %PICTBAT%relaxFillMS ^
  pop_map4.png . . %THRESH% %NUM_ITER%
pop_map4_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_map4_rfms.png ^
  -compose Distort -composite ^
  pop_m4.png
pop_m4.pngjpg

Relative displacement maps

Relative displacement maps are simpler, as each rectangle is a constant shade of gray.

%IMG7%magick ^
  %SRC% ^
  -shave 1x1 ^
  -alpha transparent ^
  -bordercolor gray(50%%) -border 1 ^
  -set page %%[fx:w*0.4]+%%[fx:h*0.4] ^
  ( -clone 0 ^
    -crop %%[fx:w*0.3]x%%[fx:h*0.2]+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    -alpha opaque ^
    -fill gray(50%%) -colorize 100 ^
  ) ^
  -background None ^
  -layers merge ^
  pop_rmap1.png
pop_rmap1.png
set rfCOMP_METRIC=PAE
set THRESH=1e-4
set NUM_ITER=1000

rem set rfCOMP_METRIC=RMSE
rem set THRESH=1e-6

call %PICTBAT%relaxFillMS ^
  pop_rmap1.png . . %THRESH% %NUM_ITER%
pop_rmap1_rfms.pngjpg

We should have no displacement:

%IMG7%magick ^
  %SRC% ^
  pop_rmap1_rfms.png ^
  -compose Displace -composite ^
  pop_rm1.png
pop_rm1.pngjpg

Displace a central block:

%IMG7%magick ^
  %SRC% ^
  -shave 1x1 ^
  -alpha transparent ^
  -bordercolor gray(50%%) -border 1 ^
  -set page %%[fx:w*0.4]+%%[fx:h*0.4] ^
  ( -clone 0 ^
    -crop %%[fx:w*0.3]x%%[fx:h*0.2]+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  -background None ^
  -layers merge ^
  pop_rmap2.png
pop_rmap2.png
call %PICTBAT%relaxFillMS ^
  pop_rmap2.png . . %THRESH% %NUM_ITER%
pop_rmap2_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_rmap2_rfms.png ^
  -compose Displace -composite ^
  pop_rm2.png
pop_rm2.pngjpg

Displace the four corners of a central block:

%IMG7%magick ^
  %SRC% ^
  -shave 1x1 ^
  -alpha transparent ^
  -bordercolor gray(50%%) -border 1 ^
  -set page %%[fx:w*0.4]+%%[fx:h*0.4] ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.4]+%%[fx:h*0.4] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.7]+%%[fx:h*0.4] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.4]+%%[fx:h*0.6] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop 1x1+%%[fx:w*0.7]+%%[fx:h*0.6] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  -background None ^
  -layers merge ^
  pop_rmap3.png
pop_rmap3.png
call %PICTBAT%relaxFillMS ^
  pop_rmap3.png . . %THRESH% %NUM_ITER%
pop_rmap3_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_rmap3_rfms.png ^
  -compose Displace -composite ^
  pop_rm3.png
pop_rm3.pngjpg

Again, this doesn't work well. Try larger rectangles.

set SW=10
set SH=10

%IMG7%magick ^
  %SRC% ^
  -shave 1x1 ^
  -alpha transparent ^
  -bordercolor gray(50%%) -border 1 ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.5]+%%[fx:h*0.5] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.8]+%%[fx:h*0.5] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.5]+%%[fx:h*0.7] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  ( -clone 0 ^
    -crop %SW%x%SH%+%%[fx:w*0.8]+%%[fx:h*0.7] ^
    -alpha opaque ^
    -fill gray(40%%) -colorize 100 ^
  ) ^
  -background None ^
+write info: ^
  -layers merge ^
  pop_rmap4.png
pop_rmap4.png
set rfCOMP_METRIC=PAE
set THRESH=1e-4
set NUM_ITER=1000

call %PICTBAT%relaxFillMS ^
  pop_rmap4.png . . %THRESH% %NUM_ITER%
pop_rmap4_rfms.pngjpg
%IMG7%magick ^
  %SRC% ^
  pop_rmap4_rfms.png ^
  -compose Displace -composite ^
  pop_rm4.png
pop_rm4.pngjpg

Future

It would be cute to allow constrained movement. For example, we might allow pixels at the left and right sides to move vertically but not horizontally.

Scripts

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

move1PinEdge.bat

rem Moves one point, while pinning all edge pixels.
rem See pinorpush.htm#move1pnt

rem %1 input image
rem %2 output
rem %3 X1xY1 move colour from this location
rem %4 X2xY2 move colour to this location
rem %5 optional process to apply to the gradient

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

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 m1pe

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

set sXY1=%3
if "%sXY1%"=="." set sXY1=
if "%sXY1%"=="" set sXY1=65cx55c

set sXY2=%4
if "%sXY2%"=="." set sXY2=
if "%sXY2%"=="" set sXY2=40cx45c

set sProcGrad=%~5

echo sProcGrad is [%sProcGrad%]

for /F "usebackq" %%A in (`%IMG7%magick ^
  %INFILE% -format "WW=%%w\nHH=%%h\n" ^
  info:`) do set %%A

call parseXxY2 !WW! !HH! xy1 %sXY1%
call parseXxY2 !WW! !HH! xy2 %sXY2%


echo xy1: %xy1_X%,%xy1_Y%
echo xy2: %xy2_X%,%xy2_Y%

%IMG7%magick ^
  %INFILE% +write mpr:INP ^
  -fill White -colorize 100 ^
  -virtual-pixel Black ^
  -distort DePolar -1,0,%xy2_X%,%xy2_Y% ^
  -set option:MYSIZE %%wx%%h ^
  ( -size %%[MYSIZE] ^
    gradient:Black-White ^
  ) ^
  ( -clone 0 ^
    -crop 0x+0+1 +repage ^
    -scale "x1^!" ^
    -scale "%%[MYSIZE]^!" ^
  ) ^
  -delete 0 ^
  -compose DivideSrc -composite ^
  -clamp ^
  -virtual-pixel Edge ^
  -distort Polar -1,0,%xy2_X%,%xy2_Y% ^
  -negate ^
  %sProcGrad% ^
  -write mpr:GRAD +delete ^
  mpr:INP ^
  -sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
  +write mpr:IDENT_MAP ^
  ( +clone ^
  -channel R ^
  -evaluate pow ^
    %%[fx:log(%xy1_X%/(w-1))/log(%xy2_X%/(w-1))] ^
  -channel G ^
  -evaluate pow ^
    %%[fx:log(%xy1_Y%/(h-1))/log(%xy2_Y%/(h-1))] ^
  +channel ^
  ) ^
  +write mpr:MOVE_MAP ^
  mpr:GRAD ^
  -alpha off ^
  -compose Over -composite ^
  +write mpr:BLND_MAP ^
  mpr:INP ^
  +swap ^
  -compose Distort -composite ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

@endlocal & set m1peOUTFILE=%OUTFILE%

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

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

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 pinorpush.h1. To re-create this web page, run "procH1 pinorpush".


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 18-November-2016.

Page created 18-Aug-2023 14:01:08.

Copyright © 2023 Alan Gibson.