Skip to content

Commit bc5b6c5

Browse files
Add alpha blending support
1 parent 5ed6f24 commit bc5b6c5

File tree

4 files changed

+74
-14
lines changed

4 files changed

+74
-14
lines changed

src/ImageSharp/Formats/Png/Chunks/FrameControl.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ internal readonly struct FrameControl
99
{
1010
public const int Size = 26;
1111

12+
public FrameControl(uint width, uint height)
13+
: this(0, width, height, 0, 0, 0, 0, default, default)
14+
{
15+
}
16+
1217
public FrameControl(
1318
uint sequenceNumber,
1419
uint width,
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
namespace SixLabors.ImageSharp.Formats.Png;
55

66
/// <summary>
7-
/// 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.
7+
/// Specifies whether the frame is to be alpha blended into the current output buffer content,
8+
/// or whether it should completely replace its region in the output buffer.
89
/// </summary>
910
public enum PngBlendMethod
1011
{
@@ -14,7 +15,8 @@ public enum PngBlendMethod
1415
Source,
1516

1617
/// <summary>
17-
/// 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.
18+
/// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as
19+
/// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2].
1820
/// </summary>
1921
Over
2022
}

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
153153
this.currentStream.Skip(8);
154154
Image<TPixel>? image = null;
155155
FrameControl? previousFrameControl = null;
156+
ImageFrame<TPixel>? previousFrame = null;
156157
ImageFrame<TPixel>? currentFrame = null;
157158
Span<byte> buffer = stackalloc byte[20];
158159

@@ -213,7 +214,21 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
213214
}
214215

215216
this.currentStream.Position += 4;
216-
this.ReadScanlines(chunk.Length - 4, currentFrame, pngMetadata, this.ReadNextDataChunkAndSkipSeq, previousFrameControl.Value, cancellationToken);
217+
this.ReadScanlines(
218+
chunk.Length - 4,
219+
currentFrame,
220+
pngMetadata,
221+
this.ReadNextDataChunkAndSkipSeq,
222+
previousFrameControl.Value,
223+
cancellationToken);
224+
225+
PngFrameMetadata pngFrameMetadata = currentFrame.Metadata.GetPngFrameMetadata();
226+
if (previousFrame != null && pngFrameMetadata.BlendMethod == PngBlendMethod.Over)
227+
{
228+
this.AlphaBlend(previousFrame, currentFrame);
229+
}
230+
231+
previousFrame = currentFrame;
217232
previousFrameControl = null;
218233
break;
219234
case PngChunkType.Data:
@@ -225,8 +240,15 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
225240
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
226241
}
227242

228-
FrameControl frameControl = previousFrameControl ?? new(0, (uint)this.header.Width, (uint)this.header.Height, 0, 0, 0, 0, default, default);
229-
this.ReadScanlines(chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, in frameControl, cancellationToken);
243+
FrameControl frameControl = previousFrameControl ?? new((uint)this.header.Width, (uint)this.header.Height);
244+
this.ReadScanlines(
245+
chunk.Length,
246+
image.Frames.RootFrame,
247+
pngMetadata,
248+
this.ReadNextDataChunk,
249+
in frameControl,
250+
cancellationToken);
251+
230252
previousFrameControl = null;
231253
break;
232254
case PngChunkType.Palette:
@@ -698,10 +720,15 @@ private void ReadScanlines<TPixel>(int chunkLength, ImageFrame<TPixel> image, Pn
698720
/// <typeparam name="TPixel">The pixel format.</typeparam>
699721
/// <param name="frameControl">The frame control</param>
700722
/// <param name="compressedStream">The compressed pixel data stream.</param>
701-
/// <param name="image">The image to decode to.</param>
723+
/// <param name="imageFrame">The image frame to decode to.</param>
702724
/// <param name="pngMetadata">The png metadata</param>
703725
/// <param name="cancellationToken">The CancellationToken</param>
704-
private void DecodePixelData<TPixel>(FrameControl frameControl, DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata, CancellationToken cancellationToken)
726+
private void DecodePixelData<TPixel>(
727+
FrameControl frameControl,
728+
DeflateStream compressedStream,
729+
ImageFrame<TPixel> imageFrame,
730+
PngMetadata pngMetadata,
731+
CancellationToken cancellationToken)
705732
where TPixel : unmanaged, IPixel<TPixel>
706733
{
707734
int currentRow = (int)frameControl.YOffset;
@@ -750,24 +777,27 @@ private void DecodePixelData<TPixel>(FrameControl frameControl, DeflateStream co
750777
break;
751778
}
752779

753-
this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, image, pngMetadata);
754-
780+
this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, imageFrame, pngMetadata);
755781
this.SwapScanlineBuffers();
756782
currentRow++;
757783
}
758784
}
759785

