Skip to content

Commit 62da42d

Browse files
Merge pull request #2894 from SixLabors/js/fix-2866-v4
V4 : Fix GIF, PNG, and WEBP Edge Case Handling
2 parents ba61e04 + 5f971c7 commit 62da42d

File tree

215 files changed

+3331
-1449
lines changed

Some content is hidden

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

215 files changed

+3331
-1449
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,11 @@ private static void Seed<TPixel>()
138138
AotCompileResamplers<TPixel>();
139139
AotCompileQuantizers<TPixel>();
140140
AotCompilePixelSamplingStrategys<TPixel>();
141+
AotCompilePixelMaps<TPixel>();
141142
AotCompileDithers<TPixel>();
142143
AotCompileMemoryManagers<TPixel>();
143144

144-
Unsafe.SizeOf<TPixel>();
145+
_ = Unsafe.SizeOf<TPixel>();
145146

146147
// TODO: Do the discovery work to figure out what works and what doesn't.
147148
}
@@ -514,6 +515,20 @@ private static void AotCompilePixelSamplingStrategys<TPixel>()
514515
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
515516
}
516517

518+
/// <summary>
519+
/// This method pre-seeds the all <see cref="IColorIndexCache{T}" /> in the AoT compiler.
520+
/// </summary>
521+
/// <typeparam name="TPixel">The pixel format.</typeparam>
522+
[Preserve]
523+
private static void AotCompilePixelMaps<TPixel>()
524+
where TPixel : unmanaged, IPixel<TPixel>
525+
{
526+
default(EuclideanPixelMap<TPixel, HybridCache>).GetClosestColor(default, out _);
527+
default(EuclideanPixelMap<TPixel, AccurateCache>).GetClosestColor(default, out _);
528+
default(EuclideanPixelMap<TPixel, CoarseCache>).GetClosestColor(default, out _);
529+
default(EuclideanPixelMap<TPixel, NullCache>).GetClosestColor(default, out _);
530+
}
531+
517532
/// <summary>
518533
/// This method pre-seeds the all <see cref="IDither" /> in the AoT compiler.
519534
/// </summary>

src/ImageSharp/Common/InlineArray.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
// <auto-generated />
5+
6+
using System;
7+
using System.Runtime.CompilerServices;
8+
9+
namespace SixLabors.ImageSharp;
10+
11+
/// <summary>
12+
/// Represents a safe, fixed sized buffer of 4 elements.
13+
/// </summary>
14+
[InlineArray(4)]
15+
internal struct InlineArray4<T>
16+
{
17+
private T t;
18+
}
19+
20+
/// <summary>
21+
/// Represents a safe, fixed sized buffer of 8 elements.
22+
/// </summary>
23+
[InlineArray(8)]
24+
internal struct InlineArray8<T>
25+
{
26+
private T t;
27+
}
28+
29+
/// <summary>
30+
/// Represents a safe, fixed sized buffer of 16 elements.
31+
/// </summary>
32+
[InlineArray(16)]
33+
internal struct InlineArray16<T>
34+
{
35+
private T t;
36+
}
37+
38+

