Browse Source

Add parsing of EXIF chunk

pull/1147/head
Brian Popow 6 years ago
parent
commit
06e7d4e524
  1. 46
      src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
  2. 56
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  3. 5
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
  4. BIN
      tests/Images/Input/WebP/exif.webp
  5. BIN
      tests/Images/Input/WebP/exif_lossless.webp

46
src/ImageSharp/Formats/WebP/Vp8LBitReader.cs

@ -1,8 +1,12 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
{ {
/// <summary> /// <summary>
@ -67,13 +71,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class. /// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
/// </summary> /// </summary>
/// <param name="inputStream">The input stream to read from.</param> /// <param name="inputStream">The input stream to read from.</param>
public Vp8LBitReader(Stream inputStream) /// <param name="imageDataSize">The image data size in bytes.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
{ {
long length = inputStream.Length - inputStream.Position; long length = imageDataSize;
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
inputStream.CopyTo(ms); CopyStream(inputStream, ms, (int)imageDataSize, memoryAllocator);
this.data = ms.ToArray(); this.data = ms.ToArray();
} }
@ -114,13 +120,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.ShiftBytes(); this.ShiftBytes();
return (uint)val; return (uint)val;
} }
else
{ this.SetEndOfStream();
this.SetEndOfStream(); return 0;
return 0;
}
} }
/// <summary>
/// Reads a single bit from the stream.
/// </summary>
/// <returns>True if the bit read was 1, false otherwise.</returns>
public bool ReadBit() public bool ReadBit()
{ {
uint bit = this.ReadBits(1); uint bit = this.ReadBits(1);
@ -155,6 +163,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits));
} }
/// <summary>
/// If not at EOS, reload up to Vp8LLbits byte-by-byte.
/// </summary>
private void ShiftBytes() private void ShiftBytes()
{ {
while (this.bitPos >= 8 && this.pos < this.len) while (this.bitPos >= 8 && this.pos < this.len)
@ -176,5 +187,24 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.eos = true; this.eos = true;
this.bitPos = 0; // To avoid undefined behaviour with shifts. this.bitPos = 0; // To avoid undefined behaviour with shifts.
} }
private static void CopyStream(Stream input, Stream output, int bytesToRead, MemoryAllocator memoryAllocator)
{
using (IManagedByteBuffer buffer = memoryAllocator.AllocateManagedByteBuffer(4096))
{
Span<byte> bufferSpan = buffer.GetSpan();
int read;
while (bytesToRead > 0 && (read = input.Read(buffer.Array, 0, Math.Min(bufferSpan.Length, bytesToRead))) > 0)
{
output.Write(buffer.Array, 0, read);
bytesToRead -= read;
}
if (bytesToRead > 0)
{
WebPThrowHelper.ThrowImageFormatException("image file has insufficient data");
}
}
}
} }
} }

56
src/ImageSharp/Formats/WebP/WebPDecoderCore.cs

