snibgo's ImageMagick pages

Displacement maps

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.


Simple examples

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 (`%IM%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.

%IM%convert ^
  xc:red xc:yellow xc:lime xc:cyan xc:blue xc:magenta +append ^
  disp_src.png

call %PICTBAT%blockPix disp_src.png
disp_src_bp.png

Create three displacement maps: black, gray and white.

%IM%convert ^
  -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
disp_black_bp.png
disp_gray_bp.png
disp_white_bp.png

Apply the three displacement maps.

%IM%convert ^
  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
disp_bl_out_bp.png
disp_gr_out_bp.png
disp_wh_out_bp.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.

%IM%convert ^
  disp_src.png ^
  ( xc:white xc:black xc:white xc:black xc:white xc:black +append ) ^
  -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.

disp_altern_bp.png

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.

%IM%convert ^
  disp_src.png ^
  ( xc:white xc:black xc:white xc:black xc:white xc:black +append ) ^
  -virtual-pixel None ^
  -compose Displace -set option:compose:args 0.5x0 -composite ^
  -depth 16 ^
  disp_altern2.png

call %PICTBAT%blockPix disp_altern2.png
disp_altern2_bp.png

So the first two pixels are very nearly the same, as are the second pair, and the third pair.

%IM%convert disp_altern2.png txt:disp_altern2.txt
# ImageMagick pixel enumeration: 6,1,65535,srgb
0,0: (65535,32767,0)  #FFFF7FFF0000  srgb(100%,50%,0%)
1,0: (65535,32768,0)  #FFFF80000000  srgb(100%,50%,0%)
2,0: (0,65535,32767)  #0000FFFF7FFF  srgb(0%,100%,50%)
3,0: (0,65535,32768)  #0000FFFF8000  srgb(0%,100%,50%)
4,0: (32767,0,65535)  #7FFF0000FFFF  srgb(50%,0%,100%)
5,0: (32768,0,65535)  #80000000FFFF  srgb(50%,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.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,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.

disp_q1_bp.png

Use white displacement map and negative argument. This should give the same result.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,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%,0%)
5,0: (65525,65525,65525)  #FFF5FFF5FFF5  srgb(100%,100%,100%)
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.

disp_q2_bp.png

Use white displacement map and negative argument of 5 * 32768/32767.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,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.

disp_q3_bp.png

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.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,65535,srgb
0,0: (65535,45535,45535)  #FFFFB1DFB1DF  srgb(100%,69%,69%)
1,0: (20000,20000,20000)  #4E204E204E20  srgb(31%,31%,31%)
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.

disp_q4_bp.png

Use grey #800080008000 displacement map and large positive argument.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,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.

disp_q5_bp.png

Use grey #800180018001 displacement map and large positive argument.

%IM%convert ^
  xc:white -size 9x1 xc:black +append ^
  -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,65535,srgb
0,0: (45535,45535,45535)  #B1DFB1DFB1DF  srgb(69%,69%,69%)
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(31%,0%,0%)

The result is wrong.

disp_q6_bp.png

Conclusion: grey #800080008000 is the correct value for zero displacement.

Does HDRI help?

Repeat with Q32 HDRI.

if "%IM32f%"=="" call %PICTBAT%setIm8

if "%IM32f%"=="" goto error

%IM32f%convert ^
  disp_src.png ^
  ( xc:white xc:black xc:white xc:black xc:white xc:black +append ) ^
  -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_bp.png

disp_altern2f.lis is:

# ImageMagick pixel enumeration: 6,1,4294967295,srgba
0,0: (100%,50%,0%,1)  #FFFFFFFF7FFFFFFF00000000  srgba(100%,50%,0%,1)
1,0: (100%,50%,0%,1)  #FFFFFFFF8000000000000000  srgba(100%,50%,0%,1)
2,0: (0%,100%,50%,1)  #00000000FFFFFFFF7FFFFFFF  srgba(0%,100%,50%,1)
3,0: (0%,100%,50%,1)  #00000000FFFFFFFF80000000  srgba(0%,100%,50%,1)
4,0: (50%,0%,100%,1)  #7FFFFFFF00000000FFFFFFFF  srgba(50%,0%,100%,1)
5,0: (50%,0%,100%,1)  #8000000000000000FFFFFFFF  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".

%IM%convert ^
  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
disp_grad_bp.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.

%IM%convert ^
  -size 1x6 gradient: -rotate 90 ^
  -depth 16 ^
  disp_ident_abs.png

call %PICTBAT%blockPix disp_ident_abs.png
disp_ident_abs_bp.png

Sadly, IM has no command to directly apply an absolute displacement map. We can either use "-fx" (see Apply Special Effects to an Image with an Fx Expression) or 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.

%IM%convert ^
  xc:black xc:white xc:black xc:white xc:black xc:white +append ^
  disp_abs_altends.png

call %PICTBAT%blockPix disp_abs_altends.png
disp_abs_altends_bp.png

The fx method works directly from the absolute map:

Use "-fx" to alternate pixels.

%IM%convert ^
  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:
v is the value found in disp_abs_altends.png;
w is the width (6 in this example);
j ranges over the y-coordinate (just zero in this example).

disp_fx_altends_out_bp.png

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.

%IM%convert ^
  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
disp_rel_altends_bp.png

The equivalent using "-compose mathematics".

%IM%convert ^
  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
disp_rel_altends2_bp.png

Apply the relative displacement map.

"-virtual-pixel Black" is only for demonstration purposes.

The first argument is (width-1).

%IM%convert ^
  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
disp_altends_out_bp.png

Are the results accurate?

%IM%convert disp_fx_altends_out.png txt:disp_fx_altends_out.txt
%IM%convert disp_altends_out.png txt:disp_altends_out.txt
# ImageMagick pixel enumeration: 6,1,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,65535,srgb
0,0: (65535,0,0)  #FFFF00000000  red
1,0: (65531,0,65535)  #FFFB0000FFFF  srgb(100%,0%,100%)
2,0: (65535,2,0)  #FFFF00020000  srgb(100%,0%,0%)
3,0: (65533,0,65535)  #FFFD0000FFFF  srgb(100%,0%,100%)
4,0: (65529,0,0)  #FFF900000000  srgb(100%,0%,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.

%IM%convert ^
  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
disp_fx_complex_bp.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.

%IM%convert ^
  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
disp_fx_abs_bp.png
disp_fx_rel_bp.png

Apply the relative map to the source image.

%IM%convert ^
  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
disp_fx_rel_out_bp.png

Are the results the same?

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

Complex displacements

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 (`%IM%identify -format "Width=%%w\nHeight=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" %FRAME%`) ^
DO set %%L
call %VIDBAT%fxTri %FRAME% tri_src.png

Here is a small version of %FRAME%:

%IM%convert %FRAME% -resize 1000x600 disp_src_sm.jpg
disp_src_sm.jpg

This is a small version of tri_src.png, which is %FRAME% transformed by fxTri:

%IM%convert tri_src.png -resize 1000x600 tri_src_sm.jpg
tri_src_sm.jpg

We build a relative displacement map by:

  1. Create a identity absolute displacement map.
  2. Apply fxTri to (1).
  3. Subtract (1) from (2) and offset by 50% so that 50% gray will represent zero.

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

%IM%convert ^
  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.

%IM%convert ^
  %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:

tri_src2_sm.jpg

The result created from the displacement map is not visibly different to the direct result.

%IM%compare -metric RMSE tri_src.png tri_src2.png NULL: 
cmd /c exit /B 0
247.558 (0.0037775)

We can look at the three maps, shrunk for convenience of this web page.

The identity absolute displacement map

%IM%convert 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.

dispMap_sm.png

The absolute displacement map for the fxTri displacement

%IM%convert 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.

dispMap_tri_sm.png

The relative displacement map for the fxTri displacement

%IM%convert dispMap_tri_r.png -resize 400x400 dispMap_tri_r_sm.png

The top-centre is mid-gray, indicating there is no displacement here.

dispMap_tri_r_sm.png

Converting between relative and absolute displacements

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.

%IM%convert ^
  dispMap_tri_r.png ^
  idAbsDispMap.png ^
  -compose ModulusAdd -composite ^
  -evaluate AddModulus 50%% ^
  dispCalcAbs.png

%IM%compare -metric RMSE dispMap_tri.png dispCalcAbs.png NULL: 
cmd /c exit /B 0
37.1602 (0.000567028)

Or, using "-compose mathematics":

%IM%convert ^
  dispMap_tri_r.png ^
  idAbsDispMap.png ^
  -compose Mathematics ^
    -define compose:args=0,1,1,-0.5 ^
    -composite ^
  dispCalcAbs2.png

%IM%compare -metric RMSE dispMap_tri.png dispCalcAbs2.png NULL: 
0.646858 (9.87041e-006)

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

%IM%convert ^
  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

%IM%convert ^
  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

%IM%compare -metric RMSE dispMap_tri_r.png disp_rel_map2.png NULL: 
0.871379 (1.32964e-005)

Adding relative displacements

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%

Inverse displacements

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

Invert it.

call %PICTBAT%invRelClut cp_asin.png

call %PICTBAT%graph1d cp_asin_irc.png
cp_asin_irc_g1d.png

Demonstrate the inversion.
Make a source image from a gradient;
displace it horizontally using cp_asin.png,
then with the inverse of cp_asin.png.

FOR /F "usebackq" %%L ^
IN (`%IM%identify ^
  -format "WW=%%w\nW_2=%%[fx:w/2]" ^
  cp_asin.png`) ^
DO set %%L

%IM%convert ^
  -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.00316653
call %PICTBAT%graph1d disp_rel0.png
call %PICTBAT%graph1d disp_rel1.png
call %PICTBAT%graph1d disp_rel2.png
disp_rel0_g1d.png disp_rel1_g1d.png disp_rel2_g1d.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
cp_asin_ih_g1d.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.

Displacing one shape into another

These techniques can be used for transforming shapes into each other. See Shape to shape: displacement maps.

Scripts

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

mDispMap.bat

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
)

%IM%convert ^
  ( -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%

blockPix.bat

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.

@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 ('%IM%identify ^
  -ping ^
  -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 ('%IM%identify ^
  -ping ^
  -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 ('%IM%identify ^
  -ping -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

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

%IM%convert ^
  %INFILE% ^
  -scale %bpSCALE%00%% ^
  %CIRC% ^
  %LINE% ^
  -background %bpGAPCOL% ^
  -crop 0x%bpSCALE% -splice 0x%bpGAP% -append -chop 0x%bpGAP% ^
  -crop %bpSCALE%x0 -splice %bpGAP%x0 +append -chop %bpGAP%x0 ^
  -bordercolor %bpGAPCOL% -compose Copy -border %bpGAP% ^
  %SHAD% ^
  +repage ^
  %TEXT% ^
  %BACK_CHK% ^
  +repage ^
  %OUTFILE%

call echoRestore & set bpOUTFILE=%OUTFILE%

inv2dAbsDisp.bat

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 channel. Default RGB.
rem %3 is 0=all rows increase or 1=all rows decrease. Default 0.
@rem
@rem Updated:
@rem   15 May 2016 use %IML% for @script.


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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 iad

set sCHANNEL=%2
if "%sCHANNEL%"=="" set sCHANNEL=RGB

set NEG=%3
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%

FOR /F "usebackq" %%L ^
IN (`%IM%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

echo %INFILE% >%FSCRIPT%
echo -size %WW%x%WW% gradient: >>%FSCRIPT%
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 ^)>>%FSCRIPT%
)

