"""图片压缩工具""" 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]