""" High-Quality Image Converter Module This module provides a class-based image converter that preserves original quality and aspect ratio during format conversion. Supports all major image formats. """ from .image_base import ImageBase from PIL import Image import os from typing import Dict, Optional, Tuple from custom_logger import logger_config import pillow_heif pillow_heif.register_heif_opener() class Converter(ImageBase): """ High-quality image format converter that preserves original quality and aspect ratio. This class handles conversion between various image formats while maintaining the highest possible quality and exact dimensional preservation. """ def __init__(self): super().__init__("converter") def _validate_output_format(self, output_format: str) -> str: """ Validate and normalize the output format. Args: output_format: Desired output format Returns: Normalized PIL format name Raises: ValueError: If format is not supported """ if not output_format or not output_format.strip(): raise ValueError("Output format cannot be empty") format_clean = output_format.lower().strip() if format_clean not in self.supported_formats: supported_list = ', '.join(self.supported_formats.keys()) raise ValueError(f"Unsupported format '{output_format}'. Supported: {supported_list}") return self.supported_formats[format_clean] def _get_optimal_save_settings(self, format_name: str) -> Dict: """ Get optimal save settings for maximum quality preservation. Args: format_name: PIL format name (e.g., 'JPEG', 'PNG') Returns: Dictionary of save settings for the format """ settings = {} if format_name == 'JPEG': settings = { 'quality': 100, # Maximum quality 'optimize': False, # Don't optimize (can reduce quality) 'progressive': False, # Standard baseline JPEG 'subsampling': 0 # No chroma subsampling (4:4:4) } elif format_name == 'PNG': settings = { 'optimize': False, # Don't optimize to preserve exact quality 'compress_level': 1 # Minimal compression for maximum quality } elif format_name == 'WEBP': settings = { 'lossless': True, # Lossless compression 'quality': 100, # Maximum quality (for fallback) 'method': 6 # Best compression method } elif format_name == 'TIFF': settings = { 'compression': None # No compression for perfect quality } elif format_name == 'BMP': settings = {} # BMP is always lossless elif format_name == 'GIF': settings = { 'optimize': False # Don't optimize palette } return settings def _preserve_metadata(self, original_image: Image.Image, format_name: str) -> Dict: """ Extract metadata from original image that's compatible with target format. Args: original_image: Original PIL Image object format_name: Target PIL format name Returns: Dictionary of metadata to preserve """ metadata = {} if not hasattr(original_image, 'info') or not original_image.info: return metadata # DPI preservation if 'dpi' in original_image.info and format_name in ['JPEG', 'PNG', 'TIFF', 'WEBP']: metadata['dpi'] = original_image.info['dpi'] # Color profile preservation if 'icc_profile' in original_image.info and format_name in ['JPEG', 'PNG', 'TIFF', 'WEBP']: metadata['icc_profile'] = original_image.info['icc_profile'] # EXIF data preservation if 'exif' in original_image.info and format_name in ['JPEG', 'TIFF', 'WEBP']: metadata['exif'] = original_image.info['exif'] return metadata def _convert_color_mode(self, image: Image.Image, target_format: str) -> Image.Image: """ Convert image color mode if required by target format, preserving quality. Args: image: PIL Image object target_format: Target PIL format name Returns: Image with appropriate color mode """ # Create exact copy to preserve original converted_image = image.copy() # Handle formats that don't support transparency if target_format == 'JPEG' and converted_image.mode in ('RGBA', 'LA', 'P'): logger_config.info("Converting to RGB (JPEG doesn't support transparency)") if converted_image.mode == 'P': # Preserve palette colors during conversion converted_image = converted_image.convert('RGBA') # Create white background and blend with perfect quality background = Image.new("RGB", converted_image.size, (255, 255, 255)) if converted_image.mode in ('RGBA', 'LA'): # Use alpha_composite for perfect quality preservation bg_rgba = background.convert('RGBA') composite = Image.alpha_composite(bg_rgba, converted_image.convert('RGBA')) converted_image = composite.convert('RGB') elif target_format == 'BMP' and converted_image.mode in ('RGBA', 'LA'): logger_config.info("Converting to RGB (BMP doesn't support transparency)") background = Image.new("RGB", converted_image.size, (255, 255, 255)) bg_rgba = background.convert('RGBA') composite = Image.alpha_composite(bg_rgba, converted_image.convert('RGBA')) converted_image = composite.convert('RGB') return converted_image def _verify_conversion_quality(self, original_size: Tuple[int, int], output_path: str) -> bool: """ Verify that the converted image maintains original quality and dimensions. Args: original_size: Original image dimensions (width, height) output_path: Path to converted image Returns: True if verification passes Raises: Exception: If verification fails """ if not os.path.exists(output_path): raise Exception("Output file was not created") # Check file size file_size = os.path.getsize(output_path) logger_config.info(f"✓ Output file created: {file_size:,} bytes") # Verify dimensions are preserved with Image.open(output_path) as verify_img: if verify_img.size != original_size: raise Exception(f"Dimensions changed! Expected: {original_size}, Got: {verify_img.size}") logger_config.info(f"✓ Aspect ratio preserved: {verify_img.size}") return True def convert_image(self, input_file_name: str, output_format: str) -> str: """ Convert an image to the specified format with maximum quality and ratio preservation. Args: input_file: Path to the input image file output_format: Target image format Returns: Path to the converted output file Raises: FileNotFoundError: If input file doesn't exist ValueError: If parameters are invalid Exception: If conversion fails """ try: # Validate inputs self.input_file_name = input_file_name self.input_file_path = f'{self.input_dir}/{self.input_file_name}' self._validate_input_file() target_format = self._validate_output_format(output_format) logger_config.info(f"Starting conversion: {self.input_file_path}") # Open and analyze original image with Image.open(self.input_file_path) as original_image: original_size = original_image.size original_mode = original_image.mode original_format = original_image.format logger_config.info(f"Original - Format: {original_format}, Mode: {original_mode}, Size: {original_size}") # Convert color mode if necessary converted_image = self._convert_color_mode(original_image, target_format) # Verify size preservation (safety check) if converted_image.size != original_size: raise Exception(f"Size changed during conversion! Original: {original_size}, Current: {converted_image.size}") # Generate output path output_path = self._generate_output_path(output_format) # Get optimal save settings save_settings = self._get_optimal_save_settings(target_format) # Preserve metadata metadata = self._preserve_metadata(original_image, target_format) save_settings.update(metadata) logger_config.info(f"Converting to {target_format} with maximum quality") # Perform conversion with quality preservation converted_image.save(output_path, format=target_format, **save_settings) # Verify conversion quality self._verify_conversion_quality(original_size, output_path) logger_config.info(f"✓ Conversion completed successfully: {output_path}") return output_path except Exception as e: logger_config.info(f"Image conversion failed: {str(e)}") return None # Example usage if __name__ == "__main__": converter = Converter() converter.convert_image("image/test.HEIC", "png")