Created
July 18, 2017 16:46
-
-
Save cwgem/7a0a0ead65f40880ac45faaf94dd6b1e to your computer and use it in GitHub Desktop.
Some code thrown together to explore resizing PNG files to a certain filesize given the oddities the format has regarding that
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Pillow external module | |
from PIL import Image | |
# bitmath external module | |
from bitmath import MB | |
# Standard system modules | |
from os import stat | |
from io import BytesIO | |
from os.path import expanduser | |
# Obviously this will be changed to a different filename local to your system. | |
# Same goes for the path down below | |
img_filename = expanduser("~/Downloads/rgb_2048_4.png") | |
def percentage_resize(img, percentage): | |
# This is written to "Bytes as a file pointer" so it's maintained in memory. | |
# Later it will be used to get the width and height to give a very ballpark | |
# estimate of "bytes per pixel" | |
memory_img = BytesIO() | |
img.resize([int(float(percentage / 100) * s) for s in img.size]).save( | |
memory_img, format="png") | |
return memory_img | |
def image_filesize_resize(filename, max_bytes, scan_spacing=3, | |
resize_filter='HAMMING'): | |
# This is just used for a quick check to see if the image is already below | |
# size | |
filesize = stat(filename).st_size | |
img_data = BytesIO() | |
if filesize > max_bytes: | |
with Image.open(filename) as img: | |
max_est_bpp = 0.0 | |
# Basically this samples resized images at various percentages | |
# to see what a ballpark bytes per pixel is. This of course is not | |
# accurate because it assumes that there are no PNG headers and data | |
# segments. However it gives a good enough ballpark to resize the | |
# image down accordingly. Multiple samplings are needing due to the | |
# fact that the average could fluctuate based on pixel proximity and | |
# other factors. | |
for scale in range(1, 99, int(100/scan_spacing)): | |
resize_data = percentage_resize(img, scale) | |
scaled_filesize = resize_data.getbuffer().nbytes | |
with Image.open(resize_data) as resized_img: | |
est_bpp = float(scaled_filesize / (resized_img.width * | |
resized_img.height)) | |
# This is basically looking for the highest estimated bytes | |
# per pixel to better ensure the end result meets the file | |
# size limit constraints | |
if est_bpp > max_est_bpp: | |
max_est_bpp = est_bpp | |
newfilesize = filesize | |
# This is since we know the existing width and height already won't | |
# work. | |
new_width = img.width - 1 | |
new_height = img.height - 1 | |
# Here's where the magic happens. By taking the highest estimated | |
# bytes per pixel from the sampling we can estimate what resolution | |
# will be lower than the max filesize limit. The algorithm also | |
# makes sure aspect ratio is preserved | |
while newfilesize > max_bytes: | |
newfilesize = (new_width * new_height) * max_est_bpp | |
if new_width > new_height: | |
new_width = new_width - 1 | |
wpercent = (new_width / float(img.width)) | |
new_height = int((float(img.height) * float(wpercent))) | |
else: | |
new_height = new_height - 1 | |
hpercent = (new_height / float(img.height)) | |
new_width = int((float(img.width) * float(hpercent))) | |
img.resize((new_width, new_height), Image.__dict__[resize_filter]).\ | |
save(img_data, format="png") | |
else: | |
with Image.open(filename) as img: | |
img.save(img_data, format="png") | |
# When saving the data, we need to make sure that the buffer is at the | |
# beginning | |
img_data.seek(0) | |
return img_data | |
if __name__ == "__main__": | |
img_data = image_filesize_resize(img_filename, MB(1).to_Byte()) | |
with open(expanduser( | |
'~/Downloads/rgb_2048_4_resized.png'), 'wb')\ | |
as fp: | |
fp.write(img_data.read()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment