SoFunction
Updated on 2025-04-06

Python uses PIL for image compression

Sometimes when sending some files such as PPT and Word, the files are too large because the pictures in the file are too large and cannot be sent. All pictures in the file can be compressed. The following code is compressed according to the user-defined target size (such as 30KB or 40KB) and ensure the sharpness of the picture as much as possible.

High-quality scaling: Use LANCZOS resampling algorithm to ensure the sharpness of the zoomed image.

Sharpening enhancement: Sharpen the image after compression to further improve clarity.

Intelligent judgment: Prioritize reducing quality, avoid unnecessary scaling, and reduce clarity loss.

from PIL import Image, ImageFilter
import io
 
 
def compress_image(input_path, output_path, max_size_kb, quality=85, step=5):
    """
     Compress the picture below the specified size and ensure clarity as much as possible.
     :param input_path: Enter the image path
     :param output_path: output image path
     :param max_size_kb: Target size (unit: KB)
     :param quality: Initial compression quality (default 85)
     :param step: each time the mass is reduced (default 5)
     """
    # Open the picture    img = (input_path)
 
    # Convert the image to RGB mode (if it is RGBA or other mode)    if  in ('RGBA', 'LA'):
        img = ('RGB')
 
    # Create a byte stream object    img_byte_arr = ()
 
    # Save the image to the byte stream, the initial quality is quality    (img_byte_arr, format='JPEG', quality=quality)
 
    # Get the current image size    current_size = len(img_byte_arr.getvalue()) / 1024  # Convert to KB 
    # If the image size exceeds the maximum limit, gradually reduce the quality    while current_size > max_size_kb and quality > 10:
        quality -= step
        img_byte_arr = ()
        (img_byte_arr, format='JPEG', quality=quality)
        current_size = len(img_byte_arr.getvalue()) / 1024
 
    # If the image size still exceeds the maximum limit, adjust the image size    while current_size > max_size_kb:
        width, height = 
        # Calculate the scaling ratio to ensure that the image size is close to the target size        scale_factor = (max_size_kb / current_size) ** 0.5
        new_width = int(width * scale_factor)
        new_height = int(height * scale_factor)
        img = ((new_width, new_height), )
        img_byte_arr = ()
        (img_byte_arr, format='JPEG', quality=quality)
        current_size = len(img_byte_arr.getvalue()) / 1024
 
    # Sharpen the image    img = ()
 
    # Save the compressed picture    with open(output_path, 'wb') as f:
        (f, format='JPEG', quality=quality)
 
    print(f"Compressed image size: {current_size:.2f} KB")
 
 
input_image = r"E:\Desktop\"
output_image = r"E:\Desktop\"
target_size_kb = 35  # Customize the target size, for example 30KBcompress_image(input_image, output_image, max_size_kb=target_size_kb)

Python+PIL will compress the image just 200KB

Solution

Compress the image to below the target size, and then fill all the difference with "0".

Core content

How to make up for the core content, the following two ideas are provided:

The first type (save):

① Open the image file and convert it to BytesIO

② Calculate the difference (target size - compressed size) and make up with "\x00"

③ Save

The second type (save2):

① cmd generates a file of the specified size

② Write the compressed binary stream to the generated file

File structure


| - - new
| - - old
| - - | - -

Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# 

from PIL import Image
from io import BytesIO
from os import system
from os import listdir
from os import remove
from  import getsize
from  import exists


__author__ = 'one-ccs'

"""
 Function: Put ".\old"All pictures in the directory are compressed and filled to 200KB and stored in ".\new" In the directory.
 """


settings = {
    'loadPath': r'.\old',  # The path to compressed image    'savePath': r'.\new',  # Save the path    'size': 200,
    'quality': 90
}


class ImageFixCompressor():

    def __init__(self) -> None:
         = None
         = None
         = None
         = 0
         = 0
         = 0

         = 'JPEG'
         = 200 # Target size (KB)         = 90     # Compression ratio If the image is larger than the target size after compression, the value should be reduced.
    def split_path(self, path:str='') -> tuple:
        """
         Extract the path and file name in the path.
         """
        if not isinstance(path, str):
            raise ValueError(f'parameter "path" The data type should be "str", But it was passed in "{type(path)}" type.')

        # Determine whether '/' '\' is used as the directory separator        flag = path[::-1].find('/')
        if flag == -1:
            flag = path[::-1].find('\\')
            if flag == -1:
                raise ValueError(f'parameter "path" The data type should be "str", But it was passed in "{type(path)}" type.')

        name = path[-flag:]
        path = path[:-flag]

        return (path, name)

    def full_path(self, path) -> str:
        return fr'{path}\{}'

    def open(self, imgPath:str) -> None:
        """
         Open the image file

         :Path: Image file path.
         """
        try:
            _,  = self.split_path(imgPath)
             = getsize(imgPath)
             = (imgPath)

            print(f'Open: "{imgPath}" success; size: { / 1024:.2f} KB.')
        except Exception as e:
            print(f'mistake: document "{imgPath}" Open failed; Reason: "{e}".')

    def show(self) -> None:
        ()

    def compress(self, format='JPEG', quality=None) -> None:
        if format == 'PNG' or format == 'png':
             = 'PNG'
        if quality:
             = quality

        ()
         = BytesIO()
        (, format=, quality=)

    def save(self, savePath:str, cover:bool=False, name=None) -> None:
        if cover:
            mode = 'wb+'
        else:
            mode = 'rb+'

        try:
             = () / 1024
            # ~ Make up for size            for i in range(0,  * 1024 - ()):
                (b'\x00')

            with open(self.full_path(savePath), mode) as fb:
                (())

             = getsize(self.full_path(savePath)) / 1024
            print(fr'save: "{self.full_path(savePath)}" success; 压缩size: {:.2f} KB; savesize: {:.2f} KB.')
        except Exception as e:
            print(f'mistake: "{}" Save failed; Reason: "{e}".')

    def save2(self, savePath:str, cover:bool=False, name=None) -> None:
        if cover:
            if exists(self.full_path(savePath)):
                remove(self.full_path(savePath))
        else:
            print(f'abnormal: document "{savePath}" Already exists, 已放弃save.')
            return

        system('@echo off')
        system(f'fsutil file createnew {self.full_path(savePath)} { * 1024}')

        try:
            with open(self.full_path(savePath), 'rb+') as fb:
                (())
                 = () / 1024
             = getsize(self.full_path(savePath)) / 1024
            print(fr'save: "{self.full_path(savePath)}" success; 压缩size: {:.2f} KB; savesize: {:.2f} KB.')
        except Exception as e:
            print(f'mistake: "{}" Save failed; Reason: "{e}".')


def main(args):
    compressor = ImageFixCompressor()

    oldImgPaths = listdir(settings['loadPath'])

    for oldImgPath in oldImgPaths:
        fullPath = f"{settings['loadPath']}\{oldImgPath}"

        (fullPath)
        ()
        (settings['savePath'], cover=True)
        # ~ return

    return 0

if __name__ == '__main__':
    import sys
    (main())

This is the end of this article about Python using PIL for image compression. For more related Python PIL compression image content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!