Browse Source

Write exif profile with padding if needed

pull/1798/head
Brian Popow 4 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
{
private const uint MaxDimension = 16777215;
private const ulong MaxCanvasPixels = 4294967295ul;
protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
/// <summary>
/// Buffer to write to.
/// </summary>
private byte[] buffer;
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
/// <summary>
/// Initializes a new instance of the <see cref="BitWriterBase"/> class.
/// </summary>
@ -81,13 +92,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="riffSize">The block length.</param>
protected void WriteRiffHeader(Stream stream, uint riffSize)
{
Span<byte> 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);
}
/// <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>
/// Writes the Exif profile to the stream.
/// </summary>
@ -97,12 +120,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{
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);
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);
}
}
/// <summary>
@ -114,14 +144,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="height">The height of the image.</param>
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<byte> buf = stackalloc byte[4];
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);

4
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();

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

@ -130,16 +130,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <inheritdoc/>
public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
Span<byte> 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.

2
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

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

Loading…
Cancel
Save