Browse Source

Fix alpha blending and add tests

pull/2511/head
James Jackson-South 3 years ago
parent
commit
66f444d200
  1. 174
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 1
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  3. 26
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  4. 12
      tests/ImageSharp.Tests/TestImages.cs
  5. 28
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  6. 7
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  7. 8
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
  8. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/00.png
  9. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/01.png
  10. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/00.png
  11. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/01.png
  12. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/00.png
  13. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/01.png
  14. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/02.png
  15. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/00.png
  16. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/01.png
  17. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/02.png
  18. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/03.png
  19. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/04.png
  20. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/05.png
  21. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/06.png
  22. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/07.png
  23. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/08.png
  24. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/104.png
  25. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/112.png
  26. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/120.png
  27. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/128.png
  28. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/16.png
  29. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/24.png
  30. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/32.png
  31. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/40.png
  32. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/48.png
  33. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/56.png
  34. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/64.png
  35. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/72.png
  36. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/80.png
  37. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/88.png
  38. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/96.png
  39. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_4-split-idat-zero-length.png/00.png
  40. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/00.png
  41. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/01.png
  42. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/02.png
  43. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/00.png
  44. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/01.png
  45. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/02.png
  46. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/00.png
  47. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/01.png
  48. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/02.png
  49. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/03.png
  50. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/04.png
  51. 3
      tests/Images/Input/Png/animated/12-dispose-prev-first.png
  52. 3
      tests/Images/Input/Png/animated/14-dispose-background-before-region.png
  53. 3
      tests/Images/Input/Png/animated/15-dispose-background-region.png
  54. 3
      tests/Images/Input/Png/animated/21-blend-over-multiple.png
  55. 3
      tests/Images/Input/Png/animated/4-split-idat-zero-length.png
  56. 3
      tests/Images/Input/Png/animated/7-dispose-none.png
  57. 3
      tests/Images/Input/Png/animated/8-dispose-background.png
  58. 0
      tests/Images/Input/Png/animated/apng.png

