Skip to content

Commit 326f76d

Browse files
Merge pull request #2699 from SixLabors/js/fix-webp-alpha-flags
Ensure VP8X alpha flag is updated correctly.
2 parents a29c6fd + a197f22 commit 326f76d

File tree

8 files changed

+105
-35
lines changed

8 files changed

+105
-35
lines changed

src/ImageSharp/Common/Helpers/RiffHelper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Buffers.Binary;
55
using System.Text;
6+
using SixLabors.ImageSharp.Formats.Webp.Chunks;
67

78
namespace SixLabors.ImageSharp.Common.Helpers;
89

@@ -107,6 +108,7 @@ public static void EndWriteChunk(Stream stream, long sizePosition)
107108
position++;
108109
}
109110

111+
// Add the size of the encoded file to the Riff header.
110112
BinaryPrimitives.WriteUInt32LittleEndian(buffer, dataSize);
111113
stream.Position = sizePosition;
112114
stream.Write(buffer);
@@ -120,5 +122,18 @@ public static long BeginWriteRiffFile(Stream stream, string formType)
120122
return sizePosition;
121123
}
122124

123-
public static void EndWriteRiffFile(Stream stream, long sizePosition) => EndWriteChunk(stream, sizePosition);
125+
public static void EndWriteRiffFile(Stream stream, in WebpVp8X vp8x, bool updateVp8x, long sizePosition)
126+
{
127+
EndWriteChunk(stream, sizePosition + 4);
128+
129+
// Write the VP8X chunk if necessary.
130+
if (updateVp8x)
131+
{
132+
long position = stream.Position;
133+
134+
stream.Position = sizePosition + 12;
135+
vp8x.WriteTo(stream);
136+
stream.Position = position;
137+
}
138+
}
124139
}

src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
8888
/// <param name="iccProfile">The color profile.</param>
8989
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
9090
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
91-
public static void WriteTrunksBeforeData(
91+
/// <returns>A <see cref="WebpVp8X"/> or a default instance.</returns>
92+
public static WebpVp8X WriteTrunksBeforeData(
9293
Stream stream,
9394
uint width,
9495
uint height,
@@ -102,16 +103,19 @@ public static void WriteTrunksBeforeData(
102103
RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
103104

104105
// Write VP8X, header if necessary.
106+
WebpVp8X vp8x = default;
105107
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
106108
if (isVp8X)
107109
{
108-
WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation);
110+
vp8x = WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation);
109111

110112
if (iccProfile != null)
111113
{
112114
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Iccp, iccProfile.ToByteArray());
113115
}
114116
}
117+
118+
return vp8x;
115119
}
116120

117121
/// <summary>
@@ -124,10 +128,16 @@ public static void WriteTrunksBeforeData(
124128
/// Write the trunks after data trunk.
125129
/// </summary>
126130
/// <param name="stream">The stream to write to.</param>
127-
/// <param name="exifProfile">The exif profile.</param>
131+
/// <param name="vp8x">The VP8X chunk.</param>
132+
/// <param name="updateVp8x">Whether to update the chunk.</param>
133+
/// <param name="initialPosition">The initial position of the stream before encoding.</param>
134+
/// <param name="exifProfile">The EXIF profile.</param>
128135
/// <param name="xmpProfile">The XMP profile.</param>
129136
public static void WriteTrunksAfterData(
130137
Stream stream,
138+
in WebpVp8X vp8x,
139+
bool updateVp8x,
140+
long initialPosition,
131141
ExifProfile? exifProfile,
132142
XmpProfile? xmpProfile)
133143
{
@@ -141,7 +151,7 @@ public static void WriteTrunksAfterData(
141151
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data);
142152
}
143153

144-
RiffHelper.EndWriteRiffFile(stream, 4);
154+
RiffHelper.EndWriteRiffFile(stream, in vp8x, updateVp8x, initialPosition);
145155
}
146156