%IML%convert ^
  @%FSCRIPT% ^
  -delete 0-1 ^
  -append ^
  %OUTFILE%

if ERRORLEVEL 1 (
  echo @0: failed for @*
  exit /B 1
)

%IM%identify %OUTFILE%

call echoRestore

invClut.bat

rem From %1, a clut image, height=1, make the inverse clut.
rem Optional %2 is output filename.

@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 (`%IM%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 (`%IM%convert ^
  %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

%IM%convert ^
  %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%

invRelClut.bat

rem From %1,
rem a single-row clut image that represents a parallel relative displacement map,
rem make the inverse clut.

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 irc

FOR /F "usebackq" %%L ^
IN (`%IM%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 (`%IM%convert ^
  %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=
)

%IM%convert ^
  %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

  %IM%convert ^
    rose: ^
    ^( +clone ^
      irc0.png -clut ^
      irc1.png -clut ^
    ^) ^
    -metric RMSE ^
    -format "irc0.png to irc1.png: %%[distortion]\n" ^
    -compare info:

  rem %IM%identify %OUTFILE%

  call %PICTBAT%graph1d %OUTFILE%
)

call echoRestore

invHRDM.bat

rem From %1,
rem where each row is a horizontal relative displacement map,
rem inverts it.
rem
rem Optional %2 is output filename.

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 ih

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

