Skip to content

Commit 4b852e6

Browse files
Update tests, fix issues
1 parent cc0727b commit 4b852e6

147 files changed

Lines changed: 398 additions & 318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/ImageSharp/Formats/AnimationUtilities.cs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,19 @@ internal static class AnimationUtilities
2727
/// <param name="currentFrame">The current frame.</param>
2828
/// <param name="resultFrame">The resultant output.</param>
2929
/// <param name="replacement">The value to use when replacing duplicate pixels.</param>
30+
/// <param name="clampingMode">The clamping bound to apply when calculating difference bounds.</param>
3031
/// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
3132
public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
3233
Configuration configuration,
3334
ImageFrame<TPixel>? previousFrame,
3435
ImageFrame<TPixel> currentFrame,
3536
ImageFrame<TPixel> resultFrame,
36-
Vector4 replacement)
37+
Vector4 replacement,
38+
ClampingMode clampingMode = ClampingMode.None)
3739
where TPixel : unmanaged, IPixel<TPixel>
3840
{
41+
// TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32.
42+
// If someone wants to do that, they have my unlimited thanks.
3943
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
4044
IMemoryOwner<Vector4> buffers = memoryAllocator.Allocate<Vector4>(currentFrame.Width * 3, AllocationOptions.Clean);
4145
Span<Vector4> previous = buffers.GetSpan()[..currentFrame.Width];
@@ -78,10 +82,11 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
7882
Vector256<float> c = Unsafe.Add(ref currentBase, x);
7983

8084
// Compare the previous and current pixels
81-
Vector256<float> neq = Avx.CompareEqual(p, c);
82-
Vector256<int> mask = neq.AsInt32();
85+
Vector256<int> mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32());
86+
mask = Avx2.CompareEqual(mask.AsInt64(), Vector256<long>.AllBitsSet).AsInt32();
87+
mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32();
8388

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

98-
// Capture the original alpha values.
99-
mask = Avx2.HorizontalAdd(mask, mask);
100-
mask = Avx2.HorizontalAdd(mask, mask);
101-
mask = Avx2.CompareEqual(mask, Vector256.Create(-4));
102-
103+
// Replace the pixel value with the replacement if the full pixel is matched.
103104
Vector256<float> r = Avx.BlendVariable(c, replacement256, mask.AsSingle());
104105
Unsafe.Add(ref resultBase, x) = r;
105106

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

157+
// Webp requires even bounds
158+
if (clampingMode == ClampingMode.Even)
159+
{
160+
bounds.Width = Math.Min(resultFrame.Width, bounds.Width + (bounds.X & 1));
161+
bounds.Height = Math.Min(resultFrame.Height, bounds.Height + (bounds.Y & 1));
162+
bounds.X = Math.Max(0, bounds.X - (bounds.X & 1));
163+
bounds.Y = Math.Max(0, bounds.Y - (bounds.Y & 1));
164+
}
165+
156166
return new(hasDiff, bounds);
157167
}
168+
169+
public static void CopySource<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle bounds)
170+
where TPixel : unmanaged, IPixel<TPixel>
171+
{
172+
Buffer2DRegion<TPixel> sourceBuffer = source.PixelBuffer.GetRegion(bounds);
173+
Buffer2DRegion<TPixel> destBuffer = destination.PixelBuffer.GetRegion(bounds);
174+
for (int y = 0; y < destination.Height; y++)
175+
{
176+
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(y);
177+
Span<TPixel> destRow = destBuffer.DangerousGetRowSpan(y);
178+
sourceRow.CopyTo(destRow);
179+
}
180+
}
181+
}
182+
183+
#pragma warning disable SA1201 // Elements should appear in the correct order
184+
internal enum ClampingMode
185+
#pragma warning restore SA1201 // Elements should appear in the correct order
186+
{
187+
None,
188+
189+
Even,
158190
}

src/ImageSharp/Formats/Gif/MetadataExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this Gif
8383
ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
8484
Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10),
8585
DisposalMode = GetMode(source.DisposalMethod),
86-
BlendMode = FrameBlendMode.Over,
86+
BlendMode = source.DisposalMethod == GifDisposalMethod.RestoreToBackground ? FrameBlendMode.Source : FrameBlendMode.Over,
8787
};
8888

8989
private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ public static PngPhysical Parse(ReadOnlySpan<byte> data)
6161
/// <returns>The constructed PngPhysicalChunkData instance.</returns>
6262
public static PngPhysical FromMetadata(ImageMetadata meta)
6363
{
64-
byte unitSpecifier = 0;
6564
uint x;
6665
uint y;
6766

67+
byte unitSpecifier;
6868
switch (meta.ResolutionUnits)
6969
{
7070
case PixelResolutionUnit.AspectRatio:

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
217217
chunk.Length - 4,
218218
currentFrame,
219219
pngMetadata,
220-
this.ReadNextDataChunkAndSkipSeq,
220+
this.ReadNextFrameDataChunk,
221221
currentFrameControl.Value,
222222
cancellationToken);
223223

@@ -1719,19 +1719,34 @@ private int ReadNextDataChunk()
17191719
}
17201720

