Skip to content

Commit c68ef21

Browse files
committed
Write exif profile with padding if needed
1 parent 72904e1 commit c68ef21

File tree

5 files changed

+70
-19
lines changed

5 files changed

+70
-19
lines changed

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
1010
{
1111
internal abstract class BitWriterBase
1212
{
13+
private const uint MaxDimension = 16777215;
14+
15+
private const ulong MaxCanvasPixels = 4294967295ul;
16+
17+
protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
18+
1319
/// <summary>
1420
/// Buffer to write to.
1521
/// </summary>
1622
private byte[] buffer;
1723

24+
/// <summary>
25+
/// A scratch buffer to reduce allocations.
26+
/// </summary>
27+
private readonly byte[] scratchBuffer = new byte[4];
28+
1829
/// <summary>
1930
/// Initializes a new instance of the <see cref="BitWriterBase"/> class.
2031
/// </summary>
@@ -81,13 +92,25 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
8192
/// <param name="riffSize">The block length.</param>
8293
protected void WriteRiffHeader(Stream stream, uint riffSize)
8394
{
84-
Span<byte> buf = stackalloc byte[4];
8595
stream.Write(WebpConstants.RiffFourCc);
86-
BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
87-
stream.Write(buf);
96+
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize);
97+
stream.Write(this.scratchBuffer.AsSpan(0, 4));
8898
stream.Write(WebpConstants.WebpHeader);
8999
}
90100

101+
/// <summary>
102+
/// Calculates the exif chunk size.
103+
/// </summary>
104+
/// <param name="exifBytes">The exif profile bytes.</param>
105+
/// <returns>The exif chunk size in bytes.</returns>
106+
protected uint ExifChunkSize(byte[] exifBytes)
107+
{
108+
uint exifSize = (uint)exifBytes.Length;
109+
uint exifChunkSize = WebpConstants.ChunkHeaderSize + exifSize + (exifSize & 1);
110+
111+
return exifChunkSize;
112+
}
113+
91114
/// <summary>
92115
/// Writes the Exif profile to the stream.
93116
/// </summary>
@@ -97,12 +120,19 @@ protected void WriteExifProfile(Stream stream, byte[] exifBytes)
97120
{
98121
DebugGuard.NotNull(exifBytes, nameof(exifBytes));
99122

100-
Span<byte> buf = stackalloc byte[4];
123+
uint size = (uint)exifBytes.Length;
124+
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
101125
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif);
102126
stream.Write(buf);
103-
BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length);
127+
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
104128
stream.Write(buf);
105129
stream.Write(exifBytes);
130+
131+
// Add padding byte if needed.
132+
if ((size & 1) == 1)
133+
{
134+
stream.WriteByte(0);
135+
}
106136
}
107137

108138
/// <summary>
@@ -114,14 +144,13 @@ protected void WriteExifProfile(Stream stream, byte[] exifBytes)
114144
/// <param name="height">The height of the image.</param>
115145
protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height)
116146
{
117-
int maxDimension = 16777215;
118-
if (width > maxDimension || height > maxDimension)
147+
if (width > MaxDimension || height > MaxDimension)
119148
{
120-
WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}");
149+
WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}");
121150
}
122151

123152
// The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
124-
if (width * height > 4294967295ul)
153+
if (width * height > MaxCanvasPixels)
125154
{
126155
WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
127156
}
@@ -133,7 +162,7 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint widt
133162
flags |= 8;
134163
}
135164

136-
Span<byte> buf = stackalloc byte[4];
165+
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
137166
stream.Write(WebpConstants.Vp8XMagicBytes);
138167
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
139168
stream.Write(buf);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,9 @@ public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifPr
408408
if (exifProfile != null)
409409
{
410410
isVp8X = true;
411-
riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
411+
riffSize += ExtendedFileChunkSize;
412412
exifBytes = exifProfile.ToByteArray();
413-
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
413+
riffSize += this.ExifChunkSize(exifBytes);
414414
}
415415

416416
this.Finish();

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,15 @@ public override void Finish()
130130
/// <inheritdoc/>
131131
public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
132132
{
133-
Span<byte> buffer = stackalloc byte[4];
134133
bool isVp8X = false;
135134
byte[] exifBytes = null;
136135
uint riffSize = 0;
137136
if (exifProfile != null)
138137
{
139138
isVp8X = true;
140-
riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
139+
riffSize += ExtendedFileChunkSize;
141140
exifBytes = exifProfile.ToByteArray();
142-
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
141+
riffSize += this.ExifChunkSize(exifBytes);
143142
}
144143

145144
this.Finish();
@@ -161,8 +160,8 @@ public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifPr
161160
stream.Write(WebpConstants.Vp8LMagicBytes);
162161

163162
// Write Vp8 Header.
164-
BinaryPrimitives.WriteUInt32LittleEndian(buffer, size);
165-
stream.Write(buffer);
163+
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size);
164+
stream.Write(this.scratchBuffer.AsSpan(0, 4));
166165
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
167166

168167
// Write the encoded bytes of the image to the stream.

src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
using System.IO;
55
using System.Threading;
66
using SixLabors.ImageSharp.Advanced;
7-
using SixLabors.ImageSharp.Formats.Bmp;
87
using SixLabors.ImageSharp.Formats.Webp.Lossless;
98
using SixLabors.ImageSharp.Formats.Webp.Lossy;
109
using SixLabors.ImageSharp.Memory;
11-
using SixLabors.ImageSharp.Metadata;
1210
using SixLabors.ImageSharp.PixelFormats;
1311

1412
namespace SixLabors.ImageSharp.Formats.Webp

tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@ public void IgnoreMetadata_ControlsWhetherIccpIsParsed<TPixel>(TestImageProvider
6363
}
6464
}
6565

66+
[Theory]
67+
[InlineData(WebpFileFormatType.Lossy)]
68+
[InlineData(WebpFileFormatType.Lossless)]
69+
public void Encode_WritesExifWithPadding(WebpFileFormatType fileFormatType)
70+
{
71+
// arrange
72+
using var input = new Image<Rgba32>(25, 25);
73+
using var memoryStream = new MemoryStream();
74+
var expectedExif = new ExifProfile();
75+
string expectedSoftware = "ImageSharp";
76+
expectedExif.SetValue(ExifTag.Software, expectedSoftware);
77+
input.Metadata.ExifProfile = expectedExif;
78+
79+
// act
80+
input.Save(memoryStream, new WebpEncoder() { FileFormat = fileFormatType });
81+
memoryStream.Position = 0;
82+
83+
// assert
84+
using var image = Image.Load<Rgba32>(memoryStream);
85+
ExifProfile actualExif = image.Metadata.ExifProfile;
86+
Assert.NotNull(actualExif);
87+
Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count);
88+
Assert.Equal(expectedSoftware, actualExif.GetValue(ExifTag.Software).Value);
89+
}
90+
6691
[Theory]
6792
[WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32)]
6893
public void EncodeLossyWebp_PreservesExif<TPixel>(TestImageProvider<TPixel> provider)

0 commit comments

Comments
 (0)