﻿ snibgo's ImageMagick pages

# Islands

Suppose we have an arbitrary black shape and an arbitrary black border. The shape is an "island"; it is surrounded by white. The black border extends to the image edges; it is a "mainland".

## Easy shapes

Some shapes have a central point, from which straight lines can be drawn to every edge point without crossing the edge. In other words, from the central point we can "see" every edge point.

How do we know if an image qualifies as an "easy shape? We could depolar from an assumed central point, scale this to a single line, then use a simplified version of graph1d.bat to scale it back up again. If the round-trip hasn't changed the image (much), then we have found a central point.

Here is an easy island surrounded by an easy mainland. You can download is_easy.xcf, which has the island in one layer and the mainland in another. See Gimp and IM on how to extract the layers with extrXcfLayers.bat.

```if not exist is_easy.xcf copy %PICTBAT%shapeEasy.xcf is_easy.xcf

call %PICTBAT%extrXcfLayers is_easy.xcf

%IM%convert ^
is_easy_island.png ^
-background White -layers flatten ^
-threshold 50%% ^
is_easy_island.png```
 is_easy_island.png is_easy_mainland.png is_easy_flattened.png We can unroll using depolar, do any processing, and invert the distortion.

 ```%IM%convert ^ -verbose ^ is_easy_flattened.png ^ -transparent White ^ -distort depolar -1 ^ ( +clone ^ ( xc:Red -size 9x1 xc:White +append ^ -write mpr:REDLINES +delete ^ ) ^ -fill mpr:REDLINES -draw "color 0,0 reset" ^ ) ^ +swap -composite ^ -write is_easy_depol.png ^ -distort polar -1 ^ is_easy_ex.png ```  Instead of drawing red lines, we might pick off boundary points and convert the coordinates back with data from "-verbose". Thus, we can find the correspondence between island points and mainland points.

```is_easy_flattened.png PNG 320x200 320x200+0+0 8-bit sRGB 1.71KB 0.000u 0:00.000
DePolar Distort, Internal Coefficents
c0 = +188.679623
c1 = +0.000000
c2 = +160.000000
c3 = +100.000000
c4 = -3.141593
c5 = +3.141593
c6 = +0.019635
c7 = +0.943398
DePolar Distort, FX Equivelent:
-fx 'aa=(i+.5)*0.019635 -3.141593;
rr=(j+.5)*0.943398 +0.000000;
xx=rr*sin(aa) +160.000000;
yy=rr*cos(aa) +100.000000;
v.p{xx-.5,yy-.5}' \
xc:Red=>Red XC 1x1 1x1+0+0 16-bit sRGB 0.000u 0:00.000
xc:White=>White XC 9x1 9x1+0+0 16-bit sRGB 0.000u 0:00.000
xc:Red=>REDLINES XC 1x1=>10x1 10x1+0+0 16-bit sRGB 0.000u 0:00.000
mpr:REDLINES=>REDLINES XC 1x1=>10x1 10x1+0+0 16-bit sRGB 0.000u 0:00.000
is_easy_flattened.png=>is_easy_depol.png PNG 320x200 320x200+0+0 8-bit sRGB 7.59KB 0.031u 0:00.010
Polar Distort, Internal Coefficents
c0 = +188.679623
c1 = +0.000000
c2 = +160.000000
c3 = +100.000000
c4 = -3.141593
c5 = +3.141593
c6 = +50.929582
c7 = +1.059998
Polar Distort, FX Equivelent:
-fx 'ii=i+page.x-160.000000; jj=j+page.y-100.000000;
xx=(atan2(ii,jj)-0.000000)/(2*pi);
xx=xx-round(xx);
xx=xx*2*pi*50.929582 + v.w/2;
yy=(hypot(ii,jj)-0.000000)*1.059998;
v.p{xx-.5,yy-.5}' \
is_easy_flattened.png=>is_easy_ex.png PNG 320x200 320x200+0+0 8-bit sRGB 29.2KB 0.063u 0:00.035```

More usefully, we can displace vertically, eg to stretch the island out to the mainland. See Clut cookbook: Cluts from graphics for techniques.

## Complex shapes

Some shapes have no central point.

I made this image with Gimp, putting the island in one layer and the border in another. You can download is_src.xcf.

