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