snibgo's ImageMagick pages

Slopes

A simple scheme for creating, storing and processing slopes.

Some image processing works on slopes, also known as vector gradients or vector gradient fields.

In essence, a slope is the value of one pixel minus the value of another pixel, divided by the distance between them. This would give us the slope at the point mid-way between the pixels.

Instead, we may prefer to say the slope at a pixel is the value of the pixel at one side minus the value of the pixel at the other side. This can be done in the x-direction and the y-direction, so we get two values. Scripts here store slopes in a pair of colour images, typically two images in a single MIFF file. The first image is the gradient in the x-direction, the second is the gradient in the y-direction.

It may be helpful to think of the input image as a Digital Elevation Model (DEM), representing heights in a landscape, from a lowest level at 0% to a mountain at 100%. At any point on the landscape, the slope has a particular direction and magnitude.

Slopes of ordinary photographs are usually close to zero, and can be positive or negative.

In this context, literature often uses scalar to mean ordinary images with one value per channel per pixel, and vector to mean two values per channel per pixel.

Slopes are a form of differentiation.

These slope methods are useful for Seamless photomontage.

CAUTION: In v1.0 of this page, slope and div images were offset so zero was represented by pixel values of 50%.

If the range of input pixel values is 0 to 100%, then the range of slopes would -100% to +100%. Scripts to make a slope would divide by two and add 50% so the resulting values were in the range 0 to 100%, with 50% representing a zero slope. (Note that when we use the difference beween pixels on either side of a central pixel, the distance between them is two, so dividing by two is reasonable.)

However, the Fifty percent problem created difficulties, so now zero slopes are represented by pixel values of 0%, and negative values are common, and HDRI should always be used.

On this page, we divide by two and add 50% purely to show the image.

Sample inputs

%IM%convert ^
  -size 20x260 ^
  xc:Black xc:White xc:Black xc:Red xc:Black ^
  xc:Lime xc:Black xc:Blue xc:Black ^
  xc:gray(50%%) xc:Black xc:srgb(50%%,0,0) xc:Black ^
  +append ^
  slp_src.png
slp_src.png
%IM%convert ^
  slp_src.png ^
  -rotate 90 ^
  slp_src_h.png
slp_src_h.png
%IM%convert ^
  slp_src.png ^
  ( slp_src_h.png -transparent Black ) ^
  -compose Over -composite ^
  slp_src_vh.png

%IM%convert ^
  slp_src_vh.png ^
  -fill Black -colorize 100 ^
  -fill White -draw "circle 130,130 130,30" ^
  slp_src_vh.png
slp_src_vh.png

toes.png

toes.pngjpg

Commands on this page use HDRI. The convert in %IM32f% is assumed to be HDRI. It is also Q32. (32f = Q32, floating point.)

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

Operations

The script slopeXY.bat takes an input colour image and makes a vector. Each output pixel is the input pixel on the right (or below) minus the input pixel on the left (or above). Hence, a 3x3 rectangular window is used, but only four pixels participate, with all having the same weight.

Ẋ(x,y) = f(x+1,y) - f(x-1,y)
Ẏ(x,y) = f(x,y+1) - f(x,y-1)

... where f is the input image, and Ẋ and Ẏ are the slopes in the x-direction and y-direction.

Here, we store the outputs as single MIFF files, but convert them to a pair of PNG files for viewing on the web.

call %PICTBAT%slopeXY ^
  slp_src.png slp_sxy.miff

%IM32f%convert ^
  slp_sxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxy_0.png

%IM32f%convert ^
  slp_sxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxy_1.png
slp_sxy_0.png slp_sxy_1.png
call %PICTBAT%slopeXY ^
  slp_src_h.png slp_h_sxy.miff

%IM32f%convert ^
  slp_h_sxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_h_sxy_0.png

%IM32f%convert ^
  slp_h_sxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_h_sxy_1.png
slp_h_sxy_0.png slp_h_sxy_1.png
call %PICTBAT%slopeXY ^
  slp_src_vh.png slp_vh_sxy.miff

%IM32f%convert ^
  slp_vh_sxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_sxy_0.png

%IM32f%convert ^
  slp_vh_sxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_sxy_1.png
slp_vh_sxy_0.png slp_vh_sxy_1.png

The slope images range from -100% to +100%. Horizontal slopes are positive when they increase to the right; vertical slopes are positive when they increase downwards.

The script slopeXYneg.bat takes a vector as input, and creates a vector output, with each value becoming arithmetically negated (reflected around zero), so +10% becomes -10%, -20% becomes +20%, etc.

call %PICTBAT%slopeXYneg ^
  slp_vh_sxy.miff slp_vh_neg.miff

%IM32f%convert ^
  slp_vh_neg.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_neg_0.png

%IM32f%convert ^
  slp_vh_neg.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_neg_1.png
slp_vh_neg_0.png slp_vh_neg_1.png

The script slopeXYmean.bat takes a vector as input, and creates a vector output where each image is 1x1 pixels, the mean of the input. If the input is a slope ranging from -100% to +100%, the mean has the same range.

This measures how much the two sides differ, and how much the top and bottom differ. This is not usually of much interest.

call %PICTBAT%slopeXYmean ^
  slp_vh_sxy.miff slp_vh_mean.miff