```if not exist is_src.xcf copy %PICTBAT%shapeSamp.xcf is_src.xcf

call %PICTBAT%extrXcfLayers is_src.xcf

%IM%convert ^
is_src_island.png ^
-background White -layers flatten ^
-threshold 50%% ^
is_src_island.png```
 is_src_island.png is_src_border.png is_src_flattened.png ## Gradient between island and mainland

A "morphology distance" on the flattened image would give us the distance from any land.

For morphing from one shape to another, we need something different: a smooth gradient from the island to the mainland. It should be exactly black where adjacent to the island, and exactly white adjacent to the mainland.

This is not a simple problem. Here is a solution.

 Make a gradient that starts black at the island, and becomes lighter at distances away from the island. ```%IM%convert ^ is_src_island.png ^ -morphology Distance Euclidean:4,600^! ^ is_isl_dist.png``` Make a gradient that starts white at the mainland, and becomes darker at distances away from the mainland. ```%IM%convert ^ is_src_border.png ^ -morphology Distance Euclidean:4,600^! ^ -negate ^ is_bord_dist.png``` Merge the two gradients into one that is black at the island and white at the mainland. ```call %PICTBAT%fanComp ^ is_bord_dist.png ^ is_isl_dist.png ^ is_grad1.png``` For fanComp.bat, see Composite compositions: Fan compose. The gradient is quite good, though it is hard to judge from this image. For clarity, show contours. ```%IM%convert ^ is_grad1.png ^ ( -size 1x500 gradient: -rotate 90 -duplicate 10 +append ) ^ -clut ^ -morphology edgein diamond:1 ^ is_grad2.png``` The contour is linear. Contours show successive stages of a morph between one shape and the other. Show contours over the flattened image. ```%IM%convert ^ is_src_flattened.png ^ ( is_grad2.png -alpha set -channel A -evaluate set 50%% ) ^ -composite ^ is_grad3.png``` The contours are entirely between the island and mainland, as required. ## Centre line

We find a centre-line between the island and mainland by reducing to a skeleton, and removing line ends. The centre-line will generally be different to the 50% gradient.

See Anthony's Morphology of shapes: Thinning down to a Skeleton.

 4-connected skeleton ```%IM%convert ^ is_src_flattened.png ^ -channel RG ^ -morphology Erode Diamond ^ -morphology Thinning:-1 Skeleton:3 +channel ^ -write is_skel_4con.png ^ -channel G ^ -morphology Thinning:-1 LineEnds ^ is_skel_4con_loops.png ```  From the 4-connected skeleton, make a thinner 8-connected skeleton. ```%IM%convert ^ is_skel_4con.png ^ -channel RG ^ -morphology Thinning:-1 Diagonals ^ -morphology Thinning Corners ^ -write is_skel_8con.png ^ -channel G ^ -morphology Thinning:-1 LineEnds ^ is_skel_8con_loops.png``` Removing these line ends doesn't work.  From the 4-connected skeleton, remove line ends and then make a thinner 8-connected skeleton. ```%IM%convert ^ is_skel_4con.png ^ -channel RG ^ -morphology Thinning:-1 LineEnds ^ -morphology Thinning:-1 Diagonals ^ -morphology Thinning Corners ^ -morphology Thinning LineEnds:4 ^ is_skel_8con_loops2.png``` ## Octagonal convex hull of the island

See Anthony's Morphology of shapes: ConvexHull.

 Very slow. ```%IM%convert ^ -verbose ^ is_src_island.png ^ -negate ^ -channel RG ^ -morphology Close Diamond ^ -morphology Thicken:-1 ConvexHull ^ -morphology Close Diamond ^ is_convhull.png``` Very slow. ```%IM%convert ^ -verbose ^ is_src_island.png ^ -negate ^ -channel RG ^ -morphology Close Square ^ -morphology Thicken:-1 ConvexHull ^ -morphology Close Square ^ is_convhullSq.png``` Very slow. ```%IM%convert ^ -verbose ^ is_src_island.png ^ -negate ^ -channel RG ^ -morphology Close Disk:1 ^ -morphology Thicken:-1 ConvexHull ^ -morphology Close Disk:1 ^ is_convhullDisk.png``` ## Joining island to mainland