174
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -153,6 +153,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream.Skip(8);
Image<TPixel>? image = null;
FrameControl? previousFrameControl = null;
FrameControl? currentFrameControl = null;
ImageFrame<TPixel>? previousFrame = null;
ImageFrame<TPixel>? currentFrame = null;
Span<byte> buffer = stackalloc byte[20];
@ -190,7 +191,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
currentFrame = null;
previousFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break;
case PngChunkType.FrameData:
if (frameCount == this.maxFrames)
@ -203,15 +204,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowMissingDefaultData();
}
if (previousFrameControl is null)
if (currentFrameControl is null)
{
PngThrowHelper.ThrowMissingFrameControl();
}
if (currentFrame is null)
{
this.InitializeFrame(previousFrameControl.Value, image, previousFrame, out currentFrame);
}
previousFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
this.InitializeFrame(previousFrameControl.Value, currentFrameControl.Value, image, previousFrame, out currentFrame);
this.currentStream.Position += 4;
this.ReadScanlines(
@ -219,37 +218,33 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
currentFrame,
pngMetadata,
this.ReadNextDataChunkAndSkipSeq,
previousFrameControl.Value,
currentFrameControl.Value,
cancellationToken);
PngFrameMetadata pngFrameMetadata = currentFrame.Metadata.GetPngFrameMetadata();
if (previousFrame != null && pngFrameMetadata.BlendMethod == PngBlendMethod.Over)
{
this.AlphaBlend(previousFrame, currentFrame, previousFrameControl.Value.Bounds);
}
previousFrame = currentFrame;
previousFrameControl = null;
previousFrameControl = currentFrameControl;
break;
case PngChunkType.Data:
currentFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
if (image is null)
{
this.InitializeImage(metadata, previousFrameControl, out image);
this.InitializeImage(metadata, currentFrameControl.Value, out image);
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
}
FrameControl frameControl = previousFrameControl ?? new((uint)this.header.Width, (uint)this.header.Height);
this.ReadScanlines(
chunk.Length,
image.Frames.RootFrame,
pngMetadata,
this.ReadNextDataChunk,
in frameControl,
currentFrameControl.Value,
cancellationToken);
previousFrameControl = null;
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
break;
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
@ -577,7 +572,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <param name="metadata">The metadata information for the image</param>
/// <param name="frameControl">The frame control information for the frame</param>
/// <param name="image">The image that we will populate</param>
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl? frameControl, out Image<TPixel> image)
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
image = Image.CreateUninitialized<TPixel>(
@ -586,11 +581,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.header.Height,
metadata);
if (frameControl is { } control)
{
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngFrameMetadata();
frameMetadata.FromChunk(in control);
}
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngFrameMetadata();
frameMetadata.FromChunk(in frameControl);
this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
@ -610,12 +602,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// Initializes the image and various buffers needed for processing
/// </summary>
/// <typeparam name="TPixel">The type the pixels will be</typeparam>
/// <param name="frameControl">The frame control information for the frame</param>
/// <param name="previousFrameControl">The frame control information for the previous frame.</param>
/// <param name="currentFrameControl">The frame control information for the current frame.</param>
/// <param name="image">The image that we will populate</param>
/// <param name="previousFrame">The previous frame.</param>
/// <param name="frame">The created frame</param>
private void InitializeFrame<TPixel>(
FrameControl frameControl,
FrameControl previousFrameControl,
FrameControl currentFrameControl,
Image<TPixel> image,
ImageFrame<TPixel>? previousFrame,
out ImageFrame<TPixel> frame)
@ -627,17 +621,17 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
frame = image.Frames.AddFrame(previousFrame ?? image.Frames.RootFrame);
// If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
if (frameControl.DisposeOperation == PngDisposalMethod.Background
|| (previousFrame is null && frameControl.DisposeOperation == PngDisposalMethod.Previous))
if (previousFrameControl.DisposeOperation == PngDisposalMethod.Background
|| (previousFrame is null && previousFrameControl.DisposeOperation == PngDisposalMethod.Previous))
{
Rectangle restoreArea = frameControl.Bounds;
Rectangle restoreArea = previousFrameControl.Bounds;
Rectangle interest = Rectangle.Intersect(frame.Bounds(), restoreArea);
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(interest);
pixelRegion.Clear();
}
PngFrameMetadata frameMetadata = frame.Metadata.GetPngFrameMetadata();
frameMetadata.FromChunk(frameControl);
frameMetadata.FromChunk(currentFrameControl);
this.previousScanline?.Dispose();
this.scanline?.Dispose();
@ -714,12 +708,18 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <param name="getData">A delegate to get more data from the inner stream for <see cref="ZlibInflateStream"/>.</param>
/// <param name="frameControl">The frame control</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void ReadScanlines<TPixel>(int chunkLength, ImageFrame<TPixel> image, PngMetadata pngMetadata, Func<int> getData, in FrameControl frameControl, CancellationToken cancellationToken)
private void ReadScanlines<TPixel>(
int chunkLength,
ImageFrame<TPixel> image,
PngMetadata pngMetadata,
Func<int> getData,
in FrameControl frameControl,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using ZlibInflateStream deframeStream = new(this.currentStream, getData);
deframeStream.AllocateNewBytes(chunkLength, true);
DeflateStream dataStream = deframeStream.CompressedStream!;
using ZlibInflateStream inflateStream = new(this.currentStream, getData);
inflateStream.AllocateNewBytes(chunkLength, true);
DeflateStream dataStream = inflateStream.CompressedStream!;
if (this.header.InterlaceMethod is PngInterlaceMode.Adam7)
{
@ -751,13 +751,23 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int currentRow = (int)frameControl.YOffset;
int currentRowBytesRead = 0;
int height = (int)frameControl.YMax;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
}
while (currentRow < height)
{
cancellationToken.ThrowIfCancellationRequested();
Span<byte> scanlineSpan = this.scanline.GetSpan();
while (currentRowBytesRead < this.bytesPerScanline)
int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1;
Span<byte> scanlineSpan = this.scanline.GetSpan()[..bytesPerFrameScanline];
while (currentRowBytesRead < bytesPerFrameScanline)
{
int bytesRead = compressedStream.Read(scanlineSpan, currentRowBytesRead, this.bytesPerScanline - currentRowBytesRead);
int bytesRead = compressedStream.Read(scanlineSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead);
if (bytesRead <= 0)
{
return;
@ -794,10 +804,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, imageFrame, pngMetadata);
this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, imageFrame, pngMetadata, blendRowBuffer);
this.SwapScanlineBuffers();
currentRow++;
}
blendMemory?.Dispose();
}
/// <summary>
@ -806,13 +818,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frameControl">The frame control</param>
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="image">The current image.</param>
/// <param name="imageFrame">The current image frame.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void DecodeInterlacedPixelData<TPixel>(
in FrameControl frameControl,
DeflateStream compressedStream,
ImageFrame<TPixel> image,
ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
@ -823,7 +835,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int width = (int)frameControl.Width;
int endRow = (int)frameControl.YMax;
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
Buffer2D<TPixel> imageBuffer = imageFrame.PixelBuffer;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
}
while (true)
{
int numColumns = Adam7.ComputeColumns(width, pass);
@ -889,10 +910,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.scanline.GetSpan(),
rowSpan,
pngMetadata,
blendRowBuffer,
pixelOffset: Adam7.FirstColumn[pass],
increment: Adam7.ColumnIncrement[pass]);
// TODO: Alpha blending.
blendRowBuffer.Clear();
this.SwapScanlineBuffers();
currentRow += Adam7.RowIncrement[pass];
@ -911,6 +933,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
}
blendMemory?.Dispose();
}
/// <summary>
@ -919,26 +943,34 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frameControl">The frame control</param>
/// <param name="currentRow">The index of the current scanline being processed.</param>
/// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="scanline">The de-filtered scanline</param>
/// <param name="pixels">The image</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
private void ProcessDefilteredScanline<TPixel>(
in FrameControl frameControl,
int currentRow,
ReadOnlySpan<byte> defilteredScanline,
ReadOnlySpan<byte> scanline,
ImageFrame<TPixel> pixels,
PngMetadata pngMetadata)
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
Span<TPixel> rowSpan = blend
? blendRowBuffer
: destination;
// Trim the first marker byte from the buffer
ReadOnlySpan<byte> trimmed = defilteredScanline[1..];
ReadOnlySpan<byte> trimmed = scanline[1..];
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
IMemoryOwner<byte>? buffer = null;
try
{
// TODO: The allocation here could be per frame, not per scanline.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
trimmed,
this.bytesPerScanline - 1,
@ -1004,6 +1036,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
if (blend)
{
PixelBlender<TPixel> blender =
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
blender.Blend<TPixel>(this.configuration, destination, destination, rowSpan, 1f);
}
}
finally
{
@ -1016,22 +1055,29 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frameControl">The frame control</param>
/// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="rowSpan">The current image row.</param>
/// <param name="scanline">The de-filtered scanline</param>
/// <param name="destination">The current image row.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(
in FrameControl frameControl,
ReadOnlySpan<byte> defilteredScanline,
Span<TPixel> rowSpan,
ReadOnlySpan<byte> scanline,
Span<TPixel> destination,
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer,
int pixelOffset = 0,
int increment = 1)
where TPixel : unmanaged, IPixel<TPixel>
{
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
Span<TPixel> rowSpan = blend
? blendRowBuffer
: destination;
// Trim the first marker byte from the buffer
ReadOnlySpan<byte> trimmed = defilteredScanline[1..];
ReadOnlySpan<byte> trimmed = scanline[1..];
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
IMemoryOwner<byte>? buffer = null;
@ -1112,6 +1158,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
if (blend)
{
PixelBlender<TPixel> blender =
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
blender.Blend<TPixel>(this.configuration, destination, destination, rowSpan, 1f);
}
}
finally
{
@ -1894,21 +1947,4 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void SwapScanlineBuffers()
=> (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline);
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst, Rectangle restoreArea)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2DRegion<TPixel> srcPixels = src.PixelBuffer.GetRegion(restoreArea);
Buffer2DRegion<TPixel> dstPixels = dst.PixelBuffer.GetRegion(restoreArea);
PixelBlender<TPixel> blender =
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
for (int y = 0; y < srcPixels.Height; y++)
{
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y);
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y);
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
}
}
}

