A batch file using ImageMagick (https://www.imagemagick.org) to straighten, convert, and trim large bitmap images scanned with a specific (but editable) background color while leaving a some of the background remaining as a border. The fuzz percentage and border left can be given as command line parameters when calling the batch file.
ECHO off | |
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS | |
REM IMprocessBMPscansOnBlackCarstock.bat | |
REM Usage: IMprocessBMPscansOnBlackCarstock.bat optional:(fuzz[integer] padding[integer]) | |
REM Creates straightened and cropped with padding 100% quality jpgs | |
REM of all BMP files in current directory and places them in a subdirectory | |
REM called output | |
REM Begin init ---------------------------------------------- | |
SET function=init | |
SET "fmtString=[4][11][11][-10][1][-16][1][70]" | |
CALL :db "started" | |
REM fuzz is an ImageMagick parameter given as a percentage that relates | |
REM to how far away from the defined background colour and still be | |
REM considered background. When fuzz is too low for a specific scan, | |
REM too much background is left un-trimmed. When fuzz is too high for | |
REM scan, it will -trim off too much. | |
SET fuzzSetpoint=52 | |
REM The main reason this batch exists is to crop image (post card) scans, | |
REM while leaving some of the background showing. Both to ensure nothing is | |
REM lost for archival reasons, but also to be consistent with the style on | |
REM our digital repository (https://pastforward.winnipeg.ca). | |
SET padding=50 | |
REM These are the RGB space components of the colour of the background we're using. | |
REM Change this here once, if you want. | |
SET red=59 | |
SET green=57 | |
SET blue=55 | |
SET /a "value=1000*(%red%+%green%+%blue%)/(3*255)" | |
SET "imRGB=rgb(%red%,%green%,%blue%)" | |
SET count=0 | |
SET param1=%1 | |
SET param2=%2 | |
SET "inFile=none" | |
SET "rotFile=" | |
SET greatSuccess=0 | |
CMD /C EXIT 0 | |
REM Set up temp and output folders | |
IF NOT EXIST temp\ ( | |
REM CALL :db "Creating temp folder." | |
CALL :db "creating Temp folder." | |
MD temp | |
) ELSE ( | |
CALL :db "temp folder already exists." | |
) | |
IF NOT EXIST output\ ( | |
CALL :db "Creating ouput folder." | |
MD output | |
) ELSE ( | |
CALL :db "output folder already exists." | |
) | |
REM Check for a first parameter and if it exists set fuzz equal to it. | |
IF "%~1" == "" ( | |
CALL :db "No parameters detected, going to :main" | |
GOTO :main | |
) ELSE ( | |
SET "fuzzSetpoint=%1" | |
CALL :db "Found parameter 1 = %1. Attempting to set starting trim fuzz to %param1%." | |
) | |
CALL :db "Starting fuzz now %fuzzSetpoint%." | |
REM Check for a second parameter and if it exists set padding equal to it. | |
IF "%~2" == "" ( | |
CALL :db "No second parameter detected, going to :main" | |
GOTO :main | |
) ELSE ( | |
SET "padding=%2" | |
CALL :db "Found parameter 2 = %2. Attempting to set padding to %2." | |
) | |
CALL :db "Starting padding now %padding%." | |
GOTO :main | |
GOTO :eof | |
REM End init ------------------------------------------------ | |
REM Begin :main ------------------------------------------------- | |
:main | |
SETLOCAL | |
SET function=main | |
CALL :db "started. trim fuzz=%fuzzSetpoint%. padding=%padding%" | |
CALL :testFolder | |
FOR %%a IN (*.bmp) DO ( | |
CALL :batchDumb %%a | |
SET fuzz=%fuzzSetpoint% | |
CALL :rotateOriginal !inFile! | |
CALL :db "rotation and conversion finished. Calling :getTrim with fuzz !fuzz!%%" | |
CALL :getTrim !fuzz! | |
IF ERRORLEVEL 1 ( | |
CALL :db "Attempting next scan." | |
MAGICK convert -background "%imRgb%" -fill red -size 165x100 -pointsize 24 label:FAILED output\!rotFile!.jpg | |
GOTO :main | |
) | |
) | |
CALL :db "deleting !rotFile!." | |
del temp\!rotFile! | |
CALL :db "ended successfully" | |
ENDLOCAL | |
EXIT /B 0 | |
REM End :main ------------------------------------------------- | |
REM Begin :getTrim ------------------------------------------------- | |
REM ImageMagick's -trim (autocropping) feature crops right up to the edge of the image. | |
REM If that's what you want, then set padding=0. | |
REM We want some of the background from the scan to show (%padding% pixels on each side). | |
REM So we call MAGICK -trim on our rotated jpg in temp but tell it just to return what | |
REM it would do, not actually do it, using the -verbose option and sending it's output to null. | |
:getTrim | |
SETLOCAL | |
SET function=getTrim | |
SET localFuzz=%1 | |
FOR /F "tokens=* USEBACKQ delims=>=" %%a IN ( | |
`MAGICK temp\!rotFile! -fuzz %1%% -format "%%@" info:` | |
) DO ( | |
FOR /F "tokens=1 delims=x" %%b IN ("%%a") DO ( | |
IF %%b LEQ 0 ( | |
CMD /C EXIT 0 | |
SET lastFuzz=!fuzz! | |
SET /a fuzz-=1 | |
CALL :db "No image found with trim fuzz =!lastFuzz!%%. Attempting to call myself with CALL :getTrim !fuzz!"" | |
CALL :getTrim !fuzz! | |
EXIT /B 0 | |
) | |
) | |
SET verbose="%%a" | |
FOR /F "tokens=1,2,3,4 delims=x+" %%a in ("!verbose!") DO ( | |
SET /a width=%%a+!padding!+!padding! | |
SET /a height=%%b+!padding!+!padding! | |
SET /a offsetX=%%c-!padding! | |
SET /a offsetY=%%d-!padding! | |
IF !offsetX! LSS 0 (set offsetX=0) | |
IF !offsetY! LSS 0 (set offsetY=0) | |
) | |
) | |
CALL :testTrim !width! !height! !offsetX! !offsetY! %inFile% | |
IF ERRORLEVEL 1 ( | |
CALL :db "testTrim failed with fuzz=%fuzz%%%." | |
IF %fuzz% GEQ 37 ( | |
CMD /C EXIT 0 | |
SET /a fuzz-=1 | |
CALL :db "Lowest fuzz not yet tried. Attempting to call myself with CALL :getTrim !fuzz!"" | |
CALL :getTrim !fuzz! | |
) ELSE ( | |
CALL :db "could not produce a good -trim. Lowest fuzz tried %fuzz%%%." | |
CALL :db "exiting." | |
ENDLOCAL | |
EXIT /B 1 | |
) | |
) ELSE ( | |
CALL :db "Found a good -trim fuzz. Calling :saveOutfile !width!x!height!+!offsetX!+!offsetY!" | |
CALL :saveOutfile !width! !height! !offsetX! !offsetY! | |
CALL :db "exiting." | |
ENDLOCAL | |
EXIT /B 0 | |
) | |
EXIT /B %ERRORLEVEL% | |
REM End :getTrim -------------------------------------------- | |
REM Begin :testTrim ------------------------------------------------- | |
REM Sometimes our best fuzz for trimming most images overtrims for some images. | |
REM Here and in :testLinewe take what :getTrim last told us what would be good | |
REM "trim lines" andtest those lines to see if they are consistently close to our | |
REM background colour (%imRgb%). If all four sides are black then | |
REM return for cropping. If a side isn't black then lower %fuzz% and CALL :getTrim | |
REM again. | |
REM | |
:testTrim | |
SETLOCAL | |
SET function=testTrim | |
CALL :db "started with %1 %2 %3 %4 %5" | |
SET /a "rightX=%1+%3" | |
SET /a "bottomY=%2+%4" | |
CALL :testLine %1 1 %3 %4 top | |
IF ERRORLEVEL 1 ( | |
CALL :db "top trimline not %imRGB%" | |
ENDLOCAL | |
EXIT /B 2 | |
) | |
CALL :testLine 1 %2 %3 %4 left | |
IF ERRORLEVEL 1 ( | |
CALL :db "left trimline not %imRGB%" | |
ENDLOCAL | |
EXIT /B 2 | |
) | |
CALL :testLine %1 1 %3 !bottomY! bottom | |
IF ERRORLEVEL 1 ( | |
CALL :db "bottom trimline not %imRGB%" | |
ENDLOCAL | |
EXIT /B 2 | |
) | |
CALL :testLine 1 %2 !rightX! %4 right | |
IF ERRORLEVEL 1 ( | |
CALL :db "right trimline not %imRGB%" | |
ENDLOCAL | |
EXIT /B 2 | |
) | |
CALL :db "ended successfully" | |
ENDLOCAL | |
EXIT /B 0 | |
REM End :testTrim ------------------------------------------------- | |
REM Begin :testLine ------------------------------------------------- | |
REM Test if 1 pixel wide line along padded auto -trim line is our black background color. | |
REM Our black produces mean values of about 0.22. A small standard of deviation is | |
REM also good, meaning it was consistently 0.22 and not - say - alternating between | |
REM 0.11 and 0.33. | |
:testLine | |
SETLOCAL | |
SET function=testLine | |
CALL :db "started %1 %2 %3 %4 %5" | |
FOR /F "tokens=1,2 USEBACKQ delims=," %%a IN (`MAGICK temp\!rotFile! -crop %1x%2+%3+%4 -format "%%[fx:(mean*1000)],%%[fx:standard_deviation]\n" info:`) DO ( | |
SET mean=%%~na | |
SET /a "delta=%value%-!mean!" | |
If !delta! GEQ 0 (set abDelta=!delta!) ELSE SET /a "abDelta=0-!delta!" | |
IF !abDelta! GEQ 10 ( | |
CALL :db "failed. " | |
ENDLOCAL | |
EXIT /B 1 | |
) | |
) | |
CALL :db "ended. " | |
ENDLOCAL | |
EXIT /B 0 | |
REM End :testLine ------------------------------------------------- | |
REM Begin :db ---------------------------------------------- | |
:db | |
CALL :format %fmtString% %DATE% %TIME% %inFile% " " %function%:%ERRORLEVEL% " " %1 | |
EXIT /B %ERRORLEVEL% | |
REM Begin :db ---------------------------------------------- | |
:format fmt str1 str2 ... -- outputs columns of strings right or left aligned | |
:: -- fmt [in] - format string specifying column width and alignment, i.e. "[-10][10][10]" | |
:$created 20060101 :$changed 20091130 :$categories Echo | |
:$source https://www.dostips.com | |
SET dbErrLvl=%ERRORLEVEL% | |
SETLOCAL | |
set "fmt=%~1" | |
set "line=" | |
set "spac= " | |
set "i=1" | |
for /f "tokens=1,2 delims=[" %%a in ('"echo..%fmt:]=&echo..%"') do ( | |
set /a i+=1 | |
call call set "subst=%%%%~%%i%%%spac%%%%%~%%i%%" | |
if %%b0 GEQ 0 ( | |
call set "subst=%%subst:~0,%%b%%" | |
) ELSE ( | |
call set "subst=%%subst:~%%b%%" | |
) | |
call set "const=%%a" | |
call set "line=%%line%%%%const:~1%%%%subst%%" | |
) | |
echo.%line% | |
EXIT /B %ERRORLEVEL% | |
REM Begin :batchDumb ---------------------------------------------- | |
:batchDumb | |
SET inFile=%1 | |
CALL :db "started. " | |
SET "rotFile=%inFile:~0,-4%-rotated.jpg" | |
SET "outFile=%inFile:~0,-4%.jpg" | |
SETLOCAL | |
SET function=batchDumb | |
CALL :db "ended. Set inFile to %inFile%, rotFile to %rotFile%, and outfile to %outFile%." | |
ENDLOCAL | |
EXIT /B 0 | |
REM Begin :batchDumb ---------------------------------------------- | |
REM Begin :testFolder ---------------------------------------------- | |
:testFolder | |
SETLOCAL | |
SET function=testFolder | |
CALL :db "started. Testing for .bmps." | |
IF NOT EXIST *.bmp ( | |
CALL :db "Folder %CD% contains no bitmap (.bmp) files. Exiting batch." | |
EXIT 1 | |
) | |
ENDLOCAL | |
EXIT /B 0 | |
REM End :testFolder ---------------------------------------------- | |
REM Begin :saveOutfile ---------------------------------------------- | |
:saveOutfile | |
SETLOCAL | |
SET function=saveOutfile | |
MAGICK temp\!rotFile! -crop %1x%2+%3+%4 output\!outFile! | |
CALL :db "saving to temp\!outfile!." | |
ENDLOCAL | |
EXIT /B 0 | |
REM Begin :saveOutfile ---------------------------------------------- | |
REM Begin :rotateOriginal -------------------------------------------- | |
REM ImageMagick didn't like to straighten (-deskew) the large images we have | |
REM so we make a smaller copy, straighten it, and use the rotation value to | |
REM rotate the original. | |
:rotateOriginal | |
SET function=rotateOriginal | |
SETLOCAL | |
SET function=rotateOriginal | |
CALL :db "started. Working on !inFile!." | |
MAGICK !inFile! -resize 2000 temp\smaller.jpg | |
FOR /f "tokens=* USEBACKQ" %%a IN (`MAGICK temp\smaller.jpg -background "%imRGB%" -deskew 20%% -print %%[deskew:angle]\n null:`) DO ( | |
MAGICK !inFile! -background "%imRGB%" -rotate "%%a" -quality "100" temp\!rotFile! | |
CALL :db "ended successfully. ImageMagick rotated %%a degrees." | |
) | |
del temp\smaller.jpg | |
ENDLOCAL | |
EXIT /B 0 | |
REM End :rotateOriginal -------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment