Skip to content

Commit

Permalink
Update tests, fix issues
Browse files Browse the repository at this point in the history
  • Loading branch information
JimBobSquarePants committed Nov 24, 2023
1 parent cc0727b commit 4b852e6
Show file tree
Hide file tree
Showing 147 changed files with 398 additions and 318 deletions.
50 changes: 41 additions & 9 deletions src/ImageSharp/Formats/AnimationUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@ internal static class AnimationUtilities
/// <param name="currentFrame">The current frame.</param>
/// <param name="resultFrame">The resultant output.</param>
/// <param name="replacement">The value to use when replacing duplicate pixels.</param>
/// <param name="clampingMode">The clamping bound to apply when calculating difference bounds.</param>
/// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
Configuration configuration,
ImageFrame<TPixel>? previousFrame,
ImageFrame<TPixel> currentFrame,
ImageFrame<TPixel> resultFrame,
Vector4 replacement)
Vector4 replacement,
ClampingMode clampingMode = ClampingMode.None)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32.
// If someone wants to do that, they have my unlimited thanks.
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
IMemoryOwner<Vector4> buffers = memoryAllocator.Allocate<Vector4>(currentFrame.Width * 3, AllocationOptions.Clean);
Span<Vector4> previous = buffers.GetSpan()[..currentFrame.Width];
Expand Down Expand Up @@ -78,10 +82,11 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
Vector256<float> c = Unsafe.Add(ref currentBase, x);

// Compare the previous and current pixels
Vector256<float> neq = Avx.CompareEqual(p, c);
Vector256<int> mask = neq.AsInt32();
Vector256<int> mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32());
mask = Avx2.CompareEqual(mask.AsInt64(), Vector256<long>.AllBitsSet).AsInt32();
mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32();

neq = Avx.Xor(neq, Vector256<float>.AllBitsSet);
Vector256<int> neq = Avx2.Xor(mask.AsInt64(), Vector256<long>.AllBitsSet).AsInt32();
int m = Avx2.MoveMask(neq.AsByte());
if (m != 0)
{
Expand All @@ -95,11 +100,7 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
hasDiff = true;
}

// Capture the original alpha values.
mask = Avx2.HorizontalAdd(mask, mask);
mask = Avx2.HorizontalAdd(mask, mask);
mask = Avx2.CompareEqual(mask, Vector256.Create(-4));

// Replace the pixel value with the replacement if the full pixel is matched.
Vector256<float> r = Avx.BlendVariable(c, replacement256, mask.AsSingle());
Unsafe.Add(ref resultBase, x) = r;

Expand Down Expand Up @@ -153,6 +154,37 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
Numerics.Clamp(right, left + 1, resultFrame.Width),
Numerics.Clamp(bottom, top + 1, resultFrame.Height));

// Webp requires even bounds
if (clampingMode == ClampingMode.Even)
{
bounds.Width = Math.Min(resultFrame.Width, bounds.Width + (bounds.X & 1));
bounds.Height = Math.Min(resultFrame.Height, bounds.Height + (bounds.Y & 1));
bounds.X = Math.Max(0, bounds.X - (bounds.X & 1));
bounds.Y = Math.Max(0, bounds.Y - (bounds.Y & 1));
}

return new(hasDiff, bounds);
}

public static void CopySource<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2DRegion<TPixel> sourceBuffer = source.PixelBuffer.GetRegion(bounds);
Buffer2DRegion<TPixel> destBuffer = destination.PixelBuffer.GetRegion(bounds);
for (int y = 0; y < destination.Height; y++)
{
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(y);
Span<TPixel> destRow = destBuffer.DangerousGetRowSpan(y);
sourceRow.CopyTo(destRow);
}
}
}

#pragma warning disable SA1201 // Elements should appear in the correct order
internal enum ClampingMode
#pragma warning restore SA1201 // Elements should appear in the correct order
{
None,

Even,
}
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Gif/MetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this Gif
ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = FrameBlendMode.Over,
BlendMode = source.DisposalMethod == GifDisposalMethod.RestoreToBackground ? FrameBlendMode.Source : FrameBlendMode.Over,
};

private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ public static PngPhysical Parse(ReadOnlySpan<byte> data)
/// <returns>The constructed PngPhysicalChunkData instance.</returns>
public static PngPhysical FromMetadata(ImageMetadata meta)
{
byte unitSpecifier = 0;
uint x;
uint y;

byte unitSpecifier;
switch (meta.ResolutionUnits)
{
case PixelResolutionUnit.AspectRatio:
Expand Down
31 changes: 23 additions & 8 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
chunk.Length - 4,
currentFrame,
pngMetadata,
this.ReadNextDataChunkAndSkipSeq,
this.ReadNextFrameDataChunk,
currentFrameControl.Value,
cancellationToken);

Expand Down Expand Up @@ -1719,19 +1719,34 @@ private int ReadNextDataChunk()
}

/// <summary>
/// Reads the next data chunk and skip sequence number.
/// Reads the next animated frame data chunk.
/// </summary>
/// <returns>Count of bytes in the next data chunk, or 0 if there are no more data chunks left.</returns>
private int ReadNextDataChunkAndSkipSeq()
private int ReadNextFrameDataChunk()
{
int length = this.ReadNextDataChunk();
if (this.ReadNextDataChunk() is 0)
if (this.nextChunk != null)
{
return length;
return 0;
}

this.currentStream.Position += 4; // Skip sequence number
return length - 4;
Span<byte> buffer = stackalloc byte[20];

_ = this.currentStream.Read(buffer, 0, 4);

if (this.TryReadChunk(buffer, out PngChunk chunk))
{
if (chunk.Type is PngChunkType.FrameData)
{
chunk.Data?.Dispose();

this.currentStream.Position += 4; // Skip sequence number
return chunk.Length - 4;
}

this.nextChunk = chunk;
}

return 0;
}