147157
/// <summary>
@@ -186,19 +196,21 @@ public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alp
186196
/// Writes a VP8X header to the stream.
187197
/// </summary>
188198
/// <param name="stream">The stream to write to.</param>
189-
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
190-
/// <param name="xmpProfile">A XMP profile or null, if it does not exist.</param>
199+
/// <param name="exifProfile">An EXIF profile or null, if it does not exist.</param>
200+
/// <param name="xmpProfile">An XMP profile or null, if it does not exist.</param>
191201
/// <param name="iccProfile">The color profile.</param>
192202
/// <param name="width">The width of the image.</param>
193203
/// <param name="height">The height of the image.</param>
194204
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
195205
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
196-
protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
206+
protected static WebpVp8X WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
197207
{
198208
WebpVp8X chunk = new(hasAnimation, xmpProfile != null, exifProfile != null, hasAlpha, iccProfile != null, width, height);
199209

200210
chunk.Validate(MaxDimension, MaxCanvasPixels);
201211

202212
chunk.WriteTo(stream);
213+
214+
return chunk;
203215
}
204216
}

src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
77

8-
internal readonly struct WebpVp8X
8+
internal readonly struct WebpVp8X : IEquatable<WebpVp8X>
99
{
1010
public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height)
1111
{
@@ -53,6 +53,24 @@ public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, boo
5353
/// </summary>
5454
public uint Height { get; }
5555

56+
public static bool operator ==(WebpVp8X left, WebpVp8X right) => left.Equals(right);
57+
58+
public static bool operator !=(WebpVp8X left, WebpVp8X right) => !(left == right);
59+
60+
public override bool Equals(object? obj) => obj is WebpVp8X x && this.Equals(x);
61+
62+
public bool Equals(WebpVp8X other)
63+
=> this.HasAnimation == other.HasAnimation
64+
&& this.HasXmp == other.HasXmp
65+
&& this.HasExif == other.HasExif
66+
&& this.HasAlpha == other.HasAlpha
67+
&& this.HasIcc == other.HasIcc
68+
&& this.Width == other.Width
69+
&& this.Height == other.Height;
70+
71+
public override int GetHashCode()
72+
=> HashCode.Combine(this.HasAnimation, this.HasXmp, this.HasExif, this.HasAlpha, this.HasIcc, this.Width, this.Height);
73+
5674
public void Validate(uint maxDimension, ulong maxCanvasPixels)
5775
{
5876
if (this.Width > maxDimension || this.Height > maxDimension)
@@ -67,6 +85,9 @@ public void Validate(uint maxDimension, ulong maxCanvasPixels)
6785
}
6886
}
6987

