File size: 4,046 Bytes
b4f2238 | 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | """Business logic services."""
import io
import logging
from PIL import Image
logger = logging.getLogger(__name__)
def remove_exif_orientation(image: Image.Image, *, exif_orientation_tag: int) -> Image.Image:
try:
exif = image.getexif()
if not exif:
return image
orientation = exif.get(exif_orientation_tag)
if not orientation or orientation == 1:
return image
if image.mode == "RGBA":
new_image = Image.new("RGBA", image.size)
else:
new_image = Image.new("RGB", image.size)
new_image.paste(image)
return new_image
except Exception as e:
logger.warning(f"移除 EXIF 方向标签失败: {e}")
return image
def clean_image(image_data: bytes, *, img_format: str, exif_orientation_tag: int) -> bytes:
img: Image.Image = Image.open(io.BytesIO(image_data))
# 检查是否有需要处理的 EXIF 方向
fmt = (img_format or "jpeg").upper()
if fmt == "JPEG":
try:
exif = img.getexif()
orientation = exif.get(exif_orientation_tag, 1) if exif else 1
except Exception:
orientation = 1
# 无 EXIF 旋转且模式正常,直接返回原始字节,避免二次压缩
if orientation in (1, None) and img.mode == "RGB":
return image_data
img = remove_exif_orientation(img, exif_orientation_tag=exif_orientation_tag)
output = io.BytesIO()
if fmt == "JPEG":
if img.mode in ("RGBA", "P"):
background: Image.Image = Image.new("RGB", img.size, (255, 255, 255))
if img.mode == "RGBA":
background.paste(img, mask=img.split()[3])
else:
background.paste(img)
img = background
elif img.mode != "RGB":
img = img.convert("RGB")
img.save(output, format="JPEG", quality=85, optimize=True)
else:
img.save(output, format=fmt)
return output.getvalue()
def resize_to_paper_size(
image_bytes: bytes,
*,
paper_size: str,
paper_sizes: dict[str, tuple[int, int]],
dpi: int = 300,
) -> bytes:
img: Image.Image = Image.open(io.BytesIO(image_bytes))
width, height = img.size
target_width_mm, target_height_mm = paper_sizes[paper_size]
target_width = int(target_width_mm * dpi / 25.4)
target_height = int(target_height_mm * dpi / 25.4)
target_ratio = target_width / target_height
img_ratio = width / height
if img_ratio > target_ratio:
new_width = target_width
new_height = int(target_width / img_ratio)
else:
new_height = target_height
new_width = int(target_height * img_ratio)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
background = Image.new("RGB", (target_width, target_height), (255, 255, 255))
x = (target_width - new_width) // 2
y = (target_height - new_height) // 2
background.paste(img, (x, y))
output = io.BytesIO()
background.save(output, format="JPEG", quality=85)
return output.getvalue()
def rotate_image_for_output(image_data: bytes, *, rotation: int, img_format: str) -> bytes:
if rotation not in (0, 90, 180, 270):
rotation = 0
img: Image.Image = Image.open(io.BytesIO(image_data))
pillow_angle = (360 - rotation) % 360
if pillow_angle != 0:
img = img.rotate(pillow_angle, expand=True)
output = io.BytesIO()
fmt = (img_format or "jpeg").upper()
if fmt == "JPEG":
if img.mode in ("RGBA", "P"):
background: Image.Image = Image.new("RGB", img.size, (255, 255, 255))
if img.mode == "RGBA":
background.paste(img, mask=img.split()[3])
else:
background.paste(img)
img = background
elif img.mode != "RGB":
img = img.convert("RGB")
img.save(output, format="JPEG", quality=85, optimize=True)
else:
img.save(output, format=fmt)
return output.getvalue()
|