snibgo's ImageMagick pages

Quilting

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. I have added a refinement: instead of selecting a rectangle at random from the input texture, my script selects the rectangle that best matches the overlap area, 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.

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.

Extend texture

This process takes 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
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 searches for the closest match in the texture image and pastes this onto the output using the script mebcOne.bat to create minimum error boundary cuts at the top and left edges of the pasted patch.

The script calls convert multiple times for each patch, so it isn't fast.

Make a large image of leaves
from patches of the smaller leaves texture imge.

call %PICTBAT%mkQuilt ^
  %SRC% qlt_q1.png . . 6x6
qlt_q1.pngjpg

Using an input from the Efros and Freeman paper:

call %PICTBAT%mkQuilt ^
  qlt_apples.png qlt_apples_q1.png . . 8x8
qlt_apples_q1.pngjpg

Imitate by texture (texture transfer)

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
mona600.pngjpg

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
qlt_monalvs1.pngjpg

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. 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
qlt_monalvs2.pngjpg
call %PICTBAT%mkQuiltLike ^
  dpa_ass1.png ^
  mona600.png ^
  qlt_monalvs3.png ^
  60x60
qlt_monalvs3.pngjpg

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=\pictures\20151008\2607_wall.tiff

%IM%convert ^
  %BRICK_WALL% ^
  -contrast-stretch 5%%x5%% ^
  -resize 5%% ^
  qlt_brcks.miff
qlt_brcks.miffjpg

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=
qlt_mona_brk.jpg

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=\pictures\20151008\2607_wall.tiff

%IM%convert ^
  %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=
qlt_mona_brk_sm.jpg

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=
qlt_mona_brkgb.jpg

Gravel is a handy texture:

%IM%convert ^
  \pictures\20151113\AGA_2831.JPG ^
  -gravity NorthEast -crop 50%%x50%%+0+0 ^
  -gravity None ^
  +repage ^
  -sigmoidal-contrast 5x50%% ^
  -modulate 100,150,100 ^
  -resize 300x300 ^
  -strip ^
  qlt_gravel.miff
qlt_gravel.miffjpg

Create some variety.

%IM%convert ^
  qlt_gravel.miff ^
  +write qlt_tmp_1.miff ^
  ( +clone ^
    -sigmoidal-contrast 5,25%% ^
    +write qlt_tmp_0.miff ^
    +delete ) ^
  ( +clone ^
    -sigmoidal-contrast 5,75%% ^
    +write qlt_tmp_2.miff ^
    +delete ) ^
  NULL:

call %PICTBAT%mebcTile ^
  qlt_tmp_XX.miff ^
  3 1 10c 10c ^
  qlt_grav_m.miff

del qlt_tmp_?.miff
qlt_grav_m.miffjpg
call %PICTBAT%mkQuiltLike ^
  qlt_grav_m.miff ^
  mona600.png ^
  qlt_mona_grv.jpg ^
  20x20
qlt_mona_grv.jpg

Imitate by proxy texture

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.

Future

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 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.

%IM%convert ^
  mona600.jpg ^
  -blur 0x1 ^
  qlt_mona_blr.miff

call %PICTBAT%slopeMag ^
  qlt_mona_blr.miff ^
  qlt_mona_sm.miff

%IM%convert ^
  qlt_mona_sm.miff ^
  -scale "172x200^!" ^
  -scale "517x600^!" ^
  -auto-level ^
  -evaluate Pow 2 ^
  qlt_mona_pri.miff
qlt_mona_pri.miffjpg

This would give us a priority order for the patches.

Scripts

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

mkQuilt.bat

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
@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".


@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_OUT=mq.png
set TEMP_PATCH=mq_patch.png
set TEMP_PATCH2=mq_patch2.png

set WW=
for /F "usebackq" %%L in (`%IM%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 /A outW=%patchW%*%patchCols%-%overX%*(%patchCols%-1)
set /A outH=%patchH%*%patchRows%-%overY%*(%patchRows%-1)

echo %patchW%x%patchH% %overX%x%overY% %patchCols%x%patchRows% %firstX%x%firstY%
echo %OUTFILE% out=%outW%x%outH%

if %outW% LSS 1 (
  echo outW=%outW%
  exit /B 1
)

if %outH% LSS 1 (
  echo outH=%outH%
  exit /B 1
)

%IM%convert ^
  %INFILE% ^
  -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

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 rowY=!rowY! colX=!colX!

      %IM%convert ^
        %TEMP_OUT% ^
        -crop %patchW%x%patchH%+!colX!+!rowY! +repage ^
        %TEMP_PATCH%

      call %PICTBAT%srchImgAlpha %INFILE% %TEMP_PATCH%

      rem echo siCOMP_FLT=!siCOMP_FLT! siCOMP_CROP=!siCOMP_CROP!

      %IM%convert ^
        %INFILE% ^
        -crop !siCOMP_CROP! +repage ^
        -repage +!colX!+!rowY! ^
        %TEMP_PATCH2%

      call %PICTBAT%mebcOne %TEMP_OUT% %TEMP_PATCH2% %overX% %overY%

    )
  )
)

set siDELTEMPSRC=
set siDELTEMPSUB=

%IM%convert %TEMP_OUT% %OUTFILE%

call echoRestore

endlocal & set mqOUTFILE=%OUTFILE%

mkQuiltLike.bat

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_OUT=mq%TMP_EXT%
set TEMP_PATCH=mq_patch%TMP_EXT%
set TEMP_PATCH2=mq_patch2%TMP_EXT%

set TEX_WW=
for /F "usebackq" %%L in (`%IM%identify ^
  -format "TEX_WW=%%w\nTEX_HH=%%h" ^
  %INFILE%`) 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 (`%IM%identify ^
  -format "REF_WW=%%w\nREF_HH=%%h\n" ^
  %REF_IMG%`) do set %%L
if "%REF_WW%"=="" exit /B 1

echo 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 (`%IM%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 (`%IM%identify ^
  -format "%CALC%" ^
  xc:`) do set %%L



echo 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 (`%IM%identify ^
  -format "%CALC%" ^
  xc:`) do set %%L

echo outW=%outW% outH=%outH%

echo patchWH=%patchW%x%patchH% overXY=%overX%,%overY% patchRC=%patchCols%x%patchRows%
echo %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
)

