snibgo's ImageMagick pages

Integral images

... for windowed mean and SD.

The Gradient contours page includes a scheme for 2D differentiation. The input image needs to be one channel (eg grayscale). The resulting differential needs two channels, for the slope in the x and y directions, so these are stored in output red and green channels. The differential at a pixel is the value at that pixel minus the pixel to the left or above. The inverse, for integrations, is also given, but this needs the top-left value to be known (the "DC offset").

The Slopes page shows a similar scheme for colour images, recording the x and y differences in a pair of images.

This page gives a different scheme for integration and differentiation. For both directions, each input channel needs only one output channel for storage, and each input pixel needs just one output pixel. Thus a colour input gives a colour output at the same size. No other data, such as a DC offset, need be recorded. However, calculations and data storage need to be HDRI.

The method is called integral images or summed area tables.

The modules should be compiled in a high-precision HDRI version of IM.

Scripts on this page assume that the version of ImageMagick in %IMDEV% has been built with various process modules (see Process modules), and is HDRI.

References

This page was inspired by:

See also:

I haven't seen any references that even mention using this method on images with transparency.

Sample input

set SRC=toes.png
toes.pngjpg

This image is mostly transparent.

set SRC_TR=toes_holed.png
toes_holed.png

toes_holes.png has binary transparency. For demonstration purposes, we make a version with tapered transparency:

%IM%convert ^
  %SRC_TR% ^
  -alpha extract ^
  -blur 0x10 ^
  %SRC% ^
  +swap ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  -background Black -alpha Background ^
  toes_holetap.png

set SRC_TT=toes_holetap.png
toes_holetap.png

Process module: integim

integim.c converts ordinary images into integral images (summed area tables).