88+
public WebpVp8X WithAlpha(bool hasAlpha)
89+
=> new(this.HasAnimation, this.HasXmp, this.HasExif, hasAlpha, this.HasIcc, this.Width, this.Height);
90+
7091
public void WriteTo(Stream stream)
7192
{
7293
byte flags = 0;

src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public Vp8LEncoder(
237237
/// </summary>
238238
public Vp8LHashChain HashChain { get; }
239239

240-
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
240+
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
241241
where TPixel : unmanaged, IPixel<TPixel>
242242
{
243243
// Write bytes from the bit-writer buffer to the stream.
@@ -247,7 +247,8 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAni
247247
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
248248
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
249249

250-
BitWriterBase.WriteTrunksBeforeData(
250+
// The alpha flag is updated following encoding.
251+
WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData(
251252
stream,
252253
(uint)image.Width,
253254
(uint)image.Height,
@@ -262,9 +263,11 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAni
262263
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
263264
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
264265
}
266+
267+
return vp8x;
265268
}
266269

267-
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
270+
public void EncodeFooter<TPixel>(Image<TPixel> image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition)
268271
where TPixel : unmanaged, IPixel<TPixel>
269272
{
270273
// Write bytes from the bit-writer buffer to the stream.
@@ -273,7 +276,9 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
273276
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
274277
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
275278

276-
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
279+
bool updateVp8x = hasAlpha && vp8x != default;
280+
WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x;
281+
BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile);
277282
}
278283

279284
/// <summary>
@@ -285,7 +290,8 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
285290
/// <param name="frameMetadata">The frame metadata.</param>
286291
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
287292
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
288-
public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
293+
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
294+
public bool Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
289295
where TPixel : unmanaged, IPixel<TPixel>
290296
{
291297
// Convert image pixels to bgra array.
@@ -324,6 +330,8 @@ public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrame
324330
{
325331
RiffHelper.EndWriteChunk(stream, prevPosition);
326332
}
333+
334+
return hasAlpha;
327335
}
328336

329337
/// <summary>
@@ -502,7 +510,7 @@ private void EncodeStream(int width, int height)
502510
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
503511
/// <param name="pixels">The frame pixel buffer to convert.</param>
504512
/// <returns>true, if the image is non opaque.</returns>
505-
private bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
513+
public bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
506514
where TPixel : unmanaged, IPixel<TPixel>
507515
{
508516
bool nonOpaque = false;

src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public Vp8Encoder(
311311
/// </summary>
312312
private int MbHeaderLimit { get; }
313313

314-
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation)
314+
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation)
315315
where TPixel : unmanaged, IPixel<TPixel>
316316
{
317317
// Write bytes from the bitwriter buffer to the stream.
@@ -321,7 +321,7 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlp
321321
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
322322
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
323323

324-
BitWriterBase.WriteTrunksBeforeData(
324+
WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData(
325325
stream,
326326
(uint)image.Width,
327327
(uint)image.Height,
@@ -336,9 +336,11 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlp
336336
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
337337
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
338338
}
339+
340+
return vp8x;
339341
}
340342

341-
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
343+
public void EncodeFooter<TPixel>(Image<TPixel> image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition)
342344
where TPixel : unmanaged, IPixel<TPixel>
343345
{
344346
// Write bytes from the bitwriter buffer to the stream.
@@ -347,7 +349,9 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
347349
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
348350
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
349351

350-
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
352+
bool updateVp8x = hasAlpha && vp8x != default;
353+
WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x;
354+
BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile);
351355
}
352356

353357
/// <summary>
@@ -358,9 +362,10 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
358362
/// <param name="stream">The stream to encode the image data to.</param>
359363
/// <param name="bounds">The region of interest within the frame to encode.</param>
360364
/// <param name="frameMetadata">The frame metadata.</param>
361-
public void EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
362-
where TPixel : unmanaged, IPixel<TPixel> =>
363-
this.Encode(stream, frame, bounds, frameMetadata, true, null);
365+
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
366+
public bool EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
367+
where TPixel : unmanaged, IPixel<TPixel>
368+
=> this.Encode(stream, frame, bounds, frameMetadata, true, null);
364369

365370
/// <summary>
366371
/// Encodes the static image frame to the specified stream.
@@ -385,7 +390,8 @@ public void EncodeStatic<TPixel>(Stream stream, Image<TPixel> image)
385390
/// <param name="frameMetadata">The frame metadata.</param>
386391
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
387392
/// <param name="image">The image to encode from.</param>
388-
private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
393+
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
394+
private bool Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
389395
where TPixel : unmanaged, IPixel<TPixel>
390396
{
391397
int width = bounds.Width;
@@ -515,6 +521,8 @@ private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle b
515521
{
516522
encodedAlphaData?.Dispose();
517523
}
524+
525+
return hasAlpha;
518526
}
519527

520528
/// <inheritdoc/>

src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers.Binary;
5-
using System.Drawing;
65
using SixLabors.ImageSharp.Formats.Webp.BitReader;
76
using SixLabors.ImageSharp.Formats.Webp.Lossy;
87
using SixLabors.ImageSharp.IO;

src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
5454
/// <summary>
5555
/// The flag to decide how to handle the background color in the Animation Chunk.
5656
/// </summary>
57-
private BackgroundColorHandling backgroundColorHandling;
57+
private readonly BackgroundColorHandling backgroundColorHandling;
5858

5959
/// <summary>
6060
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.

0 commit comments

Comments
 (0)