Make a line at the edge of the mainland.

 ```%IM%convert ^ -verbose ^ is_src_border.png ^ -morphology EdgeIn Square ^ is_src_border_edge.png``` Pick points that are adjacent to the island. For highest precision we would use all the points. Instead, we pick a sample, using nLightest.bat.

 ```%IM%convert ^ is_src_island.png ^ -morphology EdgeIn Square ^ -write is_src_island_edge.png ^ ( +clone -blur 0x10 -auto-level ) ^ +swap -compose Multiply -composite ^ is_src_island_edge_bl.png``` The indented lines are optional. They place greater emphasis on high-entropy locations.  We use an exclusion radius of 5% of min(width,height). ```set nlDEBUG=1 call %PICTBAT%nLightest is_src_island_edge_bl.png 9999 5 >is_isl_pnts.lis``` How many points did nLightest find?

```echo nl_nFound=%nl_nFound%

if "%nl_nFound%"=="" goto :error
if "%nl_nFound%"=="0" goto :error```
`nl_nFound=72 `

nLightest.bat doesn't pick the points in a nice neat clockwise or anticlockwise order. We blurred is_src_island_edge.png before calling nLightest so the more "interesting" locations were found first. (If we wanted the points in order, we could modulate the line by a circular gradient that was centred on a point that was on the island.)

The command above redirected the coordinates to is_isl_pnts.lis:

```360,227
491,156
369,246
521,262
507,169
381,229
531,244
125,70
122,115
105,176
348,248
256,97
125,169
117,193
527,68
134,97
93,157
153,317
101,114
507,247
512,149
179,277
147,279
547,73
145,78
461,113
441,138
426,152
131,208
389,252
479,253
168,295
110,93
142,226
512,82
291,152
228,161
465,268
134,297
194,263
97,136
154,243
212,253
409,166
390,175
533,138
525,180
474,97
522,225
306,166
211,173
135,328
274,107
116,150
280,134
142,181
451,283
400,278
546,104
352,178
328,241
420,283
242,112
191,180
231,245
241,144
259,239
330,172
280,237
524,201
308,235
163,183```

For each of these points, we can find the nearest point on the border (mainland). How?

It is tempting to find a load of points on the mainland, and find which of these is closest to each island point. But distances will then cross the island.

Instead, we put single black pixel on a white background, and use morphology IterativeDistance, masked by the island, to find distances from that point. Along the edge of the border, the lowest value found is the closest border to the point.

The integer after "IterativeDistance" is, ideally, the maximum expected distance.

To show the process, let us find the closest border point to the coordinate (108,96), which is towards the top-left of the island.

 We write intermediate results purely for illustration. ```set InX=108 set InY=96 set MAX_DIST=600 for /F "usebackq tokens=1-3 delims=," %%W ^ in (`%IM%convert ^ is_src_island.png ^ ^( +clone -negate -write mpr:MASK +delete ^) ^ -fill #fff -colorize 100 ^ -fill #000 -draw "point %InX%,%InY%" ^ -mask mpr:MASK ^ -morphology IterativeDistance:%MAX_DIST% Euclidean ^ +mask ^ -write is_grad_samp.png ^ is_src_border_edge.png ^ -compose CopyOpacity -composite ^ -write is_grad_samp2.png ^ -background #fff -alpha Remove ^ -auto-level ^ -format "%%[fx:minima]," -write info: ^ +transparent #000 ^ -write is_grad_samp3.png ^ sparse-color:`) ^ do ( set MIN=%%W set blackX=%%X set blackY=%%Y ) echo MIN=%MIN% blackX=%blackX% blackY=%blackY% ``` `MIN=0 blackX=108 blackY=40 `   We can show the result: ```%IM%convert ^ is_src_flattened.png ^ -fill Red -draw "line %InX%,%InY% %blackX%,%blackY%" ^ is_grad_samp4.png``` `MIN=0 blackX=108 blackY=40 ` We should check that MIN is zero. If it is, we have a valid border point.

The command above finds the match for one point. To find all matches, the script joinIslandMainland.bat loops through the points. The script needs a white island on a black background, so we negate it first.

 ```%IM%convert ^ is_src_island.png ^ -negate ^ is_island_neg.png set jimDEBUG=1 call %PICTBAT%joinIslandMainland ^ is_island_neg.png ^ is_isl_pnts.lis ^ is_src_border_edge.png ^ out.csv %IM%convert %jimDEBUG_FILE% is_join1.png``` Lines don't cross each other, which is good.