%IM32f%convert ^
  slp_vh_mean.miff ^
  -format "%%[fx:r*100],%%[fx:g*100],%%[fx:b*100]\n" ^
  +write info: ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  +append ^
  -scale 200x100 ^
  slp_vh_mean.png 
-9.71273e-11,-9.71273e-11,-9.71273e-11
3.08259e-10,3.08259e-10,3.08259e-10
slp_vh_mean.png

The script slopeXYrms.bat takes a vector as input, and creates a vector output where each image is 1x1 pixels, the RMS (root mean square) of the input. If the input is a slope ranging from -100% to +100%, the RMS ranges from 0 to 100%.

This measures how much the slope varies from zero, If the mean is zero, the RMS is also the standard deviation.

call %PICTBAT%slopeXYrms ^
  slp_vh_sxy.miff slp_vh_rms.miff

%IM32f%convert ^
  slp_vh_rms.miff ^
  -format "%%[fx:r*100],%%[fx:g*100],%%[fx:b*100]\n" ^
  +write info: ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  +append ^
  -scale 200x100 ^
  slp_vh_rms.png 
9.77387,9.77387,9.77387
9.77384,9.77384,9.77384
slp_vh_rms.png

The script slopeXYabs.bat takes a vector as input, and creates a vector output, with each value becoming the absolute of the input, so +10% remains +10%, -20% becomes +20%, etc. If the input is a slope ranging from -100% to +100%, the absolute ranges from 0 to 100%. In each channel, we do:

if Ẋ <= 0 then Ẋ' = -Ẋ else Ẋ' = Ẋ 
if Ẏ <= 0 then Ẏ' = -Ẏ else Ẏ' = Ẏ 
call %PICTBAT%slopeXYabs ^
  slp_vh_sxy.miff slp_vh_abs.miff

%IM32f%convert ^
  slp_vh_abs.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_abs_0.png

%IM32f%convert ^
  slp_vh_abs.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_abs_1.png
slp_vh_abs_0.png slp_vh_abs_1.png

The script slopeXYmag.bat takes a vector as input, and creates a scalar output, the magnitude of the vector. If the input is a slope ranging from -100% to +100%, the magnitute ranges from 0 to 100%. (It would range up to 141.421...%, but we divide by two before taking the square root.) In each channel, we do:

mag = sqrt ((Ẋ2 + Ẏ2)/2)
call %PICTBAT%slopeXYmag ^
  slp_vh_sxy.miff slp_vh_mag.png
slp_vh_mag.png

The direction is an angle, the arctangent of the two slopes. For methods of calculating arctan from a pair of images, see Gradients Cookbook: arctan. The script slopeXYdirn.bat takes a vector as input, and creates a scalar output, the direction of the vector. This uses process module: arctan2. In each channel, we do:

dirn = atan2 (Ẋ, Ẏ) + addmod

When SWAP is given, Ẋ and Ẏ are interchanged. On this page we commonly use SWAP . 75 and the formula boils down to:

dirn = atan2 (Ẋ, -Ẏ)

... where dirn is like a compass bearing of the downward slope.

Where the x- and y-slopes are both zero, the magnitude is zero, and the direction is indeterminate.

Make a direction image.

call %PICTBAT%slopeXYdirn ^
  slp_vh_sxy.miff slp_vh_dirn.png ^
  SWAP . 75
slp_vh_dirn.png

In the direction image,
make transparent where magnitude is zero.

%IM%convert ^
  slp_vh_mag.png ^
  -fill White +opaque Black ^
  slp_vh_dirn.png ^
  +swap ^
  -compose CopyOpacity -composite ^
  slp_vh_dirn_tr.png
slp_vh_dirn_tr.png

In the direction image, we could use the alpha channel to represent magnitude, but that couldn't represent all three magnitudes.

The direction is recorded as a value from 0% to 100%. When we use "SWAP . 75", 0% and 100% represent a downward slope to the north, and values increase as the slope changes angle clockwise. The edge (or contour) is perpendicular to the slope, so we can regard the direction value as representing edge directions to the east (0%), clockwise to south (25%), west (50%), north (75%) and east again (100%). As we walk along the direction of the edge, pixels to the right are lighter than pixels on the left.

The script slopeXYdiv.bat takes a vector as input, and creates a scalar output, the divergence (aka div) of the vector. The input for divergence is usually a slope, and the divergence takes the slope of this slope, then adds the two components. If the original input ranges from 0 to 100%, so the slope ranges from -100% to +100%, output divergence values will range from -50% to +50%. Values of 0% represent zero divergence.

See Wikipedia: divergence.

call %PICTBAT%slopeXYdiv ^
  slp_vh_sxy.miff slp_vh_div.miff

%IM32f%convert ^
  slp_vh_div.miff ^
  -evaluate Add 50%% ^
  slp_vh_div.png
slp_vh_div.png

The script shpDiv.bat takes an image as input, and directly makes a divergence of the slope. This is also sharper than slopeXYdiv.bat, with just one pixel on each side of an cliff-edge. The range is as for slopeXYdiv.bat, but for ordinary photographs values are closer to zero.

call %PICTBAT%shpDiv ^
  slp_src_vh.png slp_vh_shpdiv.miff

%IM32f%convert ^
  slp_vh_shpdiv.miff ^
  -evaluate Add 50%% ^
  slp_vh_shpdiv.png
