alexscottcodes commited on
Commit
b9f0706
·
1 Parent(s): b7dafe5

Update compression method and add support for HEIF/HEIC inputs.

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +81 -74
  3. requirements.txt +2 -1
README.md CHANGED
@@ -11,4 +11,4 @@ license: apache-2.0
11
  short_description: A lightweight lossless image compressor.
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
11
  short_description: A lightweight lossless image compressor.
12
  ---
13
 
14
+ This app is a lightweight lossless image compressor written in Gradio and Python. It uses Pillow (and pillow-heif to support HEIC/HEIF inputs) to compress the image by altering the PNG compression level.
app.py CHANGED
@@ -1,94 +1,101 @@
1
  import gradio as gr
2
  from PIL import Image
3
- import tempfile
4
  import os
 
5
 
6
- def compress_image(input_image):
7
- """
8
- Compresses a PIL image object into a lossless WebP format
9
- and returns the file path.
10
- """
11
- if input_image is None:
12
- raise gr.Error("No image uploaded. Please upload an image to compress.")
 
 
 
 
 
 
 
 
 
13
 
14
  try:
15
- # Create a temporary file to store the compressed image.
16
- # We use delete=False so that Gradio can access the file
17
- # after this function returns. Gradio will handle cleanup.
18
- with tempfile.NamedTemporaryFile(suffix=".webp", delete=False) as temp_file:
19
-
20
- # Save the image using Pillow in lossless WebP format
21
- # quality=100 is often recommended with lossless=True
22
- input_image.save(temp_file.name, format="WEBP", lossless=True, quality=100)
 
 
 
 
23
 
24
- output_filepath = temp_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- # Get file sizes for comparison
27
- # We need to get the size of the original PIL image.
28
- # This is a bit tricky, so we'll just show the output size.
29
- # A more complex app might take a file path input to compare.
30
- output_size = os.path.getsize(output_filepath)
31
 
32
- print(f"Image saved to: {output_filepath}")
33
- print(f"Compressed file size: {output_size / 1024:.2f} KB")
 
 
 
 
 
34
 
35
- # Return the file path to the gr.File component
36
- return output_filepath
37
 
38
  except Exception as e:
39
- print(f"Error compressing image: {e}")
40
- raise gr.Error(f"Failed to compress image: {str(e)}")
41
-
42
- # --- Gradio App Interface ---
43
 
44
- # Use gr.Blocks for a more custom layout
45
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
46
  gr.Markdown(
47
- """
48
- # Lossless Image Compressor
49
- Upload any image (PNG, JPG, BMP, etc.) to convert it into a **Lossless WebP** file.
50
- This modern format significantly reduces file size without *any* loss in image quality.
51
- """
52
  )
53
-
54
- with gr.Row(equal_height=True):
55
- with gr.Column(scale=1):
56
- image_input = gr.Image(
57
- type="pil",
58
- label="Upload Your Image",
59
- sources=["upload", "clipboard"]
60
- )
61
- compress_button = gr.Button("Compress Image", variant="primary")
62
 
63
- with gr.Column(scale=1):
64
- file_output = gr.File(
65
- label="Download Compressed WebP File",
66
- interactive=False # User cannot upload to this component
67
- )
68
-
69
- gr.Markdown("---")
70
- gr.Markdown("### Examples")
71
-
72
- # Add examples for users to try
73
- gr.Examples(
74
- examples=[
75
- # Using placeholder images as examples
76
- "https://placehold.co/800x600/png?text=Sample+PNG+Image",
77
- "https://placehold.co/800x600/jpg?text=Sample+JPG+Image",
78
- ],
79
- inputs=image_input,
80
- outputs=file_output,
81
- fn=compress_image,
82
- cache_examples=True # Speeds up example loading
83
- )
84
 
85
- # Connect the button to the function
86
- compress_button.click(
87
- fn=compress_image,
88
- inputs=image_input,
89
- outputs=file_output,
90
- api_name="compress"
91
  )
92
 
93
  if __name__ == "__main__":
94
- demo.launch()
 
1
  import gradio as gr
2
  from PIL import Image
3
+ import pillow_heif
4
  import os
5
+ import shutil
6
 
