| from PIL import Image, ImageDraw | |
| import io | |
| import logging | |
| from datetime import datetime | |
| logger = logging.getLogger(__name__) | |
| def halftone_effect(img, dot_size=10): | |
| """ | |
| Apply a halftone effect to the input image. | |
| """ | |
| grayscale = img.convert('L') | |
| width, height = grayscale.size | |
| halftone_image = Image.new('L', (width, height), color=255) | |
| draw = ImageDraw.Draw(halftone_image) | |
| for x in range(0, width, dot_size): | |
| for y in range(0, height, dot_size): | |
| block_width = min(dot_size, width - x) | |
| block_height = min(dot_size, height - y) | |
| region = grayscale.crop((x, y, x + block_width, y + block_height)) | |
| avg_brightness = sum(region.getdata()) / (block_width * block_height) | |
| max_radius = dot_size // 2 | |
| radius = int((255 - avg_brightness) / 255 * max_radius) | |
| radius = max(0, min(radius, max_radius)) | |
| center_x = x + dot_size // 2 | |
| center_y = y + dot_size // 2 | |
| draw.ellipse( | |
| ( | |
| center_x - radius, | |
| center_y - radius, | |
| center_x + radius, | |
| center_y + radius | |
| ), | |
| fill=0 | |
| ) | |
| return halftone_image | |
| def apply_halftone(image_data, dot_size=10): | |
| start_time = datetime.now() | |
| logger.info(f"Applying halftone effect - {start_time}") | |
| try: | |
| input_image = Image.open(io.BytesIO(image_data)) | |
| logger.info(f"Image opened. Mode: {input_image.mode}, Size: {input_image.size}") | |
| dot_size = max(5, min(20, dot_size)) | |
| logger.info(f"Applying halftone effect with dot size: {dot_size}") | |
| output_image = halftone_effect(input_image, dot_size) | |
| img_io = io.BytesIO() | |
| output_image.save(img_io, 'PNG') | |
| img_io.seek(0) | |
| duration = (datetime.now() - start_time).total_seconds() | |
| logger.info(f"Successfully processed. Duration: {duration} seconds") | |
| return img_io | |
| except Exception as e: | |
| logger.error(f"Halftone processing error: {str(e)}", exc_info=True) | |
| raise |