Skip to content

Commit 85e3be1

Browse files
Merge pull request #2703 from SixLabors/v4/fix-webp-alpha-flags
V4 Ensure VP8X alpha flag is updated correctly.
2 parents 995cfee + 6733e8d commit 85e3be1

File tree

8 files changed

+105
-35
lines changed

8 files changed

+105
-35
lines changed

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
@@ -3,7 +3,7 @@
33

44
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
55

6-
internal readonly struct WebpVp8X
6+
internal readonly struct WebpVp8X : IEquatable<WebpVp8X>
77
{
88
public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height)
99
{
@@ -51,6 +51,24 @@ public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, boo
5151
/// </summary>
5252
public uint Height { get; }
5353

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

86+
public WebpVp8X WithAlpha(bool hasAlpha)
87+
=> new(this.HasAnimation, this.HasXmp, this.HasExif, hasAlpha, this.HasIcc, this.Width, this.Height);
88+
6889
public void WriteTo(Stream stream)
6990
{
7091
byte flags = 0;

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

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

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

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

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

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

278283
/// <summary>
@@ -284,7 +289,8 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
284289
/// <param name="frameMetadata">The frame metadata.</param>
285290
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
286291
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
287-
public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
292+
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
293+
public bool Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
288294
where TPixel : unmanaged, IPixel<TPixel>
289295
{
290296
// Convert image pixels to bgra array.
@@ -323,6 +329,8 @@ public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrame
323329
{
324330
RiffHelper.EndWriteChunk(stream, prevPosition);
325331
}
332+
333+
return hasAlpha;
326334
}
327335

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

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

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

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

323-
BitWriterBase.WriteTrunksBeforeData(
323+
WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData(
324324
stream,
325325
(uint)image.Width,
326326
(uint)image.Height,
@@ -335,9 +335,11 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlp
335335
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
336336
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
337337
}
338+
339+
return vp8x;
338340
}
339341

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

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

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

364369
/// <summary>
365370
/// Encodes the static image frame to the specified stream.
@@ -384,7 +389,8 @@ public void EncodeStatic<TPixel>(Stream stream, Image<TPixel> image)
384389
/// <param name="frameMetadata">The frame metadata.</param>
385390
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
386391
/// <param name="image">The image to encode from.</param>
387-
private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
392+
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
393+
private bool Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
388394
where TPixel : unmanaged, IPixel<TPixel>
389395
{
390396
int width = bounds.Width;
@@ -514,6 +520,8 @@ private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle b
514520
{
515521
encodedAlphaData?.Dispose();
516522
}
523+
524+
return hasAlpha;
517525
}
518526

519527
/// <inheritdoc/>

src/ImageSharp/Formats/Webp/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.Formats.Webp;
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/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)