File size: 3,475 Bytes
6db48b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
"""图片压缩工具"""
import io
from PIL import Image
from typing import Optional


def compress_image(
    image_data: bytes,
    max_size_kb: int = 200,  # 默认200KB
    quality_start: int = 85,
    quality_min: int = 20,
    max_dimension: int = 2048
) -> bytes:
    """
    压缩图片到指定大小以内

    Args:
        image_data: 原始图片数据
        max_size_kb: 最大文件大小(KB)
        quality_start: 起始压缩质量(1-100)
        quality_min: 最低压缩质量(1-100)
        max_dimension: 最大边长(像素)

    Returns:
        压缩后的图片数据
    """
    max_size_bytes = max_size_kb * 1024

    # 如果原图已经小于目标大小,直接返回
    if len(image_data) <= max_size_bytes:
        return image_data

    try:
        # 打开图片
        img = Image.open(io.BytesIO(image_data))

        # 转换为 RGB(处理 RGBA 等格式)
        if img.mode in ('RGBA', 'LA', 'P'):
            background = Image.new('RGB', img.size, (255, 255, 255))
            if img.mode == 'P':
                img = img.convert('RGBA')
            background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
            img = background
        elif img.mode != 'RGB':
            img = img.convert('RGB')

        # 如果图片尺寸过大,先缩小
        width, height = img.size
        if width > max_dimension or height > max_dimension:
            ratio = min(max_dimension / width, max_dimension / height)
            new_width = int(width * ratio)
            new_height = int(height * ratio)
            img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)

        # 逐步降低质量直到满足大小要求
        quality = quality_start
        compressed_data = None

        while quality >= quality_min:
            output = io.BytesIO()
            img.save(output, format='JPEG', quality=quality, optimize=True)
            compressed_data = output.getvalue()

            if len(compressed_data) <= max_size_bytes:
                break

            quality -= 5

        # 如果还是太大,进一步缩小尺寸
        if len(compressed_data) > max_size_bytes:
            width, height = img.size
            while len(compressed_data) > max_size_bytes and max(width, height) > 512:
                width = int(width * 0.9)
                height = int(height * 0.9)
                img_resized = img.resize((width, height), Image.Resampling.LANCZOS)

                output = io.BytesIO()
                img_resized.save(output, format='JPEG', quality=quality_min, optimize=True)
                compressed_data = output.getvalue()

        original_size_kb = len(image_data) / 1024
        compressed_size_kb = len(compressed_data) / 1024
        compression_ratio = (1 - compressed_size_kb / original_size_kb) * 100

        print(f"[图片压缩] {original_size_kb:.1f}KB → {compressed_size_kb:.1f}KB (压缩 {compression_ratio:.1f}%)")

        return compressed_data

    except Exception as e:
        print(f"[图片压缩] 压缩失败,返回原图: {e}")
        return image_data


def compress_images(images: list[bytes], max_size_kb: int = 200) -> list[bytes]:
    """
    批量压缩图片

    Args:
        images: 图片数据列表
        max_size_kb: 最大文件大小(KB)

    Returns:
        压缩后的图片数据列表
    """
    return [compress_image(img, max_size_kb) for img in images]