Skip to content

Instantly share code, notes, and snippets.

@cwgem
Created July 18, 2017 16:46
Show Gist options
  • Save cwgem/7a0a0ead65f40880ac45faaf94dd6b1e to your computer and use it in GitHub Desktop.
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
# 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