From 79f22e0f54100666428abbd26835ba7477fabb91 Mon Sep 17 00:00:00 2001 From: Yegor Pelykh Date: Wed, 8 May 2024 17:24:16 +0300 Subject: [PATCH] fix: Fix of APNG image decoding --- src/draw/draw.ts | 32 ++++++++++++++------------ src/formats/png-decoder.ts | 46 +++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/draw/draw.ts b/src/draw/draw.ts index 7378c2d..d02e64b 100644 --- a/src/draw/draw.ts +++ b/src/draw/draw.ts @@ -124,6 +124,7 @@ export interface FillRectOptions { rect: Rectangle; color: Color; radius?: number; + alphaBlend?: boolean; mask?: MemoryImage; maskChannel?: Channel; } @@ -2000,9 +2001,10 @@ export abstract class Draw { */ public static fillRect(opt: FillRectOptions): MemoryImage { const radius = opt.radius ?? 0; + const alphaBlend = opt.alphaBlend ?? true; const maskChannel = opt.maskChannel ?? Channel.luminance; - if (opt.color.a === 0) { + if (alphaBlend && opt.color.a === 0) { return opt.image; } @@ -2070,7 +2072,10 @@ export abstract class Draw { } // If no blending is necessary, use a faster fill method. - if (opt.color.a === opt.color.maxChannelValue && opt.mask === undefined) { + if ( + !alphaBlend || + (opt.color.a === opt.color.maxChannelValue && opt.mask === undefined) + ) { const range = opt.image.getRange(xx0, yy0, ww, hh); let it: IteratorResult | undefined = undefined; while (((it = range.next()), !it.done)) { @@ -2193,6 +2198,7 @@ export abstract class Draw { * if **center** is true, the **src** will be centered in **dst**. */ public static compositeImage(opt: CompositeImageOptions): MemoryImage { + let dst = opt.dst; let dstX = opt.dstX ?? 0; let dstY = opt.dstY ?? 0; const srcX = opt.srcX ?? 0; @@ -2200,11 +2206,9 @@ export abstract class Draw { const srcW = opt.srcW ?? opt.src.width; const srcH = opt.srcH ?? opt.src.height; const dstW = - opt.dstW ?? - (opt.dst.width < opt.src.width ? opt.dst.width : opt.src.width); + opt.dstW ?? (dst.width < opt.src.width ? dst.width : opt.src.width); const dstH = - opt.dstH ?? - (opt.dst.height < opt.src.height ? opt.dst.height : opt.src.height); + opt.dstH ?? (dst.height < opt.src.height ? dst.height : opt.src.height); const blend = opt.blend ?? BlendMode.alpha; const linearBlend = opt.linearBlend ?? false; const center = opt.center ?? false; @@ -2212,18 +2216,18 @@ export abstract class Draw { if (center) { // if [src] is wider than [dst] - let wdt = opt.dst.width - opt.src.width; + let wdt = dst.width - opt.src.width; if (wdt < 0) wdt = 0; dstX = Math.trunc(wdt / 2); // if [src] is higher than [dst] - let height = opt.dst.height - opt.src.height; + let height = dst.height - opt.src.height; if (height < 0) height = 0; dstY = Math.trunc(height / 2); } - if (opt.dst.hasPalette) { - opt.dst.convert({ - numChannels: opt.dst.numChannels, + if (dst.hasPalette) { + dst = dst.convert({ + numChannels: dst.numChannels, }); } @@ -2241,7 +2245,7 @@ export abstract class Draw { if (blend === BlendMode.direct) { Draw.imgDirectComposite( opt.src, - opt.dst, + dst, dstX, dstY, dstW, @@ -2254,7 +2258,7 @@ export abstract class Draw { } else { Draw.imgComposite( opt.src, - opt.dst, + dst, dstX, dstY, dstW, @@ -2268,6 +2272,6 @@ export abstract class Draw { ); } - return opt.dst; + return dst; } } diff --git a/src/formats/png-decoder.ts b/src/formats/png-decoder.ts index f701a4b..49863ee 100644 --- a/src/formats/png-decoder.ts +++ b/src/formats/png-decoder.ts @@ -25,6 +25,7 @@ import { BlendMode } from '../draw/blend-mode'; import { PngFilterType } from './png/png-filter-type'; import { Pixel } from '../image/pixel'; import { ImageFormat } from './image-format'; +import { Rectangle } from '../common/rectangle'; /** * Decode a PNG encoded image. @@ -932,13 +933,17 @@ export class PngDecoder implements Decoder { } if (firstImage === undefined || lastImage === undefined) { - firstImage = image; - lastImage = image; + firstImage = image.convert({ + numChannels: image.numChannels, + }); + lastImage = firstImage; // Convert to MS lastImage.frameDuration = Math.trunc(frame.delay * 1000); continue; } + const prevFrame = this._info.frames[i - 1]; + if ( image.width === lastImage.width && image.height === lastImage.height && @@ -953,20 +958,41 @@ export class PngDecoder implements Decoder { continue; } - const dispose = frame.dispose; + lastImage = MemoryImage.from(firstImage.getFrame(i - 1)); + + const dispose = prevFrame.dispose; if (dispose === PngDisposeMode.background) { - lastImage = MemoryImage.from(lastImage); - lastImage.clear(this._info.backgroundColor); - } else if (dispose === PngDisposeMode.previous) { - lastImage = MemoryImage.from(lastImage); - } else { - lastImage = MemoryImage.from(lastImage); + Draw.fillRect({ + image: lastImage, + rect: new Rectangle( + prevFrame.xOffset, + prevFrame.yOffset, + prevFrame.xOffset + prevFrame.width - 1, + prevFrame.yOffset + prevFrame.height - 1 + ), + color: this._info.backgroundColor ?? new ColorRgba8(0, 0, 0, 0), + alphaBlend: false, + }); + } else if (dispose === PngDisposeMode.previous && i > 1) { + const prevImage = firstImage.getFrame(i - 2); + lastImage = Draw.compositeImage({ + dst: lastImage, + src: prevImage, + dstX: prevFrame.xOffset, + dstY: prevFrame.yOffset, + dstW: prevFrame.width, + dstH: prevFrame.height, + srcX: prevFrame.xOffset, + srcY: prevFrame.yOffset, + srcW: prevFrame.width, + srcH: prevFrame.height, + }); } // Convert to MS lastImage.frameDuration = Math.trunc(frame.delay * 1000); - Draw.compositeImage({ + lastImage = Draw.compositeImage({ dst: lastImage, src: image, dstX: frame.xOffset,