diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index 41623f287..31e636b6b 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -10,11 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{
internal abstract class BitWriterBase
{
+ private const uint MaxDimension = 16777215;
+
+ private const ulong MaxCanvasPixels = 4294967295ul;
+
+ protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
+
///
/// Buffer to write to.
///
private byte[] buffer;
+ ///
+ /// A scratch buffer to reduce allocations.
+ ///
+ private readonly byte[] scratchBuffer = new byte[4];
+
///
/// Initializes a new instance of the class.
///
@@ -81,13 +92,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// The block length.
protected void WriteRiffHeader(Stream stream, uint riffSize)
{
- Span buf = stackalloc byte[4];
stream.Write(WebpConstants.RiffFourCc);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
- stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize);
+ stream.Write(this.scratchBuffer.AsSpan(0, 4));
stream.Write(WebpConstants.WebpHeader);
}
+ ///
+ /// Calculates the exif chunk size.
+ ///
+ /// The exif profile bytes.
+ /// The exif chunk size in bytes.
+ protected uint ExifChunkSize(byte[] exifBytes)
+ {
+ uint exifSize = (uint)exifBytes.Length;
+ uint exifChunkSize = WebpConstants.ChunkHeaderSize + exifSize + (exifSize & 1);
+
+ return exifChunkSize;
+ }
+
///
/// Writes the Exif profile to the stream.
///
@@ -97,12 +120,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{
DebugGuard.NotNull(exifBytes, nameof(exifBytes));
- Span buf = stackalloc byte[4];
+ uint size = (uint)exifBytes.Length;
+ Span buf = this.scratchBuffer.AsSpan(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif);
stream.Write(buf);
- BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
stream.Write(exifBytes);
+
+ // Add padding byte if needed.
+ if ((size & 1) == 1)
+ {
+ stream.WriteByte(0);
+ }
}
///
@@ -114,14 +144,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// The height of the image.
protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
- int maxDimension = 16777215;
- if (width > maxDimension || height > maxDimension)
+ if (width > MaxDimension || height > MaxDimension)
{
- WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}");
+ WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}");
}
// The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
- if (width * height > 4294967295ul)
+ if (width * height > MaxCanvasPixels)
{
WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
}
@@ -133,7 +162,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
flags |= 8;
}
- Span buf = stackalloc byte[4];
+ Span buf = this.scratchBuffer.AsSpan(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index 7628247fd..2c943f64f 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -408,9 +408,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null)
{
isVp8X = true;
- riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
+ riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
- riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
+ riffSize += this.ExifChunkSize(exifBytes);
}
this.Finish();
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
index 2f942231f..2ce2f5550 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
@@ -130,16 +130,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
///
public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
- Span buffer = stackalloc byte[4];
bool isVp8X = false;
byte[] exifBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
- riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
+ riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
- riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
+ riffSize += this.ExifChunkSize(exifBytes);
}
this.Finish();
@@ -161,8 +160,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
stream.Write(WebpConstants.Vp8LMagicBytes);
// Write Vp8 Header.
- BinaryPrimitives.WriteUInt32LittleEndian(buffer, size);
- stream.Write(buffer);
+ BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size);
+ stream.Write(this.scratchBuffer.AsSpan(0, 4));
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
// Write the encoded bytes of the image to the stream.
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index a61fc7253..8640261b1 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -4,11 +4,9 @@
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
index 81067a41f..a051de1c0 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
@@ -63,6 +63,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
}
}
+ [Theory]
+ [InlineData(WebpFileFormatType.Lossy)]
+ [InlineData(WebpFileFormatType.Lossless)]
+ public void Encode_WritesExifWithPadding(WebpFileFormatType fileFormatType)
+ {
+ // arrange
+ using var input = new Image(25, 25);
+ using var memoryStream = new MemoryStream();
+ var expectedExif = new ExifProfile();
+ string expectedSoftware = "ImageSharp";
+ expectedExif.SetValue(ExifTag.Software, expectedSoftware);
+ input.Metadata.ExifProfile = expectedExif;
+
+ // act
+ input.Save(memoryStream, new WebpEncoder() { FileFormat = fileFormatType });
+ memoryStream.Position = 0;
+
+ // assert
+ using var image = Image.Load(memoryStream);
+ ExifProfile actualExif = image.Metadata.ExifProfile;
+ Assert.NotNull(actualExif);
+ Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count);
+ Assert.Equal(expectedSoftware, actualExif.GetValue(ExifTag.Software).Value);
+ }
+
[Theory]
[WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32)]
public void EncodeLossyWebp_PreservesExif(TestImageProvider provider)