-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
126 lines (103 loc) · 3.83 KB
/
utils.py
File metadata and controls
126 lines (103 loc) · 3.83 KB
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
import os
import io
from PIL import Image
def save_compressed_image(image, output_path, max_kb=None, output_format=None):
"""
Saves an image to the output_path, attempting to keep the file size under max_kb.
Args:
image (PIL.Image): The image to save.
output_path (str): The full path to save the image to.
max_kb (int, optional): The maximum file size in Kilobytes. If None or 0, no limit.
output_format (str, optional): 'JPEG', 'PNG', 'PDF' etc.
"""
# 1. Determine Format if not given
ext = os.path.splitext(output_path)[1].lower()
if output_format:
fmt = output_format.upper()
else:
if ext in ['.jpg', '.jpeg']:
fmt = 'JPEG'
elif ext == '.png':
fmt = 'PNG'
elif ext == '.pdf':
fmt = 'PDF'
else:
fmt = 'JPEG' # Default
# Common settings
quality = 95
dpi = image.info.get('dpi') # Preserve DPI
# PDF Logic
if fmt == 'PDF':
if image.mode != 'RGB':
image = image.convert('RGB')
# Determine resolution for PDF (default to 72 if not present, or use image's DPI)
resolution = 72.0
if dpi:
# dpi can be a tuple (x, y)
resolution = float(dpi[0]) if isinstance(dpi, tuple) else float(dpi)
if not max_kb or max_kb <= 0:
image.save(output_path, "PDF", resolution=resolution, quality=quality)
return
target_bytes = max_kb * 1024
min_q = 10
max_q = 95
best_q = min_q
# Binary Search for Quality
while min_q <= max_q:
mid_q = (min_q + max_q) // 2
buf = io.BytesIO()
image.save(buf, "PDF", resolution=resolution, quality=mid_q)
size = buf.tell()
if size <= target_bytes:
best_q = mid_q
min_q = mid_q + 1
else:
max_q = mid_q - 1
image.save(output_path, "PDF", resolution=resolution, quality=best_q)
return
# PNG Logic (Lossless, ignore max_kb usually)
if fmt == 'PNG':
if dpi:
image.save(output_path, "PNG", optimize=True, dpi=dpi)
else:
image.save(output_path, "PNG", optimize=True)
return
# JPEG Logic (Standard)
if fmt == 'JPEG':
if image.mode != 'RGB':
image = image.convert('RGB')
# Use subsampling=0 (4:4:4) to prevent color bleed/blurriness
save_kwargs = {'quality': quality, 'subsampling': 0}
if dpi:
save_kwargs['dpi'] = dpi
if not max_kb or max_kb <= 0:
image.save(output_path, "JPEG", **save_kwargs)
return
target_bytes = max_kb * 1024
# Check current quality=95
buf = io.BytesIO()
image.save(buf, "JPEG", **save_kwargs)
if buf.tell() <= target_bytes:
with open(output_path, "wb") as f:
f.write(buf.getvalue())
return
# Binary search
min_q = 5
max_q = 90
best_q = min_q
while min_q <= max_q:
mid_q = (min_q + max_q) // 2
buf = io.BytesIO()
# Update quality in kwargs
# NOTE: We maintain subsampling=0 and dpi for consistency
current_kwargs = save_kwargs.copy()
current_kwargs['quality'] = mid_q
image.save(buf, "JPEG", **current_kwargs)
if buf.tell() <= target_bytes:
best_q = mid_q
min_q = mid_q + 1
else:
max_q = mid_q - 1
final_kwargs = save_kwargs.copy()
final_kwargs['quality'] = best_q
image.save(output_path, "JPEG", **final_kwargs)