mirror of
https://github.com/uhIgnacio/EmoteManager.git
synced 2024-08-15 02:23:13 +00:00
67 lines
2.5 KiB
Python
67 lines
2.5 KiB
Python
import io
|
|
import logging
|
|
import contextlib
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
try:
|
|
from wand.image import Image
|
|
except ImportError:
|
|
logger.warn('Failed to import wand.image. Image manipulation functions will be unavailable.')
|
|
|
|
from utils import errors
|
|
|
|
def resize_until_small(image_data: io.BytesIO):
|
|
"""If the image_data is bigger than 256KB, resize it until it's not"""
|
|
# It's important that we only attempt to resize the image when we have to,
|
|
# ie when it exceeds the Discord limit of 256KiB.
|
|
# Apparently some <256KiB images become larger when we attempt to resize them,
|
|
# so resizing sometimes does more harm than good.
|
|
max_resolution = 128 # pixels
|
|
image_size = size(image_data)
|
|
while image_size > 256 * 2**10 and max_resolution >= 32: # don't resize past 32x32 or 256KiB
|
|
logger.debug('image size too big (%s bytes)', image_size)
|
|
logger.debug('attempting resize to %s*%s pixels', max_resolution, max_resolution)
|
|
image_data = thumbnail(image_data, (max_resolution, max_resolution))
|
|
image_size = size(image_data)
|
|
max_resolution //= 2
|
|
return image_data
|
|
|
|
def size(data: io.BytesIO):
|
|
"""return the size, in bytes, of the data a BytesIO object represents"""
|
|
with preserve_position(data):
|
|
data.seek(0, io.SEEK_END)
|
|
return data.tell()
|
|
|
|
def thumbnail(image_data: io.BytesIO, max_size=(128, 128)):
|
|
"""Resize an image in place to no more than max_size pixels, preserving aspect ratio."""
|
|
# Credit to @Liara#0001 (ID 136900814408122368)
|
|
# https://gitlab.com/Pandentia/element-zero/blob/47bc8eeeecc7d353ec66e1ef5235adab98ca9635/element_zero/cogs/emoji.py#L243-247
|
|
image = Image(blob=image_data)
|
|
image.resize(*scale_resolution((image.width, image.height), max_size))
|
|
# we create a new buffer here because there's wand errors otherwise.
|
|
# specific error:
|
|
# MissingDelegateError: no decode delegate for this image format `' @ error/blob.c/BlobToImage/353
|
|
out = io.BytesIO()
|
|
image.save(file=out)
|
|
out.seek(0)
|
|
return out
|
|
|
|
def scale_resolution(old_res, new_res):
|
|
# https://stackoverflow.com/a/6565988
|
|
"""Resize a resolution, preserving aspect ratio. Returned w,h will be <= new_res"""
|
|
old_width, old_height = old_res
|
|
new_width, new_height = new_res
|
|
old_ratio = old_width / old_height
|
|
new_ratio = new_width / new_height
|
|
if new_ratio > old_ratio:
|
|
return (old_width * new_height//old_height, new_height)
|
|
return new_width, old_height * new_width//old_width
|
|
|
|
class preserve_position(contextlib.AbstractContextManager):
|
|
def __init__(self, fp):
|
|
self.fp = fp
|
|
self.old_pos = fp.tell()
|
|
|
|
def __exit__(self, *excinfo):
|
|
self.fp.seek(self.old_pos)
|