v2ex / remote

Remote Worker
https://www.v2ex.com/go/dev
MIT License
80 stars 4 forks source link

Feature to explore: rescale animated formats like GIF/APNG/WEBP while maintaining its original aspect ratio #24

Open livid opened 2 years ago

livid commented 2 years ago

It is doable:

https://stackoverflow.com/questions/41718892/pillow-resizing-a-gif

luxiaba commented 2 years ago

I try to make one crude resizer according to link above, to resize gif/webp/apng, but I think there may be some problems in some places. However it's not easy to handle a image perfectly by self(transparency / pallette / foreground / background / size / speed / position / boundary / ...), maybe another choose is give them to external tools by subprocess.

I've uploaded some pic to my v2ex pic lib for test:

and another webp rainbow cat

For test:

sized_bytes_content = resize_animated('elephant.png', 'PNG', 100, save_to='sized_elephant.png')
import io
import logging

from PIL import Image
from resizeimage import resizeimage

def resize_animated(file_path, pil_fmt: str, target_size: int, save_to: str) -> bytes:
    all_frames = extract_and_resize_frames(file_path, target_size)
    first_frame, left_frames = all_frames[0], all_frames[1:]

    with io.BytesIO() as buffered:
        if not left_frames:
            logging.warning("Only 1 frame found.")
            first_frame.save(buffered, format=pil_fmt)
            return buffered.getvalue()
        first_frame.save(
            buffered,
            format=pil_fmt,
            save_all=True,
            append_images=left_frames,
            loop=0,
            disposal=0,
            optimize=True,
        )
        if save_to:
            first_frame.save(
                save_to,
                format=pil_fmt,
                save_all=True,
                append_images=left_frames,
                loop=0,
                disposal=0,
                optimize=True,
            )
        return buffered.getvalue()

def extract_and_resize_frames(p, size: int):
    original_palette = Image.open(p).getpalette()
    # load again, otherwise will lose first frame.
    img = Image.open(p)
    all_frames = []
    frames_cnt = getattr(img, 'n_frames', 1)
    for frame in range(frames_cnt):
        img.seek(frame)
        if not img.getpalette() and original_palette:
            try:
                img.putpalette(original_palette)
            except (AttributeError, ValueError):
                ...
        sub_img = resizeimage.resize_thumbnail(img.convert('RGBA'), [size, size])
        all_frames.append(sub_img)
    return all_frames
luxiaba commented 2 years ago

BTW, if an unsupported file type is uploaded, there is no prompt to the frontend, but it looks like successed already. I finally found error response from the chrome console.😂

stdc105 commented 2 years ago

@luxiaba I am working on a patch that should handle common scenarios. There are some more nuances among different image formats, which leads to a more complicated patch than what you have presented here. I will post a WIP pull request soon, feel free to review and leave comments there.

luxiaba commented 2 years ago

wow exciting!