This is a method for moving pixels. For every destination pixel, we specify where it should be copied from.
From an input image and a map of the same size, IM creates an output image of the same size. For each output pixel, IM finds the values in the corresponding pixel of the map. Those pixel values are an offset, giving the coordinates of the input image that will be copied to the output.
A displacement map is used by "-compose Displace", setting an output pixel to be from an input pixel that is offset by a given amount. IM's "-composite" will, for each output pixel, lookup the same pixel in the map. The map red channel, scaled to a value from -1 to +1, is multiplied by the first argument of "-set option:compose:args". This gives the x-offset for the pixel in the input that will be copied to the output. The green channel is used similarly for the y-displacement.
This means that a map pixel that is lighter than mid-level (in the red and green channels) will pull image pixels up and left. Dark map pixels will pull image pixels down and right.
If the "-set option:compose:args" are half the width and height, then the maximum displacements will be half the width and height, in either direction. Either argument can be negative, which will reverse the direction of the displacement.
Use "-interpolate nearest-neighbor" if desired to get the nearest pixel instead of interpolating.
If a displacement would fetch a pixel outside the range of the "destination", the result is determined by "-virtual-pixel".
See also the official usage page Distorting Images using Image Mapping.
For another example of displacement maps, including a treatment of anti-aliasing between virtual and non-virtual pixels, see my De-barrel distortion page.
To demonstrate how displacements work, we will create and manipulate a very small image, 6 pixels wide and 1 pixel high. We will displace only in the x-direction. To help us see the images, a script will enlarge the pixels.
A bit of housekeeping:
if "%IM%"=="" set IMDIS=%IM% for /F "usebackq" %%F in (`%IMG7%magick identify -format "%%q" rose:`) do set IM_Q_NUM=%%F if /I "%IM_Q_NUM%" LSS "0" goto error if /I "%IM_Q_NUM%" GTR "9" goto error
Create a simple source image with just six pixels. %IMG7%magick ^ xc:red xc:yellow xc:lime xc:cyan xc:blue xc:magenta ^ +append +repage ^ disp_src.png call %PICTBAT%blockPix disp_src.png |
|
Create three displacement maps: black, gray and white. %IMG7%magick ^ -size 6x1 ^ xc:#000 -write disp_black.png +delete ^ xc:#800080008000 -write disp_gray.png +delete ^ xc:#fff disp_white.png call %PICTBAT%blockPix disp_black.png call %PICTBAT%blockPix disp_gray.png call %PICTBAT%blockPix disp_white.png |
|
Apply the three displacement maps. %IMG7%magick ^ disp_src.png ^ -depth 16 ^ -virtual-pixel None ^ -compose Displace -set option:compose:args 3x0 ^ ( -clone 0 disp_black.png -composite -write disp_bl_out.png +delete ) ^ ( -clone 0 disp_gray.png -composite -write disp_gr_out.png +delete ) ^ ( -clone 0 disp_white.png -composite -write disp_wh_out.png +delete ) ^ NULL: call %PICTBAT%blockPix disp_bl_out.png call %PICTBAT%blockPix disp_gr_out.png call %PICTBAT%blockPix disp_wh_out.png |
|
Maps are normalised to the range -1..0..+1 (approximately). The first displacement map is entirely black which is read as -1. This is multiplied by the first argument (which is 3) to make -3. This is the relative lookup for each destination pixel. Each pixel will become the value of the pixel 3 positions to the left. (Put it another way, each pixel shifts right by 3 positions.) All the pixels in the maps are black, so all new pixels take values from the ones 3 positions to the left.
"-virtual-pixel None" make the first three pixels transparent black.
The second map is gray which is read as zero, so no displacement occurs.
The third map is white which is read as +1, so new pixels take values from the ones 3 positions to the right. The last three pixels become transparent black.
See Wikipedia Offset binary.
Pixels in a map can contain different values:
Apply an alternating displacement map to interchange pixels. %IMG7%magick ^ disp_src.png ^ ( xc:white xc:black xc:white xc:black xc:white xc:black ^ +append +repage ) ^ -virtual-pixel None ^ -compose Displace -set option:compose:args 1x0 -composite ^ disp_altern.png call %PICTBAT%blockPix disp_altern.png Red and yellow have swapped. So have green and cyan, and blue and magenta. |
The multiplier doesn't need to be an integer. If we repeat the last command with "0.5", the first pixel becomes the average of the first and the second, and the second pixel also becomes the average of the first and the second.
Apply the same map with a different argument. %IMG7%magick ^ disp_src.png ^ ( xc:white xc:black xc:white xc:black xc:white xc:black ^ +append +repage ) ^ -virtual-pixel None ^ -compose Displace -set option:compose:args 0.5x0 -composite ^ -depth 16 ^ disp_altern2.png call %PICTBAT%blockPix disp_altern2.png |
So the first two pixels are very nearly the same, as are the second pair, and the third pair.
%IMG7%magick disp_altern2.png txt:disp_altern2.txt
# ImageMagick pixel enumeration: 6,1,0,65535,srgb 0,0: (65535,32767,0) #FFFF7FFF0000 srgb(100%,49.9992%,0%) 1,0: (65535,32768,0) #FFFF80000000 srgb(100%,50.0008%,0%) 2,0: (0,65535,32767) #0000FFFF7FFF srgb(0%,100%,49.9992%) 3,0: (0,65535,32768) #0000FFFF8000 srgb(0%,100%,50.0008%) 4,0: (32767,0,65535) #7FFF0000FFFF srgb(49.9992%,0%,100%) 5,0: (32768,0,65535) #80000000FFFF srgb(50.0008%,0%,100%)
Why are they not exactly the same? I'm not sure. I think the main problem is that quantum/2 isn't an integer. For Q8, quantum/2 is 127.5. For Q16, quantum/2 is 32767.5. Repeating this command in HDRI also gives the wrong result.
It seems that maps are not normalised to the exact range -1..0..+1, but actually to -1..0.. 32767/32768 (in Q16). This can be shown using a source image of 10 pixels, all black except the first which is white. We aim to displace the white pixel 5 steps right.
Use black displacement map and positive argument. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:black ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args 5x0 -composite ^ -write txt:disp_q1.txt ^ disp_q1.png call %PICTBAT%blockpix disp_q1.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (65535,0,0) #FFFF00000000 red 1,0: (65535,0,0) #FFFF00000000 red 2,0: (65535,0,0) #FFFF00000000 red 3,0: (65535,0,0) #FFFF00000000 red 4,0: (65535,0,0) #FFFF00000000 red 5,0: (65535,65535,65535) #FFFFFFFFFFFF white 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (0,0,0) #000000000000 black The result is numerically accurate. |
|
Use white displacement map and negative argument. This should give the same result. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:white ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args -5x0 -composite ^ -write txt:disp_q2.txt ^ disp_q2.png call %PICTBAT%blockpix disp_q2.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (65535,0,0) #FFFF00000000 red 1,0: (65535,0,0) #FFFF00000000 red 2,0: (65535,0,0) #FFFF00000000 red 3,0: (65535,0,0) #FFFF00000000 red 4,0: (65535,10,10) #FFFF000A000A srgb(100%,0.0152588%,0.0152588%) 5,0: (65525,65525,65525) #FFF5FFF5FFF5 srgb(99.9847%,99.9847%,99.9847%) 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (0,0,0) #000000000000 black The result looks correct but is numerically inaccurate. |
|
Use white displacement map and negative argument of 5 * 32768/32767. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:white ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args -5.00015259x0 -composite ^ -write txt:disp_q3.txt ^ disp_q3.png call %PICTBAT%blockpix disp_q3.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (65535,0,0) #FFFF00000000 red 1,0: (65535,0,0) #FFFF00000000 red 2,0: (65535,0,0) #FFFF00000000 red 3,0: (65535,0,0) #FFFF00000000 red 4,0: (65535,0,0) #FFFF00000000 srgb(100%,2.5473e-07%,2.5473e-07%) 5,0: (65535,65535,65535) #FFFFFFFFFFFF white 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (0,0,0) #000000000000 black The result is numerically accurate. |
We can also check, as assumed above, that grey #800080008000 is the correct value for zero displacement:
Use grey #7fff7fff7fff displacement map and large positive argument. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:#7fff7fff7fff ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args 10000x0 -composite ^ -write txt:disp_q4.txt ^ disp_q4.png call %PICTBAT%blockpix disp_q4.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (65535,45535,45535) #FFFFB1DFB1DF srgb(100%,69.4824%,69.4824%) 1,0: (20000,20000,20000) #4E204E204E20 srgb(30.5176%,30.5176%,30.5176%) 2,0: (0,0,0) #000000000000 black 3,0: (0,0,0) #000000000000 black 4,0: (0,0,0) #000000000000 black 5,0: (0,0,0) #000000000000 black 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (0,0,0) #000000000000 black The result is wrong. |
|
Use grey #800080008000 displacement map and large positive argument. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:#800080008000 ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args 10000x0 -composite ^ -write txt:disp_q5.txt ^ disp_q5.png call %PICTBAT%blockpix disp_q5.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (65535,65535,65535) #FFFFFFFFFFFF white 1,0: (0,0,0) #000000000000 black 2,0: (0,0,0) #000000000000 black 3,0: (0,0,0) #000000000000 black 4,0: (0,0,0) #000000000000 black 5,0: (0,0,0) #000000000000 black 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (0,0,0) #000000000000 black The result is correct. |
|
Use grey #800180018001 displacement map and large positive argument. %IMG7%magick ^ xc:white -size 9x1 xc:black +append +repage ^ -write info: ^ -size 10x1 xc:#800180018001 ^ -background Red -virtual-pixel Background ^ -compose Displace -set option:compose:args 10000x0 -composite ^ -write txt:disp_q6.txt ^ disp_q6.png call %PICTBAT%blockpix disp_q6.png # ImageMagick pixel enumeration: 10,1,0,65535,srgb 0,0: (45535,45535,45535) #B1DFB1DFB1DF srgb(69.4824%,69.4824%,69.4824%) 1,0: (0,0,0) #000000000000 black 2,0: (0,0,0) #000000000000 black 3,0: (0,0,0) #000000000000 black 4,0: (0,0,0) #000000000000 black 5,0: (0,0,0) #000000000000 black 6,0: (0,0,0) #000000000000 black 7,0: (0,0,0) #000000000000 black 8,0: (0,0,0) #000000000000 black 9,0: (20000,0,0) #4E2000000000 srgb(30.5176%,0%,0%) The result is wrong. |
Conclusion: grey #800080008000 is the correct value for zero displacement.
Does HDRI help?
Repeat with Q32 HDRI. %IM7DEV%magick ^ disp_src.png ^ ( xc:white xc:black xc:white xc:black xc:white xc:black ^ +append +repage ) ^ -virtual-pixel None ^ -compose Displace -set option:compose:args 0.5x0 -composite ^ +depth ^ +write txt:disp_altern2f.lis ^ disp_altern2f.png if ERRORLEVEL 1 goto error call %PICTBAT%blockPix disp_altern2f.png |
disp_altern2f.lis is:
# ImageMagick pixel enumeration: 6,1,4294967295,srgba 0,0: (4.2949673e+09,2.1474836e+09,0,4.2949673e+09) #FFFFFFFF7FFFFFFF00000000FFFFFFFF srgba(100%,50%,0%,1) 1,0: (4.2949673e+09,2.1474836e+09,0,4.2949673e+09) #FFFFFFFF8000000000000000FFFFFFFF srgba(100%,50%,0%,1) 2,0: (0,4.2949673e+09,2.1474836e+09,4.2949673e+09) #00000000FFFFFFFF7FFFFFFFFFFFFFFF srgba(0%,100%,50%,1) 3,0: (0,4.2949673e+09,2.1474836e+09,4.2949673e+09) #00000000FFFFFFFF80000000FFFFFFFF srgba(0%,100%,50%,1) 4,0: (2.1474836e+09,0,4.2949673e+09,4.2949673e+09) #7FFFFFFF00000000FFFFFFFFFFFFFFFF srgba(50%,0%,100%,1) 5,0: (2.1474836e+09,0,4.2949673e+09,4.2949673e+09) #8000000000000000FFFFFFFFFFFFFFFF srgba(50%,0%,100%,1)
Again, pixels that should be identical are different (by one in the final digit).
When the map is a simple gradient, we can vary the argument from zero which will copy the image unchanged, to (width-1) to reverse the image.
Apply a gradient displacement map with argument "2". %IMG7%magick ^ disp_src.png ^ ( -size 1x6 gradient: -rotate -90 ) ^ -virtual-pixel None ^ -compose Displace -set option:compose:args 2x0 -composite ^ -depth 16 ^ disp_grad.png call %PICTBAT%blockPix disp_grad.png |
The displacement maps above are relative displacement maps; they describe the distance from a result pixel to its source. Another useful concept is the absolute displacement map, which gives for each result pixel the absolute position of its source. Black represents the left-most pixel; white is right-most pixel.
The identity absolute displacement map is a gradient with black on the left. Applying this to an image makes no change.
Create an identity absolute displacement map. %IMG7%magick ^ -size 1x6 gradient: -rotate 90 ^ -depth 16 ^ disp_ident_abs.png call %PICTBAT%blockPix disp_ident_abs.png |
To apply an absolute displacement map, we can use "-compose Distort -composite", or we can use "-fx" (see Apply Special Effects to an Image with an Fx Expression) or we can convert it to a relative displacement map.
To illustrate this, suppose we want the output to be the first and last pixels, alternating. So the absolute map is black, white, black, white, black, white.
Create an absolute displacement map to alternate pixels. %IMG7%magick ^ xc:black xc:white xc:black xc:white xc:black xc:white ^ +append +repage ^ disp_abs_altends.png call %PICTBAT%blockPix disp_abs_altends.png |
The fx method works directly from the absolute map:
Use "-fx" to alternate pixels. %IMG7%magick ^ disp_src.png ^ disp_abs_altends.png ^ -fx "p{v*w,j}" ^ -depth 16 ^ disp_fx_altends_out.png call %PICTBAT%blockPix disp_fx_altends_out.png In the FX expression:
|
For the relative map method, we first create the map, then apply it.
To create the relative map: divide the absolute map by 2, subtract half the identity absolute map, and add 50%.
From the absolute, create the relative displacement map. Without "-depth 16" we lose precision. %IMG7%magick ^ disp_ident_abs.png ^ disp_abs_altends.png ^ -evaluate divide 2 ^ -compose ModulusSubtract -composite ^ -evaluate AddModulus 50%% ^ -depth 16 ^ disp_rel_altends.png call %PICTBAT%blockPix disp_rel_altends.png |
|
The equivalent using "-compose mathematics". %IMG7%magick ^ disp_ident_abs.png ^ disp_abs_altends.png ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ -depth 16 ^ disp_rel_altends2.png call %PICTBAT%blockPix disp_rel_altends2.png |
|
Apply the relative displacement map. "-virtual-pixel Black" is only for demonstration purposes. The first argument is (width-1). %IMG7%magick ^ disp_src.png ^ disp_rel_altends2.png ^ -virtual-pixel Black ^ -compose Displace -set option:compose:args 5x0 -composite ^ -depth 16 ^ disp_altends_out.png call %PICTBAT%blockPix disp_altends_out.png |
Are the results accurate?
%IMG7%magick disp_fx_altends_out.png txt:disp_fx_altends_out.txt %IMG7%magick disp_altends_out.png txt:disp_altends_out.txt
# ImageMagick pixel enumeration: 6,1,0,65535,srgb 0,0: (65535,0,0) #FFFF00000000 red 1,0: (65535,0,65535) #FFFF0000FFFF magenta 2,0: (65535,0,0) #FFFF00000000 red 3,0: (65535,0,65535) #FFFF0000FFFF magenta 4,0: (65535,0,0) #FFFF00000000 red 5,0: (65535,0,65535) #FFFF0000FFFF magenta # ImageMagick pixel enumeration: 6,1,0,65535,srgb 0,0: (65535,0,0) #FFFF00000000 red 1,0: (65531,0,65535) #FFFB0000FFFF srgb(99.9939%,0%,100%) 2,0: (65535,2,0) #FFFF00020000 srgb(100%,0.0030518%,0%) 3,0: (65533,0,65535) #FFFD0000FFFF srgb(99.9969%,0%,100%) 4,0: (65535,4,0) #FFFF00040000 srgb(100%,0.00610361%,0%) 5,0: (65535,0,65535) #FFFF0000FFFF magenta
The "-fx" method is accurate. The relative displacement map isn't, but can be made so (in this example) by inserting "-interpolate NearestNeighbor" before "-compose Displace".
If we have an arbitrary but known displacement transformation that can be applied to a source, then we can apply the same transformation to the identity absolute map, convert to relative map, and apply this to the same source to make the same result. If we need to make the same complex transformation to many images, it may be much faster to do it once to create a displacement map and then use that map on the many images.
Here is an example. The transformation makes three "sin" calls in "-fx" to push colours to the right of the image.
A complex displacement using "-fx". This step is not essential, but will be used to confirm the map is correct. %IMG7%magick ^ disp_src.png ^ ( -size 1x6 gradient: -rotate 90 ) ^ -fx "p{sin(sin(sin(v)))*w,j}" ^ disp_fx_complex.png call %PICTBAT%blockPix disp_fx_complex.png |
|
Apply the same complex displacement to the identity absolute map, and convert it to a relative map. Just for fun, we also save and display the intermediate result, the absolute displacement map. %IMG7%magick ^ disp_ident_abs.png ^ ( -clone 0 ^ ( -size 1x6 gradient: -rotate 90 ) ^ -fx "p{sin(sin(sin(v)))*w,j}" ^ -depth 16 ^ -write disp_fx_abs.png ^ ) ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ -depth 16 ^ disp_fx_rel.png call %PICTBAT%blockPix disp_fx_abs.png call %PICTBAT%blockPix disp_fx_rel.png |
|
Apply the relative map to the source image. %IMG7%magick ^ disp_src.png ^ disp_fx_rel.png ^ -virtual-pixel Black ^ -compose Displace -set option:compose:args 5x0 -composite ^ disp_fx_rel_out.png call %PICTBAT%blockPix disp_fx_rel_out.png |
Are the results the same?
%IMG7%magick compare -metric RMSE disp_fx_complex.png disp_fx_rel_out.png NULL: cmd /c exit /B 0
0 (0)
TODO: reversability of displacements. For a 1-D image with all displacements equal, reversing is easy: d' = 1.0 - d. For a 2-D image with displacements in X or Y only, reversing is easy.
fxTri.bat crops out a central triangle and tiles it. It is a complex and slow transformation that we might want to apply to many frames of a video. Sadly, it takes 41 seconds per frame on a particular computer. We can improve the speed by creating a displacement map, then using that for each of the frames. This takes 3 seconds per frame.
To create the displacement map, we first create an identity absolute displacement map. We distort this in the desired way. The result is an absolute displacement map that will distort any image in the same way.
First, merely so we can see what we are doing, we will apply fxTri to an image %FRAME%.
set FRAME=%VIDBAT%fxFrame.png if not exist %FRAME% goto error FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify -format "Width=%%w\nHeight=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" %FRAME%`) ^ DO set %%L
set FXTEMP=\temp\ call %VIDBAT%fxTri %FRAME% tri_src.png
Here is a small version of %FRAME%:
%IMG7%magick %FRAME% -resize 1000x600 disp_src_sm.jpg
This is a small version of tri_src.png, which is %FRAME% transformed by fxTri:
%IMG7%magick tri_src.png -resize 1000x600 tri_src_sm.jpg
We build a relative displacement map by:
An absolute displacement map specifies, for each destination coordinate, the coordinate for the source pixel. The red channel gives the x-coordinate (left to right), and green gives the y-coordinate (top to bottom). We zero the blue channel. For an identity map, the top-left pixel is black while bottom-right has maximum values.
The result of step (2) is an absolute map for the fxTri displacement.
The result of step (3) is an relative map for the fxTri displacement.
call %PICTBAT%mDispMap %Width% %Height% idAbsDispMap.png call %VIDBAT%fxTri idAbsDispMap.png dispMap_tri.png %IMG7%magick ^ idAbsDispMap.png ^ dispMap_tri.png ^ -compose Mathematics ^ -define compose:args=0,1,-1,0.5 ^ -composite ^ dispMap_tri_r.png
(The compose maths is equivalent to -compose ModulusSubtract -composite -evaluate AddModulus 50%%).
We need the resulting relative displacement map, dispMap_tri_r.png, to range from zero to Quantum. One input file is subtracted from the other, so if they ranged from zero to Quantum, the subtracted result would range from -Quantum to +Quantum. This is why mDispMap.bat divides by two.
We can then use the relative displacement map to transform the source or any other frame. This is the only command that is executed for every frame. It would usually create just tri_src2.png. For the purpose of this web page, we also create a small version.
%IMG7%magick ^ %FRAME% ^ dispMap_tri_r.png ^ -compose Displace -set option:compose:args %Wm1%x%Hm1% -composite ^ -write tri_src2.png ^ -resize 1000x600 ^ tri_src2_sm.jpg
This is tri_src2_sm.jpg:
The result created from the displacement map is not visibly different to the direct result.
%IMG7%magick compare -metric RMSE tri_src.png tri_src2.png NULL: cmd /c exit /B 0
290.474 (0.00443235)
We can look at the three maps, shrunk for convenience of this web page.
The identity absolute displacement map%IMG7%magick idAbsDispMap.png -resize 400x400 dispMap_sm.png This is black in the top-left corner, with increasing red to the right and increasing green downwards. The maximum red (down the right edge) and green (along the bottom) is 50% of quantum. |
|
The absolute displacement map for the fxTri displacement%IMG7%magick dispMap_tri.png -resize 400x400 dispMap_tri_sm.png The centres of the four edges are the same dark red colour because they represent the same pixels of the source image. The top-centre of the identity absolute map is the same colour because that is the position of the source pixels. Beneath this, half way down the map, pixels are light green, so image pixels will be moved upwards. Various symmetries can be seen. |
|
The relative displacement map for the fxTri displacement%IMG7%magick dispMap_tri_r.png -resize 400x400 dispMap_tri_r_sm.png The top-centre is mid-gray, indicating there is no displacement here. |
We have seen above how to convert from absolute to relative, using modulus arithmetic:
relative = absolute - ident_absolute + 50%
Thus the opposite conversion is:
absolute = relative + ident_absolute - 50%
As an example, from the relative dispacement map created above, we can calculate the absolute displacement map, then compare with the original.
Sadly, IM doesn't have "-evaluate SubtractModulus X%". Instead, we can "-evaluate AddModulus Y%" where Y=100-X.
%IMG7%magick ^ dispMap_tri_r.png ^ idAbsDispMap.png ^ -compose ModulusAdd -composite ^ -evaluate AddModulus 50%% ^ dispCalcAbs.png %IMG7%magick compare -metric RMSE dispMap_tri.png dispCalcAbs.png NULL: cmd /c exit /B 0
98.2556 (0.00149929)
Or, using "-compose mathematics":
%IMG7%magick ^ dispMap_tri_r.png ^ idAbsDispMap.png ^ -compose Mathematics ^ -define compose:args=0,1,1,-0.5 ^ -composite ^ dispCalcAbs2.png %IMG7%magick compare -metric RMSE dispMap_tri.png dispCalcAbs2.png NULL:
7.02775 (0.000107237)
Or we can do it with -sparse-color:
set IDENT=^ 0,0,#008,^ %%[fx:w-1],0,#f08,^ 0,%%[fx:h-1],#0f8,^ %%[fx:w-1],%%[fx:h-1],#ff8 %IMG7%magick ^ dispMap_tri_r.png ^ ( +clone ^ -sparse-color bilinear "%IDENT%" ^ ) ^ -compose Mathematics ^ -define compose:args=0,1,1,-0.5 ^ -composite ^ +depth ^ disp_abs_map2.png %IMG7%magick ^ disp_abs_map2.png ^ ( +clone ^ -sparse-color bilinear "%IDENT%" ^ ) ^ -compose Mathematics ^ -define compose:args=0,-1,1,0.5 ^ -composite ^ +depth ^ disp_rel_map2.png %IMG7%magick compare -metric RMSE dispMap_tri_r.png disp_rel_map2.png NULL:
3.55286 (5.42132e-05)
When adding multiple displacement maps to get a single result map, remembering that Quantum/2 means zero displacement, we need to subtract 50% from each before the addition.
result = (relative1 - 50%) + (relative2 - 50%) + 50% = relative1 + relative2 - 50%
Given a displacement map, it would be useful if we could derive an inverse that would put pixels back where they started. However, in the forwards direction, one source pixel can map to any number of destinations, and not all the source pixels might have a destination. Both these are true of the fxTri example.
If an absolute displacement map has only one dimension, and displaces in the direction of that dimension, and either increases or decreases but not both, then invClut.bat will invert it. See Clut cookbook: inverting cluts.
A relative displacement map can also be inverted, using invRelClut.bat, provided the corresponding absolute map complies with the same conditions.
The script invRelClut.bat does the following:
Use cp_asin.png, from Cylinders: call %PICTBAT%graph1d cp_asin.png |
|
Invert it. call %PICTBAT%invRelClut cp_asin.png call %PICTBAT%graph1d cp_asin_irc.png |
|
Demonstrate the inversion.
FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify ^ -format "WW=%%w\nW_2=%%[fx:w/2]" ^ cp_asin.png`) ^ DO set %%L %IMG7%magick ^ -size 1x%WW% gradient: -rotate 90 ^ -write disp_rel0.png ^ ( +clone ^ cp_asin.png ^ -compose Displace -set option:compose:args %W_2%x0 -composite ^ -write disp_rel1.png ^ cp_asin_irc.png ^ -compose Displace -set option:compose:args %W_2%x0 -composite ^ -write disp_rel2.png ^ ) ^ -metric RMSE ^ -format "%%[distortion]" ^ -compare info: 0.00323577 call %PICTBAT%graph1d disp_rel0.png call %PICTBAT%graph1d disp_rel1.png call %PICTBAT%graph1d disp_rel2.png |
A two-dimensional displacement map that has only a horizontal displacement can be considered as a number of 1-D maps appended vertically. So one method for inversion is to loop through all the rows, cropping each one out and processing it as for invClut.bat or invRelClut.bat, then appending these vertically. See inv2dAbsDisp.bat.
Another way of inverting a horizontal relative displacement map is to transform it to a absolute map, use the process module invclut (see Process modules: invclut), and transform it back to an relative map. This can be used where each row of the map represents a different displacement. The simple script invHRDM.bat does this.
call %PICTBAT%invHRDM cp_asin.png call %PICTBAT%graph1d cp_asin_ih.png |
A two-dimensional absolute displacement map with both horizontal and vertical displacement can be inverted with the process module invdispmap. See Process module: invert displacement map.
These techniques can be used for transforming shapes into each other. See Shape to shape: displacement maps.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
@rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem setlocal set W=%1 set H=%2 set OUT_FILE=%3 if "%OUT_FILE%"=="" ( echo %0 needs 3 parameters: width height outfile exit /B 1 ) %IMG7%magick ^ ( -size %H%x%W% gradient: -rotate 90 ) ^ ( -size %W%x%H% gradient: -flip ) ^ -size %W%x%H% xc:Black ^ -combine ^ -evaluate Divide 2 ^ -set colorspace sRGB ^ %OUT_FILE%
rem From image %1, enlarges pixels into blocks. rem Optional %2 is output filename. @rem @rem Also regards: @rem bpSCALE muliplier for pixel size. Default 24. @rem bpGAP pixels gap between block pixels. Default 5. @rem bpGAPCOL colour for gap between pixels. Default None. @rem bpDO_SHAD 0=no shadows, 1=make shadows. Deafult 1. @rem bpDO_BACK_CHECK whether to put checkerboard behind blocks. Default 0. @rem bpDO_CIRCLE whether to draw a circle on a pixel. Default 0. @rem bpCIRC_X, bpCIRC_Y coords of pixel to draw circle on @rem bpDO_CROSS whether to draw a diagonal cross on a pixel. Default 0. @rem bpCROSS_X, bpCROSS_Y coords of pixel to draw cross on @rem bpTEXT Text to append at bottom. Default none. @rem @rem Updated: @rem 28-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal call echoOffSave call %PICTBAT%setInOut %1 bp if "%bpSCALE%"=="" set bpSCALE=24 if "%bpGAP%"=="" set bpGAP=5 if "%bpGAPCOL%"=="" set bpGAPCOL=none if "%bpDO_SHAD%"=="" set bpDO_SHAD=1 if "%bpDO_BACK_CHECK%"=="" set bpDO_BACK_CHECK=0 if not "%2"=="" set OUTFILE=%2 for /F %%i in ('%IMG7%magick identify ^ -format "wd2=%%[fx:%bpSCALE%/2-0.5]\nhd2=%%[fx:%bpSCALE%/2-0.5]\nsm1=%%[fx:%bpSCALE%-1]\nsm2=%%[fx:%bpSCALE%-2]" ^ xc:') do set %%i if "%bpDO_CIRCLE%"=="1" ( if "%bpCIRC_X%"=="" set CIRC_X=1 if "%bpCIRC_Y%"=="" set CIRC_Y=1 for /F %%i in ('%IMG7%magick identify ^ -format "ox=%%[fx:!bpCIRC_X!*%bpSCALE%]\noy=%%[fx:!bpCIRC_Y!*%bpSCALE%]" ^ xc:') do set %%i set CIRC=-fill none ^ -stroke white -draw "translate !ox!,!oy! circle %wd2%,%hd2%,1,%hd2%" ^ -stroke black -draw "translate !ox!,!oy! circle %wd2%,%hd2%,2,%hd2%" ) else ( set CIRC= ) if "%bpDO_CROSS%"=="1" ( if "%bpCROSS_X%"=="" set CROSS_X=2 if "%bpCROSS_Y%"=="" set CROSS_Y=1 for /F %%i in ('%IMG7%magick identify ^ -format "cx=%%[fx:!bpCROSS_X!*%bpSCALE%]\ncy=%%[fx:!bpCROSS_Y!*%bpSCALE%]" ^ xc:') do set %%i set LINE=-stroke black -draw "translate !cx!,!cy! line 0,0,%sm1%,%sm1% line 0,%sm1%,%sm1%,0" ^ -stroke white -draw "translate !cx!,!cy! line 1,0,%sm1%,%sm2% line 0,%sm2%,%sm2%,0" ) else ( set LINE= ) if not "%bpTEXT%"=="" ( set TEXT=^( +clone ^ -alpha off ^ -fill white -colorize 100 ^ -gravity South -pointsize 18 -fill Black ^ -annotate 0 "%bpTEXT%" ^ -bordercolor White ^ -border 1 -trim +repage ^ -border 4 +repage ^) ^ -append +repage ) else ( set TEXT= ) if %bpDO_BACK_CHECK%==1 ( set BACK_CHK=^( +clone ^ -fill pattern:checkerboard -draw "color 0,0 reset" ^) ^ +swap ^ -compose Over -composite ) else ( set BACK_CHK= ) if "%bpDO_SHAD%"=="1" ( set SHAD_COL=rgb^^^(0,0,75%%^^^) set SHAD_COL=black set /A SHAD_X=%bpGAP%/2 set /A SHAD_Y=%bpGAP%/2 set SHAD=^( +clone -background !SHAD_COL! -shadow 100x2+!SHAD_X!+!SHAD_Y! ^) ^ +swap -background None -compose Over -layers merge ) else ( set SHAD= ) %IMG7%magick ^ %INFILE% ^ -scale %bpSCALE%00%% ^ %CIRC% ^ %LINE% ^ -background %bpGAPCOL% ^ -crop 0x%bpSCALE% -splice 0x%bpGAP% -append +repage -chop 0x%bpGAP% ^ -crop %bpSCALE%x0 -splice %bpGAP%x0 +append +repage -chop %bpGAP%x0 ^ -bordercolor %bpGAPCOL% -compose Copy -border %bpGAP% ^ %SHAD% ^ +repage ^ %TEXT% ^ %BACK_CHK% ^ +repage ^ %OUTFILE% call echoRestore & set bpOUTFILE=%OUTFILE%
rem Given %1 is a 2-D absolute displacement map, rem where each row displaces horizontally and either increases or decreases but not both, rem makes the inverse by inverting each line separately. rem %2 is output file [mandatory] rem %3 is channel. Default RGB. rem %4 is 0=all rows increase or 1=all rows decrease. Default 0. @rem @rem Updated: @rem 15 May 2016 use %IML% for @script. @rem 21-August-2022 Upgraded for IM v7. @rem 23-August-2022 Insert %2 as output [mandatory]. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 iad set OUTFILE=%2 set sCHANNEL=%3 if "%sCHANNEL%"=="." set sCHANNEL= if "%sCHANNEL%"=="" set sCHANNEL=RGB set NEG=%4 if "%NEG%"=="." set NEG= if "%NEG%"=="" set NEG=0 rem @-files can't have drive, so can't be %TEMP% set FSCRIPT=%~n1_iad.scr set FGRAD=%TEMP%\%~n1_iad.miff del /q %FSCRIPT% 2>nul FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" %INFILE%`) ^ DO set %%L set sNEG= if %NEG%==1 set sNEG=-negate ( for /L %%Y in (0,1,%Hm1%) do ( echo ^( -clone 0 -crop %WW%x1+0+%%Y +repage -scale "%WW%x%WW%^!" -clone 1 -compose MinusDst -composite -channel %sCHANNEL% -threshold 0 %sNEG% +channel -scale "1x%WW%^!" -rotate 90 ^) ) echo -delete 0-1 -append echo -write %OUTFILE% echo -exit ) >%FSCRIPT% %IMG7%magick %INFILE% -size %WW%x%WW% gradient: -script %FSCRIPT% if ERRORLEVEL 1 ( echo %0: magick script failed exit /B 1 ) %IMG7%magick identify %OUTFILE% call echoRestore
rem From %1, a clut image, height=1, make the inverse clut. rem Optional %2 is output filename. @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 icl if not "%2"=="" set OUTFILE=%2 FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]" %INFILE%`) ^ DO set %%L if not %HH%==1 ( echo %0: Expected height of 1 in %* exit /B 1 ) FOR /F "usebackq" %%L ^ IN (`%IMG7%magick ^ %INFILE% ^ -format "negR=%%[fx:p{0,0}.r>p{%Wm1%,0}.r]\nnegG=%%[fx:p{0,0}.g>p{%Wm1%,0}.g]\nnegB=%%[fx:p{0,0}.b>p{%Wm1%,0}.b]" ^ info:`) ^ DO set %%L set sNegR= set sNegG= set sNegB= if %negR%==1 set sNegR=-channel R -negate if %negG%==1 set sNegG=-channel G -negate if %negB%==1 set sNegB=-channel B -negate %IMG7%magick ^ %INFILE% ^ -scale "%WW%x%WW%^!" ^ -size %WW%x%WW% gradient: ^ -compose MinusDst -composite ^ -channel RGB -threshold 0 ^ %sNegR% %sNegG% %sNegB% ^ +channel ^ -scale "1x%WW%^!" ^ -rotate 90 ^ %OUTFILE% call echoRestore @endlocal & set iclOUTFILE=%OUTFILE%
rem From %1, rem a single-row clut image that represents a parallel relative displacement map, rem make the inverse clut. @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 irc FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]" %INFILE%`) ^ DO set %%L if not %HH%==1 ( echo %0: Expected height of 1 in %* exit /B 1 ) FOR /F "usebackq" %%L ^ IN (`%IMG7%magick ^ %INFILE% ^ -format "negR=%%[fx:p{0,0}.r>p{%Wm1%,0}.r]\nnegG=%%[fx:p{0,0}.g>p{%Wm1%,0}.g]\nnegB=%%[fx:p{0,0}.b>p{%Wm1%,0}.b]" ^ info:`) ^ DO set %%L set sNegR= set sNegG= set sNegB= if %negR%==1 set sNegR=-channel R -negate if %negG%==1 set sNegG=-channel G -negate if %negB%==1 set sNegB=-channel B -negate if "%ircDEBUG%"=="1" ( set wr0=-write irc0.png set wr1=-write irc1.png ) else ( set wr0= set wr1= ) %IMG7%magick ^ %INFILE% ^ ( -size 1x%WW% gradient: -rotate 90 -write mpr:GRAD ) ^ -compose Mathematics -define compose:args=0,1,1,-0.5 -composite ^ %wr0% ^ -scale "%WW%x%WW%^!" ^ -size %WW%x%WW% gradient: ^ -compose MinusDst -composite ^ -channel RGB -threshold 0 ^ %sNegR% %sNegG% %sNegB% ^ +channel ^ -scale "1x%WW%^!" ^ -rotate 90 ^ %wr1% ^ mpr:GRAD ^ -compose Mathematics -define compose:args=0,-1,1,0.5 -composite ^ %OUTFILE% if "%ircDEBUG%"=="1" ( call %PICTBAT%graph1d irc0.png call %PICTBAT%graph1d irc1.png %IMG7%magick ^ rose: ^ ^( +clone ^ irc0.png -clut ^ irc1.png -clut ^ ^) ^ -metric RMSE ^ -format "irc0.png to irc1.png: %%[distortion]\n" ^ -compare info: rem %IMG7%magick identify %OUTFILE% call %PICTBAT%graph1d %OUTFILE% ) call echoRestore
rem From %1, rem where each row is a horizontal relative displacement map, rem inverts it. rem rem Optional %2 is output filename. @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 ih if not "%2"=="" set OUTFILE=%2 %IM7DEV%magick ^ %INFILE% ^ ( +clone ^ -sparse-color Bilinear 0,0,#000,%%[fx:w-1],0,#fff ^ -write mpr:GRAD ^ ) ^ -compose Mathematics -define compose:args=0,1,1,-0.5 -composite ^ -process invclut ^ mpr:GRAD ^ -compose Mathematics -define compose:args=0,-1,1,0.5 -composite ^ %OUTFILE% call echoRestore
@rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem @echo off rem See \prose\pictures\hex.bat setlocal set SRC=%1 set nRows=%3 if "%nRows%"=="" set nRows=2 set STYLE=%4 if "%STYLE%"=="" set STYLE=RESIZE set TMPEXT=miff FOR /F %%i IN ('%IMG7%magick identify -format "%%[fx:int(2*%Height%/sqrt(3)+0.5)]" xc:') DO set TriSide=%%i set /A SemiTriSide=%TriSide%/2 set /A semiWi=%Width%/2 set /A wim1=%Width%-1 set /A htm1=%Height%-1 set /A TriLeft=(%Width%-%TriSide%)/2 set /A TriRight=(%Width%+%TriSide%)/2 set /A CanvasWi=%TriSide%*3 set /A CanvasSemiWi=%TriSide%*3/2 set /A Canv23=%TriSide%*2 set /A Canv13=%TriSide% "%IMG7%magick" ^ -size %Width%x%Height% xc:White ^ -set colorspace RGB ^ -fill #000 ^ -draw "polygon 0,0 %semiWi%,0 %TriLeft%,%htm1% 0,%htm1%" ^ -alpha off ^ -colorspace sRGB ^ -write %FXTEMP%hexBlankL.%TMPEXT% ^ -flop ^ %FXTEMP%hexBlankR.%TMPEXT% "%IMG7%magick" ^ -size %CanvasWi%x%Height% xc:None ^ -gravity North ^ %SRC% ^ -compose Over -composite ^ -write %FXTEMP%hexOnCanv.%TMPEXT% ^ %FXTEMP%hexBlankR.%TMPEXT% ^ -compose CopyOpacity -composite ^ ( +clone -flip -write %FXTEMP%hexL3.%TMPEXT% +delete ) ^ -virtual-pixel None ^ -distort SRT "%CanvasSemiWi%,0 -1,1 -60 %CanvasSemiWi%,0" ^ %FXTEMP%hexR.%TMPEXT% "%IMG7%magick" ^ %FXTEMP%hexOnCanv.%TMPEXT% ^ %FXTEMP%hexBlankL.%TMPEXT% ^ -gravity North ^ -compose CopyOpacity -composite ^ ( +clone -flip -write %FXTEMP%hexR3.%TMPEXT% +delete ) ^ -virtual-pixel None ^ -distort SRT "%CanvasSemiWi%,0 -1,1 60 %CanvasSemiWi%,0" ^ %FXTEMP%hexL.%TMPEXT% "%IMG7%magick" ^ %FXTEMP%hexOnCanv.%TMPEXT% ^ -virtual-pixel None ^ ( +clone ^ -distort SRT "%Canv23%,%htm1% 1,1 120 %Canv23%,%htm1%" ^ -write %FXTEMP%hexR2.%TMPEXT% +delete ) ^ -distort SRT "%Canv13%,%htm1% 1,1 -120 %Canv13%,%htm1%" ^ %FXTEMP%hexL2.%TMPEXT% "%IMG7%magick" ^ %FXTEMP%hexOnCanv.%TMPEXT% ^ -gravity North ^ %FXTEMP%hexR.%TMPEXT% ^ -composite ^ %FXTEMP%hexR2.%TMPEXT% ^ -composite ^ %FXTEMP%hexL.%TMPEXT% ^ -composite ^ %FXTEMP%hexL2.%TMPEXT% ^ -composite ^ ( %FXTEMP%hexL3.%TMPEXT% -geometry -%CanvasSemiWi%+0 ) ^ -composite ^ ( %FXTEMP%hexR3.%TMPEXT% -geometry +%CanvasSemiWi%+0 ) ^ -composite ^ %FXTEMP%hexX.%TMPEXT% rem -crop %Width%x%Height%+0+0 ^ rem hexXc.%TMPEXT% rem If nRows==1, we've got it. if %nRows%==1 goto skipMult set sThird= if %nRows%==3 set sThird=+clone -swap 0,1 "%IMG7%magick" ^ %FXTEMP%hexX.%TMPEXT% ^ ( +clone -flip ) ^ %sThird% ^ -append +repage ^ %FXTEMP%hexX.%TMPEXT% :skipMult rem hexX.%TMPEXT% is %CanvasWi% wide, %Height% * nRows high. set sResize= set sCrop= set sPad= rem Where are these supposed to come from? set nWi=0 set nHt=0 echo STYLE = %STYLE% echo nWi=%nWi% nHt=%nHt% if /I %STYLE% EQU CROP ( echo Crop rem set sCrop=-gravity Center -crop %Width%x%Height%+0+0 +repage set /A cropWi=%Width% set /A cropHt=%Height% ) else if /I %STYLE% EQU RESIZE ( echo Resize set sResize=-resize "%Width%x%Height%^^^! " set /A cropWi=0 ) else if /I %STYLE% EQU CROPRESIZE ( echo CropResize rem FIXME if %nWi% EQU %nHt% ( echo No crop set /A cropWi=0 ) else if %nWi% GTR %nHt% ( set /A cropWi=%Width%*%nHt% set /A cropHt=%Height%*%nHt% ) else ( set /A cropWi=%Width%*%nWi% set /A cropHt=%Height%*%nWi% ) set sResize=-resize "%Width%x%Height%^^^! " ) else if /I %STYLE% EQU RESIZEPAD ( echo ResizePad set sResize=-resize "%Width%x%Height%" set sPad=-size %Width%x%Height% xc:%fxBackground% +swap -gravity Center -compose Over -composite set /A cropWi=0 ) else ( echo fxTri: Bad style [%STYLE%] exit /B 1 ) if not %cropWi%==0 set sCrop=-gravity Center -crop %cropWi%x%cropHt%+0+0 +repage "%IMG7%magick" ^ %FXTEMP%hexX.%TMPEXT% ^ %sCrop% ^ %sResize% ^ %sPad% ^ %WrFileOptions% ^ %2 "%IMG7%magick" identify %2 @echo on
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.0-49 Q16-HDRI x64 7a3f3f1:20220924 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php 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 (193331630)
Source file for this web page is displace.h1. To re-create this web page, execute displace.bat.
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 v2.3 24-July-2016.
Page created 17-Mar-2023 02:02:28.
Copyright © 2023 Alan Gibson.