From 6570fc6c7446d594e20bc8c85e1404d48fc59862 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 Jan 2024 13:22:33 +1000 Subject: [PATCH 1/8] Update WebpFrameMetadata.cs --- src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index 422ad6bc7c..cd1b5d5905 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -48,7 +48,7 @@ public class WebpFrameMetadata : IDeepCloneable internal static WebpFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) => new() { - FrameDelay = (uint)metadata.Duration.Milliseconds, + FrameDelay = (uint)metadata.Duration.TotalMilliseconds, BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendMethod.Source : WebpBlendMethod.Over, DisposalMethod = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose }; From 6580494b5aff13480f81ed623b14b5c3c1eeba62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:43:09 +0000 Subject: [PATCH 2/8] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 6 +++--- .github/workflows/code-coverage.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 75bcb8a256..57488a1d0a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -67,7 +67,7 @@ jobs: run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id - name: Git Setup LFS Cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: lfs-cache with: path: .git/lfs @@ -80,7 +80,7 @@ jobs: uses: NuGet/setup-nuget@v1 - name: NuGet Setup Cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: nuget-cache with: path: ~/.nuget @@ -162,7 +162,7 @@ jobs: uses: NuGet/setup-nuget@v1 - name: NuGet Setup Cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: nuget-cache with: path: ~/.nuget diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 62b6477ee6..f9d2da0c8b 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -34,7 +34,7 @@ jobs: run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id - name: Git Setup LFS Cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: lfs-cache with: path: .git/lfs @@ -47,7 +47,7 @@ jobs: uses: NuGet/setup-nuget@v1 - name: NuGet Setup Cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: nuget-cache with: path: ~/.nuget From ecdd12e7b58ae6ef01947b849f844ca7e5b0b254 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:10:56 +0000 Subject: [PATCH 3/8] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 62b6477ee6..19a9689ce2 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -81,7 +81,7 @@ jobs: path: tests/Images/ActualOutput/ - name: Codecov Update - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests From d865ab96f27d07435355d858fa67fb6fbad028ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:00:47 +0000 Subject: [PATCH 4/8] Bump NuGet/setup-nuget from 1 to 2 Bumps [NuGet/setup-nuget](https://github.com/nuget/setup-nuget) from 1 to 2. - [Release notes](https://github.com/nuget/setup-nuget/releases) - [Commits](https://github.com/nuget/setup-nuget/compare/v1...v2) --- updated-dependencies: - dependency-name: NuGet/setup-nuget dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 57488a1d0a..52a52c472b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -77,7 +77,7 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v1 + uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache uses: actions/cache@v4 @@ -159,7 +159,7 @@ jobs: submodules: recursive - name: NuGet Install - uses: NuGet/setup-nuget@v1 + uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache uses: actions/cache@v4 diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 58e19631f5..d531e68601 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -44,7 +44,7 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v1 + uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache uses: actions/cache@v4 From a940a55f5374e0baacb4da23dc85e7dd91807109 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 23 Feb 2024 23:07:34 +1000 Subject: [PATCH 5/8] Make DrawImage processor more robust to bad input. --- .../Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 0d81270b16..d5499d5865 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -98,9 +98,10 @@ internal class DrawImageProcessor : ImageProcessor top = 0; } - // clamp the height/width to the availible space left to prevent overflowing + // Clamp the height/width to the available space left to prevent overflowing foregroundRectangle.Width = Math.Min(source.Width - left, foregroundRectangle.Width); foregroundRectangle.Height = Math.Min(source.Height - top, foregroundRectangle.Height); + foregroundRectangle = Rectangle.Intersect(foregroundRectangle, this.ForegroundImage.Bounds); int width = foregroundRectangle.Width; int height = foregroundRectangle.Height; @@ -111,7 +112,6 @@ internal class DrawImageProcessor : ImageProcessor } // Sanitize the dimensions so that we don't try and sample outside the image. - foregroundRectangle = Rectangle.Intersect(foregroundRectangle, this.ForegroundImage.Bounds); Rectangle backgroundRectangle = Rectangle.Intersect(new(left, top, width, height), this.SourceRectangle); Configuration configuration = this.Configuration; From b4049ce517ddd94fdbac398633f895957defe157 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 24 Feb 2024 00:12:15 +1000 Subject: [PATCH 6/8] Don't skip data read on animation and frame control chunks --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 9 +++++++-- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 8 ++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Png/issues/Issue_2666.png | 3 +++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Png/issues/Issue_2666.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 95154d68d3..179bf38ba8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1871,8 +1871,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals PngChunkType type = this.ReadChunkType(buffer); // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. - // We can skip all other chunk data in the stream for better performance. - if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency && type != PngChunkType.Palette) + // We can skip most other chunk data in the stream for better performance. + if (this.colorMetadataOnly && + type != PngChunkType.Header && + type != PngChunkType.Transparency && + type != PngChunkType.Palette && + type != PngChunkType.AnimationControl && + type != PngChunkType.FrameControl) { chunk = new PngChunk(length, type); return true; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index bc277bf485..743d1f797c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.IO; using System.Runtime.Intrinsics.X86; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; @@ -665,4 +666,11 @@ public partial class PngDecoderTests Assert.True(eofHitCounter.EofHitCount <= 3); Assert.Equal(new Size(200, 120), eofHitCounter.Image.Size); } + + [Fact] + public void Decode_Issue2666() + { + string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666)); + using Image image = Image.Load(path); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8aa95d3496..82db313585 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -73,6 +73,7 @@ public static class TestImages 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"; + public const string Issue2666 = "Png/Issues/Issue_2666.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/Input/Png/issues/Issue_2666.png b/tests/Images/Input/Png/issues/Issue_2666.png new file mode 100644 index 0000000000..b918fd4744 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2666.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed7665cdfd5fad00c5995040350a254b96af6c0c95ab13975f2291e9d3fce0f3 +size 8244837 From 83ecbdb19fb180266a154896ccb9949f3bdd8c5d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 26 Feb 2024 10:27:03 +1000 Subject: [PATCH 7/8] Fix path --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 1 - tests/ImageSharp.Tests/TestImages.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 743d1f797c..b9f6075fb8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using System.Runtime.Intrinsics.X86; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 82db313585..4e33bbd8ad 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -73,7 +73,7 @@ public static class TestImages 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"; - public const string Issue2666 = "Png/Issues/Issue_2666.png"; + public const string Issue2666 = "Png/issues/Issue_2666.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; From f44b761f3b6731e2c20b56f10515962178ed958c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 27 Feb 2024 11:09:36 +1000 Subject: [PATCH 8/8] Add fixes 2668, 2676, and 2677 to main (#2678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent underflow * Fix file name * Revert "Fix file name" This reverts commit 15619ecd88bc67b6bf4a002094ba241836f8f10a. * Update AlphaDecoder.cs * Use a smarter approach to determine the transparent index * Fix casing * Update src/ImageSharp/Formats/Webp/AlphaDecoder.cs Co-authored-by: Günther Foidl * Update Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png * Normalize Color API * Aggressive inlining --------- Co-authored-by: Günther Foidl --- src/ImageSharp/Color/Color.cs | 93 +++++++++---------- .../Formats/Gif/GifFrameMetadata.cs | 4 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 20 +++- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 9 +- .../Color/ColorTests.CastTo.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 17 ++++ .../Formats/WebP/WebpDecoderTests.cs | 11 +++ tests/ImageSharp.Tests/TestImages.cs | 4 + ...antized_Encode_Alpha_Rgba32_Issue_2668.png | 3 + tests/Images/Input/Png/issues/Issue_2668.png | 3 + tests/Images/Input/Webp/issues/Issue2670.webp | 3 + 11 files changed, 112 insertions(+), 57 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png create mode 100644 tests/Images/Input/Png/issues/Issue_2668.png create mode 100644 tests/Images/Input/Webp/issues/Issue2670.webp diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 82ecab3909..8f54680ec4 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -25,7 +25,7 @@ public readonly partial struct Color : IEquatable /// Initializes a new instance of the struct. /// /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private Color(Vector4 vector) { this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); @@ -36,28 +36,13 @@ public readonly partial struct Color : IEquatable /// Initializes a new instance of the struct. /// /// The pixel containing color information. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private Color(IPixel pixel) { this.boxedHighPrecisionPixel = pixel; this.data = default; } - /// - /// Converts a to . - /// - /// The . - /// The . - public static explicit operator Vector4(Color color) => color.ToScaledVector4(); - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static explicit operator Color(Vector4 source) => new(source); - /// /// Checks whether two structures are equal. /// @@ -67,7 +52,7 @@ public readonly partial struct Color : IEquatable /// True if the parameter is equal to the parameter; /// otherwise, false. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Color left, Color right) => left.Equals(right); /// @@ -79,36 +64,44 @@ public readonly partial struct Color : IEquatable /// True if the parameter is not equal to the parameter; /// otherwise, false. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Color left, Color right) => !left.Equals(right); /// /// Creates a from the given . /// - /// The pixel to convert from. + /// The pixel to convert from. /// The pixel format. /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromPixel(TPixel pixel) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Color FromPixel(TPixel source) where TPixel : unmanaged, IPixel { // Avoid boxing in case we can convert to Vector4 safely and efficiently PixelTypeInfo info = TPixel.GetPixelTypeInfo(); if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) { - return new(pixel.ToScaledVector4()); + return new(source.ToScaledVector4()); } - return new(pixel); + return new(source); } + /// + /// Creates a from a generic scaled . + /// + /// The vector to load the pixel from. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Color FromScaledVector(Vector4 source) => new(source); + /// /// Bulk converts a span of a specified type to a span of . /// /// The pixel type to convert to. /// The source pixel span. /// The destination color span. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void FromPixel(ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel { @@ -120,7 +113,7 @@ public readonly partial struct Color : IEquatable { for (int i = 0; i < destination.Length; i++) { - destination[i] = new(source[i].ToScaledVector4()); + destination[i] = FromScaledVector(source[i].ToScaledVector4()); } } else @@ -143,7 +136,7 @@ public readonly partial struct Color : IEquatable /// /// The . /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color ParseHex(string hex) { Rgba32 rgba = Rgba32.ParseHex(hex); @@ -162,7 +155,7 @@ public readonly partial struct Color : IEquatable /// /// The . /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryParseHex(string hex, out Color result) { result = default; @@ -236,16 +229,16 @@ public readonly partial struct Color : IEquatable /// The color having it's alpha channel altered. public Color WithAlpha(float alpha) { - Vector4 v = (Vector4)this; + Vector4 v = this.ToScaledVector4(); v.W = alpha; - return new Color(v); + return FromScaledVector(v); } /// /// Gets the hexadecimal representation of the color instance in rrggbbaa form. /// /// A hexadecimal string representation of the value. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ToHex() { if (this.boxedHighPrecisionPixel is not null) @@ -263,8 +256,8 @@ public readonly partial struct Color : IEquatable /// Converts the color instance to a specified type. /// /// The pixel type to convert to. - /// The pixel value. - [MethodImpl(InliningOptions.ShortMethod)] + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public TPixel ToPixel() where TPixel : unmanaged, IPixel { @@ -281,13 +274,30 @@ public readonly partial struct Color : IEquatable return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); } + /// + /// Expands the color into a generic ("scaled") representation + /// with values scaled and clamped between 0 and 1. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() + { + if (this.boxedHighPrecisionPixel is null) + { + return this.data; + } + + return this.boxedHighPrecisionPixel.ToScaledVector4(); + } + /// /// Bulk converts a span of to a span of a specified type. /// /// The pixel type to convert to. /// The source color span. /// The destination pixel span. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ToPixel(ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel { @@ -301,7 +311,7 @@ public readonly partial struct Color : IEquatable } /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Color other) { if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) @@ -316,7 +326,7 @@ public readonly partial struct Color : IEquatable public override bool Equals(object? obj) => obj is Color other && this.Equals(other); /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { if (this.boxedHighPrecisionPixel is null) @@ -326,15 +336,4 @@ public readonly partial struct Color : IEquatable return this.boxedHighPrecisionPixel.GetHashCode(); } - - [MethodImpl(InliningOptions.ShortMethod)] - private Vector4 ToScaledVector4() - { - if (this.boxedHighPrecisionPixel is null) - { - return this.data; - } - - return this.boxedHighPrecisionPixel.ToScaledVector4(); - } } diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index f8734bb5a3..6598def2a5 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -82,13 +82,13 @@ public class GifFrameMetadata : IDeepCloneable { // TODO: v4 How do I link the parent metadata to the frame metadata to get the global color table? int index = -1; - float background = 1f; + const float background = 1f; if (metadata.ColorTable.HasValue) { ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; for (int i = 0; i < colorTable.Length; i++) { - Vector4 vector = (Vector4)colorTable[i]; + Vector4 vector = colorTable[i].ToScaledVector4(); if (vector.W < background) { index = i; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 993f53269d..113fef5957 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Buffers.Binary; using System.IO.Hashing; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; @@ -1559,7 +1560,24 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { // We can use the color data from the decoded metadata here. // We avoid dithering by default to preserve the original colors. - this.derivedTransparencyIndex = metadata.ColorTable.Value.Span.IndexOf(Color.Transparent); + ReadOnlySpan palette = metadata.ColorTable.Value.Span; + + // Certain operations perform alpha premultiplication, which can cause the color to change so we + // must search for the transparency index in the palette. + // Transparent pixels are much more likely to be found at the end of a palette. + int index = -1; + for (int i = palette.Length - 1; i >= 0; i--) + { + Vector4 instance = palette[i].ToScaledVector4(); + if (instance.W == 0f) + { + index = i; + break; + } + } + + this.derivedTransparencyIndex = index; + this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }, this.derivedTransparencyIndex); } else diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index 63e6541354..63571617fb 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -311,18 +311,15 @@ internal class AlphaDecoder : IDisposable private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { - if (Sse2.IsSupported) + // TODO: Investigate AdvSimd support for this method. + if (Sse2.IsSupported && width >= 9) { dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); - if (width <= 1) - { - return; - } - nuint i; Vector128 last = Vector128.Zero.WithElement(0, dst[0]); ref byte srcRef = ref MemoryMarshal.GetReference(input); ref byte dstRef = ref MemoryMarshal.GetReference(dst); + for (i = 1; i <= (uint)width - 8; i += 8) { Vector128 a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 14a5a44f18..4247345c7b 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -105,7 +105,7 @@ public partial class ColorTests public void Vector4Constructor() { // Act: - Color color = (Color)Vector4.One; + Color color = Color.FromScaledVector(Vector4.One); // Assert: Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 0fdd496308..a70fb86df1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -679,6 +680,22 @@ public partial class PngEncoderTests encoded.CompareToReferenceOutput(ImageComparer.Exact, provider); } + // https://github.com/SixLabors/ImageSharp/issues/2469 + [Theory] + [WithFile(TestImages.Png.Issue2668, PixelTypes.Rgba32)] + public void Issue2668_Quantized_Encode_Alpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + image.Mutate(x => x.Resize(100, 100)); + + PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette }; + + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder); + using Image encoded = Image.Load(actualOutputFile); + encoded.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index c38b13075a..0dda304b64 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -439,6 +439,17 @@ public class WebpDecoderTests image.CompareToOriginal(provider, ReferenceDecoder); } + // https://github.com/SixLabors/ImageSharp/issues/2670 + [Theory] + [WithFile(Lossy.Issue2670, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2670(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + [Theory] [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 933fcc4fae..022c3654a8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -151,6 +151,9 @@ public static class TestImages // Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447 public const string Issue2447 = "Png/issues/issue_2447.png"; + // Issue 2668: https://github.com/SixLabors/ImageSharp/issues/2668 + public const string Issue2668 = "Png/issues/Issue_2668.png"; + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; @@ -806,6 +809,7 @@ public static class TestImages public const string Issue1594 = "Webp/issues/Issue1594.webp"; public const string Issue2243 = "Webp/issues/Issue2243.webp"; public const string Issue2257 = "Webp/issues/Issue2257.webp"; + public const string Issue2670 = "Webp/issues/Issue2670.webp"; } } diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png new file mode 100644 index 0000000000..7af5391f70 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f934af128b85b9e8f557d71ac8b1f1473a0922d0754fc0c4ece0d0e3d8d94c39 +size 7702 diff --git a/tests/Images/Input/Png/issues/Issue_2668.png b/tests/Images/Input/Png/issues/Issue_2668.png new file mode 100644 index 0000000000..2ca8c46171 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2668.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8e5b2b933fd8fefd161f1d22970cb60247fd2d93b6c07b8b9ee1fdbc2241a3c +size 390225 diff --git a/tests/Images/Input/Webp/issues/Issue2670.webp b/tests/Images/Input/Webp/issues/Issue2670.webp new file mode 100644 index 0000000000..4dd1248986 --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2670.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23ad5eb449f693af68e51dd108a6b9847a8eb48b82ca5b848395a54c2e0be08f +size 152