A Python script that crops borders from images.
- Reads JPEGs and PNGs
- Saves to PNG and AVIF
- Works on color and grayscale images
- Processes a single file or a whole directory recursively
- Resizes to fit a certain width
- Adjustable thresholds for noise insusceptibility
- Multi-core processing
- Auto-detects a grayscale image and saves it as such
Bundled versions are on the Releases page. They don't require anything else (besides avifenc).
The script requires Python, Pillow and Numpy. The current version has been tested with Python 3.12.2, Pillow 10.3.0 and Numpy 1.26.4. It may work on other versions.
External AVIF encoder is required to save images to AVIF.
usage: autocrop.py [-h] -i INPUT -o OUTPUT [-fr FILLRATIO] [-cd COLORDISTANCE] [-et EXHAUSTIVETHRESHOLD]
[-rct RGBCOLORTHRESHOLD] [-rf] [-rw RESIZEWIDTH] [-rh RESIZEHEIGHT] [-rfw]
[-vrt VERTICALRESIZETARGET] [-hrt HORIZONTALRESIZETARGET] [-ea] [-aep AVIFEXECUTABLEPATH]
[-eq ENCODEQUALITY] [-pcl PNGCOMPRESSIONLEVEL] [-lp LOGPATH] [-nw]
Autocrop script R4
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Input file or directory
-o OUTPUT, --output OUTPUT
Output directory template
-fr FILLRATIO, --fillratio FILLRATIO
Border fill ratio in % (0.0 to 100.0, default=0.4)
-cd COLORDISTANCE, --colordistance COLORDISTANCE
How many colors to treat as a background color (0 to 255, default=16)
-et EXHAUSTIVETHRESHOLD, --exhaustivethreshold EXHAUSTIVETHRESHOLD
How much of a border must be cropped to not trigger exhaustive pass in % (0.0 to 100.0,
default=-1, disabled=-1, always=0)
-rct RGBCOLORTHRESHOLD, --rgbcolorthreshold RGBCOLORTHRESHOLD
How much difference between bands is allowed to save a RGB image as grayscale (0 to 255,
default=9, always=255)
-rf, --resizefit Enable fit resizing
-rw RESIZEWIDTH, --resizewidth RESIZEWIDTH
Target width for fit resizing
-rh RESIZEHEIGHT, --resizeheight RESIZEHEIGHT
Target height for fit resizing
-rfw, --resizefitwidth
Enable width fit resizing
-vrt VERTICALRESIZETARGET, --verticalresizetarget VERTICALRESIZETARGET
Width resize target for vertical images (default=1200)
-hrt HORIZONTALRESIZETARGET, --horizontalresizetarget HORIZONTALRESIZETARGET
Width resize target for horizontal images (default=1920)
-ea, --encodeavif Enable encoding to AVIF
-aep AVIFEXECUTABLEPATH, --avifexecutablepath AVIFEXECUTABLEPATH
Path to Avifenc executable (default=script directory)
-eq ENCODEQUALITY, --encodequality ENCODEQUALITY
Encode quality (0 to 100, default=54)
-pcl PNGCOMPRESSIONLEVEL, --pngcompressionlevel PNGCOMPRESSIONLEVEL
PNG compression level (0 to 9, default=1)
-lp LOGPATH, --logpath LOGPATH
Log file path (default=script directory/autocroplog.txt)
-nw, --nowait Do not wait for a key press after completion
The options -fr
(fill ratio), -cd
(color distance), and -et
(exhaustive pass threshold) are used to determine how aggressive the cropping will be performed. In essence, increasing -fr
may help with high contrast noise on a border, -cd
- with low contrast noise, -et
(disabled by default) - with sporadic noise only on some lines or in the very beginning of an image.
If your images have clean borders, you can try to leave these settings on defaults and check results, they're quite safe on images.
If you have noisy borders, then you may have to increase these values slightly, check the results and adjust them to get desired images. Don't increase then too much as very high values will corrupt the results very easily.
If you want to know more of what exactly these parameters do, please read the "How does it work?" section.
Resize is disabled by default.
There are two resizing algorithms implemented here: usual "fit" and "fit width".
For a more traditional "fit" resizing enable the -rf
switch and set the desired resolution using the -rw
(for width) and -rh
(for height) arguments.
Set the -rfw
switch to enable "fit width" mode. The resizing resolution can be controlled using the -vrt
(sets the target width for a vertical/portrait image) and -hrt
(sets the target width for a horizontal/landscape image) options. The resizing algorihm implemented now is not a "fit" one, but something like "fit width for a rotating screen". So for example for -vrt 1200
and -hrt 1920
a source 2000x3000
image will become a 1200x1800
image, and 3000x2000
will become 1920x1280
.
Process a directory R:\Input
with default thresholds without resize and save files to R:\ResultPng
and R:\ResultAvif
; use avifenc.exe from a script's directory, save log to a script's directory autocroplog.txt
:
python autocrop.py -i R:\Input -o R:\Result -ea
Process a single file R:\test.png
with default thresholds, don't encode to AVIF, don't resize, save to R:\OutputPng
, save log to R:\alog.txt
:
python autocrop.py -i R:\test.png -o R:\Output -lp R:\alog.txt
Process a directory R:\Input
, set fill ratio to 2%, set color distance to 32 colors, set exaustive pass threshold to 5%, save only in PNG to R:\SavesPng
, don't wait on exit:
python autocrop.py -i R:\Input, -o R:\Saves -fr 2 -cd 32 -et 5 -nw
Process R:\test.png
, fit the image to 1920x1080
, don't encode to AVIF and save to R:\SavePng
:
python autocrop.py -i R:\test.png -o R:\Save -rf -rw 1920 -rh 1080
The script loads an image and converts it into a grayscale/luminance-only form. Then it starts processing it line-by-line (row or coulumn, depends on what side of an image is being cropped).
It counts every unique luminance value in the line and how many pixels uses it. The most used luma value becomes a background luma value. But luma values that are neighbors to the background value are treated similarly: this is controlled by the -cd
setting (by an absolute value). So if 255 was determined to be the background value, then (if -cd
is set to 16) all luma values from 239 to 255 are treated as background. And if the background is 128, then the background range is 112-144. This may help to reduce errors from JPEG artifacts and low contrast noise.
Then the script counts all pixels in the line that have a luma value that belongs to the background range. Then it checks whether the portion of non-background pixels exceeds the "fill ratio" (-fr
setting, may help with high contrast noise or unnecessary details, such as page numbers) percentage or not. If it doesn't, then the line still belongs to the border and the script starts checking the next line. If it does, then the scripts stops analyzing at this line and crops the detected border.
Left, right, top and bottom sides are analyzed separately from an outer edge towards center.
The -et
option enables the exhaustive cropping mode (it functions on left and right borders only currently). If percentage of cropped lines is below the -et
value, then the script starts the exhaustive pass. The difference from the previous pass is now the script doesn't stop on a first line that exceeds the fill ratio. Instead, it proceeds to analyze all lines.
The -o
option sets a template for output directories. Then the script generates the directory names by adding Png
for PNGs and Avif
for AVIFs. Also, the script creates output directories for the last subdirectory of an input file. For example, if -o
is set to R:\Output
, -i
is set to R:\Input
, and the file R:\Input\SomeDir1\SomeDir2\test.png
is processed, then the file will be saved as R:\OutputPng\SomeDir2\test.png
as a PNG and R:\OutputAvif\SomeDir2\test.png
as an AVIF.
This script uses an external AVIF encoder because the Pillow AVIF Plugin was always outdated and was restricted to multithreaded processing (the script forces avifenc to use a single thread). After the script has saved an image in PNG, it passes parameters to the avifenc using the saved PNG as source, executes it and waits for it to complete.
Please open an issue if you have an idea (maybe good heuristics to automatically set parameters for each image?😁) or spotted a bug.