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 @@ namespace SixLabors.ImageSharp;
/// 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 @@ private Color(Vector4 vector)
/// 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 @@ private Color(IPixel pixel)
/// 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 @@ private Color(IPixel pixel)
/// 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 static void FromPixel(ReadOnlySpan source, Span de
{
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 static void FromPixel(ReadOnlySpan source, Span de
///
/// The .
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color ParseHex(string hex)
{
Rgba32 rgba = Rgba32.ParseHex(hex);
@@ -162,7 +155,7 @@ public static Color ParseHex(string hex)
///
/// The .
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseHex(string hex, out Color result)
{
result = default;
@@ -236,16 +229,16 @@ public static bool TryParse(string input, out Color result)
/// 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 string ToHex()
/// 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 TPixel ToPixel()
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 static void ToPixel(ReadOnlySpan source, Span dest
}
///
- [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 bool Equals(Color other)
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 override int GetHashCode()
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 @@ internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata
{
// 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 @@ private void SanitizeAndSetEncoderOptions(
{
// 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 @@ private static void ColorIndexInverseTransformAlpha(
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 void Bgr24()
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.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 void Issue2469_Quantized_Encode_Artifacts(TestImageProvider(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 void WebpDecoder_CanDecode_Issue2257(TestImageProvider pr
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 Png
// 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 Lossy
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