Less desirable features:

• Some lengths of island map to the same mainland point.
• Some lengths of mainland map to no island points.
• Some lines cross the island, for example bottom-centre and top-right. This happens when the mainland point is the closest one to the island point, even when the route has to go not in a straight line but around an island promontary.
• It is slow, finding about two lines per second.

The first two undesirable features are unavoidable unless the island and mainland are exactly parallel.

We can treat the centre line as a mainland edge, or (with a little initial processing) as an island.

 ```%IM%convert ^ is_src_flattened.png ^ -morphology Erode Diamond ^ -morphology Thinning:-1 Skeleton ^ -morphology Thinning:-1 "LineEnds:1^>" ^ is_cent_line.png``` ```set jimDEBUG=1 call %PICTBAT%joinIslandMainland ^ is_island_neg.png ^ is_isl_pnts.lis ^ is_cent_line.png ^ out.csv %IM%convert %jimDEBUG_FILE% is_join2.png``` The "less desirable features" have been reduced but not entirely eliminated.

We could iterate the process, dividing each distance into two, generating a (probably) very smooth transition from the island to the mainland. It is probably easier (and quicker?) to use the Gradient method above.

## Multiple lakes

This section was written before ImageMagick acquired the "-connected-components" feature. See the official Connected Components Labeling page.

We sometimes have multiple "lakes", ie a number of white shapes on a black background. This example includes five large and three small shapes. • How many shapes?
• Where are they? (Centre pixels, bounding rectangles, or some other measure.)
• How many pixels in each?

We start by reducing each shape to a single white pixel. If a shape was a ring, it would be reduced to multiple pixels.

 ```%IM%convert ^ is_multi_lakes.png ^ -morphology Thinning:-1 Skeleton:3 ^ -morphology Thinning:-1 LineEnds ^ is_multi_lakes_one.png``` If the command had included "-morphology Erode Diamond", it would have missed the small shapes.

We count the white pixels, to find how many shapes there are:

```%IM%convert ^
is_multi_lakes_one.png ^
( +clone -fill Black -colorize 100 ) ^
-metric AE ^
-compare -format "%%[distortion]" ^
info: ```
`8`

We list the white pixels:

```%IM%convert ^
is_multi_lakes_one.png ^
+transparent White ^
sparse-color: >is_mlo_list.lis

type is_mlo_list.lis```
`39,34,graya(255,1) 127,37,graya(255,1) 242,61,graya(255,1) 151,98,graya(255,1) 54,129,graya(255,1) 180,137,graya(255,1) 155,154,graya(255,1) 270,157,graya(255,1) `

Or we list the coordinates of the white pixels to a space-separated file:

`call %PICTBAT%listWhiteSps is_multi_lakes_one.png is_mlo_coords.lis`
```39 34
127 37
242 61
151 98
54 129
180 137
155 154
270 157```

We can walk through this list of coordinates. For each one, first find the number of pixels in the shape. Then we find the bounding rectangle.

```del is_mlo_walk.lis 2>nul

for /F "usebackq tokens=1,2" %%X in (is_mlo_coords.lis) do (

%IM%convert ^
is_multi_lakes.png ^
^( +clone ^
-fill Red -floodfill +%%X+%%Y White ^) ^
-metric AE ^
-compare -format "X=%%X,  Y=%%Y,  size=%%[distortion], " ^
info:  >>is_mlo_walk.lis

%IM%convert ^
is_multi_lakes.png ^
-fill Red -floodfill +%%X+%%Y White ^
-fill Black +opaque Red ^
-format " BoundRect=%%@\n" ^
info:  >>is_mlo_walk.lis
)
type is_mlo_walk.lis```
```X=39,  Y=34,  size=1919, BoundRect=52x49+18+14
X=127,  Y=37,  size=1, BoundRect=1x1+127+37
X=242,  Y=61,  size=8001, BoundRect=113x103+193+13
X=151,  Y=98,  size=1406, BoundRect=37x49+136+73
X=54,  Y=129,  size=6559, BoundRect=97x99+14+78
X=180,  Y=137,  size=2, BoundRect=2x1+179+137
X=155,  Y=154,  size=4, BoundRect=2x2+154+153
X=270,  Y=157,  size=2310, BoundRect=64x51+238+132```