7
+ # 1. Register HEIF opener with Pillow so it can handle .heic/.heif files
8
+ pillow_heif.register_heif_opener()
9
+
10
+ def format_bytes(size):
11
+ # Helper to make file sizes readable
12
+ power = 2**10
13
+ n = 0
14
+ power_labels = {0 : '', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'}
15
+ while size > power:
16
+ size /= power
17
+ n += 1
18
+ return f"{size:.2f} {power_labels[n]}"
19
+
20
+ def compress_image(image_path):
21
+ if image_path is None:
22
+ return None, "No image uploaded."
23
 
24
  try:
25
+ # Get original file info
26
+ original_size = os.path.getsize(image_path)
27
+ filename = os.path.basename(image_path)
28
+ name_without_ext = os.path.splitext(filename)[0]
29
+
30
+ # Define output path
31
+ output_filename = f"{name_without_ext}_compressed.png"
32
+ output_path = os.path.join(os.path.dirname(image_path), output_filename)
33
+
34
+ # Open the image
35
+ # pillow-heif handles the HEIC/HEIF format automatically here
36
+ with Image.open(image_path) as img:
37
 
38
+ # Create a copy of the image to remove specific metadata that might
39
+ # conflict with saving (though we keep pixel data lossless)
40
+ # We convert to RGBA if it supports transparency, or RGB otherwise
41
+ if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
42
+ img = img.convert('RGBA')
43
+ else:
44
+ img = img.convert('RGB')
45
+
46
+ # SAVE AS PNG
47
+ # compress_level=9 is maximum compression (slowest, smallest size)
48
+ # optimize=True performs additional passes to reduce size
49
+ img.save(output_path, "PNG", compress_level=9, optimize=True)
50
+
51
+ # Get new file info
52
+ new_size = os.path.getsize(output_path)
53
 
54
+ # Calculate stats
55
+ diff = new_size - original_size
56
+ status = "Reduced" if diff < 0 else "Increased"
57
+ percent = (abs(diff) / original_size) * 100
 
58
 
59
+ info_text = (
60
+ f"Original Size: {format_bytes(original_size)}\n"
61
+ f"Compressed Size: {format_bytes(new_size)}\n"
62
+ f"Difference: {status} by {format_bytes(abs(diff))} ({percent:.1f}%)\n\n"
63
+ f"Note: Converting JPEGs/HEICs (lossy) to PNG (lossless) may increase file size "
64
+ f"because PNG preserves the exact pixel data without compression artifacts."
65
+ )
66
 
67
+ return output_path, info_text
 
68
 
69
  except Exception as e:
70
+ return None, f"Error processing image: {str(e)}"
 
 
 
71
 
72
+ # 2. Create the Gradio Interface
73
+ with gr.Blocks(title="Lossless PNG Compressor") as app:
74
+ gr.Markdown("## 🗜️ Lossless Image Compressor (Max PNG Level)")
75
  gr.Markdown(
76
+ "Upload any image (JPG, PNG, HEIC, WEBP). It will be converted to **PNG** "
77
+ "saved at **Compression Level 9** (Maximum). \n\n"
78
+ "*Note: This preserves pixel quality perfectly. However, converting a lossy photo (JPG/HEIC) "
79
+ "to lossless PNG often results in a larger file size.*"
 
80
  )
81
+
82
+ with gr.Row():
83
+ with gr.Column():
84
+ # Input: Expects a file path
85
+ image_input = gr.Image(type="filepath", label="Upload Image")
86
+ submit_btn = gr.Button("Compress / Convert", variant="primary")
 
 
 
87
 
88
+ with gr.Column():
89
+ # Output: Returns a downloadable file and text stats
90
+ file_output = gr.File(label="Download PNG")
91
+ stats_output = gr.Textbox(label="Compression Stats")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ # Bind function
94
+ submit_btn.click(
95
+ fn=compress_image,
96
+ inputs=image_input,
97
+ outputs=[file_output, stats_output]
 
98
  )
99
 
100
  if __name__ == "__main__":
101
+ app.launch()
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  gradio
2
- pillow
 
 
1
  gradio
2
+ pillow
3
+ pillow-heif