From b198ddb45308cbae1c8ceb5c710a737ab0d30828 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Oct 2019 21:39:01 +0200 Subject: [PATCH] Additional webp constants, first attempt parsing VP8L header --- src/ImageSharp/Formats/WebP/WebPConstants.cs | 22 ++++- .../Formats/WebP/WebPDecoderCore.cs | 83 ++++++++++++++----- .../Formats/WebP/WebPImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 2 + 4 files changed, 85 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index bdc026937..038d11301 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -17,10 +17,30 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public static readonly IEnumerable MimeTypes = new[] { "image/webp", }; + /// + /// Signature which identifies a VP8 header. + /// + public static readonly byte[] Vp8MagicBytes = + { + 0x9D, + 0x01, + 0x2A + }; + + /// + /// Signature byte which identifies a VP8L header. + /// + public static byte Vp8LMagicByte = 0x2F; + + /// + /// Bits for width and height infos of a VPL8 image. + /// + public static int Vp8LImageSizeBits = 14; + /// /// The header bytes identifying RIFF file. /// - public static readonly byte[] FourCcBytes = + public static readonly byte[] RiffFourCc = { 0x52, // R 0x49, // I diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 5c5c5d3ac..49871d005 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint chunkSize = this.ReadImageHeader(); WebPImageInfo imageInfo = this.ReadVp8Info(); + // TODO: there can be optional chunks after that, like EXIF. var image = new Image(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); @@ -124,30 +125,25 @@ namespace SixLabors.ImageSharp.Formats.WebP { // Read VP8 chunk header. this.currentStream.Read(this.buffer, 0, 4); - if (this.buffer.AsSpan().SequenceEqual(WebPConstants.AlphaHeader)) - { - WebPThrowHelper.ThrowImageFormatException("Alpha channel is not yet supported"); - } - var vp8HeaderType = Vp8HeaderType.Invalid; if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header)) { - vp8HeaderType = Vp8HeaderType.Vp8; - } - else if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader)) - { - vp8HeaderType = Vp8HeaderType.Vp8L; + return this.ReadVp8Header(); } - else if (this.buffer.SequenceEqual(WebPConstants.Vp8XHeader)) + + if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader)) { - vp8HeaderType = Vp8HeaderType.Vp8X; + return this.ReadVp8LHeader(); } - else + + if (this.buffer.SequenceEqual(WebPConstants.Vp8XHeader)) { - WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + return this.ReadVp8XHeader(); } - return vp8HeaderType == Vp8HeaderType.Vp8X ? this.ReadVp8XHeader() : this.ReadVp8Header(vp8HeaderType); + WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + + return new WebPImageInfo(); } private WebPImageInfo ReadVp8XHeader() @@ -175,10 +171,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Width = width, Height = height, IsLossLess = false, // note: this is maybe incorrect here + DataSize = chunkSize }; } - private WebPImageInfo ReadVp8Header(Vp8HeaderType vp8HeaderType) + private WebPImageInfo ReadVp8Header() { // VP8 data size. this.currentStream.Read(this.buffer, 0, 3); @@ -186,22 +183,64 @@ namespace SixLabors.ImageSharp.Formats.WebP uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); // https://tools.ietf.org/html/rfc6386#page-30 - var imageInfo = new byte[11]; - this.currentStream.Read(imageInfo, 0, imageInfo.Length); - int tmp = (imageInfo[2] << 16) | (imageInfo[1] << 8) | imageInfo[0]; + // Frame tag that contains four fields: + // - A 1-bit frame type (0 for key frames, 1 for interframes). + // - A 3-bit version number. + // - A 1-bit show_frame flag. + // - A 19-bit field containing the size of the first data partition in bytes. + this.currentStream.Read(this.buffer, 0, 3); + int tmp = (this.buffer[2] << 16) | (this.buffer[1] << 8) | this.buffer[0]; int isKeyFrame = tmp & 0x1; int version = (tmp >> 1) & 0x7; int showFrame = (tmp >> 4) & 0x1; + // Check for VP8 magic bytes. + this.currentStream.Read(this.buffer, 0, 4); + if (!this.buffer.AsSpan(1).SequenceEqual(WebPConstants.Vp8MagicBytes)) + { + WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); + } + + this.currentStream.Read(this.buffer, 0, 4); + // TODO: Get horizontal and vertical scale - int width = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(7)) & 0x3fff; - int height = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(9)) & 0x3fff; + int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; + int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; + + return new WebPImageInfo() + { + Width = width, + Height = height, + IsLossLess = false, + DataSize = dataSize + }; + } + + private WebPImageInfo ReadVp8LHeader() + { + // VP8 data size. + this.currentStream.Read(this.buffer, 0, 4); + uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + // One byte signature, should be 0x2f. + byte signature = (byte)this.currentStream.ReadByte(); + if (signature != WebPConstants.Vp8LMagicByte) + { + WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); + } + + // The first 28 bits of the bitstream specify the width and height of the image. + this.currentStream.Read(this.buffer, 0, 4); + // TODO: A bitreader should be used from here on which reads least-significant-bit-first + int height = 0; + int width = 0; return new WebPImageInfo() { Width = width, Height = height, - IsLossLess = vp8HeaderType == Vp8HeaderType.Vp8L, + IsLossLess = true, + DataSize = dataSize }; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 9cd8d7916..dc0ecadc8 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// True, if its a valid RIFF FourCC. private bool IsRiffContainer(ReadOnlySpan header) { - return header.Slice(0, 4).SequenceEqual(WebPConstants.FourCcBytes); + return header.Slice(0, 4).SequenceEqual(WebPConstants.RiffFourCc); } /// diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 065e5dd7e..49aa31f5e 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -19,5 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets whether this image uses a lossless compression. /// public bool IsLossLess { get; set; } + + public uint DataSize { get; set; } } }