Skip to content

Commit 4be2645

Browse files
Merge pull request #2844 from SixLabors/js/normalize-transparency-encoding
Normalize Encoder Transparency Handling For Alpha Aware Image Formats
2 parents c873e91 + 502a354 commit 4be2645

File tree

81 files changed

+1737
-847
lines changed

Some content is hidden

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

81 files changed

+1737
-847
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats;
5+
6+
/// <summary>
7+
/// Acts as a base encoder for all formats that are aware of and can handle alpha transparency.
8+
/// </summary>
9+
public abstract class AlphaAwareImageEncoder : ImageEncoder
10+
{
11+
/// <summary>
12+
/// Gets or initializes the mode that determines how transparent pixels are handled during encoding.
13+
/// </summary>
14+
public TransparentColorMode TransparentColorMode { get; init; }
15+
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 161 additions & 66 deletions
Large diffs are not rendered by default.

src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ private CurFrameMetadata(CurFrameMetadata other)
4848
/// Gets or sets the encoding width. <br />
4949
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
5050
/// </summary>
51-
public byte EncodingWidth { get; set; }
51+
public byte? EncodingWidth { get; set; }
5252

5353
/// <summary>
5454
/// Gets or sets the encoding height. <br />
5555
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
5656
/// </summary>
57-
public byte EncodingHeight { get; set; }
57+
public byte? EncodingHeight { get; set; }
5858

5959
/// <summary>
6060
/// Gets or sets the number of bits per pixel.<br/>
@@ -80,20 +80,6 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
8080
};
8181
}
8282

83-
byte encodingWidth = metadata.EncodingWidth switch
84-
{
85-
> 255 => 0,
86-
<= 255 and >= 1 => (byte)metadata.EncodingWidth,
87-
_ => 0
88-
};
89-
90-
byte encodingHeight = metadata.EncodingHeight switch
91-
{
92-
> 255 => 0,
93-
<= 255 and >= 1 => (byte)metadata.EncodingHeight,
94-
_ => 0
95-
};
96-
9783
int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
9884
BmpBitsPerPixel bbpp = bpp switch
9985
{
@@ -116,8 +102,8 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
116102
{
117103
BmpBitsPerPixel = bbpp,
118104
Compression = compression,
119-
EncodingWidth = encodingWidth,
120-
EncodingHeight = encodingHeight,
105+
EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
106+
EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
121107
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
122108
};
123109
}
@@ -138,8 +124,8 @@ public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel
138124
{
139125
float ratioX = destination.Width / (float)source.Width;
140126
float ratioY = destination.Height / (float)source.Height;
141-
this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX);
142-
this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY);
127+
this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX);
128+
this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY);
143129
}
144130

145131
/// <inheritdoc/>
@@ -156,16 +142,16 @@ internal void FromIconDirEntry(IconDirEntry entry)
156142
this.HotspotY = entry.BitCount;
157143
}
158144

159-
internal IconDirEntry ToIconDirEntry()
145+
internal IconDirEntry ToIconDirEntry(Size size)
160146
{
161147
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
162148
? (byte)0
163149
: (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel);
164150

165151
return new()
166152
{
167-
Width = this.EncodingWidth,
168-
Height = this.EncodingHeight,
153+
Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width),
154+
Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height),
169155
Planes = this.HotspotX,
170156
BitCount = this.HotspotY,
171157
ColorCount = colorCount
@@ -233,13 +219,22 @@ private PixelTypeInfo GetPixelTypeInfo()
233219
};
234220
}
235221