1
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Collections;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

26
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -78,6 +78,18 @@ public partial class PngDecoderTests
{ TestImages.Png.Rgba64Bpp, typeof(Image<Rgba64>) },
};
public static readonly string[] MultiFrameTestFiles =
{
//TestImages.Png.APng,
//TestImages.Png.SplitIDatZeroLength,
//TestImages.Png.DisposeNone,
//TestImages.Png.DisposeBackground,
//TestImages.Png.DisposeBackgroundRegion,
//TestImages.Png.DisposePreviousFirst,
//TestImages.Png.DisposeBackgroundBeforeRegion,
TestImages.Png.BlendOverMultiple
};
[Theory]
[MemberData(nameof(PixelFormatRange))]
public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type)
@ -107,16 +119,16 @@ public partial class PngDecoderTests
}
[Theory]
[WithFile(TestImages.Png.APng, PixelTypes.Rgba32)]
public void Decode_APng<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
[WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)]
public void Decode_VerifyAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
Assert.Equal(5, image.Frames.Count);
// TODO: Assertations.
// MagickReferenceDecoder cannot decode APNGs (Though ImageMagick can, we likely need to update our mapping implementation)
// Some images have many frames, only compare a selection of them.
static bool Predicate(int i, int _) => i <= 8 || i % 8 == 0;
image.DebugSaveMultiFrame(provider, predicate: Predicate);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact, predicate: Predicate);
}
[Theory]