Option Description
Short
form
Long form
pm string premult string Whether to pre-multiply RGB values by alpha.
One of:
    yes (do pre-multiply),
    no (don't pre-multiply), or
    auto (from current alpha setting).
Default: yes.
f string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write text information.

The result from integim is not particularly useful or interesting. Instead, the result is generally used by one or more calls to deintegim.

In this example we show the integral image, which visually is almost entirely white, then the result from "-auto-level".

%IMDEV%convert ^
  toes.png ^
  -process 'integim' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  +write ii_ii1.miff ^
  -auto-level ^
  ii_ii1_al.png
ii_ii1.miffjpg ii_ii1_al.pngjpg

Process module: deintegim

deintegim.c converts from integral images (summed area tables) into ordinary images. It gives us, very quickly, the windowed mean of the image that was the input to integim.

Option Description
Short
form
Long form
w string window string The window size, two numbers separated by "x".
Each number may be suffixed with "%" or "c" or "p".
Default: 1x1.
pd string postdiv string Whether to post-divide RGB values by alpha.
One of:
    yes (do post-divide),
    no (don't post-divide), or
    auto (from current alpha setting).
Default: yes.
dd dontdivide Don't divide colour channels by anything.
f string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write text information.

The windows width and height are integers from one upwards. When the window size is 1x1, this is the inverse process to integim.

If the window option is not given, the window size will be 1x1. If the option is given, then both width and height must be given. Window dimensions are integers, from one upwards. If either dimension is in units of pixels (no suffix), it must be at least one.

A suffix may be used, to calculate the window dimension as a percentage or proportion of the image dimension. A suffix applies only to that dimension. For an image 3000x2000 pixels, the following window dimensions are equivalent:

300x100
10%x100
10cx100
0.1px100
10%x5%

If either dimension is calculated (because it has a suffix), the module will round to the nearest integer and ensure it is at least one.

When alpha is available, by default integim pre-multiplies colour channel by alpha, and deintegim post-divides by alpha. I think this is always desirable, but we can turn it off if we want (improving performance very slightly) or make this dependent on the image's -alpha setting.

If the input to deintegim is an ordinary image, not an integral image, we don't want to divide the colour channels by anything. That's what dontdivide is for.

We de-integrate the result from integim:

Default window, 1x1.

%IMDEV%convert ^
  ii_ii1.miff ^
  -process 'deintegim' ^
  ii_ii1_di.png
ii_ii1_di.pngjpg

This should be an accurate reconstruction of %SRC%. Is it?

%IM%compare -metric RMSE %SRC% ii_ii1_di.png NULL: 
32.8536 (0.000501314)

It is accurate, so the round trip integ-deinteg works.

The round trip in the other direction should also work:

Default window, 1x1.

%IMDEV%convert ^
  %SRC% ^
  -process 'deintegim' ^
  -process 'integim' ^
  ii_de_in.png
ii_de_in.pngjpg
%IM%compare -metric RMSE %SRC% ii_de_in.png NULL: 
0 (0)

It is accurate, so the round trip deinteg-integ works.

More usefully, deintegim with a larger window size gives the mean for that area, and this is a blur.

%IMDEV%convert ^
  ii_ii1.miff ^
  -process 'deintegim window 10x10' ^
  ii_ii1_di2.png
ii_ii1_di2.pngjpg
%IMDEV%convert ^
  ii_ii1.miff ^
  -process 'deintegim window 100x100' ^
  ii_ii1_di3.png
ii_ii1_di3.pngjpg
%IMDEV%convert ^
  ii_ii1.miff ^
  -process 'deintegim window 100x1' ^
  ii_ii1_di4.png
ii_ii1_di4.pngjpg
%IMDEV%convert ^
  ii_ii1.miff ^
  -process 'deintegim window 1x100' ^
  ii_ii1_di5.png
ii_ii1_di5.pngjpg

When the window size has a high aspect ratio, eg 100x1 or 1x100, the result is gradually less blurred towards the sides or top and bottom. This is due to the window size decreasing.

We can, and often do, use integim and deintegim in the same command, without saving the intermediate integral image. Saving and re-reading would often be a significant proportion of the overall time taken.

%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 25x25' ^
  ii_t_di.png
ii_t_di.pngjpg
%IMDEV%convert ^
  %SRC_TT% ^
  -process 'integim' ^
  -process 'deintegim window 25x25' ^
  ii_th_di.png
ii_th_di.pngjpg

With a bit of work, we get an angled blur. For example, 20 degrees anti-clockwise from vertical:

%IMDEV%convert ^
  %SRC% ^
  -set option:WW %%w ^
  -set option:HH %%h ^
  -virtual-pixel edge ^
  +distort SRT 1,20 ^
  -process 'integim' ^
  -process 'deintegim window 1x100' ^
  -set option:distort:viewport "%%[WW]x%%[HH]+0+0" ^
  +distort SRT 1,-20 ^
  ii_rotbl.png
ii_rotbl.pngjpg

Extreme cases

We consider a couple of extreme (or "edge") cases. The code doesn't explicity test for these; the results are simply as expected from the algorithm.

When the input image is 1x1, the integral is a copy of the image, and the deintegral is the same. For example:

%IMDEV%convert ^
  -size 1x1 xc:rgb(10%%,20%%,30%%) ^
  -process 'integim' ^
  +write txt: ^
  -process 'deintegim' ^
  txt: 
# ImageMagick pixel enumeration: 1,1,4294967295,srgb
0,0: (4.29497e+08,8.58993e+08,1.28849e+09)  #1999999A333333334CCCCCCD  srgb(10%,20%,30%)
# ImageMagick pixel enumeration: 1,1,4294967295,srgb
0,0: (4.29497e+08,8.58993e+08,1.28849e+09)  #1999999A333333334CCCCCCD  srgb(10%,20%,30%)

When a window is large enough that in any position it will always cover the entire image (which happens when the window is at least twice the image dimensions), the output of deintegim will be a flat colour, which is the same value as "-scale 1x1!". For example:

%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 2px2p' ^
  +write ii_extr2.png ^
  -unique-colors ^
  txt: 
ii_extr2.png

The colour is:

# ImageMagick pixel enumeration: 1,1,4294967295,srgb
0,0: (2.29133e+09,2.08693e+09,1.91167e+09)  #88927C6371F1  srgb(53.3492%,48.59%,44.5094%)

Compare this to the colour from -scale:

%IMDEV%convert ^
  %SRC% ^
  -scale "1x1^!" ^
  txt: 
# ImageMagick pixel enumeration: 1,1,4294967295,srgb
0,0: (2.29133e+09,2.08693e+09,1.91167e+09)  #88927C6371F1  srgb(53.3492%,48.59%,44.5094%)

As expected, they are the same.

Resampling the integral image

If we subsample the integral image (resize it down), we should usually also divide the values by the ratio of the areas.

%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -resize 50%% ^
  -evaluate Divide 4 ^
  -process 'deintegim' ^
  ii_subsamp1.png
ii_subsamp1.pngjpg
%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -resize 25%% ^
  -evaluate Divide 16 ^
  -process 'deintegim' ^
  ii_subsamp2.png
ii_subsamp2.pngjpg

Similarly for supersampling, we multiply:

%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -resize 200%% ^
  -evaluate Multiply 4 ^
  -process 'deintegim' ^
  ii_supsamp1.png
ii_supsamp1.pngjpg

The edge colours are wrong, because IM doesn't have a virtual-pixel method "continue changing values in the same direction".

Comparison with "-statistic mean"

For opaque images, the process modules and "-statistic mean" give similar results.

%IM%convert ^
  %SRC% ^
  -statistic mean 15x10 ^
  ii_smc1.png
ii_smc1.pngjpg
%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 15x10' ^
  ii_smcp1.png
ii_smcp1.pngjpg

"-statistic mean" doesn't retain alpha, even when we use -alpha on ...

%IM%convert ^
  toes_holed.png ^
  -alpha on ^
  -statistic mean 15x15 ^
  ii_sm1.png
ii_sm1.pngjpg

... so we can save the alpha and restore it afterwards, or use "-channel RGBA". But, because "-statistic mean" doesn't ignore transparent pixels, the transparent black leaks into the rest of the image. This is most obvious when we flatten against a gray background.

%IM%convert ^
  toes_holed.png ^
  ( +clone ^
    -alpha extract ^
    +write mpr:ALPH ^
    +delete ^
  ) ^
  -statistic mean 15x15 ^
  mpr:ALPH ^
  -alpha off -compose CopyOpacity -composite ^
  ii_sm2.png

The boundary is darkened.

ii_sm2.pngjpg

Apply "-statistic mean" to the extracted alpha:

%IM%convert ^
  toes_holed.png ^
  ( +clone ^
    -alpha extract ^
    -statistic mean 15x15 ^
    +write mpr:ALPH ^
    +delete ^
  ) ^
  -statistic mean 15x15 ^
  mpr:ALPH ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  -compose Over ^
  +write ii_sm3.png ^
  -background gray(50%%) ^
  -layers Flatten ^
  ii_sm3g.png

The semi-transparent area is darkened.

ii_sm3.pngjpg ii_sm3g.pngjpg

Apply "-statistic mean" to the four channels:

%IM%convert ^
  toes_holed.png ^
  -channel RGBA ^
  -statistic mean 15x15 ^
  +channel ^
  +write ii_sm3a.png ^
  -background gray(50%%) ^
  -layers Flatten ^
  ii_sm3ag.png

The semi-transparent area is darkened.

ii_sm3a.pngjpg ii_sm3ag.pngjpg

Using the process modules:

%IMDEV%convert ^
  toes_holed.png ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  ii_smpm3.png ^
  -background gray(50%%) ^
  -layers Flatten ^
  ii_smpm3g.png

There is no darkening.

ii_smpm3.pngjpg ii_smpm3g.pngjpg

Examples of thin kernels:

%IM%convert ^
  %SRC% ^
  -statistic mean 100x1 ^
  ii_smt1.png
ii_smt1.pngjpg
%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 100x1' ^
  ii_smtp1.png
ii_smtp1.pngjpg
%IM%convert ^
  %SRC% ^
  -statistic mean 1x100 ^
  ii_smt2.png
ii_smt2.pngjpg
%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 1x100' ^
  ii_smtp2.png
ii_smtp2.pngjpg

There is a difference at the edges. For example, "-statistics mean" has a sharpness at the left and right edges of the horizontal blur, whereas the modules have a more consistent blur from the center to the edges.

"-statistics mean" uses virtual pixels, which by default are "edge", so when we are working on the bottom row, with a 1x100 window, "-statistics mean" will use the bottom row 50 times out of the 100 window pixels, so this gives undue weight to the bottom row of pixels.

By contrast, deintegim doesn't use virtual pixels. When a window would overlap the edge of an image, the window size is reduced so it doesn't overlap. For example, with a 1x100 window centred at the image edge, it will cap the size of the window to 50 pixels high so the bottom row will contribute 1 out of the 50 window pixels.

The "-statistics mean" edge problem is reduced by using mirrored virtual pixels:

%IM%convert ^
  %SRC% ^
  -virtual-pixel Mirror ^
  -statistic mean 100x1 ^
  ii_smtm1.png
ii_smtm1.pngjpg
%IM%convert ^
  %SRC% ^
  -virtual-pixel Mirror ^
  -statistic mean 1x100 ^
  ii_smtm2.png
ii_smtm2.pngjpg

Comparing performance:

 %IMDEV%convert ^
  %SRC% ^
  -statistic mean 100x100 ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_sm_perf.miff
0 00:00:59
 %IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 100x100' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_sm_perf2.miff
0 00:00:01

The improvement is clear.

Performance

The time required for both integim and deintegim is proportional to the image size in pixels, and is independent of the window size. Both processes require just one pass through their inputs, so they are fast.

set LGE_IMG=AGA_1434_gms.tiff

%IM%identify %LGE_IMG% 
AGA_1434_gms.tiff TIFF 4924x7378 4924x7378+0+0 16-bit sRGB 174.1MB 0.000u 0:00.000
 %IMDEV%convert ^
  %LGE_IMG% ^
  -process 'integim' ^
  -process 'deintegim window 100x100' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_perf.miff
0 00:00:07

This time includes disk I/O.

Application: windowed statistics

We readily find windowed statistics.

The script iiMeanSd.bat creates the mean, or the standard deviation, or both.

call %PICTBAT%iiMeanSd ^
  %SRC% ^
  ii_scr_mn.miff ^
  ii_scr_sd.miff ^
  15x15
ii_scr_mn.miffjpg ii_scr_sd.miffjpg

Mean

Each result pixel is the mean of input pixels within a window. The process modules give us this directly.

%IMDEV%convert ^
  %SRC% ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_mn.miff
ii_ap_mn.miffjpg
%IMDEV%convert ^
  %SRC_TT% ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_tt_mn.miff
ii_ap_tt_mn.miffjpg

Mean square

We square the input colour channels first, and find the mean of that.

%IMDEV%convert ^
  %SRC% ^
  -channel RGB ^
  -evaluate Pow 2 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_ms.miff
ii_ap_ms.miffjpg
%IMDEV%convert ^
  %SRC_TT% ^
  -channel RGB ^
  -evaluate Pow 2 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_tt_ms.miff
ii_ap_tt_ms.miffjpg

Root mean square

We square the input, find the mean of that, and take the square root.

%IMDEV%convert ^
  %SRC% ^
  -channel RGB ^
    -evaluate Pow 2 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -channel RGB ^
    -evaluate Pow 0.5 ^
  +channel ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_rms.miff
ii_ap_rms.miffjpg
%IMDEV%convert ^
  %SRC_TT% ^
  -channel RGB ^
    -evaluate Pow 2 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  -channel RGB ^
    -evaluate Pow 0.5 ^
  +channel ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_tt_rms.miff
ii_ap_tt_rms.miffjpg

Standard deviation

SD = sqrt ( mean(image2) - imagemean2 )

mean(image2) is the mean square image as calculated above, and imagemean is the mean of the image as also calculated above.

%IMDEV%convert ^
  ii_ap_mn.miff ^
  -channel RGB ^
    -evaluate Pow 2 ^
  +channel ^
  ii_ap_ms.miff ^
  -define compose:clamp=off ^
  -compose MinusDst -composite ^
  -channel RGB ^
    -evaluate Pow 0.5 ^
  +channel ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_sd.miff
ii_ap_sd.miffjpg
%IMDEV%convert ^
  ii_ap_tt_mn.miff ^
  -channel RGB ^
    -evaluate Pow 2 ^
  +channel ^
  ii_ap_tt_ms.miff ^
  -define compose:clamp=off ^
  -compose MinusDst -composite ^
  -channel RGB ^
    -evaluate Pow 0.5 ^
  +channel ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_tt_sd.miff
ii_ap_tt_sd.miffjpg

Skew

We use the derivation:

skew = mean(diffmean3) / SD3

... where diffmean is the image minus its mean.

%IMDEV%convert ^
  %SRC% ^
  ii_ap_mn.miff ^
  -define compose:clamp=off ^
  -compose MinusSrc -composite ^
  -channel RGB ^
    -evaluate Pow 3 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  ( ii_ap_sd.miff ^
    -channel RGB ^
      -evaluate Pow 3 ^
    +channel ^
  ) ^
  -compose DivideSrc -composite ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_sk.miff
ii_ap_sk.miffjpg

Kurtosis

kurtosis = mean(diffmean4) / SD4
%IMDEV%convert ^
  %SRC% ^
  ii_ap_mn.miff ^
  -define compose:clamp=off ^
  -compose MinusSrc -composite ^
  -channel RGB ^
    -evaluate Pow 4 ^
  +channel ^
  -process 'integim' ^
  -process 'deintegim window 15x15' ^
  ( ii_ap_sd.miff ^
    -channel RGB ^
      -evaluate Pow 4 ^
    +channel ^
  ) ^
  -compose DivideSrc -composite ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  ii_ap_kt.miff
ii_ap_kt.miffjpg

Application: thresholding

We use two examples from the Bradley and Roth paper. These images are not my copyright.

bradley_0.png

bradley_0.png

bradley_1.png

bradley_1.png

The method makes a windowed mean size x s. If an input pixel is t percent less than this average the output is black, otherwise the output is white. For example, when t is 15%, we multiply the average by 0.85, and compare this to the input pixel. Bradley and Roth do not say what values they used for s and t to get their results.

set BRAD_S=12
set BRAD_T=0.85

%IMDEV%convert ^
  bradley_0.png ^
  -colorspace Gray ^
  ( +clone ^
    -process 'integim' ^
    -process 'deintegim w %BRAD_S%x%BRAD_S%' ^
    -evaluate Multiply %BRAD_T% ^
  ) ^
  -define compose:clamp=off ^
  -compose MinusSrc -composite ^
  -define quantum:format=floating-point ^
  -threshold 0 ^
  ii_brad_0.png
ii_brad_0.png
set BRAD_S=65
set BRAD_T=0.85

%IMDEV%convert ^
  bradley_1.png ^
  -colorspace Gray ^
  ( +clone ^
    -process 'integim' ^
    -process 'deintegim w %BRAD_S%x%BRAD_S%' ^
    -evaluate Multiply %BRAD_T% ^
  ) ^
  -define compose:clamp=off ^
  -compose MinusSrc -composite ^
  -define quantum:format=floating-point ^
  -threshold 0 ^
  ii_brad_1.png
ii_brad_1.png

ASIDE: This thresholding method was designed to be very fast, for real-time video processing, rather than preparation for OCR.

Application: detecting edges

The deintegim module can be used alone, with an ordinary image as input. Each output pixel depends on only four input pixels, so this is fast but sensitive to noise.

In the examples, we adjust the output to increase visibility.

%IMDEV%convert ^
  %SRC% ^
  -process 'deintegim' ^
  -evaluate Multiply 4 ^
  -evaluate Add 50%% ^
  ii_edge1.png
ii_edge1.pngjpg
%IMDEV%convert ^
  %SRC% ^
  -process 'deintegim window 5x5 dontdivide' ^
  -evaluate Add 50%% ^
  ii_edge2.png
ii_edge2.pngjpg

Note that if we dontdivide and the input to deintegim ranges from 0 to 100%, its output range is -200% to +200%.

Application: downsampling

deintegim could be modified to make the output smaller than the input. At each output pixel, calculate the required (x1,y1) and (x2,y2) window on the input, to calculate the output pixel as a present.

This would give a very fast downsampling method, though it may have little or no benefit over IM's -scale or -sample.

Application: pyramids

Multi-resolution pyramids could be constructed very quickly.

Application: approximating other convolutions

integim followed by deintegim is a box filter. Other filters can be approximated, to any desired accuracy, by a series of box filters of appropriate sizes and weights.

For example, the Stacked Integral Images (SII) method to approximate Gaussian filters can be implemented by a single run of integim followed by multiple runs of deintegim (or a single run of integim followed by a single run of a modified version of deintegim that has multiple look-ups). "Multiple" is a small number, eg 3, 4 or 5.

This is described in:

The first reference is vague on how to calculate the sizes and weights, but a method is given in the other two references.

Application: locating an approximate sub-image

Suppose we have a large image L size WLxHL and a small image S size WSxHS, and we want to find the closest approximation to S in L. Examples include tracking the movement of an object in a video sequence, and searching an image for a texture.

One method is to make a histogram of S. This contains N bins representing N intensities or colour values, each bin with a count of the pixels that fall within the bin. N might be small, eg 16. Then make a histogram for each WSxHS sub-image of L, and find the distance between this and the histogram of S. The closest histogram tells us which sub-image is the closest. (The "distance between histograms" is a complex topic.)

But that would be a large number of histograms. Instead, we make N images each size WLxHL. Each contains the cumulative number of pixels that fall into that bin. So each is an integral image, and finding the histogram for any rectangle involves four lookups in each of the N images.

This is described in:


How does it work?

Integral image

An integral (or summed area table) of an image has pixel values of each coordinate set to the sum of the image's pixel values across a rectangle from the origin (0,0) to this coordinate. This is done for each channel. Processing, and the integral image itself, needs to be HDRI.

If f(x,y) is the input image then I(x,y) is the integral image, calculated as:

I(x,y) = f(x,y) + I(x-1,y) + I(x,y-1) - I(x-1,y-1).

When x<0 or y<0, I(x,y)==0.

ii_overrect.png

ASIDE: To see why this is true, consider this diagram, which represents a 8x6 pixel integral image.

Suppose A is the sum of all the input pixels in the 7x5 blue rectangle, B is the sum of all the input pixels in the 8x5 red rectangle, and C is the sum of all the input pixels in the 7x6 green rectangle.

We want to calculate D, the sum of all the input pixels in the black rectangle.

By inspection, D equals the corresponding pixel from the input image, plus B plus C minus A.

Also, provided input pixels are positive: D>=C, D>=B, C>=A, B>=A.


So the top-left pixel of the integral will be a copy of the same pixel in the input image.

In words: the integral at a pixel is the image at that pixel, plus the integral to the left, plus the integral above, minus the integral above and left.

We can implement this using the more primitive process module cumulhisto in both directions. cumulhisto integrates in one direction only, from left to right, on each row separately. We take the output from this and rotate, then apply cumulhisto to that, and rotate back. The result is the required two-dimensional integral.

%IMDEV%convert ^
  %SRC% ^
  -process 'cumulhisto' ^
  -rotate -90 ^
  -process 'cumulhisto' ^
  -rotate +90 ^
  +depth ^
  -define quantum:format=floating-point ^
  ii_integ.miff

%IMDEV%convert ^
  ii_integ.miff ^
  info: 
ii_integ.miff MIFF 267x233 267x233+0+0 32-bit TrueColor sRGB 748KB 0.125u 0:00.125

How does cumulhisto help us?

cumulhisto returns an image, where each ouput pixel is the sum of the corresponding pixel and all the pixels to its left on the same row:

C(x,y) = f(x,y) + C(x-1,y)

When used vertically:

C'(x,y) = f'(x,y) + C'(x,y-1)

We feed C() into f'(), so substitute:

C'(x,y) = f(x,y) + C(x-1,y) + C'(x,y-1)

In words: the integral at (x,y) is the input at (x,y) plus the input pixels to the left, plus the integral at (x,y-1). Which is what we want.

We put this in a script, integral.bat.

Here are some statistics of the integral image, as multiples of Quantum:

set FMT=^
minRed=%%[fx:minima.r] minGreen=%%[fx:minima.g] minBlue=%%[fx:minima.b]\n^
meanRed=%%[fx:mean.r] meanGreen=%%[fx:mean.g] meanBlue=%%[fx:mean.b]\n^
maxRed=%%[fx:maxima.r] maxGreen=%%[fx:maxima.g] maxBlue=%%[fx:maxima.b]\n

%IMDEV%convert ^
  ii_integ.miff ^
  -format "%FMT%" ^
  info: 
minRed=0.380147 minGreen=0.443154 minBlue=0.254186
meanRed=7837.38 meanGreen=7609.21 meanBlue=6580.77
maxRed=33189.1 maxGreen=30228.4 maxBlue=27689.8

As expected, values are a large multiple of Quantum.

-auto-level compresses the values to the range 0 to 100% of Quantum, so we can view the image, just for interest:

%IMDEV%convert ^
  ii_integ.miff ^
  -auto-level ^
  ii_integ_al.png
ii_integ_al.pngjpg

In each channel, values increase monotonically from left to right and from top to bottom.

Differentiating

The inverse of integration is differentiation.

If f(x,y) is the input image and I(x,y) is the integral image, then:

f(x,y) = I(x,y) - I(x-1,y) - I(x,y-1) + I(x-1,y-1).

... where x<0 or y<0, I(x,y)==0.

In words: the differential at a pixel is set to the integral at that pixel, minus the integral to the left, minus the integral above, plus the integral above and left.

This is readily implemented as a command:

%IMDEV%convert ^
  ii_integ.miff ^
  ( -clone 0 -repage +1+0 ) ^
  ( -clone 0 -repage +0+1 ) ^
  ( -clone 0 -repage +1+1 ) ^
  -virtual-pixel Black ^
  -background None ^
  -define compose:clamp=off ^
  ( -clone 0-1 ^
    -compose MinusSrc -layers Flatten ^
    -clone 3 ^
    -compose Plus -layers Flatten ^
    -clone 2 ^
    -compose MinusSrc -layers Flatten ^
    +repage ^
  ) ^
  -delete 0-3 ^
  -alpha off ^
  ii_diff.png
ii_diff.pngjpg

Or, far more simply:

%IMDEV%convert ^
  ii_integ.miff ^
  -virtual-pixel Black ^
  -morphology convolve "2x2+0+0:1,-1,-1,1" ^
  ii_diff2.png
ii_diff2.pngjpg

We put this in a script, deintegral.bat.

This should be an accurate reconstruction of %SRC%. Is it?

%IM%compare -metric RMSE %SRC% ii_diff.png NULL: 
32.8536 (0.000501314)

It is accurate, so the round trip integrate-differentiate works.

Vice versa

We can process in the opposite direction: differentiate first, then integrate.

Differentiate:

call %PICTBAT%deintegral ^
  %SRC% ii_deint.miff
ii_deint.miffjpg

Make the differential more visible:

%IMDEV%convert ^
  ii_deint.miff ^
  -evaluate Add 50%% ^
  -sigmoidal-contrast 5,50% ^
  -auto-level ^
  ii_deint_al.png
ii_deint_al.pngjpg

Integrate:

call %PICTBAT%integral ^
  ii_deint.miff ii_deint_int.png
ii_deint_int.pngjpg

How accurately does this reconstruct %SRC%?

%IM%compare -metric RMSE %SRC% ii_deint_int.png NULL: 
27.1085 (0.000413649)

It is accurate.

De-integrating with windows

Okay, but what is the point of an integral image? It allows us to quickly calculate the sum (or mean) of the input image over a window of any size. The window width and height must both be integers, and usually odd numbers.

FUTURE: Non-integer window sizes could be handled, by deintegim interpolating the four pixels. This would be slower, and I can't see any need for this.

If the window is from (x1,y1) to (x2,y2) inclusive, then:

sum(x1,y1,x2,y2) = I(x2,y2) - I(x2,y1-1) - I(x1-1,y2) + I(x1-1,y1-1)

ASIDE: To see why this is, consider a rectangle divided into four areas A, B, C and D.

ii_abcd.png

The top-left pixel of D is at (x1,y1), so the bottom-right pixel of A is at (x1-1,y1-1).

We can calculate the area of D:

D = (A+B+C+D)
  - (A+B)
  - (A+C)
  + (A)

This gives us the sum over a window. More often we want the mean over the window, so we divide by the number of pixels in the window:

winArea = (x2-x1)*(y2-y1)

mean(x1,y1,x2,y2) = (I(x2,y2) - I(x2,y1-1) - I(x1-1,y2) + I(x1-1,y1-1)) / winArea
set WINRAD=10
set /A WINRADp1=%WINRAD%+1
set /A WINAREA=(%WINRAD%*2+1)*(%WINRAD%*2+1)

%IMDEV%convert ^
  ii_integ.miff ^
  ( -clone 0 -repage -%WINRAD%-%WINRAD% ) ^
  ( -clone 0 -repage -%WINRAD%+%WINRADp1% ) ^
  ( -clone 0 -repage +%WINRADp1%-%WINRAD% ) ^
  ( -clone 0 -repage +%WINRADp1%+%WINRADp1% ) ^
  -delete 0 ^
  -virtual-pixel Black ^
  -background None ^
  -define compose:clamp=off ^
  ( -clone 0-1 ^
    -compose MinusSrc -layers Merge ^
    -clone 3 ^
    -compose Plus -layers Merge ^
    -clone 2 ^
    -compose MinusSrc -layers Merge ^
    +repage ^
  ) ^
  -delete 0-3 ^
  -evaluate Divide %WINAREA% ^
  ii_mean.png
ii_mean.pngjpg

This is the same as the first de-integration command above, but with different offsets and a division at the end.

Pixels near the edge are a pain. The window is effectively smaller near the edges, so we might divide by another image that represented the variable window area. This is easy in C code (eg the deintegim process module) but is awkward in a CLI command.

In these scripts, we solve the edge problem by extending the edges of the input to the integral process on the left and right by the window width, and on top and bottom by the window height. Scripts take an argument of the window size, the width and height in pixels.

We put the above command in a script, integMean.bat. As input, this takes an integral image that has been made with extended edges. It creates a windowed mean as output, cropping off the extensions.

call %PICTBAT%integral ^
  %SRC% ii_integ2.miff 11x11

call %PICTBAT%integMean ^
  ii_integ2.miff ii_mean2.png 11x11
ii_mean2.pngjpg

We compare this result with "-statistic mean 11x11".

%IM%convert ^
  %SRC% ^
  -statistic mean 11x11 ^
  ii_stmean.png

%IM%compare -metric RMSE ii_stmean.png ii_mean2.png NULL: 
0.740495 (1.12992e-005)

They are virtually identical.

Another script, integMeanSd.bat, takes an ordinary image as input, and makes both mean and standard deviation outputs.

call %PICTBAT%integMeanSd ^
  %SRC% ii_mean3.png ii_sd3.png 11x11
ii_mean3.pngjpg ii_sd3.pngjpg

We compare the results of integMean.bat and integMeanSd.bat.

%IM%compare -metric RMSE ii_mean2.png ii_mean3.png NULL: 
0.0214974 (3.28029e-007)

They are virtually identical.

For more details in this topic, see the Windowed mean and standard deviation page.

Transparency

When an image has transparency, integim and deintegim calculate the alpha channel exactly as described above.

The colour channels are either also calculated as above, or integim pre-multiples by alpha and deintegim post-divides by alpha.

If a(x,y) is alpha in the input image and Ia(x,y) is the integral alpha, the pre-multiply formulae used by integim are:

I(x,y) = a(x,y)*f(x,y) + I(x-1,y) + I(x,y-1) - I(x-1,y-1).
Ia(x,y) = a(x,y) + Ia(x-1,y) + Ia(x,y-1) - Ia(x-1,y-1).

When x<0 or y<0, Ia(x,y)==0.

Rearrange, to get the action of "deintegim window 1x1":

a(x,y) = Ia(x,y) - Ia(x-1,y) - Ia(x,y-1) + Ia(x-1,y-1).

f(x,y) = (I(x,y) - I(x-1,y) - I(x,y-1) + I(x-1,y-1)) / a(x,y)

The more general case has larger windows:

winArea = (x2-x1)*(y2-y1)

k(x1,y1,x2,y2) = Ia(x2,y2) - Ia(x2,y1-1) - Ia(x1-1,y2) + Ia(x1-1,y1-1)

a(x1,y1,x2,y2) = k(x1,y1,x2,y2) / winArea

f(x1,y1,x2,y2) = (I(x2,y2) - I(x2,y1-1) - I(x1-1,y2) + I(x1-1,y1-1)) / k(x1,y1,x2,y2)

... with the exception that when k(...)==0, we set f(...) to black.

Hence, when an input to integim has an alpha, the result from deintegim will have the same alpha.

set WIND=window 100x100

%IMDEV%convert ^
  %SRC_TR% ^
  -alpha on ^
  -process 'integim' ^
  -process 'deintegim %WIND%' ^
  +write ii_tt_di0.png ^
  -background gray(50%%) ^
  -layers flatten ^
  ii_tt_di0_g.png
ii_tt_di0.png ii_tt_di0_g.pngjpg
%IMDEV%convert ^
  %SRC_TR% ^
  -process 'integim premult no' ^
  -process 'deintegim postdiv no %WIND%' ^
  +write ii_tt_di1.png ^
  -background gray(50%%) ^
  -layers flatten ^
  ii_tt_di1_g.png
ii_tt_di1.png ii_tt_di1_g.pngjpg

When we pre-multiply but don't post-divide, or don't pre-multiply but do post-divide, the results look okay against a white background but clearly not against a gray background: we see a dark or light halo in the semi-transparent areas.

%IMDEV%convert ^
  %SRC_TR% ^
  -process 'integim premult yes' ^
  -process 'deintegim postdiv no %WIND%' ^
  +write ii_tt_di2.png ^
  -background gray(50%%) ^
  -layers flatten ^
  ii_tt_di2_g.png
ii_tt_di2.png ii_tt_di2_g.pngjpg
%IMDEV%convert ^
  %SRC_TR% ^
  -process 'integim premult no' ^
  -process 'deintegim postdiv yes %WIND%' ^
  +write ii_tt_di3.png ^
  -background gray(50%%) ^
  -layers flatten ^
  ii_tt_di3_g.png
ii_tt_di3.png ii_tt_di3_g.pngjpg

We should be careful to use the same setting for both processes.

Displacement issue

Considering integim and deintegim as a single unit, each output pixel is the mean of a rectangle of the input pixels, where some pixel of the rectangle is in the same position as that output pixel.

Put another way, if the input has WxH pixels and the window is axb pixels, we could create (W+a-1)*(H*b-1) output pixels, and crop from there. The cumulhisto scripts work that way, because extending and cropping is the simplest method at the command level. But it is also expensive, when the real work needs only two passes over the image. So deintegim uses a rectangle centred on the given location, but crops the rectangle if needed so it doesn't extend beyond any image edge.

Scripts

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

integral.bat

This script is shown for interest. In practice, the integim process module is preferred.

rem From image %1, make integral %2.
rem Output should be capable of storing HDRI values.
rem %3 is optional window size, eg 5x7.
@rem
@rem Also uses:
@rem   iiREGARDALPHA if 1, uses alpha.
@rem
@rem See also deintegral.bat.


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 integ

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

call parseXxY2i 0 0 integ %3
if ERRORLEVEL 1 exit /B 1

if "%iiREGARDALPHA%"=="1" (
  set RA=regardalpha
) else (
  set RA=
)

set RA=

echo %0: integ_X=%integ_X% integ_Y=%integ_Y%

:: Setting the viewport adds the two fx: expressions as properties,
:: so this removes them.

%IMDEV%convert ^
  %INFILE% ^
  -set option:distort:viewport ^
    "%%[fx:w+2*%integ_X%]x%%[fx:h+2*%integ_Y%]-%integ_X%-%integ_Y%" ^
  -virtual-pixel Edge ^
  -filter point ^
  +distort SRT 0 ^
  +repage ^
  -process 'cumulhisto %RA%' ^
  -rotate -90 ^
  -process 'cumulhisto %RA%' ^
  -rotate +90 ^
  +depth ^
  -define quantum:format=floating-point ^
+set fx:w+2*%integ_X% ^
+set fx:h+2*%integ_Y% ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set integOUTFILE=%OUTFILE%

deintegral.bat

This script is shown for interest. In practice, the integim process module is preferred.

rem From integral %1, make image %2.
rem Input should be capable of storing HDRI values.
rem %3 is optional window size, eg 5x7.
rem
rem See also integral.bat.


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 deinteg

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

call parseXxY2 0 0 deinteg %3

echo %0: deinteg_X=%deinteg_X% deinteg_Y=%deinteg_Y%

set crpW=
for /F "usebackq" %%L in (`%IM%identify -ping ^
  -format "crpW=%%[fx:w-2*%deinteg_X%]\ncrpH=%%[fx:h-2*%deinteg_Y%]\n" ^
  %INFILE%`) do set %%L

if "%crpW%"=="" exit /B 1

set sCROP=-crop !crpW!x!crpH!+%deinteg_X%+%deinteg_Y% +repage

echo %0: sCROP=%sCROP%

%IMDEV%convert ^
  %INFILE% ^
  ( -clone 0 -repage +1+0 ) ^
  ( -clone 0 -repage +0+1 ) ^
  ( -clone 0 -repage +1+1 ) ^
  -virtual-pixel Black ^
  -background None ^
  -define compose:clamp=off ^
  ( -clone 0-1 ^
    -compose MinusSrc -layers Flatten ^
    -clone 3 ^
    -compose Plus -layers Flatten ^
    -clone 2 ^
    -compose MinusSrc -layers Flatten ^
    +repage ^
  ) ^
  -delete 0-3 ^
  %sCROP% ^
  -alpha off ^
  -define quantum:format=floating-point ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

%IM%identify %OUTFILE%

call echoRestore

endlocal & set deintegOUTFILE=%OUTFILE%

integMean.bat

This script is shown for interest. In practice, the iiMeanSd.bat script is preferred.

rem From integral %1, make image %2, the windowed mean.
rem Input should be capable of storing HDRI values.
rem %3 is optional window size, eg 5x7.
@rem
@rem Also uses:
@rem   iiREGARDALPHA if 1, uses alpha.
@rem
@rem See also integral.bat.


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

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 imn

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

call parseXxY2i 0 0 imn %3
if ERRORLEVEL 1 exit /B 1

echo %0: imn_X=%imn_X% imn_Y=%imn_Y%

set /A dx1=(%imn_X%-1)/2
set /A dx0=%imn_X%-1-%dx1%

set /A dy1=(%imn_Y%-1)/2
set /A dy0=%imn_Y%-1-%dy1%

set /A WINAREA=%imn_X%*%imn_Y%

echo %0: %dx0% %dx1%  %dy0% %dy1% WINAREA=%WINAREA%

if "%iiREGARDALPHA%"=="1" (
  set sDIVIDE=^( +clone -alpha extract ^) -compose DivideSrc -composite
  set sDIVIDE=
) else (
  set sDIVIDE=-evaluate Divide %WINAREA%
)

set sDIVIDE=-evaluate Divide %WINAREA%



set /A xp1=%dx0%+1
set /A yp1=%dy0%+1

set crpW=
for /F "usebackq" %%L in (`%IM%identify -ping ^
  -format "crpW=%%[fx:w-2*%imn_X%]\ncrpH=%%[fx:h-2*%imn_Y%]\n" ^
  %INFILE%`) do set %%L

if "%crpW%"=="" exit /B 1

set sCROP=-crop !crpW!x!crpH!+%imn_X%+%imn_Y% +repage

echo %0: sCROP=%sCROP%

%IMDEV%convert ^
  %INFILE% ^
  ( -clone 0 -repage -%dx1%-%dy1% ) ^
  ( -clone 0 -repage -%dx1%+%yp1% ) ^
  ( -clone 0 -repage +%xp1%-%dy1% ) ^
  ( -clone 0 -repage +%xp1%+%yp1% ) ^
  -delete 0 ^
  -virtual-pixel Black ^
  -background None ^
  -define compose:clamp=off ^
  -alpha off ^
  ( -clone 0-1 ^
    -compose MinusSrc -layers Merge ^
    -clone 3 ^
    -compose Plus -layers Merge ^
    -clone 2 ^
    -compose MinusSrc -layers Merge ^
  ) ^
  -delete 0-3 ^
  %sCROP% ^
  %sDIVIDE% ^
  -define quantum:format=floating-point ^
  %OUTFILE%

if "%iiREGARDALPHA%"=="1" (
  %IMDEV%convert ^
  %INFILE% ^
  -alpha extract ^
  ^( -clone 0 -repage -%dx1%-%dy1% ^) ^
  ^( -clone 0 -repage -%dx1%+%yp1% ^) ^
  ^( -clone 0 -repage +%xp1%-%dy1% ^) ^
  ^( -clone 0 -repage +%xp1%+%yp1% ^) ^
  -delete 0 ^
  -virtual-pixel Black ^
  -background None ^
  -define compose:clamp=off ^
  -alpha off ^
  ^( -clone 0-1 ^
    -compose MinusSrc -layers Merge ^
    -clone 3 ^
    -compose Plus -layers Merge ^
    -clone 2 ^
    -compose MinusSrc -layers Merge ^
  ^) ^
  -delete 0-3 ^
  %sCROP% ^
  %sDIVIDE% ^
  -define quantum:format=floating-point ^
  a.miff

  %IMDEV%convert ^
  %OUTFILE% ^
  a.miff ^
  -define compose:clamp=off ^
  -compose DivideSrc -composite ^
  -define quantum:format=floating-point ^
  a2.miff

)

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set imnOUTFILE=%OUTFILE%

integMeanSd.bat

This script is shown for interest. In practice, the iiMeanSd.bat script is preferred.

rem From image %1
rem write windowed mean %2 and standard deviation %3
rem with window size %4
rem using the cumulhisto integral image method.
@rem
@rem Todo: reduce work if user doesn't want mean or SD.
@rem


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 imnsd

set OUTMEAN=%2
if "%OUTMEAN%"=="." set OUTMEAN=
if "%OUTMEAN%"=="" set OUTMEAN=

set OUTSD=%3
if "%OUTSD%"=="." set OUTSD=
if "%OUTSD%"=="" set OUTSD=

set WINDIMS=%4

:: Temporary mean of square, and square of mean.
set TMPDIR=\temp\
set TMP_MN_SQ=%TMPDIR%ims_ms.miff
set TMP_MN_SQ2=%TMPDIR%ims_ms2.miff
set TMP_MN_SQ3=%TMPDIR%ims_ms3.miff
set TMP_SQ_MN=%TMPDIR%ims_sm.miff

if "%OUTMEAN%"=="" (
  set wrOUTMEAN=
) else (
  set wrOUTMEAN=+write %OUTMEAN%
)

%IMDEV%convert ^
  %INFILE% ^
  -evaluate Pow 2 ^
  +depth ^
  -define quantum:format=floating-point ^
  %TMP_MN_SQ%

if ERRORLEVEL 1 exit /B 1

call %PICTBAT%integral %TMP_MN_SQ% %TMP_MN_SQ2% %WINDIMS%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%integMean %TMP_MN_SQ2% %TMP_MN_SQ3% %WINDIMS%
if ERRORLEVEL 1 exit /B 1


call %PICTBAT%integral %INFILE% %TMP_SQ_MN% %WINDIMS%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%integMean %TMP_SQ_MN% %TMP_SQ_MN% %WINDIMS%
if ERRORLEVEL 1 exit /B 1

if "%OUTSD%"=="" (
  set sSQFORSD=NULL:
) else (
  set sSQFORSD=-evaluate Pow 2 %TMP_SQ_MN%
)


%IMDEV%convert ^
  %TMP_SQ_MN% ^
  %wrOUTMEAN% ^
  %sSQFORSD%

if ERRORLEVEL 1 exit /B 1

if not "%OUTSD%"=="" %IMDEV%convert ^
  %TMP_MN_SQ3% ^
  %TMP_SQ_MN% ^
  -compose MinusSrc -composite ^
  -evaluate Pow 0.5 ^
  %OUTSD%


call echoRestore

endlocal

iiMeanSd.bat

rem From image %1
rem write windowed mean %2
rem and standard deviation %3
rem with window size %4
rem using the process module integral image method.


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 rmnsd

set OUTMEAN=%2
if "%OUTMEAN%"=="." set OUTMEAN=
if "%OUTMEAN%"=="" set OUTMEAN=

set OUTSD=%3
if "%OUTSD%"=="." set OUTSD=
if "%OUTSD%"=="" set OUTSD=

set WINDIMS=%4

if "%OUTMEAN%"=="" (
  set wrOUTMEAN=
) else (
  set wrOUTMEAN=+write %OUTMEAN%
)

if "%OUTSD%"=="" (

  %IMDEV%convert ^
    %INFILE% ^
    -process 'integim' ^
    -process 'deintegim window %WINDIMS%' ^
    -depth 32 ^
    -define quantum:format=floating-point ^
    %wrOUTMEAN% ^
    NULL:

) else (

  %IMDEV%convert ^
    %INFILE% ^
    -depth 32 ^
    -define quantum:format=floating-point ^
    ^( -clone 0 ^
       -evaluate Pow 2 ^
       -process 'integim' ^
       -process 'deintegim window %WINDIMS%' ^
    ^) ^
    ^( -clone 0 ^
       -process 'integim' ^
       -process 'deintegim window %WINDIMS%' ^
       %wrOUTMEAN% ^
       -evaluate Pow 2 ^
    ^) ^
    -delete 0 ^
    -alpha off ^
    -compose MinusSrc -composite ^
    -evaluate Pow 0.5 ^
    %OUTSD%
)

call echoRestore

endlocal

parseXxY2.bat

rem Parses %4, default %1 and %2. Returns %3_X and %3_Y.
@rem
@rem %4 is one of these formats:
@rem   {blank}
@rem   .
@rem   {number}
@rem   {number}x
@rem   x{number}
@rem   {number}x{number}
@rem
@rem A number can be suffixed with %, c or p or C or P.
@rem

@set pxxy=%4
@set pxxyFirst=%pxxy:~0,1%
@rem echo pxxyFirst=%pxxyFirst%

@if "%pxxyFirst%"=="x" set pxxy=%1%pxxy%

@if "%pxxy%"=="." set pxxy=
@if "%pxxy%"=="" set pxxy=%1x%2

@for /F "usebackq tokens=1-2 delims=xX" %%X in ('%pxxy%') do @(
  @rem echo %%X,%%Y
  @set pxxyX=%%X
  @set pxxyY=%%Y
)
@if "%pxxyY%"=="" set pxxyY=%2


set CH_LAST=%pxxyX:~-1%

if "%CH_LAST%"=="^%" set CH_LAST=c

if /I "%CH_LAST%"=="c" (
  for /F "usebackq" %%L in (`%IM%identify ^
    -precision 19 ^
    -format "pxxyX=%%[fx:%1*%pxxyX:~0,-1%/100]" ^
    xc:`) do set %%L

) else if /I "%CH_LAST%"=="p" (
  for /F "usebackq" %%L in (`%IM%identify ^
    -precision 19 ^
    -format "pxxyX=%%[fx:%1*%pxxyX:~0,-1%]" ^
    xc:`) do set %%L
)


set CH_LAST=%pxxyY:~-1%

if "%CH_LAST%"=="^%" set CH_LAST=c

if /I "%CH_LAST%"=="c" (
  for /F "usebackq" %%L in (`%IM%identify ^
    -precision 19 ^
    -format "pxxyY=%%[fx:%2*%pxxyY:~0,-1%/100]" ^
    xc:`) do set %%L

) else if /I "%CH_LAST%"=="p" (
  for /F "usebackq" %%L in (`%IM%identify ^
    -precision 19 ^
    -format "pxxyY=%%[fx:%2*%pxxyY:~0,-1%]" ^
    xc:`) do set %%L
)




@rem echo pxxyX=%pxxyX% pxxyY=%pxxyY%

set %3_X=%pxxyX%
set %3_Y=%pxxyY%

parseXxY2i.bat

rem Like parseXxY2.bat, but rounds numbers to integers.

call parseXxY2 %1 %2 %3 %4

for /F "usebackq" %%L in (`%IM%identify ^
  -precision 19 ^
  -format "%3_X=%%[fx:int(!%3_X!+0.5)]\n%3_Y=%%[fx:int(!%3_Y!+0.5)]\n" ^
  xc:`) do set %%L

if ERRORLEVEL 1 exit /B 1

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
%IMDEV%identify -version
Version: ImageMagick 6.9.3-7 Q32 x86_64 2017-07-20 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2016 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP 
Delegates (built-in): bzlib cairo fftw fontconfig freetype fpx jbig jng jpeg lcms ltdl lzma pangocairo png rsvg tiff webp wmf x 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 integim.h1. To re-create this web page, execute "procH1 integim".


This page, including the images except where shown otherwise, 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-July-2017.

Page created 26-Jul-2017 04:33:14.

Copyright © 2017 Alan Gibson.