760786
/// <summary>
761787
/// Decodes the raw interlaced pixel data row by row
762-
/// <see href="https://github.com/juehv/DentalImageViewer/blob/8a1a4424b15d6cc453b5de3f273daf3ff5e3a90d/DentalImageViewer/lib/jiu-0.14.3/net/sourceforge/jiu/codecs/PNGCodec.java"/>
763788
/// </summary>
764789
/// <typeparam name="TPixel">The pixel format.</typeparam>
765790
/// <param name="frameControl">The frame control</param>
766791
/// <param name="compressedStream">The compressed pixel data stream.</param>
767792
/// <param name="image">The current image.</param>
768793
/// <param name="pngMetadata">The png metadata.</param>
769794
/// <param name="cancellationToken">The cancellation token.</param>
770-
private void DecodeInterlacedPixelData<TPixel>(in FrameControl frameControl, DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata, CancellationToken cancellationToken)
795+
private void DecodeInterlacedPixelData<TPixel>(
796+
in FrameControl frameControl,
797+
DeflateStream compressedStream,
798+
ImageFrame<TPixel> image,
799+
PngMetadata pngMetadata,
800+
CancellationToken cancellationToken)
771801
where TPixel : unmanaged, IPixel<TPixel>
772802
{
773803
int currentRow = Adam7.FirstRow[0] + (int)frameControl.YOffset;
@@ -845,6 +875,7 @@ private void DecodeInterlacedPixelData<TPixel>(in FrameControl frameControl, Def
845875
pixelOffset: Adam7.FirstColumn[pass],
846876
increment: Adam7.ColumnIncrement[pass]);
847877

878+
// TODO: Alpha blending.
848879
this.SwapScanlineBuffers();
849880

850881
currentRow += Adam7.RowIncrement[pass];
@@ -874,7 +905,12 @@ private void DecodeInterlacedPixelData<TPixel>(in FrameControl frameControl, Def
874905
/// <param name="defilteredScanline">The de-filtered scanline</param>
875906
/// <param name="pixels">The image</param>
876907
/// <param name="pngMetadata">The png metadata.</param>
877-
private void ProcessDefilteredScanline<TPixel>(in FrameControl frameControl, int currentRow, ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels, PngMetadata pngMetadata)
908+
private void ProcessDefilteredScanline<TPixel>(
909+
in FrameControl frameControl,
910+
int currentRow,
911+
ReadOnlySpan<byte> defilteredScanline,
912+
ImageFrame<TPixel> pixels,
913+
PngMetadata pngMetadata)
878914
where TPixel : unmanaged, IPixel<TPixel>
879915
{
880916
Span<TPixel> rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
@@ -1841,4 +1877,21 @@ private static bool IsXmpTextData(ReadOnlySpan<byte> keywordBytes)
18411877

18421878
private void SwapScanlineBuffers()
18431879
=> (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline);
1880+
1881+
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst)
1882+
where TPixel : unmanaged, IPixel<TPixel>
1883+
{
1884+
Buffer2D<TPixel> srcPixels = src.PixelBuffer;
1885+
Buffer2D<TPixel> dstPixels = dst.PixelBuffer;
1886+
PixelBlender<TPixel> blender =
1887+
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
1888+
1889+
for (int y = 0; y < src.Height; y++)
1890+
{
1891+
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y);
1892+
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y);
1893+
1894+
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
1895+
}
1896+
}
18441897
}

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
214214
}
215215
else
216216
{
217-
FrameControl frameControl = new(0, (uint)this.width, (uint)this.height, 0, 0, 0, 0, default, default);
217+
FrameControl frameControl = new((uint)this.width, (uint)this.height);
218218
this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false);
219219
}
220220

0 commit comments

Comments
 (0)