snibgo's ImageMagick pages

Painting with patches

A process for building images.

This is closely related to Quilting.

An artist paints someone's portrait by an iterative process:

  1. Compare the canvas so far with the sitter's face.
  2. If there is a significant difference, add paint to the canvas.
  3. Repeat until no more significant differences.

This iteration may start at the high level (broad outlines of head, shoulders and hair) and gradually work downwards to the fine details.

This process module simulates that process. It paints a canvas by successive comparisons with a master image, making the canvas a closer approximation to the master. [[It starts with large patches and works its way to small patches. Within each patch size, it progresses in a square gid pattern. At each position, it first considers whether the position is close enough to the centre of interest to consider drawing ("TRIAL_IT"). If so, it compares a square area of the canvas at that position with the corresponding area of the master. If the RMSE difference is greater than a preset limit ("CHANGE_IT"), it will draw either a square or a circle.]]

If a circle is to be drawn, the square of approximately equal area is used to compare with the master.

The square or circular patch drawn is either a solid colour of the mean colour from the master's square, or a gradient from a sparse-colour of the corners of the master's square, or a sample from a different image, or a solid colour picked from a palette.

The process picks the best colour available, assuming the colour is fully opaque. It would be cuter if it took any opacity into account.

Aside: We could pick a solid colour from an image that represents a list of colours, like "-remap". So the list might contain black and white, or primaries, etc. We can extend this "palette" type to be an index to other images: ie take 1000 images; reduce each to 1x1 and +append them; call this an index. We search for the best colour in index; use COMP_XW as which of the 1000 images to use (resized).

Increasing ppPatchNumFracPow from the default 1 to, say, 3 reduces the resulting detail. This is appropriate when the patches provide detail, for example for non-solid patch types.

See \web\im\ PaintPatches.bat, mlf.bat, ml*.png and ppOut.png.

Process module

Option Description
Short
form
Long form
c string colfrom string Method blah, one of:
    Avg
    Cent
    Gradient
    Palette
    Sample
Default = ??.
ps string patchShape string Method blah, one of:
    Rect
    Ellipse
    Min
Default = Rect.
o number opacity number Blah.
0.0 to 1.0.
Default: 1.0.
ms number multSal number Blah.
0.0 to 1.0.
Default: 0.5.
wt number wrngThresh number Blah.
0.0 to 1.0.
Default: 0.1.
ac number adjLcComp number Adjust lightness and contrast for comparisons.
??
Default: 0.0.
ap number adjLcPat number Adjust lightness and contrast of patches.
??
Default: 0.0.
fe number feather number Feather patch edges (blur sigma).
??
Default: 0.0.
x integer maxIter integer Maximum iterations.
??
Default: ?1000.
m mebc Do minimum error boundary cuts.
rc rectCanv Set rectangle to canvas.
hs hotSpot Hotspot only (for ps Min).
w filename write filename Write each iteration to a file.
Eg frame_%06d.png
Default: iterations are not written to files.
d debug Write debugging information.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.
version Write version information to stdout.

Between one and five input images, in this order:

  1. master (aka target; the only required input; at least two pixels high)
  2. initial canvas (same size as master)
  3. saliency map (same size as master)
  4. samples (source for samples, to paste) OR Nx1 palette image
  5. subsrch (source for searches; same size as sampled)

If saliency is supplied, then canvas must also be supplied.

Samples and subsrch will be two versions of one image, the same size as each other and larger than master, generally at least twice in each dimension. Output will be same size as master. The output will replace all the input images. If subsrch is supplied, then sampled must also be supplied.

If subsrch is absent, sampled is used for that purpose.

With the above rules, any number between one and five inputs can be supplied, and the module can determine what they are.

Each pasted patch colour is one of:

Each pasted patch shape is one of:

Option: work in RGB or other colorspace.

Option: paste with a certain opacity.

Option: MEBC.

Option: feather patch edges.

If we like the image at the small scale, ability to make large version.

Given a large source image (eg a painting),
from which cuts will be made and pasted to a canvas,
to make the canvas look like a target image.
Canvas is same size as target, both smaller than source.

Create a canvas; paint it white.

Pick the "most wrong" part of the canvas. ("Part" is a rectangle.)

Subimage search for that part within source painting,
to find a (rectangular) patch.

Extend the patch as far as it improves the canvas. (This is the hard part.)

Paste the patch on the canvas.

Mark the patch on the source painting as unavailable.

Repeat until the "most wrong" part of the canvas isn't very wrong,
or no more source is available.

Beware of getting stuck,
where the "worst part" doesn't have anything better to replace it.

Algorithm: If we don't have an initial canvas, create it. Then:

  1. Pick the most wrong (but fixable) pixel in canvas. If none, or none within a threshold of wrongness, end algorithm.
  2. Choose a search area (a rectangle?) in canvas centred on this pixel.
  3. Crop corresponding area from master. This is the search patch. Find the paste patch as a solid colour, or gradient, or sample. Sample is:
    1. Subimage-search for that in subsrch.
    2. Crop that area extended as far as it improves the canvas from samples. (This is the hard part.) This is the paste patch.
    3. Possibly mark these pixels on samples as unavailable.
  4. Composite the paste patch on canvas, possibly using MEBC and opacity.
  5. Repeat from the start.

