Skip to content

Commit f44b761

Browse files
Add fixes 2668, 2676, and 2677 to main (#2678)
* Prevent underflow * Fix file name * Revert "Fix file name" This reverts commit 15619ec. * 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 <[email protected]> * Update Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png * Normalize Color API * Aggressive inlining --------- Co-authored-by: Günther Foidl <[email protected]>
1 parent f85500d commit f44b761

File tree

11 files changed

+112
-57
lines changed

11 files changed

+112
-57
lines changed

src/ImageSharp/Color/Color.cs

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp;
2525
/// Initializes a new instance of the <see cref="Color"/> struct.
2626
/// </summary>
2727
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
28-
[MethodImpl(InliningOptions.ShortMethod)]
28+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2929
private Color(Vector4 vector)
3030
{
3131
this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
@@ -36,28 +36,13 @@ private Color(Vector4 vector)
3636
/// Initializes a new instance of the <see cref="Color"/> struct.
3737
/// </summary>
3838
/// <param name="pixel">The pixel containing color information.</param>
39-
[MethodImpl(InliningOptions.ShortMethod)]
39+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4040
private Color(IPixel pixel)
4141
{
4242
this.boxedHighPrecisionPixel = pixel;
4343
this.data = default;
4444
}
4545

46-
/// <summary>
47-
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
48-
/// </summary>
49-
/// <param name="color">The <see cref="Color"/>.</param>
50-
/// <returns>The <see cref="Vector4"/>.</returns>
51-
public static explicit operator Vector4(Color color) => color.ToScaledVector4();
52-
53-
/// <summary>
54-
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
55-
/// </summary>
56-
/// <param name="source">The <see cref="Vector4"/>.</param>
57-
/// <returns>The <see cref="Color"/>.</returns>
58-
[MethodImpl(InliningOptions.ShortMethod)]
59-
public static explicit operator Color(Vector4 source) => new(source);
60-
6146
/// <summary>
6247
/// Checks whether two <see cref="Color"/> structures are equal.
6348
/// </summary>
@@ -67,7 +52,7 @@ private Color(IPixel pixel)
6752
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
6853
/// otherwise, false.
6954
/// </returns>
70-
[MethodImpl(InliningOptions.ShortMethod)]
55+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7156
public static bool operator ==(Color left, Color right) => left.Equals(right);
7257

7358
/// <summary>
@@ -79,36 +64,44 @@ private Color(IPixel pixel)
7964
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
8065
/// otherwise, false.
8166
/// </returns>
82-
[MethodImpl(InliningOptions.ShortMethod)]
67+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8368
public static bool operator !=(Color left, Color right) => !left.Equals(right);
8469

8570
/// <summary>
8671
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
8772
/// </summary>
88-
/// <param name="pixel">The pixel to convert from.</param>
73+
/// <param name="source">The pixel to convert from.</param>
8974
/// <typeparam name="TPixel">The pixel format.</typeparam>
9075
/// <returns>The <see cref="Color"/>.</returns>
91-
[MethodImpl(InliningOptions.ShortMethod)]
92-
public static Color FromPixel<TPixel>(TPixel pixel)
76+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
77+
public static Color FromPixel<TPixel>(TPixel source)
9378
where TPixel : unmanaged, IPixel<TPixel>
9479
{
9580
// Avoid boxing in case we can convert to Vector4 safely and efficiently
9681
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
9782
if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
9883
{
99-
return new(pixel.ToScaledVector4());
84+
return new(source.ToScaledVector4());
10085
}
10186

102-
return new(pixel);
87+
return new(source);
10388
}
10489

90+
/// <summary>
91+
/// Creates a <see cref="Color"/> from a generic scaled <see cref="Vector4"/>.
92+
/// </summary>
93+
/// <param name="source">The vector to load the pixel from.</param>
94+
/// <returns>The <see cref="Color"/>.</returns>
95+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96+
public static Color FromScaledVector(Vector4 source) => new(source);
97+
10598
/// <summary>
10699
/// Bulk converts a span of a specified <typeparamref name="TPixel"/> type to a span of <see cref="Color"/>.
107100
/// </summary>
108101
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
109102
/// <param name="source">The source pixel span.</param>
110103
/// <param name="destination">The destination color span.</param>
111-
[MethodImpl(InliningOptions.ShortMethod)]
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
112105
public static void FromPixel<TPixel>(ReadOnlySpan<TPixel> source, Span<Color> destination)
113106
where TPixel : unmanaged, IPixel<TPixel>
114107
{
@@ -120,7 +113,7 @@ public static void FromPixel<TPixel>(ReadOnlySpan<TPixel> source, Span<Color> de
120113
{
121114
for (int i = 0; i < destination.Length; i++)
122115
{
123-
destination[i] = new(source[i].ToScaledVector4());
116+
destination[i] = FromScaledVector(source[i].ToScaledVector4());
124117
}
125118
}
126119
else
@@ -143,7 +136,7 @@ public static void FromPixel<TPixel>(ReadOnlySpan<TPixel> source, Span<Color> de
143136
/// <returns>
144137
/// The <see cref="Color"/>.
145138
/// </returns>
146-
[MethodImpl(InliningOptions.ShortMethod)]
139+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
147140
public static Color ParseHex(string hex)
148141
{
149142
Rgba32 rgba = Rgba32.ParseHex(hex);
@@ -162,7 +155,7 @@ public static Color ParseHex(string hex)
162155
/// <returns>
163156
/// The <see cref="bool"/>.
164157
/// </returns>
165-
[MethodImpl(InliningOptions.ShortMethod)]
158+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
166159
public static bool TryParseHex(string hex, out Color result)
167160
{
168161
result = default;
@@ -236,16 +229,16 @@ public static bool TryParse(string input, out Color result)
236229
/// <returns>The color having it's alpha channel altered.</returns>
237230
public Color WithAlpha(float alpha)
238231
{
239-
Vector4 v = (Vector4)this;
232+
Vector4 v = this.ToScaledVector4();
240233
v.W = alpha;
241-
return new Color(v);
234+
return FromScaledVector(v);
242235
}
243236

244237
/// <summary>
245238
/// Gets the hexadecimal representation of the color instance in rrggbbaa form.
246239
/// </summary>
247240
/// <returns>A hexadecimal string representation of the value.</returns>
248-
[MethodImpl(InliningOptions.ShortMethod)]
241+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
249242
public string ToHex()
250243
{
251244
if (this.boxedHighPrecisionPixel is not null)
@@ -263,8 +256,8 @@ public string ToHex()
263256
/// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
264257
/// </summary>
265258
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
266-
/// <returns>The pixel value.</returns>
267-
[MethodImpl(InliningOptions.ShortMethod)]
259+
/// <returns>The <typeparamref name="TPixel"/>.</returns>
260+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
268261
public TPixel ToPixel<TPixel>()
269262
where TPixel : unmanaged, IPixel<TPixel>
270263
{
@@ -281,13 +274,30 @@ public TPixel ToPixel<TPixel>()
281274
return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
282275
}
283276

277+
/// <summary>
278+
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
279+
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
280+
/// The vector components are typically expanded in least to greatest significance order.
281+
/// </summary>
282+
/// <returns>The <see cref="Vector4"/>.</returns>
283+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
284+
public Vector4 ToScaledVector4()
285+
{
286+
if (this.boxedHighPrecisionPixel is null)
287+
{
288+
return this.data;
289+
}
290+
291+
return this.boxedHighPrecisionPixel.ToScaledVector4();
292+
}
293+
284294
/// <summary>
285295
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <typeparamref name="TPixel"/> type.
286296
/// </summary>
287297
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
288298
/// <param name="source">The source color span.</param>
289299
/// <param name="destination">The destination pixel span.</param>
290-
[MethodImpl(InliningOptions.ShortMethod)]
300+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
291301
public static void ToPixel<TPixel>(ReadOnlySpan<Color> source, Span<TPixel> destination)
292302
where TPixel : unmanaged, IPixel<TPixel>
293303
{
@@ -301,7 +311,7 @@ public static void ToPixel<TPixel>(ReadOnlySpan<Color> source, Span<TPixel> dest
301311
}
302312

303313
/// <inheritdoc />
304-
[MethodImpl(InliningOptions.ShortMethod)]
314+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
305315
public bool Equals(Color other)
306316
{
307317
if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
@@ -316,7 +326,7 @@ public bool Equals(Color other)
316326
public override bool Equals(object? obj) => obj is Color other && this.Equals(other);
317327

318328
/// <inheritdoc />
319-
[MethodImpl(InliningOptions.ShortMethod)]
329+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
320330
public override int GetHashCode()
321331
{
322332
if (this.boxedHighPrecisionPixel is null)
@@ -326,15 +336,4 @@ public override int GetHashCode()
326336

327337
return this.boxedHighPrecisionPixel.GetHashCode();
328338
}
329-
330-
[MethodImpl(InliningOptions.ShortMethod)]
331-
private Vector4 ToScaledVector4()
332-
{
333-
if (this.boxedHighPrecisionPixel is null)
334-
{
335-
return this.data;
336-
}
337-
338-
return this.boxedHighPrecisionPixel.ToScaledVector4();
339-
}
340339
}

src/ImageSharp/Formats/Gif/GifFrameMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,13 @@ internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata
8282
{
8383
// TODO: v4 How do I link the parent metadata to the frame metadata to get the global color table?
8484
int index = -1;
85-
float background = 1f;
85+
const float background = 1f;
8686
if (metadata.ColorTable.HasValue)
8787
{
8888
ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
8989
for (int i = 0; i < colorTable.Length; i++)
9090
{
91-
Vector4 vector = (Vector4)colorTable[i];
91+
Vector4 vector = colorTable[i].ToScaledVector4();
9292
if (vector.W < background)
9393
{
9494
index = i;

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Buffers;
55
using System.Buffers.Binary;
66
using System.IO.Hashing;
7+
using System.Numerics;
78
using System.Runtime.CompilerServices;
89
using System.Runtime.InteropServices;
910
using SixLabors.ImageSharp.Common.Helpers;
@@ -1559,7 +1560,24 @@ private void SanitizeAndSetEncoderOptions<TPixel>(
15591560
{
15601561
// We can use the color data from the decoded metadata here.
15611562
// We avoid dithering by default to preserve the original colors.
1562-
this.derivedTransparencyIndex = metadata.ColorTable.Value.Span.IndexOf(Color.Transparent);
1563+
ReadOnlySpan<Color> palette = metadata.ColorTable.Value.Span;
1564+
1565+
// Certain operations perform alpha premultiplication, which can cause the color to change so we
1566+
// must search for the transparency index in the palette.
1567+
// Transparent pixels are much more likely to be found at the end of a palette.
1568+
int index = -1;
1569+
for (int i = palette.Length - 1; i >= 0; i--)
1570+
{
1571+
Vector4 instance = palette[i].ToScaledVector4();
1572+
if (instance.W == 0f)
1573+
{
1574+
index = i;
1575+
break;
1576+
}
1577+
}
1578+
1579+
this.derivedTransparencyIndex = index;
1580+
15631581
this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }, this.derivedTransparencyIndex);
15641582
}
15651583
else

src/ImageSharp/Formats/Webp/AlphaDecoder.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,15 @@ private static void ColorIndexInverseTransformAlpha(
311311

312312
private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
313313
{
314-
if (Sse2.IsSupported)
314+
// TODO: Investigate AdvSimd support for this method.
315+
if (Sse2.IsSupported && width >= 9)
315316
{
316317
dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0]));
317-
if (width <= 1)
318-
{
319-
return;
320-
}
321-
322318
nuint i;
323319
Vector128<int> last = Vector128<int>.Zero.WithElement(0, dst[0]);
324320
ref byte srcRef = ref MemoryMarshal.GetReference(input);
325321
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
322+
326323
for (i = 1; i <= (uint)width - 8; i += 8)
327324
{
328325
Vector128<long> a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);

tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public void Bgr24()
105105
public void Vector4Constructor()
106106
{
107107
// Act:
108-
Color color = (Color)Vector4.One;
108+
Color color = Color.FromScaledVector(Vector4.One);
109109

110110
// Assert:
111111
Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel<RgbaVector>());

tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using SixLabors.ImageSharp.Formats.Webp;
99
using SixLabors.ImageSharp.Metadata;
1010
using SixLabors.ImageSharp.PixelFormats;
11+
using SixLabors.ImageSharp.Processing;
1112
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1213
using SixLabors.ImageSharp.Tests.TestUtilities;
1314
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -679,6 +680,22 @@ public void Issue2469_Quantized_Encode_Artifacts<TPixel>(TestImageProvider<TPixe
679680
encoded.CompareToReferenceOutput(ImageComparer.Exact, provider);
680681
}
681682

683+
// https://github.com/SixLabors/ImageSharp/issues/2469
684+
[Theory]
685+
[WithFile(TestImages.Png.Issue2668, PixelTypes.Rgba32)]
686+
public void Issue2668_Quantized_Encode_Alpha<TPixel>(TestImageProvider<TPixel> provider)
687+
where TPixel : unmanaged, IPixel<TPixel>
688+
{
689+
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
690+
image.Mutate(x => x.Resize(100, 100));
691+
692+
PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette };
693+
694+
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder);
695+
using Image<Rgba32> encoded = Image.Load<Rgba32>(actualOutputFile);
696+
encoded.CompareToReferenceOutput(ImageComparer.Exact, provider);
697+
}
698+
682699
private static void TestPngEncoderCore<TPixel>(
683700
TestImageProvider<TPixel> provider,
684701
PngColorType pngColorType,

tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,17 @@ public void WebpDecoder_CanDecode_Issue2257<TPixel>(TestImageProvider<TPixel> pr
439439
image.CompareToOriginal(provider, ReferenceDecoder);
440440
}
441441

442+
// https://github.com/SixLabors/ImageSharp/issues/2670
443+
[Theory]
444+
[WithFile(Lossy.Issue2670, PixelTypes.Rgba32)]
445+
public void WebpDecoder_CanDecode_Issue2670<TPixel>(TestImageProvider<TPixel> provider)
446+
where TPixel : unmanaged, IPixel<TPixel>
447+
{
448+
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance);
449+
image.DebugSave(provider);
450+
image.CompareToOriginal(provider, ReferenceDecoder);
451+
}
452+
442453
[Theory]
443454
[WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)]
444455
public void WebpDecoder_ThrowImageFormatException_OnInvalidImages<TPixel>(TestImageProvider<TPixel> provider)

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ public static class Png
151151
// Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447
152152
public const string Issue2447 = "Png/issues/issue_2447.png";
153153

154+
// Issue 2668: https://github.com/SixLabors/ImageSharp/issues/2668
155+
public const string Issue2668 = "Png/issues/Issue_2668.png";
156+
154157
public static class Bad
155158
{
156159
public const string MissingDataChunk = "Png/xdtn0g01.png";
@@ -806,6 +809,7 @@ public static class Lossy
806809
public const string Issue1594 = "Webp/issues/Issue1594.webp";
807810
public const string Issue2243 = "Webp/issues/Issue2243.webp";
808811
public const string Issue2257 = "Webp/issues/Issue2257.webp";
812+
public const string Issue2670 = "Webp/issues/Issue2670.webp";
809813
}
810814
}
811815

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)