%IMDEV%convert ^
  %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

fxTri.bat

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

FOR /F %%i IN ('%IM%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%

"%IM%convert" ^
  -size %Width%x%Height% xc:White ^
  -set colorspace RGB ^
  -fill #000 ^
  -draw "polygon 0,0 %semiWi%,0 %TriLeft%,%htm1% 0,%htm1%" ^
  -colorspace sRGB ^
  -write %FXTEMP%hexBlankL.png ^
  -flop ^
  %FXTEMP%hexBlankR.png

"%IM%convert" ^
  -size %CanvasWi%x%Height% xc:Green ^
  -gravity North ^
  %SRC% ^
  -compose Over -composite ^
  -write %FXTEMP%hexOnCanv.png ^
  %FXTEMP%hexBlankR.png ^
  -compose CopyOpacity -composite ^
  ( +clone -flip -write %FXTEMP%hexL3.png +delete ) ^
  -virtual-pixel None ^
  -distort SRT "%CanvasSemiWi%,0 -1,1 -60 %CanvasSemiWi%,0" ^
  %FXTEMP%hexR.png

"%IM%convert" ^
  %FXTEMP%hexOnCanv.png ^
  %FXTEMP%hexBlankL.png ^
  -gravity North ^
  -compose CopyOpacity -composite ^
  ( +clone -flip -write %FXTEMP%hexR3.png +delete ) ^
  -virtual-pixel None ^
  -distort SRT "%CanvasSemiWi%,0 -1,1 60 %CanvasSemiWi%,0" ^
  %FXTEMP%hexL.png

"%IM%convert" ^
  %FXTEMP%hexOnCanv.png ^
  -virtual-pixel None ^
  ( +clone ^
    -distort SRT "%Canv23%,%htm1% 1,1 120 %Canv23%,%htm1%" ^
    -write %FXTEMP%hexR2.png +delete ) ^
  -distort SRT "%Canv13%,%htm1% 1,1 -120 %Canv13%,%htm1%" ^
  %FXTEMP%hexL2.png

"%IM%convert" ^
  %FXTEMP%hexOnCanv.png ^
  -gravity North ^
  %FXTEMP%hexR.png ^
  -composite ^
  %FXTEMP%hexR2.png ^
  -composite ^
  %FXTEMP%hexL.png ^
  -composite ^
  %FXTEMP%hexL2.png ^
  -composite ^
  ( %FXTEMP%hexL3.png -geometry -%CanvasSemiWi%+0 ) ^
  -composite ^
  ( %FXTEMP%hexR3.png -geometry +%CanvasSemiWi%+0 ) ^
  -composite ^
  %FXTEMP%hexX.png

rem  -crop %Width%x%Height%+0+0 ^
rem  hexXc.png

rem If nRows==1, we've got it.
if %nRows%==1 goto skipMult

set sThird=
if %nRows%==3 set sThird=+clone -swap 0,1

rem "%IM%convert" ^
rem   hexX.png ^
rem   ( +clone -flip ) ^
rem   -append ^
rem   hexX2.png

"%IM%convert" ^
  %FXTEMP%hexX.png ^
  ( +clone -flip ) ^
  %sThird% ^
  -append ^
  %FXTEMP%hexX.png

:skipMult
rem hexX.png 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

"%IM%convert" ^
  %FXTEMP%hexX.png ^
  %sCrop% ^
  %sResize% ^
  %sPad% ^
  %WrFileOptions% ^
  %2

"%IM%identify" %2

@echo on

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

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

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 24-Nov-2016 14:32:15.

Copyright © 2016 Alan Gibson.