diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 180f3cb5b..d3a13035d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/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 { /// @@ -67,13 +71,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Initializes a new instance of the class. /// /// The input stream to read from. - public Vp8LBitReader(Stream inputStream) + /// The image data size in bytes. + /// Used for allocating memory during reading data from the stream. + 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; } + /// + /// Reads a single bit from the stream. + /// + /// True if the bit read was 1, false otherwise. 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)); } + /// + /// If not at EOS, reload up to Vp8LLbits byte-by-byte. + /// 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 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"); + } + } + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 39d7ecdfc..104138c96 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/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 /// private readonly MemoryAllocator memoryAllocator; - /// - /// The bitmap decoder options. - /// - private readonly IWebPDecoderOptions options; - /// /// The stream to decode from. /// private Stream currentStream; - /// - /// The metadata. - /// - private ImageMetadata metadata; - /// /// The webp specific metadata. /// @@ -61,9 +52,19 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; + this.IgnoreMetadata = options.IgnoreMetadata; } + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; } + + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetadata Metadata { get; private set; } + /// /// Decodes the image from the specified and sets the data to the image. /// @@ -73,6 +74,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Image Decode(Stream stream) where TPixel : struct, IPixel { + 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(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); + var image = new Image(this.configuration, imageInfo.Width, imageInfo.Height, this.Metadata); Buffer2D 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); } /// @@ -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 /// The webp 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; } @@ -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); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 906f11efd..484f5071f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/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) diff --git a/tests/Images/Input/WebP/exif.webp b/tests/Images/Input/WebP/exif.webp new file mode 100644 index 000000000..440250dd8 Binary files /dev/null and b/tests/Images/Input/WebP/exif.webp differ diff --git a/tests/Images/Input/WebP/exif_lossless.webp b/tests/Images/Input/WebP/exif_lossless.webp new file mode 100644 index 000000000..18ef4153f Binary files /dev/null and b/tests/Images/Input/WebP/exif_lossless.webp differ