File size: 3,733 Bytes
b7dafe5
 
b9f0706
b7dafe5
b9f0706
b7dafe5
b9f0706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7dafe5
 
b9f0706
 
 
 
 
 
 
 
 
 
 
 
b7dafe5
b9f0706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7dafe5
b9f0706
 
 
 
b7dafe5
b9f0706
 
 
 
 
 
 
b7dafe5
b9f0706
b7dafe5
 
b9f0706
b7dafe5
b9f0706
 
 
b7dafe5
b9f0706
 
 
 
b7dafe5
b9f0706
 
 
 
 
 
b7dafe5
b9f0706
 
 
 
b7dafe5
b9f0706
 
 
 
 
b7dafe5
 
 
b9f0706
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
import gradio as gr
from PIL import Image
import pillow_heif
import os
import shutil

# 1. Register HEIF opener with Pillow so it can handle .heic/.heif files
pillow_heif.register_heif_opener()

def format_bytes(size):
    # Helper to make file sizes readable
    power = 2**10
    n = 0
    power_labels = {0 : '', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'}
    while size > power:
        size /= power
        n += 1
    return f"{size:.2f} {power_labels[n]}"

def compress_image(image_path):
    if image_path is None:
        return None, "No image uploaded."

    try:
        # Get original file info
        original_size = os.path.getsize(image_path)
        filename = os.path.basename(image_path)
        name_without_ext = os.path.splitext(filename)[0]
        
        # Define output path
        output_filename = f"{name_without_ext}_compressed.png"
        output_path = os.path.join(os.path.dirname(image_path), output_filename)

        # Open the image
        # pillow-heif handles the HEIC/HEIF format automatically here
        with Image.open(image_path) as img:
            
            # Create a copy of the image to remove specific metadata that might 
            # conflict with saving (though we keep pixel data lossless)
            # We convert to RGBA if it supports transparency, or RGB otherwise
            if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
                img = img.convert('RGBA')
            else:
                img = img.convert('RGB')

            # SAVE AS PNG
            # compress_level=9 is maximum compression (slowest, smallest size)
            # optimize=True performs additional passes to reduce size
            img.save(output_path, "PNG", compress_level=9, optimize=True)

        # Get new file info
        new_size = os.path.getsize(output_path)
        
        # Calculate stats
        diff = new_size - original_size
        status = "Reduced" if diff < 0 else "Increased"
        percent = (abs(diff) / original_size) * 100
        
        info_text = (
            f"Original Size: {format_bytes(original_size)}\n"
            f"Compressed Size: {format_bytes(new_size)}\n"
            f"Difference: {status} by {format_bytes(abs(diff))} ({percent:.1f}%)\n\n"
            f"Note: Converting JPEGs/HEICs (lossy) to PNG (lossless) may increase file size "
            f"because PNG preserves the exact pixel data without compression artifacts."
        )

        return output_path, info_text

    except Exception as e:
        return None, f"Error processing image: {str(e)}"

# 2. Create the Gradio Interface
with gr.Blocks(title="Lossless PNG Compressor") as app:
    gr.Markdown("## 🗜️ Lossless Image Compressor (Max PNG Level)")
    gr.Markdown(
        "Upload any image (JPG, PNG, HEIC, WEBP). It will be converted to **PNG** "
        "saved at **Compression Level 9** (Maximum). \n\n"
        "*Note: This preserves pixel quality perfectly. However, converting a lossy photo (JPG/HEIC) "
        "to lossless PNG often results in a larger file size.*"
    )

    with gr.Row():
        with gr.Column():
            # Input: Expects a file path
            image_input = gr.Image(type="filepath", label="Upload Image")
            submit_btn = gr.Button("Compress / Convert", variant="primary")
        
        with gr.Column():
            # Output: Returns a downloadable file and text stats
            file_output = gr.File(label="Download PNG")
            stats_output = gr.Textbox(label="Compression Stats")

    # Bind function
    submit_btn.click(
        fn=compress_image, 
        inputs=image_input, 
        outputs=[file_output, stats_output]
    )

if __name__ == "__main__":
    app.launch()