rem %IM%convert ^
rem   %REF_IMG% ^
rem   -background None ^
rem   -gravity NorthWest ^
rem   -extent %outW%x%outH% ^
rem   %TEMP_OUT%

%IM%convert ^
  %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!

  %IM%convert %TEMP_OUT% !FRAME_NAME!
)



set FRAME_NUM=1

for /L %%R in (0,1,%nRowm1%) do (
  for /F "usebackq" %%L in (`%IM%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 (`%IM%identify ^
      -format "colX=%%[fx:int(%%C*(%patchWi%-%overX%)+0.5)]" ^
      xc:`) do set %%L

    echo rowY=!rowY! colX=!colX!

    %IM%convert ^
      %TEMP_OUT% ^
      -crop %patchWi%x%patchHi%+!colX!+!rowY! +repage ^
      %TEMP_PATCH%

    call %PICTBAT%srchImgAlpha %INFILE% %TEMP_PATCH%

    echo siCOMP_FLT=!siCOMP_FLT! siCOMP_CROP=!siCOMP_CROP! in bits !siSUB_W!x!siSUB_H!+!siCOMP_XW!+!siCOMP_YW!

    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 (`%IM%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 (`%IM%identify ^
        -format "!CALC!" ^
        xc:`) do set %%L
    )

    set sCROP=!siSUB_W!x!siSUB_H!+!siCOMP_XW!+!siCOMP_YW!

    echo sCROP=!sCROP!

    %IM%convert ^
      %INFILE% ^
      -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!

      %IM%convert %TEMP_OUT% !FRAME_NAME!

      set /A FRAME_NUM+=1
    )

    set siDELTEMPSRC=0
  )
)

set siDELTEMPSRC=
set siDELTEMPSUB=

%IM%convert ^
  %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!

mebcOne.bat

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 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 (`%IM%identify ^
  -format "WW=%%w\nHH=%%h\noffX=%%X\noffY=%%Y" ^
  %IMG%`) do set /A %%L

echo !IMG! offX=%offX% offY=%offY%

set /A xpy=%offX%+%offY%

if %xpy%==0 (
  if exist %CANV% (
    %IM%convert ^
      %CANV% ^
      %IMG% ^
      -compose Over -layers Merge ^
      %CANV%
  ) else (
    %IM%convert ^
      %IMG% ^
      %CANV%
  )

  if ERRORLEVEL 1 exit /B 1

) else if %offX%==0 (
  set /A upY=!offY!-%overY%
  echo Merge top upY=!upY!

  %IMDEV%convert ^
    ^( %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 ^
    -compose Difference -composite ^
    -rotate -90 ^
    -process darkestpath ^
    -morphology dilate:-1 2x1+1+0:1,1 ^
    -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 Merge leftX=!LeftX!

  %IMDEV%convert ^
    ^( %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 ^
    -compose Difference -composite ^
    -process darkestpath ^
    -morphology dilate:-1 2x1+1+0:1,1 ^
    -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%
  set /A upY=!offY!
  set /A leftX=!offX!

  echo Merge top and left upY=!upY! leftX=!LeftX!

  %IMDEV%convert ^
    ^( %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 ^
    -compose Difference -composite ^
    -rotate -90 ^
    -process darkestpath ^
    -morphology dilate:-1 2x1+1+0:1,1 ^
    -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 ^
      -compose Difference -composite ^
      -process darkestpath ^
      -morphology dilate:-1 2x1+1+0:1,1 ^
      -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

parseXxY.bat

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%

xyCoord.bat

@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.

@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 (`%IM%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%

srchImgAlpha.bat

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 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 (`%IM%convert -ping %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 (
  FOR /F "usebackq tokens=1-4 delims=()@, " %%R ^
IN (`%IMDEV%convert ^
    %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
  )
)

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.

  %IM%convert ^
    %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 (`%IM%convert ^
  -ping ^
  %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% %IM%convert %SRC% +repage -resize %1%% %TMPSRC_RES%

%IM%convert %SUB% -resize %1%% %TMPSUB%

set COMP_XW=

FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^
IN (`%IMDEV%convert ^
    %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%

%IM%convert ^
  %SRC% ^
  +repage ^
  -crop %cropW%x%cropH%+%cropL%+%cropT% ^
  +repage ^
  %TMPSRC%

FOR /F "tokens=1-4 usebackq delims=()@, " %%R ^
IN (`%IMDEV%convert ^
    %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%

%IM%convert ^
  %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 (`%IMDEV%convert ^
    %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:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

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 08-Oct-2016 21:56:06.

Copyright © 2016 Alan Gibson.