slp_vh_shpdiv.png

Normalise slopes so y (or x??) component is positive, so 0 <= direction <= 180 deg. Then averaging, by "-scale", is meaningful??

For some purposes, we want to treat slopes at x° and (x+180)° as the same.

In each channel, we do:

if Ẋ < 0 then 
  Ẋ' = -Ẋ 
  Ẏ' = -Ẏ
else 
  Ẋ' = Ẋ 
  Ẏ' = Ẏ
endif

if Ẋ < 0 then Ẏ' = -Ẏ else Ẏ' = Ẏ 
call %PICTBAT%slopeXYposang ^
  slp_vh_sxy.miff slp_vh_pa.miff

%IM32f%convert ^
  slp_vh_pa.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_pa_0.png

%IM32f%convert ^
  slp_vh_pa.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_vh_pa_1.png
slp_vh_pa_0.png slp_vh_pa_1.png
call %PICTBAT%slopeXYdirn ^
  slp_vh_pa.miff slp_vh_pa_dirn.miff ^
  SWAP . 75

%IM32f%convert ^
  slp_vh_pa_dirn.miff ^
  slp_vh_pa_dirn.png

rem goto :eof
slp_vh_pa_dirn.png

The same operations on toes.png:

call %PICTBAT%slopeXY ^
  toes.png slp_toes_sxy.miff

%IM32f%convert ^
  slp_toes_sxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_sxy_0.png

%IM32f%convert ^
  slp_toes_sxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_sxy_1.png
slp_toes_sxy_0.png slp_toes_sxy_1.png
call %PICTBAT%slopeXYneg ^
  slp_toes_sxy.miff slp_toes_neg.miff

%IM32f%convert ^
  slp_toes_neg.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_neg_0.png

%IM32f%convert ^
  slp_toes_neg.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_neg_1.png
slp_toes_neg_0.png slp_toes_neg_1.png
call %PICTBAT%slopeXYmean ^
  slp_toes_sxy.miff slp_toes_mean.miff

%IM32f%convert ^
  slp_toes_mean.miff ^
  -format "%%[fx:r*100],%%[fx:g*100],%%[fx:b*100]\n" ^
  +write info: ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  +append ^
  -scale 200x100 ^
  slp_toes_mean.png 
0.216085,0.137083,0.188127
0.025961,-0.0926699,0.0056107
slp_toes_mean.png
call %PICTBAT%slopeXYrms ^
  slp_toes_sxy.miff slp_toes_rms.miff

%IM32f%convert ^
  slp_toes_rms.miff ^
  -format "%%[fx:r*100],%%[fx:g*100],%%[fx:b*100]\n" ^
  +write info: ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  +append ^
  -scale 200x100 ^
  slp_toes_rms.png 
4.42872,3.46877,4.18751
5.23035,4.33608,4.97072
slp_toes_rms.png
call %PICTBAT%slopeXYabs ^
  slp_toes_sxy.miff slp_toes_abs.miff

%IM32f%convert ^
  slp_toes_abs.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_abs_0.png

%IM32f%convert ^
  slp_toes_abs.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_abs_1.png
slp_toes_abs_0.png slp_toes_abs_1.png
call %PICTBAT%slopeXYmag ^
  slp_toes_sxy.miff slp_toes_mag.png
slp_toes_mag.pngjpg
call %PICTBAT%slopeXYdirn ^
  slp_toes_sxy.miff slp_toes_dirn.png ^
  SWAP . 75
slp_toes_dirn.pngjpg
call %PICTBAT%slopeXYdiv ^
  slp_toes_sxy.miff slp_toes_div.miff

%IM32f%convert ^
  slp_toes_div.miff ^
  -evaluate Add 50%% ^
  slp_toes_div.png
slp_toes_div.pngjpg
call %PICTBAT%shpDiv ^
  toes.png slp_toes_shpdiv.miff

%IM32f%convert ^
  slp_toes_shpdiv.miff ^
  -evaluate Add 50%% ^
  slp_toes_shpdiv.png
slp_toes_shpdiv.pngjpg
call %PICTBAT%slopeXYposang ^
  slp_toes_sxy.miff slp_toes_pa.miff

%IM32f%convert ^
  slp_toes_pa.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_pa_0.png

%IM32f%convert ^
  slp_toes_pa.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_pa_1.png
slp_toes_pa_0.png slp_toes_pa_1.png
call %PICTBAT%slopeXYdirn ^
  slp_toes_pa.miff slp_toes_pa_dirn.miff ^
  SWAP . 75

%IM32f%convert ^
  slp_toes_pa_dirn.miff ^
  slp_toes_pa_dirn.png
slp_toes_pa_dirn.png

To aid visibility, the script autoLevMid.bat adds 50% then expands the range of values so the lightest is 100% or the darkest is 0%, or both. So it works like "-auto-level" but keeps 50% at 50%.

call %PICTBAT%autoLevMid ^
  slp_toes_div.miff slp_td_alm.png 1
slp_td_alm.pngjpg

Slopes by Gaussian window

The script slopeXYbl.bat uses a Nx1 window with Guassian weighting. Hence pixels that are further from the pixel in question can participate in the slope calculation, but they contribute less weight. Instead of window size, the script takes a blur sigma, and IM automatically calculates the window size in the usual way.