"Line Ends" is slow. If the shapes approach the size of the image and there aren't many of them, an alternative may be faster. For example, the following finds a white pixel, flood-fills, and repeats until no more are found. As this needs a loop, we might as well do the other tasks (finding the size and bounding rectangle) at the same time.

```del is_mlo_walk2.lis 2>nul

set TMPIMG=%TEMP%\is_temp.png

%IM%convert ^
is_multi_lakes.png ^
%TMPIMG%

:loop

set fwX=
for /F "usebackq tokens=1,2 delims=, " %%L in (`%IM%convert ^
%TMPIMG% ^
+transparent White ^
sparse-color:`) do (
set fwX=%%L
set fwY=%%M
)

if not "!fwX!"=="" (
rem echo fwS=!fwS! fwX=!fwX! fwY=!fwY! >>is_mlo_walk2.lis

%IM%convert ^
%TMPIMG% ^
^( +clone ^
-fill Red -floodfill +!fwX!+!fwY! White ^
-write %TMPIMG% ^) ^
-metric AE ^
-compare -format "X=!fwX!,  Y=!fwY!,  size=%%[distortion], " ^
info:  >>is_mlo_walk2.lis

%IM%convert ^
is_multi_lakes.png ^
-fill Red -floodfill +!fwX!+!fwY! White ^
-fill Black +opaque Red ^
-format " BoundRect=%%@\n" ^
info:  >>is_mlo_walk2.lis
)
if not "!fwX!"=="" goto loop

type is_mlo_walk2.lis```
```X=204,  Y=13,  size=8001, BoundRect=113x103+193+13
X=30,  Y=14,  size=1919, BoundRect=52x49+18+14
X=127,  Y=37,  size=1, BoundRect=1x1+127+37
X=149,  Y=73,  size=1406, BoundRect=37x49+136+73
X=33,  Y=78,  size=6559, BoundRect=97x99+14+78
X=279,  Y=132,  size=2310, BoundRect=64x51+238+132
X=179,  Y=137,  size=2, BoundRect=2x1+179+137
X=154,  Y=153,  size=4, BoundRect=2x2+154+153```

This has found the same sizes and bounding rectangles. It has found different coordinates but they are, of course, within the shapes.

ASIDE: The commands just given use "sparse-color:" to list just the white pixels, and then pulls out the coordinates of the first one. When there are no white pixels there will be no coordinates, hence the test for !fxW! being blank.

An alternative is to create a single white pixel, and search for it:

```for /F "usebackq tokens=1,3,4 delims=()@, " %%L in (`%IM%compare ^
-metric RMSE ^
-similarity-threshold 0 ^
-dissimilarity-threshold 1 ^
-subimage-search ^
%TMPIMG% xc:White NULL: 2^>^&1`) do (
set fwS=%%L
set fwX=%%M
set fwY=%%N
)```

The test is then whether fwS is zero. If it is, then a white pixel was found. This method is much slower than using "sparse-color:".

NOTE: After the above was written, a new feature was added to ImageMagick: "-connected-components". This is a very fast method for finding patches of connected colours. See the official documentation Connected Components Labeling.

```%IM%convert ^
is_multi_lakes.png ^
-define connected-components:verbose=true -connected-components 4 ^
NULL: ```
```Objects (id: bounding-box centroid area mean-color):
0: 320x200+0+0 158.2,101.1 43798 srgb(0,0,0)
1: 113x103+193+13 246.6,65.1 8001 srgb(255,255,255)
5: 97x99+14+78 59.3,130.2 6559 srgb(255,255,255)
6: 64x51+238+132 269.3,155.2 2310 srgb(255,255,255)
2: 52x49+18+14 41.1,35.0 1919 srgb(255,255,255)
4: 37x49+136+73 152.4,97.1 1406 srgb(255,255,255)
8: 2x2+154+153 154.5,153.5 4 srgb(255,255,255)
7: 2x1+179+137 179.5,137.0 2 srgb(255,255,255)
3: 1x1+127+37 127.0,37.0 1 srgb(255,255,255)```

