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.
This commit is contained in:
xz-dev 2024-09-15 16:26:58 +08:00
parent a0ef9f84be
commit 86cb2edcfa
No known key found for this signature in database
GPG Key ID: A20912F811313E36
1 changed files with 43 additions and 3 deletions

View File

@ -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