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()