@ -7,6 +7,7 @@ using System.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -32,21 +33,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly IWebPDecoderOptions options;
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
/// </summary> /// </summary>
private Stream currentStream; private Stream currentStream;
/// <summary>
/// The metadata.
/// </summary>
private ImageMetadata metadata;
/// <summary> /// <summary>
/// The webp specific metadata. /// The webp specific metadata.
/// </summary> /// </summary>
@ -61,9 +52,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
{ {
this.configuration = configuration; this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator; this.memoryAllocator = configuration.MemoryAllocator;
this.options = options; this.IgnoreMetadata = options.IgnoreMetadata;
} }
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; }
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
public ImageMetadata Metadata { get; private set; }
/// <summary> /// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets the data to the image. /// Decodes the image from the specified <see cref="Stream"/> and sets the data to the image.
/// </summary> /// </summary>
@ -73,6 +74,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Image<TPixel> Decode<TPixel>(Stream stream) public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.Metadata = new ImageMetadata();
this.currentStream = stream; this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader();
@ -82,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); WebPThrowHelper.ThrowNotSupportedException("Animations are not supported");
} }
var image = new Image<TPixel>(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); var image = new Image<TPixel>(this.configuration, imageInfo.Width, imageInfo.Height, this.Metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (imageInfo.IsLossLess) if (imageInfo.IsLossLess)
{ {
@ -95,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
lossyDecoder.Decode(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); lossyDecoder.Decode(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize);
} }
// There can be optional chunks after the image data, like EXIF, XMP etc. // There can be optional chunks after the image data, like EXIF and XMP.
if (imageInfo.Features != null) if (imageInfo.Features != null)
{ {
this.ParseOptionalChunks(imageInfo.Features); this.ParseOptionalChunks(imageInfo.Features);
@ -117,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
// TODO: not sure yet where to get this info. Assuming 24 bits for now. // TODO: not sure yet where to get this info. Assuming 24 bits for now.
int bitsPerPixel = 24; int bitsPerPixel = 24;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), imageInfo.Width, imageInfo.Height, this.metadata); return new ImageInfo(new PixelTypeInfo(bitsPerPixel), imageInfo.Width, imageInfo.Height, this.Metadata);
} }
/// <summary> /// <summary>
@ -142,8 +144,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
private WebPImageInfo ReadVp8Info() private WebPImageInfo ReadVp8Info()
{ {
this.metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetFormatMetadata(WebPFormat.Instance); this.webpMetadata = this.Metadata.GetFormatMetadata(WebPFormat.Instance);
WebPChunkType chunkType = this.ReadChunkType(); WebPChunkType chunkType = this.ReadChunkType();
@ -322,10 +324,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.webpMetadata.Format = WebPFormatType.Lossless; this.webpMetadata.Format = WebPFormatType.Lossless;
// VP8 data size. // VP8 data size.
uint dataSize = this.ReadChunkSize(); uint imageDataSize = this.ReadChunkSize();
var bitReader = new Vp8LBitReader(this.currentStream, imageDataSize, this.memoryAllocator);
// One byte signature, should be 0x2f. // One byte signature, should be 0x2f.
var bitReader = new Vp8LBitReader(this.currentStream);
uint signature = bitReader.ReadBits(8); uint signature = bitReader.ReadBits(8);
if (signature != WebPConstants.Vp8LMagicByte) if (signature != WebPConstants.Vp8LMagicByte)
{ {
@ -349,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Width = (int)width, Width = (int)width,
Height = (int)height, Height = (int)height,
IsLossLess = true, IsLossLess = true,
ImageDataSize = dataSize, ImageDataSize = imageDataSize,
Features = features, Features = features,
Vp9LBitReader = bitReader Vp9LBitReader = bitReader
}; };
@ -363,7 +366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <param name="features">The webp features.</param> /// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebPFeatures features) private void ParseOptionalChunks(WebPFeatures features)
{ {
if (features.ExifProfile is false && features.XmpMetaData is false) if (this.IgnoreMetadata || (features.ExifProfile is false && features.XmpMetaData is false))
{ {
return; return;
} }
@ -374,8 +377,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPChunkType chunkType = this.ReadChunkType(); WebPChunkType chunkType = this.ReadChunkType();
uint chunkLength = this.ReadChunkSize(); uint chunkLength = this.ReadChunkSize();
// Skip chunk data for now. if (chunkType is WebPChunkType.Exif)
this.currentStream.Skip((int)chunkLength); {
var exifData = new byte[chunkLength];
this.currentStream.Read(exifData, 0, (int)chunkLength);
this.Metadata.ExifProfile = new ExifProfile(exifData);
}
else
{
// Skip XMP chunk data for now.
this.currentStream.Skip((int)chunkLength);
}
} }
} }

5
src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

@ -46,7 +46,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
// TODO weiter bei S.11 // TODO weiter bei S.11
// bit STREAM: See https://tools.ietf.org/html/rfc6386#page-29 ("Frame Header") // bit STREAM: See https://tools.ietf.org/html/rfc6386#page-29 ("Frame Header")
Vp8LBitReader bitReader = new Vp8LBitReader(this.currentStream); // TODO: Vp8BitReader should be used here instead
/*Vp8LBitReader bitReader = new Vp8LBitReader(this.currentStream);
bool isInterframe = bitReader.ReadBit(); bool isInterframe = bitReader.ReadBit();
if (isInterframe) if (isInterframe)
{ {
@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
bool isShowFrame = bitReader.ReadBit(); bool isShowFrame = bitReader.ReadBit();
uint firstPartitionSize = (bitReader.ReadBits(16) << 3) | bitReader.ReadBits(3); uint firstPartitionSize = (bitReader.ReadBits(16) << 3) | bitReader.ReadBits(3);*/
} }
private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version) private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version)

BIN
tests/Images/Input/WebP/exif.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
tests/Images/Input/WebP/exif_lossless.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Loading…
Cancel
Save