The hard part: We want to paste pixels that would improve the canvas. Paste the patch on a trial canvas, and find the difference with the target. For the actual canvas, find the difference with the target. Where the trial difference is less than canvas difference, that's what we copy. We copy the pixels where

abs(trial-target) < abs(canvas-target)

This is likely to cause fragmentation, which a suitable blur would alleviate.

How large should the search area be? How do we decide? A simple parameter as fraction of image dimensions?

Centre of interest. More generally: a grayscale map of saliency. Has two effects: modulates patch size (between given min and max sizes), and modulates threshold of permitted wrongness. Lighter saliency gives smaller threshold, and smaller patches.

Minimum error boundary cut: make a mask of border pixels just inside the opaque area. Cut within that border.

We need C code for a fast subimage search. Like rmsealpha, but multiscale. Done.

Given a rectangle of the target, what gradient matches the centre of the rectangle and is a good fit for the rest of the rectangle?

Saliency:

magick -size 600x400 -define gradient:center=100,100 -define gradient:radii=600,400 radial-gradient: -auto-level r.png

The order of drawing: large patches first, then smaller ones. Wrongness: high values are done first. Patch size is determined by saliency: high values give small patches. But saliency values are updated. Answer: the red channel is first negated, then decreased to mean "already processed", high values = do first. The green channel is used for patch sizes.

magick fk44.jpg ( +clone -fill White -colorize 100 ) ( -clone 0 -edge 5 -blur 0x10 -clamp -channel R -negate +channel +write a.miff ) -clamp -process 'paintpatches v ' x.png magick fk44.jpg ( +clone -fill White -colorize 100 ) a.png -clamp -process 'paintpatches v ' x.png

With ellipses (or anything other than a rectangle), we can get stuck in a corner, trying to cover a pixel that the ellipse doesn't cover. Cure: don't clip patches at image edges.

magick fk44.jpg -alpha opaque ( +clone -fill White -colorize 100 ) a.png -clamp -process 'paintpatches c cent p min o 0.5 w xxx_%06d.png v ' x2.png

The output from the process can be used as the initial canvas for another run. Any image can be used as the initial canvas, provided it is the same size as the master. If the canvas is already close to the master, the wrngThresh default of 0.1 may result in no change; a lower number can be used.

Examples

%IM7DEV%magick toes.png -process 'paintpatches help' NULL: 

cmd /c exit /B 0
Usage: -process 'paintpatches [OPTION]...'
Paint an image with patches.

  c,  colFrom string       'Avg', 'Cent', 'Gradient', 'Palette' or 'Sample'
  ps, patchShape string    'Rect', 'Ellipse' or 'Min'
  pd, patchDims W,H        maximum patch dimensions
  o,  opacity number       0.0 to 1.0
  ms, multSal number       multiplier for saliency 0.0 to 1.0
  wt, wrngThresh number    wrongness threshold 0.0 to 1.0
  ac, adjLcComp number     adjust lightness & contrast for comparisons
  ap, adjLcPat number      adjust lightness & contrast of patches
  fe, feather number       feather patch edges (blur sigma)
  x,  maxIter integer      maximum iterations
  m,  mebc                 do minimum error boundary cuts
  rc, rectCanv             set rectangle to canvas
  hs, hotSpot              hotspot only (for ps Min)
  w,  write filename       write iterations eg frame_%06d.png
  d,  debug                write debugging information
  v,  verbose              write text information
  v2, verbose2             write more text information
      version              write version information to stdout

A source image, the "master".

set SRC=toes.png
toes.pngjpg

Default settings.

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches' ^
  pp_ex1.png
pp_ex1.pngjpg

Vary colFrom:

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Avg' ^
  pp_avg.png
pp_avg.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Cent' ^
  pp_cent.png
pp_cent.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Gradient' ^
  pp_grad.png
pp_grad.pngjpg

Vary patchShape:

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches patchShape Rect' ^
  pp_rect.png
pp_rect.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches patchShape Ellipse' ^
  pp_ell.png
pp_ell.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches patchShape Min' ^
  pp_min.png
pp_min.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches rectCanv patchShape Min' ^
  pp_rc.png
pp_rc.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches c Cent rectCanv patchShape Min' ^
  pp_rc2.png
pp_rc2.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches c Avg rectCanv patchShape Min' ^
  pp_rc3.png
pp_rc3.pngjpg

A lower opacity:

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Avg opacity 0.7' ^
  pp_avgo.png
pp_avgo.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Cent opacity 0.7' ^
  pp_cento.png
pp_cento.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches colFrom Gradient opacity 0.7' ^
  pp_grado.png
pp_grado.pngjpg

Setting multSal to zero will prevent the centre of any new patches being placed on any pixel that has changed.

set OPTS=^
  colFrom Gradient opacity 0.7 multSal 0

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches %OPTS%' ^
  pp_gradoms.png
pp_gradoms.pngjpg

Other colorspaces

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches' ^
  pp_srgb.png
pp_srgb.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -colorspace RGB ^
  -process 'paintpatches' ^
  -colorspace sRGB ^
  pp_rgb.png