src/ImageSharp/Common/InlineArray.tt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<#@ template debug="false" hostspecific="false" language="C#" #>
2+
<#@ assembly name="System.Core" #>
3+
<#@ import namespace="System.Linq" #>
4+
<#@ import namespace="System.Text" #>
5+
<#@ import namespace="System.Collections.Generic" #>
6+
// Copyright (c) Six Labors.
7+
// Licensed under the Six Labors Split License.
8+
9+
// <auto-generated />
10+
11+
using System;
12+
using System.Runtime.CompilerServices;
13+
14+
namespace SixLabors.ImageSharp;
15+
16+
<#GenerateInlineArrays();#>
17+
18+
<#+
19+
private static int[] Lengths = new int[] {4, 8, 16 };
20+
21+
void GenerateInlineArrays()
22+
{
23+
foreach (int length in Lengths)
24+
{
25+
#>
26+
/// <summary>
27+
/// Represents a safe, fixed sized buffer of <#=length#> elements.
28+
/// </summary>
29+
[InlineArray(<#=length#>)]
30+
internal struct InlineArray<#=length#><T>
31+
{
32+
private T t;
33+
}
34+
35+
<#+
36+
}
37+
}
38+
#>

src/ImageSharp/Formats/AlphaAwareImageEncoder.cs

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

4+
using SixLabors.ImageSharp.Processing.Processors.Quantization;
5+
46
namespace SixLabors.ImageSharp.Formats;
57

68
/// <summary>
@@ -10,6 +12,8 @@ public abstract class AlphaAwareImageEncoder : ImageEncoder
1012
{
1113
/// <summary>
1214
/// Gets or initializes the mode that determines how transparent pixels are handled during encoding.
15+
/// This overrides any other settings that may affect the encoding of transparent pixels
16+
/// including those passed via <see cref="QuantizerOptions"/>.
1317
/// </summary>
1418
public TransparentColorMode TransparentColorMode { get; init; }
1519
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,13 @@ private void WriteImage<TPixel>(
362362
ImageFrame<TPixel>? clonedFrame = null;
363363
try
364364
{
365-
if (EncodingUtilities.ShouldClearTransparentPixels<TPixel>(this.transparentColorMode))
365+
// No need to clone when quantizing. The quantizer will do it for us.
366+
// TODO: We should really try to avoid the clone entirely.
367+
int bpp = this.bitsPerPixel != null ? (int)this.bitsPerPixel : 32;
368+
if (bpp > 8 && EncodingUtilities.ShouldReplaceTransparentPixels<TPixel>(this.transparentColorMode))
366369
{
367370
clonedFrame = image.Frames.RootFrame.Clone();
368-
EncodingUtilities.ClearTransparentPixels(clonedFrame, Color.Transparent);
371+
EncodingUtilities.ReplaceTransparentPixels(clonedFrame);
369372
}
370373

371374
ImageFrame<TPixel> encodingFrame = clonedFrame ?? image.Frames.RootFrame;

src/ImageSharp/Formats/Bmp/BmpMetadata.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,5 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
158158
/// <inheritdoc/>
159159
public void AfterImageApply<TPixel>(Image<TPixel> destination)
160160
where TPixel : unmanaged, IPixel<TPixel>
161-
{
162-
}
161+
=> this.ColorTable = null;
163162
}

src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
104104
Compression = compression,
105105
EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
106106
EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
107-
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
108107
};
109108
}
110109

@@ -113,7 +112,6 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
113112
=> new()
114113
{
115114
PixelTypeInfo = this.GetPixelTypeInfo(),
116-
ColorTable = this.ColorTable,
117115
EncodingWidth = this.EncodingWidth,
118116
EncodingHeight = this.EncodingHeight
119117
};
@@ -126,6 +124,7 @@ public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel
126124
float ratioY = destination.Height / (float)source.Height;
127125
this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX);
128126
this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY);
127+
this.ColorTable = null;
129128
}
130129

131130
/// <inheritdoc/>

src/ImageSharp/Formats/Cur/CurMetadata.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ public static CurMetadata FromFormatConnectingMetadata(FormatConnectingMetadata
7171
return new CurMetadata
7272
{
7373
BmpBitsPerPixel = bbpp,
74-
Compression = compression,
75-
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
74+
Compression = compression
7675
};
7776
}
7877

@@ -145,15 +144,13 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
145144
EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8
146145
? EncodingType.Lossy
147146
: EncodingType.Lossless,
148-
PixelTypeInfo = this.GetPixelTypeInfo(),
149-
ColorTable = this.ColorTable
147+
PixelTypeInfo = this.GetPixelTypeInfo()
150148
};
151149

152150
/// <inheritdoc/>
153151
public void AfterImageApply<TPixel>(Image<TPixel> destination)
154152
where TPixel : unmanaged, IPixel<TPixel>
155-
{
156-
}
153+
=> this.ColorTable = null;
157154

158155
/// <inheritdoc/>
159156
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

src/ImageSharp/Formats/EncodingUtilities.cs

