diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 0d44db8d8..c41c3019a 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Processing; using SixLabors.Memory; @@ -158,7 +159,8 @@ namespace SixLabors.ImageSharp new PngConfigurationModule(), new JpegConfigurationModule(), new GifConfigurationModule(), - new BmpConfigurationModule()); + new BmpConfigurationModule(), + new WebPConfigurationModule()); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs b/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs deleted file mode 100644 index 04a52f12b..000000000 --- a/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - class ExtendedDecoderCore : WebPDecoderCoreBase - { - public override Image Decode(Stream stream) - => throw new NotImplementedException(); - } -} diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs new file mode 100644 index 000000000..1ddbdbdc6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Image decoder for generating an image out of a webp stream. + /// + internal interface IWebPDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md index ba4ece71c..41409f136 100644 --- a/src/ImageSharp/Formats/WebP/Readme.md +++ b/src/ImageSharp/Formats/WebP/Readme.md @@ -1,4 +1,8 @@ -= WebP Format +# WebP Format + Reference implementation, specification and stuff like that: -https://developers.google.com/speed/webp +- [google webp introduction](https://developers.google.com/speed/webp) +- [WebP Spec 0.2](https://chromium.googlesource.com/webm/libwebp/+/v0.2.0/doc/webp-container-spec.txt) +- [WebP VP8 chunk Spec](http://tools.ietf.org/html/rfc6386) +- [WebP filefront](https://wiki.fileformat.com/image/webp/) diff --git a/src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs deleted file mode 100644 index e4cf9d103..000000000 --- a/src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - class SimpleLosslessDecoder : WebPDecoderCoreBase - { - public override Image Decode(Stream stream) - => throw new NotImplementedException(); - } -} diff --git a/src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs b/src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs deleted file mode 100644 index ad3e1d9c9..000000000 --- a/src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - class SimpleLossyDecoder : WebPDecoderCoreBase - { - public override Image Decode(Stream stream) - => throw new NotImplementedException(); - } -} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 794fa16cc..e1f0fd23d 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -16,5 +16,70 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The list of mimetypes that equate to a jpeg. /// public static readonly IEnumerable MimeTypes = new[] { "image/webp", }; + + /// + /// The header bytes identifying RIFF file. + /// + public static readonly byte[] FourCcBytes = + { + 0x52, // R + 0x49, // I + 0x46, // F + 0x46 // F + }; + + /// + /// The header bytes identifying a WebP. + /// + public static readonly byte[] WebPHeader = + { + 0x57, // W + 0x45, // E + 0x42, // B + 0x50 // P + }; + + /// + /// Header signaling the use of VP8 video format. + /// + public static readonly byte[] Vp8Header = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x20, // Space + }; + + /// + /// Header for a extended-VP8 chunk. + /// + public static readonly byte[] Vp8XHeader = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x88, // X + }; + + public static readonly byte LossLessFlag = 0x4C; // L + + /// + /// VP8 header, signaling the use of VP8L lossless format. + /// + public static readonly byte[] Vp8LHeader = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + LossLessFlag // L + }; + + public static readonly byte[] AlphaHeader = + { + 0x41, // A + 0x4C, // L + 0x50, // P + 0x48, // H + }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 8dd11dff2..723c2cf2c 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -9,20 +9,31 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Image decoder for generating an image out of a webp stream. /// - public sealed class WebPDecoder : IImageDecoder + public sealed class WebPDecoder : IImageDecoder, IWebPDecoderOptions, IImageInfoDetector { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - // TODO: [brianpopow] parse chunks and decide which decoder (subclass of WebPDecoderCoreBase) to use - throw new System.NotImplementedException(); + Guard.NotNull(stream, nameof(stream)); + + return new WebPDecoderCore(configuration, this).Decode(stream); } /// - public Image Decode(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream) { - throw new System.NotImplementedException(); + Guard.NotNull(stream, nameof(stream)); + + return new WebPDecoderCore(configuration, this).Identify(stream); } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs new file mode 100644 index 000000000..2cc8f592c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.IO; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Performs the bitmap decoding operation. + /// + internal sealed class WebPDecoderCore + { + /// + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public WebPDecoderCore(Configuration configuration, IWebPDecoderOptions options) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } + + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + var metadata = new ImageMetadata(); + WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); + this.currentStream = stream; + + 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); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (imageInfo.IsLooseLess) + { + ReadSimpleLossless(pixels, image.Width, image.Height); + } + else + { + ReadSimpleLossy(pixels, image.Width, image.Height); + } + + return image; + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) + { + var metadata = new ImageMetadata(); + WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); + this.currentStream = stream; + + this.ReadImageHeader(); + WebPImageInfo imageInfo = this.ReadVp8Info(); + + // 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); + } + + private uint ReadImageHeader() + { + // Skip FourCC header, we already know its a RIFF file at this point. + this.currentStream.Skip(4); + + // Read Chunk size. + this.currentStream.Read(this.buffer, 0, 4); + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + // Skip 'WEBP' from the header. + this.currentStream.Skip(4); + + return chunkSize; + } + + private WebPImageInfo ReadVp8Info() + { + // 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"); + } + + if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8XHeader)) + { + WebPThrowHelper.ThrowImageFormatException("Vp8X is not yet supported"); + } + + if (!(this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header) + || this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader))) + { + WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + } + + bool isLossLess = this.buffer[3] == WebPConstants.LossLessFlag; + + // VP8 data size. + this.currentStream.Read(this.buffer, 0, 3); + this.buffer[3] = 0; + 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]; + int isKeyFrame = tmp & 0x1; + int version = (tmp >> 1) & 0x7; + int showFrame = (tmp >> 4) & 0x1; + + // TODO: Get horizontal and vertical scale + int width = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(7)) & 0x3fff; + int height = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(9)) & 0x3fff; + + return new WebPImageInfo() + { + Width = width, + Height = height, + IsLooseLess = isLossLess, + Version = version, + DataSize = dataSize + }; + } + + private void ReadSimpleLossy(Buffer2D pixels, int width, int height) + where TPixel : struct, IPixel + { + // TODO: implement decoding + } + + private void ReadSimpleLossless(Buffer2D pixels, int width, int height) + where TPixel : struct, IPixel + { + // TODO: implement decoding + } + + private void ReadExtended(Buffer2D pixels, int width, int height) + where TPixel : struct, IPixel + { + // TODO: implement decoding + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs deleted file mode 100644 index 0a26cc6e5..000000000 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - /// - /// Base class for the WebP image decoders. - /// - public abstract class WebPDecoderCoreBase - { - public abstract Image Decode(Stream stream) - where TPixel : struct, IPixel; - } -} diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 4a894b174..9cd8d7916 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -6,7 +6,7 @@ using System; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Detects WebP file headers + /// Detects WebP file headers. /// public sealed class WebPImageFormatDetector : IImageFormatDetector { @@ -26,20 +26,24 @@ namespace SixLabors.ImageSharp.Formats.WebP this.IsWebPFile(header); } + /// + /// Checks, if the header starts with a valid RIFF FourCC. + /// + /// The header bytes. + /// True, if its a valid RIFF FourCC. private bool IsRiffContainer(ReadOnlySpan header) { - return header[0] == 0x52 && // R - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x46; // F + return header.Slice(0, 4).SequenceEqual(WebPConstants.FourCcBytes); } + /// + /// Checks if 'WEBP' is present in the header. + /// + /// The header bytes. + /// True, if its a webp file. private bool IsWebPFile(ReadOnlySpan header) { - return header[8] == 0x57 && // W - header[9] == 0x45 && // E - header[10] == 0x42 && // B - header[11] == 0x50; // P + return header.Slice(8, 4).SequenceEqual(WebPConstants.WebPHeader); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs new file mode 100644 index 000000000..bfa778810 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class WebPImageInfo + { + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets whether this image uses a looseless compression. + /// + public bool IsLooseLess { get; set; } + + public int Version { get; set; } + + public uint DataSize { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs new file mode 100644 index 000000000..fabbc9bc3 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class WebPThrowHelper + { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs new file mode 100644 index 000000000..051b73853 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the webp format. + /// + public sealed class WebPConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); + } + } +}