pp_rgb.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  -colorspace Lab ^
  -process 'paintpatches' ^
  -colorspace sRGB ^
  pp_lab.png
pp_lab.pngjpg

Initial canvas

The default initial canvas is black. We can use anything we want, including the output from this module. It must be the same size as the master image.

%IM7DEV%magick ^
  %SRC% ^
  ( +clone -fill White -colorize 100 ) ^
  -process 'paintpatches' ^
  pp_ic1.png
pp_ic1.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  pp_ic1.png ^
  -process 'paintpatches pd 20c,20c o 0.5 ps Ellipse' ^
  pp_ic2.png
pp_ic2.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  pp_ic2.png ^
  -process 'paintpatches pd 10c,10c o 0.5 ps Ellipse' ^
  pp_ic3.png
pp_ic3.pngjpg

Saliency

Saliency controls:

The default saliency is 50% gray. We can use anything we want, blah. It must be the same size as the master image.

Samples

A samples image provides a source for patches that will be pasted over the canvas.

This works by cropping a rectangle from master, and searching for that rectangle in subsrch if that is supplied, or samples if it isn't. The search yields coordinates that are used to crop a rectangle from samples, and this is pasted over canvas.

The samples image must be larger than the master. Too large is a waste of pixels and processing power. Around twice the size of the master seems to work well.

%IM7DEV%magick toes.png ( +clone -resize 200% ) -process 'paintpatches c sample ps Min v' x2.png

For searching, we should keep the resizes of the main image. Eg function to create the resizes, another to destroy, third to do the searching without rebuilding them.

Option to re-use pixels from samples.

We could use Vermeer's "Girl with a Pearl Earring", as the samples image:

if not exist prl_ear.png %IMG7%magick ^
  "\web\Vermeer\Girl_with_a_Pearl_Earring.jpg" ^
  -statistic median 5x5 ^
  -crop 1200x1800+540+220 +repage ^
  -resize 600x900 ^
  prl_ear.png
prl_ear.pngjpg

For example, with default settings:

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process 'paintpatches' ^
  pp_samp1.png
pp_samp1.pngjpg

The gamut of colours and tones in prl_ear.png is wider than it is for toes.png, so the same window of prl_ear.png is the closest to many parts of toes.png.

The adjLcComp option currently adds 10%-30% to the time needed for searching. (Searching is multi-scale, with the multiple scales of master being made only once. We could also use integral images for fast calculation of mean and SD, to improve the adjLcComp option.)

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process 'paintpatches adjLcComp 1.0' ^
  pp_samp2.png

This has used a wilder range of patches.

pp_samp2.pngjpg

We tame the default result by adjusting each found patch.

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process 'paintpatches adjLcPat 1.0' ^
  pp_samp3.png
pp_samp3.pngjpg

We tame the wilder result in the same way.

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process ^
    'paintpatches adjLcComp 1.0 adjLcPat 1.0' ^
  pp_samp4.png
pp_samp4.pngjpg
%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process ^
    'paintpatches adjLcComp 1.0 adjLcPat 1.0 ps min' ^
  pp_samp5.png
pp_samp5.pngjpg

We may prefer both adjustments between the extremes, eg 0.5.

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process 'paintpatches adjLcComp 0.6 adjLcPat 0.5' ^
  pp_samp_mid.png
pp_samp_mid.pngjpg

The nominal range of both adjustments is 0.0 to 1.0, but numbers outside that range can be used.

%IM7DEV%magick ^
  %SRC% ^
  prl_ear.png ^
  -process 'paintpatches adjLcPat 2.0' ^
  pp_samp_outs.png
pp_samp_outs.pngjpg

Sometimes we want to copy large patches, perhaps as large as the canvas, and then limit the patch by "patchShape Min". But this would search samples for the entire master, returning the same patch every time.

The option rectCanv blah. With this option, the most useful patchShape is Min.

Using hotspot:

%IM7DEV%magick toes.png \prose\pictures\prl_ear.png -process 'paintpatches c sample ps min rc v wt 0.1' x.png %IM7DEV%magick toes.png \prose\pictures\prl_ear.png -process 'paintpatches c sample ps min rc v wt 0.1 ac 1 ap 1' x2.png

If we have a separate subsrch image, don't we need to maintain two parallel canvases?

An alternative overall strategy: Start with patchDimension 1pxp, shape min, rc. Maybe just 1 iter. Halve the patch size.

set MASTER=barbara_test.jpg
set SAMPLES=canaletto.jpg

set PPOPT=colFrom Sample patchShape Min rectCanv maxIter 100 verbose
%IM7DEV%magick ^
  %MASTER% ^
  ( +clone -fill gray(200%%) -colorize 100 ) ^
  %SAMPLES% ^
  -process ^
    'paintpatches %PPOPT% pd 4px4p x 1' ^
  pp_pq1.png
pp_pq1.pngjpg
%IM7DEV%magick ^
  %MASTER% ^
  pp_pq1.png ^
  %SAMPLES% ^
  -process ^
    'paintpatches %PPOPT% pd 2px2p x 10' ^
  pp_pq2.png
