160 lines
7.3 KiB
Python
160 lines
7.3 KiB
Python
from PIL import Image, ImageFont, ImageDraw
|
|
import requests
|
|
import numpy as np
|
|
import cv2
|
|
import os
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
class PostImageManager:
|
|
def __init__(self, logger, font_path):
|
|
self.logger = logger
|
|
self.font_path = font_path
|
|
|
|
# 폰트 로드
|
|
self.font_load()
|
|
|
|
|
|
def font_load(self):
|
|
# 폰트 로드
|
|
try:
|
|
self.font = ImageFont.truetype(self.font_path, 36)
|
|
self.logger.log(f"폰트 로드 성공: {self.font_path}", level=logging.DEBUG)
|
|
except Exception as e:
|
|
self.logger.log(f"커스텀 폰트 로드 실패 ({self.font_path}): {e}", level=logging.WARNING)
|
|
try:
|
|
# 기본 폰트 사용
|
|
self.font = ImageFont.load_default()
|
|
self.logger.log("기본 폰트를 사용합니다.", level=logging.INFO)
|
|
except Exception as e2:
|
|
self.logger.log(f"기본 폰트 로드도 실패: {e2}", level=logging.ERROR)
|
|
# 최후의 수단으로 None 설정
|
|
self.font = None
|
|
|
|
def save_image_to_path(self, image, path):
|
|
try:
|
|
if image:
|
|
# 이미지를 저장 경로에 저장
|
|
self.logger.log(f"이미지 저장 완료 : {path}", level=logging.INFO)
|
|
image.save(path, format='PNG')
|
|
return path
|
|
|
|
except Exception as e:
|
|
raise RuntimeError(f"이미지 저장 중 오류 발생: {e}")
|
|
|
|
def add_watermark(self, image_data, watermark_text="Watermark", opacity_percent=30, angle=30, font_size=36):
|
|
"""
|
|
이미지에 텍스트 워터마크를 이미지 전체에 걸쳐서 추가하는 함수
|
|
:param image_data: PIL 이미지 객체
|
|
:param watermark_text: 워터마크로 추가할 텍스트
|
|
:param opacity_percent: 워터마크의 투명도 (0~100)
|
|
:param angle: 워터마크 텍스트 회전 각도 (기본 30도)
|
|
:param font_size: 워터마크 텍스트의 폰트 크기 (기본 36)
|
|
:return: 워터마크가 추가된 이미지
|
|
"""
|
|
try:
|
|
if isinstance(image_data, np.ndarray):
|
|
image_data = Image.fromarray(cv2.cvtColor(image_data, cv2.COLOR_BGR2RGB))
|
|
|
|
# 폰트가 로드되지 않은 경우 원본 이미지 반환
|
|
if self.font is None:
|
|
self.logger.log("폰트가 로드되지 않아 워터마크를 추가할 수 없습니다. 원본 이미지를 반환합니다.", level=logging.WARNING)
|
|
return image_data
|
|
|
|
# 이미지 복사본 생성
|
|
watermark_image = image_data.copy()
|
|
|
|
# 폰트 설정 (안전한 폰트 로딩)
|
|
try:
|
|
# self.font가 있으면 크기만 조정해서 새 폰트 생성
|
|
if hasattr(self, 'font_path') and os.path.exists(self.font_path):
|
|
font = ImageFont.truetype(self.font_path, font_size)
|
|
else:
|
|
# 크기를 조정할 수 없으면 기존 폰트 사용
|
|
font = self.font
|
|
except Exception as e:
|
|
self.logger.log(f"폰트 크기 조정 실패: {e}. 기본 폰트를 사용합니다.", level=logging.WARNING)
|
|
font = self.font
|
|
|
|
# 텍스트 투명도를 0~255로 변환
|
|
opacity = int(255 * (opacity_percent / 100))
|
|
|
|
# 텍스트 크기 측정 (textbbox 사용)
|
|
draw = ImageDraw.Draw(watermark_image)
|
|
bbox = draw.textbbox((0, 0), watermark_text, font=font)
|
|
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
|
|
# 이미지 크기
|
|
width, height = image_data.size
|
|
|
|
# 워터마크 레이어 생성
|
|
watermark_layer = Image.new("RGBA", (width, height)) # RGBA 이미지 생성
|
|
|
|
# 지그재그 간격 설정
|
|
zigzag_step = int(text_height * 2) # Y축의 지그재그 간격
|
|
|
|
|
|
# 이미지 전체에 반복적으로 워터마크 텍스트 그리기 (지그재그 형태)
|
|
for y in range(0, height, zigzag_step):
|
|
for x in range(0, width, int(text_width * 3)): # 3배 너비 간격으로 반복
|
|
# 텍스트가 한 줄씩 지그재그 형태로 X축을 교차하여 이동
|
|
x_offset = (y // zigzag_step) % 2 * int(text_width * 1.5) # 짝수 행에서는 X축을 약간 이동
|
|
|
|
# 텍스트 레이어 생성
|
|
text_layer = Image.new("RGBA", (text_width, text_height), (255, 255, 255, 0))
|
|
text_draw = ImageDraw.Draw(text_layer)
|
|
|
|
# 텍스트 그리기
|
|
text_draw.text((0, 0), watermark_text, fill=(255, 255, 255, opacity), font=font)
|
|
|
|
# 텍스트 회전
|
|
rotated_text_layer = text_layer.rotate(angle, expand=1)
|
|
|
|
# 회전된 텍스트를 워터마크 레이어에 추가
|
|
watermark_layer.paste(rotated_text_layer, (x + x_offset, y), rotated_text_layer)
|
|
|
|
# 원본 이미지와 워터마크 레이어 합성
|
|
watermark_image = Image.alpha_composite(watermark_image.convert("RGBA"), watermark_layer)
|
|
|
|
# 최종적으로 RGB 형식으로 변환 후 반환
|
|
return watermark_image.convert("RGB")
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"워터마크 추가 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
return image_data
|
|
|
|
def crop_image(self, image, is_thumb=False, crop_percentage=0.01):
|
|
"""이미지를 주어진 퍼센트만큼 크롭하는 함수"""
|
|
if is_thumb:
|
|
crop_percentage = 0.03
|
|
self.logger.log(f"썸네일 이미지 이므로 크롭 3%로 조정", level=logging.DEBUG)
|
|
|
|
width, height = image.size
|
|
left = width * crop_percentage
|
|
top = height * crop_percentage
|
|
right = width * (1 - crop_percentage)
|
|
bottom = height * (1 - crop_percentage)
|
|
|
|
cropped_image = image.crop((left, top, right, bottom))
|
|
|
|
if self.debug:
|
|
# 디버그 모드일 경우 크롭 전후 다양한 비율로 이미지 저장
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
original_image_path = os.path.join(os.getcwd(), f"original_image_{timestamp}.png")
|
|
image.save(original_image_path) # 크롭 전 이미지 저장
|
|
self.logger.log(f"크롭 전 이미지 저장됨: {original_image_path}", level=logging.DEBUG)
|
|
|
|
# 1%, 2%, 3% 크롭 이미지 저장
|
|
crop_alternatives = [0.01, 0.02, 0.03]
|
|
for crop in crop_alternatives:
|
|
left_alt = width * crop
|
|
top_alt = height * crop
|
|
right_alt = width * (1 - crop)
|
|
bottom_alt = height * (1 - crop)
|
|
|
|
cropped_alt_image = image.crop((left_alt, top_alt, right_alt, bottom_alt))
|
|
cropped_image_path = os.path.join(os.getcwd(), f"cropped_image_{int(crop*100)}_{timestamp}.png")
|
|
cropped_alt_image.save(cropped_image_path)
|
|
self.logger.log(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}", level=logging.DEBUG)
|
|
|
|
return cropped_image |