17211721
/// <summary>
1722-
/// Reads the next data chunk and skip sequence number.
1722+
/// Reads the next animated frame data chunk.
17231723
/// </summary>
17241724
/// <returns>Count of bytes in the next data chunk, or 0 if there are no more data chunks left.</returns>
1725-
private int ReadNextDataChunkAndSkipSeq()
1725+
private int ReadNextFrameDataChunk()
17261726
{
1727-
int length = this.ReadNextDataChunk();
1728-
if (this.ReadNextDataChunk() is 0)
1727+
if (this.nextChunk != null)
17291728
{
1730-
return length;
1729+
return 0;
17311730
}
17321731

1733-
this.currentStream.Position += 4; // Skip sequence number
1734-
return length - 4;
1732+
Span<byte> buffer = stackalloc byte[20];
1733+
1734+
_ = this.currentStream.Read(buffer, 0, 4);
1735+
1736+
if (this.TryReadChunk(buffer, out PngChunk chunk))
1737+
{
1738+
if (chunk.Type is PngChunkType.FrameData)
1739+
{
1740+
chunk.Data?.Dispose();
1741+
1742+
this.currentStream.Position += 4; // Skip sequence number
1743+
return chunk.Length - 4;
1744+
}
1745+
1746+
this.nextChunk = chunk;
1747+
}
1748+
1749+
return 0;
17351750
}
17361751

17371752
/// <summary>

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
214214
ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
215215
(bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero);
216216

217+
if (difference && previousDisposal != PngDisposalMethod.RestoreToBackground)
218+
{
219+
if (frameMetadata.BlendMethod == PngBlendMethod.Source)
220+
{
221+
// We've potentially introduced transparency within our area of interest
222+
// so we need to overwrite the changed area with the full data.
223+
AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds);
224+
}
225+
}
226+
217227
if (clearTransparency)
218228
{
219229
ClearTransparentPixels(encodingFrame);
@@ -258,7 +268,7 @@ private static PngMetadata GetPngMetadata<TPixel>(Image<TPixel> image)
258268
{
259269
if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
260270
{
261-
return png;
271+
return (PngMetadata)png.DeepClone();
262272
}
263273

264274
if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
@@ -282,7 +292,7 @@ private static PngFrameMetadata GetPngFrameMetadata<TPixel>(ImageFrame<TPixel> f
282292
{
283293
if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
284294
{
285-
return png;
295+
return (PngFrameMetadata)png.DeepClone();
286296
}
287297

288298
if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))

src/ImageSharp/Formats/Png/PngMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
100100
if (c == metadata.BackgroundColor)
101101
{
102102
// Png treats background as fully empty
103-
c = default;
103+
c = Color.Transparent;
104104
break;
105105
}
106106
}

src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal readonly struct WebpFrameData
1212
/// </summary>
1313
public const uint HeaderSize = 16;
1414

15-
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
15+
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
1616
{
1717
this.DataSize = dataSize;
1818
this.X = x;
@@ -32,12 +32,12 @@ public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uin
3232
width,
3333
height,
3434
duration,
35-
(flags & 2) == 0 ? WebpBlendingMethod.Over : WebpBlendingMethod.Source,
35+
(flags & 2) == 0 ? WebpBlendMethod.Over : WebpBlendMethod.Source,
3636
(flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose)
3737
{
3838
}
3939

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

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

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

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

110-
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2);
111-
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2);
110+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.X / 2f));
111+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.Y / 2f));
112112
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
113113
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
114114
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);

src/ImageSharp/Formats/Webp/MetadataExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this Web
6363
ColorTableMode = FrameColorTableMode.Global,
6464
Duration = TimeSpan.FromMilliseconds(source.FrameDelay),
6565
DisposalMode = GetMode(source.DisposalMethod),
66-
BlendMode = source.BlendMethod == WebpBlendingMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
66+
BlendMode = source.BlendMethod == WebpBlendMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
6767
};
6868

6969
private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch

src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
202202

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

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

208208
previousFrame = currentFrame ?? image.Frames.RootFrame;

src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs renamed to src/ImageSharp/Formats/Webp/WebpBlendMethod.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
namespace SixLabors.ImageSharp.Formats.Webp;
55

66
/// <summary>
77
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
88
/// </summary>
9-
public enum WebpBlendingMethod
9+
public enum WebpBlendMethod
1010
{
1111
/// <summary>
1212
/// Do not blend. After disposing of the previous frame,

0 commit comments

Comments
 (0)