pp_pq2.pngjpg
%IM7DEV%magick ^
  %MASTER% ^
  pp_pq2.png ^
  %SAMPLES% ^
  -process ^
    'paintpatches %PPOPT% pd 1px1p x 100' ^
  pp_pq3.png
pp_pq3.pngjpg
%IM7DEV%magick ^
  %MASTER% ^
  pp_pq3.png ^
  %SAMPLES% ^
  -process ^
    'paintpatches %PPOPT% pd 0.5px0.5p x 100' ^
  pp_pq4.png
pp_pq4.pngjpg
\web\caravaggio\I001386.jpg

Retargeting

Animation

The module can write an output at each iteration.

del pp_anim_fr*.gif

set OPTS=^
  colFrom Gradient opacity 0.7 ^
  write pp_anim_fr_%%06d.gif

%IM7DEV%magick ^
  %SRC% ^
  -process 'paintpatches %OPTS%' ^
  pp_anim1.png

gifsicle ^
  -O2 --delay 20 --loopcount=forever ^
  pp_anim_fr_*.gif >pp_anim1.gif

del pp_anim_fr*.gif
pp_anim1.pngjpg
pp_anim1.gif

Large images

Scripts

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

PaintPatches.bat

rem Paints patches by successive comparisons with a master image.
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

setlocal enabledelayedexpansion


rem ---------------------------------------------------------------
rem Check parameters.


rem For sampling another image, we have 2 inputs: one to get sub-image coords, the other to sample.
rem This is so a monochrome %ppMASTER% we can lookup a monochrome image but grab a colour sample.
rem More commonly they will be the same image.

rem Input files
set ppMASTER=mlf.png
if "%%"=="" (
  echo Requires ppMASTER
  exit /B 1
) else if not exist %ppMASTER% (
  echo Can't find file ppMASTER = %ppMASTER%
  exit /B 1
)

rem Sampled patches need ppSAMPLED.
rem They also needs ppSUBSRCH, which defaults to ppSAMPLED.
if "%ppSAMPLED%"=="" set ppSAMPLED=%ppMASTER%
if "%ppSUBSRCH%"=="" set ppSUBSRCH=%ppSAMPLED%

rem Output file
if "%ppOUTPUT%"=="" set ppOUTPUT=ppOut.png

rem If ppMARKED is given, creates an image marked with limits.

rem Temporary files.
rem
set fOnePix=%TEMP%\ppOnePix.png
set fOnePixPostProc=%TEMP%\ppOnePixPostProc.png
set fSmc=%TEMP%\ppSmc.png
set fMask=%TEMP%\ppMask.png
set COMP_RESULT_BAT=%TEMP%\ppComp.bat

rem Background is one of:
rem 0: constant colour ppBACK_COL
rem 1: %ppMASTER% overlaid with ppBACK_COL at 90% opacity
rem 2: constant colour, average of %ppMASTER%
rem 3: graduated colour using the corners of %ppMASTER%
rem 4: NYI sample
rem 5: NYI another image, so we can "fade" from one image to another
if "%ppBACK_TYPE%"=="" set /A ppBACK_TYPE=0
if "%ppBACK_COL%"=="" set ppBACK_COL=gray50

rem Patches are one of:
rem 0: squares of average required colour
rem 1: circles of average required colour
rem 2: squares made from sparse-color at corners
rem 3: circles made from squares made from sparse-color at corners
rem 4: square samples of sampled image
rem 5: circle samples of sampled image
rem 6: NYI squares of colour of closest single pixel taken from sampled image (ie a palette)
rem 7: NYI circles of colour of closest single pixel taken from sampled image (ie a palette)
if "%ppPATCH_TYPE%"=="" set ppPATCH_TYPE=0

rem Location of centre of interest (CoI), as fraction of width and height.
rem (0,0) is top-left.
rem Used for not placing small patches distant from centre.
rem Eg 0.5 for centre of image.
rem (Was 0.51,0.416)
if "%ppFracWiTest%"=="" set ppFracWiTest=0.5
if "%ppFracHtTest%"=="" set ppFracHtTest=0.5

rem Maximum radius, as fraction of shortest dimension.
if "%ppMAX_RAD_FACT%"=="" set ppMAX_RAD_FACT=1/2

rem Patches of all sizes will be drawn with centres within ppCoiLo, provided ppPatchNumFracPow=1.
rem No patches will be drawn outside ppCoiHi.
rem These are fractions of the semi-diagonal.
if "%ppCoiLo%"=="" set ppCoiLo=0.2
if "%ppCoiHi%"=="" set ppCoiHi=1.0

rem Raise PatchNumFrac to this power before trial.
rem Set to >1 to reduce number of smaller pathes drawn, or 0<ppPatchNumFracPow<1 to increase then.
if "%ppPatchNumFracPow%"=="" set ppPatchNumFracPow=1

rem When "-compare" is less than this, don't write the patch.
rem Zero = always change, up to 1 = never change.
if "%ppMaxPatchDiff%"=="" set ppMaxPatchDiff=0.2

rem Margins as fractions of patch radius. Can be negative (patches will overlap edges).
if "%ppMargFracL%"=="" set ppMargFracL=0
if "%ppMargFracT%"=="" set ppMargFracT=0
if "%ppMargFracR%"=="" set ppMargFracR=0
if "%ppMargFracB%"=="" set ppMargFracB=0

