diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
index c7233ada1..b912e9b09 100644
--- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
@@ -9,6 +9,11 @@ internal readonly struct FrameControl
{
public const int Size = 26;
+ public FrameControl(uint width, uint height)
+ : this(0, width, height, 0, 0, 0, 0, default, default)
+ {
+ }
+
public FrameControl(
uint sequenceNumber,
uint width,
diff --git a/src/ImageSharp/Formats/Png/PngBlendMethod.cs b/src/ImageSharp/Formats/Png/PngBlendMethod.cs
index b7ace9ccf..f71dce832 100644
--- a/src/ImageSharp/Formats/Png/PngBlendMethod.cs
+++ b/src/ImageSharp/Formats/Png/PngBlendMethod.cs
@@ -1,10 +1,11 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
///
-/// Specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
+/// Specifies whether the frame is to be alpha blended into the current output buffer content,
+/// or whether it should completely replace its region in the output buffer.
///
public enum PngBlendMethod
{
@@ -14,7 +15,8 @@ public enum PngBlendMethod
Source,
///
- /// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2]. Note that the second variation of the sample code is applicable.
+ /// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as
+ /// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2].
///
Over
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 8484fd0c6..776e52a33 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -153,6 +153,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream.Skip(8);
Image? image = null;
FrameControl? previousFrameControl = null;
+ ImageFrame? previousFrame = null;
ImageFrame? currentFrame = null;
Span buffer = stackalloc byte[20];
@@ -213,7 +214,21 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
this.currentStream.Position += 4;
- this.ReadScanlines(chunk.Length - 4, currentFrame, pngMetadata, this.ReadNextDataChunkAndSkipSeq, previousFrameControl.Value, cancellationToken);
+ this.ReadScanlines(
+ chunk.Length - 4,
+ currentFrame,
+ pngMetadata,
+ this.ReadNextDataChunkAndSkipSeq,
+ previousFrameControl.Value,
+ cancellationToken);
+
+ PngFrameMetadata pngFrameMetadata = currentFrame.Metadata.GetPngFrameMetadata();
+ if (previousFrame != null && pngFrameMetadata.BlendMethod == PngBlendMethod.Over)
+ {
+ this.AlphaBlend(previousFrame, currentFrame);
+ }
+
+ previousFrame = currentFrame;
previousFrameControl = null;
break;
case PngChunkType.Data:
@@ -225,8 +240,15 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
}
- FrameControl frameControl = previousFrameControl ?? new(0, (uint)this.header.Width, (uint)this.header.Height, 0, 0, 0, 0, default, default);
- this.ReadScanlines(chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, in frameControl, cancellationToken);
+ FrameControl frameControl = previousFrameControl ?? new((uint)this.header.Width, (uint)this.header.Height);
+ this.ReadScanlines(
+ chunk.Length,
+ image.Frames.RootFrame,
+ pngMetadata,
+ this.ReadNextDataChunk,
+ in frameControl,
+ cancellationToken);
+
previousFrameControl = null;
break;
case PngChunkType.Palette:
@@ -698,10 +720,15 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// The pixel format.
/// The frame control
/// The compressed pixel data stream.
- /// The image to decode to.
+ /// The image frame to decode to.
/// The png metadata
/// The CancellationToken
- private void DecodePixelData(FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken)
+ private void DecodePixelData(
+ FrameControl frameControl,
+ DeflateStream compressedStream,
+ ImageFrame imageFrame,
+ PngMetadata pngMetadata,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int currentRow = (int)frameControl.YOffset;
@@ -750,8 +777,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
- this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, image, pngMetadata);
-
+ this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, imageFrame, pngMetadata);
this.SwapScanlineBuffers();
currentRow++;
}
@@ -759,7 +785,6 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
///
/// Decodes the raw interlaced pixel data row by row
- ///
///
/// The pixel format.
/// The frame control
@@ -767,7 +792,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// The current image.
/// The png metadata.
/// The cancellation token.
- private void DecodeInterlacedPixelData(in FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken)
+ private void DecodeInterlacedPixelData(
+ in FrameControl frameControl,
+ DeflateStream compressedStream,
+ ImageFrame image,
+ PngMetadata pngMetadata,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int currentRow = Adam7.FirstRow[0] + (int)frameControl.YOffset;
@@ -845,6 +875,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
pixelOffset: Adam7.FirstColumn[pass],
increment: Adam7.ColumnIncrement[pass]);
+ // TODO: Alpha blending.
this.SwapScanlineBuffers();
currentRow += Adam7.RowIncrement[pass];
@@ -874,7 +905,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// The de-filtered scanline
/// The image
/// The png metadata.
- private void ProcessDefilteredScanline(in FrameControl frameControl, int currentRow, ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata)
+ private void ProcessDefilteredScanline(
+ in FrameControl frameControl,
+ int currentRow,
+ ReadOnlySpan defilteredScanline,
+ ImageFrame pixels,
+ PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel
{
Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
@@ -1841,4 +1877,21 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void SwapScanlineBuffers()
=> (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline);
+
+ private void AlphaBlend(ImageFrame src, ImageFrame dst)
+ where TPixel : unmanaged, IPixel
+ {
+ Buffer2D srcPixels = src.PixelBuffer;
+ Buffer2D dstPixels = dst.PixelBuffer;
+ PixelBlender blender =
+ PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
+
+ for (int y = 0; y < src.Height; y++)
+ {
+ Span srcPixelRow = srcPixels.DangerousGetRowSpan(y);
+ Span dstPixelRow = dstPixels.DangerousGetRowSpan(y);
+
+ blender.Blend(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
+ }
+ }
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 8eabde8d9..ef179e826 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -214,7 +214,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
}
else
{
- FrameControl frameControl = new(0, (uint)this.width, (uint)this.height, 0, 0, 0, 0, default, default);
+ FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false);
}