Browse Source

Write exif profile with padding if needed

pull/1798/head
Brian Popow 5 years ago
parent
commit
c68ef21613
  1. 49
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  2. 4
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  3. 9
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  4. 2
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  5. 25
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

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

@ -10,11 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{ {
internal abstract class BitWriterBase internal abstract class BitWriterBase
{ {
private const uint MaxDimension = 16777215;
private const ulong MaxCanvasPixels = 4294967295ul;
protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
/// <summary> /// <summary>
/// Buffer to write to. /// Buffer to write to.
/// </summary> /// </summary>
private byte[] buffer; private byte[] buffer;
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BitWriterBase"/> class. /// Initializes a new instance of the <see cref="BitWriterBase"/> class.
/// </summary> /// </summary>
@ -81,13 +92,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="riffSize">The block length.</param> /// <param name="riffSize">The block length.</param>
protected void WriteRiffHeader(Stream stream, uint riffSize) protected void WriteRiffHeader(Stream stream, uint riffSize)
{ {
Span<byte> buf = stackalloc byte[4];
stream.Write(WebpConstants.RiffFourCc); stream.Write(WebpConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize); BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize);
stream.Write(buf); stream.Write(this.scratchBuffer.AsSpan(0, 4));
stream.Write(WebpConstants.WebpHeader); stream.Write(WebpConstants.WebpHeader);
} }
/// <summary>
/// Calculates the exif chunk size.
/// </summary>
/// <param name="exifBytes">The exif profile bytes.</param>
/// <returns>The exif chunk size in bytes.</returns>
protected uint ExifChunkSize(byte[] exifBytes)
{
uint exifSize = (uint)exifBytes.Length;
uint exifChunkSize = WebpConstants.ChunkHeaderSize + exifSize + (exifSize & 1);
return exifChunkSize;
}
/// <summary> /// <summary>
/// Writes the Exif profile to the stream. /// Writes the Exif profile to the stream.
/// </summary> /// </summary>
@ -97,12 +120,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{ {
DebugGuard.NotNull(exifBytes, nameof(exifBytes)); DebugGuard.NotNull(exifBytes, nameof(exifBytes));
Span<byte> buf = stackalloc byte[4]; uint size = (uint)exifBytes.Length;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif);
stream.Write(buf); stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length); BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf); stream.Write(buf);
stream.Write(exifBytes); stream.Write(exifBytes);
// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
} }
/// <summary> /// <summary>
@ -114,14 +144,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="height">The height of the image.</param> /// <param name="height">The height of the image.</param>
protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height) 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. // 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"); 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; flags |= 8;
} }
Span<byte> buf = stackalloc byte[4]; Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes); stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize); BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf); stream.Write(buf);

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

@ -408,9 +408,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null) if (exifProfile != null)
{ {
isVp8X = true; isVp8X = true;
riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray(); exifBytes = exifProfile.ToByteArray();
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length; riffSize += this.ExifChunkSize(exifBytes);
} }
this.Finish(); this.Finish();

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

@ -130,16 +130,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <inheritdoc/> /// <inheritdoc/>
public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height) public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{ {
Span<byte> buffer = stackalloc byte[4];
bool isVp8X = false; bool isVp8X = false;
byte[] exifBytes = null; byte[] exifBytes = null;
uint riffSize = 0; uint riffSize = 0;
if (exifProfile != null) if (exifProfile != null)
{ {
isVp8X = true; isVp8X = true;
riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray(); exifBytes = exifProfile.ToByteArray();
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length; riffSize += this.ExifChunkSize(exifBytes);
} }
this.Finish(); this.Finish();
@ -161,8 +160,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
stream.Write(WebpConstants.Vp8LMagicBytes); stream.Write(WebpConstants.Vp8LMagicBytes);
// Write Vp8 Header. // Write Vp8 Header.
BinaryPrimitives.WriteUInt32LittleEndian(buffer, size); BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size);
stream.Write(buffer); stream.Write(this.scratchBuffer.AsSpan(0, 4));
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
// Write the encoded bytes of the image to the stream. // Write the encoded bytes of the image to the stream.

2
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -4,11 +4,9 @@
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp namespace SixLabors.ImageSharp.Formats.Webp

25
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<Rgba32>(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<Rgba32>(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] [Theory]
[WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32)] [WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32)]
public void EncodeLossyWebp_PreservesExif<TPixel>(TestImageProvider<TPixel> provider) public void EncodeLossyWebp_PreservesExif<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save