From 86cb2edcfa4e04114514438a4c25e0e4170e093b Mon Sep 17 00:00:00 2001 From: xz-dev Date: Sun, 15 Sep 2024 16:26:58 +0800 Subject: [PATCH] fix: Improve edge transparency handling by modifying only the Alpha channel In our testing, the method of exclusively processing the Alpha channel yielded the best results. This approach focuses on adjusting transparency while preserving the RGB color information, which prevents color distortion and maintains image detail. Key reasons for the improvement include: - Protecting RGB from color alterations, avoiding color seepage and contamination. - Precisely removing unwanted semi-transparency in the Alpha channel, eliminating white edges. - Simplifying the process, reducing complexity, and minimizing risk of introducing new issues. By targeting transparency issues directly in the Alpha channel, we achieve cleaner edges without compromising the image's color quality and detail. --- sticker/lib/util.py | 46 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/sticker/lib/util.py b/sticker/lib/util.py index 580824c..3e41bce 100644 --- a/sticker/lib/util.py +++ b/sticker/lib/util.py @@ -27,7 +27,7 @@ try: except ImportError: print("[Warning] Magic is not installed, using file extensions to guess mime types") magic = None -from PIL import Image, ImageSequence +from PIL import Image, ImageSequence, ImageFilter from . import matrix @@ -93,6 +93,32 @@ def video_to_webp(data: bytes) -> bytes: return _video_to_webp(data) +def process_frame(frame): + """ + Process GIF frame, repair edges, ensure no white or semi-transparent pixels, while keeping color information intact. + """ + frame = frame.convert('RGBA') + + # Decompose Alpha channel + alpha = frame.getchannel('A') + + # Process Alpha channel with threshold, remove semi-transparent pixels + # Threshold can be adjusted as needed (0-255), 128 is the middle value + threshold = 128 + alpha = alpha.point(lambda x: 255 if x >= threshold else 0) + + # Process Alpha channel with MinFilter, remove edge noise + alpha = alpha.filter(ImageFilter.MinFilter(3)) + + # Process Alpha channel with MaxFilter, repair edges + alpha = alpha.filter(ImageFilter.MaxFilter(3)) + + # Apply processed Alpha channel back to image + frame.putalpha(alpha) + + return frame + + def webp_to_others(data: bytes, mimetype: str) -> bytes: with tempfile.NamedTemporaryFile(suffix=".webp") as webp: webp.write(data) @@ -102,7 +128,21 @@ def webp_to_others(data: bytes, mimetype: str) -> bytes: print(".", end="", flush=True) im = Image.open(webp.name) im.info.pop('background', None) - im.save(img.name, save_all=True, lossless=True, quality=100, method=6) + + if mimetype == "image/gif": + frames = [] + duration = [] + + for frame in ImageSequence.Iterator(im): + frame = process_frame(frame) + frames.append(frame) + duration.append(frame.info.get('duration', 100)) + + frames[0].save(img.name, save_all=True, lossless=True, quality=100, method=6, + append_images=frames[1:], loop=0, duration=duration, disposal=2) + else: + im.save(img.name, save_all=True, lossless=True, quality=100, method=6) + img.seek(0) return img.read() @@ -126,7 +166,7 @@ def webp_to_gif_or_png(data: bytes) -> bytes: # check if the webp is animated image: Image.Image = Image.open(BytesIO(data)) is_animated = getattr(image, "is_animated", False) - if is_animated and is_uniform_animated_webp(data): + if is_animated and not is_uniform_animated_webp(data): return webp_to_others(data, "image/gif") else: # convert to png