File size: 10,590 Bytes
609d54c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
"""
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")