from PIL import ( Image, ImageEnhance, ImageFilter, ImageOps ) from pathlib import Path from typing import Callable, Dict, Any import traceback import os class ImageConverter: SUPPORTED_FORMATS = { ".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff" } def __init__(self): # Prevent PIL decompression bomb crash Image.MAX_IMAGE_PIXELS = None def convert( self, input_path: str, output_path: str, options: Dict[str, Any] | None = None, progress_callback: Callable = None ) -> bool: options = options or {} try: self._update_progress( progress_callback, 10 ) # Validate input if not os.path.exists(input_path): raise FileNotFoundError( input_path ) input_ext = Path(input_path).suffix.lower() output_ext = Path(output_path).suffix.lower() # Validate output format if output_ext not in self.SUPPORTED_FORMATS: raise ValueError( f"Unsupported format: {output_ext}" ) # Open image safely img = Image.open(input_path) # Fix EXIF rotation img = ImageOps.exif_transpose(img) self._update_progress( progress_callback, 25 ) # Convert palette images safely if img.mode == "P": img = img.convert("RGBA") # Optional resize max_size = options.get("max_size") if max_size: img.thumbnail( (max_size, max_size) ) self._update_progress( progress_callback, 40 ) # AI enhancement if options.get( "ai_enhancement", False ): img = self.enhance_image(img) self._update_progress( progress_callback, 60 ) # Quality quality_settings = options.get( "quality_settings", {} ) quality = int( quality_settings.get( "image", 85 ) ) # Create output folder Path(output_path).parent.mkdir( parents=True, exist_ok=True ) # Format handling if output_ext in [".jpg", ".jpeg"]: # JPEG does not support transparency if img.mode in ("RGBA", "LA"): background = Image.new( "RGB", img.size, (255, 255, 255) ) background.paste( img, mask=img.split()[-1] ) img = background else: img = img.convert("RGB") img.save( output_path, "JPEG", quality=quality, optimize=True ) elif output_ext == ".png": compress_level = 9 if quality >= 90: compress_level = 2 elif quality >= 75: compress_level = 5 img.save( output_path, "PNG", optimize=True, compress_level=compress_level ) elif output_ext == ".webp": img.save( output_path, "WEBP", quality=quality, method=6 ) elif output_ext == ".bmp": if img.mode == "RGBA": img = img.convert("RGB") img.save( output_path, "BMP" ) elif output_ext == ".tiff": img.save( output_path, "TIFF", compression="tiff_lzw" ) else: img.save(output_path) self._update_progress( progress_callback, 100 ) print( f"✓ Converted: " f"{os.path.basename(input_path)} " f"→ {output_ext}" ) return True except Exception as e: print( f"Image conversion error: {e}" ) traceback.print_exc() return False def _update_progress( self, callback, value ): try: if callback: callback(value) except Exception as e: print( f"Progress callback error: {e}" ) def enhance_image( self, img ): try: # Contrast enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(1.08) # Sharpness enhancer = ImageEnhance.Sharpness(img) img = enhancer.enhance(1.1) # Slight color boost enhancer = ImageEnhance.Color(img) img = enhancer.enhance(1.03) # Optional smooth sharpen img = img.filter( ImageFilter.SHARPEN ) return img except Exception as e: print( f"Enhancement error: {e}" ) return img