rem Gutter between patches as fractions of patch radius. Can be negative (ie patches overlap each other).
if "%ppGutterFracX%"=="" set ppGutterFracX=0
if "%ppGutterFracY%"=="" set ppGutterFracY=0

rem If ppPOST_PROC is given, post-processing of a patch before drawing it.
rem eg  set ppPOST_PROC=-alpha set -channel A -evaluate multiply 0.8

rem If non-zero, processing will stop at radius ppFLOOR_RAD (in pixels).

rem Multiplier from one radius to the next.
if "%ppRAD_MULT%"=="" set ppRAD_MULT=4/5

rem Whether to save each frame.
if "%ppSAVE_FRAMES%"=="" set ppSAVE_FRAMES=0


rem ---------------------------------------------------------------
rem Process parameters

if "%ppMASTER%"=="" (
  echo Requires ppMASTER
)

set OUT_EXT=%ppOUTPUT:*.=%

set /A SamplePatch=(ppPATCH_TYPE^^4)^|(ppPATCH_TYPE^^5)
echo SamplePatch=%SamplePatch%

FOR /F "usebackq" %%L IN (`%IMG7%magick identify ^
  -format "CoiDiff=%%[fx:%ppCoiHi%-%ppCoiLo%]" ^
  xc:`) DO set %%L

FOR /F "usebackq" %%L IN (`%IMG7%magick identify ^
  -format "Err=%%[fx:%CoiDiff%<=0]" ^
  xc:`) DO set %%L

if %Err%==1 (
  echo ppCoiLo >= ppCoiHi
  exit /B 1
)


FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "Width=%%w\nHeight=%%h" %ppMASTER%`) ^
DO set %%L

FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify ^
  -format "WiTest=%%[fx:int(%ppFracWiTest%*%Width%)]\nHtTest=%%[fx:int(%ppFracHtTest%*%Height%)]" xc:`) ^
DO set %%L

set /A SemiWidth=%Width%/2
set /A SemiHeight=%Height%/2

set /A SemiDiag2=%SemiWidth%*%SemiWidth%+%SemiHeight%*%SemiHeight%
echo SemiDiag2=%SemiDiag2%

set /A MaxRad=%WIDTH%*%ppMAX_RAD_FACT%
set /A MinRad=1

if "%ppFLOOR_RAD%"=="" set /A ppFLOOR_RAD=%MinRad%
if /I %ppFLOOR_RAD% LSS %MinRad% set /A ppFLOOR_RAD=%MinRad%

set /A nPatches=0
set /A nPatchesAdded=0

set /A nTrials=0

set /A ppPATCH_TYPE2Or3=0
set /A ppPATCH_TYPE4Or5=0

set DO_MASK=
set SaveMasterCrop=0
set PatchTypePalette=0
set PatchTypeSample=0

rem Next are not /A
rem For squares use 2, 1.
rem For circles use 17/10, 17/20.
echo ppPATCH_TYPE=%ppPATCH_TYPE%
if %ppPATCH_TYPE%==0 (
  set BlockFrac=2
  set SemiBlockFrac=1
) else if %ppPATCH_TYPE%==1 (
  set BlockFrac=17/10
  set SemiBlockFrac=17/20
) else if %ppPATCH_TYPE%==2 (
  set BlockFrac=2
  set SemiBlockFrac=1
  set SaveMasterCrop=1
  set /A ppPATCH_TYPE2Or3=1
) else if %ppPATCH_TYPE%==3 (
  set BlockFrac=2
  set SemiBlockFrac=1
  set SaveMasterCrop=1
  set DO_MASK=%fMask% -gravity NorthWest -alpha off -compose CopyOpacity -composite
  set /A ppPATCH_TYPE2Or3=1
) else if %ppPATCH_TYPE%==4 (
  set BlockFrac=2
  set SemiBlockFrac=1
  set SaveMasterCrop=1
  set /A ppPATCH_TYPE4Or5=1
  set PatchTypeSample=1
) else if %ppPATCH_TYPE%==5 (
  set BlockFrac=17/10
  set SemiBlockFrac=17/20
  set SaveMasterCrop=1
  set DO_MASK=%fMask% -gravity NorthWest -alpha off -compose CopyOpacity -composite
  set /A ppPATCH_TYPE4Or5=1
  set PatchTypeSample=1
) else if %ppPATCH_TYPE%==6 (
  set BlockFrac=2
  set SemiBlockFrac=1
  set PatchTypePalette=1
) else if %ppPATCH_TYPE%==7 (
  set BlockFrac=17/10
  set SemiBlockFrac=17/20
  set DO_MASK=%fMask% -gravity NorthWest -alpha off -compose CopyOpacity -composite
  set PatchTypePalette=1
) else (
  echo Unknown ppPATCH_TYPE=%ppPATCH_TYPE%
  exit /B 1
)

rem For type=3 we should do comparison of small square (less than circle rad)
rem then extract larger square (= circle rad) for sparse-colour gradient.


echo SaveMasterCrop=%SaveMasterCrop%

