Browse Source

Write EXIF chunk, if Exif profile is present

pull/1552/head
Brian Popow 5 years ago
parent
commit
5db2d28e69
  1. 70
      src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs
  2. 47
      src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
  3. 29
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  4. 2
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  5. 2
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  6. 16
      src/ImageSharp/Formats/WebP/WebpConstants.cs
  7. 14
      src/ImageSharp/Formats/WebP/WebpDecoderCore.cs
  8. 17
      src/ImageSharp/Formats/WebP/WebpThrowHelper.cs

70
src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs

@ -4,6 +4,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@ -55,7 +56,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// Writes the encoded image to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public abstract void WriteEncodedImageToStream(Stream stream);
/// <param name="exifProfile">The exif profile.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
public abstract void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height);
protected bool ResizeBuffer(int maxBytes, int sizeRequired)
{
@ -84,12 +88,68 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// <param name="riffSize">The block length.</param>
protected void WriteRiffHeader(Stream stream, uint riffSize)
{
Span<byte> buffer = stackalloc byte[4];
Span<byte> buf = stackalloc byte[4];
stream.Write(WebpConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, riffSize);
stream.Write(buffer);
BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
stream.Write(buf);
stream.Write(WebpConstants.WebPHeader);
}
/// <summary>
/// Writes the Exif profile to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifBytes">The exif profile bytes.</param>
protected void WriteExifProfile(Stream stream, byte[] exifBytes)
{
DebugGuard.NotNull(exifBytes, nameof(exifBytes));
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length);
stream.Write(buf);
stream.Write(exifBytes);
}
/// <summary>
/// Writes a VP8X header to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
/// <param name="width">The width of the image.</param>
/// <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)
{
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)
{
WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
}
uint flags = 0;
if (exifProfile != null)
{
// Set exif bit.
flags |= 8;
}
Span<byte> buf = stackalloc byte[4];
stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1);
stream.Write(buf.Slice(0, 3));
BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
stream.Write(buf.Slice(0, 3));
}
}
}

47
src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@ -73,16 +74,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// <param name="expectedSize">The expected size in bytes.</param>
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: this(expectedSize)
{
this.enc = enc;
}
: this(expectedSize) => this.enc = enc;
/// <inheritdoc/>
public override int NumBytes()
{
return (int)this.pos;
}
public override int NumBytes() => (int)this.pos;
public int PutCoeffs(int ctx, Vp8Residual residual)
{
@ -290,10 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
}
private bool PutBit(bool bit, int prob)
{
return this.PutBit(bit ? 1 : 0, prob);
}
private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob);
private bool PutBit(int bit, int prob)
{
@ -408,8 +400,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
/// <inheritdoc/>
public override void WriteEncodedImageToStream(Stream stream)
public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
bool isVp8X = false;
byte[] exifBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
}
this.Finish();
uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh;
@ -427,10 +430,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
// Compute RIFF size
// At the minimum it is: "WEBPVP8 nnnn" + VP8 data size.
var riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
this.WriteWebPHeaders(stream, size0, vp8Size, riffSize);
this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@ -439,6 +442,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
stream.WriteByte(0);
}
if (exifProfile != null)
{
this.WriteExifProfile(stream, exifBytes);
}
}
private uint GeneratePartition0(Vp8BitWriter bitWriter)
@ -608,9 +616,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
while (it.Next());
}
private void WriteWebPHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize)
private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile)
{
this.WriteRiffHeader(stream, riffSize);
// Write VP8X, header if necessary.
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, width, height);
}
this.WriteVp8Header(stream, vp8Size);
this.WriteFrameHeader(stream, size0);
}

29
src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@ -127,9 +128,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
/// <inheritdoc/>
public override void WriteEncodedImageToStream(Stream stream)
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;
exifBytes = exifProfile.ToByteArray();
riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
}
this.Finish();
uint size = (uint)this.NumBytes();
@ -137,8 +148,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
// Write RIFF header.
uint pad = size & 1;
uint riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad;
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad;
this.WriteRiffHeader(stream, riffSize);
// Write VP8X, header if necessary.
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, width, height);
}
// Write magic bytes indicating its a lossless webp.
stream.Write(WebpConstants.Vp8LMagicBytes);
// Write Vp8 Header.
@ -146,11 +165,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
stream.Write(buffer);
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
// Write the encoded bytes of the image to the stream.
this.WriteToStream(stream);
if (pad == 1)
{
stream.WriteByte(0);
}
if (exifProfile != null)
{
this.WriteExifProfile(stream, exifBytes);
}
}
/// <summary>

2
src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream);
this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height);
}
/// <summary>

2
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -321,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy
this.AdjustFilterStrength();
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream);
this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height);
}
/// <inheritdoc/>

16
src/ImageSharp/Formats/WebP/WebpConstants.cs

@ -57,6 +57,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
0x4C // L
};
/// <summary>
/// Signature bytes identifying a VP8X header.
/// </summary>
public static readonly byte[] Vp8XMagicBytes =
{
0x56, // V
0x50, // P
0x38, // 8
0x58 // X
};
/// <summary>
/// The header bytes identifying RIFF file.
/// </summary>
@ -94,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
/// </summary>
public const int Vp8FrameHeaderSize = 10;
/// <summary>
/// Size of a VP8X chunk in bytes.
/// </summary>
public const int Vp8XChunkSize = 10;
/// <summary>
/// Size of a chunk header.
/// </summary>

14
src/ImageSharp/Formats/WebP/WebpDecoderCore.cs

@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
this.Metadata = new ImageMetadata();
this.currentStream = stream;
this.ReadImageHeader();
var fileSize = this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info())
{
@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
/// <summary>
/// Reads and skips over the image header.
/// </summary>
/// <returns>The chunk size in bytes.</returns>
/// <returns>The file size in bytes.</returns>
private uint ReadImageHeader()
{
// Skip FourCC header, we already know its a RIFF file at this point.
@ -140,12 +140,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
// Read file size.
// The size of the file in bytes starting at offset 8.
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
uint chunkSize = this.ReadChunkSize();
uint fileSize = this.ReadChunkSize();
// Skip 'WEBP' from the header.
this.currentStream.Skip(4);
return chunkSize;
return fileSize;
}
/// <summary>
@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
private WebpImageInfo ReadVp8XHeader()
{
var features = new WebpFeatures();
uint chunkSize = this.ReadChunkSize();
uint fileSize = this.ReadChunkSize();
// The first byte contains information about the image features used.
byte imageFeatures = (byte)this.currentStream.ReadByte();
@ -474,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
WebpChunkType chunkType = this.ReadChunkType();
uint chunkLength = this.ReadChunkSize();
if (chunkType == WebpChunkType.Exif)
if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null)
{
var exifData = new byte[chunkLength];
this.currentStream.Read(exifData, 0, (int)chunkLength);
@ -482,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
}
else
{
// Skip XMP chunk data for now.
// Skip XMP chunk data or any duplicate EXIF chunk.
this.currentStream.Skip((int)chunkLength);
}
}

17
src/ImageSharp/Formats/WebP/WebpThrowHelper.cs

@ -13,19 +13,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowImageFormatException(string errorMessage)
{
throw new ImageFormatException(errorMessage);
}
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
/// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage)
{
throw new NotSupportedException(errorMessage);
}
public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage);
}
}

Loading…
Cancel
Save