For example, with sigma=1:

call %PICTBAT%slopeXYbl ^
  slp_src.png slp_sxybl.miff 1

%IM32f%convert ^
  slp_sxybl.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxybl_0.png

%IM32f%convert ^
  slp_sxybl.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxybl_1.png
slp_sxybl_0.pngjpg slp_sxybl_1.pngjpg
call %PICTBAT%slopeXYbl ^
  toes.png slp_toesbl.miff 1

%IM32f%convert ^
  slp_toesbl.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toesbl_0.png

%IM32f%convert ^
  slp_toesbl.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toesbl_1.png
slp_toesbl_0.pngjpg slp_toesbl_1.pngjpg

For example, with a larger sigma:

call %PICTBAT%slopeXYbl ^
  slp_src.png slp_sxybl.miff 5

%IM32f%convert ^
  slp_sxybl.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxybl2_0.png

%IM32f%convert ^
  slp_sxybl.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_sxybl2_1.png
slp_sxybl2_0.pngjpg slp_sxybl2_1.pngjpg
call %PICTBAT%slopeXYbl ^
  toes.png slp_toesbl2.miff 5

%IM32f%convert ^
  slp_toesbl2.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toesbl2_0.png

%IM32f%convert ^
  slp_toesbl2.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toesbl2_1.png
slp_toesbl2_0.pngjpg slp_toesbl2_1.pngjpg

When calculating the slope, the script doesn't divide by the distance between the pixels on each side. So a larger sigma may give slope values that are over-large. Beware of mixing results when different sigmas are used. I may correct this (when I figure out how).

Round-trip

Given the definition of slope, we cannot reconstruct the original image from the slope with no other data.

If we know the colours around the edges of the image, we can calculate the divergence, then use Guided gradients.

Make a hollow version, with opaque edges.

call %PICTBAT%hollowEdge ^
  toes.png slp_toes_hlw.png
slp_toes_hlw.png

Relax-fill with guidance.

call %PICTBAT%relaxFillMS ^
  slp_toes_hlw.png . slp_toes_hlw_r.png ^
  0.0001 1000 . slp_toes_div.miff
slp_toes_hlw_r.pngjpg

How accurate is the round-trip?

%IMDEV%compare -metric RMSE toes.png slp_toes_hlw_r.png NULL: 
 3.27222e+07 (0.00761872)

It is quite accurate.

We can even do this knowing the colour of just one pixel, though convergence is very slow, and numeric stability is a problem.

Inverting the calculation from slope to magnitude and direction is easier. If the slopes are Ẋ and Ẏ then:

mag =  sqrt (Ẋ2 + Ẏ2) / sqrt(2)
dirn = atan (Ẋ, Ẏ)

So:

Ẋ = mag * sqrt(2) * cos(dirn)
Ẏ = mag * sqrt(2) * sin(dirn)

The ranges of values, as percentages of quantum, are:

-100% <= Ẋ, Ẏ <= +100%
0 <= mag <= 100%
0 <= dirn <= 100%

To calculate Ẋ and Ẏ from mag and dirn, we use -function Sinusoid freq,phase,amp,bias where freq=1, phase=90-75% for Ẋ and phase=0-75% for Ẏ, amp=1, and bias=0. We implement this in the script slopeMD2XY.bat.

As above, to see these images, we need to divide by 2 and add 50%.

call %PICTBAT%slopeMD2XY ^
  slp_toes_mag.png ^
  slp_toes_dirn.png ^
  slp_toes_calcxy.miff

%IM32f%convert ^
  slp_toes_calcxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_calcx.png

%IM32f%convert ^
  slp_toes_calcxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_toes_calcy.png
slp_toes_calcx.pngjpg slp_toes_calcy.pngjpg

How accurate is the round-trip?

%IMDEV%compare -metric RMSE slp_toes_sxy.miff[0] slp_toes_calcxy.miff[0] NULL: 
 18743.5 (4.36405e-06)
%IMDEV%compare -metric RMSE slp_toes_sxy.miff[1] slp_toes_calcxy.miff[1] NULL: 
 20565.1 (4.78818e-06)

The round-trip from slope to magnitude and direction, and back, is accurate.

Application: Average direction

From the slopes within a given area, we can obtain the average slope direction and magnitude.

We can make anglegrams. An anglegram is a Nx1 image (eg 360x1) where each pixel represents a bin of slope angles, and each pixel value represents a count of slopes in that bin, each count weighted by the slope magnitude. Counts are normalised. The anglegram is then graphed and polar-distorted so the angle repesents the slope direction, and the radius represents the slope magnitude.

Blah.

Application: Blurring and sharpening by slope

Slopes can be used for selective blur and sharpening. Blurring in a direction perpendicular to the slope (that is, parallel to the edge) de-emphasises non-edge detail. We then sharpen edges, by an unsharp mask made from a blur along the slope.

A source image, toes.png.

set BS_SRC=toes.png

set BLUR_SIG1=10
set BLUR_SIG2=5
toes.pngjpg

Make a grayscale version.

%IM%convert ^
  %BS_SRC% ^
  -colorspace Gray ^
  slp_bs_gr.png
slp_bs_gr.pngjpg

Get the slopes of the grayscale version.

call %PICTBAT%slopeXYbl ^
  slp_bs_gr.png slp_bs_sxy.miff %BLUR_SIG1%