This has found nine components: the eight white shapes, and the black background.

The centroid coordinates may not be within the shape. (For example, the centroid of a crescent-moon shape is not within the crescent moon.) From the results, a crop could be made of the bounding-box, and the crop searched for a pixel of the found colour.

## Overlapping shapes

The techniques above are suitable when one shape is entirely within another.

When shapes overlap, the techniques can be adapted by making another shape that contains all the others. This could be a circle, rectangle or union of the smaller shapes. Treat that enclosing shape as the mainland, and each overlapping shape as an island, and continue as above.

## Scripts

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

### joinIslandMainland.bat

```rem Given:
rem   %1 island image (white island on black background)
rem   %2 list of coords adjacent to island
rem   %3 mainland edge image (white line on black background)
@rem Writes:
@rem   %4 output CSV file, list of coords on mainland that correspond to island coords
@rem
@rem Also uses:
@rem   jimDEBUG if 1, creates debug image
@rem   jimMAX_DIST
@rem
@rem Updated:
@rem   24-May-2016

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 jim

set ISLAND=%1
set INCOORDS=%2
set MNLAND_EDGE=%3
set OUTCSV=%4

set DEBUG_FILE=%BASENAME%_%sioCODE%_dbg%EXT%
set DBG_LINES=%TEMP%\%~n1_%sioCODE%_dbglns.txt

del %OUTCSV% 2>nul
if "%jimDEBUG%"=="1" del %DBG_LINES% 2>nul

set TMPEXT=.miff
set TMP_ISLAND=%TEMP%\%~n1%TMPEXT%
set TMP_MNLAND_EDGE=%TEMP%\%~n3%TMPEXT%

if "%jimMAX_DIST%"=="" set jimMAX_DIST=100

%IM%convert %ISLAND% %TMP_ISLAND%

%IM%convert %MNLAND_EDGE% %TMP_MNLAND_EDGE%

for /F "tokens=1-2 delims=, " %%X in (%INCOORDS%) do (
for /F "usebackq tokens=1-3 delims=," %%W in (`%IM%convert ^
%TMP_ISLAND% ^
^( +clone -write mpr:MASK +delete ^) ^
-fill #fff -colorize 100 ^
-fill #000 -draw "point %%X,%%Y" ^
-morphology IterativeDistance:%jimMAX_DIST% Euclidean ^
%TMP_MNLAND_EDGE% ^
-compose CopyOpacity -composite ^
-background #fff -alpha Remove ^
-channel RGB -auto-level +channel ^
-format "%%[fx:minima]," -write info: ^
+transparent #000 ^
sparse-color:`) do (
set MIN=%%W
set blackX=%%X
set blackY=%%Y
)

echo %%X,%%Y  MIN=!MIN! !blackX!,!blackY!

if !MIN!==0 (
echo !blackX!,!blackY! >>%OUTCSV%

if "%jimDEBUG%"=="1" echo line %%X,%%Y !blackX!,!blackY! >>%DBG_LINES%
)
)

if "%jimDEBUG%"=="1" if exist %DBG_LINES% %IM%convert ^
%ISLAND% ^
%MNLAND_EDGE% ^
-compose Lighten -composite ^
-stroke Red -draw @%DBG_LINES% ^
%DEBUG_FILE%

@call echoRestore

@endlocal & set jimDEBUG_FILE=%DEBUG_FILE%```

### nLightest.bat

