fc / apps /image_rotation /services /transform /image_transform.py
jun2024's picture
Upload 2241 files
b4f2238 verified
Raw History Blame Contribute Delete
4.05 kB
"""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()