%IM32f%convert ^
  slp_bs_sxy.miff[0] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_bs_sxy_0.png

%IM32f%convert ^
  slp_bs_sxy.miff[1] ^
  -evaluate Divide 2 ^
  -evaluate Add 50%% ^
  slp_bs_sxy_1.png
slp_bs_sxy_0.pngjpg slp_bs_sxy_1.pngjpg

Get the slope direction of the grayscale version.

call %PICTBAT%slopeXYdirn ^
  slp_bs_sxy.miff slp_bs_dirn.png ^
  SWAP . 75
slp_bs_dirn.pngjpg

Put the direction in a blue channel for a mask.

%IM%convert ^
  slp_bs_dirn.png ^
  ( +clone -fill White -colorize 100 ) ^
  ( +clone ) ^
  -swap 0,2 ^
  -combine ^
  slp_bs_msk.png
slp_bs_msk.pngjpg

Blur with the directional mask.

%IM%convert ^
  %BS_SRC% ^
  slp_bs_msk.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x0+0+360 ^
  -composite ^
  slp_bs_blr.png
slp_bs_blr.pngjpg

We can sharpen the previous result through conventional processing, or by blurring it in the perpendicular direction and using that as an unsharp mask.

Sharpen with an unsharp mask.

%IM%convert ^
  slp_bs_blr.png ^
  ( +clone ^
    slp_bs_msk.png ^
    -compose Blur ^
    -set option:compose:args 0x%BLUR_SIG2%+0+360 ^
    -composite ^
  ) ^
  +swap ^
  -compose blend ^
    -define compose:args=150 ^
  -composite ^
  slp_bs_blr_sh.png
slp_bs_blr_sh.pngjpg

A related effect is to use a two-dimensional blur, modulating the radius by the stretched negated slope magnitude. Hence we get more blur where the slope is lowest. The "-contrast-stretch" puts a proportion of the pixels at zero blur, and another larger proportion at maximum blur.

Calculate the magnitude.

call %PICTBAT%slopeXYmag ^
  slp_bs_sxy.miff slp_bs_mag.png ^
  AUTOLEV
slp_bs_mag.pngjpg

Put the negated magnitude in the red and green channels for a mask.

%IM%convert ^
  slp_bs_mag.png ^
  -negate ^
  -contrast-stretch 20%%x30%% ^
  -channel B -evaluate set 100%% +channel ^
  slp_bs_msk2.png
slp_bs_msk2.pngjpg

2D blur with the modulating mask.

%IM%convert ^
  %BS_SRC% ^
  slp_bs_msk2.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x%BLUR_SIG2%+0+360 ^
  -composite ^
  slp_bs_blr2.png
slp_bs_blr2.pngjpg

We can combine these effects, to get a one-dimensional blur that is angled to be perpendicular to the slope (ie parallel to the edge) and with the blur size inversely proportional to the slope magnitude (so we get most blurring where the image is least sharp).

We make a mask with the negated magnitude in the red and green channels, and the direction in the blue channel.

Make a mask.

%IM%convert ^
  slp_bs_mag.png -negate ^
  ( +clone ) ^
  slp_bs_dirn.png ^
  -combine ^
  slp_bs_msk2.png
slp_bs_msk2.pngjpg

Blur with the directional mask.

%IM%convert ^
  %BS_SRC% ^
  slp_bs_msk2.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x0+0+360 ^
  -composite ^
  slp_bs_blr2.png
slp_bs_blr2.pngjpg

Application: Visualise contours

We can represent countour of equal slope, wich are lines perpendicular to slope directions, by applying the blur mask to a grid of dots.

Create a grid of dots.

%IM%convert ^
  %BS_SRC% ^
  ( -size 1x1 xc:White ^
    -gravity Center ^
    -bordercolor Black -border 4 ^
    -write mpr:TILE +delete ^
  ) ^
  -tile mpr:TILE -draw "color 0,0 reset" ^
  slp_vs_src.png
slp_vs_src.pngjpg

First, we consider direction only, without magnitude.

Make a mask.

%IM%convert ^
  slp_bs_dirn.png ^
  ( +clone -evaluate set 100%% ) ^
  ( +clone ) ^
  -swap 0,2 ^
  -combine ^
  slp_vs_msk.png
slp_vs_msk.pngjpg

Blur it with the directional mask.

%IM%convert ^
  slp_vs_src.png ^
  slp_vs_msk.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x0+0+360 ^
  -composite ^
  -auto-level ^
  slp_vs_blr.png
slp_vs_blr.pngjpg

Then, we consider direction and magnitude together. We need a heavy "-sigmoidal-contrast" to show the blurred dots.

Make a mask.

%IM%convert ^
  slp_bs_mag.png ^
  ( +clone ) ^
  slp_bs_dirn.png ^
  -combine ^
  slp_vs_msk2.png
slp_vs_msk2.pngjpg

Blur it with the directional mask.

%IM%convert ^
  slp_vs_src.png ^
  slp_vs_msk2.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x0+0+360 ^
  -composite ^
  -auto-level ^
  -sigmoidal-contrast 20,10% ^
  slp_vs_blr2.png
slp_vs_blr2.pngjpg

By stretching the magnitude, we emphasise steep slopes and de-emphasise shallow slopes.