/// <summary>
Expand Down
14 changes: 12 additions & 2 deletions src/ImageSharp/Formats/Png/PngEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);

if (difference && previousDisposal != PngDisposalMethod.RestoreToBackground)
{
if (frameMetadata.BlendMethod == PngBlendMethod.Source)
{
// We've potentially introduced transparency within our area of interest
// so we need to overwrite the changed area with the full data.
AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds);
}
}

if (clearTransparency)
{
ClearTransparentPixels(encodingFrame);
Expand Down Expand Up @@ -258,7 +268,7 @@ private static PngMetadata GetPngMetadata<TPixel>(Image<TPixel> image)
{
if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
{
return png;
return (PngMetadata)png.DeepClone();
}

if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
Expand All @@ -282,7 +292,7 @@ private static PngFrameMetadata GetPngFrameMetadata<TPixel>(ImageFrame<TPixel> f
{
if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
{
return png;
return (PngFrameMetadata)png.DeepClone();
}

if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Png/PngMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
if (c == metadata.BackgroundColor)
{
// Png treats background as fully empty
c = default;
c = Color.Transparent;
break;
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal readonly struct WebpFrameData
/// </summary>
public const uint HeaderSize = 16;

public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
{
this.DataSize = dataSize;
this.X = x;
Expand All @@ -32,12 +32,12 @@ public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uin
width,
height,
duration,
(flags & 2) == 0 ? WebpBlendingMethod.Over : WebpBlendingMethod.Source,
(flags & 2) == 0 ? WebpBlendMethod.Over : WebpBlendMethod.Source,
(flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose)
{
}

public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
: this(0, x, y, width, height, duration, blendingMethod, disposalMethod)
{
}
Expand Down Expand Up @@ -76,7 +76,7 @@ public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, Web
/// <summary>
/// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendingMethod BlendingMethod { get; }
public WebpBlendMethod BlendingMethod { get; }

/// <summary>
/// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
Expand All @@ -93,7 +93,7 @@ public long WriteHeaderTo(Stream stream)
{
byte flags = 0;

if (this.BlendingMethod is WebpBlendingMethod.Source)
if (this.BlendingMethod is WebpBlendMethod.Source)
{
// Set blending flag.
flags |= 2;
Expand All @@ -107,8 +107,8 @@ public long WriteHeaderTo(Stream stream)

long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData);

WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.X / 2f));
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.Y / 2f));
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Webp/MetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this Web
ColorTableMode = FrameColorTableMode.Global,
Duration = TimeSpan.FromMilliseconds(source.FrameDelay),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = source.BlendMethod == WebpBlendingMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
BlendMode = source.BlendMethod == WebpBlendMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
};

private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima

using Buffer2D<TPixel> decodedImageFrame = this.DecodeImageFrameData<TPixel>(frameData, webpInfo);

bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.Over;
bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendMethod.Over;
DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend);

previousFrame = currentFrame ?? image.Frames.RootFrame;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Formats.Webp;

/// <summary>
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public enum WebpBlendingMethod
public enum WebpBlendMethod
{
/// <summary>
/// Do not blend. After disposing of the previous frame,
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static WebpMetadata GetWebpMetadata<TPixel>(Image<TPixel> image)
{
if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
{
return webp;
return (WebpMetadata)webp.DeepClone();
}

if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
Expand All @@ -44,7 +44,7 @@ public static WebpFrameMetadata GetWebpFrameMetadata<TPixel>(ImageFrame<TPixel>
{
if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
{
return webp;
return (WebpFrameMetadata)webp.DeepClone();
}

if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
Expand Down
25 changes: 23 additions & 2 deletions src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,18 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);

ImageFrame<TPixel>? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);

(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even);

if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground)
{
if (frameMetadata.BlendMethod == WebpBlendMethod.Source)
{
// We've potentially introduced transparency within our area of interest
// so we need to overwrite the changed area with the full data.
AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds);
}
}

using Vp8LEncoder animatedEncoder = new(
this.memoryAllocator,
Expand Down Expand Up @@ -225,7 +236,17 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);

ImageFrame<TPixel>? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);
(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even);

if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground)
{
if (frameMetadata.BlendMethod == WebpBlendMethod.Source)
{
// We've potentially introduced transparency within our area of interest
// so we need to overwrite the changed area with the full data.
AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds);
}
}

using Vp8Encoder animatedEncoder = new(
this.memoryAllocator,
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private WebpFrameMetadata(WebpFrameMetadata other)
/// <summary>
/// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendingMethod BlendMethod { get; set; }
public WebpBlendMethod BlendMethod { get; set; }

/// <summary>
/// Gets or sets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
Expand All @@ -49,7 +49,7 @@ internal static WebpFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadat
=> new()
{
FrameDelay = (uint)metadata.Duration.Milliseconds,
BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendingMethod.Source : WebpBlendingMethod.Over,
BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendMethod.Source : WebpBlendMethod.Over,
DisposalMethod = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose
};
}
Loading

0 comments on commit 4b852e6

Please sign in to comment.