```@rem From image %1, finds (%2) lightest points.
@rem
@rem After each point is found,
@rem    blacks out pixels within radius (r) of that point
@rem where r = min(width,height) * %3/100
@rem %3 defaults to 10 (percent).
@rem
@rem Returns number of points found. Could be < n, or even zero (?).
@rem
@rem Also uses:
@rem   nlDEBUG if 1, creates debugging image.
@rem   nlPOINTSIZE optional, pointsize for debug images.
@rem   nlSTROKEWIDTH for debug.
@rem   nlONLY_WHITE if 1, doesn't auto-level,
@rem      so finds only points that are exactly white.
@rem
@rem Returns:
@rem   nl_nFound number of points found, integer >= 0
@rem   echos list of coordinates.
@rem
@rem Updated:
@rem   24-May-2016 v7 needs -channel RGB for -auto-level.
@rem   7-May-2017 "+antialias" the circles.

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 nl

for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L

@set TMPFILE=%TEMP%\%~n1_%sioCODE%.miff
@set CYGTMPFILE=%CYGTEMP%\%~n1_%sioCODE%.miff
@set LISTFILE=%BASENAME%_%sioCODE%.lis
@set TMPDEBUGFILE=%BASENAME%_%sioCODE%_dbg.miff
@set DEBUGFILE=%BASENAME%_%sioCODE%_dbg%EXT%
@set DEBUGDRAW=%TEMP%\%~n1_%sioCODE%_dbgdrw.txt

del %DEBUGDRAW% 2>nul

if "%nlPOINTSIZE%"=="" set nlPOINTSIZE=20

if "%nlSTROKEWIDTH%"=="" set nlSTROKEWIDTH=1

set MAX_FIND=%2
if "%MAX_FIND%"=="" set MAX_FIND=10

set AUTOLEV=-channel RGB -auto-level +channel
if "%nlONLY_WHITE%"=="1" set AUTOLEV=

for /F "usebackq" %%L in (`%IM%convert ^
-ping ^
%INFILE% ^
info:`) do set %%L

%IM%convert %INFILE% -colorspace Gray %AUTOLEV% %TMPFILE%

if "%nlDEBUG%"=="1" %IM%convert %INFILE% %TMPDEBUGFILE%

set /A nFound=0

:loop

set MAX=
set whiteX=

for /F "usebackq tokens=1-3 delims=:, " %%W ^
in (`%IMDEV%convert ^
%TMPFILE% ^
-process onewhite ^
NULL: 2^>^&1`) ^
do (
if "%%W"=="onewhite" (
set MAX=%%W
set whiteX=%%X
set whiteY=%%Y
)
)

if "!MAX!"=="onewhite" if "!whiteX!" neq "none" (
%IM%convert ^
%TMPFILE% ^
+antialias -fill #000 ^
-draw "circle %whiteX% %whiteY% %whiteX2% %whiteY%" ^
%AUTOLEV% ^
%TMPFILE%

if "%nlDEBUG%"=="1" (
(
echo fill None stroke #f00 circle %whiteX%,%whiteY%,%whiteX2%,%whiteY%
echo fill #f00 stroke None text %whiteX%,%whiteY% '!nFound!'
) >>%DEBUGDRAW%
)

set /A nFound+=1
echo %whiteX%,%whiteY%
if !nFound! LSS %MAX_FIND% goto loop
)

rem if "%nlDEBUG%"=="1" %IM%convert %TMPDEBUGFILE% %DEBUGFILE%

if "%nlDEBUG%"=="1" if exist %DEBUGDRAW% %IM%convert ^
%INFILE% ^
-strokewidth %nlSTROKEWIDTH% ^
-pointsize %nlPOINTSIZE% ^
-draw @%DEBUGDRAW% ^
%DEBUGFILE%

call echoRestore

@endlocal & set nl_nFound=%nFound%& set nlDEBUG_FILE=%DEBUGFILE%```

### fanComp.bat

```rem A compose like a fan.
@rem
@rem result = src / (src-dest+1)

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

@%IM%convert ^
%1 ^
%2 ^
( -clone 1 -evaluate Divide 2 ) ^
( -clone 0-1 ^
-compose Mathematics ^
-define compose:args=0,0.5,-0.5,0.5 ^
-composite ^
) ^
-delete 0-1 ^
-compose DivideSrc -composite ^
%3```

### listWhiteSps.bat

```rem From image %1,
rem creates text file %2 with coords of white pixels. (Space between x and y.)
rem Uses sparse-color and DOS tools; fast but messy.

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

@setlocal

call echoOffSave

set BAT_FILE=%TEMP%\%~n1_lws.bat

echo BAT_FILE=%BAT_FILE%

echo call %PICTBAT%sparseCoord.bat ^^>%BAT_FILE%
%IM%convert %1 -alpha set +transparent #fff sparse-color:>>%BAT_FILE%

call %BAT_FILE%>%2

call echoRestore```

### sparseCoord.bat

```:loop
@if "%1"=="" @exit /B 0
@echo %1 %2
@shift
@shift
@shift
@shift
@goto loop```

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

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.