Make a mask.

%IM%convert ^
  slp_bs_mag.png ^
  -contrast-stretch 20%%x30%% ^
  ( +clone ) ^
  slp_bs_dirn.png ^
  -combine ^
  slp_vs_msk3.png
slp_vs_msk3.pngjpg

Blur it with the directional mask.

%IM%convert ^
  slp_vs_src.png ^
  slp_vs_msk3.png ^
  -compose Blur ^
  -set option:compose:args %BLUR_SIG2%x0+0+360 ^
  -composite ^
  -auto-level ^
  -sigmoidal-contrast 20,10% ^
  slp_vs_blr3.png
slp_vs_blr3.pngjpg

We can remove small marks, for example by using "-connected-components".

Threshold.

%IM%convert ^
  slp_vs_blr3.png ^
  -threshold 0 ^
  slp_vs_blr3_th.png
slp_vs_blr3_th.png

Find large components.

%IM%convert ^
  slp_vs_blr3_th.png ^
  -define connected-components:mean-color=true ^
  -define connected-components:area-threshold=20 ^
  -connected-components 4 ^
  slp_vs_blr3_lc.png
slp_vs_blr3_lc.png

Black-out the others.

%IM%convert ^
  slp_vs_blr3.png ^
  slp_vs_blr3_lc.png ^
  -compose Darken -composite ^
  slp_vs_blr3_lge.png
slp_vs_blr3_lge.png

Application: Seamless photomontage

Slope and divergence are important tools for seamless photomontage. Some examples from that page:

Inputs Output
perez_bear.jpg perez_water.jpg spm_s2.png
perez_bear.jpg spm_chq.png spm_chq_out.png spm_idb_out2.png
perez_scribble.jpg spm_wall_crp.png spm_wall_msk_rfs2.jpg
spm_flower.png spm_flr_bwc4_u.png
perez_boy.jpg spm_boy_out3.pngjpg
spm_pan_out1.png spm_cc_123b.png

Application: automatic cropping

A slope magnitude makes a useful saliency map for Crop to detail.

Input

sofa_cat_small.jpg

Output, automatically cropped

c2d_scs0.jpg

Scripts

slopeXY.bat

rem From colour image %1,
rem find slope of RGB channels.
rem Write output to _two_ images %2: first is x-slope, second is y-slope.
rem %3 is optional pre-process.
rem In output, 0% means no slope, > 0% value increases rightwards or downwards.

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxy

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

set PREPROC=%~3
if "%PREPROC%"=="." set PREPROC=

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

%IM32f%convert ^
  %INFILE% ^
  %PREPROC% ^
  -alpha off ^
  -virtual-pixel Edge ^
  ( -clone 0 -morphology Convolve 3x1:1,0,-1 ) ^
  ( -clone 0 -morphology Convolve 1x3:1,0,-1 ) ^
  -delete 0 ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set sxyOUTFILE=%OUTFILE%

slopeXYbl.bat

rem From colour image %1,
rem find slope of RGB channels, by comet blur method.
rem Write output %2 to _two_ images: first is x-slope, second is y-slope.
rem %3 is blur sigma.
rem In output, 0% means no slope, > 0% value increases rightwards or downwards.
@rem
@rem See also slopeXYblMag.bat.

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxy

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

set BLR_SIG=%3
if "%BLR_SIG%"=="." set BLR_SIG=
if "%BLR_SIG%"=="" set BLR_SIG=1

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

%IM32f%convert ^
  %INFILE% ^
  -alpha off ^
  -virtual-pixel Edge ^
  -define compose:clamp=off ^
  -define convolve:scale="^!" ^
  ( -clone 0 ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG% ) ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,180 ) ^
    -delete 0 ^
    -compose Mathematics -define compose:args=0,1,-1,0 -composite ^
  ) ^
  ( -clone 0 ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,90 ) ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,270 ) ^
    -delete 0 ^
    -compose Mathematics -define compose:args=0,1,-1,0 -composite ^
  ) ^
  -delete 0 ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set sxyOUTFILE=%OUTFILE%

slopeXYminus.bat

rem Given %1 and %2 are slopeXY structures (thus contain 2 images each)
rem returns %3 a slopeXY structure, each channel is half (%2 minus %1.)
@rem
@rem Updated:
@rem   25-June-2017 Removed 50% bias.


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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxyd

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

set IN_A=%INFILE%
set IN_B=%2

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

%IM32f%convert ^
  -define compose:clamp=off ^
  ( %IN_A%[0] ^
    %IN_B%[0] ^
    -compose Mathematics -define compose:args=0,0.5,-0.5,0 -composite ^
  ) ^
  ( %IN_A%[1] ^
    %IN_B%[1] ^
    -compose Mathematics -define compose:args=0,0.5,-0.5,0 -composite ^
  ) ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%


call echoRestore

endlocal & set sxydOUTFILE=%OUTFILE%

slopeXYweighted.bat

rem Given %1 and %2 are slopeXY structures (thus contain 2 images each)
rem write weighted output %3 a slopeXY structure.
rem %4 is w1, floating-point weight (typically 0 to 1).
rem %5 is w2, floating-point weight (typically 0 to 1).
rem
rem Output each channel is w1 * %1 + w2 * %2

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxyw

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

set WT1=%4
if "%WT1%"=="." set WT1=
if "%WT1%"=="" set WT1=0.5