if %SaveMasterCrop%==1 (
  set SMC=^^^( -clone 0 -write %fSmc% +delete ^^^)
) else (
  set SMC=
)

echo SMC=%SMC%

rem Write a sample
if not "%ppMARKED%"=="" (

  FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify ^
-format "RadLo=%%[fx:int(sqrt(%SemiDiag2%)*%ppCoiLo%+0.5)]\nRadHi=%%[fx:int(sqrt(%SemiDiag2%)*%ppCoiHi%+0.5)]" ^
xc:`) ^
DO set /A %%L

  %IMG7%magick ^
    %ppMASTER% ^
    -fill Red ^
    -draw "line 0,%HtTest% %Width%,%HtTest%" ^
    -draw "line %WiTest%,0 %WiTest%,%Height%" ^
    -fill None ^
    -stroke Green -draw "translate %WiTest%,%HtTest% circle 0,0 0,!RadLo!" ^
    -stroke Red -draw "translate %WiTest%,%HtTest% circle 0,0 0,!RadHi!" ^
    %ppMARKED%
)


rem ---------------------------------------------------------------
rem Do the processing.

rem Count the number of radiuses we will use.

set /A Rad=%MaxRad%
set /A nRads=0

:loopCount
@set /A nRads+=1
@set PrevRad=%Rad%
@set /A Rad=%Rad%*%ppRAD_MULT%
@if %PrevRad%==%Rad% set /A Rad-=1
@if %Rad% GEQ %MinRad% goto loopCount

echo nRads=%nRads%

set /A Rad=%MaxRad%

set /A RadNum=%nRads%


%IMG7%magick -size %Width%x%Height% xc:gray50 %ppOUTPUT%

if %ppBACK_TYPE%==0 (
  %IMG7%magick -size %Width%x%Height% xc:%ppBACK_COL% %ppOUTPUT%
) else if %ppBACK_TYPE%==1 (
  rem FIXME
  %IMG7%magick ^
    %ppMASTER% ^
    ^( -size %Width%x%Height% xc:%ppBACK_COL% ^
       -alpha set -channel A -evaluate multiply 0.9 ^
    ^) ^
    -composite ^
    %ppOUTPUT%
) else if %ppBACK_TYPE%==2 (
  %IMG7%magick ^
    %ppMASTER% ^
    -scale "1x1^!" ^
    -scale "%Width%x%Height%^!" ^
    %ppOUTPUT%
) else if %ppBACK_TYPE%==3 (
  set /A Wm2=%Width%-2
  set /A Hm2=%Height%-2

  %IMG7%magick ^
    %ppMASTER% ^
    -gravity Center ^
    -chop !Wm2!x!Hm2!+0+0 ^
    -gravity Center ^
    -resize "%Width%x%Height%^!" ^
    %ppOUTPUT%

) else (
  echo Unknown ppBACK_TYPE=%ppBACK_TYPE%
  exit /B 1
)


:loopRad
FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "MargL=%%[fx:int(%ppMargFracL%*%Rad%)]\nMargT=%%[fx:int(%ppMargFracT%*%Rad%)]\nMargR=%%[fx:int(%ppMargFracR%*%Rad%)]\nMargB=%%[fx:int(%ppMargFracB%*%Rad%)]" xc:`) ^
DO set %%L

FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "RadmHalf=%%[fx:%Rad%-0.5]\nGutterX=%%[fx:int(%ppGutterFracX%*%Rad%)]\nGutterY=%%[fx:int(%ppGutterFracY%*%Rad%)]" xc:`) ^
DO set %%L

set /A TwoRad=%Rad%+%Rad%

@rem set /A nW=^(%Width%-^(%MargL%^)-^(%MargR%^)^)/^(%TwoRad%+(%GutterX%)^)+1
@rem set /A nH=^(%Height%-^(%MargT%^)-^(%MargB%^)^)/^(%TwoRad%+(%GutterY%)^)+1

FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "nW=%%[fx:int((%Width%-(%MargL%)-(%MargR%)+(%GutterX%))/(%TwoRad%+(%GutterX%)))]" xc:`) ^
DO set %%L

FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "nH=%%[fx:int((%Height%-(%MargT%)-(%MargB%)+(%GutterY%))/(%TwoRad%+(%GutterY%)))]" xc:`) ^
DO set %%L

if %nW%==0 set nW=1
if %nH%==0 set nH=1

echo nW=%nW% nH=%nH%

rem When nW and nW would not be integer we centralise the patches.

set /A LeftCent=^(^(%Width%-^(%MargL%^)-^(%MargR%^)^)-%nW%*^(%TwoRad%+^(%GutterX%^)^)+(%GutterX%^)^)/2
set /A TopCent=^(^(%Height%-^(%MargT%^)-^(%MargB%^)^)-%nH%*^(%TwoRad%+^(%GutterY%^)^)+(%GutterY%^)^)/2
echo LeftCent=%LeftCent% TopCent=%TopCent%

if %MaxRad% GTR %MinRad% (
FOR /F "usebackq" %%L IN (`%IMG7%magick identify ^
-format "CircFrac=%%[fx:(%Rad%-%MinRad%)/(%MaxRad%-%MinRad%)]" ^
xc:`) DO set %%L

FOR /F "usebackq" %%L IN (`%IMG7%magick identify ^
-format "CircFrac2=%%[fx:!CircFrac!*!CircFrac!]" ^
xc:`) DO set %%L
) else (
  set CircFrac=1
  set CircFrac2=1
)

FOR /F "usebackq" %%L IN (`%IMG7%magick identify &
-format "PatchNumFrac=%%[fx:(%RadNum%/%nRads%)^%ppPatchNumFracPow%]" ^
xc:`) DO set %%L

echo CircFrac2=%CircFrac2%

echo PatchNumFrac=%PatchNumFrac%

echo Rad=%Rad% nW=%nW% nH=%nH%

set /A nPatches+=%nW%*%nH%

@rem set /A BlockW=%TwoRad%
@rem set /A BlockH=%TwoRad%

set /A BlockW=%Rad%*%BlockFrac%
set /A BlockH=%Rad%*%BlockFrac%

set /A SemiBlockW=%Rad%*%SemiBlockFrac%
set /A SemiBlockH=%Rad%*%SemiBlockFrac%

rem We might, at this level, resize FACE and REFERENCE so we just need one pixel from each.

@set /A ChopW=%TwoRad%-2
@set /A ChopH=%TwoRad%-2

@if %ChopW% LSS 0 set /A ChopW=0
@if %ChopH% LSS 0 set /A ChopH=0

if not "%DO_MASK%"=="" (
  %IMG7%magick ^
    -size %BlockW%x%BlockH% xc:#000 ^
    -fill #fff ^
    -draw "circle %SemiBlockW%,%SemiBlockH% %SemiBlockW%,0" ^
    %fMask%
)

set EndTrials=0
set TrialedPrevRow=0

echo off
for /L %%Y in (1, 1, %nH%) do (
  set TrialedThisRow=0

  if !EndTrials!==0 (
  set /A TOP=%TopCent%+%MargT%+^(%%Y-1^)*^(%TwoRad%^+%GutterY%^)
  set /A CentY=!TOP!+%Rad%
  set /A BlockTop=!CentY!-%SemiBlockH%
  set /A BlockBot=!BlockTop!+%BlockH%
  set /A dy=!CentY!-%HtTest%
  set /A dy2=!dy!*!dy!
  for /L %%X in (1, 1, %nW%) do (
    set /A LEFT=%LeftCent%+%MargL%+^(%%X-1^)*^(%TwoRad%+%GutterX%^)
    set /A CentX=!LEFT!+%Rad%
    set /A dx=!CentX!-%WiTest%
    set /A dx2=!dx!*!dx!
    set /A FromCent2=!dx2!+!dy2!
    @rem echo CircFrac2=%CircFrac2% FromCent2=!FromCent2! SemiDiag2=%SemiDiag2%

    rem echo PatchNumFrac=%PatchNumFrac%  FromCent2=!FromCent2!  SemiDiag2=%SemiDiag2%

    FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "TRIAL_IT=%%[fx:%PatchNumFrac%^>(sqrt(!FromCent2!/%SemiDiag2%)-%ppCoiLo%)/%CoiDiff%]" xc:`) DO set /A %%L

    rem echo TRIAL_IT=!TRIAL_IT!

    if !TRIAL_IT!==1 (
      set TrialedThisRow=1
      set /A nTrials+=1
      echo Rad=%Rad% X=%%X Y=%%Y LEFT=!LEFT! TOP=!TOP! SemiDiag2=%SemiDiag2% FromCent2=!FromCent2!
      set /A BlockLeft=!CentX!-%SemiBlockW%
      set /A BlockRight=!BlockLeft!+%BlockW%

      set ONE_CROP=-crop %BlockW%x%BlockH%+!BlockLeft!+!BlockTop! +repage
      echo !ONE_CROP!

      set PATCH_DIFF=0
      FOR /F "usebackq" %%L IN (`%IMG7%magick %ppMASTER% %ppOUTPUT% !ONE_CROP! -metric RMSE -compare -format "PATCH_DIFF=%%[distortion]" info:`) DO set %%L
      FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "CHANGE_IT=%%[fx:!PATCH_DIFF!^>%ppMaxPatchDiff%]" xc:`) DO set /A %%L

      @rem echo PATCH_DIFF=!PATCH_DIFF!

      if !CHANGE_IT!==1 (
        %IMG7%magick %ppMASTER% !ONE_CROP! %SMC% -scale 1x1 -write %fOnePix% %ppPOST_PROC% %fOnePixPostProc%

        if %ppPATCH_TYPE%==0 (
          %IMG7%magick %ppOUTPUT% -tile %fOnePixPostProc% -draw "rectangle !BlockLeft!,!BlockTop! !BlockRight!,!BlockBot!" %ppOUTPUT%
        ) else if %ppPATCH_TYPE%==1 (
          FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "CentXmHalf=%%[fx:!CentX!-0.5]\nCentYmHalf=%%[fx:!CentY!-0.5]" xc:`) DO set %%L
          %IMG7%magick %ppOUTPUT% -tile %fOnePixPostProc% -draw "circle !CentXmHalf!,!CentYmHalf! !CentXmHalf!,!TOP!" %ppOUTPUT%
        ) else if %ppPATCH_TYPE2Or3% NEQ 0 (

@rem If chop isn't possible (because crop ran over edge of picture), chop doesn't happen.

          FOR /F "usebackq" %%L ^
IN (`%IMG7%magick identify -format "smcW=%%[fx:w]\nsmcWm2=%%[fx:w-2]\nsmcH=%%[fx:h]\nsmcHm2=%%[fx:h-2]" %fSmc%`) ^
DO set %%L

          if !smcWm2! LSS 0 set !smcWm2!=0
          if !smcHm2! LSS 0 set !smcHm2!=0

@rem echo smcW=!smcW! smcWm2=!smcWm2! smcH=!smcH! smcHm2=!smcHm2!

@rem But position is then wrong if overlapped left or top edge

          if !BlockLeft! LSS 0 set BlockLeft=0
          if !BlockTop! LSS 0 set BlockTop=0

          %IMG7%magick ^
            %ppOUTPUT% ^
            ^( %fSmc% -gravity Center ^
               -chop !smcWm2!x!smcHm2!+0+0 ^
               -resize "!smcW!x!smcH!^!" ^
               %DO_MASK% ^
               %ppPOST_PROC% ^
            ^) ^
            -gravity NorthWest ^
            -geometry +!BlockLeft!+!BlockTop! ^
            -compose Over -composite ^
            %ppOUTPUT%
        ) else if %PatchTypeSample%==1 (
          call %PICTBAT%srchImg %ppSUBSRCH% %fSmc% %COMP_RESULT_BAT%
          call %COMP_RESULT_BAT%

          FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "CHANGE_IT=%%[fx:!COMP_FLT!^<!PATCH_DIFF!]" xc:`) DO set /A %%L

          if !CHANGE_IT!==1 %IMG7%magick ^
            %ppOUTPUT% ^
            ^( %ppSAMPLED% -crop !COMP_CROP! +repage %DO_MASK% %ppPOST_PROC% ^) ^
            -gravity NorthWest ^
            -geometry +!BlockLeft!+!BlockTop! ^
            -compose Over -composite ^
            %ppOUTPUT%
        ) else if %PatchTypePalette%==1 (

          FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^
IN (`%IMG7%magick compare ^
    -similarity-threshold 0 -dissimilarity-threshold 1 -subimage-search ^
    -metric RMSE %ppSUBSRCH% %fOnePix% NULL: 2^>^&1`) ^
DO (
    set COMP_INT=%%R
    set COMP_FLT=%%S
    set COMP_XW=%%T
    set COMP_YW=%%U
  )

          echo Palette: !COMP_INT! !COMP_FLT! !COMP_XW! !COMP_YW!

          FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "CHANGE_IT=%%[fx:!COMP_FLT!^<!PATCH_DIFF!]" xc:`) DO set /A %%L

          if !CHANGE_IT!==1 %IMG7%magick ^
            %ppOUTPUT% ^
            ^( %ppSAMPLED% -crop 1x1+!COMP_XW!+!COMP_YW! +repage -scale %TwoRad%x%TwoRad% %DO_MASK% %ppPOST_PROC% ^) ^
            -gravity NorthWest ^
            -geometry +!BlockLeft!+!BlockTop! ^
            -compose Over -composite ^
            %ppOUTPUT%

        ) else (
          echo Unknown ppPATCH_TYPE=%ppPATCH_TYPE%
          exit /B 1
        )

        if !CHANGE_IT!==1 (
          set /A nPatchesAdded+=1
          echo nPatchesAdded: !nPatchesAdded!
          if %ppSAVE_FRAMES%==1 (
            set frNum=00000!nPatchesAdded!
            set frNum=!frNum:~-6!
            copy /y %ppOUTPUT% %ppSAVE_FRAMES_PREFIX%!frNum!.%OUT_EXT%>nul:
          )
        )
      )
    )
  )
  rem EndTrials = (TrialedPrevRow==1) && (TrialledThisRow==0)
  @rem if %TrialedPrevRow%==1 set EndTrials==1 if %TrialedThisRow%==0 set EndTrials==1
  if !TrialedPrevRow!==1 if !TrialedThisRow!==0 set EndTrials=1
  echo TrialedPrevRow = !TrialedPrevRow! TrialedThisRow = !TrialedThisRow! EndTrials = !EndTrials!
  set TrialedPrevRow=!TrialedThisRow!
  )
)
echo on

set /A RadNum-=1
echo RadNum=%RadNum%

set PrevRad=%Rad%
set /A Rad=%Rad%*%ppRAD_MULT%
if %PrevRad%==%Rad% set /A Rad-=1

echo nPatches: %nPatches%  nTrials: %nTrials%  nPatchesAdded: %nPatchesAdded%

if %Rad% GEQ %ppFLOOR_RAD% goto loopRad

echo PaintPatches finished: created %ppOUTPUT%

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

%IMG7%magick -version
Version: ImageMagick 7.1.0-49 Q16-HDRI x64 7a3f3f1:20220924 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenCL 
Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib
Compiler: Visual Studio 2022 (193331630)

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


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 v1.0 30-August-2017.

Page created 17-Mar-2023 20:46:45.

Copyright © 2023 Alan Gibson.