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.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
@ -67,13 +71,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
/// </summary>
/// <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())
{
inputStream.CopyTo(ms);
CopyStream(inputStream, ms, (int)imageDataSize, memoryAllocator);
this.data = ms.ToArray();
}
@ -114,13 +120,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.ShiftBytes();
return (uint)val;
}
else
{
this.SetEndOfStream();
return 0;
}
this.SetEndOfStream();
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()
{
uint bit = this.ReadBits(1);
@ -155,6 +163,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
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()
{
while (this.bitPos >= 8 && this.pos < this.len)
@ -176,5 +187,24 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.eos = true;
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.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -32,21 +33,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly IWebPDecoderOptions options;
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream currentStream;
/// <summary>
/// The metadata.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// The webp specific metadata.
/// </summary>
@ -61,9 +52,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
this.configuration = configuration;
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>
/// Decodes the image from the specified <see cref="Stream"/> and sets the data to the image.
/// </summary>
@ -73,6 +74,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
{
this.Metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader();
@ -82,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
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();
if (imageInfo.IsLossLess)
{
@ -95,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
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)
{
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.
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>
@ -142,8 +144,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
private WebPImageInfo ReadVp8Info()
{
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetFormatMetadata(WebPFormat.Instance);
this.Metadata = new ImageMetadata();
this.webpMetadata = this.Metadata.GetFormatMetadata(WebPFormat.Instance);
WebPChunkType chunkType = this.ReadChunkType();
@ -322,10 +324,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.webpMetadata.Format = WebPFormatType.Lossless;
// 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.
var bitReader = new Vp8LBitReader(this.currentStream);
uint signature = bitReader.ReadBits(8);
if (signature != WebPConstants.Vp8LMagicByte)
{
@ -349,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Width = (int)width,
Height = (int)height,
IsLossLess = true,
ImageDataSize = dataSize,
ImageDataSize = imageDataSize,
Features = features,
Vp9LBitReader = bitReader
};
@ -363,7 +366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <param name="features">The webp features.</param>
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;
}
@ -374,8 +377,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPChunkType chunkType = this.ReadChunkType();
uint chunkLength = this.ReadChunkSize();
// Skip chunk data for now.
this.currentStream.Skip((int)chunkLength);
if (chunkType is WebPChunkType.Exif)
{
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
// 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();
if (isInterframe)
{
@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
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)

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