A method for graduated distortion.
IM provides an operator -distort SRT to scale, rotate and translate an entire image. We might want to apply this distortion to part of the image, and graduate the effect between that part and unaffected parts.
The general method is:
Where the mask is gray, each result pixel will come from somewhere on the straight line between the source pixel with the same coordinates, and the source pixel corresponding to the full distortion.
This method needs a two-dimensional (WWxHH) mask. For a similar method that instead uses two look-up tables (WWx1 and HHx1) see Specify scaling by LUTs.
For the source, we use an image with a grid so we can see what is happening.
set SRC=dms_src.png call %PICTBAT%gridOver ^ toes.png %SRC% 10 10 1 yellow for /F "usebackq" %%L in (`%IMG7%magick ^ %SRC% ^ -format "WW=%%w\nHH=%%h\n" ^ info:`) do set %%L echo WW=%WW% HH=%HH% WW=267 HH=233 |
We make some sample masks:
%IMG7%magick ^ -size %WW%x%HH% ^ gradient: ^ dms_msk1.png |
|
call %PICTBAT%mEllipMsk ^ dms_msk2.png ^ %WW%x%HH% "0.7p,0.6p" 0.4px0.3p |
|
call %PICTBAT%mPntEdgeMsk ^ dms_msk3.png ^ %WW%x%HH% "0.7p,0.6p" . both 10 90 |
|
%IMG7%magick ^ toes.png ^ -colorspace Gray ^ -blur 0x10 ^ -auto-level ^ -sigmoidal-contrast 5,50%% ^ dms_msk4.png |
Each mask has a transition between black and white. If there were no transition, the result would contain sudden "jumps" in displacement -- a lack of first-order continuity.
Masks can be made or modified using methods shown in the Gradients Cookbook.
The script mskdSrt.bat performs the remaining steps. It takes parameters:
Parameter | Description |
---|---|
%1 | input image to be distorted |
%2 | input grayscale mask |
%3 | output file |
%4 | up to 7 quoted comma-separated numbers |
The comma-separated numbers are:
Parameter | Description |
---|---|
CX,CY | Centre of effect.
Each number may be suffixed with one of %cCpP, which mean the number is a percentage or proportion of the width or height. Default: 0.5p,0.5p, half width and height. |
ScaleX,ScaleY | Scale factors for the two directions.
Default: 1. |
Angle | Rotation, degrees clockwise.
Default: 0. |
dX,dY | Deltas, which are added to CX and CY to make the last two parameters to IM's -distort SRT.
Each number may be suffixed with one of %cCpP, which mean the number is a percentage or proportion of the width or height. Default: 0. |
Trailing numbers can be omitted.
The seven parameters are very similar to the seven-parameter version of IM's -distort SRT, but:
The output will be the same size as the input. Depending on the effect, parts of the input may not be present in the output.
In the examples, we set the centre of effect to be the same as the centre of the mask.
Scale (magnifying) effect:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_s1.png ^ "0.7p,0.6p,1.3,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_s2.png ^ "0.7p,0.6p,1.3,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_s3.png ^ "0.7p,0.6p,1.3,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_s4.png ^ "0.7p,0.6p,1.3,1.3" |
Scale (magnifying) in just the y-direction:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_sy1.png ^ "0.7p,0.6p,1,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_sy2.png ^ "0.7p,0.6p,1,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_sy3.png ^ "0.7p,0.6p,1,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_sy4.png ^ "0.7p,0.6p,1,1.3" |
When we magnify where the mask is white, and the mask edge is black so there is no change at the image edge, then the transition area will include image reduction.
Magnify in the y-direction, and reduce in the x-direction:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_sxsy1.png ^ "0.7p,0.6p,0.7,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_sxsy2.png ^ "0.7p,0.6p,0.7,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_sxsy3.png ^ "0.7p,0.6p,0.7,1.3" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_sxsy4.png ^ "0.7p,0.6p,0.7,1.3" |
Rotation effect:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_r1.png ^ "0.7p,0.6p,1,1,20" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_r2.png ^ "0.7p,0.6p,1,1,20" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_r3.png ^ "0.7p,0.6p,1,1,20" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_r4.png ^ "0.7p,0.6p,1,1,20" |
In the transition area, where the mask is between black and white, we do not get a reduced rotation. This is because each destination pixel is set to an input colour that lies on a straight line between that coordinate and the full-effect distortion coordinate. As a rotation does not move pixels in straight lines, the chosen coordinate will not represent a reduced rotation that corresponds to the grayscale lightness.
The other transformations (scale and translation) do move pixels in straight lines, so partial transformations are correct.
Translation effect:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_t1.png ^ "0.7p,0.6p,1,1,0,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_t2.png ^ "0.7p,0.6p,1,1,0,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_t3.png ^ "0.7p,0.6p,1,1,0,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_t4.png ^ "0.7p,0.6p,1,1,0,-0.08p,0.05p" |
Scale, rotate and translate effect:
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk1.png ^ dms_out_srt1.png ^ "0.7p,0.6p,1.3,1.3,20,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk2.png ^ dms_out_srt2.png ^ "0.7p,0.6p,1.3,1.3,20,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk3.png ^ dms_out_srt3.png ^ "0.7p,0.6p,1.3,1.3,20,-0.08p,0.05p" |
|
call %PICTBAT%mskdSrt ^ %SRC% ^ dms_msk4.png ^ dms_out_srt4.png ^ "0.7p,0.6p,1.3,1.3,20,-0.08p,0.05p" |
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
rem Makes elliptical mask, with gradation. rem %1 output file rem %2 WWxHH image width and height, pixels, eg 1920x1080 rem %3 quoted CX,CY ellipse centre rem %4 ERXxERY ellipse radius width and height, pixels, eg 150x100 rem %5 angle of rotation of ellipse, degrees [0] rem %6 RF, thickness of gradation, 0.0 < RF <= 1.0 [0.5] @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal rem @call echoOffSave set OUTFILE=%1 set IMGSIZE=%2 if "%IMGSIZE%"=="." set IMGSIZE= if "%IMGSIZE%"=="" set IMGSIZE=600x400 call parseXxY2 600 400 mem_ %IMGSIZE% set ELL_CENT=%3 if [%ELL_CENT%]==[.] set ELL_CENT= if [%ELL_CENT%]==[] ( set /A CX=%mem__X%/2 set /A CY=%mem__Y%/2 set ELL_CENT="!CX!,!CY!" ) call parseCommaList %ELL_CENT% mem_argc mem_argv if ERRORLEVEL 1 exit /B 1 call propOf %mem__X% %mem_argv[0]% mem_argv[0] if ERRORLEVEL 1 exit /B 1 call propOf %mem__Y% %mem_argv[1]% mem_argv[1] if ERRORLEVEL 1 exit /B 1 set ELL_RAD=%~4 if "%ELL_RAD%"=="." set ELL_RAD= if "%ELL_RAD%"=="" ( set /A RX=%mem__X%/4 set /A RY=%mem__Y%/4 set ELL_RAD=!RX!x!RY! ) call parseXxY2 %mem__X% %mem__Y% mem_er_ %ELL_RAD% set ANG=%5 if "%ANG%"=="." set ANG= if "%ANG%"=="0.0" set ANG=0 if "%ANG%"=="" set ANG=0 if "%ANG%"=="" ( set ROT= ) else ( set ROT=-distort SRT 1,%ANG% set ROT=-define gradient:angle=%ANG% ) set RF=%6 if "%RF%"=="." set RF= if "%RF%"=="" set RF=0.5 set mem_ %IMG7%magick ^ -size %mem__X%x%mem__Y% ^ -define gradient:center=%mem_argv[0]%,%mem_argv[1]% ^ -define gradient:radii=%mem_er__X%,%mem_er__Y% ^ %ROT% ^ radial-gradient: ^ -level 0,%%[fx:QuantumRange*%RF%] ^ %OUTFILE% call echoRestore @endlocal
rem Make a mask white at point, black at edges. rem %1 output file rem %2 WWxHH image width and height, pixels, eg 1920x1080 rem %3 quoted CX,CY centre rem %4 composite method [gm] rem %5 smoothing: none, start, end, or both [none] rem %6 L1, percentage start of gradation [0] rem %7 L2, percentage start of gradation [100] @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 pem set OUTFILE=%1 set IMGSIZE=%2 if "%IMGSIZE%"=="." set IMGSIZE= if "%IMGSIZE%"=="" set IMGSIZE=600x400 call parseXxY2 600 400 pem_ %IMGSIZE% set CENT=%3 if [%CENT%]==[.] set CENT= if [%CENT%]==[] ( set CX=0.5 set CY=0.5 set CENT="!CX!,!CY!" ) call parseCommaList %CENT% pem_argc pem_argv if ERRORLEVEL 1 exit /B 1 call propOfProp %pem__X% %pem_argv[0]% pem_argv[0] if ERRORLEVEL 1 exit /B 1 call propOfProp %pem__Y% %pem_argv[1]% pem_argv[1] if ERRORLEVEL 1 exit /B 1 set WW=%pem__X% set HH=%pem__Y% set CX=%pem_argv[0]% set CY=%pem_argv[1]% set pem_ set P1h=%CX% set S1h=1/%CX% set S2h=1/(%CX%-1) set P1v=%CY% set S1v=1/%CY% set S2v=1/(%CY%-1) set MTHD=%4 if "%MTHD%"=="." set MTHD= if "%MTHD%"=="" set MTHD=gm set SMTH=%5 if "%SMTH%"=="." set SMTH= if "%SMTH%"=="" set SMTH=none set L1=%6 if "%L1%"=="." set L1= if "%L1%"=="" set L1=0 set L2=%7 if "%L2%"=="." set L2= if "%L2%"=="" set L2=100 set sLEV= if %L1% NEQ 0 if %L2% NEQ 100 ( set sLEV=-level %L1%%%,%L2%%% ) if /I "%MTHD%"=="Darken" ( set COMPOS=-compose Darken -composite ) else if /I "%MTHD%"=="Lighten" ( set COMPOS=-compose Lighten -composite ) else if /I "%MTHD%"=="Mean" ( set COMPOS=-evaluate-sequence Mean ) else if /I "%MTHD%"=="Multiply" ( set COMPOS=-compose Multiply -composite ) else if /I "%MTHD%"=="gm" ( set COMPOS=-compose Multiply -composite -evaluate Pow 0.5 ) else ( echo %0: Bad MTHD [%MTHD%] exit /B 1 ) if /I "%SMTH%"=="none" ( set sSMTH= ) else if /I "%SMTH%"=="start" ( set sSMTH=-function sinusoid 0.25,-90,1,1 ) else if /I "%SMTH%"=="end" ( set sSMTH=-function sinusoid 0.25,0,1,0 ) else if /I "%SMTH%"=="both" ( set sSMTH=-function sinusoid 0.5,-90,0.5,0.5 ) else ( echo %0: Bad SMTH [%SMTH%] exit /B 1 ) %IMG7%magick ^ ( -size 1x%WW% gradient: -rotate 90 ^ -fx "u<%P1h%?u*%S1h%:1+(u-%P1h%)*%S2h%" ^ %sSMTH% ^ -scale "%WW%x%HH%^!" ^ ) ^ ( -size 1x%HH% gradient: -flip ^ -fx "u<%P1v%?u*%S1v%:1+(u-%P1v%)*%S2v%" ^ %sSMTH% ^ -scale "%WW%x%HH%^!" ^ ) ^ %COMPOS% ^ %sLEV% ^ %OUTFILE% call echoRestore @endlocal
rem Given %1 is a (quoted) comma- or space-separated list, rem sets environment variables %2 to the number of values rem and %3 (as an array) to the values. set LIST=%~1 set pclNUM=%2 if "%pclNUM%"=="." set pclNUM= if "%pclNUM%"=="" set pclNUM=argc set pclVALS=%3 if "%pclVALS%"=="." set pclVALS= if "%pclVALS%"=="" set pclVALS=argv call :doParse %LIST% set /A %pclNUM%=%pclN% rem echo %0: pclVALS=%pclVALS% exit /B 0 :: -------------------------------------------- :: Subroutine :doParse set pclN=0 :loop if "%1"=="" exit /B 0 rem echo %0: 1=%1 2=%2 3=%3 set %pclVALS%[%pclN%]=%1 set /A pclN+=1 shift /1 goto loop exit /B 0
rem Parses %4, default %1 and %2. Returns %3_X and %3_Y. @rem @rem %1 and %2 can be expressions such as "600-1". @rem @rem %4 is one of these formats: @rem {blank} @rem . @rem {number} @rem {number}x @rem x{number} @rem {number}x{number} @rem @rem Each number can be suffixed with %, c or p or C or P. @rem @rem Updated: @rem 4-October-2022 for IM v7. @rem 20-August-2023 Put %1 and %2 in parentheses so they can be expressions. @rem @set pxxy=%4 @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=xX" %%X in ('%pxxy%') do @( @rem echo %%X,%%Y @set pxxyX=%%X @set pxxyY=%%Y ) @if "%pxxyY%"=="" set pxxyY=%2 set CH_LAST=%pxxyX:~-1% if "%CH_LAST%"=="^%" set CH_LAST=c if /I "%CH_LAST%"=="c" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 19 ^ -format "pxxyX=%%[fx:(%1)*%pxxyX:~0,-1%/100]" ^ xc:`) do set %%L ) else if /I "%CH_LAST%"=="p" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 19 ^ -format "pxxyX=%%[fx:(%1)*%pxxyX:~0,-1%]" ^ xc:`) do set %%L ) set CH_LAST=%pxxyY:~-1% if "%CH_LAST%"=="^%" set CH_LAST=c if /I "%CH_LAST%"=="c" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 19 ^ -format "pxxyY=%%[fx:(%2)*%pxxyY:~0,-1%/100]" ^ xc:`) do set %%L ) else if /I "%CH_LAST%"=="p" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 19 ^ -format "pxxyY=%%[fx:(%2)*%pxxyY:~0,-1%]" ^ xc:`) do set %%L ) @rem echo pxxyX=%pxxyX% pxxyY=%pxxyY% set %3_X=%pxxyX% set %3_Y=%pxxyY%
rem Given %1 is a number, rem %2 is a number possibly suffixed with % or c or p or C or P, rem sets variable %3 to result. @rem All can be floating-point. @rem @rem Eg if %1 is 300 @rem then %2 might be 60 or 20% or 20c or 0.2p, and the result is 60. @rem @rem See also propOf.bat @rem @rem Updated: @rem 17-September-2022 @rem setlocal set VAR_S=%2 set OUTV=%VAR_S% set CH_LAST=%VAR_S:~-1% if "%CH_LAST%"=="^%" set CH_LAST=c if /I "%CH_LAST%"=="c" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 16 ^ -format "OUTV=%%[fx:%1*%VAR_S:~0,-1%/100]" ^ xc:`) do set %%L ) else if /I "%CH_LAST%"=="p" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 16 ^ -format "OUTV=%%[fx:%1*%VAR_S:~0,-1%]" ^ xc:`) do set %%L ) echo OUTV=%OUTV% @endlocal & set %3=%OUTV%
rem Like propOf.bat but result is proportion of %1. rem Given %1 is a number, rem %2 is a number possibly suffixed with % or c or p or C or P, rem sets variable %3 to result. @rem All can be floating-point. @rem @rem Eg if %1 is 300 @rem then %2 might be 60 or 20% or 20c or 0.2p, and the result is 0.2. @rem @rem Updated: @rem 4-October-2022 for IM v7. @rem setlocal set VAR_S=%2 set OUTV=%VAR_S% set CH_LAST=%VAR_S:~-1% if "%CH_LAST%"=="^%" set CH_LAST=c if /I "%CH_LAST%"=="c" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 16 ^ -format "OUTV=%%[fx:%VAR_S:~0,-1%/100]" ^ xc:`) do set %%L ) else if /I "%CH_LAST%"=="p" ( set OUTV=%VAR_S:~0,-1% ) else ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 16 ^ -format "OUTV=%%[fx:%VAR_S%/%1]" ^ xc:`) do set %%L ) echo OUTV=%OUTV% @endlocal & set %3=%OUTV%
rem %1 input image to be distorted rem %2 input grayscale mask, white for full effect rem %3 output file rem %4 up to 7 quoted comma-separated numbers: rem X,Y centre of effect rem ScaleX,ScaleY rem Angle rem dX,dY @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal rem @call echoOffSave call %PICTBAT%setInOut %1 ms set INMSK=%2 if not "%3"=="" if not "%3"=="." set OUTFILE=%3 set CSNUMS=%~4 if "%CSNUMS%"=="." set CSNUMS= if "%CSNUMS%"=="" set CSNUMS=0.5p,0.5p set WW= for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\nW_2=%%[fx:w/2]\nH_2=%%[fx:h/2]\n" ^ %INFILE%`) do set %%L if "%WW%"=="" ( echo %0: Can't read INFILE [%INFILE%] exit /B 1 ) call parseCommaList "%CSNUMS%" ms_argc ms_argv if "%ms_argv[0]%"=="" set %ms_argv[0]=%W_2% if "%ms_argv[1]%"=="" set %ms_argv[1]=%H_2% if "%ms_argv[2]%"=="" set %ms_argv[2]=1 if "%ms_argv[3]%"=="" set %ms_argv[3]=1 if "%ms_argv[4]%"=="" set %ms_argv[4]=0 if "%ms_argv[5]%"=="" set %ms_argv[5]=0 if "%ms_argv[6]%"=="" set %ms_argv[6]=0 if ERRORLEVEL 1 exit /B 1 call propOf %WW% %ms_argv[0]% ms_argv[0] if ERRORLEVEL 1 exit /B 1 call propOf %HH% %ms_argv[1]% ms_argv[1] if ERRORLEVEL 1 exit /B 1 call propOf %WW% %ms_argv[5]% ms_argv[5] if ERRORLEVEL 1 exit /B 1 call propOf %HH% %ms_argv[6]% ms_argv[6] if ERRORLEVEL 1 exit /B 1 set ms_ set SSFACT=2 set ms_argv[5a]=%%[fx:!ms_argv[0]!*%SSFACT%+!ms_argv[5]!*%SSFACT%] set ms_argv[6a]=%%[fx:!ms_argv[1]!*%SSFACT%+!ms_argv[6]!*%SSFACT%] for %%I in (0,1,5,6) do ( set ms_argv[%%I]=%%[fx:!ms_argv[%%I]!*%SSFACT%] ) set ms_ %IMG7%magick ^ %INFILE% ^ -resize 200%% ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( +clone ^ -sparse-color bilinear ^ 0,0,#008,^ 0,%%[fx:h-1],#0f8,^ %%[fx:w-1],0,#f08,^ %%[fx:w-1],%%[fx:h-1],#ff8 ^ +write mpr:IDENT ^ +delete ^ ) ^ ( mpr:IDENT ^ ( +clone ^ -distort SRT %ms_argv[0]%,%ms_argv[1]%,%ms_argv[2]%,%ms_argv[3]%,%ms_argv[4]%,%ms_argv[5a]%,%ms_argv[6a]% ^ ) ^ ( %INMSK% -resize 200%% ) ^ -compose Over -composite ^ ) ^ -compose Distort -composite ^ -resize 50%% ^ %OUTFILE% call echoRestore @endlocal
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.1-15 Q16-HDRI x64 a0a5f3d:20230730 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL OpenMP(2.0) 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 (193532217)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
Source file for this web page is dispmsksrt.h1. To re-create this web page, execute "procH1 dispmsksrt".
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 10-July-2019.
Page created 29-Sep-2023 01:50:33.
Copyright © 2023 Alan Gibson.