236-
private static byte Scale(byte? value, int destination, float ratio)
222+
private static byte ScaleEncodingDimension(byte? value, int destination, float ratio)
237223
{
238224
if (value is null)
239225
{
240-
return (byte)Math.Clamp(destination, 0, 255);
226+
return ClampEncodingDimension(destination);
241227
}
242228

243-
return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255));
229+
return ClampEncodingDimension(MathF.Ceiling(value.Value * ratio));
244230
}
231+
232+
private static byte ClampEncodingDimension(float? dimension)
233+
=> dimension switch
234+
{
235+
// Encoding dimensions can be between 0-256 where 0 means 256 or greater.
236+
> 255 => 0,
237+
<= 255 and >= 1 => (byte)dimension,
238+
_ => 0
239+
};
245240
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using System.Numerics;
6+
using System.Runtime.Intrinsics;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Formats;
11+
12+
/// <summary>
13+
/// Provides utilities for encoding images.
14+
/// </summary>
15+
internal static class EncodingUtilities
16+
{
17+
public static bool ShouldClearTransparentPixels<TPixel>(TransparentColorMode mode)
18+
where TPixel : unmanaged, IPixel<TPixel>
19+
=> mode == TransparentColorMode.Clear &&
20+
TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated;
21+
22+
/// <summary>
23+
/// Convert transparent pixels, to pixels represented by <paramref name="color"/>, which can yield
24+
/// to better compression in some cases.
25+
/// </summary>
26+
/// <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)
30+
where TPixel : unmanaged, IPixel<TPixel>
31+
{
32+
Buffer2DRegion<TPixel> buffer = clone.PixelBuffer.GetRegion();
33+
ClearTransparentPixels(clone.Configuration, ref buffer, color);
34+
}
35+
36+
/// <summary>
37+
/// Convert transparent pixels, to pixels represented by <paramref name="color"/>, which can yield
38+
/// to better compression in some cases.
39+
/// </summary>
40+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
41+
/// <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>(
45+
Configuration configuration,
46+
ref Buffer2DRegion<TPixel> clone,
47+
Color color)
48+
where TPixel : unmanaged, IPixel<TPixel>
49+
{
50+
using IMemoryOwner<Vector4> vectors = configuration.MemoryAllocator.Allocate<Vector4>(clone.Width);
51+
Span<Vector4> vectorsSpan = vectors.GetSpan();
52+
Vector4 replacement = color.ToScaledVector4();
53+
for (int y = 0; y < clone.Height; y++)
54+
{
55+
Span<TPixel> span = clone.DangerousGetRowSpan(y);
56+
PixelOperations<TPixel>.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale);
57+
ClearTransparentPixelRow(vectorsSpan, replacement);
58+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale);
59+
}
60+
}
61+
62+
private static void ClearTransparentPixelRow(
63+
Span<Vector4> vectorsSpan,
64+
Vector4 replacement)
65+
{
66+
if (Vector128.IsHardwareAccelerated)
67+
{
68+
Vector128<float> replacement128 = replacement.AsVector128();
69+
70+
for (int i = 0; i < vectorsSpan.Length; i++)
71+
{
72+
ref Vector4 v = ref vectorsSpan[i];
73+
Vector128<float> v128 = v.AsVector128();
74+
75+
// Do `vector == 0`
76+
Vector128<float> mask = Vector128.Equals(v128, Vector128<float>.Zero);
77+
78+
// Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise)
79+
mask = Vector128.Shuffle(mask, Vector128.Create(3, 3, 3, 3));
80+
81+
// Use the mask to select the replacement vector
82+
// (replacement & mask) | (v128 & ~mask)
83+
v = Vector128.ConditionalSelect(mask, replacement128, v128).AsVector4();
84+
}
85+
}
86+
else
87+
{
88+
for (int i = 0; i < vectorsSpan.Length; i++)
89+
{
90+
if (vectorsSpan[i].W == 0F)
91+
{
92+
vectorsSpan[i] = replacement;
93+
}
94+
}
95+
}
96+
}
97+
}

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
665665
return;
666666
}
667667

668-
Rectangle interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value);
668+
Rectangle interest = Rectangle.Intersect(frame.Bounds, this.restoreArea.Value);
669669
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(interest);
670670
pixelRegion.Clear();
671671

0 commit comments

Comments
 (0)