set WT2=%5
if "%WT2%"=="." set WT2=

if "%WT2%"=="" for /F "usebackq" %%L in (`%IM%identify ^
  -format "WT2=%%[fx:1-%WT1%]" ^
  xc:`) do set %%L

set IN_A=%INFILE%
set IN_B=%2


%IM%convert ^
  ( %IN_A%[0] ^
    %IN_B%[0] ^
    -compose Mathematics -define compose:args=0,%WT2%,%WT1%,-0.5 -composite ^
  ) ^
  ( %IN_A%[1] ^
    %IN_B%[1] ^
    -compose Mathematics -define compose:args=0,%WT2%,%WT1%,-0.5 -composite ^
  ) ^
  +depth ^
  %OUTFILE%


call echoRestore

endlocal & set sxywOUTFILE=%OUTFILE%

slopeXYmag.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem returns %2 conventional RGB image
rem with channels set to magnitude.
rem %3 if AUTOLEV, auto-levels the output.
@rem
@rem Updated:
@rem   25-June-2017 Removed 50% bias

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxym

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

set AUTOLEV=%3
if /I "%AUTOLEV%"=="AUTOLEV" (
  set sAUTOLEV=-auto-level
) else (
  set sAUTOLEV=
)

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

goto skip

%IM32f%convert ^
  %INFILE% ^
  -channel RGB ^
  -evaluate Pow 2 ^
  -separate ^
  -define compose:clamp=off ^
  ( -clone 0,3 -compose plus -composite ) ^
  ( -clone 1,4 -compose plus -composite ) ^
  ( -clone 2,5 -compose plus -composite ) ^
  -delete 0-5 ^
  -evaluate Divide 2 ^
  -evaluate Pow 0.5 ^
  -combine ^
  %sAUTOLEV% ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

:skip

%IM32f%convert ^
  %INFILE% ^
  -channel RGB ^
  -evaluate Pow 2 ^
  -define compose:clamp=off ^
  -compose plus -composite ^
  -evaluate Divide 2 ^
  -evaluate Pow 0.5 ^
  %sAUTOLEV% ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

call echoRestore

endlocal & set sxymOUTFILE=%OUTFILE%

slopeXYdirn.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem returns %2 conventional RGB image
rem with each channel set to direction.
rem %3 if SWAP, swap the two inputs.
rem %4 if SUB, subtract 50% before taking the arctan.
rem %5 percentage for addmodulus.

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxyd

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

set SWAP=%3
if "%SWAP%"=="." set SWAP=
if "%SWAP%"=="" set SWAP=0

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

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

if /I "%SWAP%"=="SWAP" (
  set sSWAP=+swap
) else (
  set sSWAP=
)

if /I "%SUBHALF%"=="SUB" (
  set sSUBHALF=-evaluate Subtract 50%%
) else (
  set sSUBHALF=
)

echo %0: sSWAP=%sSWAP% sSUBHALF=%sSUBHALF% nADDMOD=%nADDMOD%

%IMDEV%convert ^
  %INFILE% ^
  %sSWAP% ^
  %sSUBHALF% ^
  -process 'arctan2 b 0' ^
  -evaluate AddModulus %nADDMOD%%% ^
  +depth ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set sxydOUTFILE=%OUTFILE%

slopeXYdiv.bat

rem Given %1 is slopeXY structure (thus contains 2 images)
rem returns %2 scalar image, the divergence of %1.
@rem
@rem The divergence of %1 is the x-slope of the x component,
@rem plus the y-slope of the y component.
@rem See Perez and https://en.wikipedia.org/wiki/Divergence.
@rem (Yeah, I think this is weird.)
@rem We find slope in the same way as slopeXY.bat.
@rem
@rem Updated:
@rem   25-June-2017 Removed bias 50%.


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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxyd

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

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

%IM32f%convert ^
  -define compose:clamp=off ^
  ( %INFILE%[0] ^
    -alpha off ^
    -morphology Convolve 3x1:0.5,0,-0.5 ^
  ) ^
  ( %INFILE%[1] ^
    -alpha off ^
    -morphology Convolve 1x3:0.5,0,-0.5 ^
  ) ^
  -compose Mathematics -define compose:args=0,0.5,0.5,0 -composite ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%


call echoRestore

endlocal & set sxydOUTFILE=%OUTFILE%

shpDiv.bat

rem From colour image %1,
rem returns %2 scalar image, the divergence of %1.

@rem Updated:
@rem   26-June-2017  Remove bias 50%


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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 shd

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

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

%IM32f%convert ^
  %INFILE% ^
  -morphology Convolve 3x3:0,0.25,0,0.25,-1,0.25,0,0.25,0 ^
  -depth 32 -define quantum:format=floating-point ^
  %OUTFILE%

call echoRestore

endlocal & set shdOUTFILE=%OUTFILE%

slopeXYneg.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem outputs %2 slopeXY with values the arithmetic negative of the input values.
rem eg +10% becomes -10%; -20% becomes +20%.

call %PICTBAT%arithNeg %1 %2

arithNeg.bat

An alternative is to clone, make that black, and subtract. However, when the input contains multiple images, that wouldn't work.

rem Given HDRI image %1,
rem makes %2 the arithmetic negative
rem eg +10% becomes -10%; -20% becomes +20%.