Lines changed: 105 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System.Buffers;
55
using System.Numerics;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
68
using System.Runtime.Intrinsics;
79
using SixLabors.ImageSharp.Memory;
810
using SixLabors.ImageSharp.PixelFormats;
@@ -14,62 +16,132 @@ namespace SixLabors.ImageSharp.Formats;
1416
/// </summary>
1517
internal static class EncodingUtilities
1618
{
17-
public static bool ShouldClearTransparentPixels<TPixel>(TransparentColorMode mode)
19+
/// <summary>
20+
/// Determines if transparent pixels can be replaced based on the specified color mode and pixel type.
21+
/// </summary>
22+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
23+
/// <param name="mode">Indicates the color mode used to assess the ability to replace transparent pixels.</param>
24+
/// <returns>Returns true if transparent pixels can be replaced; otherwise, false.</returns>
25+
public static bool ShouldReplaceTransparentPixels<TPixel>(TransparentColorMode mode)
26+
where TPixel : unmanaged, IPixel<TPixel>
27+
=> mode == TransparentColorMode.Clear && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated;
28+
29+
/// <summary>
30+
/// Replaces pixels with a transparent alpha component with fully transparent pixels.
31+
/// </summary>
32+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
33+
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> where the transparent pixels will be changed.</param>
34+
public static void ReplaceTransparentPixels<TPixel>(ImageFrame<TPixel> frame)
1835
where TPixel : unmanaged, IPixel<TPixel>
19-
=> mode == TransparentColorMode.Clear &&
20-
TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated;
36+
=> ReplaceTransparentPixels(frame.Configuration, frame.PixelBuffer);
2137

2238
/// <summary>
23-
/// Convert transparent pixels, to pixels represented by <paramref name="color"/>, which can yield
24-
/// to better compression in some cases.
39+
/// Replaces pixels with a transparent alpha component with fully transparent pixels.
2540
/// </summary>
2641
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
27-
/// <param name="clone">The cloned <see cref="ImageFrame{TPixel}"/> where the transparent pixels will be changed.</param>
28-
/// <param name="color">The color to replace transparent pixels with.</param>
29-
public static void ClearTransparentPixels<TPixel>(ImageFrame<TPixel> clone, Color color)
42+
/// <param name="configuration">The configuration.</param>
43+
/// <param name="buffer">The <see cref="Buffer2D{TPixel}"/> where the transparent pixels will be changed.</param>
44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
public static void ReplaceTransparentPixels<TPixel>(Configuration configuration, Buffer2D<TPixel> buffer)
3046
where TPixel : unmanaged, IPixel<TPixel>
3147
{
32-
Buffer2DRegion<TPixel> buffer = clone.PixelBuffer.GetRegion();
33-
ClearTransparentPixels(clone.Configuration, ref buffer, color);
48+
Buffer2DRegion<TPixel> region = buffer.GetRegion();
49+
ReplaceTransparentPixels(configuration, in region);
3450
}
3551

3652
/// <summary>
37-
/// Convert transparent pixels, to pixels represented by <paramref name="color"/>, which can yield
38-
/// to better compression in some cases.
53+
/// Replaces pixels with a transparent alpha component with fully transparent pixels.
3954
/// </summary>
4055
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
4156
/// <param name="configuration">The configuration.</param>
42-
/// <param name="clone">The cloned <see cref="Buffer2DRegion{T}"/> where the transparent pixels will be changed.</param>
43-
/// <param name="color">The color to replace transparent pixels with.</param>
44-
public static void ClearTransparentPixels<TPixel>(
57+
/// <param name="region">The <see cref="Buffer2DRegion{T}"/> where the transparent pixels will be changed.</param>
58+
public static void ReplaceTransparentPixels<TPixel>(
4559
Configuration configuration,
46-
ref Buffer2DRegion<TPixel> clone,
47-
Color color)
60+
in Buffer2DRegion<TPixel> region)
4861
where TPixel : unmanaged, IPixel<TPixel>
4962
{
50-
using IMemoryOwner<Vector4> vectors = configuration.MemoryAllocator.Allocate<Vector4>(clone.Width);
63+
using IMemoryOwner<Vector4> vectors = configuration.MemoryAllocator.Allocate<Vector4>(region.Width);
5164
Span<Vector4> vectorsSpan = vectors.GetSpan();
52-
Vector4 replacement = color.ToScaledVector4();
53-
for (int y = 0; y < clone.Height; y++)
65+
for (int y = 0; y < region.Height; y++)
5466
{
55-
Span<TPixel> span = clone.DangerousGetRowSpan(y);
67+
Span<TPixel> span = region.DangerousGetRowSpan(y);
5668
PixelOperations<TPixel>.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale);
57-
ClearTransparentPixelRow(vectorsSpan, replacement);
69+
ReplaceTransparentPixels(vectorsSpan);
5870
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale);
5971
}
6072
}
6173

