A process for building images.
This is closely related to Quilting.
An artist paints someone's portrait by an iterative process:
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.
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:
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:
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.pngThe 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.pngWith 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.pngThe 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.
%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 |
|
Default settings. %IM7DEV%magick ^ %SRC% ^ -process 'paintpatches' ^ pp_ex1.png |
Vary colFrom:
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Avg' ^ pp_avg.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Cent' ^ pp_cent.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Gradient' ^ pp_grad.png |
Vary patchShape:
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches patchShape Rect' ^ pp_rect.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches patchShape Ellipse' ^ pp_ell.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches patchShape Min' ^ pp_min.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches rectCanv patchShape Min' ^ pp_rc.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches c Cent rectCanv patchShape Min' ^ pp_rc2.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches c Avg rectCanv patchShape Min' ^ pp_rc3.png |
A lower opacity:
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Avg opacity 0.7' ^ pp_avgo.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Cent opacity 0.7' ^ pp_cento.png |
|
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches colFrom Gradient opacity 0.7' ^ pp_grado.png |
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 |
%IM7DEV%magick ^ %SRC% ^ -process 'paintpatches' ^ pp_srgb.png |
|
%IM7DEV%magick ^ %SRC% ^ -colorspace RGB ^ -process 'paintpatches' ^ -colorspace sRGB ^ pp_rgb.png |
|
%IM7DEV%magick ^ %SRC% ^ -colorspace Lab ^ -process 'paintpatches' ^ -colorspace sRGB ^ pp_lab.png |
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 |
|
%IM7DEV%magick ^ %SRC% ^ pp_ic1.png ^ -process 'paintpatches pd 20c,20c o 0.5 ps Ellipse' ^ pp_ic2.png |
|
%IM7DEV%magick ^ %SRC% ^ pp_ic2.png ^ -process 'paintpatches pd 10c,10c o 0.5 ps Ellipse' ^ pp_ic3.png |
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.
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.pngFor 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 |
For example, with default settings:
%IM7DEV%magick ^ %SRC% ^ prl_ear.png ^ -process 'paintpatches' ^ pp_samp1.png |
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. |
|
We tame the default result by adjusting each found patch. %IM7DEV%magick ^ %SRC% ^ prl_ear.png ^ -process 'paintpatches adjLcPat 1.0' ^ pp_samp3.png |
|
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 |
|
%IM7DEV%magick ^ %SRC% ^ prl_ear.png ^ -process ^ 'paintpatches adjLcComp 1.0 adjLcPat 1.0 ps min' ^ pp_samp5.png |
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 |
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 |
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.pngIf 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 |
|
%IM7DEV%magick ^ %MASTER% ^ pp_pq1.png ^ %SAMPLES% ^ -process ^ 'paintpatches %PPOPT% pd 2px2p x 10' ^ pp_pq2.png |
|
%IM7DEV%magick ^ %MASTER% ^ pp_pq2.png ^ %SAMPLES% ^ -process ^ 'paintpatches %PPOPT% pd 1px1p x 100' ^ pp_pq3.png |
|
%IM7DEV%magick ^ %MASTER% ^ pp_pq3.png ^ %SAMPLES% ^ -process ^ 'paintpatches %PPOPT% pd 0.5px0.5p x 100' ^ pp_pq4.png |
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 |
|
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
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.