We can build an image by quilting together ragged-cut rectangles from a texture image.
The page is heavily based on Image Quilting for Texture Synthesis and Transfer, Alexei A. Efros and William T. Freeman, 2001. My script can select a rectangle at random from the input texture, or the rectangle that best matches the overlap area that has already been constructed, as measured by lowest RMSE score. As the overlap area is generally L-shaped, I implemented the measurement by a process module (called rmsealpha) that ignores transparent pixels.
See also Efros and Freeman Image Quilting Algorithm for Texture Synthesis, Lara Raad, Bruno Galerne, 2017.
The minimum error boundary cut (MEBC) described by Efros and Freeman is implemented in another process module, darkestpath. See also the Dark paths page. For the process modules, see Process modules page.
Texture can be extended by repeating the image, as shown in Tiling with dark paths. The repetition is obvious to human eyes.
But we might want a process that take an image of some texture, and extends it to any size we want, such that we don't get obvious repeating patterns, and we can't easily see the join between the patches. (There will certainly be repeats; they may or may not form patterns.)
As an example input, we use:
set SRC=dpt_lvs_sm.jpg |
The script mkQuilt.bat creates a blank output canvas conceptually divided into a grid pattern of equal-sized overlapping patches. For each output patch area, it makes an input patch from a texture image, The input patch is either the best match for pixels that already overlap the output location, or is simply a random patch. It pastes this patch onto the output using the script mebcOne.bat to create minimum error boundary cuts at the top and left edges of the pasted patch.
Generating random coordinates is much faster than searching for the best match. In addition, the search method wastes time reconstructing the texture image in the top-left of the result, one patch at a time.
(The Efros and Freeman paper finds all texture patches that closely match the overlapping area, within 0.1 of the best match. Then it chooses one of those patches at random. This avoids replicating the texture image at top-left of the output.)
The script calls magick multiple times for each patch, so it isn't fast.
Make a large image of leaves
call %PICTBAT%mkQuilt ^ %SRC% qlt_q1.png ^ . . 6x6 . random |
|
Using an input from the Efros and Freeman paper: call %PICTBAT%mkQuilt ^ qlt_apples.png qlt_apples_q1.png ^ . . 8x8 . search |
This process is similar, but instead of a blank canvas, it uses a reference image. Each patch of this reference is replaced by the most suitable patch from the texture.
As an example reference image, we will use:
set REF=mona600.png |
The script mkQuiltLike.bat copies the reference (which here is Mona Lisa) to a canvas, slightly extending it to allow for an integral number of patches. We extend with a viewport on a null "-distort SRT" to replicate pixels from the edges. For each patch on this canvas, it finds the best match in a texture image. The patches on the canvas overlap, so most patches searched for will already contain some leaf fragments, overlaps from prevous patches.
To get a reasonable chance of capturing Lisa's features, we need a small patch size. Each leaf is about 20 pixels across, so we don't want the patch size less overlap to be smaller than 20. If we accept the default overlap of 33%, this requires the patch size to be at least 30 pixels.
call %PICTBAT%mkQuiltLike ^ dpt_lvs_sm.jpg ^ mona600.png ^ qlt_monalvs1.png ^ 30x30 |
Well, it works, but the result isn't very leaf-like. It has used the same patch of leaf multiple times where the painting changes only gradually. The sky and flesh have been simplified to become the same leaf colour. And there isn't a wide palette to choose from. We can widen the palette by using an image from Assembling with dark paths that appends variations of the leaf image into a single image, with MEBC in case a patch that straddles boundaries is chosen.
call %PICTBAT%mkQuiltLike ^ dpa_ass1.png ^ mona600.png ^ qlt_monalvs2.png ^ 30x30 |
|
As previous, but larger patches. call %PICTBAT%mkQuiltLike ^ dpa_ass1.png ^ mona600.png ^ qlt_monalvs3.png ^ 60x60 |
Smaller patch sizes create results more like Lisa. Larger patch sizes create results more like leaves.
To paint Lisa with bricks, we select a photo of a brick wall with virtually no geometric distortion. We stretch the colours to give the matching process some variety to choose from. The texture needs to have at least as much colour variety as the reference, to avoid many texture patches capping the limits on output patches. Ideally, the gamut of the reference should be a subset of the texture gamut.
set BRICK_WALL=%PICTLIB%20151008\2607_wall.tiff %IMG7%magick ^ %BRICK_WALL% ^ -contrast-stretch 5%%x5%% ^ -resize 5%% ^ qlt_brcks.miff |
We want the bricks to line up. In fact, I insist on this, even if this reduces the imitation. I want the result to look like a real brick wall. The bricks are 24.414285 pixels between centres horizontally, and 16.25862 between two centres vertically. (We need to capture two courses of bricks, or the pattern will be broken.) So we tell the script mkQuiltLike.bat to snap to this grid. It will copy patches from the texture at multiples of this grid, and also paste patches to the new canvas at multiples of integers of this grid.
set mqlSNAP_X=24.414285 set mqlSNAP_Y=16.25862 call %PICTBAT%mkQuiltLike ^ qlt_brcks.miff ^ mona600.png ^ qlt_mona_brk.jpg ^ %mqlSNAP_X%+10x%mqlSNAP_Y%+7 ^ 10x7 set mqlSNAP_X= set mqlSNAP_Y= |
She has the ghost of a smile. The alignment of brick courses isn't perfect because the texture photo isn't a perfect grid.
(I'd like a script to automatically determine whether an image has a repeating pattern. If it has, I'd like it to tell me the pattern intervals. Ideally, it could also do any minor corrections so the intervals were constant. This is a difficult problem. Hough lines might help towards a solution for the brick wall.)
Most patches have an MEBC at the top and left edges. The top MEBC usually follows the horizontal mortar line between bricks, so isn't obvious. The left has to cut through at least one brick, so is sometimes obvious.
Leonardo painted detail in Lisa's facial features, and less detail in the landscape. We might follow his example, and try using sub-brick sampling, with suitable masks.
More simply, we can just use smaller bricks:
set BRICK_WALL=%PICTLIB%20151008\2607_wall.tiff %IMG7%magick ^ %BRICK_WALL% ^ -contrast-stretch 5%%x5%% ^ -resize 2.5%% ^ qlt_brcks_sm.miff set mqlSNAP_X=12.2071425 set mqlSNAP_Y=8.12931 call %PICTBAT%mkQuiltLike ^ qlt_brcks_sm.miff ^ mona600.png ^ qlt_mona_brk_sm.jpg ^ %mqlSNAP_X%+5x%mqlSNAP_Y%+3 ^ 5x3 set mqlSNAP_X= set mqlSNAP_Y= |
Aliasing of patch selection shows in the cheek on the left. This suggests another approach: texture transfer can be regarded as a remapping problem, and dithering can accumulate errors so they can be corrected, giving better overall accuracy.
For a closer imitation of the reference (but less fidelity to the texture), we can apply the Gain and bias method by setting mqlGAIN_BIAS to 1.
set mqlSNAP_X=24.414285 set mqlSNAP_Y=16.25862 set mqlGAIN_BIAS=1 call %PICTBAT%mkQuiltLike ^ qlt_brcks.miff ^ mona600.png ^ qlt_mona_brkgb.jpg ^ %mqlSNAP_X%+10x%mqlSNAP_Y%+7 ^ 10x7 set mqlGAIN_BIAS= set mqlSNAP_X= set mqlSNAP_Y= |
Gravel is a handy texture:
%IMG7%magick ^ %PICTLIB%20151113\AGA_2831_sRGB.tiff ^ -gravity NorthEast -crop 50%%x50%%+0+0 ^ -gravity None ^ +repage ^ -sigmoidal-contrast 5x50%% ^ -modulate 100,150,100 ^ -resize 300x300 ^ -strip ^ +write qlt_gravel.tiff ^ qlt_gravel.jpg |
|
Create some variety. %IMG7%magick ^ qlt_gravel.tiff ^ +write qlt_tmp_1.miff ^ ( +clone ^ -sigmoidal-contrast 5,25%% ^ +write qlt_tmp_0.miff ^ +delete ) ^ ( +clone ^ -sigmoidal-contrast 5,50%% ^ +write qlt_tmp_2.miff ^ +delete ) ^ ( +clone ^ -sigmoidal-contrast 5,75%% ^ +write qlt_tmp_3.miff ^ +delete ) ^ NULL: call %PICTBAT%mebcTile ^ qlt_tmp_XX.miff ^ 4 1 10c 10c ^ qlt_grav_m.miff del qlt_tmp_?.miff %IMG7%magick ^ +write qlt_tmp_0.miff ^ ( +clone ^ -modulate 100,100,90 ^ +write qlt_tmp_1.miff ^ +delete ) ^ ( +clone ^ -modulate 100,100,110 ^ +write qlt_tmp_2.miff ^ +delete ) ^ call %PICTBAT%mebcTile ^ qlt_tmp_XX.miff ^ 1 3 10c 10c ^ qlt_grav_m.miff del qlt_tmp_?.miff |
|
call %PICTBAT%mkQuiltLike ^ qlt_grav_m.miff ^ mona600.png ^ qlt_mona_grv.jpg ^ 30x30 |
This process uses a third input image: a proxy for the texture. It creates two outputs: the same output as mkQuiltLike.bat, and a second output where the pixels are copied from a stand-in for the texture file. Eg: a heavily blurred version of the bricks.
The processes above are slow, as they execute multiple converts for each patch. The process module Pixel match can perhaps be used to massively increase performance. A process module could be written to perform all the work, without needing to re-run multiple converts and read/write for every patch.
The script mkQuilt.bat can select random texture patches for pasting. For added variety, patches cold be randomly flipped/flopped. However, this might cause shadow inconsistency.
The script mkQuiltLike.bat currently searches for a texture patch that most closely matches an area in the canvas as it currently stands, which will be part of the reference image overlaid with overlaps from previously copied texture patches. This could be changed to allow a weighting for the previous overlap, and this weighting could be zero. The result could then more closely resemble the reference, at the expense of more split bricks. There is always a tradeoff: should the result look more like a brick wall, or more like the Mona Lisa? (A mechanism could be to get the corresponding patch from the reference, and composite-blend this over the canvas patch before finding a match.)
The scripts mkQuilt.bat and mkQuiltLike.bat could generate displacement maps, although the MEBCs would make this difficult.
Dithering could be added to scripts mkQuilt.bat etc. When we have found the closest texture patch, compare that to the corresponding patch from reference, and add the error to the next cavas patch before its search. For example, if the reference patch was lighter than its replacement texture patch, we need to make the canvas patch lighter before its search. Dithering horizontally is easy; dithering verticaly is less easy.
It might be interesting to ensure that each texture patch is used only once, so that the texture image of gravel is rearranged to resemble the Mona Lisa. In itself, this is easy: when a texture patch is copied, make it transparent in the texture image so it won't be matched again. More difficult: as each patch can be used only once, we want to ensure it is used in the place it is most effective. More globally, we want to minimise the RMSE of the entire image. See PatchMatch: A Randomized Correspondence Algorithm for Structural Image Editing, Connelly Barnes, Eli Shechtman, Adam Finkelstein, and Dan B Goldman, 2009, and other papers.
We might also consider identifying the important parts of an image, (a "saliency" map) eg Lisa's facial features. Find the best patches for these first. This map could also be used to modulate the gain-bias effect.
%IMG7%magick ^ mona600.jpg ^ -blur 0x1 ^ qlt_mona_blr.miff call %PICTBAT%slopeMag ^ qlt_mona_blr.miff ^ qlt_mona_sm.miff %IMG7%magick ^ qlt_mona_sm.miff ^ -scale "172x200^!" ^ -scale "517x600^!" ^ -auto-level ^ -evaluate Pow 2 ^ qlt_mona_pri.miff |
This would give us a priority order for the patches.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
rem From %1 texture image, make quilt. @rem %2 output file. @rem %3 width and height of each patch (including overlap) [default one-third] @rem %4 width and height of overlaps [default 30 percent of patch size] @rem %5 number of columns and rows of patches [default 4x4] @rem %6 coordinates within input texture for first patch [default 0,0] @rem %7 method for choosing patch from texture: "random" or "search". Default: "random". @rem @rem %3 to %6 are each of format XxY @rem where X and Y are both optional integers each optionally suffixed with one of %cp. @rem @rem Future: For %6, take strings "random" or "centre". @rem @rem Updated: @rem 14-July-2022 for IM v7. Added %7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 mq if not "%2"=="" set OUTFILE=%2 set TEMP_IN=mq_%INNAME%_in.miff set TEMP_OUT=mq_%INNAME%_out.miff set TEMP_PATCH=mq_%INNAME%_patch.miff set TEMP_PATCH2=mq_%INNAME%_patch2.miff set WW= for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h" ^ %INFILE%`) do set %%L if "%WW%"=="" exit /B 1 call parseXxY 33.3333c 33.3333c %3 set patchW=%pxxyX% set patchH=%pxxyY% call xyCoord %WW% %HH% %patchW% %patchH% set patchW=%xycXi% set patchH=%xycYi% call parseXxY 33.3333c 33.3333c %4 set overX=%pxxyX% set overY=%pxxyY% call xyCoord %patchW% %patchH% %overX% %overY% set overX=%xycXi% set overY=%xycYi% call parseXxY 4 4 %5 set patchCols=%pxxyX% set patchRows=%pxxyY% call parseXxY 0 0 %6 set firstX=%pxxyX% set firstY=%pxxyY% set chText=%7 if "%chText%"=="." set chText= if "%chText%"=="" set chText=random if /I %chText%==search ( set sCHTEXT=s ) else if /I %chText%==random ( set sCHTEXT=r ) else ( echo %0: Bad chText [%chText%] exit /B 1 ) set /A outW=%patchW%*%patchCols%-%overX%*(%patchCols%-1) set /A outH=%patchH%*%patchRows%-%overY%*(%patchRows%-1) echo %0: patchWH=%patchW%x%patchH% overXY=%overX%x%overY% patchCR=%patchCols%x%patchRows% firstXY=%firstX%x%firstY% echo %0: %OUTFILE% out=%outW%x%outH% if %outW% LSS 1 ( echo %0: outW=%outW% exit /B 1 ) if %outH% LSS 1 ( echo %0: outH=%outH% exit /B 1 ) %IMG7%magick ^ %INFILE% ^ +write %TEMP_IN% ^ -crop %patchW%x%patchH%+%firstX%+%firstY% +repage ^ -background None ^ -extent %outW%x%outH% ^ %TEMP_OUT% set /A nColm1=%patchCols%-1 set /A nRowm1=%patchRows%-1 set FIRST=1 set siDELTEMPSRC=1 set siDELTEMPSUB=1 set siCOMP_FLT= set siCOMP_CROP= for /L %%R in (0,1,%nRowm1%) do ( set /A rowY=%%R*^(%patchH%-%overY%^) for /L %%C in (0,1,%nColm1%) do ( if !FIRST!==1 ( set FIRST=0 ) else ( set /A colX=%%C*^(%patchW%-%overX%^) rem echo %0: rowY=!rowY! colX=!colX! if %sCHTEXT%==s ( for /F "usebackq tokens=1-2 delims= " %%A in (`%IM7DEV%magick ^ %TEMP_IN% ^ ^( %TEMP_OUT% ^ -crop %patchW%x%patchH%+!colX!+!rowY! +repage ^ ^) -process 'rmsealpha multi_scale stdout' ^ NULL:`) do ( if "%%A"=="rmsealphaCrop:" set siCOMP_CROP=%%B ) rem call %PICTBAT%srchImgAlpha %TEMP_IN% %TEMP_PATCH% ) else ( for /F "usebackq" %%L in (`%IMG7%magick xc: ^ -format "siCOMP_CROP=%patchW%x%patchH%+%%[fx:int((%WW%-%patchW%)*rand())]+%%[fx:int((%HH%-%patchH%)*rand())]" ^ info:`) do set %%L ) echo %0: siCOMP_FLT=!siCOMP_FLT! siCOMP_CROP=!siCOMP_CROP! %IMG7%magick ^ %TEMP_IN% ^ -crop !siCOMP_CROP! +repage ^ -repage +!colX!+!rowY! ^ %TEMP_PATCH2% call %PICTBAT%mebcOne %TEMP_OUT% %TEMP_PATCH2% %overX% %overY% ) ) ) set siDELTEMPSRC= set siDELTEMPSUB= %IMG7%magick %TEMP_OUT% %OUTFILE% call echoRestore endlocal & set mqOUTFILE=%OUTFILE%
rem From %1 texture image and %2 reference, make quilt like the reference. @rem %3 output file. @rem %4 width and height of each patch (including overlap) [default one-third] @rem %5 width and height of overlaps [default 30 percent of patch size] @rem @rem %4 and %5 are each of format XxY @rem where X and Y are both optional numbers each optionally suffixed with one of %cp. @rem @rem Also uses: @rem mqlGAIN_BIAS if 1, adjusts gain and bias of texture patch to match canvas. @rem mqlSNAP_X if set, snaps horizontally to multiple of this (pixels, can be fractional). @rem mqlSNAP_Y likewise, vertically @rem mqlFRAME_FILE if set, writes one frame per patch. @rem XX in filename will be sustituted by a frame number with leading zeros, 6 digits. @rem Eg mql_fr_XX.png will create frames named mql_fr_000000.png, mql_fr_000001.png etc. @rem First and last frames (all reference, and all texture) will be included. @rem @rem Future: For %7, take strings "random" or "centre" or "bottom-right". @rem Future: Use proxies for texture and reference. @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 mql set REF_IMG=%2 if not "%3"=="" set OUTFILE=%3 set TMP_EXT=.miff set TEMP_IN=mq_%INNAME%_in%TMP_EXT% set TEMP_OUT=mq_%INNAME%%TMP_EXT% set TEMP_PATCH=mq_%INNAME%_patch%TMP_EXT% set TEMP_PATCH2=mq_%INNAME%_patch2%TMP_EXT% set TEX_WW= for /F "usebackq" %%L in (`%IMG7%magick ^ %INFILE% ^ +write %TEMP_IN% ^ -format "TEX_WW=%%w\nTEX_HH=%%h" ^ info:`) do set %%L if "%TEX_WW%"=="" exit /B 1 call parseXxY 33.3333c 33.3333c %4 set patchW=%pxxyX% set patchH=%pxxyY% call xyCoord %TEX_WW% %TEX_HH% %patchW% %patchH% set patchW=%xycX% set patchH=%xycY% set patchWi=%xycXi% set patchHi=%xycYi% call parseXxY 33.3333c 33.3333c %5 set overX=%pxxyX% set overY=%pxxyY% call xyCoord %patchW% %patchH% %overX% %overY% set overX=%xycXi% set overY=%xycYi% echo %0: patchWH=%patchW%x%patchH% patchWHi=%patchWi%x%patchHi% overXY=%overX%,%overY% set REF_WW= for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "REF_WW=%%w\nREF_HH=%%h\n" ^ %REF_IMG%`) do set %%L if "%REF_WW%"=="" exit /B 1 echo %0: REF_WW=%REF_WW% REF_HH=%REF_HH% set CALC=^ patchCols=%%[fx:ceil((%REF_WW%-%overX%)/(%patchWi%-%overX%))]\n^ patchRows=%%[fx:ceil((%REF_HH%-%overY%)/(%patchHi%-%overY%))] for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "%CALC%" ^ xc:`) do set %%L set CALC=^ texCols=%%[fx:ceil((%TEX_WW%-%overX%)/(%patchWi%-%overX%))]\n^ texRows=%%[fx:ceil((%TEX_HH%-%overY%)/(%patchHi%-%overY%))] for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "%CALC%" ^ xc:`) do set %%L echo %0: patchCols=%patchCols% patchRows=%patchRows% texCols=%texCols% texRows=%texRows% rem set /A outW=%patchWi%*%patchCols% rem if %patchCols% GTR 1 set /A outW-=%overX% rem rem set /A outH=%patchHi%*%patchRows% rem if %patchRows% GTR 1 set /A outH-=%overY% rem set /A outW=%patchWi%*%patchCols%-%overX%*(%patchCols%-1) rem set /A outH=%patchHi%*%patchRows%-%overY%*(%patchRows%-1) set CALC=^ overXi=%%[fx:int(%overX%+0.5)]\n^ overYi=%%[fx:int(%overY%+0.5)]\n^ outW=%%[fx:ceil^(%patchWi%*%patchCols%-%overX%*^(%patchCols%-1^)^)]\n^ outH=%%[fx:ceil^(%patchHi%*%patchRows%-%overY%*^(%patchRows%-1^)^)] for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "%CALC%" ^ xc:`) do set %%L echo outW=%outW% outH=%outH% echo %0: patchWH=%patchW%x%patchH% overXY=%overX%,%overY% patchRC=%patchCols%x%patchRows% echo %0: %OUTFILE% outWH=%outW%x%outH% if %outW% LSS 1 ( echo outW=%outW% exit /B 1 ) if %outH% LSS 1 ( echo outH=%outH% exit /B 1 ) %IMG7%magick ^ %REF_IMG% ^ -virtual-pixel Edge ^ -define distort:viewport=%outW%x%outH% ^ -distort SRT 1,0 +repage ^ %TEMP_OUT% set /A nColm1=%patchCols%-1 set /A nRowm1=%patchRows%-1 rem Last row/column of texture may be incomplete, so never match it. set /A nTexColm2=%texCols%-2 set /A nTexRowm2=%texRows%-2 set siDELTEMPSRC=1 set siDELTEMPSUB=1 if not "%mqlFRAME_FILE%"=="" ( call :getFrameName 000000 echo FRAME_NAME=!FRAME_NAME! %IMG7%magick %TEMP_OUT% !FRAME_NAME! ) if "%mqlGAIN_BIAS%"=="1" ( set wrTMP_PATCH=+write %TEMP_PATCH% ) else ( set wrTMP_PATCH= ) set FRAME_NUM=1 for /L %%R in (0,1,%nRowm1%) do ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "rowY=%%[fx:int(%%R*(%patchHi%-%overY%)+0.5)]" ^ xc:`) do set %%L for /L %%C in (0,1,%nColm1%) do ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "colX=%%[fx:int(%%C*(%patchWi%-%overX%)+0.5)]" ^ xc:`) do set %%L echo %0: rowY=!rowY! colX=!colX! for /F "usebackq tokens=1-5 delims=x+ " %%A in (`%IM7DEV%magick ^ %TEMP_IN% ^ ^( %TEMP_OUT% ^ -crop %patchWi%x%patchHi%+!colX!+!rowY! +repage ^ %wrTMP_PATCH% ^ ^) ^ -process 'rmsealpha multi_scale stdout' ^ NULL:`) do ( if "%%A"=="rmsealphaCrop:" ( set siSUB_W=%%B set siSUB_H=%%C set siCOMP_XW=%%D set siCOMP_YW=%%E set siCOMP_CROP=!siSUB_W!x!siSUB_H!+!siCOMP_XW!+!siCOMP_YW! ) ) rem call %PICTBAT%srchImgAlpha %TEMP_IN% %TEMP_PATCH% echo %0: siCOMP_FLT=!siCOMP_FLT! siCOMP_CROP=!siCOMP_CROP! in bits !siSUB_W!x!siSUB_H!+!siCOMP_XW!+!siCOMP_YW! if "!siCOMP_XW!"=="" exit /B 1 if not !siSUB_W!==%patchWi% echo bad W !siSUB_W!==%patchWi% if not !siSUB_H!==%patchHi% echo bad H !siSUB_H!==%patchHi% if not "%mqlSNAP_X%"=="" ( set CALC=^ siCOMP_XW=%%[fx:int^(int^(!siCOMP_XW!/^(%mqlSNAP_X%^)+0.5^)*^(%mqlSNAP_X%^)+0.5^)] set CALC=^ siCOMP_XW=%%[fx:NN=int^(!siCOMP_XW!/^(%mqlSNAP_X%^)+0.5^);NN=NN^>%nTexColm2%?%nTexColm2%:NN;int^(NN*^(%mqlSNAP_X%^)+0.5^)] for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "!CALC!" ^ xc:`) do set %%L ) if not "%mqlSNAP_Y%"=="" ( set CALC=^ siCOMP_YW=%%[fx:int^(int^(!siCOMP_YW!/^(%mqlSNAP_Y%^)+0.5^)*^(%mqlSNAP_Y%^)+0.5^)] set CALC=^ siCOMP_YW=%%[fx:NN=int^(!siCOMP_YW!/^(%mqlSNAP_Y%^)+0.5^);NN=NN^>%nTexRowm2%?%nTexRowm2%:NN;int^(NN*^(%mqlSNAP_Y%^)+0.5^)] for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "!CALC!" ^ xc:`) do set %%L ) set sCROP=!siSUB_W!x!siSUB_H!+!siCOMP_XW!+!siCOMP_YW! echo sCROP=!sCROP! %IMG7%magick ^ %TEMP_IN% ^ -crop !sCROP! +repage ^ -repage +!colX!+!rowY! ^ %TEMP_PATCH2% if "%mqlGAIN_BIAS%"=="1" call %PICTBAT%imgGainBias %TEMP_PATCH2% %TEMP_PATCH% %TEMP_PATCH2% call %PICTBAT%mebcOne %TEMP_OUT% %TEMP_PATCH2% %overXi% %overYi% if not "%mqlFRAME_FILE%"=="" ( set LZ=000000!FRAME_NUM! set LZ=!LZ:~-6! call :getFrameName !LZ! echo FRAME_NAME=!FRAME_NAME! %IMG7%magick %TEMP_OUT% !FRAME_NAME! set /A FRAME_NUM+=1 ) set siDELTEMPSRC=0 ) ) set siDELTEMPSRC= set siDELTEMPSUB= %IMG7%magick ^ %TEMP_OUT% ^ -crop %REF_WW%x%REF_HH%+0+0 +repage ^ %OUTFILE% echo %0: Written %OUTFILE% call echoRestore @endlocal & set mqlOUTFILE=%OUTFILE% @goto :eof rem ----------------- Subroutines ------------------------- :getFrameName set FRAME_NAME=!mqlFRAME_FILE:XX=%1!
rem Called from mebcAssemble.bat and mkQuilt.bat, and others. rem Given %1 is input/output canvas, rem %2 is image patch to be merged onto canvas, with the required canvas offset, rem %3 is horizontal overlap in pixels rem %4 is vertical overlap in pixels rem merges images possibly with Minimum Error Boundary Cut along top edge or left edge or both. @rem @rem Updated: @rem 14-July-2022 for IM v7. @rem @rem FUTURE: accept parameters for offsets, then no need to "identify". @rem FUTURE: make the blur an environment variable. @setlocal enabledelayedexpansion set CANV=%1 set IMG=%2 set overX=%3 set overY=%4 for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\noffX=%%X\noffY=%%Y" ^ %IMG%`) do set /A %%L echo !IMG! WW=%WW% HH=%HH% offX=%offX% offY=%offY% set /A xPlusy=%offX%+%offY% if %xPlusy%==0 ( echo Cut nothing if exist %CANV% ( %IMG7%magick ^ %CANV% ^ %IMG% ^ -compose Over -layers Merge ^ %CANV% ) else ( %IMG7%magick ^ %IMG% ^ %CANV% ) if ERRORLEVEL 1 exit /B 1 ) else if %offX%==0 ( set /A upY=!offY!-%overY% echo Cut top upY=!upY! %IM7DEV%magick ^ ^( %CANV% +write mpr:CANV ^ +repage ^ -crop %WW%x%overY%+0+%upY% +repage ^ ^) ^ ^( %IMG% +write mpr:IMG ^ +repage ^ -crop %WW%x%overY%+0+0 +repage ^ ^) ^ -colorspace Gray ^ -alpha off ^ -compose Difference -composite ^ -rotate -90 ^ -process darkestpath ^ -morphology dilate:-1 2x1+1+0:1,1 ^ -alpha off ^ -blur 0x0.5 ^ -rotate 90 ^ -background White -extent %WW%x%HH% ^ mpr:IMG ^ +swap ^ -alpha off ^ -compose CopyOpacity -composite ^ mpr:CANV ^ +swap ^ -background None ^ -compose Over -layers Merge ^ +repage ^ %CANV% if ERRORLEVEL 1 exit /B 1 ) else if %offY%==0 ( set /A leftX=!offX!-%overX% echo Cut leftX=!LeftX! %IM7DEV%magick ^ ^( %CANV% +write mpr:CANV ^ +repage ^ -crop %overX%x%HH%+%leftX%+0 +repage ^ ^) ^ ^( %IMG% +write mpr:IMG ^ +repage ^ -crop %overX%x%HH%+0+0 +repage ^ ^) ^ -colorspace Gray ^ -alpha off ^ -compose Difference -composite ^ -process darkestpath ^ -morphology dilate:-1 2x1+1+0:1,1 ^ -alpha off ^ -blur 0x0.5 ^ -background White -extent %WW%x%HH% ^ mpr:IMG ^ +swap ^ -alpha off ^ -compose CopyOpacity -composite ^ mpr:CANV ^ +swap ^ -background None ^ -compose Over -layers Merge ^ +repage ^ %CANV% if ERRORLEVEL 1 exit /B 1 ) else ( set /A upY=!offY!-%overY% set /A leftX=!offX!-%overX% rem set /A upY=!offY! rem set /A leftX=!offX! echo Cut left and top leftX=!LeftX! upY=!upY! rem Does this correctly cut left and top "torn" edges? %IM7DEV%magick ^ ^( %CANV% +write mpr:CANV ^ +repage ^ -crop %WW%x%overY%+0+%upY% +repage ^ ^) ^ ^( %IMG% +write mpr:IMG ^ +repage ^ -crop %WW%x%overY%+0+0 +repage ^ ^) ^ -colorspace Gray ^ -alpha off ^ -compose Difference -composite ^ -rotate -90 ^ -process darkestpath ^ -morphology dilate:-1 2x1+1+0:1,1 ^ -alpha off ^ -blur 0x0.5 ^ -rotate 90 ^ -background White -extent %WW%x%HH% ^ ^( ^ ^( mpr:CANV ^ +repage ^ -crop %overX%x%HH%+%leftX%+0 +repage ^ ^) ^ ^( mpr:IMG ^ +repage ^ -crop %overX%x%HH%+0+0 +repage ^ ^) ^ -colorspace Gray ^ -alpha off ^ -compose Difference -composite ^ -process darkestpath ^ -morphology dilate:-1 2x1+1+0:1,1 ^ -alpha off ^ -blur 0x0.5 ^ -background White -extent %WW%x%HH% ^ ^) ^ -compose Multiply -composite ^ mpr:IMG ^ +swap ^ -alpha off ^ -compose CopyOpacity -composite ^ mpr:CANV ^ +swap ^ -background None ^ -compose Over -layers Merge ^ +repage ^ %CANV% if ERRORLEVEL 1 exit /B 1 ) endlocal
rem Parses %3, default %1 and %2. Returns pxxyX and pxxyY. rem Also returns env vars %4_X and %4_Y @rem @rem %3 is one of these formats: @rem {blank} @rem . @rem {number} @rem {number}x @rem x{number} @rem {number}x{number} set pxxy=%3 set pxxyFirst=%pxxy:~0,1% rem echo pxxyFirst=%pxxyFirst% if "%pxxyFirst%"=="x" set pxxy=%1%pxxy% if "%pxxy%"=="." set pxxy= if "%pxxy%"=="" set pxxy=%1x%2 for /F "usebackq tokens=1-2 delims=x" %%X in ('%pxxy%') do ( rem echo %%X,%%Y set pxxyX=%%X set pxxyY=%%Y ) if "%pxxyY%"=="" set pxxyY=%2 rem echo pxxyX=%pxxyX% pxxyY=%pxxyY% set %4_X=%pxxyX% set %4_Y=%pxxyY%
@rem %1 is image width @rem %2 is image height @rem %3 is an x-coord, @rem possibly fractional, possibly negative, possibly suffixed with c or % or p @rem %4 is an y-coord, likewise @rem Returns actual pixel coords, possibly fractional, as xycX and xycY @rem and as rounded integers in xycXi and xycYi. @rem @rem Updated: @rem 15-July-2022 for IM v7. @setlocal enabledelayedexpansion @call echoOffSave set WW=%1 set HH=%2 set X=%3 set chX=%X:~-1% set vX=%X:~0,-1% set Y=%4 set chY=%Y:~-1% set vY=%Y:~0,-1% if "%chX%"=="%%" set chX=c if "%chY%"=="%%" set chY=c if "%chX%"=="c" ( set vX=%vX%*%1/100 ) else if "%chX%"=="p" ( set vX=%vX%*%1 ) else ( set vX=%X% ) if "%chY%"=="c" ( set vY=%vY%*%2/100 ) else if "%chY%"=="p" ( set vY=%vY%*%2 ) else ( set vY=%Y% ) for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 16 ^ -format "vX=%%[fx:%vX%]\nvY=%%[fx:%vY%]\nvXi=%%[fx:int(%vX%+0.5)]\nvYi=%%[fx:int(%vY%+0.5)]" ^ xc:`) do set %%L if ERRORLEVEL 1 ( echo %0: vX=[%vX%] vY=[%vY%] exit /B 1 ) call echoRestore @endlocal & set xycX=%vX%& set xycY=%vY%& set xycXi=%vXi%& set xycYi=%vYi%
rem Searches image %1 for image %2. rem Like srchImg.bat, but with "-process rmsealpha". rem @rem Writes results to optional batch file %3. Don't use this; it is for legacy purposes only. @rem If dimensions are large, resizes to find approx location, @rem and searches again within cropped limits. @rem Assumes width and height of %2 is smaller than of %1. @rem @rem Also uses: @rem [[siMETRIC defaults to RMSE]] @rem siDEBUG if 1, creates a marked-up copy of image. @rem siDEBUG_STROKE eg to get thick strokes @rem siDELTEMPSRC if not 0, will remove temporary source (%1) files. @rem siDELTEMPSUB if not 0, will remove temporary subimage (%2) files. @rem siMOD_OPTS rmsealpha process module options. @rem @rem Returns: @rem siCOMP_XW, siCOMP_YW coord of top-left of found area @rem siCOMP_FLT RMSE difference beween %2 and found area @rem siCOMP_CROP=%subW%x%subH%+%COMP_XW%+%COMP_YW% crop spec of found area @rem Written: 31 Jan 2014 @rem Revised: 6 May 2014 @rem Revised: 31 May 2014 Don't resize src if already exists. @rem Revised: 14-July-2022 for IM v7. @rem @rem FIXME: +repage, then add offsets (-format "%X %Y") at end. @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 si if "%siDEBUG%"=="1" ( set DEBUG_OUT=%BASENAME%_si_dbg%EXT% if "%siDEBUG_STROKE%"=="" set siDEBUG_STROKE=-fill None -stroke #f00 ) set SRCNAME=%~n1 set SRC=%INFILE% set SUB=%2 set fBAT=%3 if "%fBAT%"=="" set fBAT=NUL if "%siMETRIC%"=="" set siMETRIC=RMSE set TMPEXT=.miff set TMPDIR=%TEMP% set SRC_PREF=%TMPDIR%\siSrc_%SRCNAME% set TMPSRC=%SRC_PREF%%TMPEXT% set TMPSUB=%TMPDIR%\siSub%TMPEXT% if not "%siDELTEMPSRC%"=="0" ( del %TMPSRC% 2>nul ) if not "%siDELTEMPSUB%"=="0" ( del %TMPSUB% 2>nul ) if not "%fBat%"=="NUL" del %fBat% 2>nul FOR /F "usebackq" %%L ^ IN (`%IMG7%magick %SUB% -format "subW=%%w\nsubH=%%h\nMinDim=%%[fx:w<h?w:h]" info:`) ^ DO set /A %%L FOR /F "usebackq" %%L ^ IN (`%IM%identify -format "OFFSET_X=%%X\nOFFSET_Y=%%Y" %SRC%`) ^ DO set /A %%L echo MinDim=%MinDim% Offset=(%OFFSET_X%,%OFFSET_Y%) if %MinDim% GEQ 10000 ( call :ResizeSrch 0.1 1000 call :CropSrch !COMP_XW! !COMP_YW! 2000 ) else if %MinDim% GEQ 5000 ( call :ResizeSrch 0.2 500 call :CropSrch !COMP_XW! !COMP_YW! 1000 ) else if %MinDim% GEQ 2000 ( call :ResizeSrch 0.5 200 rem call :CropSrch !COMP_XW! !COMP_YW! 400 call :CropResizeSrch !COMP_XW! !COMP_YW! 100 5 20 call :CropResizeSrch !COMP_XW! !COMP_YW! 40 10 10 call :CropResizeSrch !COMP_XW! !COMP_YW! 20 20 5 call :CropResizeSrch !COMP_XW! !COMP_YW! 10 50 2 call :CropSrch !COMP_XW! !COMP_YW! 4 ) else if %MinDim% GEQ 1000 ( call :ResizeSrch 1 100 rem call :CropSrch !COMP_XW! !COMP_YW! 200 call :CropResizeSrch !COMP_XW! !COMP_YW! 100 5 20 call :CropResizeSrch !COMP_XW! !COMP_YW! 40 10 10 call :CropResizeSrch !COMP_XW! !COMP_YW! 20 20 5 call :CropResizeSrch !COMP_XW! !COMP_YW! 10 50 2 call :CropSrch !COMP_XW! !COMP_YW! 4 ) else if %MinDim% GEQ 500 ( call :ResizeSrch 2 50 call :CropResizeSrch !COMP_XW! !COMP_YW! 100 5 20 call :CropResizeSrch !COMP_XW! !COMP_YW! 40 10 10 call :CropResizeSrch !COMP_XW! !COMP_YW! 20 20 5 call :CropResizeSrch !COMP_XW! !COMP_YW! 10 50 2 call :CropSrch !COMP_XW! !COMP_YW! 4 ) else if %MinDim% GEQ 200 ( call :ResizeSrch 5 20 call :CropResizeSrch !COMP_XW! !COMP_YW! 40 10 10 call :CropResizeSrch !COMP_XW! !COMP_YW! 20 20 5 call :CropResizeSrch !COMP_XW! !COMP_YW! 10 50 2 call :CropSrch !COMP_XW! !COMP_YW! 4 ) else if %MinDim% GEQ 100 ( call :ResizeSrch 10 10 call :CropSrch !COMP_XW! !COMP_YW! 20 ) else if %MinDim% GEQ 50 ( call :ResizeSrch 20 5 call :CropSrch !COMP_XW! !COMP_YW! 10 ) else if %MinDim% GEQ 20 ( call :ResizeSrch 50 2 call :CropSrch !COMP_XW! !COMP_YW! 2 rem ) else if %MinDim% GEQ 10 ( rem call :ResizeSrch 50 2 rem call :CropSrch !COMP_XW! !COMP_YW! 2 ) else ( %IM7DEV%magick ^ %SRC% %SUB% -process 'rmsealpha %siMOD_OPTS%' NULL: FOR /F "usebackq tokens=1-4 delims=()@, " %%R ^ IN (`%IM7DEV%magick ^ %SRC% %SUB% -process 'rmsealpha %siMOD_OPTS%' NULL: 2^>^&1`) ^ DO ( set COMP_INT=%%R set COMP_FLT=%%S set COMP_XW=%%T set COMP_YW=%%U ) ) if "%COMP_XW%"=="" ( echo %0: COMP_XW is blank. exit /B 1 ) rem set /A COMP_XW+=%OFFSET_X% rem set /A COMP_YW+=%OFFSET_Y% if "%siDEBUG%"=="1" ( set /A endX=%COMP_XW%+%subW%-1 set /A endY=%COMP_YW%+%subH%-1 rem FIXME: does draw respect offsets? Add +repage to make this irrelevant. %IMG7%magick ^ %SRC% ^ +repage ^ %siDEBUG_STROKE% ^ -draw "rectangle %COMP_XW%,%COMP_YW% !endX!,!endY!" ^ %DEBUG_OUT% ) if not "%fBat%"=="NUL" echo set COMP_XW=%COMP_XW%^&set COMP_YW=%COMP_YW%^&set COMP_FLT=%COMP_FLT%^&set COMP_CROP=%subW%x%subH%+%COMP_XW%+%COMP_YW% >%fBat% FOR /F "usebackq" %%L ^ IN (`%IMG7%magick ^ %SUB% ^ -format "centX=%%[fx:int(%COMP_XW%+w/2)]\ncentY=%%[fx:int(%COMP_YW%+h/2)]" ^ info:`) ^ DO set /A %%L if not "%siDELTEMPSRC%"=="0" ( del %TMPSRC% 2>nul ) if not "%siDELTEMPSUB%"=="0" ( del %TMPSUB% 2>nul ) @call echoRestore @endlocal & set siCOMP_XW=%COMP_XW%& set siCOMP_YW=%COMP_YW%& set siCOMP_FLT=%COMP_FLT%& set siCENT_X=%centX%& set siCENT_Y=%centY%& set siSUB_W=%subW%& set siSUB_H=%subH%& set siCOMP_CROP=%subW%x%subH%+%COMP_XW%+%COMP_YW% @exit /B 0 :error echo Error in %0 exit /B 1 rem ------ Subroutines ---------------------------------------------------------- :ResizeSrch rem %1 is percentage reduction, eg 50 for 50%. May be <1. rem %2 is multiplier (=100/%1) echo ResizeSrch %1 %2 rem The convert and compare could be combined into one convert. set COMP_XW=0 set COMP_YW=0 set TMPSRC_RES=%SRC_PREF%_%1%TMPEXT% @rem FIXME: +repage, then add offsets (-format "%X %Y") at end. if not "%siDELTEMPSRC%"=="0" ( del %TMPSRC_RES% 2>nul ) if not exist %TMPSRC_RES% %IMG7%magick %SRC% +repage -resize %1%% %TMPSRC_RES% %IMG7%magick %SUB% -resize %1%% %TMPSUB% set COMP_XW= FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^ IN (`%IM7DEV%magick ^ %TMPSRC_RES% %TMPSUB% -process 'rmsealpha %siMOD_OPTS%' NULL: 2^>^&1`) ^ DO ( rem echo %%R %%S %%T %%U set COMP_INT=%%R set COMP_FLT=%%S if not "%%T"=="" set /A COMP_XW=%%T*%2 if not "%%U"=="" set /A COMP_YW=%%U*%2 ) if "%COMP_XW%"=="" exit /B 1 if not "%siDELTEMPSRC%"=="0" ( del %TMPSRC_RES% 2>nul ) rem echo COMP_XW, YW = %COMP_XW% %COMP_YW% exit /B 0 :CropSrch rem %1,%2 are coords for top-left of estimated result. rem %3 is plus or minus for tolerance in both directions. echo CropSrch %1 %2 %3 set /A cropL=%1-%3 set /A cropT=%2-%3 if %cropL% LSS 0 set cropL=0 if %cropT% LSS 0 set cropT=0 set /A cropW=%subW%+%3+%3 set /A cropH=%subH%+%3+%3 set COMP_XW=%cropL% set COMP_YW=%cropT% %IMG7%magick ^ %SRC% ^ +repage ^ -crop %cropW%x%cropH%+%cropL%+%cropT% ^ +repage ^ %TMPSRC% FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^ IN (`%IM7DEV%magick ^ %TMPSRC% %SUB% -process 'rmsealpha %siMOD_OPTS%' NULL: 2^>^&1`) ^ DO ( rem echo %%R %%S %%T %%U set COMP_INT=%%R set COMP_FLT=%%S if not "%%T"=="" set /A COMP_XW+=%%T if not "%%U"=="" set /A COMP_YW+=%%U ) rem echo COMP_XW, YW = %COMP_XW% %COMP_YW% exit /B 0 :CropResizeSrch rem %1,%2 are coords for top-left of estimated result. rem %3 is plus or minus for tolerance in both directions. rem %4 is percentage reduction, eg 50 for 50%. May be <1. rem %5 is multiplier (=100/%1) rem %1 to %3 are in terms of original full size. echo CropResizeSrch %1 %2 %3 %4 %5 set /A cropL=%1-%3 set /A cropT=%2-%3 if %cropL% LSS 0 set cropL=0 if %cropT% LSS 0 set cropT=0 set /A cropW=%subW%+%3+%3 set /A cropH=%subH%+%3+%3 set COMP_XW=%cropL% set COMP_YW=%cropT% %IMG7%magick ^ %SRC% ^ -crop %cropW%x%cropH%+%cropL%+%cropT% ^ +repage ^ -resize %4%% -write %TMPSRC% +delete ^ %SUB% -resize %4%% %TMPSUB% FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^ IN (`%IM7DEV%magick ^ %TMPSRC% %TMPSUB% -process 'rmsealpha %siMOD_OPTS%' NULL: 2^>^&1`) ^ DO ( echo %%R %%S %%T %%U set COMP_INT=%%R set COMP_FLT=%%S if not "%%T"=="" set /A COMP_XW+=%%T*%5 if not "%%U"=="" set /A COMP_YW+=%%U*%5 ) rem echo COMP_XW, YW = %COMP_XW% %COMP_YW% exit /B 0
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 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 (193231332)
Source file for this web page is quilt.h1. To re-create this web page, execute "procH1 quilt".
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 9-Dec-2015.
Page created 10-Sep-2022 22:36:45.
Copyright © 2022 Alan Gibson.