rem Note: this works even if %1 has multiple images.

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

rem "Add 1" because 100% is QuantumRange+1.

%IM32f%convert ^
  %1 ^
  -channel RGB ^
  -negate -evaluate Subtract 100%% -evaluate Add 1 ^
  +channel ^
  %2

slopeXYmean.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem outputs %2 slopeXY structure, each image 1x1, the mean values.
rem Calculated by "-scale", so transparent input pixels don't count.

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

%IM32f%convert ^
  %1 ^
  -scale "1x1%!" ^
  %2

slopeXYrms.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem outputs %2 slopeXY structure, each image 1x1, the RMS values.
rem Calculated by "-scale", so transparent input pixels don't count.

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

%IM32f%convert ^
  %1 ^
  -channel RGB -evaluate Pow 2 +channel ^
  -scale "1x1%!" ^
  -channel RGB -evaluate Pow 0.5 +channel ^
  %2

slopeXYabs.bat

rem Given %1 is slopeXY structure (thus contains 2 images),
rem outputs %2 slopeXY with values the absolute of the input values.

@call echoOffSave

rem FIXME: Now we can do this in one call, with all images.

(
  call %PICTBAT%absRGB %1[0] -
  call %PICTBAT%absRGB %1[1] -
) >%2

call echoRestore

absRGB.bat

rem Given HDRI image %1,
rem makes %2 with absolute image values in RGB channels.

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

:: FIXME: What happens at zero?

goto skip

%IM32f%convert ^
  %1 ^
  -define compose:clamp=off ^
  ( -clone 0 ^
    -channel RGB ^
    -negate -evaluate Subtract 100%% -evaluate Add 1 ^
    +channel ^
  ) ^
  +swap ^
  ( -clone 1 ^
    -channel RGB ^
    -threshold 0 ^
    +channel ^
  ) ^
  -separate ^
  ( -clone 0,3,6 -compose Over -composite ) ^
  ( -clone 1,4,7 -compose Over -composite ) ^
  ( -clone 2,5,8 -compose Over -composite ) ^
  -delete 0-8 ^
  -combine ^
  -define "quantum:format=floating-point" ^
  %2

rem Can't we do this with -evaluate Abs 0??

:skip

%IM32f%convert ^
  %1 ^
  -define compose:clamp=off ^
  -evaluate Abs 0 ^
  -define "quantum:format=floating-point" ^
  %2

autoLevMid.bat

rem Makes auotolevel version, such that 50% remains at 50%.
rem Output %2 will have 0% or 100%, but generally not both.
rem %3 if not 0, will first add 50% tp the image.
@rem
@rem Updated:
@rem   25-June-2017 Added "ADDHALF" facility.
@rem

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 alm

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

set ADDHALF=%3
if "%ADDHALF%"=="." set ADDHALF=
if "%ADDHALF%"=="" set ADDHALF=0

if "%ADDHALF%"=="0" (
  set sADDHALF=
) else (
  set sADDHALF=-evaluate Add 50%%
)

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

for /F "usebackq" %%L in (`%IM32f%convert ^
  %INFILE% ^
  %sADDHALF% ^
  -format "dm=%%[fx:abs(minima-0.5)>abs(maxima)?minima:maxima]" ^
  info:`) do set %%L

echo dm=%dm%

for /F "usebackq" %%L in (`%IM%identify ^
  -format "dm=%%[fx:100*(%dm%>0.5?1-%dm%:%dm%)]" ^
  xc:`) do set %%L

echo dm=%dm%

for /F "usebackq" %%L in (`%IM%identify ^
  -format "dmo=%%[fx:100-%dm%]" ^
  xc:`) do set %%L

echo dmo=%dmo%

%IM32f%convert ^
  %INFILE% ^
  %sADDHALF% ^
  -level %dm%,%dmo%%% ^
  %OUTFILE%


call echoRestore

endlocal & set almOUTFILE=%OUTFILE%&

slopeMD2XY.bat

rem Give %1 is magnitude
rem %2 is direction,
rem writes %3 a slopeXY structure.
@rem
@rem Assumes direction has been created with "SWAP . 75".

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

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 smdxy

set MAG=%1
set DIRN=%2
set OUTFILE=%3

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

%IM32f%convert ^
  %MAG% -evaluate Multiply 1.414213562373095 +write mpr:MAG +delete ^
  %DIRN% +write mpr:DIRN +delete ^
  -define compose:clamp=off ^
  ( mpr:DIRN -function Sinusoid 1,180,1,0 ^
    mpr:MAG ^
    -compose Multiply -composite ^
  ) ^
  ( mpr:DIRN -function Sinusoid 1,90,1,0 ^
    mpr:MAG ^
    -compose Multiply -composite ^
  ) ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

call echoRestore

endlocal & set smdxyOUTFILE=%OUTFILE%

hollowEdge.bat

rem From image %1,
rem make %2 with all non-edge pixels transparent.

%IM%convert ^
  %1 ^
  ( +clone ^
    -fill Black -colorize 100 ^
    -shave 1x1 ^
    -bordercolor White -border 1 ^
  ) ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  -background White -alpha Background ^
  %2

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

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

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


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.0 22-June-2017.

Page created 30-Jul-2017 16:43:54.

Copyright © 2017 Alan Gibson.