Skip to content

Instantly share code, notes, and snippets.

@pystardust
Created May 1, 2022 21:09
Show Gist options
  • Save pystardust/48b36f358839d49a5c29509f4d40034c to your computer and use it in GitHub Desktop.
Save pystardust/48b36f358839d49a5c29509f4d40034c to your computer and use it in GitHub Desktop.
Change contrast of pdf using python
"""
# pdf_contrast.py
Modify contrast of pdf
## Install
```
pip install Pillow pdf2image img2pdf tqdm
```
> Save this file as pdf_contrast.py
## USAGE
```
$ python pdf_contrast.py 2.3 -i in.pdf -o out.pdf
Loading pdf in.pdf
Pages: 48
Contrast x2.3: 100%|███████████████████████| 48/48 [00:02<00:00, 18.42pages/s]
Saving pdf to out.pdf
```
"""
from argparse import ArgumentParser
import io
from PIL import ImageEnhance
import img2pdf
import pdf2image
from tqdm import tqdm
def pdf_contrast(input_file: str, contrast: float, output_file: str):
"""
Create a new pdf corresponding to the contrast multiplier
`input_file`: name the of the input_file
`contrast`: contrast multiplier. 1 corresponds to no change
`output_file`: name of the file to be saved
"""
print(f'Loading pdf {input_file}')
input_images = pdf2image.convert_from_path(input_file)
print(f'Pages: {len(input_images)}')
output_images: list[bytes] = []
for img in tqdm(input_images,
desc=f"Contrast x{contrast}",
unit="pages"
):
enhancer = ImageEnhance.Contrast(img)
out_im = enhancer.enhance(contrast)
out_img_bytes = io.BytesIO()
out_im.save(out_img_bytes, format="JPEG")
output_images.append(out_img_bytes.getvalue())
print(f'Saving pdf to {output_file}')
with open(output_file, "wb") as outf:
img2pdf.convert(*output_images, outputstream=outf)
if __name__ == "__main__":
input_file = "in.pdf"
contrast = 1.2
output_file = f"out_contrast{contrast}.pdf"
parser = ArgumentParser(description="Modify contrast of pdfs")
parser.add_argument('-i', '--input',
help="Input filename",
dest="input_file")
parser.add_argument('-o', '--output',
help="Output file name",
dest="output_file")
parser.add_argument('contrast', type=float,
help="""
Contrast multipler, must be a float.
1 corresponds to no change (1x).
2 corresponds to double (2x).
0.5 corresponds to half (0.5x).
""")
args = parser.parse_args()
pdf_contrast(args.input_file, args.contrast, args.output_file)
@chapmanjacobd
Copy link

chapmanjacobd commented Nov 30, 2023

The problem with the tiled version is that it applies the transformation across each tile rather than the whole image so you end up with inconsistent, blocky output.

Contrast: Original image is blended with a gray image of the same size. Here's the only point, where some calculation takes place. The gray value is determined by the mean of the original image' grayscale version.
https://stackoverflow.com/questions/59166448/whats-the-formula-used-in-pil-imageenhance-enhance-feature-for-color-brightnes

I exposed the other PIL ImageEnhance methods:

#!/usr/bin/python3

import argparse
import io
import os

import img2pdf
import pdf2image
from PIL import ImageEnhance
from tqdm import tqdm


def pdf_contrast(args):
    input_images = pdf2image.convert_from_path(args.input_path)
    print(f'Loaded {len(input_images)} pages')

    output_images: list[bytes] = []
    for img in tqdm(input_images, unit="pages"):
        for method in ['Brightness', 'Contrast', 'Color', 'Sharpness']:
            val = getattr(args, method.lower())
            if val != 100:
                enhancer = getattr(ImageEnhance, method)(img)
                img = enhancer.enhance(val / 100)

        out_img_bytes = io.BytesIO()
        img.save(out_img_bytes, format="JPEG")
        output_images.append(out_img_bytes.getvalue())

    print(f'Saving {args.output_path}')
    with open(args.output_path, "wb") as outf:
        img2pdf.convert(*output_images, outputstream=outf)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--brightness", "-b", type=int, default=100)
    parser.add_argument("--contrast", "-c", type=int, default=100)
    parser.add_argument("--color", "-C", type=int, default=100)
    parser.add_argument("--sharpness", "-s", type=int, default=100)

    parser.add_argument("input_path", help="Input PDF file")
    parser.add_argument("output_path", nargs='?', help="Output PDF file")
    args = parser.parse_args()

    if args.output_path is None:
        params = []
        if args.contrast != 100:
            params.append(f"c{args.contrast}")
        if args.brightness != 100:
            params.append(f"b{args.brightness}")
        if args.color != 100:
            params.append(f"C{args.color}")
        if args.sharpness != 100:
            params.append(f"s{args.sharpness}")

        suffix = '.' + '.'.join(params) + '.pdf'
        args.output_path = os.path.splitext(args.input_path)[0] + suffix

    pdf_contrast(args)
$ pdf_contrast.py out.pdf -b 115 -c 120 -C 70 -s 80
Loaded 56 pages
100%|███████████████████| 56/56 [00:08<00:00,  6.82pages/s]
Saving out.c120.b115.C70.s80.pdf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment