File size: 5,260 Bytes
cd922fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import io
import subprocess
from PIL import Image, ImageOps
import cv2
import numpy as np
from pathlib import Path
import piexif

class ImageCompressor:
    def __init__(self):
        self.supported_formats = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.tiff'}
        
    def compress(self, input_path, output_path, method="auto", **kwargs):
        """Main compression entry point"""
        img = Image.open(input_path)
        original_size = Path(input_path).stat().st_size
        
        # Get original format
        original_format = img.format
        
        if method == "auto":
            # Use AI analyzer to determine best method
            from .ai_analyzer import ContentAnalyzer
            analyzer = ContentAnalyzer()
            _, strategy = analyzer.analyze(input_path)
            method = strategy.get("method", "avif")
            kwargs = {**kwargs, **strategy}
        
        # Apply compression based on method
        if method == "avif":
            compressed = self._compress_avif(img, kwargs.get('quality', 75))
            output_path = str(output_path).replace(Path(output_path).suffix, '.avif')
        elif method == "webp_lossless":
            compressed = self._compress_webp_lossless(img, kwargs.get('quality', 90))
            output_path = str(output_path).replace(Path(output_path).suffix, '.webp')
        elif method == "png_optimized":
            compressed = self._compress_png_optimized(img, kwargs.get('colors', 256))
            output_path = str(output_path).replace(Path(output_path).suffix, '.png')
        elif method == "png_quantized":
            compressed = self._compress_png_quantized(img, kwargs.get('colors', 128))
            output_path = str(output_path).replace(Path(output_path).suffix, '.png')
        elif method == "jpeg_high_quality":
            compressed = self._compress_jpeg(img, kwargs.get('quality', 85))
            output_path = str(output_path).replace(Path(output_path).suffix, '.jpg')
        else:
            # Default to WebP
            compressed = self._compress_webp(img, kwargs.get('quality', 80))
            output_path = str(output_path).replace(Path(output_path).suffix, '.webp')
        
        # Save compressed image
        with open(output_path, 'wb') as f:
            f.write(compressed)
        
        compressed_size = Path(output_path).stat().st_size
        ratio = (1 - compressed_size / original_size) * 100
        
        return output_path, ratio
    
    def _compress_avif(self, img, quality=75):
        """AVIF compression - best for photos"""
        output = io.BytesIO()
        
        # Convert RGBA to RGB if needed (AVIF doesn't support alpha well)
        if img.mode == 'RGBA':
            background = Image.new('RGB', img.size, (255, 255, 255))
            background.paste(img, mask=img.split()[-1])
            img = background
        
        # Save as AVIF (requires pillow-avif-plugin)
        img.save(output, format='AVIF', quality=quality, speed=4)
        return output.getvalue()
    
    def _compress_webp_lossless(self, img, quality=90):
        """Lossless WebP for text/screenshots"""
        output = io.BytesIO()
        img.save(output, format='WEBP', lossless=True, quality=quality, method=6)
        return output.getvalue()
    
    def _compress_webp(self, img, quality=80):
        """Standard WebP compression"""
        output = io.BytesIO()
        img.save(output, format='WEBP', quality=quality, method=4)
        return output.getvalue()
    
    def _compress_png_optimized(self, img, colors=256):
        """PNG with color palette reduction"""
        output = io.BytesIO()
        
        # Convert to palette mode
        if colors < 256:
            img = img.quantize(colors=colors, method=Image.MEDIANCUT)
            img.save(output, format='PNG', optimize=True)
        else:
            img.save(output, format='PNG', optimize=True)
        
        return output.getvalue()
    
    def _compress_png_quantized(self, img, colors=128):
        """Heavy PNG quantization for graphics"""
        output = io.BytesIO()
        
        # Reduce colors dramatically
        img_quantized = img.quantize(colors=colors, method=Image.FASTOCTREE)
        img_quantized.save(output, format='PNG', optimize=True)
        
        return output.getvalue()
    
    def _compress_jpeg(self, img, quality=85):
        """JPEG compression"""
        output = io.BytesIO()
        
        # Convert to RGB if needed
        if img.mode in ('RGBA', 'LA', 'P'):
            img = img.convert('RGB')
        
        img.save(output, format='JPEG', quality=quality, optimize=True, progressive=True)
        return output.getvalue()
    
    def smart_resize(self, img, target_dimension=2048):
        """Intelligently resize if image is too large"""
        width, height = img.size
        max_dim = max(width, height)
        
        if max_dim > target_dimension:
            scale = target_dimension / max_dim
            new_size = (int(width * scale), int(height * scale))
            return img.resize(new_size, Image.Resampling.LANCZOS)
        return img

    def remove_metadata(self, image_path):
        """Strip all EXIF metadata"""
        piexif.remove(str(image_path))