12
tests/ImageSharp.Tests/TestImages.cs

@ -61,7 +61,17 @@ public static class TestImages
public const string TestPattern31x31 = "Png/testpattern31x31.png";
public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png";
public const string XmpColorPalette = "Png/xmp-colorpalette.png";
public const string APng = "Png/apng.png";
// Animated
// https://philip.html5.org/tests/apng/tests.html
public const string APng = "Png/animated/apng.png";
public const string SplitIDatZeroLength = "Png/animated/4-split-idat-zero-length.png";
public const string DisposeNone = "Png/animated/7-dispose-none.png";
public const string DisposeBackground = "Png/animated/8-dispose-background.png";
public const string DisposeBackgroundBeforeRegion = "Png/animated/14-dispose-background-before-region.png";
public const string DisposeBackgroundRegion = "Png/animated/15-dispose-background-region.png";
public const string DisposePreviousFirst = "Png/animated/12-dispose-prev-first.png";
public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png";
// Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
public const string Filter0 = "Png/filter0.png";

28
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -179,7 +179,7 @@ public class ImagingTestCaseUtility
return path;
}
public IEnumerable<string> GetTestOutputFileNamesMultiFrame(
public IEnumerable<(int Index, string FileName)> GetTestOutputFileNamesMultiFrame(
int frameCount,
string extension = null,
object testOutputDetails = null,
@ -201,11 +201,11 @@ public class ImagingTestCaseUtility
continue;
}
yield return $"{baseDir}/{i:D2}.{extension}";
yield return (i, $"{baseDir}/{i:D2}.{extension}");
}
}
public string[] SaveTestOutputFileMultiFrame<TPixel>(
public (int Index, string FileName)[] SaveTestOutputFileMultiFrame<TPixel>(
Image<TPixel> image,
string extension = "png",
IImageEncoder encoder = null,
@ -216,27 +216,17 @@ public class ImagingTestCaseUtility
{
encoder ??= TestEnvironment.GetReferenceEncoder($"foo.{extension}");
string[] files = this.GetTestOutputFileNamesMultiFrame(
(int Index, string FileName)[] files = this.GetTestOutputFileNamesMultiFrame(
image.Frames.Count,
extension,
testOutputDetails,
appendPixelTypeToFileName,
predicate: predicate).ToArray();
for (int i = 0; i < image.Frames.Count; i++)
foreach ((int Index, string FileName) file in files)
{
if (predicate != null && !predicate(i, image.Frames.Count))
{
continue;
}
if (i >= files.Length)
{
break;
}
using Image<TPixel> frameImage = image.Frames.CloneFrame(i);
string filePath = files[i];
using Image<TPixel> frameImage = image.Frames.CloneFrame(file.Index);
string filePath = file.FileName;
using FileStream stream = File.OpenWrite(filePath);
frameImage.Save(stream, encoder);
}
@ -252,14 +242,14 @@ public class ImagingTestCaseUtility
=> TestEnvironment.GetReferenceOutputFileName(
this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription));
public string[] GetReferenceOutputFileNamesMultiFrame(
public (int Index, string FileName)[] GetReferenceOutputFileNamesMultiFrame(
int frameCount,
string extension,
object testOutputDetails,
bool appendPixelTypeToFileName = true,
Func<int, int, bool> predicate = null)
=> this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails, appendPixelTypeToFileName, predicate: predicate)
.Select(TestEnvironment.GetReferenceOutputFileName).ToArray();
.Select(x => (x.Index, TestEnvironment.GetReferenceOutputFileName(x.FileName))).ToArray();
internal void Init(string typeName, string methodName, string outputSubfolderName)
{

7
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -336,7 +336,7 @@ public static class TestImageExtensions
Func<int, int, bool> predicate = null)
where TPixel : unmanaged, IPixel<TPixel>
{
string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame(
(int Index, string FileName)[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame(
frameCount,
extension,
testOutputDetails,
@ -345,10 +345,11 @@ public static class TestImageExtensions
List<Image<TPixel>> temporaryFrameImages = new();
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]);
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0].FileName);
foreach (string path in frameFiles)
for (int i = 0; i < frameFiles.Length; i++)
{
string path = frameFiles[i].FileName;
if (!File.Exists(path))
{
throw new FileNotFoundException("Reference output file missing: " + path);

8
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -200,13 +200,13 @@ public class TestImageProviderTests
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image);
(int Index, string FileName)[] files = provider.Utility.SaveTestOutputFileMultiFrame(image);
Assert.True(files.Length > 2);
foreach (string path in files)
foreach ((int Index, string FileName) file in files)
{
this.Output.WriteLine(path);
Assert.True(File.Exists(path));
this.Output.WriteLine(file.FileName);
Assert.True(File.Exists(file.FileName));
}
}

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556
size 102

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556
size 102

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:02c81691db45508be3fe8c6051e8b09937eaa347f332f1097026e00a0e084b38
size 99

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:02c81691db45508be3fe8c6051e8b09937eaa347f332f1097026e00a0e084b38
size 99

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/02.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e1322aa335ad845cacfa20266bc0ffc31db117376373c15bcdb222abcf4b8f83
size 113

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/02.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/03.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/04.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/05.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/06.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/07.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/08.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/104.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/112.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/120.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/128.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/16.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/24.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/32.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/40.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/48.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/56.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/64.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/72.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/80.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/88.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/96.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_4-split-idat-zero-length.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/02.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8
size 89

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/02.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556
size 102

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6118abf41302696bfe4a62baa32a7798b3833ca49fc3854dcde4a810905fc457
size 1012

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e8d11d84cab8580efc7397870116ff3ddde4c3a5da9c2c2baa473eb463326072
size 915

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/02.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f36ea3ed9e652fe005c2767d758da268feb444e90833e02ab3fb15d1155037fd
size 971

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/03.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eb61715535a98977f4a3cb89ac85bc56826a54b4bdd4393d89ca445f50865d22
size 990

3
tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/04.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0648a06346a6ccca69503da187bc5901c7275ade03834030a8f3895ad03ff58a
size 941

3
tests/Images/Input/Png/animated/12-dispose-prev-first.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:28138dd4a4ad56f86c18216b051b96a1bb353b69ebd85ce272928b085bb84400
size 371

3
tests/Images/Input/Png/animated/14-dispose-background-before-region.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3d4ba499c333a600dd1e42f374a9a68fb783b0f3274091ab34f5b395462eae8
size 327

3
tests/Images/Input/Png/animated/15-dispose-background-region.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6db2a90911b40067b7f35b01869115f081858ee15b28374e57c51c7e5c0cb524
size 492

3
tests/Images/Input/Png/animated/21-blend-over-multiple.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b571f7034ef1fb355182cf00fa6ccd7d784720709f229e3bcc5948abf2f81ee
size 28791

3
tests/Images/Input/Png/animated/4-split-idat-zero-length.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e0ffdbe7dc6dad05dfc4cacd712b76c1121cd7378671212ae000d76c07b1a4e
size 273

3
tests/Images/Input/Png/animated/7-dispose-none.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1abab0c7de5252a16da34777ff34c4a29c6000493d23ac1777cd17415e6aab33
size 617

3
tests/Images/Input/Png/animated/8-dispose-background.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f26f544d5f7f0c8d4448ca020c93f79b64e1d607c7c561082bc989ca2e91fad
size 572

0
tests/Images/Input/Png/apng.png → tests/Images/Input/Png/animated/apng.png

Loading…
Cancel
Save