62-
private static void ClearTransparentPixelRow(
63-
Span<Vector4> vectorsSpan,
64-
Vector4 replacement)
74+
/// <summary>
75+
/// Replaces pixels with a transparent alpha component with fully transparent pixels.
76+
/// </summary>
77+
/// <param name="source">A span of color vectors that will be checked for transparency and potentially modified.</param>
78+
public static void ReplaceTransparentPixels(Span<Vector4> source)
6579
{
66-
if (Vector128.IsHardwareAccelerated)
80+
if (Vector512.IsHardwareAccelerated && source.Length >= 4)
81+
{
82+
Span<Vector512<float>> source512 = MemoryMarshal.Cast<Vector4, Vector512<float>>(source);
83+
for (int i = 0; i < source512.Length; i++)
84+
{
85+
ref Vector512<float> v = ref source512[i];
86+
87+
// Do `vector < threshold`
88+
Vector512<float> mask = Vector512.Equals(v, Vector512<float>.Zero);
89+
90+
// Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise)
91+
mask = Vector512.Shuffle(mask, Vector512.Create(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15));
92+
93+
// Use the mask to select the replacement vector
94+
// (replacement & mask) | (v512 & ~mask)
95+
v = Vector512.ConditionalSelect(mask, Vector512<float>.Zero, v);
96+
}
97+
98+
int m = Numerics.Modulo4(source.Length);
99+
if (m != 0)
100+
{
101+
for (int i = source.Length - m; i < source.Length; i++)
102+
{
103+
if (source[i].W == 0)
104+
{
105+
source[i] = Vector4.Zero;
106+
}
107+
}
108+
}
109+
}
110+
else if (Vector256.IsHardwareAccelerated && source.Length >= 2)
67111
{
68-
Vector128<float> replacement128 = replacement.AsVector128();
112+
Span<Vector256<float>> source256 = MemoryMarshal.Cast<Vector4, Vector256<float>>(source);
113+
for (int i = 0; i < source256.Length; i++)
114+
{
115+
ref Vector256<float> v = ref source256[i];
116+
117+
// Do `vector < threshold`
118+
Vector256<float> mask = Vector256.Equals(v, Vector256<float>.Zero);
119+
120+
// Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise)
121+
mask = Vector256.Shuffle(mask, Vector256.Create(3, 3, 3, 3, 7, 7, 7, 7));
69122

70-
for (int i = 0; i < vectorsSpan.Length; i++)
123+
// Use the mask to select the replacement vector
124+
// (replacement & mask) | (v256 & ~mask)
125+
v = Vector256.ConditionalSelect(mask, Vector256<float>.Zero, v);
126+
}
127+
128+
int m = Numerics.Modulo2(source.Length);
129+
if (m != 0)
130+
{
131+
for (int i = source.Length - m; i < source.Length; i++)
132+
{
133+
if (source[i].W == 0)
134+
{
135+
source[i] = Vector4.Zero;
136+
}
137+
}
138+
}
139+
}
140+
else if (Vector128.IsHardwareAccelerated)
141+
{
142+
for (int i = 0; i < source.Length; i++)
71143
{
72-
ref Vector4 v = ref vectorsSpan[i];
144+
ref Vector4 v = ref source[i];
73145
Vector128<float> v128 = v.AsVector128();
74146

75147
// Do `vector == 0`
@@ -80,16 +152,16 @@ private static void ClearTransparentPixelRow(
80152

81153
// Use the mask to select the replacement vector
82154
// (replacement & mask) | (v128 & ~mask)
83-
v = Vector128.ConditionalSelect(mask, replacement128, v128).AsVector4();
155+
v = Vector128.ConditionalSelect(mask, Vector128<float>.Zero, v128).AsVector4();
84156
}
85157
}
86158
else
87159
{
88-
for (int i = 0; i < vectorsSpan.Length; i++)
160+
for (int i = 0; i < source.Length; i++)
89161
{
90-
if (vectorsSpan[i].W == 0F)
162+
if (source[i].W == 0F)
91163
{
92-
vectorsSpan[i] = replacement;
164+
source[i] = Vector4.Zero;
93165
}
94166
}
95167
}

0 commit comments

Comments
 (0)