From 415881dddf932fe21e9095a0252fab41c2ec1776 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Fri, 11 Oct 2019 22:39:01 +0200 Subject: [PATCH 001/359] Initial Commit towards WebP --- src/ImageSharp/Formats/WebP/Readme.md | 4 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 20 +++++++++ src/ImageSharp/Formats/WebP/WebPDecoder.cs | 10 +++++ src/ImageSharp/Formats/WebP/WebPFormat.cs | 33 +++++++++++++++ .../Formats/WebP/WebPImageFormatDetector.cs | 42 +++++++++++++++++++ src/ImageSharp/Formats/WebP/WebPMetadata.cs | 16 +++++++ 6 files changed, 125 insertions(+) create mode 100644 src/ImageSharp/Formats/WebP/Readme.md create mode 100644 src/ImageSharp/Formats/WebP/WebPConstants.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPFormat.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md new file mode 100644 index 000000000..ba4ece71c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Readme.md @@ -0,0 +1,4 @@ += WebP Format +Reference implementation, specification and stuff like that: +https://developers.google.com/speed/webp + diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs new file mode 100644 index 000000000..794fa16cc --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class WebPConstants + { + /// + /// The list of file extensions that equate to WebP. + /// + public static readonly IEnumerable FileExtensions = new[] { "webp" }; + + /// + /// The list of mimetypes that equate to a jpeg. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/webp", }; + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs new file mode 100644 index 000000000..100715328 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + public sealed class WebPDecoder : IImageDecoder + { + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs new file mode 100644 index 000000000..48a595258 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the WebP format + /// + public sealed class WebPFormat : IImageFormat + { + /// + /// Gets the current instance. + /// + public static WebPFormat Instance { get; } = new WebPFormat(); + + /// + public string Name => "WebP"; + + /// + public string DefaultMimeType => "image/webp"; + + /// + public IEnumerable MimeTypes => WebPConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => WebPConstants.FileExtensions; + + /// + public WebPMetadata CreateDefaultFormatMetadata() => new WebPMetadata(); + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs new file mode 100644 index 000000000..208229c4d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -0,0 +1,42 @@ +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Detects WebP file headers + /// + public sealed class WebPImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 12; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? WebPFormat.Instance : null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && + this.IsRiffContainer(header) && + this.IsWebPFile(header); + } + + private bool IsRiffContainer(ReadOnlySpan header) + { + return header[0] == 0x52 && // R + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x46; // F + } + + private bool IsWebPFile(ReadOnlySpan header) + { + return header[8] == 0x57 && // W + header[9] == 0x45 && // E + header[10] == 0x42 && // B + header[11] == 0x50; // P + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs new file mode 100644 index 000000000..95f50fe27 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Provides WebP specific metadata information for the image. + /// + public class WebPMetadata : IDeepCloneable + { + /// + public IDeepCloneable DeepClone() => throw new NotImplementedException(); + } +} From 4e7b722d791e0449cda09a462391b10ac7c502e0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Oct 2019 00:03:10 +0200 Subject: [PATCH 002/359] Add empty implementation of IImageDecoder to test github colaboration --- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 23 ++++++++++++++++--- .../Formats/WebP/WebPImageFormatDetector.cs | 3 +++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 100715328..2675e8610 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -1,10 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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 { + /// + /// Image decoder for generating an image out of a webp stream. + /// public sealed class WebPDecoder : IImageDecoder { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + throw new System.NotImplementedException(); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + { + throw new System.NotImplementedException(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 208229c4d..4a894b174 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; namespace SixLabors.ImageSharp.Formats.WebP From c0f455fe2d11c4626f5ab32cadaad215a3fab6d2 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 12 Oct 2019 14:27:41 +0200 Subject: [PATCH 003/359] introduce decoderCore classes (without implementation) --- src/ImageSharp/Formats/WebP/ExtendedDecoder.cs | 14 ++++++++++++++ .../Formats/WebP/SimpleLosslessDecoder.cs | 14 ++++++++++++++ .../Formats/WebP/SimpleLossyDecoder.cs | 14 ++++++++++++++ src/ImageSharp/Formats/WebP/WebPDecoder.cs | 1 + .../Formats/WebP/WebPDecoderCoreBase.cs | 18 ++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 src/ImageSharp/Formats/WebP/ExtendedDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs diff --git a/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs b/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs new file mode 100644 index 000000000..04a52f12b --- /dev/null +++ b/src/ImageSharp/Formats/WebP/ExtendedDecoder.cs @@ -0,0 +1,14 @@ +// 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/SimpleLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs new file mode 100644 index 000000000..e4cf9d103 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs @@ -0,0 +1,14 @@ +// 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 new file mode 100644 index 000000000..ad3e1d9c9 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs @@ -0,0 +1,14 @@ +// 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/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 2675e8610..8dd11dff2 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Formats.WebP 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(); } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs new file mode 100644 index 000000000..0a26cc6e5 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs @@ -0,0 +1,18 @@ +// 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; + } +} From 06980070a4c6ceaf001bc2ff78ac400c16bbe6be Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Oct 2019 20:35:06 +0200 Subject: [PATCH 004/359] First attempt parsing minimal image info --- src/ImageSharp/Configuration.cs | 6 +- .../Formats/WebP/ExtendedDecoder.cs | 14 -- .../Formats/WebP/IWebPDecoderOptions.cs | 16 ++ src/ImageSharp/Formats/WebP/Readme.md | 8 +- .../Formats/WebP/SimpleLosslessDecoder.cs | 14 -- .../Formats/WebP/SimpleLossyDecoder.cs | 14 -- src/ImageSharp/Formats/WebP/WebPConstants.cs | 65 ++++++++ src/ImageSharp/Formats/WebP/WebPDecoder.cs | 21 ++- .../Formats/WebP/WebPDecoderCore.cs | 148 ++++++++++++++++++ .../Formats/WebP/WebPDecoderCoreBase.cs | 18 --- .../Formats/WebP/WebPImageFormatDetector.cs | 22 +-- .../Formats/WebP/WebPThrowHelper.cs | 20 +++ .../Formats/WebP/WebpConfigurationModule.cs | 18 +++ 13 files changed, 306 insertions(+), 78 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/ExtendedDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/WebP/SimpleLosslessDecoder.cs delete mode 100644 src/ImageSharp/Formats/WebP/SimpleLossyDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPDecoderCore.cs delete mode 100644 src/ImageSharp/Formats/WebP/WebPDecoderCoreBase.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPThrowHelper.cs create mode 100644 src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs 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..fe0761a32 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -0,0 +1,148 @@ +// 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; + + /// + /// 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; + + // 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(buffer); + + // Skip 'WEBP' from the header. + this.currentStream.Skip(4); + + // Read VP8 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 + byte[] c = new byte[11]; + this.currentStream.Read(c, 0, c.Length); + int tmp = (c[2] << 16) | (c[1] << 8) | c[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(c.AsSpan(7)) & 0x3fff; + int height = BinaryPrimitives.ReadInt16LittleEndian(c.AsSpan(9)) & 0x3fff; + + // TODO: DO something with the data + + // var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); + + // TODO: there can be optional chunks after that, like EXIF. + + throw new NotImplementedException(); + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) + { + throw new NotImplementedException(); + + //this.ReadImageHeaders(stream, out _, out _); + //return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); + } + + 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/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()); + } + } +} From 52fd45bf07a7e0992ab787d7c597c6844d5619fc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Oct 2019 21:08:12 +0200 Subject: [PATCH 005/359] Identify works at least for reading the image dimensions so far --- .../Formats/WebP/WebPDecoderCore.cs | 93 +++++++++++++------ src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 27 ++++++ 2 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPImageInfo.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index fe0761a32..2cc8f592c 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private Stream currentStream; + /// + /// The metadata. + /// + private ImageMetadata metadata; + /// /// Initializes a new instance of the class. /// @@ -61,17 +66,60 @@ namespace SixLabors.ImageSharp.Formats.WebP 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(buffer); + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); // Skip 'WEBP' from the header. this.currentStream.Skip(4); - // Read VP8 header. + return chunkSize; + } + + private WebPImageInfo ReadVp8Info() + { + // Read VP8 chunk header. this.currentStream.Read(this.buffer, 0, 4); if (this.buffer.AsSpan().SequenceEqual(WebPConstants.AlphaHeader)) { @@ -83,7 +131,8 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Vp8X is not yet supported"); } - if (!(this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header) || this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader))) + if (!(this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header) + || this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader))) { WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); } @@ -96,35 +145,25 @@ namespace SixLabors.ImageSharp.Formats.WebP uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); // https://tools.ietf.org/html/rfc6386#page-30 - byte[] c = new byte[11]; - this.currentStream.Read(c, 0, c.Length); - int tmp = (c[2] << 16) | (c[1] << 8) | c[0]; + 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(c.AsSpan(7)) & 0x3fff; - int height = BinaryPrimitives.ReadInt16LittleEndian(c.AsSpan(9)) & 0x3fff; - - // TODO: DO something with the data - - // var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); - - // TODO: there can be optional chunks after that, like EXIF. - - throw new NotImplementedException(); - } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) - { - throw new NotImplementedException(); + // TODO: Get horizontal and vertical scale + int width = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(7)) & 0x3fff; + int height = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(9)) & 0x3fff; - //this.ReadImageHeaders(stream, out _, out _); - //return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); + return new WebPImageInfo() + { + Width = width, + Height = height, + IsLooseLess = isLossLess, + Version = version, + DataSize = dataSize + }; } private void ReadSimpleLossy(Buffer2D pixels, int width, int height) 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; } + } +} From a9ff1fb6ff58c9a0ca197cefde63d42b0bc2ede3 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Sat, 12 Oct 2019 22:31:38 +0200 Subject: [PATCH 006/359] WebP test input files added, test skeletons added (copied from Bmp tests) --- ImageSharp.sln | 33 +++++ .../Formats/WebP/WebPDecoderTests.cs | 106 ++++++++++++++++ .../Formats/WebP/WebPEncoderTests.cs | 116 ++++++++++++++++++ .../Formats/WebP/WebPFileHeaderTests.cs | 21 ++++ .../Formats/WebP/WebPMetaDataTests.cs | 50 ++++++++ tests/Images/External | 2 +- .../Images/Input/WebP/Lossless/1_webp_ll.webp | 3 + .../Images/Input/WebP/Lossless/2_webp_ll.webp | 3 + .../Images/Input/WebP/Lossless/3_webp_ll.webp | 3 + .../Images/Input/WebP/Lossless/4_webp_ll.webp | 3 + .../Images/Input/WebP/Lossless/5_webp_ll.webp | 3 + tests/Images/Input/WebP/Lossy/1.webp | 3 + tests/Images/Input/WebP/Lossy/2.webp | 3 + tests/Images/Input/WebP/Lossy/3.webp | 3 + tests/Images/Input/WebP/Lossy/4.webp | 3 + tests/Images/Input/WebP/Lossy/5.webp | 3 + .../Input/WebP/Lossy/Alpha/1_webp_a.webp | 3 + .../Input/WebP/Lossy/Alpha/2_webp_a.webp | 3 + .../Input/WebP/Lossy/Alpha/3_webp_a.webp | 3 + .../Input/WebP/Lossy/Alpha/4_webp_a.webp | 3 + .../Input/WebP/Lossy/Alpha/5_webp_a.webp | 3 + 21 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs create mode 100644 tests/Images/Input/WebP/Lossless/1_webp_ll.webp create mode 100644 tests/Images/Input/WebP/Lossless/2_webp_ll.webp create mode 100644 tests/Images/Input/WebP/Lossless/3_webp_ll.webp create mode 100644 tests/Images/Input/WebP/Lossless/4_webp_ll.webp create mode 100644 tests/Images/Input/WebP/Lossless/5_webp_ll.webp create mode 100644 tests/Images/Input/WebP/Lossy/1.webp create mode 100644 tests/Images/Input/WebP/Lossy/2.webp create mode 100644 tests/Images/Input/WebP/Lossy/3.webp create mode 100644 tests/Images/Input/WebP/Lossy/4.webp create mode 100644 tests/Images/Input/WebP/Lossy/5.webp create mode 100644 tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp create mode 100644 tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp create mode 100644 tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp create mode 100644 tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp create mode 100644 tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp diff --git a/ImageSharp.sln b/ImageSharp.sln index d4a0419ee..4ffb55399 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -356,6 +356,35 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{7BEE9435-1833-4686-8B36-C4EE2F13D908}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lossy", "Lossy", "{41D10A70-59CE-4634-9145-CE9B60050371}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\WebP\Lossy\1.webp = tests\Images\Input\WebP\Lossy\1.webp + tests\Images\Input\WebP\Lossy\2.webp = tests\Images\Input\WebP\Lossy\2.webp + tests\Images\Input\WebP\Lossy\3.webp = tests\Images\Input\WebP\Lossy\3.webp + tests\Images\Input\WebP\Lossy\4.webp = tests\Images\Input\WebP\Lossy\4.webp + tests\Images\Input\WebP\Lossy\5.webp = tests\Images\Input\WebP\Lossy\5.webp + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lossless", "Lossless", "{F19E6F18-4102-4134-8A96-FEC2AB67F794}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\WebP\Lossless\1_webp_ll.webp = tests\Images\Input\WebP\Lossless\1_webp_ll.webp + tests\Images\Input\WebP\Lossless\2_webp_ll.webp = tests\Images\Input\WebP\Lossless\2_webp_ll.webp + tests\Images\Input\WebP\Lossless\3_webp_ll.webp = tests\Images\Input\WebP\Lossless\3_webp_ll.webp + tests\Images\Input\WebP\Lossless\4_webp_ll.webp = tests\Images\Input\WebP\Lossless\4_webp_ll.webp + tests\Images\Input\WebP\Lossless\5_webp_ll.webp = tests\Images\Input\WebP\Lossless\5_webp_ll.webp + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Alpha", "Alpha", "{E6B81E19-B27F-4656-9169-4B8415D064A2}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\WebP\Lossy\Alpha\1_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\1_webp_a.webp + tests\Images\Input\WebP\Lossy\Alpha\2_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\2_webp_a.webp + tests\Images\Input\WebP\Lossy\Alpha\3_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\3_webp_a.webp + tests\Images\Input\WebP\Lossy\Alpha\4_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\4_webp_a.webp + tests\Images\Input\WebP\Lossy\Alpha\5_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\5_webp_a.webp + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -453,6 +482,10 @@ Global {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {7BEE9435-1833-4686-8B36-C4EE2F13D908} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {41D10A70-59CE-4634-9145-CE9B60050371} = {7BEE9435-1833-4686-8B36-C4EE2F13D908} + {F19E6F18-4102-4134-8A96-FEC2AB67F794} = {7BEE9435-1833-4686-8B36-C4EE2F13D908} + {E6B81E19-B27F-4656-9169-4B8415D064A2} = {41D10A70-59CE-4634-9145-CE9B60050371} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs new file mode 100644 index 000000000..5de15316b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + using SixLabors.ImageSharp.Metadata; + using static TestImages.Bmp; + + public class WebPDecoderTests + { + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + + public static readonly string[] MiscBmpFiles = Miscellaneous; + + public static readonly string[] BitfieldsBmpFiles = BitFields; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; + + [Theory] + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } + + [Theory] + [WithFile(Bit16Inverted, PixelTypes.Rgba32)] + [WithFile(Bit8Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [InlineData(Bit32Rgb, 32)] + [InlineData(Bit32Rgba, 32)] + [InlineData(Car, 24)] + [InlineData(F, 24)] + [InlineData(NegHeight, 24)] + [InlineData(Bit16, 16)] + [InlineData(Bit16Inverted, 16)] + [InlineData(Bit8, 8)] + [InlineData(Bit8Inverted, 8)] + [InlineData(Bit4, 4)] + [InlineData(Bit1, 1)] + [InlineData(Bit1Pal1, 1)] + public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new BmpDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs new file mode 100644 index 000000000..d85141622 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + using static TestImages.Bmp; + + public class WebPEncoderTests + { + public static readonly TheoryData BitsPerPixel = + new TheoryData + { + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; + + public static readonly TheoryData BmpBitsPerPixelFiles = + new TheoryData + { + { Car, BmpBitsPerPixel.Pixel24 }, + { Bit32Rgb, BmpBitsPerPixel.Pixel32 } + }; + + public WebPEncoderTests(ITestOutputHelper output) => this.Output = output; + + private ITestOutputHelper Output { get; } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new BmpEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] + public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + public void Encode_32Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + // if supportTransparency is false, a v3 bitmap header will be written + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + private static void TestBmpEncoderCore( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel, + bool supportTransparency = true, + ImageComparer customComparer = null) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel != BmpBitsPerPixel.Pixel32) + { + image.Mutate(c => c.MakeOpaque()); + } + + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs new file mode 100644 index 000000000..bcc177d34 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs @@ -0,0 +1,21 @@ +using System; +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + public class WebPFileHeaderTests + { + [Fact] + public void TestWrite() + { + var header = new BmpFileHeader(1, 2, 3, 4); + + var buffer = new byte[14]; + + header.WriteTo(buffer); + + Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs new file mode 100644 index 000000000..b9ccef315 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + using static TestImages.Bmp; + + public class WebPMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (BmpMetadata)meta.DeepClone(); + + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + } + + [Theory] + [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] + [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] + [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] + [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] + [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] + [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] + [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] + [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] + public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + BmpMetadata bitmapMetaData = imageInfo.Metadata.GetFormatMetadata(BmpFormat.Instance); + Assert.NotNull(bitmapMetaData); + Assert.Equal(expectedInfoHeaderType, bitmapMetaData.InfoHeaderType); + } + } + } +} diff --git a/tests/Images/External b/tests/Images/External index 1d3d4e365..99a2bc523 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 1d3d4e3652dc95bd8bd420346bfe0f189addc587 +Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57 diff --git a/tests/Images/Input/WebP/Lossless/1_webp_ll.webp b/tests/Images/Input/WebP/Lossless/1_webp_ll.webp new file mode 100644 index 000000000..8f40d3913 --- /dev/null +++ b/tests/Images/Input/WebP/Lossless/1_webp_ll.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:694f9011eddb7d0f08fc76ddad6b2129f2390d1e44c41a9ed9de0f08bb14c43b +size 81977 diff --git a/tests/Images/Input/WebP/Lossless/2_webp_ll.webp b/tests/Images/Input/WebP/Lossless/2_webp_ll.webp new file mode 100644 index 000000000..42e1f839f --- /dev/null +++ b/tests/Images/Input/WebP/Lossless/2_webp_ll.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee83dd1211ba339e9a61a9af5e24a9fa5b8168ea448eacf4743bdcc36f58452b +size 27669 diff --git a/tests/Images/Input/WebP/Lossless/3_webp_ll.webp b/tests/Images/Input/WebP/Lossless/3_webp_ll.webp new file mode 100644 index 000000000..df117cc33 --- /dev/null +++ b/tests/Images/Input/WebP/Lossless/3_webp_ll.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c96ed502862c0adc1f4221da1bd31d0921853f62eebcbbb89fec4e862ecb1de +size 152634 diff --git a/tests/Images/Input/WebP/Lossless/4_webp_ll.webp b/tests/Images/Input/WebP/Lossless/4_webp_ll.webp new file mode 100644 index 000000000..411f5b273 --- /dev/null +++ b/tests/Images/Input/WebP/Lossless/4_webp_ll.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eff718866d464cd27161e171bb83ad73a05befae0724c2c75a772a7e21ac3b9 +size 34096 diff --git a/tests/Images/Input/WebP/Lossless/5_webp_ll.webp b/tests/Images/Input/WebP/Lossless/5_webp_ll.webp new file mode 100644 index 000000000..c2bbefa8e --- /dev/null +++ b/tests/Images/Input/WebP/Lossless/5_webp_ll.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0da93a44371abecdc68ad082c73ec633c3cd02d547fe28992403086a9e110946 +size 99425 diff --git a/tests/Images/Input/WebP/Lossy/1.webp b/tests/Images/Input/WebP/Lossy/1.webp new file mode 100644 index 000000000..447e38de2 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8b4596c10a23135931b4e63957eda41e9b3874811d9b724d890b589d66ea12f +size 30319 diff --git a/tests/Images/Input/WebP/Lossy/2.webp b/tests/Images/Input/WebP/Lossy/2.webp new file mode 100644 index 000000000..39fc0b63c --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ac427ad555ba5898015e8b60396e1852bfc1e9cd7ae5da6dd42c85ea3e169f7 +size 60600 diff --git a/tests/Images/Input/WebP/Lossy/3.webp b/tests/Images/Input/WebP/Lossy/3.webp new file mode 100644 index 000000000..acc899195 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebf4dc48653bd5f3c5b929ae3a411a0e440eb316aa50c80b1591d98db865bd5a +size 203135 diff --git a/tests/Images/Input/WebP/Lossy/4.webp b/tests/Images/Input/WebP/Lossy/4.webp new file mode 100644 index 000000000..62cddcab6 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f354f8954071f0b979a40866de95f2113a6ce54f4fe483c7f2b269c913f0df3 +size 176969 diff --git a/tests/Images/Input/WebP/Lossy/5.webp b/tests/Images/Input/WebP/Lossy/5.webp new file mode 100644 index 000000000..e991f3153 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/5.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7558b0f5da141787fcc41857743f8bc9417c844e2a19aeaa5fee25f1433abe4e +size 82697 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp new file mode 100644 index 000000000..00cca4a56 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e89d207eff1f1499acd6a5b8dc4a326ecdf3d303e4a7e6fc65c268035326a75f +size 18840 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp new file mode 100644 index 000000000..76ba0b9da --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c7f6772cdaf74fe5dac79d8acd7da48cc3700159f41e9dd2ebae8dd7c81b548 +size 14412 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp new file mode 100644 index 000000000..6f489e6fe --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c648a83fa93378259b8c71db58b4f47e5b4bf6f980b7d418346c4a2cf9ad6664 +size 53817 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp new file mode 100644 index 000000000..88624600a --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2b4164bf2facd2b212e5eb15eb3066254c785b35d14d79341d178d8692e6e28 +size 19440 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp new file mode 100644 index 000000000..278b91091 --- /dev/null +++ b/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5988b9e71686b385d76c5fba81d09642d1f08a079cabe81d653b025a40abe4f1 +size 58712 From 4599713c31cbe6184f2727246eb6132f79af8f87 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Mon, 14 Oct 2019 20:18:28 +0200 Subject: [PATCH 007/359] fix typo --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 4 ++-- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 2cc8f592c..d4d7503c5 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var image = new Image(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (imageInfo.IsLooseLess) + if (imageInfo.IsLossLess) { ReadSimpleLossless(pixels, image.Width, image.Height); } @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { Width = width, Height = height, - IsLooseLess = isLossLess, + IsLossLess = isLossLess, Version = version, DataSize = dataSize }; diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index bfa778810..1cf98865c 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -16,9 +16,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public int Height { get; set; } /// - /// Gets or sets whether this image uses a looseless compression. + /// Gets or sets whether this image uses a lossless compression. /// - public bool IsLooseLess { get; set; } + public bool IsLossLess { get; set; } public int Version { get; set; } From 9fb13a25881230ff8a0c27b0bec6cb29a9c35ba5 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Mon, 14 Oct 2019 22:46:39 +0200 Subject: [PATCH 008/359] WebP tests WIP --- .../Formats/WebP/WebPDecoderTests.cs | 12 ++--- .../Formats/WebP/WebPMetaDataTests.cs | 24 ++++----- tests/ImageSharp.Tests/TestImages.cs | 49 +++++++++++++++++++ 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 5de15316b..356fa534c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -1,19 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; -using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.WebP { + using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Metadata; using static TestImages.Bmp; @@ -38,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(new WebPDecoder())) { image.DebugSave(provider); if (TestEnvironment.IsWindows) @@ -54,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(new WebPDecoder())) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -92,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - var decoder = new BmpDecoder(); + var decoder = new WebPDecoder(); using (Image image = decoder.Decode(Configuration.Default, stream)) { ImageMetadata meta = image.Metadata; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index b9ccef315..d1b130102 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.WebP; using Xunit; // ReSharper disable InconsistentNaming @@ -17,23 +18,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Fact] public void CloneIsDeep() { - var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; - var clone = (BmpMetadata)meta.DeepClone(); + /* TODO: + var meta = new WebPMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (WebPMetadata)meta.DeepClone(); clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; - Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel));*/ } [Theory] - [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] - [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] - [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] - [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] - [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] - [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] - [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] - [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] + [InlineData(TestImages.WebP.Lossy.SampleWebpOne, BmpInfoHeaderType.WinVersion2)] public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) { var testFile = TestFile.Create(imagePath); @@ -41,9 +36,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); - BmpMetadata bitmapMetaData = imageInfo.Metadata.GetFormatMetadata(BmpFormat.Instance); - Assert.NotNull(bitmapMetaData); - Assert.Equal(expectedInfoHeaderType, bitmapMetaData.InfoHeaderType); + WebPMetadata webpMetaData = imageInfo.Metadata.GetFormatMetadata(WebPFormat.Instance); + Assert.NotNull(webpMetaData); + //TODO: + //Assert.Equal(expectedInfoHeaderType, webpMetaData.InfoHeaderType); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 146f2efcd..1a11c81b5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -365,5 +365,54 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } + + public static class WebP + { + public static class Lossless + { + public const string SampleWebpOne = "WebP/Lossless/1_webp_ll.webp"; + public const string SampleWebpTwo = "WebP/Lossless/2_webp_ll.webp"; + public const string SampleWebpThree = "WebP/Lossless/3_webp_ll.webp"; + public const string SampleWebpFour = "WebP/Lossless/4_webp_ll.webp"; + public const string SampleWebpFive = "WebP/Lossless/5_webp_ll.webp"; + } + + public static class Lossy + { + public const string SampleWebpOne = "WebP/Lossy/1.webp"; + public const string SampleWebpTwo = "WebP/Lossy/2.webp"; + public const string SampleWebpThree = "WebP/Lossy/3.webp"; + public const string SampleWebpFour = "WebP/Lossy/4.webp"; + public const string SampleWebpFive = "WebP/Lossy/5.webp"; + + public static class Alpha + { + public const string SampleWebpOne = "WebP/Lossy/Alpha/1_webp_a.webp"; + public const string SampleWebpTwo = "WebP/Lossy/Alpha/2_webp_a.webp"; + public const string SampleWebpThree = "WebP/Lossy/Alpha/3_webp_a.webp"; + public const string SampleWebpFour = "WebP/Lossy/Alpha/4_webp_a.webp"; + public const string SampleWebpFive = "WebP/Lossy/Alpha/5_webp_a.webp"; + } + } + + public static readonly string[] All = + { + Lossless.SampleWebpOne, + Lossless.SampleWebpTwo, + Lossless.SampleWebpThree, + Lossless.SampleWebpFour, + Lossless.SampleWebpFive, + Lossy.SampleWebpOne, + Lossy.SampleWebpTwo, + Lossy.SampleWebpThree, + Lossy.SampleWebpFour, + Lossy.SampleWebpFive, + Lossy.Alpha.SampleWebpOne, + Lossy.Alpha.SampleWebpTwo, + Lossy.Alpha.SampleWebpThree, + Lossy.Alpha.SampleWebpFour, + Lossy.Alpha.SampleWebpFive + }; + } } } From 0392d0ea87c30419068851f8c7637c95c32f727b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 16 Oct 2019 21:16:23 +0200 Subject: [PATCH 009/359] Add reading of VP8X header --- src/ImageSharp/Formats/WebP/Readme.md | 2 +- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 31 +++++++++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 +- .../Formats/WebP/WebPDecoderCore.cs | 65 +++++++++++++++---- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 -- 5 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8HeaderType.cs diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md index 41409f136..664df5043 100644 --- a/src/ImageSharp/Formats/WebP/Readme.md +++ b/src/ImageSharp/Formats/WebP/Readme.md @@ -3,6 +3,6 @@ Reference implementation, specification and stuff like that: - [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 Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/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/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs new file mode 100644 index 000000000..18e311da3 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Enum for the different VP8 chunk header types. + /// + public enum Vp8HeaderType + { + /// + /// Invalid VP8 header. + /// + Invalid = 0, + + /// + /// A VP8 header. + /// + Vp8 = 1, + + /// + /// VP8 header, signaling the use of VP8L lossless format. + /// + Vp8L = 2, + + /// + /// Header for a extended-VP8 chunk. + /// + Vp8X = 3, + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index e1f0fd23d..bdc026937 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x56, // V 0x50, // P 0x38, // 8 - 0x88, // X + 0x58, // X }; public static readonly byte LossLessFlag = 0x4C; // L diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index d4d7503c5..5c5c5d3ac 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -108,6 +109,8 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Skip(4); // Read Chunk size. + // The size of the file in bytes starting at offset 8. + // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. this.currentStream.Read(this.buffer, 0, 4); uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); @@ -126,19 +129,57 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Alpha channel is not yet supported"); } - if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8XHeader)) + var vp8HeaderType = Vp8HeaderType.Invalid; + if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header)) { - WebPThrowHelper.ThrowImageFormatException("Vp8X is not yet supported"); + vp8HeaderType = Vp8HeaderType.Vp8; } - - if (!(this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header) - || this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader))) + else if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader)) + { + vp8HeaderType = Vp8HeaderType.Vp8L; + } + else if (this.buffer.SequenceEqual(WebPConstants.Vp8XHeader)) + { + vp8HeaderType = Vp8HeaderType.Vp8X; + } + else { WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); } - bool isLossLess = this.buffer[3] == WebPConstants.LossLessFlag; + return vp8HeaderType == Vp8HeaderType.Vp8X ? this.ReadVp8XHeader() : this.ReadVp8Header(vp8HeaderType); + } + private WebPImageInfo ReadVp8XHeader() + { + this.currentStream.Read(this.buffer, 0, 4); + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + byte imageFeatures = (byte)this.currentStream.ReadByte(); + + // 3 reserved bytes should follow which are supposed to be zero. + this.currentStream.Read(this.buffer, 0, 3); + + // 3 bytes for the width. + this.currentStream.Read(this.buffer, 0, 3); + this.buffer[3] = 0; + int width = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; + + // 3 bytes for the height. + this.currentStream.Read(this.buffer, 0, 3); + this.buffer[3] = 0; + int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; + + return new WebPImageInfo() + { + Width = width, + Height = height, + IsLossLess = false, // note: this is maybe incorrect here + }; + } + + private WebPImageInfo ReadVp8Header(Vp8HeaderType vp8HeaderType) + { // VP8 data size. this.currentStream.Read(this.buffer, 0, 3); this.buffer[3] = 0; @@ -157,13 +198,11 @@ namespace SixLabors.ImageSharp.Formats.WebP int height = BinaryPrimitives.ReadInt16LittleEndian(imageInfo.AsSpan(9)) & 0x3fff; return new WebPImageInfo() - { - Width = width, - Height = height, - IsLossLess = isLossLess, - Version = version, - DataSize = dataSize - }; + { + Width = width, + Height = height, + IsLossLess = vp8HeaderType == Vp8HeaderType.Vp8L, + }; } private void ReadSimpleLossy(Buffer2D pixels, int width, int height) diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 1cf98865c..065e5dd7e 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -19,9 +19,5 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets whether this image uses a lossless compression. /// public bool IsLossLess { get; set; } - - public int Version { get; set; } - - public uint DataSize { get; set; } } } From b198ddb45308cbae1c8ceb5c710a737ab0d30828 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Oct 2019 21:39:01 +0200 Subject: [PATCH 010/359] 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; } } } From 80cf5d83ef5e394d91cd1049f1465f55471b7c72 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Thu, 17 Oct 2019 22:38:47 +0200 Subject: [PATCH 011/359] WebP Identify test added, webp sample files copied from official samples repository --- src/ImageSharp/Formats/WebP/Readme.md | 1 + .../Formats/WebP/WebPDecoderTests.cs | 73 +-- tests/ImageSharp.Tests/TestImages.cs | 23 +- .../Images/Input/WebP/Lossless/1_webp_ll.webp | 3 - .../Images/Input/WebP/Lossless/2_webp_ll.webp | 3 - .../Images/Input/WebP/Lossless/3_webp_ll.webp | 3 - .../Images/Input/WebP/Lossless/4_webp_ll.webp | 3 - .../Images/Input/WebP/Lossless/5_webp_ll.webp | 3 - tests/Images/Input/WebP/Lossy/1.webp | 3 - tests/Images/Input/WebP/Lossy/2.webp | 3 - tests/Images/Input/WebP/Lossy/3.webp | 3 - tests/Images/Input/WebP/Lossy/4.webp | 3 - tests/Images/Input/WebP/Lossy/5.webp | 3 - .../Input/WebP/Lossy/Alpha/1_webp_a.webp | 3 - .../Input/WebP/Lossy/Alpha/2_webp_a.webp | 3 - .../Input/WebP/Lossy/Alpha/3_webp_a.webp | 3 - .../Input/WebP/Lossy/Alpha/4_webp_a.webp | 3 - .../Input/WebP/Lossy/Alpha/5_webp_a.webp | 3 - .../Images/Input/WebP/alpha_color_cache.webp | 3 + .../Input/WebP/alpha_filter_0_method_0.webp | 3 + .../Input/WebP/alpha_filter_0_method_1.webp | 3 + tests/Images/Input/WebP/alpha_filter_1.webp | 3 + .../Input/WebP/alpha_filter_1_method_0.webp | 3 + .../Input/WebP/alpha_filter_1_method_1.webp | 3 + tests/Images/Input/WebP/alpha_filter_2.webp | 3 + .../Input/WebP/alpha_filter_2_method_0.webp | 3 + .../Input/WebP/alpha_filter_2_method_1.webp | 3 + tests/Images/Input/WebP/alpha_filter_3.webp | 3 + .../Input/WebP/alpha_filter_3_method_0.webp | 3 + .../Input/WebP/alpha_filter_3_method_1.webp | 3 + .../Input/WebP/alpha_no_compression.webp | 3 + .../Images/Input/WebP/bad_palette_index.webp | 3 + .../Images/Input/WebP/big_endian_bug_393.webp | 3 + tests/Images/Input/WebP/bryce.webp | 3 + tests/Images/Input/WebP/bug3.webp | 3 + .../Input/WebP/color_cache_bits_11.webp | 3 + tests/Images/Input/WebP/grid.bmp | 3 + tests/Images/Input/WebP/grid.pam | Bin 0 -> 1091 bytes tests/Images/Input/WebP/grid.pgm | 4 + tests/Images/Input/WebP/grid.png | 3 + tests/Images/Input/WebP/grid.ppm | Bin 0 -> 781 bytes tests/Images/Input/WebP/grid.tiff | 3 + tests/Images/Input/WebP/libwebp_tests.md5 | 470 ++++++++++++++++++ tests/Images/Input/WebP/lossless1.webp | 3 + tests/Images/Input/WebP/lossless2.webp | 3 + tests/Images/Input/WebP/lossless3.webp | 3 + tests/Images/Input/WebP/lossless4.webp | 3 + .../Input/WebP/lossless_big_random_alpha.webp | 3 + .../Input/WebP/lossless_color_transform.bmp | 3 + .../Input/WebP/lossless_color_transform.pam | Bin 0 -> 1048645 bytes .../Input/WebP/lossless_color_transform.pgm | 4 + .../Input/WebP/lossless_color_transform.ppm | Bin 0 -> 786447 bytes .../Input/WebP/lossless_color_transform.tiff | 3 + .../Input/WebP/lossless_color_transform.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_0.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_1.webp | 3 + .../Images/Input/WebP/lossless_vec_1_10.webp | 3 + .../Images/Input/WebP/lossless_vec_1_11.webp | 3 + .../Images/Input/WebP/lossless_vec_1_12.webp | 3 + .../Images/Input/WebP/lossless_vec_1_13.webp | 3 + .../Images/Input/WebP/lossless_vec_1_14.webp | 3 + .../Images/Input/WebP/lossless_vec_1_15.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_2.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_3.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_4.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_5.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_6.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_7.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_8.webp | 3 + tests/Images/Input/WebP/lossless_vec_1_9.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_0.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_1.webp | 3 + .../Images/Input/WebP/lossless_vec_2_10.webp | 3 + .../Images/Input/WebP/lossless_vec_2_11.webp | 3 + .../Images/Input/WebP/lossless_vec_2_12.webp | 3 + .../Images/Input/WebP/lossless_vec_2_13.webp | 3 + .../Images/Input/WebP/lossless_vec_2_14.webp | 3 + .../Images/Input/WebP/lossless_vec_2_15.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_2.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_3.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_4.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_5.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_6.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_7.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_8.webp | 3 + tests/Images/Input/WebP/lossless_vec_2_9.webp | 3 + tests/Images/Input/WebP/lossless_vec_list.txt | 44 ++ tests/Images/Input/WebP/lossy_alpha1.webp | 3 + tests/Images/Input/WebP/lossy_alpha2.webp | 3 + tests/Images/Input/WebP/lossy_alpha3.webp | 3 + tests/Images/Input/WebP/lossy_alpha4.webp | 3 + .../WebP/lossy_extreme_probabilities.webp | 3 + tests/Images/Input/WebP/lossy_q0_f100.webp | 3 + tests/Images/Input/WebP/near_lossless_75.webp | 3 + tests/Images/Input/WebP/peak.bmp | 3 + tests/Images/Input/WebP/peak.pam | 8 + tests/Images/Input/WebP/peak.pgm | 4 + tests/Images/Input/WebP/peak.png | 3 + tests/Images/Input/WebP/peak.ppm | 4 + tests/Images/Input/WebP/peak.tiff | 3 + tests/Images/Input/WebP/segment01.webp | 3 + tests/Images/Input/WebP/segment02.webp | 3 + tests/Images/Input/WebP/segment03.webp | 3 + tests/Images/Input/WebP/small_13x1.webp | 3 + tests/Images/Input/WebP/small_1x1.webp | 3 + tests/Images/Input/WebP/small_1x13.webp | 3 + tests/Images/Input/WebP/small_31x13.webp | 3 + tests/Images/Input/WebP/test-nostrong.webp | 3 + tests/Images/Input/WebP/test.webp | 3 + tests/Images/Input/WebP/test_cwebp.sh | 97 ++++ tests/Images/Input/WebP/test_dwebp.sh | 101 ++++ tests/Images/Input/WebP/test_lossless.sh | 82 +++ tests/Images/Input/WebP/very_short.webp | 3 + .../Input/WebP/vp80-00-comprehensive-001.webp | 3 + .../Input/WebP/vp80-00-comprehensive-002.webp | 3 + .../Input/WebP/vp80-00-comprehensive-003.webp | 3 + .../Input/WebP/vp80-00-comprehensive-004.webp | 3 + .../Input/WebP/vp80-00-comprehensive-005.webp | 3 + .../Input/WebP/vp80-00-comprehensive-006.webp | 3 + .../Input/WebP/vp80-00-comprehensive-007.webp | 3 + .../Input/WebP/vp80-00-comprehensive-008.webp | 3 + .../Input/WebP/vp80-00-comprehensive-009.webp | 3 + .../Input/WebP/vp80-00-comprehensive-010.webp | 3 + .../Input/WebP/vp80-00-comprehensive-011.webp | 3 + .../Input/WebP/vp80-00-comprehensive-012.webp | 3 + .../Input/WebP/vp80-00-comprehensive-013.webp | 3 + .../Input/WebP/vp80-00-comprehensive-014.webp | 3 + .../Input/WebP/vp80-00-comprehensive-015.webp | 3 + .../Input/WebP/vp80-00-comprehensive-016.webp | 3 + .../Input/WebP/vp80-00-comprehensive-017.webp | 3 + .../Images/Input/WebP/vp80-01-intra-1400.webp | 3 + .../Images/Input/WebP/vp80-01-intra-1411.webp | 3 + .../Images/Input/WebP/vp80-01-intra-1416.webp | 3 + .../Images/Input/WebP/vp80-01-intra-1417.webp | 3 + .../Images/Input/WebP/vp80-02-inter-1402.webp | 3 + .../Images/Input/WebP/vp80-02-inter-1412.webp | 3 + .../Images/Input/WebP/vp80-02-inter-1418.webp | 3 + .../Images/Input/WebP/vp80-02-inter-1424.webp | 3 + .../Input/WebP/vp80-03-segmentation-1401.webp | 3 + .../Input/WebP/vp80-03-segmentation-1403.webp | 3 + .../Input/WebP/vp80-03-segmentation-1407.webp | 3 + .../Input/WebP/vp80-03-segmentation-1408.webp | 3 + .../Input/WebP/vp80-03-segmentation-1409.webp | 3 + .../Input/WebP/vp80-03-segmentation-1410.webp | 3 + .../Input/WebP/vp80-03-segmentation-1413.webp | 3 + .../Input/WebP/vp80-03-segmentation-1414.webp | 3 + .../Input/WebP/vp80-03-segmentation-1415.webp | 3 + .../Input/WebP/vp80-03-segmentation-1425.webp | 3 + .../Input/WebP/vp80-03-segmentation-1426.webp | 3 + .../Input/WebP/vp80-03-segmentation-1427.webp | 3 + .../Input/WebP/vp80-03-segmentation-1432.webp | 3 + .../Input/WebP/vp80-03-segmentation-1435.webp | 3 + .../Input/WebP/vp80-03-segmentation-1436.webp | 3 + .../Input/WebP/vp80-03-segmentation-1437.webp | 3 + .../Input/WebP/vp80-03-segmentation-1441.webp | 3 + .../Input/WebP/vp80-03-segmentation-1442.webp | 3 + .../Input/WebP/vp80-04-partitions-1404.webp | 3 + .../Input/WebP/vp80-04-partitions-1405.webp | 3 + .../Input/WebP/vp80-04-partitions-1406.webp | 3 + .../Input/WebP/vp80-05-sharpness-1428.webp | 3 + .../Input/WebP/vp80-05-sharpness-1429.webp | 3 + .../Input/WebP/vp80-05-sharpness-1430.webp | 3 + .../Input/WebP/vp80-05-sharpness-1431.webp | 3 + .../Input/WebP/vp80-05-sharpness-1433.webp | 3 + .../Input/WebP/vp80-05-sharpness-1434.webp | 3 + .../Input/WebP/vp80-05-sharpness-1438.webp | 3 + .../Input/WebP/vp80-05-sharpness-1439.webp | 3 + .../Input/WebP/vp80-05-sharpness-1440.webp | 3 + .../Input/WebP/vp80-05-sharpness-1443.webp | 3 + 169 files changed, 1238 insertions(+), 133 deletions(-) delete mode 100644 tests/Images/Input/WebP/Lossless/1_webp_ll.webp delete mode 100644 tests/Images/Input/WebP/Lossless/2_webp_ll.webp delete mode 100644 tests/Images/Input/WebP/Lossless/3_webp_ll.webp delete mode 100644 tests/Images/Input/WebP/Lossless/4_webp_ll.webp delete mode 100644 tests/Images/Input/WebP/Lossless/5_webp_ll.webp delete mode 100644 tests/Images/Input/WebP/Lossy/1.webp delete mode 100644 tests/Images/Input/WebP/Lossy/2.webp delete mode 100644 tests/Images/Input/WebP/Lossy/3.webp delete mode 100644 tests/Images/Input/WebP/Lossy/4.webp delete mode 100644 tests/Images/Input/WebP/Lossy/5.webp delete mode 100644 tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp delete mode 100644 tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp delete mode 100644 tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp delete mode 100644 tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp delete mode 100644 tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp create mode 100644 tests/Images/Input/WebP/alpha_color_cache.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_0_method_0.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_0_method_1.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_1.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_1_method_0.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_1_method_1.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_2.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_2_method_0.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_2_method_1.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_3.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_3_method_0.webp create mode 100644 tests/Images/Input/WebP/alpha_filter_3_method_1.webp create mode 100644 tests/Images/Input/WebP/alpha_no_compression.webp create mode 100644 tests/Images/Input/WebP/bad_palette_index.webp create mode 100644 tests/Images/Input/WebP/big_endian_bug_393.webp create mode 100644 tests/Images/Input/WebP/bryce.webp create mode 100644 tests/Images/Input/WebP/bug3.webp create mode 100644 tests/Images/Input/WebP/color_cache_bits_11.webp create mode 100644 tests/Images/Input/WebP/grid.bmp create mode 100644 tests/Images/Input/WebP/grid.pam create mode 100644 tests/Images/Input/WebP/grid.pgm create mode 100644 tests/Images/Input/WebP/grid.png create mode 100644 tests/Images/Input/WebP/grid.ppm create mode 100644 tests/Images/Input/WebP/grid.tiff create mode 100644 tests/Images/Input/WebP/libwebp_tests.md5 create mode 100644 tests/Images/Input/WebP/lossless1.webp create mode 100644 tests/Images/Input/WebP/lossless2.webp create mode 100644 tests/Images/Input/WebP/lossless3.webp create mode 100644 tests/Images/Input/WebP/lossless4.webp create mode 100644 tests/Images/Input/WebP/lossless_big_random_alpha.webp create mode 100644 tests/Images/Input/WebP/lossless_color_transform.bmp create mode 100644 tests/Images/Input/WebP/lossless_color_transform.pam create mode 100644 tests/Images/Input/WebP/lossless_color_transform.pgm create mode 100644 tests/Images/Input/WebP/lossless_color_transform.ppm create mode 100644 tests/Images/Input/WebP/lossless_color_transform.tiff create mode 100644 tests/Images/Input/WebP/lossless_color_transform.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_0.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_1.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_10.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_11.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_12.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_13.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_14.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_15.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_2.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_3.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_4.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_5.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_6.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_7.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_8.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_1_9.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_0.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_1.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_10.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_11.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_12.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_13.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_14.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_15.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_2.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_3.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_4.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_5.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_6.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_7.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_8.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_2_9.webp create mode 100644 tests/Images/Input/WebP/lossless_vec_list.txt create mode 100644 tests/Images/Input/WebP/lossy_alpha1.webp create mode 100644 tests/Images/Input/WebP/lossy_alpha2.webp create mode 100644 tests/Images/Input/WebP/lossy_alpha3.webp create mode 100644 tests/Images/Input/WebP/lossy_alpha4.webp create mode 100644 tests/Images/Input/WebP/lossy_extreme_probabilities.webp create mode 100644 tests/Images/Input/WebP/lossy_q0_f100.webp create mode 100644 tests/Images/Input/WebP/near_lossless_75.webp create mode 100644 tests/Images/Input/WebP/peak.bmp create mode 100644 tests/Images/Input/WebP/peak.pam create mode 100644 tests/Images/Input/WebP/peak.pgm create mode 100644 tests/Images/Input/WebP/peak.png create mode 100644 tests/Images/Input/WebP/peak.ppm create mode 100644 tests/Images/Input/WebP/peak.tiff create mode 100644 tests/Images/Input/WebP/segment01.webp create mode 100644 tests/Images/Input/WebP/segment02.webp create mode 100644 tests/Images/Input/WebP/segment03.webp create mode 100644 tests/Images/Input/WebP/small_13x1.webp create mode 100644 tests/Images/Input/WebP/small_1x1.webp create mode 100644 tests/Images/Input/WebP/small_1x13.webp create mode 100644 tests/Images/Input/WebP/small_31x13.webp create mode 100644 tests/Images/Input/WebP/test-nostrong.webp create mode 100644 tests/Images/Input/WebP/test.webp create mode 100644 tests/Images/Input/WebP/test_cwebp.sh create mode 100644 tests/Images/Input/WebP/test_dwebp.sh create mode 100644 tests/Images/Input/WebP/test_lossless.sh create mode 100644 tests/Images/Input/WebP/very_short.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-001.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-002.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-003.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-004.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-005.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-006.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-007.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-008.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-009.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-010.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-011.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-012.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-013.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-014.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-015.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-016.webp create mode 100644 tests/Images/Input/WebP/vp80-00-comprehensive-017.webp create mode 100644 tests/Images/Input/WebP/vp80-01-intra-1400.webp create mode 100644 tests/Images/Input/WebP/vp80-01-intra-1411.webp create mode 100644 tests/Images/Input/WebP/vp80-01-intra-1416.webp create mode 100644 tests/Images/Input/WebP/vp80-01-intra-1417.webp create mode 100644 tests/Images/Input/WebP/vp80-02-inter-1402.webp create mode 100644 tests/Images/Input/WebP/vp80-02-inter-1412.webp create mode 100644 tests/Images/Input/WebP/vp80-02-inter-1418.webp create mode 100644 tests/Images/Input/WebP/vp80-02-inter-1424.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1401.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1403.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1407.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1408.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1409.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1410.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1413.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1414.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1415.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1425.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1426.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1427.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1432.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1435.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1436.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1437.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1441.webp create mode 100644 tests/Images/Input/WebP/vp80-03-segmentation-1442.webp create mode 100644 tests/Images/Input/WebP/vp80-04-partitions-1404.webp create mode 100644 tests/Images/Input/WebP/vp80-04-partitions-1405.webp create mode 100644 tests/Images/Input/WebP/vp80-04-partitions-1406.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1428.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1429.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1430.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1431.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1433.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1434.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1438.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1439.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1440.webp create mode 100644 tests/Images/Input/WebP/vp80-05-sharpness-1443.webp diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md index 664df5043..c4c800464 100644 --- a/src/ImageSharp/Formats/WebP/Readme.md +++ b/src/ImageSharp/Formats/WebP/Readme.md @@ -6,3 +6,4 @@ Reference implementation, specification and stuff like that: - [WebP Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) - [WebP VP8 chunk Spec](http://tools.ietf.org/html/rfc6386) - [WebP filefront](https://wiki.fileformat.com/image/webp/) +- [WebP test data](https://github.com/webmproject/libwebp-test-data/) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 356fa534c..49e85319b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -2,25 +2,18 @@ // Licensed under the Apache License, Version 2.0. using System.IO; -using SixLabors.ImageSharp.PixelFormats; using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.WebP { - using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Metadata; + using static SixLabors.ImageSharp.Tests.TestImages.WebP; using static TestImages.Bmp; public class WebPDecoderTests { - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - - public static readonly string[] MiscBmpFiles = Miscellaneous; - - public static readonly string[] BitfieldsBmpFiles = BitFields; - public static readonly TheoryData RatioFiles = new TheoryData { @@ -30,72 +23,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP }; [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage(new WebPDecoder())) - { - image.DebugSave(provider); - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } - } - } - - [Theory] - [WithFile(Bit16Inverted, PixelTypes.Rgba32)] - [WithFile(Bit8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage(new WebPDecoder())) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - } - - [Theory] - [InlineData(Bit32Rgb, 32)] - [InlineData(Bit32Rgba, 32)] - [InlineData(Car, 24)] - [InlineData(F, 24)] - [InlineData(NegHeight, 24)] - [InlineData(Bit16, 16)] - [InlineData(Bit16Inverted, 16)] - [InlineData(Bit8, 8)] - [InlineData(Bit8Inverted, 8)] - [InlineData(Bit4, 4)] - [InlineData(Bit1, 1)] - [InlineData(Bit1Pal1, 1)] - public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) + [InlineData(Lossless.Lossless1, 1000, 307)] + public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new WebPDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1a11c81b5..6f310f470 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -368,13 +368,10 @@ namespace SixLabors.ImageSharp.Tests public static class WebP { + //TODO: actualize it with fresh sample images public static class Lossless { - public const string SampleWebpOne = "WebP/Lossless/1_webp_ll.webp"; - public const string SampleWebpTwo = "WebP/Lossless/2_webp_ll.webp"; - public const string SampleWebpThree = "WebP/Lossless/3_webp_ll.webp"; - public const string SampleWebpFour = "WebP/Lossless/4_webp_ll.webp"; - public const string SampleWebpFive = "WebP/Lossless/5_webp_ll.webp"; + public const string Lossless1 = "WebP/lossless1.webp"; } public static class Lossy @@ -397,21 +394,7 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { - Lossless.SampleWebpOne, - Lossless.SampleWebpTwo, - Lossless.SampleWebpThree, - Lossless.SampleWebpFour, - Lossless.SampleWebpFive, - Lossy.SampleWebpOne, - Lossy.SampleWebpTwo, - Lossy.SampleWebpThree, - Lossy.SampleWebpFour, - Lossy.SampleWebpFive, - Lossy.Alpha.SampleWebpOne, - Lossy.Alpha.SampleWebpTwo, - Lossy.Alpha.SampleWebpThree, - Lossy.Alpha.SampleWebpFour, - Lossy.Alpha.SampleWebpFive + Lossless.Lossless1 }; } } diff --git a/tests/Images/Input/WebP/Lossless/1_webp_ll.webp b/tests/Images/Input/WebP/Lossless/1_webp_ll.webp deleted file mode 100644 index 8f40d3913..000000000 --- a/tests/Images/Input/WebP/Lossless/1_webp_ll.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:694f9011eddb7d0f08fc76ddad6b2129f2390d1e44c41a9ed9de0f08bb14c43b -size 81977 diff --git a/tests/Images/Input/WebP/Lossless/2_webp_ll.webp b/tests/Images/Input/WebP/Lossless/2_webp_ll.webp deleted file mode 100644 index 42e1f839f..000000000 --- a/tests/Images/Input/WebP/Lossless/2_webp_ll.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee83dd1211ba339e9a61a9af5e24a9fa5b8168ea448eacf4743bdcc36f58452b -size 27669 diff --git a/tests/Images/Input/WebP/Lossless/3_webp_ll.webp b/tests/Images/Input/WebP/Lossless/3_webp_ll.webp deleted file mode 100644 index df117cc33..000000000 --- a/tests/Images/Input/WebP/Lossless/3_webp_ll.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c96ed502862c0adc1f4221da1bd31d0921853f62eebcbbb89fec4e862ecb1de -size 152634 diff --git a/tests/Images/Input/WebP/Lossless/4_webp_ll.webp b/tests/Images/Input/WebP/Lossless/4_webp_ll.webp deleted file mode 100644 index 411f5b273..000000000 --- a/tests/Images/Input/WebP/Lossless/4_webp_ll.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eff718866d464cd27161e171bb83ad73a05befae0724c2c75a772a7e21ac3b9 -size 34096 diff --git a/tests/Images/Input/WebP/Lossless/5_webp_ll.webp b/tests/Images/Input/WebP/Lossless/5_webp_ll.webp deleted file mode 100644 index c2bbefa8e..000000000 --- a/tests/Images/Input/WebP/Lossless/5_webp_ll.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0da93a44371abecdc68ad082c73ec633c3cd02d547fe28992403086a9e110946 -size 99425 diff --git a/tests/Images/Input/WebP/Lossy/1.webp b/tests/Images/Input/WebP/Lossy/1.webp deleted file mode 100644 index 447e38de2..000000000 --- a/tests/Images/Input/WebP/Lossy/1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8b4596c10a23135931b4e63957eda41e9b3874811d9b724d890b589d66ea12f -size 30319 diff --git a/tests/Images/Input/WebP/Lossy/2.webp b/tests/Images/Input/WebP/Lossy/2.webp deleted file mode 100644 index 39fc0b63c..000000000 --- a/tests/Images/Input/WebP/Lossy/2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ac427ad555ba5898015e8b60396e1852bfc1e9cd7ae5da6dd42c85ea3e169f7 -size 60600 diff --git a/tests/Images/Input/WebP/Lossy/3.webp b/tests/Images/Input/WebP/Lossy/3.webp deleted file mode 100644 index acc899195..000000000 --- a/tests/Images/Input/WebP/Lossy/3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ebf4dc48653bd5f3c5b929ae3a411a0e440eb316aa50c80b1591d98db865bd5a -size 203135 diff --git a/tests/Images/Input/WebP/Lossy/4.webp b/tests/Images/Input/WebP/Lossy/4.webp deleted file mode 100644 index 62cddcab6..000000000 --- a/tests/Images/Input/WebP/Lossy/4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f354f8954071f0b979a40866de95f2113a6ce54f4fe483c7f2b269c913f0df3 -size 176969 diff --git a/tests/Images/Input/WebP/Lossy/5.webp b/tests/Images/Input/WebP/Lossy/5.webp deleted file mode 100644 index e991f3153..000000000 --- a/tests/Images/Input/WebP/Lossy/5.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7558b0f5da141787fcc41857743f8bc9417c844e2a19aeaa5fee25f1433abe4e -size 82697 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp deleted file mode 100644 index 00cca4a56..000000000 --- a/tests/Images/Input/WebP/Lossy/Alpha/1_webp_a.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e89d207eff1f1499acd6a5b8dc4a326ecdf3d303e4a7e6fc65c268035326a75f -size 18840 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp deleted file mode 100644 index 76ba0b9da..000000000 --- a/tests/Images/Input/WebP/Lossy/Alpha/2_webp_a.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c7f6772cdaf74fe5dac79d8acd7da48cc3700159f41e9dd2ebae8dd7c81b548 -size 14412 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp deleted file mode 100644 index 6f489e6fe..000000000 --- a/tests/Images/Input/WebP/Lossy/Alpha/3_webp_a.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c648a83fa93378259b8c71db58b4f47e5b4bf6f980b7d418346c4a2cf9ad6664 -size 53817 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp deleted file mode 100644 index 88624600a..000000000 --- a/tests/Images/Input/WebP/Lossy/Alpha/4_webp_a.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2b4164bf2facd2b212e5eb15eb3066254c785b35d14d79341d178d8692e6e28 -size 19440 diff --git a/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp b/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp deleted file mode 100644 index 278b91091..000000000 --- a/tests/Images/Input/WebP/Lossy/Alpha/5_webp_a.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5988b9e71686b385d76c5fba81d09642d1f08a079cabe81d653b025a40abe4f1 -size 58712 diff --git a/tests/Images/Input/WebP/alpha_color_cache.webp b/tests/Images/Input/WebP/alpha_color_cache.webp new file mode 100644 index 000000000..ec5d7540e --- /dev/null +++ b/tests/Images/Input/WebP/alpha_color_cache.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4b9e2459858e6f6a1d919c2adeb73d7e2ad251d6bfbfb99303a6b4508ca757a +size 1838 diff --git a/tests/Images/Input/WebP/alpha_filter_0_method_0.webp b/tests/Images/Input/WebP/alpha_filter_0_method_0.webp new file mode 100644 index 000000000..d85e800dc --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_0_method_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:150ff79f16e254281153d7e75e5968663c7f83ae58217b36c12d11088045eb07 +size 22038 diff --git a/tests/Images/Input/WebP/alpha_filter_0_method_1.webp b/tests/Images/Input/WebP/alpha_filter_0_method_1.webp new file mode 100644 index 000000000..56318eca9 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_0_method_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96f514bce6faa65330eba17c06eb1cf120ba8c133288ab2633afa63d7c6c66ad +size 12162 diff --git a/tests/Images/Input/WebP/alpha_filter_1.webp b/tests/Images/Input/WebP/alpha_filter_1.webp new file mode 100644 index 000000000..216f2eef6 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ad8a7038fed7bdfa90db1c1f987782a9f46903aaccd5ad04dd78d067632fba +size 114 diff --git a/tests/Images/Input/WebP/alpha_filter_1_method_0.webp b/tests/Images/Input/WebP/alpha_filter_1_method_0.webp new file mode 100644 index 000000000..94a605e13 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_1_method_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dc2d060c723aa0a855bc696191d220a60a36bad014cda9764ee93516fa9f073 +size 22038 diff --git a/tests/Images/Input/WebP/alpha_filter_1_method_1.webp b/tests/Images/Input/WebP/alpha_filter_1_method_1.webp new file mode 100644 index 000000000..a3f0cd93e --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_1_method_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79000c9c553f28e4ea589e772dd46a47a604f961050ca191a84a03d92d212eb8 +size 15592 diff --git a/tests/Images/Input/WebP/alpha_filter_2.webp b/tests/Images/Input/WebP/alpha_filter_2.webp new file mode 100644 index 000000000..d38845444 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1eba20a9ba6a09735c2424d31e26c9282be64a0d7795dd689a42c4921d436b +size 114 diff --git a/tests/Images/Input/WebP/alpha_filter_2_method_0.webp b/tests/Images/Input/WebP/alpha_filter_2_method_0.webp new file mode 100644 index 000000000..e5429119f --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_2_method_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d169bfee11e65f1b5870142531d1e35539e2686640d19fa196b36a5b7b33a45 +size 22038 diff --git a/tests/Images/Input/WebP/alpha_filter_2_method_1.webp b/tests/Images/Input/WebP/alpha_filter_2_method_1.webp new file mode 100644 index 000000000..e7bffc1db --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_2_method_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16ce80b417c8d5d95d895e3a50be00247262a3ab5700e2a22a408f5042884042 +size 15604 diff --git a/tests/Images/Input/WebP/alpha_filter_3.webp b/tests/Images/Input/WebP/alpha_filter_3.webp new file mode 100644 index 000000000..b75c44759 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4052952ea401afa934b2d262f2852f615160c7cb82c4406c47622d26b343e95e +size 118 diff --git a/tests/Images/Input/WebP/alpha_filter_3_method_0.webp b/tests/Images/Input/WebP/alpha_filter_3_method_0.webp new file mode 100644 index 000000000..ca0baef06 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_3_method_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79c7f73faec6a9b0f7ec5d274a3dd10a7eb002ebab114122a49f9794c5b8541a +size 22038 diff --git a/tests/Images/Input/WebP/alpha_filter_3_method_1.webp b/tests/Images/Input/WebP/alpha_filter_3_method_1.webp new file mode 100644 index 000000000..414723d96 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_filter_3_method_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e11b944fd8aa2e5f90c7bea05527a94e919492bef0bc464bac1493e00724ae01 +size 18266 diff --git a/tests/Images/Input/WebP/alpha_no_compression.webp b/tests/Images/Input/WebP/alpha_no_compression.webp new file mode 100644 index 000000000..a7d058e89 --- /dev/null +++ b/tests/Images/Input/WebP/alpha_no_compression.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:864de77c2209a8346004f8fe5595ffb35cfaacb71f385cc8487236689056df7d +size 336 diff --git a/tests/Images/Input/WebP/bad_palette_index.webp b/tests/Images/Input/WebP/bad_palette_index.webp new file mode 100644 index 000000000..dd8e7fd3f --- /dev/null +++ b/tests/Images/Input/WebP/bad_palette_index.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8540997edf54f030201f7354f52438dd04bf248fb21a72b71a93095fc681fb5e +size 9682 diff --git a/tests/Images/Input/WebP/big_endian_bug_393.webp b/tests/Images/Input/WebP/big_endian_bug_393.webp new file mode 100644 index 000000000..ae0c85b42 --- /dev/null +++ b/tests/Images/Input/WebP/big_endian_bug_393.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f2359f5425d78dbe16665d9e15cb56b84559eff527ab316c509a5dd1708c126 +size 16313 diff --git a/tests/Images/Input/WebP/bryce.webp b/tests/Images/Input/WebP/bryce.webp new file mode 100644 index 000000000..6cb404fae --- /dev/null +++ b/tests/Images/Input/WebP/bryce.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bed2a30a0857c188db0d2c7caf7d0e95c29eb624494c0a5d7a9b13407c19df0 +size 3533773 diff --git a/tests/Images/Input/WebP/bug3.webp b/tests/Images/Input/WebP/bug3.webp new file mode 100644 index 000000000..97ae77e91 --- /dev/null +++ b/tests/Images/Input/WebP/bug3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37d98a0b3e2132f7b5bbc03935a88a8659735c61268d8ce6acdfccfa574f4166 +size 954 diff --git a/tests/Images/Input/WebP/color_cache_bits_11.webp b/tests/Images/Input/WebP/color_cache_bits_11.webp new file mode 100644 index 000000000..29a7f190f --- /dev/null +++ b/tests/Images/Input/WebP/color_cache_bits_11.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a19dde0c51ce4c83d9bc05ea3b8f3cfed9cfac7ca19dcb23d85c56e465242350 +size 15822 diff --git a/tests/Images/Input/WebP/grid.bmp b/tests/Images/Input/WebP/grid.bmp new file mode 100644 index 000000000..a83fbf5ea --- /dev/null +++ b/tests/Images/Input/WebP/grid.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37ba61a7842f1361f6b5ec563fecadda9bde615b784a55ce372d83fa67177fa1 +size 1078 diff --git a/tests/Images/Input/WebP/grid.pam b/tests/Images/Input/WebP/grid.pam new file mode 100644 index 0000000000000000000000000000000000000000..2d1271c1dd1c28af8b330abd6f134b2e80263698 GIT binary patch literal 1091 zcmWGA=L+|93Gq-cG~@Dc^>p_L0kK?M1Asy%T)vJGVU9iuMy94*A)x_2A&~*D3PJ8p w@s2(L9*$hDel8v^L0k+B|NsAIU}zwhrbI8uPIB#q=^M45{J0wp|IzdZ027tY?f?J) literal 0 HcmV?d00001 diff --git a/tests/Images/Input/WebP/grid.pgm b/tests/Images/Input/WebP/grid.pgm new file mode 100644 index 000000000..0e9373079 --- /dev/null +++ b/tests/Images/Input/WebP/grid.pgm @@ -0,0 +1,4 @@ +P5 +16 40 +255 +)R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R)¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ \ No newline at end of file diff --git a/tests/Images/Input/WebP/grid.png b/tests/Images/Input/WebP/grid.png new file mode 100644 index 000000000..33f6ac334 --- /dev/null +++ b/tests/Images/Input/WebP/grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c53fb4527509058a8a4caf72e03ee8634f4704ab5369a8e5d194e62359d6ad0 +size 117 diff --git a/tests/Images/Input/WebP/grid.ppm b/tests/Images/Input/WebP/grid.ppm new file mode 100644 index 0000000000000000000000000000000000000000..6facbe3fc7dba8956e943cb2280a8224b354df5a GIT binary patch literal 781 wcmWGA<1#c;Ff`*bGBxF5VEF%^0SJgCNm2|nmUxpPDo4%7A7Z27L*4KJ0JGKsJ^%m! literal 0 HcmV?d00001 diff --git a/tests/Images/Input/WebP/grid.tiff b/tests/Images/Input/WebP/grid.tiff new file mode 100644 index 000000000..8c94aee3d --- /dev/null +++ b/tests/Images/Input/WebP/grid.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7db514b70352b0599ccfc7169c3795d935e9a5ec21126eefdcab6d6b8984d12e +size 1234 diff --git a/tests/Images/Input/WebP/libwebp_tests.md5 b/tests/Images/Input/WebP/libwebp_tests.md5 new file mode 100644 index 000000000..64b398baa --- /dev/null +++ b/tests/Images/Input/WebP/libwebp_tests.md5 @@ -0,0 +1,470 @@ +752cf34b353c61f5f741cb70c8265e5c bug3.webp.bmp +e27a4bd5ea7d83bcbcb255fd57fa8921 bug3.webp.pam +f3766e3c21c5ad73c5e04c76f6963961 bug3.webp.pgm +ae5fa25df6e26d5f97e526ac8cf4a2c0 bug3.webp.ppm +a5d93d118527678a3d54506a2852cf3a bug3.webp.tiff +4a2f38e6075d12677f902f6ef2035fcd lossless1.webp.bmp +fd52591b61fc34192d7c337fa024bf12 lossless1.webp.pam +8141a733978e9efeacc668687f8c773b lossless1.webp.pgm +3a1da0ba5657c5f65fec5c84cc9a888a lossless1.webp.ppm +1edba87958a360dbfad2a9def2e31ed4 lossless1.webp.tiff +4a2f38e6075d12677f902f6ef2035fcd lossless2.webp.bmp +fd52591b61fc34192d7c337fa024bf12 lossless2.webp.pam +8141a733978e9efeacc668687f8c773b lossless2.webp.pgm +3a1da0ba5657c5f65fec5c84cc9a888a lossless2.webp.ppm +1edba87958a360dbfad2a9def2e31ed4 lossless2.webp.tiff +4a2f38e6075d12677f902f6ef2035fcd lossless3.webp.bmp +fd52591b61fc34192d7c337fa024bf12 lossless3.webp.pam +8141a733978e9efeacc668687f8c773b lossless3.webp.pgm +3a1da0ba5657c5f65fec5c84cc9a888a lossless3.webp.ppm +1edba87958a360dbfad2a9def2e31ed4 lossless3.webp.tiff +9b62f79cf1f623a3ac12c008c95bf9c2 lossy_alpha1.webp.bmp +c5c77aff5b4015d3416817d12c2c2377 lossy_alpha1.webp.pam +7dfa7e2ee84b6d0d21dd5395c43e58a0 lossy_alpha1.webp.pgm +060930f62b7e79001069c2a1c6387ace lossy_alpha1.webp.ppm +26784e7f5a919c2e5c5b07429c2789f2 lossy_alpha1.webp.tiff +57a73105a2f7259d05594c7d722cfff5 lossy_alpha2.webp.bmp +5a98f393a1dfd2e56c1fdf8f18a028a6 lossy_alpha2.webp.pam +923e7e529bbbc207d82570ea8aace080 lossy_alpha2.webp.pgm +b34c384890fdd1ef19d337cd5cabfb87 lossy_alpha2.webp.ppm +61de03fb7f4321438c2bd97c1776d298 lossy_alpha2.webp.tiff +34e893a765451a4dbb7234ca2e3c0e54 lossy_alpha3.webp.bmp +4ab07c625657aac74fdfa440b7d80661 lossy_alpha3.webp.pam +9be96d161ea68e222c98c79e9e6bfe55 lossy_alpha3.webp.pgm +b8577b69f3e781ef3db275f1689c7a67 lossy_alpha3.webp.ppm +70051b36f2751894047ce61fb81c5077 lossy_alpha3.webp.tiff +8cdc224c1c98fd3d7a2eccef39615fa2 lossy_extreme_probabilities.webp.bmp +48acff24a64848886eb5fbc7f4d9f48f lossy_extreme_probabilities.webp.pam +7b45594189937b3081c5ff296df0748e lossy_extreme_probabilities.webp.pgm +35b296b4847410c55973cd9b26a00c9e lossy_extreme_probabilities.webp.ppm +6a5b5f4663c9420681fed41031b7e367 lossy_extreme_probabilities.webp.tiff +5d0e23758492b9054edbc3468916a25c segment01.webp.bmp +9cdc59716def2771ed44d6e59e60118e segment01.webp.pam +b6fdd7a449ca379d9c73d3af132f708e segment01.webp.pgm +31fe5642d04d90dd7aa5cedd6c761640 segment01.webp.ppm +48563a05febd80370280e23cb48fda92 segment01.webp.tiff +58b61363438effccdddd8b2d48d39cd4 segment02.webp.bmp +53fea7f9739ebc82633b3abb7742b106 segment02.webp.pam +390293f54eae1df3477d7122351f1a72 segment02.webp.pgm +aca97156b5c91251536becec093e4869 segment02.webp.ppm +afccf77585d5cf6f0ea3320dbec4b120 segment02.webp.tiff +49d19c40152a3b0fde7bcd1c91a5b7be segment03.webp.bmp +55150fffd5fe83e00eff2ca2035bb87b segment03.webp.pam +b7bb5c2b5b48d014f75e2f9db9e45718 segment03.webp.pgm +653d32a9016c1ee5b6fec6f4afefadd8 segment03.webp.ppm +e059fdc5de402db01519ffd2b3018c52 segment03.webp.tiff +4f606f42cb00f1c575a23c4cce540157 small_13x1.webp.bmp +48f544271e281d68a2d406b928de1841 small_13x1.webp.pam +5683f2f30a22f9b800915bf4edfd14de small_13x1.webp.pgm +188a9ac1aa2f4a7d256831ae7a5cb682 small_13x1.webp.ppm +3c336cfb8fd451efb7f52b75afd7b643 small_13x1.webp.tiff +d16c13d5bdd9bfdd62c612f68570f302 small_1x1.webp.bmp +d6605e1f351452a8f8c8cbe7fa9218bd small_1x1.webp.pam +a40ac01f9a60ff4473f1a40ef57f6ff5 small_1x1.webp.pgm +d4e7037a5b97e3c82aa4fd343fc068e4 small_1x1.webp.ppm +5f1f089d669b8c3671c28819cbb9e25b small_1x1.webp.tiff +04429ff71bd421664f73be6d0e8dee45 small_1x13.webp.bmp +b99d3b58c1c1f322f570a2c2ad24080f small_1x13.webp.pam +bd0c99456093f5b4ba8d87b2fb964478 small_1x13.webp.pgm +37fb89b8ec87dcfc4c15e258e0d75246 small_1x13.webp.ppm +47aa7b343bcb14315c3491ad59e1ba1d small_1x13.webp.tiff +9a7f2b9bd981ae5899fb4f75f1405f76 small_31x13.webp.bmp +586337da3501d1fae607ef0e2930a1b1 small_31x13.webp.pam +1d71e36e683771fa452110c61b98ea12 small_31x13.webp.pgm +c803f81036d4ea998cf94e3fd9be9a7f small_31x13.webp.ppm +825990e0c570245defdb6dd2d4678226 small_31x13.webp.tiff +a3b449dc60a7e6dd399d6c146c29f38d test-nostrong.webp.bmp +ce12aa49f7e4f2afa0963f18f11dc332 test-nostrong.webp.pam +20e3e0c26b596803c4c0a51c7fc544d2 test-nostrong.webp.pgm +dc97fd4b0ac668f3a0d3925d998c1908 test-nostrong.webp.ppm +2e660e7ddaffcac8f823db3f1d80c5d5 test-nostrong.webp.tiff +34efa50cddbff8575720f270387414c9 test.webp.bmp +3d9213ea387706db93f0b39247d77573 test.webp.pam +e46f3d66c69e8b47a2c9a298ecc516b9 test.webp.pgm +ebdd46e0760b2a4891e6550b37c00660 test.webp.ppm +a956c5897f57ca3432c3eff371e577f5 test.webp.tiff +54ed492d774eeb15339eade270ef0a2c very_short.webp.bmp +2ec8e78a5fef6ab980cff79948eb5d2c very_short.webp.pam +0517d3e5b01a67dde947fb09564473b7 very_short.webp.pgm +17fbb51aa95d17f3c9440c1e6a1411c3 very_short.webp.ppm +936b795d3dd76e7bae65af1c92181baf very_short.webp.tiff +df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-001.webp.bmp +131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-001.webp.pam +83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-001.webp.pgm +d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-001.webp.ppm +c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-001.webp.tiff +80daf19056e45cc74baa01286f30f33a vp80-00-comprehensive-002.webp.bmp +b8b258d3bb66c5918906d6a167f4673d vp80-00-comprehensive-002.webp.pam +3dd031f2cb1906d5fe1a5f6aee4b0461 vp80-00-comprehensive-002.webp.pgm +9cf357fc1a98224436d0a167e04b8041 vp80-00-comprehensive-002.webp.ppm +21440d3544780283097de49e2ffd65b9 vp80-00-comprehensive-002.webp.tiff +6a83b957594e3d5983b4cf605a43171d vp80-00-comprehensive-003.webp.bmp +e889db2f00f3b788673fd76e35a38591 vp80-00-comprehensive-003.webp.pam +0be1ab40b30824ff9d09722c074271ff vp80-00-comprehensive-003.webp.pgm +4fc3367f461a18119ed534843648a06e vp80-00-comprehensive-003.webp.ppm +d5f951a6b267674066cc40179db791ab vp80-00-comprehensive-003.webp.tiff +df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-004.webp.bmp +131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-004.webp.pam +83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-004.webp.pgm +d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-004.webp.ppm +c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-004.webp.tiff +20e26306afdfd6aeafb832a5934c7331 vp80-00-comprehensive-005.webp.bmp +be0a3cba6d4307a211bc516032726162 vp80-00-comprehensive-005.webp.pam +7f43d5472ffb0be617840cb300787547 vp80-00-comprehensive-005.webp.pgm +0f861236782aad77186c872185c5788d vp80-00-comprehensive-005.webp.ppm +4a112bab41e414c85f6e178d5d510480 vp80-00-comprehensive-005.webp.tiff +d4d78d8840debaaa08aee9cc2b82ef26 vp80-00-comprehensive-006.webp.bmp +ec670bbb7dc209ec061b193f5bd85afe vp80-00-comprehensive-006.webp.pam +ef432fcf2599fac6e7e9c2abaa7d7635 vp80-00-comprehensive-006.webp.pgm +77de8cf761dc28c44b9d4630331d1077 vp80-00-comprehensive-006.webp.ppm +56ea1fc09a7bbf749a54bdb94820b7d0 vp80-00-comprehensive-006.webp.tiff +b5805421c3d192b21afa88203db9049c vp80-00-comprehensive-007.webp.bmp +682ea7892cdfa16e32c080558d3aa6d1 vp80-00-comprehensive-007.webp.pam +d19cc392d55bed791967bc0d97fbe89b vp80-00-comprehensive-007.webp.pgm +9d0abc72e3d44e1a92dbe93fe03ec193 vp80-00-comprehensive-007.webp.ppm +40d06ddca14f3bbf8379bce7db616282 vp80-00-comprehensive-007.webp.tiff +f23de86715e12f0a4eea5beac488c028 vp80-00-comprehensive-008.webp.bmp +595e44c414148ccd73d77ef35218dfe6 vp80-00-comprehensive-008.webp.pam +8a3aa03341721dc43d7154f95ceea4ba vp80-00-comprehensive-008.webp.pgm +ea6f107e0489d9b2e9d1c4a2edec37ee vp80-00-comprehensive-008.webp.ppm +fafa9e2293493e68af1149c0d1e895ce vp80-00-comprehensive-008.webp.tiff +a086ecef18cfe6e2a5147e0ed4dd8976 vp80-00-comprehensive-009.webp.bmp +e07f8c0ae66de49c286ce7532122aff8 vp80-00-comprehensive-009.webp.pam +80ee73b2f08a9c14ca1e9f3936b873dc vp80-00-comprehensive-009.webp.pgm +fed589d9874314c66b8627263865dc0d vp80-00-comprehensive-009.webp.ppm +b4a781da320f6052b4cc9626744ca87d vp80-00-comprehensive-009.webp.tiff +625d334a9d0c4a08871065ae97ce52a7 vp80-00-comprehensive-010.webp.bmp +daac194407ea1483c6e91a8d683f4318 vp80-00-comprehensive-010.webp.pam +828eee458e38de2f706426dc3c326138 vp80-00-comprehensive-010.webp.pgm +9eb59d831bec86417b09bfaa075da197 vp80-00-comprehensive-010.webp.ppm +be2bd1b975e1fb6369024f31912df193 vp80-00-comprehensive-010.webp.tiff +df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-011.webp.bmp +131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-011.webp.pam +83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-011.webp.pgm +d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-011.webp.ppm +c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-011.webp.tiff +80fbbd6f508898f7c26b6bd7e0724986 vp80-00-comprehensive-012.webp.bmp +bd864949ce28ad7c3c4478ed06d2eca2 vp80-00-comprehensive-012.webp.pam +2b98514d0699353bb0876e1cd6474226 vp80-00-comprehensive-012.webp.pgm +e19222f69d98ff61eef85f241b8a279f vp80-00-comprehensive-012.webp.ppm +f13560abf907158e95d0c99619f2d3d6 vp80-00-comprehensive-012.webp.tiff +108b1c8742c0bea9509c9bb013093622 vp80-00-comprehensive-013.webp.bmp +d474b510bd58178b95b74b5c1e35bf62 vp80-00-comprehensive-013.webp.pam +5cd2d920340f771d1e535e4b7f632f18 vp80-00-comprehensive-013.webp.pgm +c4f32060e80bf13fd3e87e55d31b49ad vp80-00-comprehensive-013.webp.ppm +6c74f63613eda4e1f35fdeeb82e70616 vp80-00-comprehensive-013.webp.tiff +1067a63f05f52446a2afb9b0a57c7001 vp80-00-comprehensive-014.webp.bmp +bd3ae3b0ff577f36d46fb874c6f3a82d vp80-00-comprehensive-014.webp.pam +39b06e302571acd69cf71c0bb2cf7752 vp80-00-comprehensive-014.webp.pgm +dea00c2e8d6df679d383196c16dab89c vp80-00-comprehensive-014.webp.ppm +a8903a156cb4f6e58137ef0496c8ef2b vp80-00-comprehensive-014.webp.tiff +3f4d1ac502b5310a9ca401f8c2254bdb vp80-00-comprehensive-015.webp.bmp +9041921a26f7de41f1cda79ac355c0d7 vp80-00-comprehensive-015.webp.pam +7df64ec81488aaca964fcf09fa13b017 vp80-00-comprehensive-015.webp.pgm +fdd58f7ef85dec0503915b802c7b8f26 vp80-00-comprehensive-015.webp.ppm +f6f99798c4c75a8b89d59e9ee00acc13 vp80-00-comprehensive-015.webp.tiff +b0271ce129966000ff0fdd618cedf429 vp80-00-comprehensive-016.webp.bmp +691cb65996f8347d696474ff34e714fc vp80-00-comprehensive-016.webp.pam +7f57f6187412786f64752c08f8be1fe8 vp80-00-comprehensive-016.webp.pgm +f40b8a72514c9ca35dd2f6eaf6208cfb vp80-00-comprehensive-016.webp.ppm +3559ae9a914e7f0154654e0d75aa5efc vp80-00-comprehensive-016.webp.tiff +b0271ce129966000ff0fdd618cedf429 vp80-00-comprehensive-017.webp.bmp +691cb65996f8347d696474ff34e714fc vp80-00-comprehensive-017.webp.pam +7f57f6187412786f64752c08f8be1fe8 vp80-00-comprehensive-017.webp.pgm +f40b8a72514c9ca35dd2f6eaf6208cfb vp80-00-comprehensive-017.webp.ppm +3559ae9a914e7f0154654e0d75aa5efc vp80-00-comprehensive-017.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-01-intra-1400.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-01-intra-1400.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-01-intra-1400.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-01-intra-1400.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-01-intra-1400.webp.tiff +faeeb6228af0caf9e2394acf12c9fde9 vp80-01-intra-1411.webp.bmp +a0930ca8ccf3a5f135692104ae6c177c vp80-01-intra-1411.webp.pam +a2fab5648ef79a82cc71c5e6ec81611d vp80-01-intra-1411.webp.pgm +e56a3d6dc156823f63749174d4d1ecad vp80-01-intra-1411.webp.ppm +b4be9fc15957093c586f009621400c07 vp80-01-intra-1411.webp.tiff +10ef2d26d016bfd6f82bb10f3ad5c4de vp80-01-intra-1416.webp.bmp +0c7bfbb9ecff4853b493d2a6dd0b8fb8 vp80-01-intra-1416.webp.pam +cc7dab0840259b9a659db905b8babd14 vp80-01-intra-1416.webp.pgm +6175ed41106971eed7648b2edf63f832 vp80-01-intra-1416.webp.ppm +ceb5352cf316ef4a0df74be7f03ec122 vp80-01-intra-1416.webp.tiff +c63a158d762c02744b8f1cc98a5ea863 vp80-01-intra-1417.webp.bmp +7b43d51e05c850b3a79709f24bb65f90 vp80-01-intra-1417.webp.pam +91dc3f9fa9f2bc09145adfc05c1e659f vp80-01-intra-1417.webp.pgm +02277ee0fb9ec05c960e83d88aafb0e7 vp80-01-intra-1417.webp.ppm +b498e84ebe1ff6cf70b90b6968baed20 vp80-01-intra-1417.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-02-inter-1402.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-02-inter-1402.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-02-inter-1402.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-02-inter-1402.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-02-inter-1402.webp.tiff +faeeb6228af0caf9e2394acf12c9fde9 vp80-02-inter-1412.webp.bmp +a0930ca8ccf3a5f135692104ae6c177c vp80-02-inter-1412.webp.pam +a2fab5648ef79a82cc71c5e6ec81611d vp80-02-inter-1412.webp.pgm +e56a3d6dc156823f63749174d4d1ecad vp80-02-inter-1412.webp.ppm +b4be9fc15957093c586f009621400c07 vp80-02-inter-1412.webp.tiff +3587a8cd220edc08b52514c210aee0f6 vp80-02-inter-1418.webp.bmp +17b4a0e6a7fc7ed6d9694bf0aee061a2 vp80-02-inter-1418.webp.pam +dee12174722df68136de55f03de72905 vp80-02-inter-1418.webp.pgm +e974bc9609c361b80c8a524f2e1342f4 vp80-02-inter-1418.webp.ppm +75063d7e1cda0828d6bb0b94eb4e71e2 vp80-02-inter-1418.webp.tiff +7672a847500f6ebe8d2068e3fd5915fc vp80-02-inter-1424.webp.bmp +a9c677bc3f6886dac2d96e7b4fb1803f vp80-02-inter-1424.webp.pam +63ce6da2644ba3de11b9d50e0449c38f vp80-02-inter-1424.webp.pgm +fb9564457d6d0763f309d0aaa6ec9fe4 vp80-02-inter-1424.webp.ppm +f854fa1993f48ac9ed394089ef121ead vp80-02-inter-1424.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-03-segmentation-1401.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-03-segmentation-1401.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-03-segmentation-1401.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-03-segmentation-1401.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-03-segmentation-1401.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-03-segmentation-1403.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-03-segmentation-1403.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-03-segmentation-1403.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-03-segmentation-1403.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-03-segmentation-1403.webp.tiff +323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1407.webp.bmp +7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1407.webp.pam +686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1407.webp.pgm +b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1407.webp.ppm +f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1407.webp.tiff +323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1408.webp.bmp +7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1408.webp.pam +686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1408.webp.pgm +b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1408.webp.ppm +f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1408.webp.tiff +323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1409.webp.bmp +7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1409.webp.pam +686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1409.webp.pgm +b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1409.webp.ppm +f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1409.webp.tiff +323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1410.webp.bmp +7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1410.webp.pam +686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1410.webp.pgm +b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1410.webp.ppm +f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1410.webp.tiff +faeeb6228af0caf9e2394acf12c9fde9 vp80-03-segmentation-1413.webp.bmp +a0930ca8ccf3a5f135692104ae6c177c vp80-03-segmentation-1413.webp.pam +a2fab5648ef79a82cc71c5e6ec81611d vp80-03-segmentation-1413.webp.pgm +e56a3d6dc156823f63749174d4d1ecad vp80-03-segmentation-1413.webp.ppm +b4be9fc15957093c586f009621400c07 vp80-03-segmentation-1413.webp.tiff +e406ddb31ed29c7b87caacdac1f0a0dd vp80-03-segmentation-1414.webp.bmp +a3523ae5a8c4b632291590937f10e77d vp80-03-segmentation-1414.webp.pam +0825b61488bc8cd27820d7ebb7fcb2de vp80-03-segmentation-1414.webp.pgm +ae6230c3f0983b4165d1d4c367fa3af6 vp80-03-segmentation-1414.webp.ppm +d8a2356a4c105e20d242ffdbfacab919 vp80-03-segmentation-1414.webp.tiff +e406ddb31ed29c7b87caacdac1f0a0dd vp80-03-segmentation-1415.webp.bmp +a3523ae5a8c4b632291590937f10e77d vp80-03-segmentation-1415.webp.pam +0825b61488bc8cd27820d7ebb7fcb2de vp80-03-segmentation-1415.webp.pgm +ae6230c3f0983b4165d1d4c367fa3af6 vp80-03-segmentation-1415.webp.ppm +d8a2356a4c105e20d242ffdbfacab919 vp80-03-segmentation-1415.webp.tiff +300bb54edf23e74e2f1762a89d81f32f vp80-03-segmentation-1425.webp.bmp +577964dc9d5d867be2c0c0923fb2baa1 vp80-03-segmentation-1425.webp.pam +7cf96b3757244675c2b7402b4135c760 vp80-03-segmentation-1425.webp.pgm +fc302f122658ef20fd0e2712fa5eec3e vp80-03-segmentation-1425.webp.ppm +f2982fbb8a94c5c0cb66e41703e7fec8 vp80-03-segmentation-1425.webp.tiff +e4e3682e0b44a45cbebdf831578f826d vp80-03-segmentation-1426.webp.bmp +d3605f57d5be180a450453a8ba7eacf9 vp80-03-segmentation-1426.webp.pam +1911149733a7c14d7a57efb646a5958a vp80-03-segmentation-1426.webp.pgm +e7b49578d08759bf9ddddc4ef0e31ad2 vp80-03-segmentation-1426.webp.ppm +6bc9c0a37192535a6ff7b6daeb8025a3 vp80-03-segmentation-1426.webp.tiff +6817a8881625061e43c2d8ce3afe75fd vp80-03-segmentation-1427.webp.bmp +01b3895cd497a1e0ff20872488b227cb vp80-03-segmentation-1427.webp.pam +3cb4d32d2163bef67c483e2aa38bec50 vp80-03-segmentation-1427.webp.pgm +c6480d79d6e0f83c865bb80eacb45d85 vp80-03-segmentation-1427.webp.ppm +760dd78471f88ce5044d9bd406e9c5a0 vp80-03-segmentation-1427.webp.tiff +5b910f0f5593483274196c710388e79d vp80-03-segmentation-1432.webp.bmp +6572b0954b3462d308e8d39e81d2a069 vp80-03-segmentation-1432.webp.pam +68a4e3fe38ab9981080d9c5087c10100 vp80-03-segmentation-1432.webp.pgm +8a6a27f16352f2af9bf23c9a1bfb11a5 vp80-03-segmentation-1432.webp.ppm +e99e5dc77d0e511482255a76290fb0b9 vp80-03-segmentation-1432.webp.tiff +55bdbcb76b41493bed87f2b661e811f9 vp80-03-segmentation-1435.webp.bmp +0544e7ee82a8c00dfb7e7ae002656578 vp80-03-segmentation-1435.webp.pam +dd072cb089a6656f82cc0474e88d5057 vp80-03-segmentation-1435.webp.pgm +35d685ecacf6dc3930371932486a11e7 vp80-03-segmentation-1435.webp.ppm +54bce5209886f305cb24a39024344071 vp80-03-segmentation-1435.webp.tiff +bbf5499355c2168984e780613e65227f vp80-03-segmentation-1436.webp.bmp +bfac8c040851686262a369892a849df8 vp80-03-segmentation-1436.webp.pam +728c352f485cdf199cbdc541ad4c3275 vp80-03-segmentation-1436.webp.pgm +f92f6333eb0d20b7ec1bf7c0cba3396b vp80-03-segmentation-1436.webp.ppm +820193c918fda34ec69b9b51a9980de9 vp80-03-segmentation-1436.webp.tiff +fd6fa5a841f263a82d69abe737ce8674 vp80-03-segmentation-1437.webp.bmp +cb30469eefa905410bb4ffdaff0e2e60 vp80-03-segmentation-1437.webp.pam +e70f445cd30eea193704c41989dc1511 vp80-03-segmentation-1437.webp.pgm +9b5cc3e123b2cd04bdfbf68d093bfb74 vp80-03-segmentation-1437.webp.ppm +8a558c55610d7575b891c39f5fe48a8f vp80-03-segmentation-1437.webp.tiff +2bd4b8fda0fb5e6fc749244bba535ace vp80-03-segmentation-1441.webp.bmp +dcf08939b95abbdae4e1113246ec52a4 vp80-03-segmentation-1441.webp.pam +4a2aaf38eef45410280a725d7452bc35 vp80-03-segmentation-1441.webp.pgm +ce0a53e7d8e4bde0de3fd5fe268ce94c vp80-03-segmentation-1441.webp.ppm +1b7265bcd32583451855212318d31186 vp80-03-segmentation-1441.webp.tiff +5068b19e13d158b42dc4fa74df7c7271 vp80-03-segmentation-1442.webp.bmp +b7b7ac6d5e7795222e13712678fc3f7f vp80-03-segmentation-1442.webp.pam +39e48e2454516fb689aff52bf5b4ae65 vp80-03-segmentation-1442.webp.pgm +714d34a4bc636b5396bac041c1775e47 vp80-03-segmentation-1442.webp.ppm +0015813e079438bb0243537202084d5c vp80-03-segmentation-1442.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1404.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1404.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1404.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1404.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1404.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1405.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1405.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1405.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1405.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1405.webp.tiff +b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1406.webp.bmp +0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1406.webp.pam +41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1406.webp.pgm +21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1406.webp.ppm +5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1406.webp.tiff +788d879f438e69f48f94575d2a02dfdc vp80-05-sharpness-1428.webp.bmp +c51ee0ed93ed81b4cc58d8d2ddc93afa vp80-05-sharpness-1428.webp.pam +4be708158caebde5d4f181b5574ca38b vp80-05-sharpness-1428.webp.pgm +8675a10c26a5f367ce743eef3e934930 vp80-05-sharpness-1428.webp.ppm +a188cfdc0bdd3a3d40ec9bb88cb11667 vp80-05-sharpness-1428.webp.tiff +5127dd7416cd90349b8b34d5b1f7ce4a vp80-05-sharpness-1429.webp.bmp +a314ffdd4c568c7fa503fbe600150d54 vp80-05-sharpness-1429.webp.pam +94b28172c35b4d8336c644a1bee01f8a vp80-05-sharpness-1429.webp.pgm +828ce5ec185db55202a9d01f7da0899c vp80-05-sharpness-1429.webp.ppm +54988f2aeac058b08e90627b39b7b441 vp80-05-sharpness-1429.webp.tiff +b2c9a7f6c38a92ad59c14a9fe1164678 vp80-05-sharpness-1430.webp.bmp +c03140c24d0bb238f602b2c01f6fbe98 vp80-05-sharpness-1430.webp.pam +4729c407cc7a3335bbff0a534d9f3a9c vp80-05-sharpness-1430.webp.pgm +be138896b80a40d19f0740a58e138500 vp80-05-sharpness-1430.webp.ppm +bb6b19089f93879eba2d4eb30a00867f vp80-05-sharpness-1430.webp.tiff +0def67a2d0e4ed448118fef3b7ace743 vp80-05-sharpness-1431.webp.bmp +7e4b9e153e7e1f2c6cdac993a8b813e4 vp80-05-sharpness-1431.webp.pam +547ffc3bca806ed8ccd5e0a8144711d9 vp80-05-sharpness-1431.webp.pgm +ed4a1efbf16d356da90f42a4905c998a vp80-05-sharpness-1431.webp.ppm +16ad1d77b4951f18252da24760436b27 vp80-05-sharpness-1431.webp.tiff +bbf5499355c2168984e780613e65227f vp80-05-sharpness-1433.webp.bmp +bfac8c040851686262a369892a849df8 vp80-05-sharpness-1433.webp.pam +728c352f485cdf199cbdc541ad4c3275 vp80-05-sharpness-1433.webp.pgm +f92f6333eb0d20b7ec1bf7c0cba3396b vp80-05-sharpness-1433.webp.ppm +820193c918fda34ec69b9b51a9980de9 vp80-05-sharpness-1433.webp.tiff +f1df5772fcbfac53f924ba591dacf90f vp80-05-sharpness-1434.webp.bmp +5a42133ab3abbf4f59f79d3ca1f860e2 vp80-05-sharpness-1434.webp.pam +8490ff50ec57b37e161aaedde0dd8db2 vp80-05-sharpness-1434.webp.pgm +2603f6a7df3ea5534ef04726826f9dd8 vp80-05-sharpness-1434.webp.ppm +82f4a703c24dd3cf7cf4593b5d01d413 vp80-05-sharpness-1434.webp.tiff +cf0cc73d9244d09791e0604bcc280da7 vp80-05-sharpness-1438.webp.bmp +e576fd6f57cbab1b5c96914e10bed9cc vp80-05-sharpness-1438.webp.pam +93c83925208743650db167783fd81542 vp80-05-sharpness-1438.webp.pgm +e6864282b45e7e51bd7d32031b6e5438 vp80-05-sharpness-1438.webp.ppm +dcafcd8155647258b957a1c1c159b49f vp80-05-sharpness-1438.webp.tiff +7534c5260cfa7ee93d9ded1ed6ab271b vp80-05-sharpness-1439.webp.bmp +66fc50052705274987f8efdfa6f5097a vp80-05-sharpness-1439.webp.pam +8e6a07c24d81652c8c8c0dfeec86d4b7 vp80-05-sharpness-1439.webp.pgm +8c1a1d0aa8e4f218fc92a2a677f7dc16 vp80-05-sharpness-1439.webp.ppm +d00ecbfea2f577e0f11c97854c01a278 vp80-05-sharpness-1439.webp.tiff +bbf5499355c2168984e780613e65227f vp80-05-sharpness-1440.webp.bmp +bfac8c040851686262a369892a849df8 vp80-05-sharpness-1440.webp.pam +728c352f485cdf199cbdc541ad4c3275 vp80-05-sharpness-1440.webp.pgm +f92f6333eb0d20b7ec1bf7c0cba3396b vp80-05-sharpness-1440.webp.ppm +820193c918fda34ec69b9b51a9980de9 vp80-05-sharpness-1440.webp.tiff +98683016a53aed12c94c4a18b73b0c74 vp80-05-sharpness-1443.webp.bmp +177119e26f624e1bbb4fcbe747908ecd vp80-05-sharpness-1443.webp.pam +a76c918a727cce42e41a9c72de5f76f1 vp80-05-sharpness-1443.webp.pgm +d6e6cb69d45ee9ef4b6af06e9b38db60 vp80-05-sharpness-1443.webp.ppm +b0bd5ef4ee3da632e829ec840d5dd8a4 vp80-05-sharpness-1443.webp.tiff +5d7f826f8ffb21190258a4a1e5bd7530 bad_palette_index.webp.bmp +75da37897db997f7bf7b86cd0a34eeb0 bad_palette_index.webp.pam +5b1e96464eac7a124232de5732b703a0 bad_palette_index.webp.pgm +8533d3a64063c7120879582766f63551 bad_palette_index.webp.ppm +953e6c351bda4b4b65a6c33e0f7b85f7 bad_palette_index.webp.tiff +94cd8c8c425643962da4bb3183342a5a alpha_filter_1.webp.bmp +a717bf0070e8264f253cebb7113f0555 alpha_filter_1.webp.pam +d6416589945519a945d43da512f75072 alpha_filter_1.webp.pgm +76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_1.webp.ppm +aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_1.webp.tiff +94cd8c8c425643962da4bb3183342a5a alpha_filter_2.webp.bmp +a717bf0070e8264f253cebb7113f0555 alpha_filter_2.webp.pam +d6416589945519a945d43da512f75072 alpha_filter_2.webp.pgm +76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_2.webp.ppm +aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_2.webp.tiff +94cd8c8c425643962da4bb3183342a5a alpha_filter_3.webp.bmp +a717bf0070e8264f253cebb7113f0555 alpha_filter_3.webp.pam +d6416589945519a945d43da512f75072 alpha_filter_3.webp.pgm +76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_3.webp.ppm +aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_3.webp.tiff +94cd8c8c425643962da4bb3183342a5a alpha_no_compression.webp.bmp +a717bf0070e8264f253cebb7113f0555 alpha_no_compression.webp.pam +d6416589945519a945d43da512f75072 alpha_no_compression.webp.pgm +76c9aa742d9f3c8fe0cc568b939e688f alpha_no_compression.webp.ppm +aceecb9488e46b9ea91bb83b0cbe6e17 alpha_no_compression.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_0_method_0.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_0_method_0.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_0_method_0.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_0_method_0.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_0_method_0.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_1_method_0.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_1_method_0.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_1_method_0.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_1_method_0.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_1_method_0.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_2_method_0.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_2_method_0.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_2_method_0.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_2_method_0.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_2_method_0.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_3_method_0.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_3_method_0.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_3_method_0.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_3_method_0.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_3_method_0.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_0_method_1.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_0_method_1.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_0_method_1.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_0_method_1.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_0_method_1.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_1_method_1.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_1_method_1.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_1_method_1.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_1_method_1.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_1_method_1.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_2_method_1.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_2_method_1.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_2_method_1.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_2_method_1.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_2_method_1.webp.tiff +1871cf8be60ff362642dd3f4f2f50fae alpha_filter_3_method_1.webp.bmp +33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_3_method_1.webp.pam +32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_3_method_1.webp.pgm +8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_3_method_1.webp.ppm +8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_3_method_1.webp.tiff +d9950a87e5cf2de155fcd31c01475e93 alpha_color_cache.webp.bmp +91cf9afc53048c718ee51644dad6a34f alpha_color_cache.webp.pam +9d8e492f6b7227a74c04456c84e5113a alpha_color_cache.webp.pgm +82b1c0db2dc88c8fc8c109b622cd84d0 alpha_color_cache.webp.ppm +968aacad5d8a930b85698fc41d1a4f1b alpha_color_cache.webp.tiff +311d2535f9a76bd5623e3cd05e39fb89 lossy_q0_f100.webp.bmp +21a9f2a4ccc334498756a4738fa9b262 lossy_q0_f100.webp.pam +a6b90760b4aabf97de791615aca4a7f8 lossy_q0_f100.webp.pgm +3401967fb1d77a298198362ab5591534 lossy_q0_f100.webp.ppm +1c4cbf811d940f2f347c541606a78870 lossy_q0_f100.webp.tiff +b5c041d9a4f47452072ac69eaa6455cd lossless4.webp.bmp +85a73782fe7504bae587af5aea111844 lossless4.webp.pam +147b72dcacdb989714877612e927504b lossless4.webp.pgm +f434b118e30f3146c49db487e1ff2ba5 lossless4.webp.ppm +22e4581c62b8f17f2fc8e9c3e865fdc7 lossless4.webp.tiff +9bb8a5556e6c7cec368eac26210fd4a8 lossy_alpha4.webp.bmp +63945faa35db26000573bff7a02bba2e lossy_alpha4.webp.pam +96507416669c3135a73ced1b4f79d45c lossy_alpha4.webp.pgm +2f761d6794b556840b572d3db93e7bee lossy_alpha4.webp.ppm +70139ffba2b922bc2e93de3aa162d914 lossy_alpha4.webp.tiff +501113e927e73c99e90f874bc635e06d near_lossless_75.webp.bmp +dc04940d59a46f514c00cd7c90393c13 near_lossless_75.webp.pam +ef032f8837e7245def5ab012f7a04c8d near_lossless_75.webp.pgm +a81c1e1c64508cdea757fd2ac8f9d31b near_lossless_75.webp.ppm +a23482cf9c7e4ed2c4e5bc2534455dcb near_lossless_75.webp.tiff +34efa50cddbff8575720f270387414c9 color_cache_bits_11.webp.bmp +3d9213ea387706db93f0b39247d77573 color_cache_bits_11.webp.pam +28a26055225a9b5086c05aaf7b73e3ec color_cache_bits_11.webp.pgm +ebdd46e0760b2a4891e6550b37c00660 color_cache_bits_11.webp.ppm +a956c5897f57ca3432c3eff371e577f5 color_cache_bits_11.webp.tiff +7823bb625c9002171398fa5a190fe326 big_endian_bug_393.webp.bmp +7d41a1e1f15453ee91fc05b0b92ff13b big_endian_bug_393.webp.pam +1700bae9a667cd9478ba2b8a969491df big_endian_bug_393.webp.pgm +f8d4f927b7dc47d52265a7951b6eb891 big_endian_bug_393.webp.ppm +ffc5abfa7d15035bafc4285faece9b9a big_endian_bug_393.webp.tiff diff --git a/tests/Images/Input/WebP/lossless1.webp b/tests/Images/Input/WebP/lossless1.webp new file mode 100644 index 000000000..1d561f9ad --- /dev/null +++ b/tests/Images/Input/WebP/lossless1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5eaf3d3e7f7a38487afa8d3f91062167eb061cd6a5dfa455d24a9a2004860311 +size 15368 diff --git a/tests/Images/Input/WebP/lossless2.webp b/tests/Images/Input/WebP/lossless2.webp new file mode 100644 index 000000000..1c975384f --- /dev/null +++ b/tests/Images/Input/WebP/lossless2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5131b5d7c0ba6bd7d6e6f74a325e0ffa2d388197b5132ed46a5c36ea8453cb22 +size 15898 diff --git a/tests/Images/Input/WebP/lossless3.webp b/tests/Images/Input/WebP/lossless3.webp new file mode 100644 index 000000000..34bc7919f --- /dev/null +++ b/tests/Images/Input/WebP/lossless3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bcca2ea2a1a43d19c839528e9b831519e0a6875e4c9a2ce8bb9c34bb85ece3a +size 15734 diff --git a/tests/Images/Input/WebP/lossless4.webp b/tests/Images/Input/WebP/lossless4.webp new file mode 100644 index 000000000..5c46787d1 --- /dev/null +++ b/tests/Images/Input/WebP/lossless4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a96cc5243569ada325efadb3a6c78816b4a015a73283a400c5cc94893584901f +size 4332 diff --git a/tests/Images/Input/WebP/lossless_big_random_alpha.webp b/tests/Images/Input/WebP/lossless_big_random_alpha.webp new file mode 100644 index 000000000..7f90af8a5 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_big_random_alpha.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a14082445329a37dfcd2954425f87f0930f81066cd1ac7d0624b6c994c4191f0 +size 13968251 diff --git a/tests/Images/Input/WebP/lossless_color_transform.bmp b/tests/Images/Input/WebP/lossless_color_transform.bmp new file mode 100644 index 000000000..e02262eca --- /dev/null +++ b/tests/Images/Input/WebP/lossless_color_transform.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0739131f43940b1159b05e0480456d2c483a1f4ac35f4dcb8ffdf2cbfc2889fa +size 786486 diff --git a/tests/Images/Input/WebP/lossless_color_transform.pam b/tests/Images/Input/WebP/lossless_color_transform.pam new file mode 100644 index 0000000000000000000000000000000000000000..57e467942421f6e8e0520af6789f5b4c8d6a7035 GIT binary patch literal 1048645 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�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%0A`U~0r0|5p>P$07adqJWAvH;jY83#;%tI+`w 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ʀWlC{AwU5@ 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ೝN6vis}ZJ)iWL6zJhz0Ae4#am>1j zAlx`?ZATbBADaUH%)S8p2f7hp%`=Y)eMBMf=ncZ`2tWRvufOx3^8del`B(qzqk<>y zCtkciTm7ED@1vgRXRej675;fsALax8v9}9C_X=g&vv~AQ@z6~U{22J7=X<8S{$zzr zdsdYns=~Bq>Heysebt2jZa9C}j%Anhd~3EW)Jo5n@ZXqhboKdbx`Wl7q4L&n3E>~j zsfli@ift~By-)(%pLjMuA@Gmpl!Xhc2>+_ufW$uDcEG=V0?vOF@E;gXj1JCu`T_q# zu`Yps>$O?L{>_)Bn>F@7JJon*s^N5+=Kn;h?%1S(|Dhz3AHctAZ@hYUOyD1_+)48f z_?ORxnfy$LX#OVyB{ctmqPW5zWBp} zq!WPmq?rgZ{AUp$g@Bz0LMK4@FHa%>8U;xuVEX}CK@i^hC<62$ED$1y0w`d1{0;gDJkV%2Se&N4r zfGq&<*_jDq6(D7Rr~nE9v$7|GR0dE0I28cT2zvW1?Do2S`wo&H&>u%WWAm>w@Gos& zCO?oI!e0jdkzUF7L3OjSkHNkw0GgOg{;}sPg*{(!9QDuS-yHSt9v#JLpWO4+*W1_K z%{^c3?U@4~a0TIl!Ign)LCt?A{NdyeH*!1fU&8;j>A#_V=96jzk9HFH)A}>`$wB$Y zG*FTsmW<%Pz+b#SCexTyg8sOC@g>3L;DTp+tffUJdhnfP`+oeM^j`~qvL6fP-*Nt& z`7r}OjC}}yoBJ#Ljrm_2`;+|$egOadfIl8!|KTG?jvPID^vJQJ>C0)Kn18l?VgBDv zrr(>I`2g^rU;JcI&Hu`mtGmD6v*(*VB_EV%6`=j!>}T|XAF(gMv;)?Z0Oqcdf99~z zNAC-u1h5duetgUAgMaw;kN#8mfA_2Z_CG(`{`gzr&G+Z4-V64A)ED`zcGp*VJHN=C z|0Hkvqx{r+==m0e-UR%MWYoWS=vIkLdzN-yD&w^0nev8HgnzX<|AWl@1^zhU2LnF_ z{@a!W{&Te(XKP=WuFVbg)b<8zyF)I4e`zz}pIaN2JjET{1?Og!}-VkMNvOz zenMWrKi}`k_YwYig#Sd&=y*BFOfG?d|}X1=4XK@Lwgs zY!4IuXL2A$L76Im{O3Cz4=kGnaUh7+k87<4NEx6h5Gw$E01r|CNIN)F0`LuOk|6F6 zc6JEc7zl5CDF6Tzod~sYkl7bNM3Dbf04o3tgNy>e|7UdrfW53m(P>|mH}1-oH76sU>8Fmwf|NJvPuAx0uldM+78G*Ck{FwNf4_53V@UXnHY$T z0FWPTU(Wov>g!>OK#yv3o@?@PqgCCB1s`tkag*{&!@sBu{{~Y+F`De=) zv5zD_`vLz$YW)u%K6>QHv13P%9XodX`0?Er_n_^o=06Sd|31wB-2BJ$+2$Yc-}{@r z>95k66zCCEfcApdbcE%WVLU7}duss3fuI8TH>UIZ&^F<3od|kVJ3uxCtmz2Y3 zEa|yg+C})6EBtE#|D%-^hpWmDR+S#8F4~9KN8w+cBhDYm&lZXO1^#tq6C?G#A%TCm zqAgt75-Dtm<_Y|_l*cxf#-A%pJe{B1P!!)%65CN0DX0pUxPq0n!P*9YQ z{yn2Uz<*?r@DKJLjCKqB+pf>HT%B#XO!M!+zwu1E;Z(Z*M7sXiq{6@EP_pI#lAnZ2 zoxhrYw0)Pz|MGS70NxU`|MDzV08J9C5|+~o#+%1Ug7{`)5Tpr^#z4%1Y!U?5IMuZs#Vu)Zm0c!tP2CxcH z6)=SW=@0(@{&#%Hk{n2$tOVFbfOdnm2p|Gk2gq`|0U`jV4v>y1H9$WE|11Hl0GP3$ zObs9^0P8IpKpD^?!083E3P2KMdcqvgM+v}T9q9*T>Hw<(P86hd0Pg8^=MK%kGw_e# zAE6K=0&;;r3aTQ>4}u?x6Dk*c3& z;i08JAm(4#kH|+-A94P6k1w}+Df~I_X?niK`{R%wt_=K_msfS(e-CGV!2kVO z$&Um-#{4V%Po6w==FFKBmrf+^C1C!ivd#bJyO8|D{O|c@H{ieTxBKwu`?SwW08WQk zI}W60N3S~_!X!fLN&qFlj)s2AP$z_*WJ0uSV?8o-aoIE&OHRr*`99?S|>Pnqe>D-xG3mg)7@5 zWv!8-M#6tbb?n88IN<+$QR0~b2maB5%1EgzR9P3SGw}Czx5N2QjP{Os2S#Ib{sYT_ zKEVHEvh(t^z@G!3&6j4HFU&NZpK5ZFADI8+gn#Xk$=X9H!XM^eVt*z-tK99S$q(AT z6?5V8*)Woy5+*;v;*^;G7*2chqvs3w)BO89`CftlggXcPADP%OJifhuBv<(_C4lq- z7aom!AycE0r-Fj7$sntEfN6#&p1fS0K5fR1_=E# zMSwFABwNE&2BZwI-2j~lWsb` z&tIqlBnmPDz${1xf@~u|HGmUALVqC9N&vh+7u>Qg`evI02}-3OY^nfFf}DXMAeYqD zI>7D>ApV_>aMl44;{TlzU|kGkih%dHDFDAr5%3NseD2)24f9X<-@Pk`e7=DV0q6}d z`Fa066cmsagirVy2!EJ=_A&4+Nb;juKQxcuzgbNFVg8Z)>uGP1NQgK2b;dqN26@_N ze@`#QKC$N)=D!Ws82}6WfQtp!2hBeUKm>kL+2LiRdP}*+*G2iy>|cj}z<+%h4aj^F zZLro~d%iog*-z=GA%Hh1#N`5!ft{fF7x*)pbX7x%U}f-C!RtVt@V=n=XT(GJGwx9l zkblE}!NtG_OE-CG&(}u&+~-C1&r0$`=dZCpoIgi?z<)FIW1PQ%Kb$|3AI<$0{-8gS zABF#k6DLlcK7H=&*$WphUb=F5-)nne{^vfLUHEum>9eKf&zDyv`QP&!n*V*j-M9aD zcWd7F_1&`OY`r z(Z?gr|KEK2)c<~W;Q4#YCGW&KJ{b0X(melVG2#Dd-t2>XjQS_U`3K%8^4tadOBDXS z*GjuCm$hFg2mBjPS76lN#Zi9;{#986Kdv1+W!e+Ye}V8X2=_D&2J3snbv@ylu1Hl! zq`Wm!4EWc@a$K>kmGO;b3jfrGqQsU`IR6a%Ya0Sh&2avN{{Z1X?(HZ1r`-d)0RIT# ze`&h?#+)WUEth5y`!}7JIVO{evY*!oN69_`~@ZO6>ecznmu_>O_G?QLz1Mgp`9kSGY( zzgc!32ya;Dm>^UEc$+v&Y5*w$Gz!Wp0!$5nH#zaISrCf=+XR@B>j0A6T{u;9Fn$k9k5GLBO9MSTYubkeL<{{D%Ut z22cgC8t@zDK#%~c0H`NB#0>pd0LYR#NCrWGKR>ttO@Nd9Cqh9|mI3SqFcIRm08<1& z2Apm%b0C`qNfm%XfPGjEgrpPfOawV+1et9C*;PR1fC$iEk{}cUu>E%6pKV`^fO6pf zZ6-hXPf$q^{ygRfUjpyxEAaoE--JY5_(sL^3;yK^Sq1MM1eqjE21@Mw6!oVl4AAvt+e!zd3`GHq~7~zt}MJ{dMW_+DOc**!h0L(wir;O=m zyBC>13LrD^L*I`D^b7qN{22Iy{|@|hvzJW!>sBu&KYJAZI`V_$=kTFJi2aWs^_SS^ z#PJiSPMtb?_RRV7=PzBleD&(p8#iuTc=f`(B>!ytezCIp70kcFU-(Z6*q?Ve&*48d z1?XdKFM#_3*0lpT4uo3ZAqkKy=+6g14|Rmm3tl@8L*~;dcdF4p6K~jmofH-^T)KOD}T3(JzpL5XY5nEX|C4g9&Z{5 zH}r+U|C*k#z&~2t6wR-X<e1!Bkia2$Mcrb_>S^eer2S@6_(g1(AeZ_YxQ+^ zcm}%MfWK#8G%_+g?H=3}=sytYIg#wVIMsfg@Nc~$$*tWuI6z#q=PY$jaB*gsT~3YHN5On!ZbW08s9!Rw!MFBTSs>_j1J5W@vjn~od78Vr~=#Q$m|V9GuZY6SO+)@ z^v}wIg#Q@{aB?6P0U`q|1jv8U0F?oV0N%3lU>d}sP<|r*9SLA?Y?km}G(g_>cdRKmKm0=P^KfKMs|6acz^ zS@h7N0IC9_0HOgV31T}~2ZHzv0BaTC91=tU&?HDthXAQ112RDnQhPkC4rmI5X&~DT zU;%*by?2=Wi1~l*?i+8s{^px{&KnB^{Ff&A;n5U~{UJ{hUNOzW_oUyW`L^x1k4OXg zHt+{;tYe=@{{4QKfA9GC#ONrF`j^u_Vg9?jJ9*#_4t!_pm|v|A4>5J`hSQ#6EETu>N-5b7gg9 z1=Ier*GrNgT7UKa2SESB41U!7BlSNH^MCrx>2v4KUA%bl%9ShEuV24)>(*`b@?X7k z?EPaf{}}t(^EHwm@Sn-geu014&a&c#;*FO#W=X)KheA0Kw5|}~IMBm)jlnq3pF1g( z<-kK#z#~#1BLP4E{)6wY;Xft7sK8%+_LF~jzxC;Rs|9bTnm-ty_@r(6%d%|vC*Q-U ze^JoDzhvYN;orwm|FVvY0{=6Be`Sq~`d47!=U}zK-&L3m|D81)^2mdAcX+B5zH^Ka$N)!G_e$e(k0{Eve@L9vy zKjGS~@Q+vRidH%BpNkOwF#qXLDd5k6Pk%9z9~1lg3jE#zpEu9r&2xKl$KAPb{v#7P zLlZj&$F~oRZ|@u3*40;P@h=j!+&5nf`0+#q1khy97s8*(WFm8uU+w(T z{L7v%n*W}ju1-1XUrzhPRfG`&;zyi6_>WgxE(9b$MXVjg{1g6cK~>S40RGbUrOQF`gXF)p)r9-X zfBu90u%QnOe^%_HEnkNI5JVb(hW-Zr8vFqMSj4~3Uj{yz`|lC_>$JaxziIj^{EyT8 zgZ;+*U$}7L(&fw7u3fuv^9JDm@=G$u_1fKAZ{N%^{|5dr|D}s1Me{{DJ9Bw15H|(e z$HTpV`@dpQ@K8HIJHpxRU^aqrUi6;}g7mIoYYG8w4ST2;fQit*{OrH4=l?hC1^ngb zKmO7C^-tYf&3k*Y=>zxZCmrc8Dp$VBUHT$#4$hy`o(0jj3q$vce6JNxyizjyQpphZ zd0jW~Z#!Snbf%)=WM$29I)C(h0e|#-i}$$-_tN>xz)#I~z#juYi?wy0iPphz^I*8K zKT_Wp2K-%}k&3oxX>+WwA(mSk-&P&pT#?vNl6>B=A3;=qCK<#r)6F{4@EPX+A##{x_bPZsg2Q8nJ&p;Xhf2 z!qOjj2KY%}yADI6MPwp6< zf6g$?{|>@`Y+KLhw$8ykSRGvOTv7$tB7jPO#S(yv?+tlKH(2Wc_Jc(M)-(gO3_u0I zR)DDk@HS%&AiV%aL2L-K@@6jp7SP5(Rs`_&n4g!Cp!XIA%npC((#r*U7#s!b} zL0elp6COdmf?w#5q~9j~H2#kDC;YMf0Uwh26aVI}E-?Q%;Rnv&Nq&s=H|IPk{P*tV z%n!#sIqlDZe++yw_J{XBc2u4J$&)9~oH=vu-1&=_E?vE9%s(_;1s%tC@J!b??_GXy zdEamM?vvOb@Gsj{R=ikTFk6^AmAmoex)MO!!D~ChxN+no_XY5-G3z_R=9r*w5dusE z{o)Ni*6a&-lmz^jufO{+|Nr94zyHC9)xW&AnDf?D{rjGgkGoP|RIPq3@c$%#`lEu> z`voz;|IK0p|2ri^w@Ul3m-bvK>%3Inb^!xF6%D5HHP`dt3#GeO7DO z^W8=GS4IcgMuV+G4*Vl^y%ASew4yy)+7bo)^XlT;U5PCf$&Dq+=ZaF#7bQ2BB({~u z^DCppfPZbs)evfI4gmgL9p3&f51hZde>^-qnsyH@`3Lq!`i>_J{AvE@X#Ot?{4Y$4 z`3L+F`%l6AbLM9a{FBD~uf(gDY5tk~EJVuze>ML|egJLLf5|#4Lyt zLEOiSU{m#`}%uf{@M8j{@ZXh;o`yN0%L&70Mbv9mp#9D=_Iwo zfuGc64dp;`Gx(_jAhdT%0EmIef-(ML{)zwW_?>?RJ|5^y>2+-Q+N1PGCd&ffp^x+5r zCqn=H*04thL2t+gVc8e(llz(GucHFr`|A7u-$!Ln-k;h2_DtRTzM+qM5}&(Pzs}kD zMc%vv|91*Q_ltb57rS36LC@E~|Ej{jqWP@A|3qcgQNkawzc_!y{w~D+d8@9R<(eJ4 zYKo_@G1u2V8g3g7w+x1x1|ki8fPb{6D_YeNEo+GtHO3YGFIFZumID4s2mX1LF~Ywt zTtoN=+gkk={%(c;%~?z}$Veq+Av+FaY!xz@`x|1B3_{?++UVc=8X zpRQNTVZ?SOyp=+>^$t@T5jn%f&-c|-*=CBPaH&=^Qt z0W1Vq0`S?kgbn}kE;0gux0F-?^#52Q0?>er0)YQn3ZPX0H9!?Wo`D7{15O>l{70i8 z?FLW;qz@nxzY+_f*pvYHWTGJL2OIj^Fi5HZpjejh--v()L0Sb^4Y1u{ z5ZsOgF$glP0I309z5D7*FVQGs><3){Z1+-7peX=#fKmYdI`d=UkAZ(fe|!)8o@Dz2 z$!`~?Sf9-)kcxCaDqKwnmisDTC zOYaxyPbT~^q=y3N{14&JvVeVIEJ=M#^20Pw>N@;U)P9A(ll)-bKa>3M>ZE;XkfR=2 zVE)_Cq+XAE5rFI`eB;iNfggi^1Am16jQ!{3bWiE}%FGW&{js>wm+&Y3>HP7?jQp%g ze%SU!)A!_wQ)2$5?Mw5Iwl9*Om*h3YA%41pPd0Ev(sAGiUf6f;esH&XvASdd=D#3s zDtCKo`-|}xpFjBgdj3-da7NJDDnJ?m4+TLuAmZU(fCNF@7x3tgFvdZp9k8|vz`iiF zgQX+<<9B}SU|)apU%&bv|LZ})&)-UJeQVD3UZDR$Z|t*LIDf!@;nVz?kMbu!D3DSA zB0u2&YRUM^CBwH$2X2)13jEtI5dMvD{wFG{4g9N0VgC2I0Dr{(3jh3t#+HD)YdqYc z@E;QRBl!XRy9NHOvEs&fK4O1Yg77a(JzosxpW0NC6z324M=NRx|AwYuE8ySmgYzHm zaZmJ(hlWQdCjkG!J&}F`|2av1<{kKJ^3!w{&VRb`l$ihH>4u|X{HwPou>iosYJlWG6abL`b_65`Vn@LEf2{)6%!N_|@PS3DlK##>5Oe_aH|^le zKoHx(C;@CsSov?B$$uFM%8~$40h)M<06(+@0Nd64zw)xC#59UX3E&d3DH!G-{{jAU z)D-wLAvcWuWwRH)OqDQ&ze#;8{9$LoHyB{@M9e^Irzt#%l#sEC012&*)zk`A*npFV6WB{&<$mNB>JI29h7mePrlIyg$u9 zt`T|%8TiLV2J?^0P~fk-y_o#a{I|6s)qlid)-Rr)xexHK0s!`t{M_V=Lmp(!XZHDu z`Pa-pEA}V+W#(ruPWd7HNq;@(0nY#M;X~Z&Me9%RkJul{&uPqj+P3et>(_4Gyv5{) z<{#U)Ut{ni=3n>u0sa{IVmTHF3`7PBrVD8P6Wg}LwrmVm%q;6 z`Q;k;NA4E|UN80#{-wjWO9yV20sb=WSfMFaQ~5b@40l>wOKg-_UnXy+vT}dkcMs_or(2iTPh;>|ebrF0sFuf5!fia>V}WP}wB;UlI?N0RGWHQ8-Wp_y_z2 zK3{>?m+uzyKkm*Ob>{;9lKkxGgZUo?{I@j?Z?5QnA-Ct5vi4$J_96jR1UM2z1t9-f z2}lfNS^<0))BR@(0k|h~<1kwYDE`5JS$LaCFF?-;%9a2QUaJPk@epVRuoWQuR{@X; zz?1>D3XmX3+W}55Sc-s5CBPVnK@hE_6j0T7VtWkiqWdQI` zC15)kAI@^B04)K~?R^>WH|M!+-FbAOB>9g)eMSHT|I`I6vHr|`^d3KiKYKfTb%p-85a|8OaLx~Rdnx=O zK)57vbu#&BaFUG9%b-zLX)49XEE@99&KNke{K6R&C?xxH2*gFQR^?e zJ>Ct4LjgZ`VT_HBw|m;R#kXyWY~B#s@XYRK9#sOcDGd0pzjq{i!Rv>Dq#f);Kv}=} z^S2Cms1d-v!0S)`-Gjf&=Kufvc+2DO_%_{Ns(3fj^U+}FvxZ$?7vhkIh0pS59u%na zC;ZXD(VX z8zuZ<{wG6aF#ic-{)?FW5dLER3IBW~KY;%jVjs@@5RH!O|nFt~@=;TZggc<;`t_}rB4CFkC2+U4l;407`(W02BZk@Ri}eZ3jy+ zASxhL06GD-8O&V)SSAKCod8IH&I2(EG83U7IF=&-tN~0Ggec$+&UqvJGbu66za&4p z+Y5gT{!siqtV3kt6DlS6H>p4AuLX~M(_osKe}It(KG^$w`Te-bFKIJ6Ix>Wt{K|d4 zbk7&ssc;auXkZg?F`xjX(1ZVS;2Y~T?)fc}7b6q?fap5;PsnG`Z+-{!kG?P-41J{M zEBrU!pD*d!xqPu~yE zPwy{_$q!Q>VZZczNq;l%$$?L2;Ac11p52oC*xcs;&UxUt|B=J!`6BkG`9A^t!}_1Z zey`I=elA|LGoLqay@cck=KpTy>?Ym8jh9t+a=a6bvKQm^*~7PodFO@3p~lU=&ChtB z`SnY71V~!|_8GT~Q59HI1w1?yYR(8+p9NteNIJsbIu|63;I%=}8&Cetr+?${|NEal z|I>HJH{9P@{%*AEqv613jZ0q@Eq|4__*wq!#|7yR3lr}aM&2q4+u~|bv;oR z;NKQ6X-*V2CUR?&+p1GrDpDIV@K5GdCW>4!asJ`@=1@y(ptIfYz&|iFo*W+)_zxV8 z_nk?1U!Co|xzKTAp-thB*#9D8|5?Dl>Ff;P-vsl2g66;BXqs)`gOhcP{ZlRz`zJW_ zvol_`MED#2&qT_n!sRgkgg?!HaV#jYzrQFL0Q?JS{=GE+lKkY3xN`yj!3l}|$G0Q- zX&u>8J+Psm_u0*zPyMRx7caDI!j;edoGgw5Ngd$q3t$D{)Btv0015!=fGlYT2>&?` zWMUxG3gDo({E{jFrpq(~vf9B$1*`;AgATGp0L&Ad8yB%}S5_w=!+$dnq#6L{=ipz< z0Lnm?1_=6T6=@j%_{V?YFQ*K^Z!`+Z3h zpeo5KWq=a}k^i&HHErNC|DDgb`*@`A;9AtB!2zX)@-`IpI0XY`Z&mjp;YGx0H@zmlKM z-@u>eJg}lu{IdeYpDrVx_`C2o()?qquO9L#u|K{lSQ;1|T=HmAThJT&+keb_*4SUb z@33FCdI|d#|M31;81E`*6gY34VCOnTOXzI_`79g-j3v_Ws#V-N1;E`q+Fr0|fu0n%_Lv}agx8)FYJ;F}=fBL0t~(&&uRj0F?|$U;p!a zgD5Uj^P^G!XD#zzm#kR$PkmUJe6LVO{foKJ>lKXpmk!-5>%Ugka|Q6PXchR^ z1OCUw`78VnxN3K{_r*QK!SJ9z-0xNAAL#=8M%53MY#der~z#l!|D|G&Ve+vda&(i!io|;DT((@`U|KcajKl^#p)9vhf83mAWP#XlvP>{vHiGid7kSf3t0965} z9ZUtV%>WYxWd%VH0D3@vU;+LP|D_C&B*;+!ykn&h5bvfM0NY4(z|gS_U@;(j0_?^B zyxnaDAn>lg@E`565+M8+4bUpUvfl(jG8aT7(jD``Wdy*1zls23 zpsXxN=$~l@uh9UKn2Y>pN0^O(Yy}{%@d5tp10n#5^MlJdA>u>EKQFz+fq$I&WAE*V zqaK+2@TdoS$fqa_&iPULjDB0TNWF9$y8 zQU60j0|VIeD|@~=JK!m39!%#~%s&L4;SXOryjFOTg#Uac5Z$1Aiv^z3)N}Te9$%J* z{q~=X@n=Y6;m@Y84Sx7K<4;Cs8Qy^KFVCFtjH?cpBdiLOA3f@!t(E4VyLe?0|LYeb zf7|s{+i#q|8TJSIh5j<_=?whXLmmkK6??q%L$N=%dLjAY#3y5aS#0~B!cjjo|Cc!P z$=$xUZo&M&^orcF=Z)8Kvu4xE7fm<#XYy~)-*e7i>h10A>f#m*)Xx7kQh= zhacZs1(n!{BM=@Unl%K1^&%vE9*~H)gG^^ zK3ZLQq`K@-b;o?)aLhLv4vzr-f$)GYjO3@s9qAg6bc{vX#QZl6#v1#h^}Vs0u2^M9 zytE}z)Rf4pOKx{1U#v`RESr43C@t_$)t_mH}D)U^^6=JrW8H$S6QY z222egpRy(3p|Ma4fAHUt0H+sV!XQ%w;1g>AGYn*U0Zt)61yBuO2&5?xpu)!*6>#bR z>@Z~vB>1NUWO@Pr>hK>$fYbqqfouujYzxp5;GvNqs{k;}OoI4K^fN-&GJq1G%M<}@ z1F#byNsw#`&^ka`0qh1?1u*=_CuTvm1fT?{4oDe*UVsq*8wH`&E9M_Y5p7?#2yy2Z z4G47R2mc5B0c!q*{_IZUOUyFnN6{HL4O-Y>(Q4Di?dZU;B{3j6i8UNZAzoj+bxf&VsmW43iO%lP(cSiEV=m)vK(x1D%B>6da_T0q_7p`2v!2h+Iw{GJ2CNy(i zh53K|bv?M1$&XC@SnvNX$}t|n;qgvPOpJ_R3pS^4dtm-MJMpIGYf<0e^?I=NV0F)G z^k}5wcEx%Xc(@nL6bRkn4E*JQAo<6K4v3JU&_|C0X^%Mjn7HqK^}WCT;-~-cLHpBh z?kRj{vgN}G_owaCUzM+Zjf0)@=RYZ!{-`kZeqqdk|Et*Nl?{K4`d1qGpCJ63_p}X9 zkGtcc@n~pF;6IcF|L*aqz(305hwzWp_r+?uV^tlAGQvNZSD)PBN^Py2+*Fo+zBs*! z&fmbltTsmH-yCjk4Ry8$`nvoAe~)j-9UmK;@r^8p1`o#jPfhk*p6$N9h~%gJ`a=6v znE!dUeI@y6IX4UQ&*Vqo-)P{UX6z66OV5|^Uro4{6V;OZAo)k^UkUh2@V`HI_rC!6KhyEc$6KEGVdGDK z(D;*oYWz9qZv-Ht0o(>-=0c?%Y-GSu0Z`v+09};!gH0PiIsuG=m`2Uz;~}o0LVcJ5JLxB$0ZejdD3P8p0x-N4Pd|wmq{X^Bg|**1xOSm z5}=j9AOH1V|0o*pFOmc~QILc{d_ooAbcD0|!4{6-B>nPl75>@}kf9(O1)&5$5x@vY zS^*{lq6T1PDS$a4D7y+UodDYomTmyc0H+EN{@W@5&47HFb$YPt8;29t)1UU)R=#SKNC#;vFA(8zjM?- zZt|{szBn3;!cSjQyn<`Zzl1-!7Z2uN?Z3Kz!~cv7;CB!O{Es3Nve8QiKJ#&J7pDG5 z_~8Ai{&WO1|BU^y*}%;Eb1DHBCTdnW;Gai5!1@sWjP~jA`CI!B>iLO%p}(2;F_}Nf zpB4K<9y#r)xqk-!D_PEe4`LrP@OcpU=T#H7SrD5cF^r@aH9fTf?|Jj3?@Tp)=o$Z{bLz`Vfq&lorwad~_&ap|{?|*~ua=H+;76SQRXG3h_VbwbtZX<{ z)q1FLcyZX9_WP0{Pdqdc4UdH*qoMFnATr>K^m`*co`}T$(e_b+{}AEd&=;%ii39%S zt%;JRM1DgmrzVA-@22wfhT`e!#Hy086lmAx$|9Q53FU+-^C;XdFi}^nx=Km<1f4c6F@PD5sKNkL+ z`H5F8#;fLICi$6)luw4sQ{l2?n8^>}A1DrS=BLQ-FZB8gJ!1Z`+l%n`=beet2_f{|noDo_V(OS3hg}`Hz|&`$5A`@%VA$*8w9Zqu>8;78o-fInFz{m2bj%ac$cyx%o+giSUoJ1DgX)4WoANc z79(kyoK^tnza|MHFtiHzSF=nRKox)npaj6mG=!l56ag6u)n+jG zpDh7S5TyNJrsz@xe8Y(#bOU6W8o=oVNEINR0MiX%Ad?vhGC`2j3J}{)^DY$tWq>h| zDFH}-+{yQqSF}ZlTl>E8=6xRW%;e`iobx8TyXR8qhK6GQx7moUu1AjR8Q{MpW@|9)wBe!}n=Y5*+ zhvr}QeQEfk1wdB#1LAA9SNpxj`e*j}a^}b4Uy~ofKQ0N_1(*l4eet&=btL?Cx0gNS z6ZBW?JItp9s1D$FvIu{5{>J^&^kdCC_?MpVlA8aWO9uYh^<9;LPqWuc2R?bmn{4$u zD7laJd}XVz>H22Rd|tgSNB!Kv%x89*hdD01rVR9W+Mk^E^qw*QUYdW%@emsE@^WAb z3to=qANw!l%TTOo zAlA?ytLuqZcgD+Glf})+0>HmEwXJG$GvHq`CGeljtpxlNWwr6@2Eac8=ikvD?ClB+ z^#cB0?~n)ZpZ1RK3JxEL4G8>uZY_4;!PsBSKlp!XzLoHwX`%Cj8{V@M2BtNcIll;K^S1-n@76AWP#SCVCqUCAwAMl6yFO7u&|B|rq-ybMK^5gLp zczhW6N9-@jkB7;R%>3-=8QU*yVcO& zlmPsM_iv^XK>Eu>kn9S;wIquNM$6(zkn{n#I~czq{G}a?_r7!kSOMrn5c#kB0&Ew+ z76IB0Ff*YR{!SHORX}S1RRC=UPzC7q@IevK0zg8b%rQZXfFx~}LO{5qjYq~oaE|}> zZxjGj0j3OK6(BK?D1Zh*ECN9P>^eZAAW;CSfH-IQ#MUWj>MV(YSOORc&`JQ{75YjP zq>TWh09F7b3X)a;RRD`Q5IX|IzX*VcfITKiTLG|$#wa2sX7U60n}L7K1|ccJ{}F!# zN(}rpvMoWApRd*VTi>tVpY%s76X6DIFHmVazu5B?M)Hp^0kpw^KSRSf?Q;mbfARir>-pZ;$A=Y-+S@M6AQn5n@^W-_%i&AB?SiCmJa)AT z-!&8|Qw4}d;Y;P0yRy2xx+`%g(S5#q%WIq0jRWBy*X;|CGvY7~^oTPezHMKa@*mH? zd~0UMJF|5k_=i8~Nq$kY`!{*JzRXwnr#~nJ{3G{^18A^i8p22P~hVuvfVg60*A1RxR0RCl(u*Uwy?D;bGFY>|s8~9_lm%<Q`>R7k*dx zz_A5Oi;F-VXbAI5s|(E$ub%asCPaYc~AF`Kt(1XH-S3IBCVy}qEIX^-UnN`5`tQ;a{Yzh%FS``F-z&RgR03IGow(kKYq(on1LYsRz^ z4=0t6rQJ)hgBWM*E4))^w16kY=^cc#Is#|}$W7$e6au_)7#|FL9`)Gv_FU};!NHIF z<6qS7`Ayz3;Qv{{?1RGehee5ZE&T76hHuoR&vnlq9bMe-S=jBLUG}G!g2@HIKNOw{ z!TkG^A#Wl?_(uusi2XaqV(p_b2mbZFaaUKOqFvzMn98e5ZFi-& zRHEAp_)onE_|y3(OKam*4Y9iB2;tuz>;e3He53td&!8tdHj(y>?gad!gU3_-7iM~I zE_P}1L-?EIr|se#&HuT%mNT0C%rr^z(|9D^a9H6#SqJ#<1^iPq|H}#2&P4SR;2$^e zhxwn1mQO|``3aTAk^F=(^MmB4I1pg+1OCJOyL|=Y-uzMF|Bxqlz%9wo#EzDc7b}N0 z=Jh?dvFq2rZ2!eSHUI1fjgS2R{=doie@Fm&L&|@V0K6wy3h=!F8DJfNW!eG6zv%?1 z2ACqiDFdVxKoP*(j1OnoAczi3@Gk;jL_qQ&-5qQNz@|WW^KxGRo@L1!8gFZ^OdVjh z2CxjUHGtFsq5&KNG3{Ux0aXA^fxv%RkO0yjA4UYA0TcnipDJKVfQ$k#1+vWm&IM^h zm}P*l6VW+IszP8J0^q-B08^k$5@ZHK8NoQ4!(@Xn>j1DnlLK*c0BQg`4n!Tmk`&0) z0Xi3o`#HXP_tn=mCD!EUZF|(iN9M-9kP-gLn*2yS>=cY68Q6s{)q*UU_IdURS)1kZit0+2$X$7sH=?ntz%65&lbe z*CD?B4gW2izqEW=Er|1H@M8~o6YGzAdzqOZ$PKO^{ME1-xajbe;F`oG3K(ka58ty6 z{|NeJW0wd3ek)V{LVg+e$vWQ0O#BG`CH0qaf85XAn16Cq5BULR_c8W26Q7d&fc_l# z)Mhyd(E(zI)fsaoK@Sfj{Ywt$Z;5VP_Bvx7{3u z`5)-TZP#S%ldlCvuyFE{s4(6*sFR0=hNJ>e5fBY9^cMv{89)h>#bSmP&93F0v%BXa z2O|Tg25PRjHr?HnX#{gXX#MTu34gtP{I>)_)$fM}J|2vI(XjGO!SYx6i=P+FeOx&A zUfJ?%jeBl&?Y}s9;Pm+ZW8Qs7e5(h2JNNk)cl+m-15-N#sfA#C4)70$(&0cV>`R1U z{wHD)#Qr0p=ujX!;EVQqqrHIt1bV)Jf4prtPUk-mukVZ3bR{YQ|JGCy;Xk?EHTfdo zUncM`qw_CtB}(fO0{JV=q<@Z7S%0epC0;zv}qK zKeznshfR;^!zlrN)c6?ij|w150+fiQHUdxsNH;(Q0I;+QAQ_6-X^nwwJ3wG7Pnj_2 zOQ{1gB|ugg07y$eSSNy@0FVG_2S^O05J^T>w^HUIJ=X!0ifC(D8Vi~tDoO&y?OAn;fI3;GFv-qlfaf4q)( zO(ppu{ITE=aLwQvl4IWRRbb##&iSGFC;s7fl=6Ib8ofpU)bewG7bHNZeK6=Rxlg9$ z%Yh$p{yUd+uNQt=yA+v1e>iIdKMxIjlKsTLz#rxx^cVAgin0IMGq}Uc1(^SuOBui~U&;Wh0xAHM0F*aUPJeS^`4(|_79GU1l+1GNe zrTTJp(T$?*ceZbQb>p+IKl{`hPeBEKb?=w>%`>k(yWy1$+iq_wxK>bkp)znUy84#; z^1Hn^-t4=6cj($nqnEFbU$`=H=7RggInR-ko`c5?{CDm5FYXD_5|F;6K%H2&u|9ulV?PFVA!GVI~YB|5*nJ z{pA@*P^0+9y*K2>?lSQIUgoF=gho&h{BfH6@QaYTgyl*W{Q2F;2j1#y_k3aO)49)= zcfu|A`9jn@ILHGZbk5w4eM0{6n&Xwl3yRkdFBb;4*rA~oQ+ah8wuV0z|C;}p2nfu_ zVlx20#QHP&p;)k8jQ^v+UvKHI@LxCcgA0fQpSav`?cs{V6$)X3pMk}}HJ{->f5&CH zPw->W`5WW!O!~_vU!dQCzl?k0mX4X^2kfNv*E8N6=g)~x#y;e~v)fBXe$@J(I&~&< z)DO%*2mW!^!^{&e*o|w0{qWS_g!D;zOw}Q%goPW2a}%#%>1-noNqlp-+FG2&L1;B zCkX$>W77>s2>*tI>3W#|eUo*2CTn-6YFAP<%gJga|BLZzCO`42*_b&0XoV&}3E@AI zA2|O|P~absMZv{L}60agH@0~7#>gQOq8A^?k|HuPs0L=6!5%QKw3#z5=@Qw%=FsL$)K>w&;L z=MAKQLo@^b4<6vZ!v6z*2>wW!`N_oo_&T9l@;xZ{DFAr8<1LN%EhvahQ8fQ?n*R`w z`sbdn3AxYLuo?U8#eE)e4dG(J6#*xJ*BuoeG6%efDBJk*V9zfme-QrTXU?t*ck4>_ zPo!u2f6`v#9)6a8lH5lRcw@sr;SULsfls`oe1T=)6Bh#(>;nE?{Oy|jz=xnhg;_xu z!icl8Lr_n<&%{SkANiyT;Fx}Te~T*){OCR}VL$Lj;*ow?@RuGc2#Q?YI)9n>$IQ}wBam%)sCCxKKygJ7v&R^kg&A%PQ z(ndVC;Pv)u7oN#K-hnV(_)*|3gSQBNOt|9l!^ew7_*(_wu|!xR0Z;%d0xJ{&n**^9 zV3i{uyKplz`K1y-4Uk{_vB-zW0M0^^1r4wgz#k;n)!V_luM_?^?~Gpq{I9vsUUHv0 z=K=f=o$&4#_^<5u@7x<$SPjf92PYBx&xfKj0{?WxpNatfo_J(D5*-akM}jd)eqw!I zf&WCTV~p@`9Zob4CK~$_wY|w|z`s3J(mYuJ_}8YlRZndxpBDH}5&qSa3jbP#e@D2d zGc?!}0Q}v9zR;+5(lfCT7~2yaJ}U6P2KbBfzbWSb>O#krg?52|o4{X^pP6PjeJHeeRsj@$I!ETS^8tZ0mjYna*E5-uC2=njRvZ3wFu?w1c%HEDE6f2ML7#QUst3pblhL0nC7` z2H-Mv7T&;S!9QviB;3#H2Jo$J%K(u8K7ljfzbOGE2O=D>Oa%b`b3q7H1@LzG6BabU zDnM2lz+QlK1Ng}n0pvfkAPxiZ!5E0=#Nk;v>XZPm)#N`G`>U)2_{3g-(EvvTAOJ=I zq!*w9pe2B40PrslW zG%g5#`SP9b1yrN?pPhv5ajC11E+;U^CX zU;u;n25_t6f55+GDA6>Kr~~}FQWXmS#&mAo6yaZ? z&VOo0WxBwXDydDu`PVkb8e5}n?Tmc}dxInW0nd;xIO0vZ3IE-Z;Umexv(x?8=6hb+ z*>!uVi|}9SxVG5A(U!NodVg|uwC4l@F1+Xa)`@tp&QU%bdAh`dG2#5p-{tIB)4q)0?O^qFB$%H@*gV! zZ1}P=z+#c0u@8eE-Ri5@DKCf>7ahiZG;%~+KlYg3vAR_@NVVglu_-NVb<@9_F{CUiSbIt>%J<;_&W#F$fpI0tl zxh6;bVCM4`p56Bv=D75z2b27;qLa{H_>Xcdh+d2jd%ip{|HD#?_4e5VA6i?D`LE^J zC*B%(3GnmBj~}v&A89BQ0{0H^^ME(UMEG;!^g`|@@7xyxR_|M(g2VZi^GZ|~v2>cPOyeFFcL;MC4g zY9Sn(4M(OU!E_{$iujUIz<)yEKN^Y+1!IH$Sidjc>xp+y82Go1B$@^O^?gZKSF#fD zZ%Gw4rt|93+g(#HR!nbX>@&5!DxL3|1pF)O6Sd9pCcwWV(%lvA7x=pe{ecm0%I%)_ z8~C4@?!P+UOZdb5?*#liuLJ&zlKd>R5&m;XeiZ&R|Ho&V0RJP_`~&`y`~dz)eq0KF zHUD$*s+m~jbWGqMCHxbBf0(g&Mt}-{X$NaRK>NY4jV22!DgM*H|LNcV{okz$unoYa0`PCz2#`8J zivXt#$PR(10e?`28WCWmjt?v`*|r-%{EG@mFF*u<_%{pQT<}j$>-iAOf$)$;{;L8| z3o;@A4S+v<1M`8>BE+!(H2?4O_Ac7?CH#477c4RV>wCTeeF_+>8GI|Swcs3V8oV(( zzjB|ia2Th(NuSZ}8J9g@gR;q}x3{kwd%ikw;14ea2tWBR;SbF}Vh0U>O3PGlHU6Rh zm*F29{u|~~1DN*cFMh(b4}aOr-+{lHf5_*OyWPM4PO{n_5FMnxRH25f85ne?6CmTnEyQ{_UGaLi2V=B zT^%L%7w^xeuWt1{ZTS!Le+lM4tL=*|+z|A8+`-3td%c4>F7|wd`|tBMZ!rI19mMj8 z9FL&$JJ^S@PXuut*z;u$-#`%rIm8$GVK8Tz__5Ey7;4%GT`Lt$c zp9K8<`;Gwq!Cm`;i+h5zD|B8l0O;fzF zHP+S;8;nm_OoG@3kYyBr z-2hPltpc)&0Fwo+kpQ|mS~@I~1HsLx1hCfU%A!9oAWKw$od6h5I~Zyj0*s5r|4R)3 zC;|SmQ~^*4*meLV!1e;nSSaZ4BthB>kan=ifdr(i0~n?-RbeTh8sKyT;JYaTT(sdL z0a^n<1WXw~6_BAIVKxQ8NC0&J(-Z!6;NP@;IrF2tz3@jsU07}K zX6|DBwMrrPu_VkQ{qgq3+ZbuU!7@pTgS?Cd(&C-ueo8~2~tP^Kj@;_BjtG_CG0yylL`t?(Er%7xj?mYqxI69bWXV4HWcy zSw}qx{`F?hA8_mk242j+Tg*QXd{Fa`_bc9&c;n%Pzz+i|jGqt9e@TgB{?S{<4-<6? z*^ehlfgBZp7C_6)vZJB$jZy&kUCSdo2Vyrsm(~Cx0V0en~R|5X&;@VV2 zL$anRLHM^v>HK>_h<()gCng;D4+H*J=KBc$UEMJMH+ObjU+QG?vq<<~ny2|chnXM3 zzva|yGm;;`Uy~oge@fwB$C)3-{!a2!J)fwWjaRDqC;Y|yGxmr14-@`Oe#HEH$bW&q zkIsL{n-BOS_HQ2BS~;{SumAat-B16b=;T#2SEYMAZP>3cxj;Q2|MUC<4M7%YUa3ApS9UP6mlt_iZI0n!oHJ^;-#7q~BRx+4IR0@-GO6a=aODgx91EEWM@ zs{+_0Nce9=z_f$cOTed}VplKte_v8!CO_{I{%ZaKf3yss8Td2sXW}ow--sAWRf%6F zO+V?c{KvNeJBK$cj<-QPj$7cG10U>=p$9j4AD5&4hlU3F(Q0%~`)r5%$E(d37t@A( z$tcnwkAy!|T!uf=`vt_A{m7DZN8%q_0SNr%Uu)t|_S=CU)AP+FKa_V4eBy*>4*cV< z<-otp{1E}Te0&UwIQFNyt+9kaJ|=T6^~F#m}Co!!2U`G5Ik9QBhK_}ATDH2<3X;HCZG zgLo_+LGmxVFecpYQ8Z!(&Eb3aHu28HyAa>LRAI8`t3nRkE-uE;4?j%EE%?86>sE+6 z)XHjrr~tkT(%%sP6gKi$rOL@Ke$P%7z#=GX$&W^ULM#O&17ZwB0YL49D9EV<_Uubv zxav83(RbpU@5pKYp%ec7M+3V7{{z9ry`lNl@bs=wdP(6Q2~R~8{=P)a9gj^!V`Gun zNJ!xCkN0^KJ)T52;6I*dA5AL!8wXOgJ*n!>$?~>zadWz$VJfF~dYgg&%r=2PVxJ8B zI~4xmk^Z22FaYNtpYY83Csrb(hmymmrUowq{yTfX|J%C=f5!exfPcs3g|>@Ke%d(j z3HZ;ooCN%5CHa|Yg84r*)d1(e-{VgpM3Ou`#rfWWKpIe{!am&vDe|bO!G7*r< zfzu1F=&bI>`G9t58NfG*N`MZ9iUzO|pc6sT3D$Nnr9fK3&;X|jpa^7gAfo_M1eh{_ zp3F#q)BwzaoC<(RkP`&y_TYztAZH@TRsmW72>#g(c0>SNFj0^Y2>fRbBoAo@uoobO zz<*$^uK+{`GzhZzrwFhSz%0nj1KBKy13`4qr;$_Ao;-|pJ0WP{NOkUn16JM@!#P8jXy-qKk$z)f*lGj zz@O-M7T(u*_kwxA}ukm&iA(ko(5uEJ#g8t%1r^)}09XZ>!;eN_n z(Y}FNap6msB`skMgBS&^uK}bbz=FpJfK~&#Hy|Se{4rq~NCtu^nv~B>4WL!PuI1Tt zmwYGB`HlkqCj%X&8lAm3O{Q>_@n14F|#r8|Wf6V-x zoo_|*b843SXY4QVNAh!Ex?%rR-Cia?b-O2N{&%IA{46G13yG?^c=b%&Nq)*<{^JqA zzl`vw`7c58;{*TM_AONSkNOH2`+J%Ew2g0fjch3xcwtNLGf#Cs^^?}06aE?g8zERz z2uM=^B|uYmGtxqffB8)KOBKKhK$f(GaZ#}uY*hfA0B0=JX$Q-u0I34B4v+#MQv|RE zPzgW^Bnn_k0UAasfV2Wc3Ru450}GWrA9Ry^NFl%!NRlA71HgW}Q~;c3n*!l8@Sll+ zvb%^N`UayKCu@-6=28JBtaAa zs{r4up9f+ofHcS~#Xp~=7hqLD@&DOp$cHifiCcgxD8L9*1k(0(?(L=UH;268>p(a~ ziBjVa`sF-GOApRbIQX(zWoiHzj%L)rYQIkQ81`B@LvGJ4E;^&PyWLO6NjWf_)mHg zq!#{<-0CYkePRBy+rAeu^Lb5XKJVb1H@Usn8?V2Ph{V$DkkPKQ6oQ0zt3H z@#rY`eDVB^o*uk+@%`dW2#*ExFA*Fi81*iGX!3JM=NH4ZJ8&b-7hil4?;YY_BtULR zZ#4iVfGml%@Ev0DJ2m)cEnpXgkV`btNC5e77x^zgKCPjo5MU{Qb@H75*lGXalYW7J zaOFS{@L$*yo?Q+T{tJMAG&~LX$0YWTc@ltsd^{Q-4abKG|G2_mlAmP9Sh8&-*)o)D z8cYHHuI|Z-cEZ1~aVocNX1hz_KfA4RI^Q)_TsuklHzylg`){F{#{{4w(* z=3ioe!hc1~|ITF1ViNE-=6^a~nT|8|Pe#iV5%S+AKP7>Xz&}XmKLP&xG4liX=VRb= zz?;`GvAt$wOVQwlExphFy7SjR#qn+}Kl=yef3^y=WCcN{BkH30jf+=A`00jU`fDHaA0J3n& zI;DV>0PYIN8VhoI0on|P1UO}YgMRsq#z1^FWdL&m+X|p_R0&`b^dJB6fBqftHwus~ z0n!Ly7DNHiXIllZ8SIP$Q3o6k?UVq_fHE^dwg`|~z_f#z1ZBzqWX5d0zUs7y6$Egg;SYf5;E&5n6vP^F`~| zkGC|uE#919A(H<|-sdYBi}H8@i5h$p6B;#h&lirD#Px&RkcLw_zj$S#<*47lVUcFG znvqh1$efxHp z|1B?W*}Qo($PWIC0-y-chXg@*%8~$40NW3y?lBGGLMy=ZgSj(61%M*y@Lz%;D*;*s z$WL}*;n1=0;gf*_#{zqg1XtMe75J}&r*=gE|M*-q!q`6+NXPvDm$3Knv+BC?eE+ZM z>?F&!B(lhe3`G?cC@3oDob%1Maurz;$`K?afh2^GEZOaL2Y1_U&rFAz={L`IyX}78 z-&%X0dkd*QZ*l8Xfe;1f-1AFo?S0a7qa;6bb0zSfNOg^){L^j2>6SqU{|w-NzB^Of zA@Hwg%$;Ze{0j$b3V{F96$`sgE$ls0JXD!KR+Bqbm#L~x*EOV2{>?2j9c?qcT~mWS zlVg1ovx8%WvElXUfoJFYU(NO0UhMo3@Lz8W;IG)9&w82>mH@0b&4Ytk?&zBuF%{IR`4^0E2%& zSPrCZVqgIMpxg#{4y3y|x&Sc6S)fD!1k5E82v$H%K*7IO22*wvO7P)juwm#=5aj)U z6Tflwj{$HG;5I-I(iZFs-~7iR%2xJ@WP1n^I` zDj@cQ1^$Nrgf>#{W&=w7VEF*bIe_tgwgpI%9}oiZ|M!3YGk8JC&o6(;|IHu4A1%LO z0P& zh&<}OaFzxCrv6R*tC4GipF(<1dQktN9Q;l9Tj($FNBqk<@JHCp7Jr`LU*ONPy(0K) zwU<5K1?jK#=yA0d$xm}LK1DEZB0oWW4}9X$g}+3<+P#)m8~8`%@6a!di^?BGtk|E8 z9!&a&X@7*jE%ntxUwfoG;IBR20{Fl7nx_2?{Av5%diTz|@3YDCJ)ZEQ1z(?qF3t_3$98jA~oTZS2tKjdVbe*6iUE-GVy>oyb$tJ%xC0iFbb`;h}m3;+jMUGjlYvjXCQ*br7~ zW+osUIH8Yqxf=R2P27)`eW{zK{J!E|F^rlB`;z9&=Hkv-d*t7yufxR^g$pFdPv*nhUT zyJBG{;9pTZSXDS$ojX;VtvnC-r!O=C{<8xA&gr4p=OJ#s1BJzhZwA|F@PK-&(%-=F&xh|Kf#LEctmUg8y@Xe_r5!)xaP8U(cLf%~Y+V zD=ql}{Ld8TDsqPZh<_zNi2oUqpA(Z)$Hym+ll+WK98>H+eq><$nZEJEJ!8*wj~;3r zK2STb`*_ceeVtpLYJK8cfWIMsIDQTO(*=kw!1AA6jr+lW5kRDXm;_lLAT)$y7KDT< zYY3Nm0iuCqDpX8hy#PqS7(hXgTL9#kQGj~F+@3&%+W_$ZPC&C7aVpeEKzWeRKQKTU z3kd(!Tm<}8ru+o{{sR5I5gb1`69_2+2L#>d6ks0UBZ>4&UraT@0fggoGF8*!sQ=xtk`NDl;0rvsI zf4KuH4*Z1V$H8BjT?BtFuFzkbd@A-wJ76n$e?dn0P#6E6{6vs1|7>IsenHybG9Qjs zA^of z&v)N_@4eC!ULHRDl! z|MBcR_z(CG0sb?ozUfr&WU6~2)j67O8%ehgWts;w5&W}to!PT(xiiiAlNa;H&KC~V z757&c_nZOz7xz>w9IDD6t;wIN%T=Av0R9b4^9lG5_DrGtX9maeV!?&{8yS{@^kTxC4s+^pN0CD7Odx6IOpJhJzop>U&%`R z1O96nOMcRoi>bb%mjo3;BO@lpc4A)lfXaVzuSPo0X7w+49EyT&@b>;Xkb~85rCTj zU_S|hLMy;1ATa@k{eu6<0R{u5D9Bxa8v&UBeT2oq@&S^MdMO7waq>4_zcva8@E=J4 z=rV!g%kLX9br*`mC#?M2*_U;0C@mW zfHiUh1-K1hMJVE*`4EP}HS-hNzG2}1>%jjJkZHznpO=S!=Q@`j6sY*dFwM-~fR>uTc8$ zDk=X`@^e%yVjUZEayIRMrb59F=ug`>nfbBghc_YbP2Q-yi+OK5%$E?~z9Q|Xmh?&D zU%KBuT>OJk4q66dLbD)q<1P=v#H(R@Pn5F_`-A^Lf7s;b#TP^JFzhmlNSA;O zH;x~Nc{!h)-u?S|nnD!+yLRr_xr2+PCCcExC?NO%Gc+7j@Ebg!e=0`6O18&*K(af8 zf*>nDMLOz)c?x7KU>s0l0jJirMFaoo)$0cS8?)Kf+4S=4?84kkac(M4>~G+o68I0! zr23~*Jp%u9*I1h5r*$~fGMH)V&o=aBFZ5*2b!MyE0sq`71OLNyg#&YX2?6ot1YnsNk5n%NH5-5Y6pk_sB-*Q8Wc+K-{?V*NFdAu zB`%;e5+oa7Ucd~1nSi)DIFJA$&MbiSgGB_sC?L{6h=I80gAZ^I01ikB@}`eHVdcWi zpEw7EJct`2_yFPqp_AbM#BY8J0q{W|Ah-av0%8^vG9Z1D2MA6eHiSh7Y6XM}U>Yny z;DN{h(Fq9uqX|$7Mn8o`QU4q0%K^j^^n2zn@aJW*KPX0!R5tUI zBtL5VD*2I&u;52+Uk88QuqYXWcfK!Nhno5P zQmvfuux2xKaJs;sb==e(Yrdu!`?Ta=m!n^en9^=<;W=L(!SViwC^=(LFDDXHCGo$1 zpTZx+zlA?LcI==Lkl=r00sWh$|cwfPYK=bYuSb`NH9I#e>xgdn*@qonA!$TkMlRS(gL+Yl(dtQv(0Cxt`7$ z1OLhC!SUSK=*ra4wfVu9a(!Xz`tYc(D}i=6@9x7b#LF^_T+b)zxO8>zw<{9{(O<(KVJk2_=*dl zBXItk-)i^)5{_c=W@G?a0191ap8^dC|BVG;fXD%X25cZGp#b1tL68i99DvGPKoSH6 z4iNmSbo3-B7y)+z3W7={^tTnlyrsnh6cCg2gQE)&1;`AjNP@~a0Qgq{`(ZBBQe#t) z(FU9p{Qu^+b~FMI2`C1#Pu2zC;LQNRzZf8x0GkR5LC_-%P(}g5e{Tv4enT8oq5yRQ z%mpZ+DQgFpC_pv<3h*Rf&>tp3e?`Lo6IT1$cnJ8w;~;r57|FjUKR5<_gatqJeEHsv z{$eHPDY_Tl!sf0eVv63m=6pxq z54>Y||hMQ!IB_ZfKc$By8t^6Aqf&I1{Sar#lZ-( z)DgB5$$@j~6d-d&WBqbw8SuX}n_rtvuguLa&MEoH&rcfokEI3vL$m4rnRM?|x(Dzd z&vcBD{A5~(vdsgTi->=~zdKvgk*@^&n+hi`6psM@H4*$P77tbykJJTPqg(F9ZG(|F0~n z?aRRD&0_uYg?jM+M!wF$-`l?H+3MAh{8TNZD*^vPikTn4zal+%8t~_(-qxyj{f1nUU8oXB|mS3w(r|K;pLXvz8`Gf>Id-uGCr)?c6l@K2mdj3 zRuccyan0A@P#=wW8ZpMjx)!YYI&T+(vCp$uu(0folLw1(UfD^5Cn&r4SLb)vF2H~L z_U+rYZKJ;<_fp~jvLIOip?`pX{;i@r=K!66zyTHo@di>QHo%(!))m&-Q-xI%VgYeL z!UC)73yYWM^6PUM2YRjq#E$gSJngmne&b5rk45k_POrPnSt)<(Y~pv z!2teO=Z0R)_Pw>>;NS7!Qv3al_K!A*{af#@x4h45P07zYs{(%wd|L7Y{=aJAuh<{( zNBkGh-6+&~@^d*?eJNKB_^)Npu4K+GWvUj_7W=2p0RCA6|J3Yhl>h9^sp;vHh<^wF zQ3wCYBLfqF|B>GD!+?Lo(EdtB{d;!o>e%wVmhb;r<70o^_?_JGg}ZfdqJ; zYKI7!fLM_NfPc||%Ey6xAjmBM;BR4&>How7AOIBwg^~jRf-nKdLlrVnuM+&r3Wxz> zp^<*8A^_S%1B5{#1&XZzwS+wk3hiKsKt8}3Krv8>1OQlT2!pnUw_0Xpv5V>dU;)tN zih=k@76r%wsMHfyS$bpWFCGvBP`?UU&_Di)IZuS5RN~(;@Ndb_&r7Sl`2T(8$Hc#Z zzb5^|-%WjM!+(&Co05qPiGOzc%rN$uLTXM=v#~r+6=Zucsm(sM8X5bvhy1pLxz$(wh68TvnP0QJ9T z@1FAT2k_svbz56|yTN~n0b~JWY@7u6w}#^1Eea6$M+V?aO~D2VfF$LSXa=hvEDErh zQ0oS$g>L6n=wE6D6xQdmD|4x(c_lxE)MP$2kxhaBBlGE@x%A*H;GaSKcTb@FGwq`& ze|o-vf9_&m?m|zlt}}PGHGihLaI&Fzw7&RE?ZW=EAU_RXl>frvn&OGN{F(E)8Uz2v zmQ*{jPseNk|AEQO=-Be)$d$RF7qSCy6?@-b>3(p@!2eSF#~W>cKjL4p|5}TK|LxVr zTPtPYztlkT^Ah5J;XLBs!2jI!e4WSsmq>ncfWO55GRY6%pRO#VDoK8Fa}xi6zs3F% z|5Gy|`2qY9|3i~U2PclW`0p7z+%$Zks(<&)5B{R*yCv{93cv{bv9mzZ z3$O^tgP`aI1paId)ZW=q!hZ=mYXt-v;O!86fEfTW03oV%0u=iC4`o2SRLz@$AT15E zdL#)d>j}ps$XLK~palQr1}NRpfQnqbhe5#uhyr+nTg4y<7>F+?0dW9jAYmL}JYW>S z!8!qv1e^wb``drFm*Kz1K!88+AKL*o3>0Y~^aG^*t&9TX0bl@rBpl!x8zUgSt%E2a zT7a-ckQg9xK(qjX074QJ2|)0#ez1{%@Lw$OEAWAkAa3%B_}4Dc#6wa1%T)MMFZ=dU z_=^FoNd6K3Jmhz^H1>)3kA23l)&HPv^^e-@>Mq~yQ!$@uPFNskA36TxYRdb|KI6~%*{{pkYC^Mfw4~) z|NfBQ=4SrQXe;g;t^%aU`9a|*`9Bsqzl>NN*q@C3hwumSkNV%bbt@5C#DA?_wiw6= zz`~&51KbFR_r!j|0VE_4DFFC)A0Yg%6Pzyz-AICtY#DNpUTfq+|NT`9#6BnL3TNs8|Ewwh zR9oA8cPHRKGuk&jH87bT8DE+lb?|?q*!LdbztQpO<&IBG{Dc1j|MgZSKWicOH}T*2 z#`47^`ANY4`C|REg>%;nmi**vH-Uc;|5Zi6U*i8vlKjkC@&ou!Nc^7|n>y~v&)~$- ze#HO8k)H9xO(O?tH0`-_cjwk8TA%nv)8l|Y$$t?4(E{+(&gu+y%H3NC-eq z;NPu80$%C`Q=SB20#vab4EhHbV2yyXEGRHQg8$0aBL~O@7zY>)SP~;6pf4f?#D=h< zAS(qoaVSW*A6vp23JNyhSJoT+o4D134g70D=)d!O(EjEtNPhS};-B!Bapafw7YTrF zU>)M0;dtNbKb1~t$Z*;>d@%8E;ZJvu?=U053G;Jx!5kC+T51X&rd|N|5%qhHudR3Hl>tv1?!&7tg`pf*;vIsE0QTe+@PrfsYU$lK#9` zaj@-5{;3lBmqdR=e=&dvK%(DD*@S2QCjAY=0DtLQsa{8a)5YLq1pntH{sDiX|BGSV zU#Wkx)b~vX|6BHW$M@fV@7_J0@bb|o4?cPLFl_as)xLVRS6S>&=r3FMGqYoQ&e!;u zI==%^{A1lwX54uq;`;MJ{8zHozrr7h|6^ripJn0CuAQau2mHr;DZYAETNd$8(#tE@ z0MCI+9bs7jt7rnC0XsW7Npb-8$$3*2z)NfzD)<1-Gfr!9Wnq3Xh4P=urzZve=}~&V zh=0ZYnZBuP&jiU&wqrEgHk@r4$^!m~|AyY&xz2obd*Mt=@zllQu?q`_>qvVR_f#(K zu2?)!xp1Vqc%rsYc|I@jZ^|^aqy_#c|DKrU?my zB>wMhkoBa$d@RUE<%8pY++q zw7?(nFYuoy_OFTGe0Q*nG@sF$H%6Rk4~BLpFBD+De({Zw~QRD8Q62Ych|nI ztxvT*@y+JP|D^G;KT-1Y*e3jU7a$9;xe*-k9}A$l(1vfHZmaBs0Lt;dlm!t683%Cl z$OPykGy;MPFdrZr5b@uP00*Oh(h^|_F=qgbfK7&mASg71lcfRP3NDL-zu_KBbr1jPyv3&2JS5c&#(y%fc4#{XagNP|?u|9_72KnjCY5&vNV z@ZA}}<6*UZZIe${dm;Wc^TQ^e{1IeJ-t)C@Z+TA;{?1Y$8h0eH2S~CLS}vcmUjE(+Qad>ngq_J&TqBlZul$VKYd#1&*L%tkHeo5_%{EO2!6is zIe$xjz<;QM(87DZzR3^UJFB=>5&T)|8=mdOTSefXZ1vF8$X^e)>TO?x|KuQ~2lvAv z%6`CKlPHw%C;|U9Eoc2|?2iCeDvtIKJG>=JeSv?){%ZPK*Ov#oyr!Wap6YAD|1IC- z`Ocm9KDhe<;D7()k4b($`%KUF{n3|}{QQJgjtzX$^^M@4;o%FqBGZ%F@BupJat!pa z^LCdue1Pk?%}o5;Gk4A=;{PP4o|DPxWwI&}|GRf<(eKWkJ9lg+{3rd9{-+}RLjfxF zvyB822Czneh@cb&DF-48vmY@95)oK|{$2uqBLS?G!@mZCV%b?=om)sxll%bw+4N{S zJu;sen#&9T{?l2D{j(io+4hkfJ>RCm+{J->LvQ|ESH7mbP}Nd6)mS`!Ve!bhg@e_L zdj}4ScRN8u(v)jo4?Y;bp*o5%90SxuE1Hi2p+El|=mKEcrqF zr>hnW{B7pvOnSZ|HFtWBfzR1f(=#Ulf5bod5BLu|{*(Mn5c?l#9X(h#xc6iq;J5nz{xbVl!AIpk)EE7ZBl}cCZ@4{CK1nP#?O%(E%6>cnTCeK;VI- z8(_TvBLPLi76zgJWdo9auz(}7fE$6*L@4_IcfkTc1Lgt@{>xavEPyB=6z~rLs2d>Y z=NNnd1mG+{FF+70ku4h#jDRG#`2dT8k}Rmy4iNa02Lb+x4JdhlKmry53I7@X1SZgj zJOVO4*ca)q|BiRCKgkAu;2f9!u|dJ#%<-Q)lzWlrn+cFYp_t8PQ`+iZ;(x*(DLyo; zHDCQ~FG={5?DpB@o6Oj=Wf}Y5yyjPos?M*L9l8Ej{3G$NQBFYKGX5z3#RmRi{}J#P z^p|?RkjQ57Z?QjJUtj7=?yuEeo3?u34JzT%-k#vTzHs1ApkGu#m72Z+q7wKEs=zJF zvIS?r8d&GhC+#nJj96BMX@6$@0sof_{NqyJ*MWaFc{B8vyZe|CTNApZ#m_0OkXX1Uw4TKi2#X2az9ThiC$<9U?BUa`fkj z575CF)S3b20(4pv7vLnoiO((;CiCfuY%SwLEE|~Zy`N^922mB@eNq%NePR*X2H1J3K z8~7g^oK*7DJ8`6K^w7DXeW&`LKG3~=Tl-UwwHWv}8UFjxSO6k;)CfFk0luUB-;BWS z=7U`k{xJbE0u}`s0mue~9LP;T(hA^?@^K)fl^6iu8A%}V0qO++{q~Askb1%14F>;{ z!O*ZS0M+I(koJp4#l;HqKyCuUW^tt?h!n`;W^)0tA3!s}<$rlISR`O95RxFQ0HtI8%^NFIPJ$l+f$z`FrP0RLC(!lDTf{yWIZ420-K z@E?;PYsbn3Sa}M>QLrr*pvpL)WC3IZRG>U7fXN5`URyoHO+E$w+T@M@n?DO);Sbk0 zq@pKrkHTM%+m%}q%mW+Q?JbY^cd0pRea3M;=WA$)MMft6ySsU3@GjuebCJ1{@Hf~C zITOoq?y+4^EXf1-Z)2an-@otrVE8WzC>5e#KkjC)N6-mv^*eVQNj$t-@=+zV!8$Q@>ZzldxOxzXR0KC!hUz{|~ z9z@DXrL%tEfF=Jn_OHpGojZ5L$)Bx809&?f;SmoqGXAeJ3t$KFoR^G%^@BAMBqFei z9ANDLqkz~5(1`>8jX}i%?g5+w5(@f)uN4N~UG5e5UzYg)#Kiwcm)bn}VdlqTf5daG;(NPbBE*K;+inQEK)vE(OB>|dEnRc2Et|BCrJB|kH> zr>0E&kI$R{{70r4_!RgLOdJLLJI0me0NCDt~!T{>u#(qG^fnp5w-~R_sf%zF*J!pr2+P-?WBjO)=z%e-f z`@~TIf1{%qQ0X&wB}y!cf7X0u=`+sxkYVziudwE;wA&}w7e41Ia{w!XGX>Cxe$HKXnXz^4AD6 zKN|RNGVpIh#aPTEtS{v~4*n+jmHAkK{Hk*4FX1n^a`eAq1@;U74FN41LHo1E!}G-c z4E@mbeaT~gOMYG>_6Pjqz^4{_z0G%_@KKtymAAZ5Zn!jY` zN4dY|xc)lEJ_3Kr>=qQ7eF|4oSW5owu{&G}&VX1r?)J&~Qu1~zhCfmK@7c3w z_tSKKfq(Fy)^C#j0RLM?Mn~*lCI|nS6c}@So2N&e8MD68m>g=DNmn9i#cS5x_s+te&sHzpkTD z-Bzq^WR^Y$;jD!Co;9pT+sBXxeGw^S1OBwjj4foAX@XUZ=z<+#wcJ#UQ z(5r=kcb5A;+UWl5O6R9nIv-q7^79chKeT<7{ItHe*7B~BpXOTv|E4!rF4FdWZTX^- zpC!cq`4@utufHMjf9@*yPx6zmS$E~1J&X8X$eb;vs}%bq{!p=AwWYX>_Ghy#KP2y>wx0?7%4fuI-#xe@R% z9vTniJ#UF^Fz^}nB4*oQnx%zx-*a!Hx z1tvVzUFeVW=P$|oRk*Jb_9qpFd>r-z_?z+v{-yl2&P%|=q>AB{^s9lHq(9(a690fd zOT8HQ@vg6O|Cc@adF5+%_$2lR{oi?q)xPhU_}4yf4^i#rzu)VBB8ZKe`ZX_)u-TecGC@a`DgH=l*^B+3SBd@GtQn zJHOi*{0!m$maSV*grYMl#6ao=+sl(6Hvu*jYA!%YkT(N_{!RcoOHP-D|A7FKX0TB} zXa|SIVVrTDb+y~`i%Y|)ti}GhzUf@gWUf1af48NHDnezh`Irw|8nR3OC2%!v6-LsmOJ3Tz`q&r_v8ogC;1WhFI_P3KmS~@{>H+&>%{>6EcHeC zGw``$$Y!}VsTJ~6i5RXSb;ba1oW4>0U-$r_-{tQfM3|ZnE>DbVjz1t3Ahcg z7)VyY%P2roK^g}Fe#8P=7iK--Fc~T*00kHefEu0yMHWcrf)oR(FI@2-|6vZmO~9i@ zKwSW*foKA3ftWb~?*nu}=y3#Y#XKcO~~AD5wn|Y^#5X|9K4=PEJmZ zjgOBEd-C7k)7#6tgZF_8PA(@`jSB@gvqnQ>Kb+>uxaWhw-^9NdKtT@TAM}^_SHu&` zqaZZ|RQ!gro-f-4Az(|fzdH94|GJKRw;BdMZM7F~6m$cB8B-Y{`2iRu^HxXnXu5+CRqr2L3+r^M+#oH<y&Uxhz=~l0=9tuTeffx4gAS~^a??cy21J| z%i|QV$pt_H!3N+0j0MD%fdgVESUjo|sxoGcMj)JZBLQ88v2=cLHrqd)>qYr1`N_AB z=39sJ&4c;I{(`{2t61H>P}#C@s&Vny`K7~kO9!f#_g1d#t6F-ddda}QSls~l=PtHn zTib|zQhnX?!+mp;0{`j4#01J8@PDN+cze0;L%{zkvA<@1?qBZs_;R}?KeT<@-d}IM zvo7&}8|A+W_#^%;_P6Ax1pW*4&o0yh{xSKf-N@Ihk^JOp!2hKz;=fAZpFTtKlX38$ zub7=Xt>kC+6yT5YA0_z#{HKnS{PayK`Dq+EP&K&sXz$LaySDzI?TJ5cdi>84@HY$K zL?GDziV=9U5nvOce{9MBcN(8WJR~ItK*0e9{eB2Z5ES4)L;#TjycZm!Afdnc0Ph9_ z{0}C;QlPI;fOP_5Gr+?j7$6~mWGqy!9#IoY@NXUA@({?Br=dR#;DrYe6R65Zf|9w= zq!&=ae+5B~|1dx#0VP1cQ$yHOAU<2!3gO?{NKg_2g+_o9AhmO?rkW4^m34{FJ_KnGpCPw-F|5}>)iK)Nizp)a3A?{G_ zMeaC|(bjw=&-t32nhrze!$Y>_t2f!|UxE{2hsUvOTpI*97l5;k%L?I-b$$c>gZ{h( z{*Q=%@LpuFsYDWJ|FG7}z+cIal)r&L@GtPE>r2~L=&xNq9sI#S$qmH6ZS}yrRmlzv zAQmv>cPcRG2lfU2R=xm9xP$tP$RCxzM5|y-k~Xw`1NfUfHsv1%K4Z_0 zzkxro52a0>Y5U$~;Q#)EDE@y$+ZXWH#HS@c9{U^kga6q~mNj29GurTBd~Dpr{~%LX z+^29H$_vK>_vu0y3(J4ec`^LwWO9m?{4@2B_y_;@gwAhy_|NdqmaPJRD1ci>VU{>R z6*>XRfrS57j{iEs0U-!-3vfMvzevD}C@3kxzm+IJOrR14XuD{|Kz6_ctfz}h4E*#? z<-5o80sKeuEra={fkH!H;aqpIM&Q2)_%rZx_*?}4$`u3u#ftOAvkirFjrj!p`vL!c z#Xf+4etdFmX6#0KylE>%9*CWwC$zy-NZ7-xK(^++I`c|JG{L8!NnG?7zg! z&xM9ivawkp-`I%Dp`Rt7tj(TAnrzr+cH*?0zm;r0I~tRd&|Ng z#Ae z2*(ttd|^P`BFLtJ7zi>F&_rnD0Du?NwIC>r1pObwf4Tu`2q4gv0;wzravwm~HZ%j= z1N@7^Ak75DUVtY-zXbo|gMI&}RtkIDR~({0*S-UNUMoSrwWt3dBpbIQcO8hh6gPax zl-7Jrj*aW7f~d{@{_?R;+v?v&%?a!1&p^STvBZB>?EKo68freq(I0>)!Fi;AbO6b( z=?i`j1^63dSnLzTzr%m&*L^w+OC z^hflU@R#|xDiKafNLQJ(uCWE1eW^jjz>K#WVhtm}*T*SzPeCjNnc>-pOD{%>mF z^A@{&-hStfCqEy4biZu1?~i}{V{P(`_}BIwzRXv$|L?5`e?l*YHD8JNSMu*;pL*sR z7lKpaR{h=Jq+WCV1&9RE3U2?5Xw2n3+Bt5ZxR9w4B<1#oL?gR_O6iG0_1zGJk| z2KWyZ8V3p&`-}D6#oCUA%GQO`O^YWQmX4fTI#@&IgYu8yU%wE+KiArx>FiAR^+@@T z56sRCPv<5kS7*in|CjPZx0d?vuJ=B?(hdGw@+0y85d)uq|E0F~H(CLIlAm|hns2Q& z1O6!gH&z;7jcwl-moD5~I{*BlgTKVT#r}1d1pc)y{+F`VBtHiJRSy21{LIZ&%*>tk z}6_^%&cF$W43AXyhsq5z@4S4e`m5sd}Z z4z_-9OoC+6`LMZAG+R=z0A3@Fc;2Z$(%LpWmfN~ok z3m}xTip_w4|27fyAHU~^IRH2y@POdoqM+Y06BJE=I|0jqv@pP48V9mQ0N}5e(jf5P zCxYYxz+XPh1*jbm=WzH8LK{xY2}BYQ188bTpCSpk2avSi)D5slz|iyk6{`UZ{F9}= zVikQK>p$q5mldeS{Ri|Rk4gRu`O=0DCjKYK#(b-PZTMhw<|a7pvYPnUQX{Sa=UrCI z&a7fI5X8fOnlRSB6j4aL`^4r^oLgOFF4adJ4@q|p`M&QhFins(ACWSwo_T*77 z2fjx9Ljc>hZ}-6;px+Gu7vd(JY&n3$1SAZ=Kf2rpgoOd6j<7Ny;lEcT0b_v31L~x3 zW`h9;VURq4N*BPF2dDDOT@!_lF~Gk7_%{s}NPf=u6l*&dt6CQ;4E&GQFCD5~*>~2# zfB9JL;%R|@(UgC##lgQjRRaI<$<^ucYpKzf^22X04cuMp`}B$>KUcfd^R?vX!%OXe z{|A=*yt@wgx7@awpEp+<-&k!_>`&Wwx#6Yd3on5GfIrDk(ZIhb@qZ~_hxm8!U(TLg zRPuASkP-N2)0G1MRK=VnKL-9vegJ>rKd}$sKPB*Q9ebv3c>l@%-TQmCZ|QvM+wAZI z`1{K!;Hxg+znlp2rD0`@0#0>Q(#Y$z4?aNwB@!?WfB|@SK>>V#{&q-cfE&wvfH?uJ z2@?Y-3G#8EFdACEHURxE9*{FnOn?!9kpSrLIS^mC2~ZFeOF@vlfVTsb1vwQM{2K#^ z1k@A;4%7)S5)l520<44B7}0sc+7j0<$>biuz9fLgW={^bM!5Bzg7 z69feW7SQg`ztX_Jz+cIax(ECL%9XU_z_q^%|L_;#4&^rFt^);^O#HLkXF7`isW@jM z@z0!LFKL51zXW|T{0VD*xd5DJPOhCyI=|X*fW!vqcK8Q{9NU%uL>B-9s5puf!haX^ z`@AQ)e*}M*{<=6m^AlEkf&a;Bukx)P)b8Z)D+wg%KtF++*{r9-$i=C{>JHLp3HvB1x z|1kOEga69@y+i<8xA2<{{>=lJ1@ME~0ioOg=qNUn1Mn1xBOX9JS;Bv_0WtxQfI9(s zs!)+>aRFjoqX5HyF~R!4bg^x;&^laf9xOKY7Xkme&c(BBixn-4Coe7u{OgwY3;b6P zRRjJ@r|TE18j5vIg@zWuKa2A3?MV;y&5sYxP7lvy$ETL3C$6Q&UNrFkU=#Rv-oMiE z@s;+EE-U%@z{LNZ^;Yoz_F9XH|3>itb&{Wz1_S>FlAjkAFFdz!Ua|iI$xpq&zaa2m z&)2Tzs+TSH_v9yU_)qMgo(JGXqN^#>*JcMD+05(W4;khKH;i!6v)AgvK2{P}*y6$gYMNGVX#3Gg<6AH%AE&l8!Gz6dl6(%6*1_a=>>?LF|fdnMb4gJHXO%}kX zf&}QH(Ed&W;CyHZ1OGnrqo4DiecE4+LVvWOf5Jb3jDR)nIAkvf$s=zI#X>gAbH38E zHugC=siz7KkErw8*Uy?Sy%o6R2u`>hv5n;BYH22Q+tnK^p$H{8n*!Cs(XW&yCxBiUs*oUQjToE>Wn48t++jcqF z@L{k|jhL|clEgn3qTa;6#y+*?*Tz0iay~iDTJvjTpM*ao|BQVy`Lk;mlRrBNf3|Jk zMwQ_|1h9qQ#=lDzKqf$gO>P6M7c2@;@&)vJH^BJN7{FNIhEagdmQI>jAml&_g8~b1 zTEzhRL|M!x9xwtZa{=ocnc_;@aG`mq*fdbQ&|9qQS~%Ojc&2$t;D3JknYxwz)hl}) z{OguZpD%%bvx9$k%D{ha2JjzCz<=b;r9r^|!R4OMuXaDQ44$Y~p`S;9s+n zt4Wd{fqy25|M~edi2s?n3dH}UwSC8CP73^|CI0)Tj&+P5K0k8c^uV5jy*svdZTW87 z5B|Ib4}k%Ie`f%NK*j@Z28;m+fs&z6 z4F&n)Fed?H09J;Tf}mgn;_eWU20RQRmvJLtKrQ^|WeHFq0pQ3ECH@JY!-mhmIiT(sZ|c9)1uzoOf3g2;G!dr#4g62q z#@uTA0%;6AFeFC!*@p*@Mckt7*>1n?nC?o{(!uLzT1&iz`YUnc`!XJY&7tXGe6qn$47q3#J^4Z1OD&6^X~ihct^nh6Wi+N zho5V;?~niT$3OWiJ+jAFdqwc)p`9$|_y=8)c?Plk$vdONYQ)>l+y2bW7L9%Sg%AlN z{wwicmi!;LHD7do+3cUr?|#<&?$g{STRuE(lRrBp{tf;;{5J=H`ro>h|1A7>3edmO zmNh#K&<1c5U?EV#0Js3B02w5Kze?a=Rw4i>K&2z+m(v`|Pdi7YK%xLkf#d;(W)>a% z&vh+Sw=Y((m^#)uapc12!83z<5B2Wc(Y5vQ_9p=UKPUP3@c%3L@9ltK z1mdCq=-}&Jz{SUQw(bM_sXio~j?MtUzZD$dHXv~U!2-AqfC4tBK%N9a0+hu-3WCfE zl$M6U0OdYFE&%*j7-X+t0<0Ar+riEOP65sVfdY=A64D7Axs4*?hj{KxO<1VjdKBcM)jjDo}fB^EIJ4@r>Wzr{gHg3JV% z5lGqraf1l81Jnw5gaDub_W~XUJ(2>I7ytr@6!6dgq~s^=^JAfrOMmcRMnmXt36yms zr2mm+1h7CKs(6tNA6)#0HD6)Kd}Kt6jCh+M3K0J=J-!Y%7sUSsHkp*{wJkXKz~bq67c7nnD)1Uf3Cdcf~DjKtzqEb zZsOn3Ur;aUzp2U+f3&}&zvm8y|Bgt)C&McPFw1=`_c!UU?LDa%UHq5t^PryZYxZC- zB|kRsskZMs@Ay8ScR!@Tz;)@^IoOL=>64!PcHZV@ESc|iT?+N{~ukE^1m0wzk~m6DgWlT)=>V! z|5sN~{uf_D`7bp*zu52`;J--nW8hyD_-_FG+7t0 zd38WbZ|eX8f?L{Ouz-RfIe_2-N;bfKfNZ~^f205-fv_gPRtDezlm#Uv0$@Izet;Ev zk(B{P0a_ZyOYmEa9^?g8e8LU>3mQplAU^0wouqp0Mrcpq$5j0J}g~+t-qx|MjoL{$?la z4+sD4+xhqAh%x8x1M`T!5&s(d%;z%<8P02C`Kc*(`%E@`h- z_E+vN@gKpTuCL8|0{-Ow_Ebm1f0BQ8_>WutXp`qpS?x>Pm&bd>nNQ99{EfAJS;6hP zU)uj4{EEzI6$ZP#jSdYDbLTo2bGONeG4^RLj5j(bhf6?I#;Id)%EBLw{e;c_5&wig z0snXI*tuhe$^SqAWexxefDvr>Qt=!AOfv#v0USWYe?D^}j0HRkau!HjfK7#(2Qd7P z6tI~Dcn_+g2hiu}1MF%TDPsgC=2vPv7OUEpPB$$dZ&-2g-+LDDUp-pu;J;YcDDZD? zbMVg$^`^!KO5lHae*9)`^o_-#JF5ft0srgWpCSIQc7J-M3-SN4z`w)5zul6b79~F> z{@+?_Ciy}61OABrmzFM){Gj{+f07@-U&#;Rzo_Kj#DDE_zGf*`y^yUgX3hfsxpY;= z+P?EClz&A4|GAR}{wGHw{`X8CZ5%sPHMH+Y|I>i~589tfz+cF(7Zea%z+Z0!z8jVX z2>ib$1v=SP0r)%q^G5QL1&|344MY=QSrG3tf}mK)KfdIsD!BmeHuVEU0HOe+0XG1` ze`5eUhya2M5C_NtsK63RpESCWP=JWQf*>;ja`?ss76ru+h&}*0kdC1Xz$fuQ2!f0S zywpCi{5u8k2Iog;285{~OMo;K zL`eq10HOdLi~x)SfTm~!%nAtl6BiItAo&1;f25_AS%5$M;XnWIpa1D3knjMl08fHw z1jLyjvY?3n%7IE0fc&Q;pcF_ZAO=A)0alL1Wd^_qARz&t3Ka_g;l==fzrUOW=mjM0 z09XGy68no1{`Ft*31W;qp>=$Zs~AJm+hAV$z=T>>mI%_s}FiMO7TAECaWeiQoZR~+^Q{nVz^$CN+e z10WGg!Nt__P4&TBmY0Jko)I9&!`tOKPx(B3Rhb_bJxPf5L^`hYQgiihZ80K ztExEvQT+3qM?FX3Ae~?Ef1eip?%w_AvM+5!2+^;3;2 zN6)W5Q@gs~!M}FpRQ+SdiA6#yK5AeU#YO#Ndk{`f-wdu8x{4~A<_%Am+zl8V~ z{ww)es7L%?Dq8YWs9nhu``0Yys*6f~&gL^`vzf|tx-yjp{LcXX)AJ-hn)wm<&z>Bf zIYHaEZ|ZpS_~Dx2{m1)v@9o|GWapM|@<=c6-(N8T5(OyyiTTeXL{P&2*ba{Pj~Do} z#>ZRx+JOJT0cQYo9q2!xgE+tlKqMd|5PbmsV5&Ewe{=%I0KPOpK7eLhTpMQH0N$IS zLPH?f0Ly~PU4U$WXF-PkAq(P$4izcD;a`};9q$$(fPZ9wzyU@89AY7hL;;!y;z;2E zJPv{j5)a^65cq<|lnprhpOiSj%ab6vfrJ9g2UsgO;Qu3OkSz{23eZH57{Ie2Zw16K z$XP%}Ae0GpSfI2hKngxmfFNBgU=?kE;64Y97Bk&Kf#$5&g zlKk^DltQuSQgeRJxB3rJ^N_@UUl9Lg4|#{1>B&I1j9rORwdS`h{6V-cl7NQQ?m`2Kr5c5`Hu zdzag^r`sR9(}sf5g-{Cy{8#@4`=vF%v|Wy?^UGeUJjq4)zdwxqEBx6-YnRs!YP<4( zLA@IQjtYL1{cPK@V<+Hm1fcv!A67T<0p14Sd?_-`l7X_KSQAd&+vbsFI9 zr(OVu-~wc}1pk!aUzJ=G`$CrqlQufN{>-`6{nd(nj?}IS{4Xq-@-H^E<=Z>+J>A*C zzVzroYHC>EU!0oWn4f$uJO0|j$h)gUfd3D#ll-{&@BZX!*Zr%VA74fM2k?LY5qjgl zLELC@D3nKliw2AZEDG`z$Vebk02z?QK#)Kw21-&OB|(CJJAnS78z2kdF2F`YApn(4 z1Q`z~0q`^kL~t8m6p-NmIb}hS0z?2-o&;(A(558F9RNq+KP4AXW&*qw5C?+f0w^Z| zwE>(0EC-4{Kny^+{x=@rkhFtci$em|2}oEV34=;3ARpi?pioB?5E=o21w03`JjlSG z-{Z*dyb^@JuwUU5Q8xItv7y~QmzS3pwAFvI;X`<&_?XnDjeUlP{F?Z0vo*hst~n9^ z-uY#pVU7JO{2_WiZ5w{-Xj)IM(le^x3A@yqbOT=TCx{5$&lA}^u8#XdqAP$$Md22W^J@G5}6 zp}#OsslP|#ihf>s`DL5;^yJ5aA8CK4{cWw+ZQ8!?+_}Rh&$iXirq#Yb`pHjh;9r}3 z^29chAKSb2=b^A;^Yf`P@vr?i5N6zY1aaI({M$JRoDEJ3;@|xj;$Kf%I2JejIRyCo znqRUX-|*8Xe|Byc_^Sl>UnzRTqBoVBfFuYi?*(u+LOTHX4@r>P!AS)Q;3RS)4gHM) zg#AVVP5^2LNBox&Fai(-_{$i;qo9qA=Dzg<)oVxUR!-J0pSiG9)3k8D1@JGlcjW;8 zfxe7^|NQ*uTyb({9p#@Le|2Hxot2@FE)59$Z}fbAz31Vz9+IE?SGx@SJMLYP^4H8y zNPb#xt+y!lUu%AS)sml;#+O$B|BD9x7j7(_zrJ|>+G70`f&aOU!Z{6m3jAxAay1JQ z|J5k}T*hO6B|o#C{LEKO%$**eJB9Lh@F)3cn>c(9@E_cBpl|2auC3o`f8sCN4E&7( zumCawEpP#i;Ic&M|1StC-zMTf#}U9^&_5IiAaHtSz;GF;&f#?Bjc^2rc(ohgDH3C$^{!J!8L||nu zKwt4I7W|mzy+w;NZW)1$!mVE)}5&sS7O9!aHO5Q*tLWcc5@G0>3YZvZcgq&lot1qk6=ad&u*V_tAEyfosjrvc{R^bJ`lveb$<8k*|p2${|+yK zzu;dVn``?v@*+q7Kma0v5(U73oQhDK7EVo~|E)*zY*`MZFo+WH$H7qR2Z#bFoo$_R zqh50WasiP8h=Rn-mlOcSk8^EnC+b(uG%VFLE(-kH3+)~G?jDqXW^^z;H7xKiObPsN z2>eHHuMB;7Y2fo~eGdMf{J8l4=t}2@R}}k4@Ncu^r}eG176bq0*TDbP#+Ozuz9{^^ zpyX%i!VSRRk{@D!z<<3^rrzz%}{ zk`YLJKsgJPW9aPQW}sh=URW0487oCjkP3NCCb!Kpr5(K()Ru zAQFHz1FEC`k2Fwn0Uia>2+&-pQ9!v35D8!dVn-nMgN*~UQ=CzNY=AEi29QG>?-|_9Wg+84kQW)EWj81SNpB{GZOG8_K(>A|8gU8uW?I(gzSrB;=hn( ztN&Ek>VJ}_3XY9wx6c7R=L_eD%j0r#-Qa1A85e+4>c(au(G=Nx~mtw>Ew7@V^ZI zLj)wyFJV7F5kDCCEBE1lQ}S=hU#$d4MUk9xI)=CScCb0$7trG!Ad~2;y;@soHNjB4 z2Mz9n0oVlnz`mh>$o$vW4NHXol9)pN5c>#?ybP^e$ZFt6_^&A#>-qjy$&X2Yw)elK zo-f+}-FH~+%RZl)`6T)I^wUq(_Wk0Eu-f;pfBLsR^J87#pF8~j;uo+$CL4M&Q&W@U zD=MXx#193QT$&-@^D%>L7XQ}80U`3DNZRfwLD9K$sh0h?p5cP& zyix@GhaLRCxFPZX+4Ua9{u2L6ehmCOKDb2e-%i^%BtPo{|2Nhg{F{{g2>csvE?;=o zz#s8X?6010(ZIh@hxj+}m-sJaQU1jKfWO+lb7_*FGoJh){>Nq!|EGp$PXPX%lSeO% z9y&9$_fY@SJG!?$-ti>JFZ^%)KF1INmArsafQaCGf9^-I0PSGS1$})}xasllv^>>0 z*fnfbMgYME@TL;}$C43Hi351EsSOZ5tQiaoSam}Ko}PHZ0&jprfX+4hjA{3%C^k5##_u0TD@o7qS3+zyWRr zyb%y`Ac(+A5)&v>CHYUpe?FJG0cBG`(F6QWOT#vUzdV3appXQ~0$>Ep0wi%zBFGiX zI0cwocPC)Uy}JNI^n?Lo5M(T1?SP2?690Gw{h#({*qA}lWPF_;<7lQ^i()^9{%{)A)yWMU&8;O{*w-X z;NOdxQ2&phzoH_6KmTVye+PfTzr?>T5SNSZ$YsUtapidzz(mc={KQQ@dDr1g%Q2s? zCHDb-2Yvq>_7w=yUzlQHgLJ1U|A_se=?i-Kv?uzuT>C5evDn|bzG10XK!4lhP0Ifr zw)wPuK0mxi+ZXZw*=P1_-yi*$w(n1E=11E9XNLav;D%qQexA>p9h+mr2Ojb}F=m$o zRp#QK`-r=RJA-#Umw_|GJC=ou#ATd6$doh8jvC(iJrKg5y?esgCr^6cxpRl%e~5n| z0pkEW8uoM84lS#(op=C`fdGHKbdUw$<;?)$zx9OW1Ckslw1TY}9G3;C6%c|TOV{jt zi*iN0q5z`-T@EN9aRK2f8T^X@HZr-j`j&;pcEG>T-IE*W%Z?6YriN2TKZC!5{H2@Xut*z&~|na{jcEAHaVO@K@~LHF>mQ z>`>+KzGwQM-r2MLdz}pYNco%iw=YWKKg2=9Xh0RW2r?rO z7$6~l>N4OjeVKFu_#$ROasrkCX|uT43y2P&Y@ayo5aCIXC_ql2$00bBM zy9-d9lMIDQ~z@@gI_( z&0GE0YTr`Z_iz5@Z+}MezLdqW`Kl-XaB+{WMQdBU5SqUd z1y^aqRn8qW%85S8*rz||QEPq)ayISxVCrA^56Ew>Z88I~=(^ijtNyJ{0`feeAzZQm zFNgs`79K7E3MjV$5P)%ju%EJafb@Te zf6@RtRL3Gy}{7 z1Rr1qz)gS|0mJ{;3veS4NWjrq@b52Jz}M+Y!^epr?*qsKkne!}P5^>`k$^rh0sOjy ze}MlI@Y}cg^{;_7?kesdLUMI}+7FUHQU0L+aVdW*P5bMqj_Ud1Cir8a7Te~h z2DauqLh4*%$R7^^0D}HX65F)e3;W4iQCM%7FA_+wKTrU`Z$thY8{mgf1UylZGlUAc z4+B3ouL5KV{0+;bZf|Oz2XBdL+8^C-(w}MnHyQRf>HkRVf9KshVXL3}+Un=?&p-b{ z$&cE;f2|$<|HkJ1>G>l475#wxeB|?a#(w7K_2iui?e;dt{YrNZE=F8&|G;{7A;iKa z{;RnFtdfZ0UyG_0{)9ze`xO4{M*f$U{q6+*xAWr>3hta`k1I0D7Zh$VI zF5^Zzz0%*83*bMN%1z9z%+6fPOukeYe|u&0?#9rk*9N}4(MR(0c?5rwAM5!d{yXnp zLHu{zxeWNXll*e2vLq;40S|%{0(lU`7sdc_CN!D=-ug}g z<^qKKR)i8Z6$JE)29yLL76t!F1qw(QAaMaAfD#8N0V+rR&0pKHfWQKPzmEjPJtD|~ z5(?mpKmoD=F$YphKv|F)!J!!(Q=nff1d1M@Y-NDdd~g9J60k7Hh(IpDI6yS;fBrXi zA#Cp;@R#UMK2TuSu3hCe0ufhM+3;bhPzYn6Jm+g>W@d7VA@gzGEl2`HfGA$#fAe!5 zmHZ<+#huFKCH}+YkGi(XeoEngZ2yL&C;6m?uciJlgyX-1KLjH2Z{W`#%!N?!qn?ee z_7ePG;OYzf8Tc3Yw<0TeoAC~Gm zWh3~%SR8+AdGv#gVZi@K&js*D{7d;?>%M=j3+4ZziU0R6cf5C5;{P2J|8H+d`~&_Z zKd-Je3j9|!^Yc8(kAeU7#S2##E%{lf-&i=eR;*ho)Gill7A5{`4E(FV7W@_a&wK2D zvVZz`^Tgrmk^RR9_w4E2`DC|&f4kv-d&qx+1^6ljBu#;@t_uhq;m`{{(^ox8Zb*&r zQqe++Igk;+u#zCJ@{u6Vf#NifC_t}1rocr6VRe{-Ac})J0URR<7y;;D=q~~&D+u7L zF2MT1N`qto%mo++zyjRkR^0Ad8myIJF$gjTAR15*%#m^nz-NJf>;{AV;(W0lo1+5G|Q=EuQb=8Q{(fdlSL{FD4>KtS)5*09=(cN_0Qa30BT@UJhu9P=MR ze<25GB2X#8KM@2-L@*63Sy~~;avTHGEcY?(@41hu|758zd;4pj9|k^eg{8jC{M@;7 z=YzW+eDvXmpWMIy@KcHZFTZ4~AMNw@)7bVk@K^5dvCl7l`Adm^+wE<}HhdTxbMfyx zZ)@z6yM)_-3&A@ZW_16B`HJG7CoE{um*M{b;lFi$UHmip6X0I}@8yUu$anaUzrKTs zKmsmOE={<;VlF^tAeIq8U;ywxFn~C~qOuSKDF!kZ0F$~0aAHk7fX=v8X&?yThXkx_ z602-36!2H+`spPb2L)^_Nz@Oyj{3XC2 z&ZbQK&!$oSXQomW6Y~uGoVM8iRR7G0*2yEaqX$k5?cLY6 z>#6Ro-)evIn}EN+ECTwbwE#jdz&iqheS4L9!8Q?8uKx}H$%SYHSN7G63H+S^L;}$U zY$5?;03FbKp#}!{f&)QNTo`5q00{{GA%I8$@>F1qYD$fDi`BlZynpFaTN~W}giH z)e^S-AuI}#h%1c*B^00($S9!91o$37;sEg9j|Tsx6bOiL7vM<{Ot1<6H)la12vRFR zKa2yy+OW-t|COJ`iO|>xh%7)5q;7ybKxhWY1Lz~P0+McUbO8iG!3G!ylqet?fzn*4 zY=D(5>v99&S&(Re`d_U168`%i;+Sto5e)Yo5Xa2}Fs`nyF2?74rAhux{7-4lgs3?_ z%DxQ+E%rzIOZhYIkM>WLzb1Zc;1lqF|AY7M-Me@H z5JO0NFpVjC0j4x}td1YjHh7e*ssK~QW5V*|_wKmuU@O@Y60fSpecoLOf8-y%XyVV!iN z03Emz(Fo|mCj)Kg988O6#IXE zqc_C<*Sih;iTyk8UhRDUiX}gn+izcLd;3!BTN|eQ1^&&itSR|grr8o(drpNYxOY`StfE%2x9J1+b`H8OW?@ z3zU2SHUKX`4e1B3K`6jG!hk<-I5B{HKv*3tG3YM92tY0%6cSM+;Nw7n2*?b$Cv~8} zf02OufanHv=klSMAWMNP2}*nbPQc3uz+C{6$8#XL0B;3=|Kb5lgKTMlOh7F0fXa-3 zw4)q=+W<2G?gOF=us%S-07AB}ApWDjAQQkVDG!3w4G2pE%G$xs02T&e0Ne`50VoJE z@vkh%fZE!$krnS&%MRngkF;P zXTyg@?e><e|ZtUXfP#D8hc7uJr;$3=pl;bBfYEXx_?JaXD(m#Rw}ej?n<@!uwT zHfKKx>|5;P%HNY8@A*pnpE(1UKqxlzV*{TGer)E)p6U+n>3zW4!Z-Q!f&cC<-h}Gv zI_6v26XD;`UpaAregl6%1n_tK7s3c~B>p}0H}E(77k=F|@K@9KMMFOUqO^Yr{B4sr z27a{C7x8bw&z(E(-Fffs-Mb%sq?yl$4EX zYv&I8v2$mkkbhMR+w?D%3$Rl7Wgn2CmClS}prj#eF_2M!#(@$WpeDL!L8T}t*&+f0 z;N+Kcfbl>n2-0PWe67nzsTJU5_|GN2y0Y-b(%5_JBcEIyGUea@#j|~YzXm>U^n7xi zb2fAYm66ky>07s`Ub|2Py{q5v}jQT*2rG>ngrkLfk07n9t) zWCp|n>IQJMG$_e}(Enxx9`ykt05buJ5r`fD0!Und;lKF+BB0;_K>zN902mDg{(XUP zZ~Gk7xqYacj37C;Q358e43t$_Uy1trY@Pl1d8q6dhL0PtTF5M!Xw4i*Pk zJGjgTmjwz`%>^g|N?d@$zk#;317a3b zHV^~{cq1UV04P8NV2i^<0OA0C9kSk?jLt^a+2pH(Zr6X3RD^mjRHmAC;~-W1=UedeM`|uC-wmnML}94 z2udR=NUP#`o*S>xBzo@qeq+qF)-Lcq-h^}Yj=g^Cw`n~ z`@P2Fz4Bn+m&aD$fWPp+ckighy@;d(S+y7yLitz@9VqCHXJsC$_)N&(`e) z{v`h=Z@A&4Yj61b_2U1>KeOrhv#$H_N$XY}bLA25Tl>Dh;E)HvKluM=@1z38a-fxq zf+qimPsZm}4*rWz{oRGve2OfNiOf~-F9n1E$_gsSs9!2zf`2#w z0%!(61I*=sS`svt1Cg($8mta5&V&QP0$CSY_+NzpOy;Q)@Q32Rq>u$cO#!txObcjY z09>H@$gZ%dBHT8R5}>QWqI8RZEYvrLClmq;Cn#pnR1RjLr}!@ofd1SJSNbPR`bRLm zm_$sKn{Rg6=ia@0cklK&UpMxiuR7`<{IBPH0XUv8hPy(K&;c}e!4AoPReq63S@t=Z zKccu#YAIwds4uTazkeox`u$$)IktM~`=8vIf8d`(-n9McApS)<5$ciglL9b6U?rWO zlzka{!T+WJO`!2>-fs>3i~Pah&|k16qKT%bRUz5qj;m##^P|4n{9)x!t^E<(S3tkN;wA7m@|Qt` zSU~0Eg7F3gr~;w{lmNpCDiVMNqLLO$U}AyR0mBQ@3W8$Ae;Ht<3XlMdVFKAfO#qDG z;J@9Mk0t>bz*mU=mOF1f`1qaIx!^P4|Ky!(0e_PJ$M3lG(K}-M-!Ax5_67WLe!zd7 zpIa_0@c+t9fdBblxM?^)0sq_gXU)&Pv#j~#am0e@|Olm9qBpWLSN^9jKJ`r!Yj zZ6|NI@g$s|0{=@kpK#8GkDRi8^@p!I;sa~n|Cg8iEtv21>D=G00_NpF1OG=MKou~M zf3kz#f638nuf1X;;4k`bkN`GB0;mKGb-*YGtWyS5i2nxgF99?Our4Sifhz|ISPdYW z1yT==6SQ_L3dJp$ssY6U3V=xji2q&?KotYL&!ymn4HOC(EHG7r!T;ic6a(-8A_C$C z#Q<6`0BPzd2Vhs|SQH1GRUrc`0)#OH|2Y7kcg**H-xuQrwG*TR^t)()Nd{z1(6lZn z)qs)!us|yTg}|Z^00WFgLB#??28?3BYy$=TqXL5e|EKwXsANDt$^Yl>W z{pI}cn(B<%^OdK{G7#ux)EzpGgtIUQ(xr`QRXCKEqnT(QQuM6)KNagg<`4D9rTL%5 z|BHm5PqWOw;h)SuwohgLC!IVNd=BTwdJNsdKgT}``0M-t|HJtK{`v2jYFxHfh&T0n zhv0XBg^mFh1tEp}0hjjuO+|&J_!L#bG?^{1fSNANE|1DYeiB4xiGULdqm^mS0JAyF{2Ui!k z?6#lt2*sNGSLJv0>S^VV+kTG#|38TNBl;VHeFdKn*#E%$>-V|yo%EL#4j3R23Wy9) z1k5sEss&gLn8Suk3>{$pKSKCVLoX#jRRf{|U}B|!j$E^VEL|Ev2JpI#UgQM}RFn#+ z{?0&n;P%bh{&%hY=esU@;?Bzk_?!F-{-6EyH*WjXNd7M*`M>w(3$gu4{=anS{4WCj zH=TFS!E^6EcLT-N{^(Ule(>`5|BnLy&-~4waWw_t0>K5p&kL%(V51^X zcF>|6@R7A2-&nC>a!IHvpq0Q-0VeT5|CRxR11RuTIUpqPOP7SI0b&NB0t)~0*Y#h>gkttE zMR3Ir96Yek)82OP^uUK3x5jErVbXWDJiD*a7`%B9JGQUk)6| zvj0=q_GJnx`kVdN;?H-&e-;G={{2RxZJ#{Wabf%Om+Sl#__O9G=nw7*{;v5+^6x;> z`Qg9X?(g9Y0P6EQBkwPi5HgHl$rAggr@7x%X}{Be)^dFrXBo_?mE?Q8PyQ4cS@JkI&)%>VV*b^asAcgT58=^KBpe z;D?wpqW{4EB7ru7q5^0W9kJ2@F@mIkuz)rY7e55B>w^bbM8EFHW$f1;D77Bvu@sZ<{@qWHT!p$^K<&{U8n8b^~oJO z0e@_Nlm9K-E&C4eKWY69f4_FiiRW$n$S1Bn=D4elJmT^X{1pp+%=~fwEAyZ7e?D1> z|3d<>FM#Eshb;=C5I{xv|G4O{&$#;Rjf@zs1Pl-gh!9{H5h(@~3#1wl^j{DDryQUQ zG}eV)eN77h%fW!bI49`JtC|8z0q6%o0BrI8o%T>A>7vp&dg*ZFVkFXyKge3tV=Z_q88c6KBG zgY#;^C+Ga|VBaMFjIhhrR)U}WUl~9I_ZLVIge{axTs@_!$z{g=DFV*7K@Ll%63{@;2S=jTz%zTbIrJmKZJ`H*MO|0h2&`G4)z zwtZfI{dMfPT{}JM`Gy-fe02*`m?>)Vf5kZPp-b@RF`DUxbV0UVB>AVg-ShiV&7Ti* z+VhnB*UBHaeSzp4`yjv<*1x~9e%|~fU-VcAzlY%aN$`^YhXmROiVDEu(f9pl}-F(r#w-orB{D0x5^Y6Lo z+_wD%|Fdt~AMlUue{k=b1G`E7&%pTs{C5fdpWMEavM-tcsW?BIx19p`Z`gJc$$x?W z1sjk1?eU3x2BR%RxWd{v`j8 zKFSkbe7qwI{-1fe&v|?C2m1a#*!N|ZeCqnY_8NIV;7?TYU%N1v$jsH!ViedYrone) zLyUEXG^3Zsq57WW-vc)!+$8^)KOXp#M}4iTZC~PleE+yV0DFP|;rFHmk)PmuiEZ1q z!FBfz4p0X~3)mQu0ZSXmR%$1xBtV%#wjwX%^ET>~|4RZ=ZD|1UKP;dIPzi(tLd*0| zbO1(B2YqJ;LITJ*l|bQty4e*$4D_xAFfbev6obBe+t!B#f1IDs0RF7`0sOW70smVB z|F0(b$M*l?p#uMN?>>O@a}H~MK3mSu-m?zvJ@cl$o&2}+v+J~g|0lQYJay|1!2i_E z+fRw@U(U}-S8V;*g`18){n}$syyobmuKdt@E_>g9`^_diZ-+~O?Z`1fiFz+0;T-ogO% zR#CPjAOp~ciI4!@i?0_SsJncO0Y*kx;Fg>3xOM-1pItBb59j9}N&W@@iwgX8eh!^q zw!g{$LBRiKG8&dYY_J)(L+WN6iZ94vp z>yG`{HLF%#dBl4K|Mw33mj(XJ>u=tH1yBLCD5#f%MhgrI0Q^_4J^sp#S20w!Y~hv& z`iB94{*nQ~|4;x_0P3Jnc{pV;Fm?{AbK&}dvfC|94vROb5Xd7sh0-ypC7RIbE zBU)ghffNE}79fdJ157rM1Tbj;3BZe+2Zcfa;edHrXw?FAf&%`zi~d;E;Q;pqfLqZ3 zlL}a{0PqqDkOK%=pm1lfK(hJ&{B3be&?p9sgW}Wz5do zfF_Vu(4c|g15H8z6;Sy<(~GIZQ~?5~1AW$-g6K!`*GhUh_2k!Xk!a|HhwKZ1XZ z|H3eEO_&4vO@%RjT=esg?)4@2&w@{izB%W?-Chr|-)m*r_nBv&d+ym6UU>e8FTVIA z*L;rMUa!5{ng1Iyz@FVM`@E4|7+be)+O*m3a1}0lzOKOGVtz0OXaTYs7_lgr1+px= zg?MRjR@T}DS#LGKevhyt2{tJwD-&u&^iDL=%8;#Js1HX33M#npzcuKMi&1%!-xNV-Ky1B z9r1T--~Z>AOyEC#5(+>CR26tC2MqjQZVE05@V-lrI)DAAwp?!n{I_ggFhFSlSO7Z+ z8rTT<`>N0lnxZ)XC#Va;jJWm90DMX=NLVtXbJ)m0I&cQ;4LNLxIjw= zXfG(}9|i#TM?p9o5FJpp;F$$qWi?Rwy`JR14t(G_UtRg- zA@801)8v(N(u`@W?D?H4zk1IeILXo<&^+0H;6KIR8T_Zuz$t$p$G?icX8#~qulNM~ z>zE(We+^xMLq|{9gZ8U?eZfG+1A~PzgsLRdD8OGZfE3`>^PhLJ!ioX@rjO#kn34c* zK%Ibpa{nMzJnY#1@qPsVe+U}hFZjp!@tlVTzMj2aW&1zqnx97=e%P|_x83ba^6#3@ z=RM^4M>*ktI6trG{8YR_^1rWZF`;87EmM-oM{)(nF;AEc)e$rZ!<6>n%nhE20DIEx zG%(9Pk3Vj4+Oyd|XFhP?=UDk8qLcqC0fPU17yg&vpKtq6_<|(pJ_w*0K=f}00RJfp zk0=6|H2~N@d>}pz0kjNQZ~*8p2gm?&Ky&~jwWBjNQAj{ZHGm>$Ghs#+*F*yRSMc9I>x!Vz08AkM*S(CO zQ4J^?ND3G#;NMaXZUr#y4znCw6@e@Z9UL$#fS`X-fbjn>5XC+Be4eSqBw;qNB7WNQ zwQE<^8E;?+Fr;%w|8WI$ym1rD%u{M7&YdmcJ6d=fjKV|>4 z|Kl0{jrzRjvf%qM4Szzjh&UPMSONci$lIDT=mNT<&>t2D{^?)(9q?y#FlIo1M%9%1 z1NL!!lmCyY&pR;C&>xrh_S*v(ZTlDc12v+*&_iQ^N<`pF=t|~qmitw&$u z{2%8$eDj;y{&m#P6CCyPWUu*r?zu(T_oY18@l~9Ez(4r^hM^p*T8v)YCf4gQ37Kc) zQwlLp-m@c$g=rkR0IfvZ(TWf%&Ogcjas8ZM(sq}9lKsd0IqC=&eg@Tp{gvi>Z`5B@ zdo_LhR`mjZ2r#i{s$YzNXQW%|ma=?!It&mJNEZtJz`s{nLDmAM7{kDUk#Q0HzZEf198ep?@49)*mv6r0 zD>q;GrJFDK0^qOnZ}NZVfpc#^aL#AJ|NUnR{`=0t`DyUq1Nfi5Yxn7ZKhDpMxnldD zx=HXq1@K>g<4KonIq~d`A3d3+{#PIQfy+Pemw^8z?*;$8^2yQ#iV-vxf=&{kK1n(F zJ(nJ_X8k!Je+BrTVETp#wjcm_dEcBEEMQHTbYi0n&?+Dm;UNQ{fJID3P~hKNRRc&x z;sMp2aiI#hvNQl*P}YRjJ>}po1vCZ32m=3`1)u|S?uUiN}8eqDI1b8F! zD-j_2la}%}+dx$c7&4$#z#xDq0ZRc)o`fo3VuEv~funZ&?=F zr2vZnBboq4Az;Y>m_V)z9XepF36%kc5Li(O7$iV3z^JWZzxvhxMNSLMfqPT(zi;gM z+IHhMtOkY)i9xc>bpQ@TF8p5t=3ETpLa@jx2-LLmER%n7bHT^c{&V0n_#g9!jJ}e4 z;D`YHJpd?}-=}Ab2 z>pjW+A7r=JLrMN0;hfJEPk4F3q4- zp_oC;4rT&BKJ8GwKv}A`-NBwvDb1VYzfODp@Uh4EsIS0(;Xgpmwl82E$j?pee}@P~ zcoY4>|09psw{IV1ZfYH}05mW-04u1%c98-G0W=K^3YhSp#uWdhfZ|puAQl@|(tlo+Cqx4b@b69H7^21|6GqeF*1gAI{I3{d?E!)%h9Vzl)mhCvVvC$?JD~ zV#|(CY}$S*$^UiRbbcNtkf$i9=FOYx68!UO0%!&(6_9m7WPS?< zSnLd#7(fzO#MNYpA%LL*paCxfArLW8Bv55xWPnzXg}Nqyg8vXeJ3)-`B7$Wx00iQi z2%s3CbwI2j<^&{=sDiK>ph>`tQ4KKa0O0?5B?JZ!FqaHrh8F?iq#`W(D+AO4XZ}~A zzy7=?5TC^mn%0DRJ_H|a1X&CCk2Zq(oS+H)tByNr0Tu$91lZv#3%DRCuQ)*nfL#Cb zOU?rY-IyWF1bj~_zx%W2tE>h_5@UzafgXhS=~sFXP^DvnIW{=(Px23K+CFE&hn)B` zt^Cop2mT4N(0Dpa3MdAcuwUb6&iuuGlYi_Zz~3i(@nFXR|1>-Q8ePM`%fGI+8%I4L z4-5PmOM&`i{i41h@-IRFNQeys`N55t$U=$e67&anKCcx~HQz6YRwS=M{|0}Yp9J`? z=U=b;R>0YR%7zvhR1tvweT?!i(knWWoQdfPb8yVE@YGzt3MvcU(0u`=ldi z0&*DIgnSDhXV1x?l)A37w z+i%Nv1OCAOkw@~g^YaqT0Hpx9TMU2^Ljt1~APLMoU@;&BYD4lO)BxUqe$hW+lNbJT z9~wX(E(8z~poa`A0aHC#{AXvFB+xXF?oB@{0VWl|0GI}Z@Nc9TP&7~{gh&8~yxn?e z=j}ILbLYYH?>Jb=|2end{Ol+BZ}8u@cg>zo{wL?>lUo7*?Vs4Z{nSm{S@0?NU$*sQ z=WROf6W1Pl+%-pk=*kcM6({@%{=t9lB>_J3oxz4 z<&6fD)t6BMAPgW6B(|sm!T~1#g9TaxY@R3p7BFyt457c{9aX@(_JIKZb?Shk0rB5J z0QBYnWq{~kDqvuLHU^B%VWEI9faQP?Kr4WVfCB$ZN$nD>DIF@XL-uA+e8zov58K^|p^1_%d~6XdEOf{FI7;s4Actpc!tI-&u>0em(& zL0JtHJLosPDpU?Y1sq-skN|Rt|3d`Sa-awSz=~PHsMq-)%RYDP$g)q8|74r+KRrxm zCi(BmFUdCSN;A@G(qta>Rmp!H_Y8F7KY-yNdGO!Vo=E!Nw_56xUsxE1CHT%Df51Nv z^<@hI=+9oV+UrZ3C-*-akp^Lt^wOdHPoe~Iem(;|F_0Kt)5Pvi##HCMV1K#`4vqi8 z{yRsyazw=d^>vgY%9&$!$7 zg%@6^2fM!%=cmE{wbustze$c=wHWt&ZMh!Dmbu7OV@i?yBg7EE{P-sS*?b`b=gbY7 zp2kFFEe`zgIlnP~Rv%4dZHmoI|;6yfi`{~v??{OBP+C4ed* zwcx27{0^l6WRuqvQh+QF1t0}T0-Z4? z)yu^JS>x6HUkZRhvNVEjg#d~JJSRl(uV9)Gw;a6w)`OSdx?lX)`Pp~ofxT-0|Gj(8 z*t2I!{!hdC0sKk+1OD3n*KRxc@~s~~fAevlyzbcJ*R4A8$|L@I?fd@wrGGo{e`0}Q z1WovVNv#Nd&wszh(ZUfC3Hep7!%|H^fMGs7t&dy@>|Kj2ma7fGz?AA z=1(4tM$TUNFZhc?7JmhQUsL2$zqqvh`4iYaC-cW~esqy6`nuJZnkE9H(BH1fA)k;r z=!fcK7?2H+EksIQK27S01)S}-y$$&T2Z0AY9|PRz)L-~F*Sx1Pe~x%F#Qrbo`+QYr zJ1H*Uj|l=AQueLPALnP-{>l7xeja}K5zhIf?EBP{Pd@!LW#4C?f9^TTzCTRzU(QeP zAKNF!&zo<&iSxgA?@pZm?c12hOxASAwahAJ4`~v5m~qayE*2&ufr9A)8johAv03$B z2mXANJzqKPe_r`R<+t*GJbhF9#(5DxnBQ+p^auNiqht>9Js)P!1mMqK3aIJsNCFW7RsqC+UrYSI`OqP*j++i1I=JImxA}(w{PF(Ya62?9MA;7oaHmXf9uxiD)$HNLIq0-*wjK`S`oCse=EW{ zCZd!;)dE}*G){>q{D%qFg#ti|1^ibfpmo5ND>$HZK#IXd0dl}#0N)V;QUFuBVo?i# z0=gV51Jor0q$(T=kOU?QkOo#T0H_fL5c?s4IZ6irEpk9bNFeoqK?5*>MiAGT?>7GH z0hI*ETNuCvp|OI(0@y%tg5-b*fJlLV8-;*U0U?3Z0b&Hr>wy~n6#~D^ilA41@)O28 zxy`|T$ZrmO*tU%U#7JS0Fx^hi7T}Opi#g-KADT|s0ss3szgB+TbL5_1OTXec5dskX zD|(3!zmN#>wQ_!Zq!-~IGbY=gWA;u1*5DT;moyyk*ZGN@qt;X4k2d7L$H+1HSJ%(l zzEl1Q{xtv^1`7O15lt3@8Nz=ufAK%)A1^{U8pez@_27fuf0FH9<@^BqZ@g)k{O|Fx%cy(lcw<)KUULnCnxB$s z!4J;hW*9S8X&IV`9)NslJ~)@=t;#QZzFhX1ZC|TauReO!sww>k(M{|d`{%cj_4}3f zeT04k_$NR`@xK@V@TUbr07?KffL9~{cf+>AFB(DnMFLFgLciQDP;mfFohbNE^S{jq zO5e<05DFj+F!?}ts{rWB;pkOIqXMcHECEa$Kx)u~rOHIb0WAQe0K+%XKOd67O*iFs zlX39iuKoKr?cI09u00p-*nRqqyH2}-kbyWmr{C8IXQ5O&>}pb&U&_CNCfW`I%v^^3aadm8+4uC@L1R9{#8 zkoqsS`kw8BeWTY{@oDnU$}g25Yd*8)X9j;}^mI+guM8N_4+O;bDdaEg*GV>+?94y8 zf7$+KreCU}uMhQ2N~_huQ+-MOJ>-q#Kj7cVzct?nS@ZKC;Qz>@lzm zMK9otCHe2uK55sn?6VI1(fr}Sx7Di){7L>@`6>Df@o#5<<^pTOM}#EG^bhzScieHo ze$xM3kpbfW-~jPo21o<_L(2f#ESDrO)dI}_+X|v}g?~bo#l1KH`2S*_9jO8+6%ZXD zHyeYiKbKk!y+<&pM}*a*112MAVt{afEKt!YLyATc0qH*!3dB{vCVvZQi+e-L^ezZ`k>%Ej!P>cKe!3ww`?UrV~!O_Sn@|9r<@xeDE(X zd*5GO_JQ|ae&h$PIA+yV#~r`!q*Jb4bMB@KFWq|i#%-H+8FW4uFGKv_(LBIsvH&9k z4KU)yvV!1%Q4F9O%*ZJYzzGWc7X^^qCE^01D>pA}pvZtw03^_%2&jk&R1_fihX9~} zkU-)8HP^%lVw_3=O#>iE0@R;rfN>lWxSUCQMYDh^VA25~PDif_l>>+(0g!*h3KmcS z7z%*M0G9&|FQ|EdX+wY@73Bgo4}<}L|MP|j0RN)^m}RkoFoM(o5Wu_Q`n=;`ve+2# zyDSZ@a&TKg7K1|p3mx#Af14QqIvAyZX+=<0g)S(d$^pN`pk~nSUp(iF0mI-h`QLCI zE+1V;FVQb_1?^o8OS8e3%j6&M!FhJuSC;)_|DQ}QA7DQm|H}vp^5;;982^5^sXu=Z z^ylxv_xJe$9`eTV{un=+Z*J9&O5;BOqtn6g5&tVz3h>_}gxs`&!` zDf@cV&yzYo-~ImgzW3bov-87(|5sl532T0`;8Wwj@So&=&z?GbJIOy@oLUSc?rMHa z$gYT(c3`MZ$-k|C%Bg5BlmEqO&pGp9#mXPOpTK_|`rL4zbic@;zApNM|Acl?(Eq5T zXmH4mRZg;i)K>&>zd?&6Y#d%As>NmzAOUnec&Y_}|Fm$hKj_~KVDpy}&j_VaOoxs~8qdi(d&tpv=z2u^ind*2=kJyY!3wVRI$Ss<}v=T5Kf zI}`wk7(r41;LoioU}k_DjEMoRmjqA&b;SqT(i)%`V6XuBe{T>5|BD5{0zO~C0#N}n zfF!W4jG%SKR1Ag+1_5XSjamQ|VR*oBYd~ICTrU3S4*X9}5NS@r*$wvl3iyWr$ob%c zi2}?5nG?iZ3@8@}l$a=hVxeA8&>#4JI{{QFfDBWh2?GE*MF5Zh6K%Xy0q+Ri@6s1x zfYJgi34#JD#v=nj|G@xbcUYVtfq&4zpaAWlxIw7}gaJ|tzy%^+c;Wd@{`;u^oc76q z4;!-1u=af6{PU=nO8zV7Oy!pb!3WE3h+6h}8o)i9|EB-%kpBbxZT$37e~$QZv44f; z5l?j#{6+tEemLZfmPd(6%jeEb?d)^B0`<{v4H~=c#6`vr9 zzu+-kA8mg^>>0Ly<3I3kVk^u6c%UCx$eN#k|JT1UYQ99PcnbO|BW|)Dgpd-@7{gne#~-aFx5IHB=e2w#H?Yp97CU< zokl>jlAOSem_N1blZJ#)N&Z)uKOFdW%rRyD9Cg&u;JA=(_CG=qAmr=z_)*dC1Nd1q zq;SPX^FWb+ zYz+@2O#jOXnq&Z7oL(-XPsiUW@!z2rq#7KlLBV&R2{1H( z0w6pf7eohy0we%f01g--12h5b+q-XO0jfM4w@9n*o@qb^7!)uVK=hxYouG&S$p99R z20G9Hvl9dd5TgGq0$_nH1V&IeAmw13AZmVz><*qv!h-{<9B>`^zf8bA$HYkl73&5I zAOZYHNC8}lAp@EMlK;CsjG1e|0g06=U~M}=azJ>XSb%#dpo;;5zjn|dfH*$=d8z+eEY!T;VO z03)c(AfR9L|F=-UugeLF5E#1x#zAqqK~#geSO>(IW$f+R3EFtz1LJ}rK>yOGbR2*~ zpFqQ~Eo}?xUU;DmMYDj&L7sZlqsc$Zb--_v_yYeS0BZebPJpXgaMt={r&lN<=#TFc z@TYOLOVYf)NU`6f8}7U`5}L}^2$o*g8B0;le}(a0B7=7<{$9S za-Seap}*LFk9hNi0scu%P5!_76;s(l|NDHZBPf_By1UgE+kfCcuD`Z_lK=69muFn_ z`TTS4_WfZFdDixSWpaMV{NMQLn?EJMiQUjK2R?B4_Vwey2Ojc!waGvG?-=UjHW#46 z=mnhrGkeb$4G5RQuVwyV)O^I!??;X~_E=7RaNQ^HUGRTY=l=^57^vUi?;F1hPgLF? z@IU(4V~&OJz6RmR0e(zT6d)-o14;@E0dTLBNyE{2GybChxCk}Cv?yr6KO#UKFd0E# z3<*d8MFn&c_27HF0RN>CG&sP1twljlfFvLV#0NqM0RG_svx2a|G+==L&q@Kq2~q=a zK?CAHx2gz01vje#&;g5DKr}#Z$N+^vV$uQoV1PjaazL>F@INSEDFs>p?2HRkG5`-~ zK>|B=Y&W*?5ds(t&^*9w#tp&;GUR}jEKn362Vew+1~y9pMFOn=NIcpE>NkL)bpW_O zMdQD3C;$T$Q21XOfX|BoCKZ4PC>j9&;{z1~NCM!0-AL-lb}nlMXz*vggZ)f<=Dsn? z!I1($Ldk&a4K5M@vswc9PNW>nn?hjH0KtEdO>nE-!Kwf$V2+dnpa65x|LyqyziMBA zZ$$7v6c8y;<=|ogivg|)f&?rF{6-Rx0zBU{B;d~A-WQ-0Py=*5z+wRNclYi*k+c>Z{;plmAPn1AnsTH=c7muru-gNz?&s;eV|Bp^TgCe-JzF5}`+rGY;S;Mhl~7B>&83WB#R6 zyrZFiVf*O$x9cD0zY~A0AJAVgBJbDu0sM7-Ks4NK0dCm-U;7$m-+8O=()oGxkrhvP zdG0yOz5#y^`FtgZeB%5h^Vj(){09{15#}`0mYLbff4G>+FGHTejgn=oGBjx)8VbGC zmETm>ll=Rf-&B49f6#yRs-yiVFn93}3?RmHZxWaYpnfagQSdKo6#ny1^Ya>EfUrO_ zfDKX{5CRAT*iv~F5!iaPAz>N7o9$W*;C%#*Zp#P%b5{c_31DdiK?8}I1dsqN0-6Az z0kOX*fX?q>AO|ooq6mA`WaxnYp#VvMs5`hX19Sxa0e{dxF-w5R0I^>g0QwgP$N*df zG5{4YNT4Wy^dCovG)(y41NnQ~<&_T<09-KZfE`T&+jl?!T%`iGb;Jw0F&r>z0m1*G zfLR2P`mYFFAYxM!fQ5juD%5JQQFka{1VZ3igg}*oO9rf4x4t=mO2FWNCIKj*CBRi+ zKd&hS{}F)yECr+h1b{CRpR$3<2r3$2HV{JunEyuui2PLz=94N0R7F4rsA@pGAQ-?P zl^P-ZO8~hL0#LxYZ~*rb0Tu(ag1CN9;{NV_StE!IV#NW!P5eh~46Dsy76U54{*;3i z0OCJUM?9AlaCh)12Mqj|3Z8%dc?Q>Z(dI@{0tNtHAFdvKMi|*(VGL z@>JPz;6Dh|#a|M7fE{qJ^`GKB+5h4V;1Bl860!YF{e%5%^%~C48Ls((W%#$?VY&p4 zrn3P5^sdQ2|2M`5BZra140aL+_dDI^SBC?Crhc%1n@NA6Kej=w_qj&`2=>Ja+Y#-8pFoL;{+8@q@J!wqswU1@b z@A1d8>DR;l&HZ!Y+p1N9IOb2;KaKiQz=8zwDX2dJz8Khl^r}^>fG$q77XDW-z^DZj z1Ee4zM>Yop{9^~fpAiDqg2MvgfY1Q=|Be(eH4*~G*!cg$PbU>*9bU+33Jw8x) z0Ps&tD}urRxIncc2=o{KQwZQaDj@hT0VHM>z<4bUu&x>4>LCD11%w6U0yIEHW^fxp zU`|m$34u_69Kc6NfV&i6F(7vf0ZR^$1P}snKwUUMfPeWwfQJ&GqiX>nfI$Mn|D*z- zf%8fQgazP$1c*euyFmZ@|F#H#+JwS1@JR-+P^-{>16-F_k{m} z|0($w_um#Gf93w2`tyg){8v2H*W|wzdZTmfeNj_oY_tix9~ zF}IjKOcSO9Acv zp#tFDh=2$IyTbmkR|vpA0aL@(yzVI$unsW1AQS*35E@7)kBtF$Rp(0s)39)e%%jCY z^w}yPl?s3XN&*1>Tq7(6=mjAHh>!sIF9|GIAS$4Q05lK@u%LiQfxTq{^_%!#0R_kd zkpWVG8lXhLE_qu%+-vm&b0;Xa>seo%FfFc3h3<@w?uyt5ik{m)d4L6S_1GE5wO$&VS>W{P(W3Kr?aAUf*yF_>kJWw1lo_D zMb6P_^iL(6G%j>XyU|j>KdmABQ~EsRpD75m{2Tr8 ziF&PnoS(M+Il~6yheJM7_B}UGbwB@ny2UDhhrm=8-9w8|E*5e(Y zc_w9FpY8jDAMnEOm}rY`f8xkz`ARxw>J@8tgy1~DB0 zAJPlhW0c6*C5L*VNwL=Yz#q?hi}`cxFn>Vsqucx;rJo@Fh%$fj>m~B@9ef+XpCZWo zSFK*fknjgiv99w6jY8n+@eOKWSktuTU`0;&`c zW~L}C0k}MCtO<$)h!(IEka9pM01_~i0HFXnKjhCSsKHPyFh&g{W++H0fc)RrZCwor z4PXO>1X2nP_BZ;+2on73LJ5HWbzka$1JJ;JgVeu`AV^;l2oY2*m|;)~U^gs)3FMU+ z5+LtzKmrQb0SRPLC>#(ThyYLoq!!!^ur<|yrhth6u5St`76=1u1`0HTHsyebVFV!o zh=6|;gG&ZnR}6q@K(4}Y<+Tn4z-Q|%1gu+dz}15TvK|N`kOnLTBvb*+XM^$H9AG(k zL?nRue?@UXp?@*JAOR2y65wtut)MOkhX=ru3MSMP1OHP9Xd5Up03iVW>jO0d{Qivn z-T$3l4OGPdv_Kg_Z3j)&fJp>&L0Ie`NrLV~V7!(W?29DWVpnkG@n9hDJuZI7g?&N<-{10F48SocW zOfX3|Nj?D~)~IlSQul&b_pNY#3jGEDvF7KYQS$}-AA9W4Cwk2%yM4ch^Yeor3TnfMP3knu5@nYMHX(~k+o{J|`}oMoTc^F{VTYtRd@qx_5c^9fG- zJe6j3+5d?r9)AMPe?RBf?7z(a;6F$nP@mx6oF4|L)PMde*dP4oy9NJ(|JAE$PsGj$ z`OnDmC&9ljus`@O2S@{$WfDLK@K00bUK~K<4i126C4s{^U|JER1~C1%i+n2zC=OWQ zf06lE6BPK*tq7odUmi$=1&Rdt`7(?cJ#m4!HT;JIA_JrVR6uip`F}Y_zZ3$~qEILR zY?;cz5CLGr6pIPOo1p|~CBV!}oOABGkMe)W-Tra{M8EnDNDYi=6gm}eN*(K(V0|QbDPV$dts%EFWl4U=A+OyC3)%@Tu2mlJ0%K;Gr+)@mV2mt@X0AT@v ze+YpztLVSb0TTmA0u^=zQQcCDf))gjUbFY?LVGe-oS?}Hs_xFcZ6Ha&(J&~WivgWR zr~zgQC;?Cm00op46bTR+fE_f70Wg5*pO|U^QzgKC0b@mw*uP)^io&8lA@~dbxg-GK zzX?DE&?*2c$SXnSKgae20$?V9P(T=9vVpb^4iNuqQ4k!E81O$cz$TPHB8x)J|BVgd z0Mq{>0MbT~qa*Mi5{L}&mOu+!V;};&5oZ6n!2qoRW+9*yPzFQ?#0Xjlfe3(9geNZu z0+0h1I|F77z{HtcAkbd|Aj~M^1z89vFG%?3gL^38f*}K}2ou0jbbvD84?*j9|9eRO z|JS@GsDwb-K*|8h!OMbhXdoO=M4$v16wt*0YXQId)ss&?N&iyap`%Ft=?iF=HWqV+ z`2+0mQ;R$)JA(e0Kdd~l_!#&n3!UI!tZxeNGk;n4CHjN^(}Ew)cw2%$W#4lZTl}N; z$3<24jq{VTFRR2D9)f@6ZvDnDn7Z6@rNUQpyhOkAzR*9ef5C*v5%>?R6#9P&`1z`- z|HAe+lLi0HcR_zp5HwsS{|}e%|HvbcKlT`9-zT4{qkb&={@}$IeYWFE9`*3*tM2yV zfaf?r0Kd8a!J7^;`$_hg%S=}0B6E&OMRzbW=nIp7D!)noX%?4#t{F%DpDN{&{MVkZ z<0<^&{B!2Ru_O5h{#UW|r@{Yl$j`5#g$eliB-qa_@DG0+vznEM!T;Pseo`Q>a6s1r zzNQKwXeBwI_)-x-L_RH5fcZakz)b-#L;#<*2+#^D=W?SqM-EzyT!y#!;ce28s&! z_ox7cz;A{ZG-&`ni3afYD+UhyO&`$s_UL2Zexi>0vF!WYv#j~dg8!F(T<5&K?wX$le@&mC8RUaZc_uPbmwCyoV}{|Q zGCTM&`FSPcs4x-{gi-uAOE1VLnv;g^1Ak74`D5ibrQb4tmiWI&{c9yqePIFqz<%NX z4E`t!l#T}*jR5=A0fPTf0fPvN0|o_z1u%jN{%Obi?!!sDR~2v{@DKRI03(_Othl`0 z2>NnMfT{x*2heHsU92DoAVg3iAo$O1t_Xk5UJnhJ0cZv(0302O6@$q1!4UqtARzt^ zDNrOZ7+_F9s==5*%7BRiz<$fY2Pp(h9MEe*4`2iFL1azn3K39BAo~LL5WBs?0lRji z0a^wC|8M{=@P7pZgaX0>T?@EzQUM_WtHG%VEJ&d0!7+lahXe$Fug!)=koUlUh(Han zQ3_}hm{h=ZqJJ2mivid`1_Ut7AXz{jKn4`}b4LaY9e@y+qyQgPK?-jOw*I6u!nKMr|*`DMz!ulZ~* zmwf8{1o^pP5r6?q?;Rxnb=upM{O7<2yiSI%$^V5p>OcC6_J=)f^K$+_#({4q9Dn@r z(}8bE|FiO6_oo0J=ciC#i$7nQ(*F96f&Ki6qj7z>R#QPh7;>T=%bXSr@PPQASU~}{ z5dt8$r~n&I2@n}jEI@6IC?F(2C)%H_ z17;y$PeTNW5P#rbA>f#(+`z$70-#2aB4DO~Nd@o;JmBu&1qDO~pacxyzv9rKfYbt% z010$n0tGNu1xWx+1N#OCAOiO8Z8HcZkWc{3DxfF;1pxjR1uR$q=x-e$2_#^EdLKsw z%@yI8Ky?H8BLRG?fCMH9&_+-Rf!-nZjua65hX;(I0v7Op4M`?AL!bf%1wa6@z*V4+ z1ke-^_)kq(4bUtA2UJ`(G=LPqBuS_O5@CQ*3%Gdb02!dF0s26b7ZmKziXbTf21uv@ zI>G|TfFT6VJMVu7u>XO7C*W@g{t4;;TQD=d zfT>Oc)1c6%S}BhgF~$X+7D0XZq5>6v6#qe^P(9dWrvC z^UVpLX8``a+Y8?xuHj##59keWkN*b$8FT;3F5~~ks30rmziQ?-pg-@<;zYmkIdhyy z-hWrXUmzeejOm}uAMg*J6#O^%ga3ghv(@`d{z0_Lc1?kWLRbAf^iZAf`7o>fS@8MT zW32i7_II9q^1Dwz{e6=E=Q!%;#UJAQzm!8hU#X)WYQbmV|K~sd+0S+UN&cD2%vLfp zX55B4f1{RtlKfvfp7Rw8F3EqL_F0wRB>&TC&mWEXlc&9q{;yg!n?C@)a3B9aG*G0F zyMW)pAF1Gv1O5c&3_mXnKrBfh8bA{$N}z>+{+RGj&}gE61stG*#_PUT0pS2B)+h-8 z?DI*`KQ>Uy0KmU_fIg~g1p$cv#Q{a=YC6MiHtg@XBRN8in|V0hZaMfc z5||_a6d(yyHJ}*a)@%-5Qox}R(EvpNGJqri0UWk9Ock&n6NtCCKzz!uh!Hdj!Vmyl z01==9at{s20P?_2WWb~Y7)u?Ca=_#Tk^eUV++ZN%qjp(B1!?AUl{yHya)P2 z0-*oziU0=qO9beE-WZTla5#Xy0kMHn4Q?YSHjqvb(5Lwmezn~`L-PM=$Pfyo{EhdI z?d-D8Gy74Gz&|OdP(G6X0R05}(o7f-AKjNPy;RXH!{UHE=!<8jj@KalT3yT0HkRQY% zeFe*0=<+oIPv{2=AAIn^2LDd}v3{O-{PFMn^LL(l>Z$L!<}lYdV?gR)E3Tb<0- zIv%Dop7YDH&z0uSDzkqh{(mDro`2{6e!<`KN(B0$4KCmx5y7;C>BN3Dz=)VZXhtLW zF9(z%WI=ejH9+_;0ac{{8KA-jRtFff4Fo3+0|L0at5N6Q4EGY0F3a}Dz>n*u%MF0>vBmxz%!Uu9q zXpA8GJNPdFRe_{ZLpf~^mh!Ai==%9dgD`Y@4z%{7_aF+mp zpYnm2;v>xULImLdYz0jsAlT0&A(lG>#Q)}i(g2|UXW#*7z!+NsR&YQn!chVjfjpB6 z0RLtk5F;pyf&PfdKmF6SGq^>7rGUW#wJ{*@KMKM#38WZ+6i5ss2o}Jarl~R5XiFQb zD!&)_BWwP6?x}lz*JS07D-Xf{!t#RlLjP0R>IeP(6hT0QAcFqdKClK6+hsIh6dTAHzd+{S z$v?1SmKp0A{0EQ{0|Dy2eKRx z{D%WV1C|4fg$MxsClNq7xP-tK%E4iQwt##C{&@`=5d2dRUcmpnBq;bF4rm4l4L|{l zVgTcu0Q~FDCo({j0CX^9z_c@flxXOHt_3Io1`D(k+|0;ABi7{73ynE2wG#!wJF+a%=EZ4IZ0=^@Fk~bWste7LalPZji?W z{p-Jii@_W!ze*$;fgc_C2YF2X&zkTb@Ya7G@Gri{0;*KMK!1S$T<A!n_&<>~pWpp1k9T}V=jVm2`Fx25 z|D5yo>T53ef0GFO7xw=gB-p?I0Fs_*%bY}sGrdTlm>*08vK@vs!*0x_TTr3az3-T+W-(L;r2mJGmxfk~9`~dz;HiQm8 zua^|C5CL()iUMdEgpnlB8sO_Cm?jGNrzQaFC|<;ULj`mpAmA_j`$|VN2PgsJ1`Q2h zEdUp&bwK04Bv5=_1Tg9Vh5z(AJ#SPN5D}1}5g9NOK&b#zi-`lEfUtn2;I0NY3k(Jr z;ExVK1PmQeR**7a3OYP0;9wEJO``|^_#Z$7L0o1tOtYwngz-QDguZG z0RJI?CIN-Opn$f4;sSB2=tZFyNdVmD015%ad~o}t!v5R-@k0N>0XZK64k!|+O2Eo` za2r8NfnQfS_%|L9@#?Fu;-Jx7v{3!<=VSi3?7z$(lTT{3o%}EG-?Hv9lJ(aYV*3o! zr|^F|-kC!3Wc#zObTfwS zh?HPL!EU=N=7n%6uy^u*;t9tc5BhuHXQ97KHi7@ut3dWb`Jg@Lvu1 z6Ex97{J=#4;Q;t=ctFYjhYA4vD{Lw&C5cfApdAS`0N@|=FDnQZkY;(^OQXvGgyIVd zC>A)(2(q81n((9pvMr!gKuEyObumzGq%2Xs&n zo}>WaZ`=m@=T=n!O98jx1P%C)4>Yz0Oca0%)Di#@K-8U2h7Jh!FYq7m=Yjy50eWWu z_}>H&4k!sA1z-dz0t`d|Od$HJ07h(U5wHyc*j5}+LcoFm83;_kooE#R0c;Wc69@oA zK*<5%Um3uNEd?O_^U)kIR)s|{@0h$ge1PK340MdZypBs?)r-uI+ zLFj;SQXHR@251pbm4G&asut`$m4Ld*0tPC8Nu%{|^3R@M4}1f8eD=+p{GZ_g{hI%v zd7J;{|L+uhmES6v!1wF>@F(+B#~k!v*_XeWl|}q3F8DnEeEw1XY3efg9&{5uNRQIV zbUNUFHO|kQOptOW`A_B#D2Y_UR(uUC zjt2n$I6vUOD?VBBW9kq1C;9gYFDCz8_GQhdXFZhjlN0{m5d41z_$S`_Ig7)|_n61b zS0*Jhj@f1MpJku?u!veZf-y;Z&!apZT?j9zc7C&{g(YR<^P&JM+y1d z_#~h6o8mqxM)$EwIzTu z;QmPnNV`D)y%In~fV(dy{I`3g=(u!t7$U%+2NT_!^lH%GF6KpN)8TY_C;%1EG9Yp= znE@OS3J3$tR#1}w`Ne_(G=Y==)&f!tkOO#G2$lu992^Qz1;hw?hl(&VzySRrf$+fc zxabxD#RO#k`?Z568)#GlFoSkaDj+0)6xc2H4+dCNgNp{FfLamCcuSD_cl3(TaKKCf z*He$WJ{kb{FBVubfKE_ZL8%BA0c?N;D!_i?+F}5p->4_Xq#!)e0O^hwJC6(|xEeqX zXdj4A1_x9*SSb+bpPis!|5`i(u%aX2Ki^3Iy;=r902dRvtvnzC8VLS(H5lYa2n-Pb z0TdBj@F$l4#|BFJ5B7`yTowOOy8}`X9>;|O{^bRQ3{nTE8(IL20(IrU{G=rRUHJuk z&ZP7^ANbsdzNPv*9R9%pVS|`L6Zkjw3;$X036uf;U>eebe}aDpl!IVt@NlsljS`_Z z>0El8+@Ddg68rg8;Gf%wn!{#wnc8N12@rrt^557Gc922>C1Q)%PwM{yxXUJp2TUNRDMr5f$YD`A5Q%AX)m7n zM*bgYKYC^Wp??{EL!!|?1R(q$%M*h?Ondml8sUMkKoNjc=!aS$m4L1V1pUJST?;@8 znE4kA2>k>9hX9ZO?z=BK0QesiAPXeG|6vA60Q8mEPo!Ma3^1_(9b46aB6tXZ2nX2r zctFDc=c59K5u^@SGC(T<@t=!`(HIgy0mKNB0|o`2s`_)ky-NEx6h zU|U4MvKD{6RlpDeihv;m zM1RBiF8C+Z{)zyB05mXA0NfHw5ikfKM?^#nTp|sCC4`{DD*^teB<#5Y!wM4pQxVVx z;;N7YDuAbmfImI_FAC@dL6HCwfa^i?1_6xqK&b?*d}M&|Pc6XvuYbeL1A+NL0c7g2 zwddF5bH&OZiofOm1M`V+0QtTtKRhtLT%Late{z4Ye^K+TL*6?1S6A_m(&CgJU}k!Y zZUg=4VS7L0A#-w?ocwCwKJU)mtn>u=3;eD4RTTVxo}@77Z_XI>SfD@Pf1gM+fxqim z0RMiZd!Fj};DbEdC7Hj@&toM2k9XPk>8CmB=ljp`gcqEjJlpq`SNicTZ@wAlC&_<- z|4k(SIq-og$~+|PVpcI{m=uh8Mmc#5KR)XeXpQtjp0*b9r6pxSpdtW>AhqCP0b6{g042bPaKKOjSr`ibPZU50mJEQ$JF*&R6odo+p@1$3oBwkq ztObVzg#VHNqz)tv4k&J8pveHW)<6=tT_>mm5=a32>Hs9bOaS2jWCcwG;HscQMF9i- zr2w%%!H}p$p`t&p1qHwW1hC&qK;S33B;5GVo6tXEXOn;w&_tjeR2(o-KwO|E0mfkq z0U2NzK@kDEK!XGZ1IPnV0I!Y}tAaL`4j`o|GiVY5;(uv@Pyn|CYeI(=gc2AkV8H+j z6(IU|NCHIxD>l=}Wk7QP1P~Dr0+>mlivg|(9V7rUc|`(nf13sW$Nz#Z zkmo~qD=qNbSp@|Divq?DVO4&07hn} zfKO)TB=tR?j5%w{KZ3l@%wPN`^)rG7$q>mAza;v{`bqL1=jUDz`I*7Ls=mG6^BVvk zFzAE59{v`q{paN0vhP1v+4tG!`Ux*D{rIJyy!Ke*$bNCzd0a60HH)^1SNw1sRm>PRg{1?g@BP| zp^HU9B?Iog7a_1@0JVT#Py+mq5STI(g59kGSQ7;PhX>FBQh;Yflow_iA?1SL=aGC<1! z=6lCj5~KiFgalAhU@$uY%{{P$MYM^;J5a_?807L-zM%w9tr})lnI6bRY{s{ks>;4ll z_VM=z`qz8^GR!b6|FV6O`+Lwsp+A3j?e=2DCv-#e>iqPxy-5CJ`;+`%#cr>w5QvTX zJwFEznDkHK=b6ui|DEy01rP_y^9Lac{za63&} zw*TXg;rx92sVAR$`Wch|=bz6Z&+~%+ob&V3H#y=brhmZy7jH51$?eGHn5fJ@QZ1$u z$v=~UyoUkK*doBy0vg5OFGlZGMZg3g_>TrKDF_WDM{uY_ zFh&NHjAV>WJP;|sZLS8~F^K?q05j;e&sHfQyMw|1p#*A4P+39au+W<)4S)*p!+-?5 z63Bq5AUtV+{SygT3~mM>|1Sy9=ubwGU}RMcFQ_;GKPc!Q4ZtlNP>&3liovc675)iE z=GMG||Gg>{4j2LMHw|o>IiQXR;xNgL8)bkEb<3^*S>Rf1Anp~z34#IOfUXKN&nuJw zLkC3slNxn7I7G0*3A&<RJC?H0VB;ble{0Bl7 z@EQW3@V`jlPcr{s5W=hilmJly6aum!h@!AZL`+c+j*I|M22|h$kvmxV75+^=!Jk_B zGm5`u{s{7ihg-b5=vy=q@aKrPa(?*BdaJJ|e4guWicbEs+l!t+q0v{spvgbZ&sBo| zn2rPer}qW^*DTI_US$5-{v?9K_z6^Gtv{(GxKh}EACY{t!5`QX$gnn8{POh3{G<%z%d+e<$^UZMr$=qm+$85F{~!POiR}5!nGbo?*Np#->I3|P z_u~KfLcb#q>N_P2b%OzkTzKI{aGb~4iT%X^f&RLw1ZWS9An(NisRy(ZR5noBPV6_( z0fPaionfH=KZO9oxJ3Zv0+kB5PZDSrkf9?1>K+y#|Az$)a76qAR-_<;KRchkPw5dSO4t`^ZO0QSQJfd6oTq<|Iy3lbPI zU;(y&il2`4Knj5>2!sAv55(=a;=f){0s{E=*g(L)R?u&Ff&V}H(GLs%-S&m`zc};9 z{GY^rGJpd2vOtl6_@Ce7+sgKtYrgYVUuZ@2&u*`Feq8gz3D48By;AnY`MKt5mIr9g zPMAMuXO-|fXXms(%R9{R2&X;K;IBCd&wm%`>Z`*dCu248=9tQXm z0mK+ut@UH(p9Mcbf54xh$%6lVD+)*{KqX)d3MdvRCrB9(CrHK|JHu$h@`5yiCJHDO zz+s^@cXNPkA3LaJfc+u(4+4;)qtikF!G9FMSBDB{7O-c(G#Npu1g{VRH4@qja&+(t z3uL@h)=(VaaLRb=5vS8w5i>{zNZcM4$YG&j0AzqdfXi@2P_e)*g92O;8UjEAq#VF_ z5dXQ47!)uSgc&bG1xy-%1Og3!3WyK@_&db^1U^u`%K(D|$_AQgfC1RCEwK;*3jz@T zrGTJ+QvgZJ&;h7`=m6iu|1dz#h(H1ma)6gwKmr(E5Cnh@kN}{7Db^tag8s~SDc~9u zz#suE;K2b+05O2N9!#p}RXQN(Uqn#g-wY5D012Q2Iz<0b41fXnkOL|%mH?Up+>ELh zl%NjqrwIBh|4*R+z61lv0lzI7U_BTHNI9VJU-b9!@x=kZkpbWU%fVz#ah_B8o$`NI zeue==`Ho=!_(i`<5~Qr?g;Dd>`QhMtg1>eGf6KnMu5C?c&@+fNI2=yL41xbi21I$I zaX$%K$e@ycX6@b1-&)mCd0o&y@Sg+_0uUd*+zUPd53odB3HXCM01#-DN~LfW923+? zbTz~0t-db!biuz*c=--x-|w;J^Z6X|+-2XNu;|CfyZrP`7krZY1O0-(_|IhD&w&qn z_AnQ>ZM%`QiW$S4V9YbT8NLiuh9hkOCt{0HA3-gtyq3>->^;9Re?IE7zQAp=_rU*( z;;$4ymOq%loIhU>{9WyN41c6@fBpjg5=`7j5(=F~NPfV76$6R|CKX^4*-lv#R7zl> z0Umf;Eg%wLoDmcWkQ*I9Ck!DV{*Op6d85Y+`z}V13c!MJbU=EyZeN1sAp$}ts7e8? z0N?<|M2!sufS=TGp&XEK9Az}sfSYrUl$uQc>jM9&1#=q};VuVA0eQ&)p@8UsUJ+D0 zFm?zZf(51k{|5;S5kT%Q3p4@DB(QH*0)_u8wg-<=!2gp9*x9v!VFyJ7r~;q>$^cdZ z2t~lG17LuPaad?bAT%(1AkkkQC=Qr625e~f4+kIw6aj(%ctOztVStnZB!H>}NCC3I zLI-FBp^}vV-Ydijs{si;gM|weEigEMrR3!U1^$}@g#RoIGW`$y7X#EC2WU_L5C!@- z1+2u{Kha;s1{$@1mG~bTa7EB$1x*w%+d#khHS|20Kkfh2u|F%)e-OXddGc9wfyR$K zQ7e8`eKF9z@#F$?!kpp(kU}fcHTNcQaQ5 z{W`f$VeXmNOl>p07{DX>2OX;B3uZL<2mMp^{U;5He7&+YPwFfuSLK&IzbAf-2lBG*YwY259uR9)JKSGiWRdf-fVQJS;RH*g#OJ zY^nh8qJ8y(XzHebh%TsC{+%?y{T2hf5;Flv0V`;Lo*b8j;H(P`35fqq0haq60*-}T zA%KYl1_ex8!x&GFsxjuwIidql0e%z*)W!e=fD~Z#o`ArA6o4vVQUI}mZWaE0MFo@& zSWo~2AOjr41?pk|Hc-`rC4r^^(ue&JKvO^~fHH!X%YpXn-ou6fIH0{CDgiq?c0>oX z86*)Th6w}(Gy%9MbSei3{c(bNXV?$|TUrMI`WQlnBA|lxK-nRt4A@u{pb50_friGW>aCA@i1RZh;+T<# zy(eueRDByinf9cBhOkg*K?W&~5P=s{|Ckcs(h~g5{2$DMPksNg{mb}y;@jW; z=O>@!2`|q)o3if@e&CwVa(-Ta-Q?e@@6Uc-3w}WU3MMy`m*k&`NSeirVzQ7EQTamV zFn$@LFd+E`oCr5kA3-kBbg(F`%uZ^K)Mn3bnLpyc@K5$X^8d#Fi2{=Q2mZNvC5j>@ z@VDkmeB>jb9NXFaIhzEE16l}79MJV(;2$cWg`@#hk{W=>zObT!$qEYoO96yUT-AWA z3$@{61U3At0_G_EPZnr84wbf>cJ8~@PFojnF1(1?Wh3%cZ_ntf&wNLuwZ~2 zx)#tXpk%->f+7K`BCwfJ&4-uKJA}dijf{1OAQZ5I1aN|?5-_oVCE>^b7X`rpVSzyb z*%@{t&?m{@?MY{dV6 z@V^KF2WX`X@RRjWeb0BPVi%#S7h2mK5F2k?(dfJK4E{?7bU@&)|0 z{RzQ8wm%DtSYCRexqq+uq4+{i&@=Q>oS!S`V7eV_oO7{8{{Dh*Ow!C*=50^mfIk|a zbg$4q5D@V1a~>M}3tPY%)*+gXHu!^8KGjh~1OKPwKluMRssH1TKasNUyxaGMAN=Tt zKYHnTM^JA+#q#Q$P|M`1m$5k&!GO(<bUEz<(B0Ndb`og99cV z5EhOESW-YUfZ#tdz`aO-r~rK+tsrO}Q|K!Rdm0)D52Wk8#Rr;IK(v5H5Ya5qqsOlPT0K5_qfGlu}6u@OHRs>NFC@1J9wd3fRMVLkJ)_jhz$&A_UL?EC-rtVEd2(+qO{^ zCL#lTZzrgXpu+yH2ZQ~_mK1}x7@L&HIvhNdBKN`G3l? z@6(ihpMRlu`{Mk(`brl3zuCLJ-g@h;SpF~o@c#=YG!vI8$^2tlnf#9f9~jjp|5koE zd1*@iJ@J92qa|rsDl8{w-RH+x`IBe89M{=@lK=AmkKuv8E5Lt-{d_hue{%n0{1YO- zL3dEpFarO@0NxFauZaPAO{gtn^YBsu#0d&%+H$!ICkPK{N@0 z9$hugGTyk6`!@$51Ih;)A^;<3A^|nPqyi=eXcbT#AQ5z+06M||fPc`R@j{UOM*=7U zN(UUU9Lz{suz>hKQ9#!M)B#NZ%7CQ^kOBM<{U-_-_`ls?d>R%53jYQFt^`yq_=X#- z2Zs$p0oPyOMo^Ukq<~GTfT01R12z(a0ZIjo)j(Ac2n&pI@SuQZfr9@f1%MC)KtT)w v06E+tJY)dx#Q>0iJP-*W@^^qZQ#}9@NT>nw$bdLO+?bJ*1i%T>3?lw-x)bhd literal 0 HcmV?d00001 diff --git a/tests/Images/Input/WebP/lossless_color_transform.tiff b/tests/Images/Input/WebP/lossless_color_transform.tiff new file mode 100644 index 000000000..6efa917d6 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_color_transform.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f252a25468c25e56ced9e70b9872c2324b84441eb18d036f6f763210dc565e42 +size 786642 diff --git a/tests/Images/Input/WebP/lossless_color_transform.webp b/tests/Images/Input/WebP/lossless_color_transform.webp new file mode 100644 index 000000000..89276eae4 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_color_transform.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b9557b7f3798bb9b511f2edad5dad330d7346f5f13440a70627488f9a53ec81 +size 163807 diff --git a/tests/Images/Input/WebP/lossless_vec_1_0.webp b/tests/Images/Input/WebP/lossless_vec_1_0.webp new file mode 100644 index 000000000..ea5faa2d2 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011089057caf7e11c9a59d3ec2b3448ea56d83545622e313f8584a22c322bc90 +size 50 diff --git a/tests/Images/Input/WebP/lossless_vec_1_1.webp b/tests/Images/Input/WebP/lossless_vec_1_1.webp new file mode 100644 index 000000000..6cdad61d0 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:482c1304367ede7a4b2e43e14aefced318c075e82e466473720d3bdabc0526fc +size 106 diff --git a/tests/Images/Input/WebP/lossless_vec_1_10.webp b/tests/Images/Input/WebP/lossless_vec_1_10.webp new file mode 100644 index 000000000..39475bf46 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_10.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f91a575ba29729357a612eb511a9ebab725c2d34a6a6eaaf6b6a16cee3ba25a2 +size 80 diff --git a/tests/Images/Input/WebP/lossless_vec_1_11.webp b/tests/Images/Input/WebP/lossless_vec_1_11.webp new file mode 100644 index 000000000..d516737cd --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_11.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e598e8d2aef6a562a80c3fc9cb7cc6fdd979e210c26ed3a4defbdf895ae1c1cc +size 132 diff --git a/tests/Images/Input/WebP/lossless_vec_1_12.webp b/tests/Images/Input/WebP/lossless_vec_1_12.webp new file mode 100644 index 000000000..6f8ed9551 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_12.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45fa843b9d374e1949f58e9d0d2a2ecf97d4a9cc2af55dfa3ef488d846ea3c80 +size 56 diff --git a/tests/Images/Input/WebP/lossless_vec_1_13.webp b/tests/Images/Input/WebP/lossless_vec_1_13.webp new file mode 100644 index 000000000..2e2bb6dcd --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_13.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04a9d1c2e8b43224f5d12623e4a085be1e1c5dda716bfd108202c64b1d796179 +size 114 diff --git a/tests/Images/Input/WebP/lossless_vec_1_14.webp b/tests/Images/Input/WebP/lossless_vec_1_14.webp new file mode 100644 index 000000000..55b0f3b10 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_14.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8b8130c71e6958fd7eab9539f431125a35075cf0c38fc7ebb1316aa0a4d1946 +size 78 diff --git a/tests/Images/Input/WebP/lossless_vec_1_15.webp b/tests/Images/Input/WebP/lossless_vec_1_15.webp new file mode 100644 index 000000000..13f3ff7b2 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_15.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f06eaa2e3fdc11235d9b9c14485e6e5a83f4f4de10caf05a70c0fdfc253c7a67 +size 130 diff --git a/tests/Images/Input/WebP/lossless_vec_1_2.webp b/tests/Images/Input/WebP/lossless_vec_1_2.webp new file mode 100644 index 000000000..8971121c0 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eed151dabacbad9b99aa5ad47787240f5344d8cd653f2c3842ccc0f95d6ce798 +size 76 diff --git a/tests/Images/Input/WebP/lossless_vec_1_3.webp b/tests/Images/Input/WebP/lossless_vec_1_3.webp new file mode 100644 index 000000000..5060ae091 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45a32abfcc449acff80249885078ffe56d244d85db7120fdb30f58ae2bf89ac9 +size 132 diff --git a/tests/Images/Input/WebP/lossless_vec_1_4.webp b/tests/Images/Input/WebP/lossless_vec_1_4.webp new file mode 100644 index 000000000..b346c4216 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6021a817ad3e17053de4b170b9ff2c7646ee2ef365b23a5e75bea3159d83023a +size 50 diff --git a/tests/Images/Input/WebP/lossless_vec_1_5.webp b/tests/Images/Input/WebP/lossless_vec_1_5.webp new file mode 100644 index 000000000..f2a2aa0d3 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_5.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:673161af330a911bd6a3bc3f0ab266a34eafba139a174372dd20727b8831e7e1 +size 106 diff --git a/tests/Images/Input/WebP/lossless_vec_1_6.webp b/tests/Images/Input/WebP/lossless_vec_1_6.webp new file mode 100644 index 000000000..248bcf6ba --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_6.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b90db591321b6235cfbf2c4a9083f29459185b84953c7ca02b47da16f82df149 +size 76 diff --git a/tests/Images/Input/WebP/lossless_vec_1_7.webp b/tests/Images/Input/WebP/lossless_vec_1_7.webp new file mode 100644 index 000000000..788e7a33a --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_7.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89aaf7749eefb289403ca6bdac93c0b80ac47da498f5064ea9f064994479045e +size 122 diff --git a/tests/Images/Input/WebP/lossless_vec_1_8.webp b/tests/Images/Input/WebP/lossless_vec_1_8.webp new file mode 100644 index 000000000..d55c10e10 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_8.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c255d6803fb2d9fa549322e9eb1ac5641ee091c32a24810310464d207bb73cc +size 56 diff --git a/tests/Images/Input/WebP/lossless_vec_1_9.webp b/tests/Images/Input/WebP/lossless_vec_1_9.webp new file mode 100644 index 000000000..07f0cdb54 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_1_9.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99e579d9b23ac41a1c6162b6c0585d7cd9a797058f7c89e5a5a245bd159cd1b0 +size 112 diff --git a/tests/Images/Input/WebP/lossless_vec_2_0.webp b/tests/Images/Input/WebP/lossless_vec_2_0.webp new file mode 100644 index 000000000..f338a8642 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_0.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b90cb047e529364197e0435122e96be3e105c7e4b21688a56be9532af9a08609 +size 12822 diff --git a/tests/Images/Input/WebP/lossless_vec_2_1.webp b/tests/Images/Input/WebP/lossless_vec_2_1.webp new file mode 100644 index 000000000..007695445 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ee2154490d6342aff5bbebae8a29aa00ba2aa4630b5c071fe7f45c327e1e56b +size 10672 diff --git a/tests/Images/Input/WebP/lossless_vec_2_10.webp b/tests/Images/Input/WebP/lossless_vec_2_10.webp new file mode 100644 index 000000000..7c5ee058c --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_10.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ef60485d13cd7c19d7974707d3c0b00bb8518f653669b992e1de9aea4fdd305 +size 20362 diff --git a/tests/Images/Input/WebP/lossless_vec_2_11.webp b/tests/Images/Input/WebP/lossless_vec_2_11.webp new file mode 100644 index 000000000..e029941fd --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_11.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:418e017cafcf4bbb09ed95c7ec7d3a08935b3e266b2de2a3b392eb7c0db7e408 +size 10980 diff --git a/tests/Images/Input/WebP/lossless_vec_2_12.webp b/tests/Images/Input/WebP/lossless_vec_2_12.webp new file mode 100644 index 000000000..59d05f33e --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_12.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0923abbeaf98db4d50d5a857ec4a61ca2cdf90cb9f7819e07e101c4fda574af0 +size 14280 diff --git a/tests/Images/Input/WebP/lossless_vec_2_13.webp b/tests/Images/Input/WebP/lossless_vec_2_13.webp new file mode 100644 index 000000000..5ba8186a4 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_13.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13abcefd4630e562f132040956aa71a66df244e18ea4454bcb58c390aba0e3a7 +size 9818 diff --git a/tests/Images/Input/WebP/lossless_vec_2_14.webp b/tests/Images/Input/WebP/lossless_vec_2_14.webp new file mode 100644 index 000000000..e2ec8c74c --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_14.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f73aea1e60ae1d702aefd5df65c64920c7a1f7c547ecee1189864c9ecd118c00 +size 20704 diff --git a/tests/Images/Input/WebP/lossless_vec_2_15.webp b/tests/Images/Input/WebP/lossless_vec_2_15.webp new file mode 100644 index 000000000..f3c130168 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_15.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b5cb1bf92525785231986c48f9d668b22166d2ff78b1a1f3fdcae6548c5e24b +size 11438 diff --git a/tests/Images/Input/WebP/lossless_vec_2_2.webp b/tests/Images/Input/WebP/lossless_vec_2_2.webp new file mode 100644 index 000000000..694201b29 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:880f63d6da0647bc1468551a5117b225f84f0e9c6df0cbb7e9cffbebcec159da +size 21444 diff --git a/tests/Images/Input/WebP/lossless_vec_2_3.webp b/tests/Images/Input/WebP/lossless_vec_2_3.webp new file mode 100644 index 000000000..8bb0a902e --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50bbb2e1c3e8fb8ee86beb034a0d0673b6238fa16f83479b38dec90aba4a9019 +size 11432 diff --git a/tests/Images/Input/WebP/lossless_vec_2_4.webp b/tests/Images/Input/WebP/lossless_vec_2_4.webp new file mode 100644 index 000000000..53eb696ff --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ec19bfa2bd9852cc735320dde4fa7047b14aca8281f3fbc1ad5fa3ad8215d6b +size 12491 diff --git a/tests/Images/Input/WebP/lossless_vec_2_5.webp b/tests/Images/Input/WebP/lossless_vec_2_5.webp new file mode 100644 index 000000000..e6f83941f --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_5.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09b72c5e236ef7c27a1cc80677e2c63f2b8effd004009de1c62fad88d4ad6559 +size 10294 diff --git a/tests/Images/Input/WebP/lossless_vec_2_6.webp b/tests/Images/Input/WebP/lossless_vec_2_6.webp new file mode 100644 index 000000000..bc17d4ee3 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_6.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6f63102a86ec168f1eeb4ad3bd80fc7c1db0d48b767736591108320b5bed9f8 +size 21922 diff --git a/tests/Images/Input/WebP/lossless_vec_2_7.webp b/tests/Images/Input/WebP/lossless_vec_2_7.webp new file mode 100644 index 000000000..81871bebc --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_7.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99353d740c62b60ddc594648f5885380aeb2ebe2acb0feb15a115539c6eebdc1 +size 11211 diff --git a/tests/Images/Input/WebP/lossless_vec_2_8.webp b/tests/Images/Input/WebP/lossless_vec_2_8.webp new file mode 100644 index 000000000..9656571eb --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_8.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:215ace49899cf63ade891f4ec802ecb9657001c51fbd1a8c2f0880bc4fb2760a +size 12640 diff --git a/tests/Images/Input/WebP/lossless_vec_2_9.webp b/tests/Images/Input/WebP/lossless_vec_2_9.webp new file mode 100644 index 000000000..831be6c32 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_2_9.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:033e7d1034513392a7b527176eeb7fab22568af5c2365dd1f65fdc3ad4c0f270 +size 10304 diff --git a/tests/Images/Input/WebP/lossless_vec_list.txt b/tests/Images/Input/WebP/lossless_vec_list.txt new file mode 100644 index 000000000..119169699 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_vec_list.txt @@ -0,0 +1,44 @@ +List of features used in each test vector. +All the 'lossless_vec_1_*.webp' WebP files should decode to an image comparable to 'grid.pam' or, +equivalently 'grid.png'. +This synthetic picture is made of 16x16 grid-alternating pixels with RGBA values equal to +blue B=(0,0,255,255) and half-transparent red R=(255,0,0,128), according to +the pattern: +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB +BRBRBRBRBRBRBRBR +RBRBRBRBRBRBRBRB + +The 'lossless_vec_2_*.webp' WebP files should decode to an image comparable +to 'peak.pam' or, equivalently 'peak.png'. Their alpha channel is fully +opaque. + +Feature list: +lossless_vec_?_0.webp: none +lossless_vec_?_1.webp: PALETTE +lossless_vec_?_2.webp: PREDICTION +lossless_vec_?_3.webp: PREDICTION PALETTE +lossless_vec_?_4.webp: SUBTRACT-GREEN +lossless_vec_?_5.webp: SUBTRACT-GREEN PALETTE +lossless_vec_?_6.webp: PREDICTION SUBTRACT-GREEN +lossless_vec_?_7.webp: PREDICTION SUBTRACT-GREEN PALETTE +lossless_vec_?_8.webp: CROSS-COLOR-TRANSFORM +lossless_vec_?_9.webp: CROSS-COLOR-TRANSFORM PALETTE +lossless_vec_?_10.webp: PREDICTION CROSS-COLOR-TRANSFORM +lossless_vec_?_11.webp: PREDICTION CROSS-COLOR-TRANSFORM PALETTE +lossless_vec_?_12.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN +lossless_vec_?_13.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE +lossless_vec_?_14_.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN +lossless_vec_?_15.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE diff --git a/tests/Images/Input/WebP/lossy_alpha1.webp b/tests/Images/Input/WebP/lossy_alpha1.webp new file mode 100644 index 000000000..9f1e3c2be --- /dev/null +++ b/tests/Images/Input/WebP/lossy_alpha1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:403dff2d4cffc78607bcd6088fade38ed4a0b26e83b2927b0b1f28c0a826ef1c +size 19478 diff --git a/tests/Images/Input/WebP/lossy_alpha2.webp b/tests/Images/Input/WebP/lossy_alpha2.webp new file mode 100644 index 000000000..a3cbe5c23 --- /dev/null +++ b/tests/Images/Input/WebP/lossy_alpha2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f2cd2585d5254903227bd86f367b400861cde62db9337fb74dd98d6123ce06c +size 13566 diff --git a/tests/Images/Input/WebP/lossy_alpha3.webp b/tests/Images/Input/WebP/lossy_alpha3.webp new file mode 100644 index 000000000..f87deec5a --- /dev/null +++ b/tests/Images/Input/WebP/lossy_alpha3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca1d20c440c56eb8b1507e2abafe3447a4f4e11f3d4976a0dc1e93df68881126 +size 9960 diff --git a/tests/Images/Input/WebP/lossy_alpha4.webp b/tests/Images/Input/WebP/lossy_alpha4.webp new file mode 100644 index 000000000..82193f4b8 --- /dev/null +++ b/tests/Images/Input/WebP/lossy_alpha4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2feb221aee944cb273b11cf02c268601d657f6a8def745e4a6b24031650cd701 +size 4262 diff --git a/tests/Images/Input/WebP/lossy_extreme_probabilities.webp b/tests/Images/Input/WebP/lossy_extreme_probabilities.webp new file mode 100644 index 000000000..94110f8fe --- /dev/null +++ b/tests/Images/Input/WebP/lossy_extreme_probabilities.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bad65a42ed076a8684494c8a11eb8be02da328195228aa635276f90b4523f27 +size 468740 diff --git a/tests/Images/Input/WebP/lossy_q0_f100.webp b/tests/Images/Input/WebP/lossy_q0_f100.webp new file mode 100644 index 000000000..c10e07c2c --- /dev/null +++ b/tests/Images/Input/WebP/lossy_q0_f100.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf480a1328f5f68b541f80e8af1bf82545f948874dd05aacd355adee2b7ca935 +size 270 diff --git a/tests/Images/Input/WebP/near_lossless_75.webp b/tests/Images/Input/WebP/near_lossless_75.webp new file mode 100644 index 000000000..86c426aa5 --- /dev/null +++ b/tests/Images/Input/WebP/near_lossless_75.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12e6b033cb2e636224bd787843bc528cfe42f33fd7c1f3814b1f77269b1ec2ab +size 45274 diff --git a/tests/Images/Input/WebP/peak.bmp b/tests/Images/Input/WebP/peak.bmp new file mode 100644 index 000000000..a03e57d38 --- /dev/null +++ b/tests/Images/Input/WebP/peak.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb45b4f5d7114ca7bf93e5025d91f3558c1925908fb368017ebc5d01a64a00b +size 49206 diff --git a/tests/Images/Input/WebP/peak.pam b/tests/Images/Input/WebP/peak.pam new file mode 100644 index 000000000..1a4f1dd13 --- /dev/null +++ b/tests/Images/Input/WebP/peak.pam @@ -0,0 +1,8 @@ +P7 +WIDTH 128 +HEIGHT 128 +DEPTH 4 +MAXVAL 255 +TUPLTYPE RGB_ALPHA +ENDHDR +ÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌâüÿÔâùÿÌâüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÌæùÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔâùÿÌâüÿÔèüÿÔâùÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔéôÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔéôÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔéôÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÔâùÿÌæùÿÌâüÿÔâùÿÌâüÿÌæùÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔéôÿÔèüÿÔèüÿÔèüÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌæùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÔâùÿÌæùÿÌâüÿÌæùÿØâäÿÌæùÿÌâüÿÌæùÿØâäÿÌæùÿÌâüÿÔéôÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÌæùÿÌæùÿÔèüÿÌæùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÌæùÿÔîüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌâüÿÔéôÿÌæùÿÔéôÿÔéôÿÌâüÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÌâüÿÔèüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÌæùÿÔéôÿÌæùÿÔèüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÌæùÿÔâùÿÌæùÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÔéôÿÌâüÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔâùÿÌÛìÿÌâüÿÌÝ÷ÿÔéôÿÔâùÿÔéôÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔðôÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÌæùÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔâùÿÌÝ÷ÿÄÖìÿ´ÆÙÿ„”§ÿŒ›©ÿŒ›©ÿ´·ÅÿœªÀÿ¬²ÄÿÌÖæÿÔÝîÿÔâùÿÔéôÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔîüÿÔîüÿÜêüÿÔîüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔâùÿÔèüÿÔâùÿÔâùÿÔéôÿÔâùÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÜêüÿÔâùÿÔÞüÿÔâùÿÌÝ÷ÿÌÝ÷ÿœªÀÿl~œÿdnŒÿly”ÿly”ÿtyŽÿ„‹žÿ|€œÿ„‹žÿ”•¦ÿ¤ª¿ÿ´·Åÿ¼ÅÖÿÌÛìÿÔâùÿÔèüÿÔéôÿÔèüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔéôÿÔâùÿÔéôÿÌæùÿÔâùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌâüÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÌæùÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌâüÿÌâüÿÔèüÿÔèüÿÔâùÿÔâùÿÔâùÿÜêüÿÔÞüÿ¬¸Ôÿly”ÿ\n„ÿly”ÿkrÿlvŠÿ„‹žÿ„‹žÿŒ“¨ÿ|€œÿŒŒ”ÿ|†ÿ¬²Äÿ¼½Ìÿ”›¨ÿ¼ÌÙÿØâäÿÔèüÿÔâùÿÔéôÿÔèüÿÔèüÿÔéôÿÔâùÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔîüÿÜêüÿÔèüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÜêüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÌâüÿÔéôÿÔâùÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌæùÿÌæùÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÌâüÿÌâüÿÔâùÿÔèüÿ´¾Üÿ|ެÿtœÿTb|ÿ\g€ÿ|‰¤ÿœ¤¼ÿkrÿ„¬ÿ|€œÿ„…–ÿœš­ÿ„…–ÿ„…–ÿ„…–ÿ”›¨ÿ´±¼ÿ¬¸Çÿ”›¨ÿ´ÀÉÿÄÒäÿÔâùÿÜéòÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÜéòÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔâùÿÔéôÿÔèüÿÔèüÿÔâùÿÔéôÿÌæùÿÔâùÿÔéôÿÌâüÿÔéôÿÌæùÿØâäÿÌæùÿÌâüÿÔéôÿÌâüÿÔâùÿÌâüÿÔâùÿÌæùÿÔèüÿÌâüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÌæùÿÔèüÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÌÝ÷ÿÜêüÿÔèüÿÄÖìÿ|‰¤ÿL\|ÿTg~ÿTb|ÿ\g€ÿdnŒÿlmŒÿ„¬ÿdnŒÿŒ¬ÿtyŽÿ|€œÿ|zœÿtrŽÿŒÿœš­ÿŒ†–ÿ”›¨ÿžž­ÿÄÌ×ÿ¼ÅÖÿœ£°ÿ¬¸ÇÿÔâùÿÜéòÿÜêüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔéôÿÌæùÿÔèüÿÌâüÿÌæùÿÌæùÿÔéôÿÔéôÿÌâüÿÔâùÿÌæùÿÌâüÿÌæùÿÔâùÿÌâüÿÌæùÿÔâùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÄÖìÿdv”ÿL\|ÿL[tÿL[tÿTVuÿ\g€ÿkrÿ|€œÿtyŽÿtrŽÿ„…¤ÿ„…¤ÿtrŽÿtrŽÿ|€œÿžž­ÿœš­ÿ|€œÿœ™¡ÿ¤ª±ÿ¼ºÄÿŒ“œÿŒ“¨ÿŒ“¨ÿ¬²ÄÿÔÝîÿÜâïÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÌæùÿÔâùÿÔéôÿÌæùÿÔâùÿÔèüÿÔéôÿÌâüÿÔéôÿÌæùÿÔèüÿÔéôÿÌæùÿÌâüÿÔéôÿÌâüÿÌâüÿÔâùÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÔèüÿÌâüÿ¼Îçÿ\n„ÿL[tÿL[tÿT]|ÿL[tÿdjÿtyŽÿŒ“¨ÿ„…¤ÿ|€œÿ„…¤ÿ¬ªÌÿ|€œÿ|€œÿtrŽÿ„Œÿœš­ÿžž­ÿ„…–ÿ¬¶¼ÿ¤¤®ÿ|†ÿ¬°¼ÿ|ÿŒÿ”›¨ÿ´¾ÜÿÔÝîÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔîüÿÜñüÿÔèüÿÔîüÿÜñüÿÔîüÿÜêüÿÔîüÿÜêüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔîüÿÜêüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔâùÿÔèüÿÔéôÿÔâùÿÔèüÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÔéôÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌÝ÷ÿ\n„ÿDVoÿL[tÿL[tÿDNiÿly”ÿžž­ÿ¤¦½ÿ”–´ÿtyŽÿtrŽÿ¤¦½ÿ¼ºÜÿ´±ÆÿŒ¬ÿlm~ÿ|zœÿtw„ÿžž­ÿ¬°¼ÿÌÍÕÿŒ“œÿŒÿžž­ÿœš­ÿ|†ÿŒ“¨ÿ¬°¼ÿÔÝîÿÔÝîÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔîüÿÔèüÿÔîüÿÔîüÿÔèüÿÔîüÿÔîüÿÜêüÿÔèüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔâùÿÔéôÿÔâùÿÔéôÿÔèüÿÌæùÿÔâùÿÔéôÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌæùÿÔèüÿÌâüÿÌâüÿÔèüÿÔèüÿÔèüÿ¼Îçÿl~œÿLUtÿLUtÿLUtÿDNiÿTVuÿkrÿÄÆÔÿ¤¤®ÿÜâïÿ¼ºÄÿ„…–ÿžž­ÿÌÎäÿÜÞìÿìêøÿ´·Åÿ¤¤®ÿtw„ÿ”•¦ÿ´±¼ÿ¼ºÄÿŒŒ”ÿ””šÿœž¡ÿ¬®³ÿ”›¨ÿ„†‰ÿ¤¤®ÿ¬°¼ÿÔÝîÿÜéòÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÜêüÿÜêüÿÜêüÿÜêüÿÜêüÿÜêüÿÜñüÿÔèüÿÜêüÿÔèüÿÜêüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÔèüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÔèüÿÌâüÿÌâüÿ¬ÂÔÿTg~ÿL\|ÿL\|ÿDNtÿLUtÿDNtÿdjÿŒ¬ÿ”•¦ÿ´±ÆÿÜÜÜÿÔÒÖÿ|€œÿ”•¦ÿ¼ºÜÿÌÆÏÿ¬°¼ÿœ™¡ÿŒÿ|zŽÿ´¶ºÿ¤¤®ÿžž­ÿ|ÿœ™¡ÿ¬®³ÿ¬ª´ÿ¼ºÄÿ¤°ºÿžž­ÿžž­ÿ¤¦½ÿÌÛìÿÔâùÿÜêüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔèüÿÜñüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌæùÿÌæùÿÔâùÿÌæùÿÔâùÿ¼ÎçÿTb|ÿDVoÿLUtÿT]|ÿLUtÿLSjÿLNiÿkrÿ”•¦ÿ„‹žÿœ£°ÿŒŒ”ÿŒÿlm~ÿ„Œÿ|zŽÿ”•¦ÿlm~ÿlfwÿtw„ÿ„‹žÿÌÍÕÿ„‹”ÿtw„ÿ”Žžÿ¤¤®ÿ¬ª´ÿœ™¡ÿ¬ª´ÿ´·Åÿ”•¦ÿ¤°ºÿ„‹”ÿ¤°ºÿÄÖìÿÔéôÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÜñüÿÜêüÿÔèüÿÜêüÿÜêüÿÔèüÿÜêüÿÜêüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÔîüÿÜêüÿÔîüÿÔîüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿ¤²ÇÿTg~ÿDVoÿLUtÿL\|ÿT]|ÿLUtÿLUtÿDNtÿdfxÿ|€œÿlm~ÿtw„ÿ|~ÿ|~ÿ\^pÿ|€œÿ¤¤®ÿlloÿdjÿdfxÿ\brÿtw„ÿŒÿ„Œÿ„‹žÿtw„ÿdjqÿ¤¤®ÿÄÃÄÿ””šÿŒŒ”ÿ|ÿ””šÿ„‹”ÿœž¡ÿÌÖæÿÜâïÿÜêüÿÜêüÿÜêüÿÔèüÿÔèüÿÜêüÿÔèüÿÜêüÿÔèüÿÜêüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜêüÿÔèüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÔîüÿÜñüÿÜêüÿÜñüÿÔîüÿÜêüÿÔðôÿÔèüÿÜòóÿÔîüÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔèüÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌÛìÿl~œÿL[tÿLUtÿL[tÿL\|ÿLUtÿL[tÿT]|ÿDNiÿT]|ÿtzœÿkrÿ\^pÿlvŠÿdjqÿ„…–ÿdjÿŒŒ”ÿ„‹”ÿtw„ÿ„‹žÿ|ÿlm~ÿdfxÿ\^pÿtw„ÿ„‹žÿdfxÿdalÿ„Œÿ¬²Äÿ¼½Ìÿ¬¶¼ÿÄÌ×ÿ´ÀÉÿ¤¤®ÿ´·Åÿ´·Åÿ¼ÌÙÿÜâïÿÔéôÿÜêüÿÔèüÿÜñüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÜêüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÜêüÿÔðôÿÜêüÿÔèüÿÜñüÿÔèüÿÔðôÿÜêüÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔðôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÄÖìÿ\n„ÿL[tÿL[tÿDNiÿT]|ÿL[tÿLUtÿLUtÿLUtÿLNiÿ\g€ÿkrÿT[rÿT[rÿ\Vkÿdjÿtw„ÿtyŽÿlm~ÿ\brÿ\^pÿlrÿ\brÿ\^pÿdjÿdjÿdfxÿlm~ÿdfxÿ|ÿ”•¦ÿ¤¤®ÿ¬ª´ÿ¤¤®ÿ¼¾Âÿ¬°¼ÿ¬¶¼ÿÄÅÌÿÌÔÙÿ¼ºÄÿ¼ÅÖÿÜâïÿÜêüÿÜêüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔðôÿÔèüÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔéôÿÔéôÿÔéôÿÔðôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿ´ÆÙÿTg~ÿDVoÿL[tÿDNiÿTg~ÿLUtÿLUtÿT]|ÿLNtÿLUtÿ\b}ÿ\g€ÿTVuÿLUtÿLSjÿLNiÿT]|ÿlm~ÿT]|ÿdfxÿT[rÿT[rÿT[rÿ\brÿT[rÿ\b}ÿlrÿdr|ÿT[rÿTUiÿtrÿ””šÿ¬ª´ÿ”•¦ÿŒŒ”ÿŒŒ”ÿ„‹”ÿ´¶ºÿ´¶ºÿÌÌÌÿÜÞäÿ¼ÂÌÿ¼ÂÌÿÔâùÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔîüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÔðôÿÜñüÿÔîüÿÜòóÿÔîüÿÔîüÿÜòóÿÔîüÿÜñüÿÜéòÿÔîüÿÜñüÿÔéôÿÜñüÿÔéôÿÔîüÿÜêüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔðôÿÌæùÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿ´ÆÙÿL[tÿL[tÿLUtÿDVoÿTb|ÿLUtÿLUtÿL\|ÿDNiÿLUtÿT]|ÿT]|ÿT]|ÿT]|ÿLUtÿTVuÿTVuÿ|ÿ\b}ÿTUiÿLUtÿTUiÿLUtÿ\VkÿkrÿTVuÿTUiÿkrÿtyŽÿdbuÿT[rÿ|ÿœš­ÿ”Žžÿ””šÿ|ÿ„‹”ÿ|~ÿ|z{ÿŒŒ”ÿÄÃÄÿÌÍÕÿ¤ª±ÿ¬¸ÇÿÄÔÚÿÔâùÿÜñüÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÜêüÿÔîüÿÜêüÿÔîüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔðôÿÔèüÿÔðôÿÔèüÿÔîüÿÔéôÿÔèüÿÔéôÿÔèüÿÔéôÿÔèüÿÔðôÿÌæùÿÔéôÿÔèüÿÔéôÿÔéôÿÔéôÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÌâüÿÔèüÿÔèüÿ¬¸Ôÿ\g€ÿL[tÿL[tÿL\|ÿT]|ÿL\|ÿL[tÿL\|ÿL[tÿDNtÿL\|ÿT]|ÿT]|ÿ\^|ÿT]|ÿTVuÿTVuÿ\^|ÿdnŒÿTVuÿLSjÿLNiÿDHaÿDHaÿDHaÿ„‹”ÿkrÿTVuÿ|€œÿ„…–ÿTVuÿdfxÿtw„ÿ¤ª±ÿ„‹”ÿ|ÿtrÿ„…–ÿŒÿ„Œÿ„…–ÿ¤ž¤ÿÄÆÔÿ´·Åÿ¤¤®ÿ¤¤®ÿÄÌ×ÿÜâïÿÜêüÿÜñüÿÜêüÿÔèüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜêüÿÜñüÿÔîüÿÜêüÿÔîüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜòóÿÜñüÿÔðôÿÜñüÿÔðôÿÔîüÿÔîüÿÜñüÿÔèüÿÜñüÿÔéôÿÔîüÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔéôÿÌæùÿÔðôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿ|ެÿL\|ÿL[tÿLUtÿLUtÿT]|ÿL[tÿL[tÿL[tÿDVoÿL[tÿDVoÿTb|ÿT]|ÿ\b}ÿ\b}ÿdnŒÿ\VkÿT]|ÿT]|ÿ\^pÿTVuÿTVuÿLUtÿLSjÿDHaÿLNiÿ|ÿ\b}ÿ\b}ÿ”›´ÿ„…–ÿT]|ÿTVuÿdr|ÿŒÿ¤¤®ÿ¬°¼ÿtw„ÿŒÿ„…–ÿœš­ÿŒ†–ÿtrÿœ™¡ÿ¼½Ìÿ„Šˆÿ¤¤®ÿ¼ÂÂÿÌÍÕÿÜéòÿÔèüÿÜêüÿÜñüÿÜñüÿÜñüÿÔîüÿÔîüÿÔîüÿÜñüÿÔèüÿÜêüÿÜñüÿÔîüÿÜêüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜòóÿÜñüÿÔîüÿÜñüÿÔîüÿÔéôÿÜñüÿÔðôÿÜñüÿÔéôÿÔîüÿÔðôÿÔèüÿÔîüÿÔéôÿÔîüÿÔèüÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔéôÿÌæùÿÔðôÿÔéôÿÔéôÿÌæùÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔâùÿÜêüÿt†¤ÿL[tÿDNiÿL[tÿLUtÿT[rÿT]|ÿL[tÿT]|ÿL[tÿDNiÿL[tÿL\|ÿTb|ÿ\b}ÿkrÿ\b}ÿtrŽÿ|€œÿ\b}ÿTVuÿ\b}ÿT[rÿDJlÿLIXÿLNtÿDHaÿDNPÿ\^|ÿ\^pÿ|ÿœ¤¼ÿtyŽÿT[rÿ\^pÿtw„ÿtw„ÿ„‹žÿŒÿ„ŒÿŒ†–ÿ¤¤®ÿ¬ª¼ÿ¼ºÄÿtw„ÿ|ÿžž­ÿ¤ª±ÿ¬®³ÿ¤ª±ÿ´ÀÉÿÜéòÿÜêüÿÜêüÿÜêüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜòóÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÔèüÿÔîüÿÜñüÿÔèüÿÔðôÿÜêüÿÔîüÿÔéôÿÜñüÿÔéôÿÔîüÿÔèüÿÔðôÿÔéôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÌæùÿÔðôÿÌæùÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔâùÿÔâùÿÔèüÿ|ެÿDVoÿL[tÿL[tÿLUtÿLUtÿT[rÿLUtÿT]|ÿT[rÿLUtÿLUtÿT]|ÿT]|ÿT]|ÿ\g€ÿT]|ÿdbuÿtzœÿ„…¤ÿ|zœÿT[rÿ\b}ÿ\^pÿTVuÿLUtÿLSjÿTUiÿLUtÿLSjÿTUiÿ|€œÿœ¤¼ÿlrÿ\b}ÿ\^|ÿtw„ÿTZ]ÿ\^pÿlm~ÿ„Œÿ”›¨ÿ´±Æÿ¼ºÄÿ¬ª´ÿdfxÿtw„ÿŒŒ”ÿŒÿ”›¨ÿœ™¡ÿœ£°ÿÄÌ×ÿÜêüÿÜêüÿÜêüÿÜêüÿÔîüÿÔðôÿÜñüÿÜêüÿÜñüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜòóÿÔîüÿÔðôÿÔîüÿÔéôÿÜñüÿÔîüÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔîüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔðôÿÌæùÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔéôÿÔèüÿÔèüÿÔâùÿÔèüÿÌæùÿÔèüÿÔèüÿÔâùÿÜêüÿÌÝ÷ÿÌÝ÷ÿly”ÿLUtÿLUtÿDVoÿDNiÿLUtÿLUtÿT]|ÿLUtÿLSjÿLUtÿLUtÿT]|ÿLZdÿT]|ÿT]|ÿdbuÿkrÿlmŒÿlmŒÿ|€œÿlmŒÿ\^|ÿLNiÿLSjÿTVuÿLUtÿ\^pÿLNiÿLSjÿTUiÿT]|ÿdfxÿT[rÿT[rÿlm~ÿT]|ÿ\brÿ\b}ÿdfxÿdfxÿdfxÿ¤ž¤ÿ¤¤®ÿžž­ÿŒÿlm~ÿdfxÿtw„ÿ|ÿŒŒ”ÿŒÿ¤¤®ÿ¤¤®ÿÌÛìÿÜéòÿÔéôÿÜñüÿÜñüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÔéôÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÜòóÿÔèüÿÔîüÿÔîüÿÔéôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÌâüÿÔèüÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔèüÿÔâùÿÌÝ÷ÿdv”ÿDNiÿL[tÿDNiÿDVoÿL[tÿT]|ÿT]|ÿLNiÿLSjÿLNtÿT]|ÿ\b}ÿtyŽÿ\^|ÿ\brÿ\b}ÿdnŒÿdfxÿdjÿdfxÿ\b}ÿTUiÿLSjÿT[rÿ\b}ÿ\^|ÿLSjÿTUiÿ\g€ÿTVuÿDNPÿLNiÿLNiÿLSjÿdjÿ|ÿlm~ÿT[rÿlm~ÿ„ŒÿŒÿ„Œÿ„…–ÿ¬ª´ÿžž­ÿ|zŽÿ„…–ÿ„Œÿ|ÿ|zŽÿ”Žžÿ””šÿ¤ª¿ÿ”›¨ÿ¼ÌÙÿÜâïÿÜêüÿÜéòÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÜêüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜòóÿÔîüÿÔîüÿÔîüÿÜñüÿÔîüÿÔðôÿÔîüÿÔèüÿÜòóÿÔîüÿÔîüÿÔèüÿÜòóÿÔîüÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔîüÿÔéôÿÔðôÿÔéôÿÔéôÿÔèüÿÔèüÿÔèüÿÔèüÿÌæùÿÔèüÿÔèüÿÔâùÿÔâùÿ|€œÿDVoÿL[tÿL\|ÿLUtÿL[tÿT]|ÿT]|ÿLUtÿDNiÿLUtÿLUtÿ\^|ÿlmŒÿ|€œÿkrÿTUiÿdfxÿ„…–ÿT]|ÿ\^pÿT[rÿLSjÿ\^pÿ\g€ÿdfxÿtÿtyŽÿLUtÿTVuÿdjÿDHaÿDNiÿLUtÿDNiÿ\^pÿtÿ|ˆœÿT[rÿTUiÿtyŽÿŒ¬ÿ”•¦ÿžž­ÿœ™¡ÿ¬®³ÿŒŒ”ÿtw„ÿ|€œÿ„…–ÿŒÿ|zŽÿ”•¦ÿœ™¡ÿ´±Æÿ”•¦ÿ¤°ºÿÌÖæÿÜéòÿÜêüÿÜñüÿÔéôÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÜñüÿÜêüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜêüÿÔîüÿÜñüÿÔðôÿÔèüÿÔîüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔâùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿ|ެÿDNiÿLUtÿLUtÿLUtÿL[tÿT]|ÿT]|ÿL[tÿDNtÿLNiÿDJlÿLUtÿdfxÿŒ¬ÿ¤ª¿ÿžž­ÿ\^pÿ|€œÿlm~ÿTUiÿTUiÿLSjÿDHaÿLSjÿ\g€ÿlvŠÿT]|ÿT[rÿ\g€ÿLSjÿDNiÿLUtÿLUtÿDNiÿLNiÿDNiÿtyŽÿT]|ÿLNiÿTNdÿtzœÿœš­ÿ„…–ÿ„…–ÿŒÿ¼ºÄÿ””šÿ„Œÿ„‹”ÿ„…–ÿ|y„ÿtrÿŒÿžž­ÿ´±¼ÿ¬°¼ÿ”›¨ÿ¬°¼ÿÌÜâÿÜéòÿÜéòÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔðôÿÜñüÿÜñüÿÔîüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÜñüÿÔîüÿÜòóÿÔîüÿÔîüÿÜòóÿÔîüÿÔéôÿÔîüÿÔèüÿÜñüÿÔèüÿÔðôÿÔèüÿÜòóÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔèüÿÌæùÿÔâùÿÔèüÿÔâùÿÔâùÿÔèüÿly”ÿLUtÿLUtÿLSjÿLUtÿT]|ÿ\b}ÿTb|ÿLUtÿLNiÿDNtÿDNiÿLSjÿTVuÿdjÿœ¤¼ÿ´·ÅÿŒ¬ÿdjÿlm~ÿ\b}ÿLSjÿLNiÿTUiÿLSjÿT[rÿdbuÿdfxÿDHaÿLUtÿLNiÿDNiÿLUtÿ\b}ÿDHaÿTVuÿLNiÿTVuÿdjÿLSjÿLSjÿT]|ÿ|€œÿ|zŽÿ\^pÿdbuÿdfxÿ”•¦ÿ”ŽžÿŒŒ”ÿŒÿœš­ÿ\^pÿdbuÿŒ†–ÿžž­ÿÄÆÔÿ¼ºÄÿ¤¤®ÿ¤¤®ÿ¤°ºÿÌÔÙÿÜòóÿÜòóÿÜñüÿÜêüÿÜêüÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÔîüÿÜñüÿÜòóÿÔîüÿÜñüÿÔîüÿÜòóÿÔîüÿÔðôÿÜñüÿÔîüÿÜòóÿÔèüÿÔðôÿÜêüÿÔðôÿÔèüÿÔðôÿÔéôÿÔîüÿÔéôÿÔðôÿÔéôÿÔéôÿÔâùÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿl~œÿTb|ÿLUtÿLUtÿLUtÿTVuÿT]|ÿT]|ÿTVuÿDNiÿDJlÿLUtÿLUtÿDNiÿ\brÿly”ÿœ¤¼ÿ¬¸Çÿ\fpÿ\b}ÿ\b}ÿT[rÿT[rÿLSjÿLSjÿ\^pÿdr|ÿ\^pÿdnŒÿLIXÿDNiÿLNtÿDNiÿT[rÿlm~ÿ\b}ÿ|€œÿ\^|ÿTVuÿdjÿTVuÿLSjÿdbuÿŒ¬ÿ\^|ÿ\^pÿ|zŽÿ|zŽÿœ™¡ÿŒÿ”Žžÿ”•¦ÿžž­ÿ\Vkÿ\Vkÿlm~ÿžž­ÿ´±¼ÿ¼ºÄÿ¼¾Âÿ¬ª´ÿ¤°ºÿ´·ÅÿÌÜâÿäòüÿÜéòÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜòóÿÔðôÿÔðôÿÜñüÿÔðôÿÜòóÿÔîüÿÜòóÿÔèüÿÔðôÿÔéôÿÔðôÿÔéôÿÔðôÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔèüÿÔâùÿÔèüÿÔâùÿÔâùÿ„¬ÿLUtÿT]|ÿT[rÿLUtÿLUtÿL\|ÿT]|ÿTVuÿDNtÿDNtÿTVuÿLNtÿDHaÿLNiÿLUtÿlvŠÿ´·ÅÿlvŠÿ\^pÿdfxÿ\^|ÿLSjÿTUiÿTUiÿLUtÿdfxÿ\brÿlm~ÿ\b}ÿDJlÿLUtÿLSjÿDNiÿLUtÿT[rÿtw„ÿ¤¦½ÿtw„ÿT]|ÿlm~ÿLNiÿTVuÿkrÿ„…–ÿLNiÿ\^pÿtw„ÿŒ†–ÿ¤¤®ÿŒÿœš­ÿŒ†–ÿtrÿtrÿ\^pÿ\^pÿ„‹žÿ¬°¼ÿ´±¼ÿ´¶ºÿľÌÿœ¢¡ÿ¬¸ÇÿÄÌ×ÿÜéòÿÜéòÿÜñüÿÜêüÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÔðôÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜ÷üÿÜòóÿÜ÷üÿÔðôÿÜñüÿÜñüÿÜòóÿÔðôÿÜñüÿÜòóÿÔèüÿÜòóÿÔèüÿÔîüÿÜòóÿÔéôÿÔîüÿÔðôÿÜòóÿÔéôÿÔðôÿÜéòÿÔîüÿÔðôÿÔéôÿÔðôÿÔéôÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔéôÿÔâùÿÜêüÿŒš´ÿT[rÿT[rÿLUtÿT]|ÿLUtÿLUtÿT]|ÿLUtÿLUtÿDNiÿDNtÿTVuÿT]|ÿLSjÿLUtÿDNiÿT[rÿ|€œÿ\b}ÿT]|ÿT[rÿLNiÿ\ÿ\ÿ\ÿ\ÿ\ÿDB\ÿ\ÿ\ÿDJlÿDHaÿ\ÿDB\ÿ\ÿ,2<ÿ45<ÿ,.<ÿ434ÿ,,3ÿ434ÿDGLÿTSLÿLIXÿ45<ÿ,.<ÿ45<ÿIIERRRPRTWRPT_WPRW[_dWLPLRWWWb_WWY_Y[_WLR_bd_W__RY_R_WSWLW___W__kˆ¸ªr€ƒ˜°¿³‘»Ç–†}{{€„–™¥£z|“·ÖÖàÝàÝÝÝÚÝ>BPEIEEIRPTRLLRILLIIWL>BBIBPLPPLWRIW_WRPRW_bWLLILPWR__WWY_WWWRPW_idg[WW_RWYTWPLIP_Wd[W__d˜|kkk€ˆªº¢y£~„{u}€y„•ž£yuz“³ÝÝÝÝÚàÝÝÝEBILPPI>RPLLILRPILLIWPB@B::BEILWWR_WBIRPLLRPEILEIILEBITW_IEBLWLRR_WRWPIBMYPRTYWSLPPSWWPWTS_`^U^[``bbko~e`joŠzrro‡ÃÙÖÙàWTLEE@BEI_dREPCEMLWMLRPT_W_YWYU_\RMLRY`YYY_YY\``_``W\`ddbg`_vLRWLW:BPT_PSP@BJMPLLESWWYRWRSYRSWTRPUYYSSYWQYY_Y[YYP`YUSU[U`\W[RIRYPBEE63@IELLEILPWWPEW_PPTWWPLWLPWWEBCEMMMELPYQWRSMWYTWU\WU_YYPSU`MMYSY_Y`SUUYSMS\\Y_RLILLB:B@33<<@@ELBBEI@BIWY_RRLRWW__PRWLBLPEBBLRPLPLRWLIBT_WLPPRLWRRPRSLIMRYLYWHB>BEJBEEIISPMPJLJLRUYYY[UYSLMISPSMPSYMW[TMU\QUYYYRLPEIWP<::6:6BEEPB<@<<BEP>EPWWBLWW_Y_LILLIEILLE@RLRE@<>ILLLLLLRSMIMWM@LILEUYPHCEPYWRIMEPLRYRPRY\SPSUPMIMPURRYYTMMRRLMSUPTWLBBE<66EIB66MY[WLMIPMMMLJMRWPMLPSMMLLRLLRYTRQURMMQRWWWLBIIC:6EL@IBBE6>IEELPMLIMELCMEBBEEEURE>@IMR_PEBESWYTRLJMPLMIELMLRSIMRPLMRRMRR\RMMPQILLMW_[WBEIEJ<@@3>::666@RWLIELPSYYRILWLLIBEBBLELLIELE<:BCLELEE@EB>B>@>>@BBEPUP>@IML`RE:>LY[dRMEEEJEEBEPMIR@JRMEPURLLMWMLIMLEEMQRWWW@BEEC6B>@LE<>3:3663:<@EEPLLIEB<>>BB>CE@BLSM<>EIL_WIBMRY[_WSEECEEB@JPSMUIMPMPUWULPLRTMMMMEELMWYWL:3<:336>TWI<6:><@>>>>>>PMI>>@ELYULEPQTW_[SIC@JC>CEHIISMLMPSLPRRMLMPLLIEEEIMRWTLBB>616><@EE:61311136@:BEIB@B>B:><>><<<<@PSM>C@<@EIEIMIBEQPLRSWTILPMIME@EMLWRRWRIE<63>><61111<>:EIIEBLLRWLWTPRLI@EPRRRPIELTTL>>6<<@BBB@@<:<::@>::::BMQE><@JEMYMLIRLPYYRMLB<>>>C>HLPHEELMMIMUMPMMEEEE@EE@TP_RLE>:66E<6><3111136>BEE@@EELIELPW@EEBBILTWLLLLIPRRE<6>>E@88:>@>88<8>>JEJWMIEQPLWYWLRH><8>:EILMIBELLEELWRMEPMECBEEEEWLWWIL<<6:E<6::1111316BEEB<<>86:>>>:<86>EUJ<<@MCISMEPPLMYYRP\^@>>>>>HIEB@CIJEIMTRPIMEC>>JMJMRWWRPI<61:<>::888F:>8<68>EBHWPCEEIPYWWL_bI<8>C>IJME>C@IEEMPPMMMPE>:6@EPPIE<66@6@BB>:68>6688868>SSSE8>C@IMJBICBEW_PP_YSE<86CEEE>>EJEEBIMMPPMML>I:331111133BEITRB@BEEEE@66>>>6B><:83888686861@RPYQ<>C>MMEEMB>IRRPRWYYSB<6>C>>><>ECB>EMPMMILIB@EMMEBEW[RI<16ES>6633313>1PMLI>:B@@BTRB>@IEEB>:8><<6@>@J688383836:6@JMMB@:<6>E@>><>>CEECEEEMMPME@BEILIEBPWPLB16MW<<>:8113C3@EY@6:66@C><6<6::>88811836636BMPUUJ:6>EE>BEE>BLWYTRYRP::6CJ>:>>>>>@>@CILMPL>>BELLMŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹ŠŒŠŒŒ‹ŒŒ‹ŒttttttttttttuuuuuuuvvvwwuuuuwwwwvvvvvvvvvvvvvvvvvvvvvvuuuuuvuuvuŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹Œ‹‹‹Œ‹‹‹Œ‹‹ŠŒŒ‰Œ‰ŒuuuuuuuuututuuuuuuuvuutuuuuvwwwvvvvvvvvvvvvvvvvvuvvvvvuuuuvuvuvuŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹ŒŒ‹‹‹‹ŒŠ‹Œ‹Š‹Š‹ŒutttttttuuuuvvuvuuuuvuuuuuuuvwwwvvvvvvvvvvvvvwvvvuuuuvvuuuuuuutvŽŽŽŒŽŒŒŒ‹‹ŒŒŒŒŒŒŒŒ‹Œ‹‹Œ‹‹Œ‹Œ‹‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹Œ‹uttttuutvuutvvuvuvwvvwvvvvuuwwwwvvvvvvvvvvvvvvvvvvvuvvvuuuuuuuuuŽŽŽŽŒŒŽ‰Š‰‰‹‹ŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŒŒ‹‹‹‹‹‹‹‹‹Œtttuuuvuuvuvuwwwvxxxz||{xvwuwvvvvvvvvvvvvuvvvvuvvvuvvvvvwwxwuuuuŽŽŽŒŽŒ‰ˆ‡‡†ŒŠ‹ŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŒŠ‹‹‹ŠŒ‹‹‹‹‹Œ‹Š‰Œttuuuuuvuuuuuuxxyx{{}~}}{xxwvwvvwvwvvvvvuuuuvuvvvwvvwvvvvwwwuuvuŒŒŽŽŠ‰††‰‰ŠŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠ‹‹‹Š‹Œ‹Œ‹‹ŒŠŒŠ‹vuuuuvvuvuvvuwwwz||}~€~}{zywvvvvwvvvvvvvvvvvvvuwvwvvvvvvwvwutuuŽŒŽ‰ŒŒŒˆ‡„‡‡Š‰ŒŒŒŒ‹‹‹‹‹‹‹‹‹Š‹‹‹ŠŠŠ‹‹‹Š‹ŒŠŒŠ‹‹‹‹‹‹‹uvuvvuuuuuuuvvxy}}~~~~~~|{vvvvwvvvvvvvvuvvuvuvvwvvvvvvwwuwuuuu‡ƒ‰‰…‡…†ƒ……ˆŠ‹ŒŒ‹Š‹Š‹ŒŠ‹ŠŠŠŠŠŠŠŠ‰Š‹‹ŠŠ‰ŠŠŠŠ‰‹‹‹Šuuuuuuvvuuuvvyz}~€~}xwvvwxxwwwvwvvvvvvuuvwvvvvuvvvvvuuuuŒŽ‹ˆ„‰†ˆˆ…‡„ƒ†…‡‰‹ŒŠ‹Š‹Œ‹Š‹‹ŠŠŠŠ‰‰ŠŠŠ‰Š‰‰Š‰‰‰‰‰Š‰ŠŠuuuuuuuuuvwwxz{}}~€}€~}{xxvwwwxvvvvvvuuuuuuwvvvuuvuvuvvuvuuŒŽŽŠ‡ˆ‡ˆ‰ˆ‰††„……‡‰‹Œ‹‹Š‹‹ŠŠ‹ŠŠŠ‰‰Š‰ŠŠŠŠ‰Š‰Š‰‰Šˆ‰ŠˆŠuuuuuuuuuwxzyz{{}~~}}~~~€}}~|ywvvvvwwwvvvvuuuuuvwvvuvuvuuvvvtvuŒŽŽŽ‹ŒŒ‹Œ‰Š†……ƒ‚„ˆ‹‹‹‹‹‹‹ŠŠŠŠ‹ŠˆˆŠ‰‰Š‰Š‰‰‰ŠŠŠŠŠ‰Š‰Šuuuuuuuuwxxyy{z{}|||}}|}€€~€~zwwvvvvvwwvuvvuuuuwvvvvuuvvvuuvtvuŒŒŒŒŒŽŽŽŒ‰Š††‡‡††ƒ„‰Š‰ŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆ‰‰Š‰Š‰ŠŠˆŠ‰Šwvvvwwvwyyxwxy{|||||}|}|}~}xwvuvvvwuuuvuuvvvvutvuutuuvuvuuuŒŒŒŒŒŽŽŽŽŽ‹Œ‹ŠŠŒ…ˆ††††……ˆŠŠ‰ŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆ‰ˆŠ‰Š‰‰Š‰Š‰ˆŠ‰vvuvwwwyz{zzyz|}}|}}|~|}~~€€~}zyvvwvwvuuvuvvuuvvuutvvuvtvvtvuuŒŒŒŽŽŽ‹‹ŒŒŒ‹‰Œ‹ŠŠ‡†…ˆ‡††‡ˆˆŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‰ˆˆŠ‰ˆ‹‰Š‰‰‰‰‰‰vwvvxxzxz{{{{|}}}||}}}|}}€€{ywvvwvvvvuvvvvvvvvvuuuvuvuuuuuvŒŒŒŽŽŽ‰‰‹‹‹‹ŒŒŽŽ‹‹Š†ƒˆ‡ˆ†‡†ˆˆ‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‰ˆ‰Š‰‹Š‰‰‰ŠŠ‰wvvwyyyzy{|}~~}|{{{{{{|{~€~€}{yvvvvvuuuuvvvvvvvuuuvuvvuuuvvvŒŒŒŽŽŽŒŠ‹‹ŒŠ‰Š‹ŒŒŠˆ††ˆˆ†„……†Š‰ˆˆˆˆˆˆˆˆˆ‡ˆˆˆ‡ˆˆ‡‡‡‡‰ˆˆˆ‡ˆvwxwz{{z{z{{|||}|}|z}}||}~€€€€€}zxxwvvvvvvvvvuvvvvuvutuuvvuuuvŠŒŽŽ‘‹‹ŠŒ‰ŒŽŽŠ‰Œ‹‹‡‡†‡ˆ‡ƒƒ††‰ˆˆˆ‡‡ˆˆ‡‡‡ˆ‡‡‡ˆ‡ˆ‡‡ˆ†ˆˆ‡‡ˆˆwxz{{y{y||z|}|~|}||z}~|}}~€€‚€~€€|{wwvvuuvvuuuvuvvvvvuuuuuwuvvv‡ŠŒŽŒ‹Œ‹‰Š†‹ŒŠ‹‰ŠŠ‰‡…‡ˆˆ†ƒ……ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡‡‡‡‡ˆˆ‡‡‡ˆˆ‡ˆ‡‡ˆzz{{|z{{{|{{|}}}||{{~~~~~~€€€€~{zwvvuuuuuuuuvuvvvvvvuvvuvuuv‡†ˆŠ‹ŽŽŠŽŒ‹Š‰ŠŒŒ‰‡ˆ†Š‰†‡‡ˆˆ†ƒ„„‡‰‡ˆˆˆˆˆˆ‡‡‡‡ˆ‡‡ˆˆˆ‡‡‡ˆ‡ˆˆˆ‡ˆ{}~}|{{z|{{z}{}|}}{{€€~~€€~yxvvvvvvuuuuvuvvvvuvuvuvvuvvƒ„„„…ˆ‰ŒŽŽŒŒ‰ŠŒŽŒˆ‡ˆ‡†…‡‡†‡„ƒ„„†‡ˆˆ‡‡‡††††††…††‡ˆˆˆˆ‡‡†ˆ‡‡‡~~~~}}||{{||{|{{{~~~~}€€€€|yxvuvvvuuuuutuuvvvvvvuustuu‚„‚‚‡‹ŒŒŒŽŒŠŠ‹ŒŠ‰‰‰ˆ†‡…‡††………‚‡‡‡‡‡‡‡††††††††…‡‡ˆ‡‡ˆ‡ˆˆ‡‡‡€~€€~}~~~{{|{{z{{|}~~~~~€€€€€~|xvvvwvuuuuuuuuvvvvvvuvttuu‚‚‚ЉŒŒŽŒŽŠ‹ŒŒŠ‡†‰†‡†‡…†…„„ƒ…„††‡‡‡†††††††……†‡ˆ‡ˆˆ‡‡‡‡‡ˆ‡~~€}}}|{|z{{{{{|~€~~~~€€€€~yuwvvvuuuuuuttuvvvvvuuuutt‚‚€‚‚‚„†‰ŒŒŒŒŒŒ‹‰‰‡ˆ‡††…‡…†„…„ƒƒ„††‡††††††‡††††‡‡†ˆ‡ˆ‡ˆ‡‡ˆ‡€€€~~}|||{|{{|{|}|}~~~~€€€€€€€~zwvvuuvuuuuvuuvuuvuvvvttttƒ‚ƒ†…††‹Ž‹†…†‡‡‡„ƒ„„†…‚ƒ‚„‚†„†‡……†…„„…………†„†…††‡†‡†††€€€~~{{z{{{{{{|~€€€€€€€|zvuvvvwwwuuuuutuuvvvvtuttƒ‚ƒ‚…†††…ŠŽŽŒ‡‡‡‡‡…„ƒ„ƒ†„„‚ƒ‚‚ƒ…‡……†…†……………………†…†‡††……††€€€€€~}|z{z{zzz{|~}€€€€€€€}wwvuvvvvuuuuvtvuuvuvtust‚ƒƒ„„„†„†‰ŒŽŒˆ†‡‡††…ƒ………„ƒ‚‚‚ƒ„„††……„……„………†……„………†………††€€€€~}}z{zzzzz{|~€€€€€€€€€€zwwvwvuwuvuuuuttuuvuutstƒƒ‚‚ƒ„†……„††ŽŽŽŽŠŠ…†‡†ˆ…‚ƒƒ……‚‚‚„„†‡………………………………††„„……„…††€€€€~~}{z{{{{{{|}€€~€€€€€~{xuvvvuuuuvuuvuvvuvtuts‚‚ƒ‚‚…‰‡‡…‰‹Œ‰ˆ†‡†…„„„ƒƒƒ‚‚‚ƒ‚…††„„…„ƒ„ƒ„„…„„„„„„„………†€€€€}€~}}||{{{|||~~€€€€€€}zwvvwtuvuvuvuuutuuuvut‚‚‚‚…ˆˆ†…†‰ŒŽŽŽŒ‰……„……„‚ƒƒƒ‚‚ƒ‚„ƒ…†…„…„ƒ„„ƒƒ…„„„„„„„………†€€€€~~~€}}{{|{{{}}€€€€€€€{yuvuuuvuvuuuuttuuvuut‚‚‚‚„†ˆ†…‡‡‰ŽŽŽŽŒŽŒŒ‰…†…††ƒ‚„„‚‚‚‚‚‚‚€…„ˆ„…ƒƒ„ƒƒ‚ƒ„…„„„„„ƒ……†…€~~~~}{{{|{||}}€€€€€€€€€~}|vtuuuuvvuvtuttttvuuu‚‚€‚‚…ˆˆ‰‡†„Š‹ŽŽŠ‡…‡……ƒƒ‚„‚ƒ‚ƒ‚ƒƒ‚„„…‡…ƒƒƒƒƒƒ„…„„„„„„ƒ………†€€€€~~€|}|{{{|{}}~€€€€€€€€€€€}xutvuvvuvvutttttuuuu€„†Šˆ†„„‡ˆŒŒŒŒ‹„„…‡†ƒ„ƒ…‚‚‚ƒƒ‚‚ƒƒ……„ƒƒƒ‚‚‚ƒ„ƒƒƒƒƒ„…………€€€€€~€€€~zz||}}|~€€€€€€€€€€{wustvuvvuuutttuvvuu€€‚…‡†ˆ…ƒƒƒ‰‹ŒŽŒŒŒŒˆ…†„…‡ƒ„‚‚ƒ‚‚‚ƒ………ƒƒ‚‚‚‚ƒ‚ƒƒƒƒƒƒ„„……€€€€€€€€€€€€|{}}|}|~€€€€€€€€€€€}zwsuvvvutuuttuuuuuu€ƒƒ„‚‚ƒ„†‹‹Ž‹ŒŒ‰‡†„„†…„ƒ‚‚‚ƒ„…„ƒ‚‚‚‚‚ƒƒƒƒƒƒƒƒ„„……€€€€€€€€€~}|}}|||€€€€€€€€€€€€€€}|wuuuuvuvuuuuuuuuuu~~~~€€‚ƒƒ‰‹Š‹Œ‡†…‡…ƒ„…†ƒ‚€‚ƒ„„„„‚‚‚‚‚ƒƒƒ‚ƒ„„„„„……€€€€€€€€}||}}~~€€€€€€€€~{xvuuvuuvvvuuuvvvuv~~~~€~€€€€€‚ˆŠ‡ŠŒŠ†‡…„„…†…‚‚€‚‚ƒƒ…„‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ„„„„€€€€€€€€€~~}}}€€€€€€€€€€~ywuvvuwwwwwwvvvvvv€~~~~~€~~~€‚ƒ‚„‰Š…†„„††ƒ„‚‚‚‚‚ƒ„…‚„‚‚‚‚‚‚‚‚ƒ‚ƒƒƒ„„„€€€€€€€€€€€€~}~€€€€€€€€€€€€€~ywuuvvwwwwwwxwxvvv€~~~~~~~~€€€ƒ„‡…‚‚„„……‚‚‚‚€‚„ƒ„‚‚‚‚‚‚‚‚‚„ƒƒƒ„„€€€€€€€€€€~€€€€€€€~€€€€€€€€~{vvuvuwwwwwwwxvwwv€~~~~~~€~€€€€‚‚‚€ƒ„……€‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚‚‚ƒ„ƒ„„„€€€€€€€€€€~~€€€€€€€€€€€€€€€~zxvvvvwwwwwwwwwwvw~~~~~~~~~~~€‚ƒ……ƒ‚‚€ƒ‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}|xwwwwwwxyxyyxyxw~~~~~~~~~~~~~~~€€€€„„‚ƒ‚€‚‚ƒ‚‚‚‚‚‚ƒƒƒƒ„ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~yxyyxwxywxyxxywx~~~~~~~~~~~~~~~~~~€~‚ƒ‚‚€‚€€€€‚‚‚„…‰ŒŠˆ„„ƒ„ƒƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}{|{{xxyxywxxyxyy~~~~~~~~~~~~~~~~~~~~~~~~€€‚‚‚‚‚€‚ƒ‚…ŠŽŒ‰‹ˆ…„ƒƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}yyxyyxyxyyxy~}~~~~~}~~}~~}~~~~~~~€~€‚€‚†ˆ‹‹ŠŠ‹Š…„ƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}}|}|zyvwzyyy~€~~~~~€~~~}~~~~~~~~~~~~~€€€‚„‡‰ŽŽˆ‰‹‰ƒ‚ƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}}~}zyzwyzyy€€€€€‚€€~~~~~~~}~~~~‚‚~~~€ƒˆ‹Ž‹Š†Š‰ˆ…ƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~}}}|{yzzyzy€€€~€€~~~~‚‚ƒƒ€€‚„‡ˆŠ‰‹ŠŠŠ‰…ƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}~}}|{{yyyyy€€€€€€~€€€€~~€~~~~~€~‚„‚ƒ€€‚‚…‡‹Šˆ‹Žˆ„ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€~~}{{{zx€€€~~€€‚~€€~~ƒ‚ƒ€€€‚€‚€‚„…†‹ˆˆ‹‡„ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€|}|}|{yy~€€€~~€~‚€~€€€€€€€€‚€€€‚€~‚€ƒ‚‚„…ƒ††„„…„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~||{{~€€~€~€€‚€€~€€€€€‚€€€‚€ƒ€€ƒƒ‚€€‚€„ƒƒ…„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€~~}|€~~€~€€€€~~€€€‚‚‚‚€€‚ƒ€€€€‚ƒ€‚‚‚€‚‡ƒ€‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}}€€€€~~~~€‚€€~~‚€~€~€€„‚‚€€€€€ƒ€‚ƒ€‚„„ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~€ƒ€€~~~€€€€~~€€‚‚€ƒ€€€€‚‚ƒ€‚‚‚ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~€€‚‚€~€~€€€€~€€ƒ‚‚‚€‚€‚‚‚‚ƒƒ‚ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}€~€‚€~€~€€€€‚€€€€ƒ‚ƒ€€€€€ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~~€‚ƒ€‚€€€€‚€€€‚ƒ‚€‚ƒ€ƒ‚‚‚‚€€€‚‚€€€€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~~}„‚ƒ„„ƒ€€€‚€€€€€ƒ‚€€‚‚ƒ‚‚€ƒ‚‚‚‚‚‚€€€€‚‚‚€€€€€€~~€€€€€€€€€€€€€€€€€€€€€€€€~~~€‚„‚‚„„…ƒ€€€~€€€ƒ‚€‚‚‚ƒ‚‚‚€‚‚ƒ„ƒƒ€‚‚€€€‚‚€€~~€€€€€€€~€€€€€€€€€€€€€€€€~~~€‚ƒ‚‚‚……ƒ‚~€€€ƒ‚‚„…‚…„ƒ‚ƒ‚ƒ‚€‚‚„ƒ‚ƒ€ƒ‚‚‚€€€€€~€€€€€€€€€€€€€~~~€€€€€€€€€€€€€€~~‚‚‚„…„…€€€€€ƒ‚ƒ……‡„ƒƒ„ƒ‚ƒ‚€€‚„„ƒ‚„‚€‚ƒ‚‚€€€~€€€€€€€€€€~~~~€€€€€€€€€€€€€€€€€€€€}‚ƒ‚ƒ……„ƒ€‚‚‚‚€‚‚€‚€ƒƒƒ‚‚„†„†…„ƒ„„‚‚‚‚‚„ƒƒ‚ƒ‚‚€‚‚‚€€€€€~~€€€€€€€€€€€€€€€€€~€€€€€€€€€€€€€€€€€~~‚ƒƒƒ„ƒ‚‚ƒ‚ƒ‚€‚ƒ‚ƒ‚‚ƒ†††„ƒ‚ƒ‚„‚‚‚~€ƒƒƒ‚ƒƒ‚‚‚‚€€€€€€€€€€€€€€€€€€€€~~~€€~€€€€€€€€~€€€€€ \ No newline at end of file diff --git a/tests/Images/Input/WebP/peak.png b/tests/Images/Input/WebP/peak.png new file mode 100644 index 000000000..5a417b9c0 --- /dev/null +++ b/tests/Images/Input/WebP/peak.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9b56ed5c1278664222c77f9a452b824b4f9215c819502b3f6b0e0d44270e7e7 +size 26456 diff --git a/tests/Images/Input/WebP/peak.ppm b/tests/Images/Input/WebP/peak.ppm new file mode 100644 index 000000000..c1ddfca70 --- /dev/null +++ b/tests/Images/Input/WebP/peak.ppm @@ -0,0 +1,4 @@ +P6 +128 128 +255 +ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÌæùÔâùÌâüÔâùÌâüÔâùÔèüÔâùÔèüÔâùÌæùÔèüÌæùÔèüÌâüÔèüÌæùÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÌæùÔèüÌæùÔéôÌæùÔèüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌâüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔâùÌâüÔâùÌæùÔâùÌæùÔâùÌâüÔèüÌæùÔâùÌæùÔâùÌâüÔâùÌâüÔèüÔâùÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔéôÔèüÌæùÔéôÌâüÔéôÌæùÔéôÌâüÔéôÌâüÌæùÔâùÌæùÌâüÔéôÌâüÌæùÔâùÌæùÌâüÔéôÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÔâùÌæùÌâüÔâùÌâüÌæùÔèüÌæùÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔéôÔèüÔèüÔèüÌæùÔèüÌæùÔéôÌâüÔéôÌæùÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌâüÔèüÌâüÌæùÔâùÌæùÌæùÌæùÔâùÌæùÌâüÔèüÌæùÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÔèüÔâùÌæùÔâùÔèüÌâüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÔéôÔèüÔèüÔèüÔéôÔèüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌæùÔâùÌæùÌâüÌæùØâäÌæùÌâüÌæùØâäÌæùÌâüÔéôÌâüÌÝ÷ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÔâùÌâüÔâùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌæùÔèüÌâüÌæùÌæùÔèüÌæùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÌæùÔîüÔèüÔéôÔèüÔèüÔéôÌæùÔèüÌâüÔéôÌæùÔéôÔéôÌâüÌæùÔéôÌæùÔéôÌâüÌæùÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÌæùÌâüÌæùÌâüÔâùÌæùÔâùÌæùÌâüÔèüÌâüÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèüÔèüÔèüÌâüÔèüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÌæùÔéôÌæùÔèüÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔéôÌâüÔéôÌæùÌæùÔâùÌæùÌâüÌÝ÷ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÔâùÌâüÔâùÌâüÌâüÌæùÔâùÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÌâüÔâùÌæùÔâùÌæùÔèüÌæùÔèüÔâùÔèüÔéôÌâüÔâùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÔèüÜñüÔèüÜêüÔèüÔîüÔèüÔéôÔèüÔèüÔéôÔèüÔîüÔèüÔèüÔèüÔéôÔèüÔèüÔèüÔéôÌâüÔéôÌæùÔéôÌâüÔéôÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÌæùÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÌæùÌâüÌæùÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔâùÌâüÔâùÌâüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔâùÌÛìÌâüÌÝ÷ÔéôÔâùÔéôÔèüÔâùÔèüÔâùÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÔðôÔèüÔèüÔéôÌæùÔèüÌæùÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔéôÌâüÔéôÌæùÌâüÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÔâùÌæùÔâùÌæùÔèüÌâüÔèüÔâùÔèüÌâüÔèüÔâùÔèüÌæùÔâùÌâüÌâüÔâùÌâüÔèüÌâüÔèüÔâùÔâùÌÝ÷ÄÖì´ÆÙ„”§Œ›©Œ›©´·ÅœªÀ¬²ÄÌÖæÔÝîÔâùÔéôÔâùÔèüÔèüÔâùÔèüÔèüÌæùÔèüÔâùÔèüÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÜêüÔîüÜêüÔèüÜñüÔèüÔîüÜêüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔîüÔîüÜêüÔîüÔîüÜêüÔèüÜñüÔèüÔîüÜêüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÔèüÔèüÔéôÔâùÔèüÔâùÔâùÔéôÔâùÌæùÔéôÌâüÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÔâùÌâüÌâüÔèüÌâüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÌâüÔèüÔâùÔèüÔâùÜêüÔâùÔÞüÔâùÌÝ÷ÌÝ÷œªÀl~œdnŒly”ly”tyŽ„‹ž|€œ„‹ž”•¦¤ª¿´·Å¼ÅÖÌÛìÔâùÔèüÔéôÔèüÔèüÌâüÔèüÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÜêüÔîüÔèüÔîüÔèüÜêüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÜñüÔèüÔîüÔèüÜêüÔîüÔèüÔèüÔèüÔèüÔéôÔèüÔèüÔèüÔèüÔâùÔèüÔéôÔâùÔéôÌæùÔâùÔéôÌâüÔéôÌâüÔéôÌâüÌâüÔéôÌâüÌâüÌâüÌâüÌâüÌæùÌâüÌæùÔâùÌæùÌâüÌæùÔâùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÌâüÌâüÔèüÔèüÔâùÔâùÔâùÜêüÔÞü¬¸Ôly”\n„ly”krlvŠ„‹ž„‹žŒ“¨|€œŒŒ”|†¬²Ä¼½Ì”›¨¼ÌÙØâäÔèüÔâùÔéôÔèüÔèüÔéôÔâùÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔîüÜêüÔèüÔîüÜêüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÜêüÔîüÜêüÔèüÜêüÔèüÔîüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔéôÔèüÔéôÌâüÔéôÔâùÌæùÔâùÔèüÔâùÌæùÔéôÌæùÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÔâùÌâüÔâùÌâüÌâüÌâüÔâùÌæùÌæùÌâüÔâùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèüÔèüÌâüÌâüÔâùÔèü´¾Ü|ެtœTb|\g€|‰¤œ¤¼kr„¬|€œ„…–œš­„…–„…–„…–”›¨´±¼¬¸Ç”›¨´ÀÉÄÒäÔâùÜéòÔâùÔèüÔâùÔèüÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔèüÔîüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÜêüÔèüÜéòÔèüÔèüÔéôÔèüÔèüÔèüÔâùÔéôÔèüÔèüÔâùÔéôÌæùÔâùÔéôÌâüÔéôÌæùØâäÌæùÌâüÔéôÌâüÔâùÌâüÔâùÌæùÔèüÌâüÌæùÔèüÌâüÔèüÌâüÔèüÔâùÌæùÔèüÌæùÔâùÌæùÔèüÌâüÔèüÌâüÔèüÔèüÌÝ÷ÜêüÔèüÄÖì|‰¤L\|Tg~Tb|\g€dnŒlmŒ„¬dnŒŒ¬tyŽ|€œ|zœtrŽŒœš­Œ†–”›¨žž­ÄÌ×¼ÅÖœ£°¬¸ÇÔâùÜéòÜêüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔîüÜêüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÜñüÔîüÜñüÔîüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÔâùÔèüÔâùÔèüÔèüÔâùÔéôÌæùÔèüÌâüÌæùÌæùÔéôÔéôÌâüÔâùÌæùÌâüÌæùÔâùÌâüÌæùÔâùÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÌâüÔèüÄÖìdv”L\|L[tL[tTVu\g€kr|€œtyŽtrŽ„…¤„…¤trŽtrŽ|€œžž­œš­|€œœ™¡¤ª±¼ºÄŒ“œŒ“¨Œ“¨¬²ÄÔÝîÜâïÜêüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÔîüÜêüÔèüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜêüÔîüÔèüÔèüÔéôÔèüÔèüÔèüÌæùÔâùÔéôÌæùÔâùÔèüÔéôÌâüÔéôÌæùÔèüÔéôÌæùÌâüÔéôÌâüÌâüÔâùÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÌâüÔèüÔèüÌâü¼Îç\n„L[tL[tT]|L[tdjtyŽŒ“¨„…¤|€œ„…¤¬ªÌ|€œ|€œtrŽ„Œœš­žž­„…–¬¶¼¤¤®|†¬°¼|Œ”›¨´¾ÜÔÝîÜêüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔîüÔîüÜñüÔèüÔîüÜñüÔîüÜêüÔîüÜêüÔèüÔîüÔèüÜñüÔèüÜêüÔèüÔèüÔèüÔîüÜêüÔèüÔèüÔéôÔèüÔèüÔâùÔèüÔéôÔâùÔèüÌâüÔéôÌâüÔéôÌâüÔéôÔéôÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌÝ÷\n„DVoL[tL[tDNily”žž­¤¦½”–´tyŽtrޤ¦½¼ºÜ´±ÆŒ¬lm~|zœtw„žž­¬°¼ÌÍÕŒ“œŒžž­œš­|†Œ“¨¬°¼ÔÝîÔÝîÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔîüÔèüÔîüÔîüÔèüÔîüÔîüÜêüÔèüÔîüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÔèüÔâùÔéôÔâùÔéôÔèüÌæùÔâùÔéôÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÌæùÔèüÌâüÌâüÔèüÔèüÔèü¼Îçl~œLUtLUtLUtDNiTVukrÄÆÔ¤¤®ÜâZĄ…–žž­ÌÎäÜÞììêø´·Å¤¤®tw„”•¦´±¼¼ºÄŒŒ”””šœž¡¬®³”›¨„†‰¤¤®¬°¼ÔÝîÜéòÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÜêüÜêüÜêüÜêüÜêüÜêüÜñüÔèüÜêüÔèüÜêüÔîüÜêüÜñüÔèüÜñüÜñüÔèüÜñüÜñüÔèüÜñüÜñüÔèüÜñüÔèüÜñüÔîüÔèüÜñüÔèüÜñüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔéôÔèüÔðôÔèüÔéôÔèüÔèüÔéôÔèüÔéôÔèüÔéôÌæùÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔèüÔèüÌâüÌâü¬ÂÔTg~L\|L\|DNtLUtDNtdjŒ¬”•¦´±ÆÜÜÜÔÒÖ|€œ”•¦¼ºÜÌÆÏ¬°¼œ™¡Œ|zŽ´¶º¤¤®žž­|œ™¡¬®³¬ª´¼ºÄ¤°ºžž­žž­¤¦½ÌÛìÔâùÜêüÜñüÔèüÔîüÔèüÔèüÜêüÔèüÜñüÔèüÜêüÔèüÜêüÔèüÜêüÔîüÔèüÔèüÜñüÔèüÔèüÜêüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÔîüÜñüÔèüÜñüÜêüÔîüÔèüÜñüÔîüÜñüÔîüÔèüÔèüÔðôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÔéôÌæùÔéôÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌæùÔéôÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÌæùÌæùÔâùÌæùÔâù¼ÎçTb|DVoLUtT]|LUtLSjLNikr”•¦„‹žœ£°ŒŒ”Œlm~„Œ|zŽ”•¦lm~lfwtw„„‹žÌÍÕ„‹”tw„”Žž¤¤®¬ª´œ™¡¬ª´´·Å”•¦¤°º„‹”¤°ºÄÖìÔéôÔèüÜêüÔèüÔèüÔèüÜñüÜêüÔèüÜêüÜêüÔèüÜêüÜêüÔèüÔèüÜêüÔèüÔèüÜñüÔèüÔîüÜñüÔèüÔîüÜêüÔîüÔîüÜêüÔîüÔîüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔéôÔîüÔéôÔéôÔéôÔéôÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèü¤²ÇTg~DVoLUtL\|T]|LUtLUtDNtdfx|€œlm~tw„|~|~\^p|€œ¤¤®llodjdfx\brtw„Œ„Œ„‹žtw„djq¤¤®ÄÃÄ””šŒŒ”|””š„‹”œž¡ÌÖæÜâïÜêüÜêüÜêüÔèüÔèüÜêüÔèüÜêüÔèüÜêüÜñüÔèüÜêüÔîüÔèüÔîüÔèüÜñüÔèüÔèüÜêüÔèüÔîüÜñüÔîüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔîüÔîüÔîüÜñüÜêüÜñüÔîüÜêüÔðôÔèüÜòóÔîüÔèüÔðôÔèüÔéôÔèüÔðôÔèüÔéôÔéôÔèüÔðôÌæùÔéôÌæùÔéôÔéôÌæùÔéôÔéôÔéôÌæùÔéôÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌÛìl~œL[tLUtL[tL\|LUtL[tT]|DNiT]|tzœkr\^plvŠdjq„…–djŒŒ”„‹”tw„„‹ž|lm~dfx\^ptw„„‹ždfxdal„Œ¬²Ä¼½Ì¬¶¼ÄÌ×´Àɤ¤®´·Å´·Å¼ÌÙÜâïÔéôÜêüÔèüÜñüÔèüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÜêüÔîüÜêüÔèüÜñüÜêüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔèüÜñüÜêüÔðôÜêüÔèüÜñüÔèüÔðôÜêüÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔðôÔèüÔéôÔîüÔéôÔðôÌæùÔéôÔéôÌæùÔéôÔéôÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÄÖì\n„L[tL[tDNiT]|L[tLUtLUtLUtLNi\g€krT[rT[r\Vkdjtw„tyŽlm~\br\^plr\br\^pdjdjdfxlm~dfx|”•¦¤¤®¬ª´¤¤®¼¾Â¬°¼¬¶¼ÄÅÌÌÔÙ¼ºÄ¼ÅÖÜâïÜêüÜêüÔèüÔîüÔèüÜêüÔèüÜñüÔèüÜêüÔîüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÔðôÔèüÔîüÔéôÔîüÔéôÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔéôÔéôÔéôÔðôÌæùÔéôÔéôÌæùÔéôÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèü´ÆÙTg~DVoL[tDNiTg~LUtLUtT]|LNtLUt\b}\g€TVuLUtLSjLNiT]|lm~T]|dfxT[rT[rT[r\brT[r\b}lrdr|T[rTUitr””š¬ª´”•¦ŒŒ”ŒŒ”„‹”´¶º´¶ºÌÌÌÜÞä¼Â̼ÂÌÔâùÜêüÔèüÜêüÔîüÔèüÔîüÔèüÔîüÔîüÜêüÔîüÔèüÜñüÔèüÜêüÔîüÜêüÜñüÔèüÜñüÔîüÜñüÔîüÔèüÜñüÔèüÔðôÜñüÔîüÜòóÔîüÔîüÜòóÔîüÜñüÜéòÔîüÜñüÔéôÜñüÔéôÔîüÜêüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔéôÔèüÔðôÌæùÔðôÌæùÔéôÌæùÔéôÔéôÔéôÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèü´ÆÙL[tL[tLUtDVoTb|LUtLUtL\|DNiLUtT]|T]|T]|T]|LUtTVuTVu|\b}TUiLUtTUiLUt\VkkrTVuTUikrtyŽdbuT[r|œš­”Žž””š|„‹”|~|z{ŒŒ”ÄÃÄÌÍÕ¤ª±¬¸ÇÄÔÚÔâùÜñüÜêüÔèüÜêüÔîüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÔèüÜñüÜêüÔîüÜêüÔîüÜêüÔîüÔèüÜñüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÔîüÔèüÜñüÜñüÔèüÜñüÔèüÔîüÜêüÔðôÔèüÔðôÔèüÔîüÔéôÔèüÔéôÔèüÔéôÔèüÔðôÌæùÔéôÔèüÔéôÔéôÔéôÔðôÌæùÔéôÌæùÔéôÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔèüÔâùÌâüÔèüÔèü¬¸Ô\g€L[tL[tL\|T]|L\|L[tL\|L[tDNtL\|T]|T]|\^|T]|TVuTVu\^|dnŒTVuLSjLNiDHaDHaDHa„‹”krTVu|€œ„…–TVudfxtw„¤ª±„‹”|tr„…–Œ„Œ„…–¤ž¤ÄÆÔ´·Å¤¤®¤¤®ÄÌ×ÜâïÜêüÜñüÜêüÔèüÔîüÜñüÔîüÜñüÔîüÜêüÜñüÔîüÜêüÔîüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÜñüÜñüÔîüÜñüÜñüÜñüÜñüÜñüÔîüÜòóÜñüÔðôÜñüÔðôÔîüÔîüÜñüÔèüÜñüÔéôÔîüÔîüÔðôÔîüÔéôÔîüÔéôÔîüÔéôÔéôÌæùÔðôÔéôÌæùÔéôÌæùÔéôÔéôÌæùÔèüÔâùÔèüÌâüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔâùÔèü|ެL\|L[tLUtLUtT]|L[tL[tL[tDVoL[tDVoTb|T]|\b}\b}dnŒ\VkT]|T]|\^pTVuTVuLUtLSjDHaLNi|\b}\b}”›´„…–T]|TVudr|Œ¤¤®¬°¼tw„Œ„…–œš­Œ†–trœ™¡¼½Ì„Šˆ¤¤®¼ÂÂÌÍÕÜéòÔèüÜêüÜñüÜñüÜñüÔîüÔîüÔîüÜñüÔèüÜêüÜñüÔîüÜêüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔîüÜòóÜñüÔîüÜñüÔîüÔéôÜñüÔðôÜñüÔéôÔîüÔðôÔèüÔîüÔéôÔîüÔèüÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔéôÌæùÔðôÔéôÔéôÌæùÔèüÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔâùÜêüt†¤L[tDNiL[tLUtT[rT]|L[tT]|L[tDNiL[tL\|Tb|\b}kr\b}trŽ|€œ\b}TVu\b}T[rDJlLIXLNtDHaDNP\^|\^p|œ¤¼tyŽT[r\^ptw„tw„„‹žŒ„ŒŒ†–¤¤®¬ª¼¼ºÄtw„|žž­¤ª±¬®³¤ª±´ÀÉÜéòÜêüÜêüÜêüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔîüÜêüÜñüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜòóÜñüÔðôÜñüÔîüÜñüÔèüÔîüÜñüÔèüÔðôÜêüÔîüÔéôÜñüÔéôÔîüÔèüÔðôÔéôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÌæùÔðôÌæùÔâùÔèüÌâüÔèüÔèüÌæùÔèüÔèüÔèüÔâùÔâùÔèü|ެDVoL[tL[tLUtLUtT[rLUtT]|T[rLUtLUtT]|T]|T]|\g€T]|dbutzœ„…¤|zœT[r\b}\^pTVuLUtLSjTUiLUtLSjTUi|€œœ¤¼lr\b}\^|tw„TZ]\^plm~„Œ”›¨´±Æ¼ºÄ¬ª´dfxtw„ŒŒ”Œ”›¨œ™¡œ£°ÄÌ×ÜêüÜêüÜêüÜêüÔîüÔðôÜñüÜêüÜñüÔîüÜêüÜñüÔèüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÔèüÜòóÔîüÔðôÔîüÔéôÜñüÔîüÔèüÔðôÔèüÔðôÔèüÔîüÔðôÔèüÔéôÔîüÔéôÔðôÌæùÔéôÔðôÔéôÔéôÔéôÔéôÔèüÔèüÔâùÔèüÌæùÔèüÔèüÔâùÜêüÌÝ÷ÌÝ÷ly”LUtLUtDVoDNiLUtLUtT]|LUtLSjLUtLUtT]|LZdT]|T]|dbukrlmŒlmŒ|€œlmŒ\^|LNiLSjTVuLUt\^pLNiLSjTUiT]|dfxT[rT[rlm~T]|\br\b}dfxdfxdfx¤ž¤¤¤®žž­Œlm~dfxtw„|ŒŒ”Œ¤¤®¤¤®ÌÛìÜéòÔéôÜñüÜñüÜñüÔèüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÜñüÔðôÜñüÔîüÜñüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÜñüÔðôÜñüÜñüÜòóÜñüÔîüÜñüÜñüÔîüÜñüÔîüÔéôÜñüÔèüÜñüÔèüÔîüÜòóÔèüÔîüÔîüÔéôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÔéôÌæùÌâüÔèüÌæùÔâùÔèüÔâùÌæùÔèüÔâùÌÝ÷dv”DNiL[tDNiDVoL[tT]|T]|LNiLSjLNtT]|\b}tyŽ\^|\br\b}dnŒdfxdjdfx\b}TUiLSjT[r\b}\^|LSjTUi\g€TVuDNPLNiLNiLSjdj|lm~T[rlm~„ŒŒ„Œ„…–¬ª´žž­|zŽ„…–„Œ||zŽ”Žž””š¤ª¿”›¨¼ÌÙÜâïÜêüÜéòÜñüÔîüÜñüÜñüÜñüÜñüÜêüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÜñüÜñüÜñüÜñüÜñüÔîüÜñüÔîüÜñüÜòóÔîüÔîüÔîüÜñüÔîüÔðôÔîüÔèüÜòóÔîüÔîüÔèüÜòóÔîüÔîüÔðôÔîüÔéôÔîüÔîüÔéôÔðôÔéôÔéôÔèüÔèüÔèüÔèüÌæùÔèüÔèüÔâùÔâù|€œDVoL[tL\|LUtL[tT]|T]|LUtDNiLUtLUt\^|lmŒ|€œkrTUidfx„…–T]|\^pT[rLSj\^p\g€dfxttyŽLUtTVudjDHaDNiLUtDNi\^pt|ˆœT[rTUityŽŒ¬”•¦žž­œ™¡¬®³ŒŒ”tw„|€œ„…–Œ|zŽ”•¦œ™¡´±Æ”•¦¤°ºÌÖæÜéòÜêüÜñüÔéôÜñüÔîüÜñüÔèüÜñüÜñüÜêüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÔðôÜñüÜñüÜñüÔðôÜñüÔîüÜñüÔîüÜñüÔîüÔîüÜêüÔîüÜñüÔðôÔèüÔîüÜñüÔèüÔîüÔèüÜñüÔèüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔâùÔèüÌâüÔèüÔâùÔèüÔâùÔèü|ެDNiLUtLUtLUtL[tT]|T]|L[tDNtLNiDJlLUtdfxŒ¬¤ª¿žž­\^p|€œlm~TUiTUiLSjDHaLSj\g€lvŠT]|T[r\g€LSjDNiLUtLUtDNiLNiDNityŽT]|LNiTNdtzœœš­„…–„…–Œ¼ºÄ””š„Œ„‹”„…–|y„trŒžž­´±¼¬°¼”›¨¬°¼ÌÜâÜéòÜéòÜñüÜñüÜñüÜñüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜñüÔðôÜñüÜñüÔîüÜñüÔðôÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜòóÜñüÔîüÜòóÔîüÔîüÜòóÔîüÔéôÔîüÔèüÜñüÔèüÔðôÔèüÜòóÔîüÔðôÔîüÔéôÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔèüÌæùÔâùÔèüÔâùÔâùÔèüly”LUtLUtLSjLUtT]|\b}Tb|LUtLNiDNtDNiLSjTVudjœ¤¼´·ÅŒ¬djlm~\b}LSjLNiTUiLSjT[rdbudfxDHaLUtLNiDNiLUt\b}DHaTVuLNiTVudjLSjLSjT]||€œ|zŽ\^pdbudfx”•¦”ŽžŒŒ”Œœš­\^pdbuŒ†–žž­ÄÆÔ¼ºÄ¤¤®¤¤®¤°ºÌÔÙÜòóÜòóÜñüÜêüÜêüÜñüÜêüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜòóÔîüÜñüÜòóÔîüÜñüÔîüÜòóÔîüÔðôÜñüÔîüÜòóÔèüÔðôÜêüÔðôÔèüÔðôÔéôÔîüÔéôÔðôÔéôÔéôÔâùÔèüÔèüÔèüÔâùÔèül~œTb|LUtLUtLUtTVuT]|T]|TVuDNiDJlLUtLUtDNi\brly”œ¤¼¬¸Ç\fp\b}\b}T[rT[rLSjLSj\^pdr|\^pdnŒLIXDNiLNtDNiT[rlm~\b}|€œ\^|TVudjTVuLSjdbuŒ¬\^|\^p|zŽ|zŽœ™¡Œ”Žž”•¦žž­\Vk\Vklm~žž­´±¼¼ºÄ¼¾Â¬ª´¤°º´·ÅÌÜâäòüÜéòÜñüÜêüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜñüÜñüÔðôÜñüÜñüÔðôÜñüÜñüÔðôÜòóÔðôÔðôÜñüÔðôÜòóÔîüÜòóÔèüÔðôÔéôÔðôÔéôÔðôÔéôÔðôÔéôÔéôÔéôÔèüÔâùÔèüÔâùÔâù„¬LUtT]|T[rLUtLUtL\|T]|TVuDNtDNtTVuLNtDHaLNiLUtlvŠ´·ÅlvŠ\^pdfx\^|LSjTUiTUiLUtdfx\brlm~\b}DJlLUtLSjDNiLUtT[rtw„¤¦½tw„T]|lm~LNiTVukr„…–LNi\^ptw„Œ†–¤¤®Œœš­Œ†–trtr\^p\^p„‹ž¬°¼´±¼´¶ºÄ¾Ìœ¢¡¬¸ÇÄÌ×ÜéòÜéòÜñüÜêüÜñüÜêüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÔðôÜ÷üÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜ÷üÜòóÜ÷üÔðôÜñüÜñüÜòóÔðôÜñüÜòóÔèüÜòóÔèüÔîüÜòóÔéôÔîüÔðôÜòóÔéôÔðôÜéòÔîüÔðôÔéôÔðôÔéôÔéôÔðôÔéôÔéôÔéôÔéôÔâùÜêüŒš´T[rT[rLUtT]|LUtLUtT]|LUtLUtDNiDNtTVuT]|LSjLUtDNiT[r|€œ\b}T]|T[rLNi\\\\\DB\\\DJlDHa\DB\\,2<45<,.<434,,3434DGLTSLLIX45<,.<45< + +Options: + --exec= + --md5exec= + --loop= + --nocheck + --mt + --noalpha + --lossless + --extra_args= +EOT + exit 1 +} + +run() { + # simple means for a batch speed test + ${executable} $file +} + +check() { + # test the optimized vs. unoptimized versions. this is a bit + # fragile, but good enough for optimization testing. + md5=$({ ${executable} -o - $file || echo "fail1"; } | ${md5exec}) + md5_noasm=$( { ${executable} -noasm -o - $file || echo "fail2"; } | ${md5exec}) + + printf "$file:\t" + if [ "$md5" = "$md5_noasm" ]; then + printf "OK\n" + else + printf "FAILED\n" + exit 1 + fi +} + +check="true" +noalpha="" +lossless="" +mt="" +md5exec="md5sum" +extra_args="" + +n=1 +for opt; do + optval=${opt#*=} + case ${opt} in + --exec=*) executable="${optval}";; + --md5exec=*) md5exec="${optval}";; + --loop=*) n="${optval}";; + --mt) mt="-mt";; + --lossless) lossless="-lossless";; + --noalpha) noalpha="-noalpha";; + --nocheck) check="";; + --extra_args=*) extra_args="${optval}";; + -*) usage;; + *) break;; + esac + shift +done + +[ $# -gt 0 ] || usage +[ "$n" -gt 0 ] || usage + +executable=${executable:-cwebp} +${executable} 2>/dev/null | grep -q Usage || usage +executable="${executable} -quiet ${mt} ${lossless} ${noalpha} ${extra_args}" +set +e + +if [ "$check" = "true" ]; then + TEST=check +else + TEST=run +fi + +N=$n +while [ $n -gt 0 ]; do + for file; do + $TEST + done + n=$((n - 1)) + printf "DONE (%d of %d)\n" $(($N - $n)) $N +done diff --git a/tests/Images/Input/WebP/test_dwebp.sh b/tests/Images/Input/WebP/test_dwebp.sh new file mode 100644 index 000000000..245d1da26 --- /dev/null +++ b/tests/Images/Input/WebP/test_dwebp.sh @@ -0,0 +1,101 @@ +#!/bin/sh +## +## test_dwebp.sh +## +## Author: John Koleszar +## +## Simple test driver for validating (via md5 sum) the output of the libwebp +## dwebp example utility. +## +## This file distributed under the same terms as libwebp. See the libwebp +## COPYING file for more information. +## + +self=$0 + +usage() { + cat < (must support '-c') + --mt + --extra_args= + --formats=format_list (default: $formats) + --dump-md5s +EOT + exit 1 +} + +# Decode $1 and verify against md5s. +check() { + local f="$1" + shift + # Decode the file to the requested formats. + for fmt in $formats; do + eval ${executable} ${mt} -${fmt} ${extra_args} "$@" \ + -o "${f}.${fmt}" "$f" ${devnull} + done + + if [ "$dump_md5s" = "true" ]; then + for fmt in $formats; do + (cd $(dirname $f); ${md5exec} "${f##*/}.${fmt}") + done + else + for fmt in $formats; do + # Check the md5sums + grep ${f##*/}.${fmt} "$tests" | (cd $(dirname $f); ${md5exec} -c -) \ + || exit 1 + done + fi + + # Clean up. + for fmt in $formats; do + rm -f "${f}.${fmt}" + done +} + +# PPM (RGB), PAM (RGBA), PGM (YUV), BMP (BGRA/BGR), TIFF (rgbA/RGB) +formats="bmp pam pgm ppm tiff" +mt="" +md5exec="md5sum" +devnull="> /dev/null 2>&1" +dump_md5s="false" +for opt; do + optval=${opt#*=} + case ${opt} in + --exec=*) executable="${optval}";; + --md5exec=*) md5exec="${optval}";; + --formats=*) formats="${optval}";; + --dump-md5s) dump_md5s="true";; + --mt) mt="-mt";; + --extra_args=*) extra_args="${optval}";; + -v) devnull="";; + -*) usage;; + *) [ -z "$tests" ] || usage; tests="$opt";; + esac +done + +# Validate test file +if [ -z "$tests" ]; then + [ -f "$(dirname $self)/libwebp_tests.md5" ] && tests="$(dirname $self)/libwebp_tests.md5" +fi +[ -f "$tests" ] || usage + +# Validate test executable +executable=${executable:-dwebp} +${executable} 2>/dev/null | grep -q Usage || usage + +test_dir=$(dirname ${tests}) +for f in $(grep -o '[[:alnum:]_-]*\.webp' "$tests" | uniq); do + f="${test_dir}/${f}" + check "$f" + + if [ "$dump_md5s" = "false" ]; then + # Decode again, without optimization this time + check "$f" -noasm + fi +done + +echo "DONE" diff --git a/tests/Images/Input/WebP/test_lossless.sh b/tests/Images/Input/WebP/test_lossless.sh new file mode 100644 index 000000000..347ccbd54 --- /dev/null +++ b/tests/Images/Input/WebP/test_lossless.sh @@ -0,0 +1,82 @@ +#!/bin/sh +## +## test_lossless.sh +## +## Simple test to validate decoding of lossless test vectors using +## the dwebp example utility. +## +## This file distributed under the same terms as libwebp. See the libwebp +## COPYING file for more information. +## +set -e + +self=$0 +usage() { + cat < + --formats=format_list (default: $formats) +EOT + exit 1 +} + +# Decode $1 as a pam and compare to $2. Additional parameters are passed to the +# executable. +check() { + local infile="$1" + local reffile="$2" + local outfile="$infile.${reffile##*.}" + shift 2 + printf "${outfile##*/}: " + eval ${executable} "$infile" $extra_args -o "$outfile" "$@" ${devnull} + cmp "$outfile" "$reffile" + echo "OK" + + rm -f "$outfile" +} + +# PPM (RGB), PAM (RGBA), PGM (YUV), BMP (BGRA/BGR), TIFF (rgbA/RGB) +formats="ppm pam pgm bmp tiff" +devnull="> /dev/null 2>&1" +for opt; do + optval=${opt#*=} + case ${opt} in + --exec=*) executable="${optval}";; + --extra_args=*) extra_args="${optval}";; + --formats=*) formats="${optval}";; + -v) devnull="";; + *) usage;; + esac +done +test_file_dir=$(dirname $self) + +executable=${executable:-dwebp} +${executable} 2>/dev/null | grep -q Usage || usage + +vectors="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" +for i in $vectors; do + for fmt in $formats; do + file="$test_file_dir/lossless_vec_1_$i.webp" + check "$file" "$test_file_dir/grid.$fmt" -$fmt + check "$file" "$test_file_dir/grid.$fmt" -$fmt -noasm + done +done + +for i in $vectors; do + for fmt in $formats; do + file="$test_file_dir/lossless_vec_2_$i.webp" + check "$file" "$test_file_dir/peak.$fmt" -$fmt + check "$file" "$test_file_dir/peak.$fmt" -$fmt -noasm + done +done + +for fmt in $formats; do + file="$test_file_dir/lossless_color_transform.webp" + check "$file" "$test_file_dir/lossless_color_transform.$fmt" -$fmt + check "$file" "$test_file_dir/lossless_color_transform.$fmt" -$fmt -noasm +done + +echo "ALL TESTS OK" diff --git a/tests/Images/Input/WebP/very_short.webp b/tests/Images/Input/WebP/very_short.webp new file mode 100644 index 000000000..f1297cfc3 --- /dev/null +++ b/tests/Images/Input/WebP/very_short.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c3d70b2fd3caad1fbe01b7a0a6b0c9152525b2ed4dde7a50fbba6c1ea6a0d6 +size 86 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-001.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-001.webp new file mode 100644 index 000000000..410e0a090 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-001.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44e1ddf3593d26148a03fb95379e03bb21fb397e3c9a26d64b47433e521d91c6 +size 754 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-002.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-002.webp new file mode 100644 index 000000000..d16d3e20d --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-002.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbd5f5c38f1d2692b1e21b9981d50f71522faff68d57929674899c810cb5ed88 +size 4448 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-003.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-003.webp new file mode 100644 index 000000000..ca443b856 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-003.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:526dbfc452a5c72edc672a2bed229196a67232dcc852b0b5c806d82e9bc108f2 +size 4500 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-004.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-004.webp new file mode 100644 index 000000000..b956bf8db --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-004.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fb9183e44744997a9afec7242120c2e92c1dd9a9351ca5312abbe5ee0606c39 +size 754 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-005.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-005.webp new file mode 100644 index 000000000..48574db18 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-005.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:788a937a4287e41d8116fc8f79673fea47a90090e68bdc966af9a14943eff11c +size 4444 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-006.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-006.webp new file mode 100644 index 000000000..e74cf8997 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-006.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9642771e93f4d43fa0cde4116a447dc108cada765c94c0d46cdd1750d3712db8 +size 8528 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-007.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-007.webp new file mode 100644 index 000000000..d727a3586 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-007.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b44d063894291fb1b5309206bf476a2daa3263373db4e9f32a5f9a525b3d8b59 +size 346 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-008.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-008.webp new file mode 100644 index 000000000..1e139b39d --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-008.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c3e47eaa974319611baa3c12faeb9ce9ffbdd73e088c0f9dde164384b6b866c +size 45636 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-009.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-009.webp new file mode 100644 index 000000000..80bd6f70c --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-009.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc9d71fffce845fc46717bf7fc89647a711afd4398a3c16668727aa38585f5c3 +size 8502 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-010.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-010.webp new file mode 100644 index 000000000..7fcff7b58 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-010.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de3cef909dc6cf5f0211351401ab1b303efe2358b9654a8d6ac01e3a4f29178b +size 16050 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-011.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-011.webp new file mode 100644 index 000000000..8dcacc6ef --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-011.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a856ab961966aad8dde337c71303f145e24e3d8a066eeb7a08d323d90c84221e +size 758 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-012.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-012.webp new file mode 100644 index 000000000..b660134df --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-012.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ffe43365b937f075ee508159ae166fec7bc0671358ff5c2bdc8f5689b20f860 +size 1044 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-013.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-013.webp new file mode 100644 index 000000000..51b2d184a --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-013.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d5301de57b7fd3cfdf55e463020742a88e0d3b522e602090acc2cf1f7a264ef +size 758 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-014.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-014.webp new file mode 100644 index 000000000..1d537103b --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-014.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:957cf5dfd41e46ee332791082fdb2e42ca63881a0b76865ced7a09c4fbab6138 +size 11982 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-015.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-015.webp new file mode 100644 index 000000000..ca82dcaa1 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-015.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d65d42a905b9cdccc38b968573f9b7d86a296dbb3972ec07ae56caae84ee40f1 +size 7412 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-016.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-016.webp new file mode 100644 index 000000000..eda3b185c --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-016.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77d17a74d586ccd2abea002e7b966d2b887bab68be2aa1c3866d5ea2f3587e76 +size 188 diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-017.webp b/tests/Images/Input/WebP/vp80-00-comprehensive-017.webp new file mode 100644 index 000000000..abedc9556 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-00-comprehensive-017.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0797cd3ff4e12e1de9a2330ccd78fb675e6d2d7422803350a00df5c1125faaf8 +size 188 diff --git a/tests/Images/Input/WebP/vp80-01-intra-1400.webp b/tests/Images/Input/WebP/vp80-01-intra-1400.webp new file mode 100644 index 000000000..3f53c34e5 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-01-intra-1400.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:385b683228d78120162c4be79287010bba55175ed06c5ad0d2fe82f837704641 +size 15294 diff --git a/tests/Images/Input/WebP/vp80-01-intra-1411.webp b/tests/Images/Input/WebP/vp80-01-intra-1411.webp new file mode 100644 index 000000000..89436b3cf --- /dev/null +++ b/tests/Images/Input/WebP/vp80-01-intra-1411.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9330d735d1cdda007810e76a9cd836f07a6e3954363a0f82b1aca52adf346b4 +size 11963 diff --git a/tests/Images/Input/WebP/vp80-01-intra-1416.webp b/tests/Images/Input/WebP/vp80-01-intra-1416.webp new file mode 100644 index 000000000..f1171b9cc --- /dev/null +++ b/tests/Images/Input/WebP/vp80-01-intra-1416.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3e253211155418d55618ac0a70ed1118a96917ce63b129bc49914d09f3620cf +size 11227 diff --git a/tests/Images/Input/WebP/vp80-01-intra-1417.webp b/tests/Images/Input/WebP/vp80-01-intra-1417.webp new file mode 100644 index 000000000..23e8c8fc6 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-01-intra-1417.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d28624b166b4bcff6494f53f14e3335d5a762faa8c8e7fbfb0045f2b04123724 +size 11364 diff --git a/tests/Images/Input/WebP/vp80-02-inter-1402.webp b/tests/Images/Input/WebP/vp80-02-inter-1402.webp new file mode 100644 index 000000000..6853283e1 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-02-inter-1402.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ea8dcf7462d978ce3f5a38f505caebe09b3d9170a03fdb6479a8111c6bf54c2 +size 15294 diff --git a/tests/Images/Input/WebP/vp80-02-inter-1412.webp b/tests/Images/Input/WebP/vp80-02-inter-1412.webp new file mode 100644 index 000000000..0af4ef532 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-02-inter-1412.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2286cf0613e7a910b44494338157ef73504fefd88cd9427b4aecfbed7a034ae +size 11963 diff --git a/tests/Images/Input/WebP/vp80-02-inter-1418.webp b/tests/Images/Input/WebP/vp80-02-inter-1418.webp new file mode 100644 index 000000000..8d825257b --- /dev/null +++ b/tests/Images/Input/WebP/vp80-02-inter-1418.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7a9da63fdec6ca0c42447867f6a7c7d165b0c3fcbf9313cacd6fc8eeb79a6fa +size 17680 diff --git a/tests/Images/Input/WebP/vp80-02-inter-1424.webp b/tests/Images/Input/WebP/vp80-02-inter-1424.webp new file mode 100644 index 000000000..934cae5bf --- /dev/null +++ b/tests/Images/Input/WebP/vp80-02-inter-1424.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8ce9c3078e56a9281620cace12abb39aebdfc0ab25a6586f676e3cf2981704 +size 5254 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1401.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1401.webp new file mode 100644 index 000000000..c4f23515a --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1401.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a243611a69fef3a8306dd3c48d645d7d4295f60781428b39e1f32bea5c5df46c +size 15296 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1403.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1403.webp new file mode 100644 index 000000000..a0322ce69 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1403.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:007c78b248d71d135638637417a458d0a89ba3a46850df4503d10b576a3433c6 +size 15296 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1407.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1407.webp new file mode 100644 index 000000000..4075c52a3 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1407.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7fdcc6f20e730074602fbf4fbfbb76f614f13f7bdb7ce038ba32dc691fdfd09 +size 26388 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1408.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1408.webp new file mode 100644 index 000000000..737b281b3 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1408.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83c9a6874afc10ef08b853b7c990947fe78206b6a9ca8ad092fda3941d78d2b4 +size 26392 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1409.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1409.webp new file mode 100644 index 000000000..0af47a0a7 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1409.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52c0f64e79056e8927ec9625e3fcfe3b0665afe30a12270f3c61665e80e5f4ed +size 26402 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1410.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1410.webp new file mode 100644 index 000000000..10cbce996 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1410.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28dd9a50e95436ca29fd8b191d6073a6a7c049de732e512bb127b925eeba9102 +size 26420 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1413.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1413.webp new file mode 100644 index 000000000..6087cae87 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1413.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a757b7f0b539d51d9c450bce6f40c99d00109a8b61ea327a07867ebce23c397 +size 11998 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1414.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1414.webp new file mode 100644 index 000000000..d4ac35db2 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1414.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f9fe429dbef950dbd9b096b22566f4df4e036af0d95a4c05c954da44490e4af +size 19884 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1415.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1415.webp new file mode 100644 index 000000000..52ee59a12 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1415.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:feac273c9e5152e6f9ccdffa65f0a9ce863abbbd446625a32ad42922452429e3 +size 19877 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1425.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1425.webp new file mode 100644 index 000000000..d3e3ff1de --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1425.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65b704903e9e11c43132aef1d9353011928954c9d5fdd5312477de40ddb26fb9 +size 3632 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1426.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1426.webp new file mode 100644 index 000000000..1a444068a --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1426.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11bc8b1f337cbe0dd1760eee1483d82243147917c5588b9463353a71c0b03271 +size 18524 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1427.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1427.webp new file mode 100644 index 000000000..95d6289d9 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1427.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4a1ee18d804b4f83bf5ef7ff1d3d849a136a7480dd074c721c41e788b51868c +size 32982 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1432.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1432.webp new file mode 100644 index 000000000..44257b641 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1432.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa1d8b9426f595db1c92733470c60bddfcf021cb95a6c27da829598180e0a6d0 +size 20094 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1435.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1435.webp new file mode 100644 index 000000000..281b63983 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1435.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb8129ea589ab5cab6368be88861125bcc55a492ba2eb20c086aa99c7c9410d2 +size 12080 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1436.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1436.webp new file mode 100644 index 000000000..39c8b7191 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1436.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78a5fd9afa2edb44d73235a0d1a160abf34efd8ee9495121d3405aa89a8f8b63 +size 14512 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1437.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1437.webp new file mode 100644 index 000000000..0c094e8c4 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1437.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:299ac1152847f4bded20bdd114c9f1f5a12ceee767a924cb347db7508d784375 +size 27132 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1441.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1441.webp new file mode 100644 index 000000000..d13f619af --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1441.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70d3bc371f42f3a7d8f59eb89c66a3d1ef10476baa86e66487f158370403b595 +size 4606 diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1442.webp b/tests/Images/Input/WebP/vp80-03-segmentation-1442.webp new file mode 100644 index 000000000..047bf1572 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-03-segmentation-1442.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ef18fbc941074c644d01db06fcf06b9e29628e6bedf23db29c239aa1795cc9b +size 14804 diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1404.webp b/tests/Images/Input/WebP/vp80-04-partitions-1404.webp new file mode 100644 index 000000000..2d29d86fd --- /dev/null +++ b/tests/Images/Input/WebP/vp80-04-partitions-1404.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25cd4540f189f61ab0119f8f26e3dc28ba1a7840843b205389948dc3019eee6d +size 15298 diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1405.webp b/tests/Images/Input/WebP/vp80-04-partitions-1405.webp new file mode 100644 index 000000000..f8704e166 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-04-partitions-1405.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d2daf0d7c7e902208621342450bd4009a7bfe3b6aaf36b7d43d232066cd9037 +size 15308 diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1406.webp b/tests/Images/Input/WebP/vp80-04-partitions-1406.webp new file mode 100644 index 000000000..dcf7a73a5 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-04-partitions-1406.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af294ab9f4de0ca82f9df0a29d60f00b1bc20099d337ffaac63e6e1e5c4a14e6 +size 15324 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1428.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1428.webp new file mode 100644 index 000000000..727ec0e10 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1428.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a13d9081c8d55dacd6819704712a64a7d25971e59c0ba7e5e5ae4c86ca522b7b +size 8864 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1429.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1429.webp new file mode 100644 index 000000000..d1f36de81 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1429.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99e3f0a30900c65f5af22e41bc60c4fc7209e2c8f93d2edf5d5ff09db6beb900 +size 14518 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1430.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1430.webp new file mode 100644 index 000000000..01399b5e2 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1430.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acd9c9fb1876fd2035405b9b72aa5a985922ec1aab2055f6c32a21e02fdd9dbd +size 290 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1431.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1431.webp new file mode 100644 index 000000000..b924e43c4 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1431.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9aa0b809dc9aac340acab0f7f4953f497e4b6cefc9dda14f823ab3053a11d5cd +size 6666 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1433.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1433.webp new file mode 100644 index 000000000..340c4a448 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1433.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:005811b6b16550a1da22a1df767311c1b85f1cc7c2409d7917fd594d0b48d4c1 +size 14508 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1434.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1434.webp new file mode 100644 index 000000000..c06ea3fb9 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1434.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da5bef25e427bb9a8be2889c76a65f9506cdfc4bab455b3285b6b627e5880285 +size 18224 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1438.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1438.webp new file mode 100644 index 000000000..618f5e358 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1438.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd0ea301c7446b6fd6d002b9ab48b383501ce05c3953d589be48ede5cf293f9d +size 9981 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1439.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1439.webp new file mode 100644 index 000000000..e3ac596a2 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1439.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36bf4fef87be45f49411f93102433f117d54356a7aebd294ae1b68799938ce1c +size 20068 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1440.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1440.webp new file mode 100644 index 000000000..809a2fd9d --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1440.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66a1bc109f04baa07a530fb79267d931899591c10798c4dc95f59eb03c5ac44 +size 14508 diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1443.webp b/tests/Images/Input/WebP/vp80-05-sharpness-1443.webp new file mode 100644 index 000000000..851dfb6b6 --- /dev/null +++ b/tests/Images/Input/WebP/vp80-05-sharpness-1443.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcedf4d253801cf2461bd01675558dadc3895395a6432d0ba8f5cb9734b4040c +size 6188 From 4cd76ccf4bac9611c7be28bdc8351aa50f5c8e92 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Fri, 18 Oct 2019 08:33:51 +0200 Subject: [PATCH 012/359] Two sample images missed --- tests/Images/Input/WebP/bryce.webp | 4 ++-- tests/Images/Input/WebP/lossless_big_random_alpha.webp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Images/Input/WebP/bryce.webp b/tests/Images/Input/WebP/bryce.webp index 6cb404fae..763ac2428 100644 --- a/tests/Images/Input/WebP/bryce.webp +++ b/tests/Images/Input/WebP/bryce.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bed2a30a0857c188db0d2c7caf7d0e95c29eb624494c0a5d7a9b13407c19df0 -size 3533773 +oid sha256:d6ba1142638a7ae9df901bb7fc8e3a9e6afbe0ec8320fd28e35308a57a2e3e4f +size 3533772 diff --git a/tests/Images/Input/WebP/lossless_big_random_alpha.webp b/tests/Images/Input/WebP/lossless_big_random_alpha.webp index 7f90af8a5..a2baaf1a3 100644 --- a/tests/Images/Input/WebP/lossless_big_random_alpha.webp +++ b/tests/Images/Input/WebP/lossless_big_random_alpha.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a14082445329a37dfcd2954425f87f0930f81066cd1ac7d0624b6c994c4191f0 -size 13968251 +oid sha256:40928dc5a6ca61e7008d212e66b24f5e62f43d5fe55f23add9843414168cbaa6 +size 13968249 From 01412d09819494d2c6ff5c1a80ea704df90d8d80 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 19 Oct 2019 22:19:58 +0200 Subject: [PATCH 013/359] Add bitreader for a VP8L stream. Determining image size for VP8L should work now. --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 93 +++++++++++++++++++ .../Formats/WebP/WebPDecoderCore.cs | 11 +-- 2 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8LBitReader.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs new file mode 100644 index 000000000..bcde6e74c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + ///

+ /// A bit reader for VP8 streams. + /// + public class Vp8LBitreader + { + private readonly Stream stream; + + /// + /// Initializes a new instance of the class. + /// + /// The stream to read from. + public Vp8LBitreader(Stream stream) + { + this.stream = new MemoryStream(); + stream.CopyTo(this.stream); + this.Offset = 0; + this.Bit = 0; + } + + private long Offset { get; set; } + + private int Bit { get; set; } + + /// + /// Check if the offset is inside the stream length. + /// + private bool ValidPosition + { + get + { + return this.Offset < this.stream.Length; + } + } + + /// + /// Reads a unsigned short value from the stream. The bits of each byte are read in least-significant-bit-first order. + /// + /// The number of bits to read (should not exceed 16). + /// A ushort value. + public uint Read(int count) + { + uint readValue = 0; + for (int bitPos = 0; bitPos < count; bitPos++) + { + bool bitRead = this.ReadBit(); + if (bitRead) + { + readValue = (uint)(readValue | (1 << bitPos)); + } + } + + return readValue; + } + + /// + /// Reads one bit. + /// + /// True, if the bit is one, otherwise false. + public bool ReadBit() + { + if (!ValidPosition) + { + WebPThrowHelper.ThrowImageFormatException("The image stream does not contain enough data"); + } + + this.stream.Seek(this.Offset, SeekOrigin.Begin); + byte value = (byte)((this.stream.ReadByte() >> this.Bit) & 1); + this.AdvanceBit(); + this.stream.Seek(this.Offset, SeekOrigin.Begin); + + return value == 1; + } + + /// + /// Advances the stream by one Bit. + /// + public void AdvanceBit() + { + this.Bit = (this.Bit + 1) % 8; + if (this.Bit == 0) + { + this.Offset++; + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 49871d005..769c1a1ca 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -230,15 +230,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } // 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; + var bitReader = new Vp8LBitreader(this.currentStream); + uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; + uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; return new WebPImageInfo() { - Width = width, - Height = height, + Width = (int)width, + Height = (int)height, IsLossLess = true, DataSize = dataSize }; From 593d247cd369513b7c257b969cb6395e4001307d Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 20 Oct 2019 09:48:57 +0200 Subject: [PATCH 014/359] height comes before width, switch order. --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 769c1a1ca..72000b8e9 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -231,8 +231,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // The first 28 bits of the bitstream specify the width and height of the image. var bitReader = new Vp8LBitreader(this.currentStream); - uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; return new WebPImageInfo() { From d4efca86074097e6bf2cc9fd1264b2a4ec7de29c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Oct 2019 19:31:07 +0200 Subject: [PATCH 015/359] Parsing the transforms (still WIP) --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 10 +-- .../Formats/WebP/WebPDecoderCore.cs | 62 ++++++++++++++++++- .../Formats/WebP/WebPTransformType.cs | 37 +++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPTransformType.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index bcde6e74c..39ee78cd6 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -8,15 +8,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// A bit reader for VP8 streams. /// - public class Vp8LBitreader + public class Vp8LBitReader { private readonly Stream stream; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The stream to read from. - public Vp8LBitreader(Stream stream) + public Vp8LBitReader(Stream stream) { this.stream = new MemoryStream(); stream.CopyTo(this.stream); @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int Bit { get; set; } /// - /// Check if the offset is inside the stream length. + /// Gets a value indicating whether the offset is inside the stream length. /// private bool ValidPosition { @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// True, if the bit is one, otherwise false. public bool ReadBit() { - if (!ValidPosition) + if (!this.ValidPosition) { WebPThrowHelper.ThrowImageFormatException("The image stream does not contain enough data"); } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 72000b8e9..b8bbf7599 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -230,10 +230,70 @@ namespace SixLabors.ImageSharp.Formats.WebP } // The first 28 bits of the bitstream specify the width and height of the image. - var bitReader = new Vp8LBitreader(this.currentStream); + var bitReader = new Vp8LBitReader(this.currentStream); uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; + // The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. + bool alphaIsUsed = bitReader.ReadBit(); + + // The next 3 bytes are the version. The version_number is a 3 bit code that must be set to 0. + // Any other value should be treated as an error. + uint version = bitReader.Read(3); + if (version != 0) + { + WebPThrowHelper.ThrowImageFormatException($"Unexpected webp version number: {version}"); + } + + // Next bit indicates, if a transformation is present. + bool transformPresent = bitReader.ReadBit(); + int numberOfTransformsPresent = 0; + while (transformPresent) + { + var transformType = (WebPTransformType)bitReader.Read(2); + switch (transformType) + { + case WebPTransformType.SubtractGreen: + // There is no data associated with this transform. + break; + case WebPTransformType.ColorIndexingTransform: + // The transform data contains color table size and the entries in the color table. + // 8 bit value for color table size. + uint colorTableSize = bitReader.Read(8) + 1; + // TODO: color table should follow here? + break; + + case WebPTransformType.PredictorTransform: + { + // The first 3 bits of prediction data define the block width and height in number of bits. + // The number of block columns, block_xsize, is used in indexing two-dimensionally. + uint sizeBits = bitReader.Read(3) + 2; + int blockWidth = 1 << (int)sizeBits; + int blockHeight = 1 << (int)sizeBits; + + break; + } + + case WebPTransformType.ColorTransform: + { + // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, + // just like the predictor transform: + uint sizeBits = bitReader.Read(3) + 2; + int blockWidth = 1 << (int)sizeBits; + int blockHeight = 1 << (int)sizeBits; + break; + } + } + + numberOfTransformsPresent++; + if (numberOfTransformsPresent == 4) + { + break; + } + + transformPresent = bitReader.ReadBit(); + } + return new WebPImageInfo() { Width = (int)width, diff --git a/src/ImageSharp/Formats/WebP/WebPTransformType.cs b/src/ImageSharp/Formats/WebP/WebPTransformType.cs new file mode 100644 index 000000000..ed6e37e0a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPTransformType.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Enum for the different transform types. Transformations are reversible manipulations of the image data + /// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. + /// Transformations can make the final compression more dense. + /// + public enum WebPTransformType : uint + { + /// + /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. + /// + PredictorTransform = 0, + + /// + /// The goal of the color transform is to decorrelate the R, G and B values of each pixel. + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + ColorTransform = 1, + + /// + /// The subtract green transform subtracts green values from red and blue values of each pixel. + /// When this transform is present, the decoder needs to add the green value to both red and blue. + /// There is no data associated with this transform. + /// + SubtractGreen = 2, + + /// + /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. + /// The color indexing transform achieves this. + /// + ColorIndexingTransform = 3, + } +} From 9b19a762074e2289e17b8a7b8de2813295b894b3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Oct 2019 19:12:49 +0200 Subject: [PATCH 016/359] Add additional chunk header constants --- src/ImageSharp/Formats/WebP/WebPConstants.cs | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 038d11301..f07a79814 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -94,6 +94,9 @@ namespace SixLabors.ImageSharp.Formats.WebP LossLessFlag // L }; + /// + /// Chunk contains information about the alpha channel. + /// public static readonly byte[] AlphaHeader = { 0x41, // A @@ -101,5 +104,60 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x50, // P 0x48, // H }; + + /// + /// Chunk which contains a color profile. + /// + public static readonly byte[] IccpHeader = + { + 0x49, // I + 0x43, // C + 0x43, // C + 0x50, // P + }; + + /// + /// Chunk which contains EXIF metadata about the image. + /// + public static readonly byte[] ExifHeader = + { + 0x45, // E + 0x58, // X + 0x49, // I + 0x46, // F + }; + + /// + /// Chunk contains XMP metadata about the image. + /// + public static readonly byte[] XmpHeader = + { + 0x58, // X + 0x4D, // M + 0x50, // P + 0x20, // Space + }; + + /// + /// For an animated image, this chunk contains the global parameters of the animation. + /// + public static readonly byte[] AnimationParameterHeader = + { + 0x41, // A + 0x4E, // N + 0x49, // I + 0x4D, // M + }; + + /// + /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. + /// + public static readonly byte[] AnimationHeader = + { + 0x41, // A + 0x4E, // N + 0x4D, // M + 0x46, // F + }; } } From ca93805da6031a4ac0d11d8d21f45871579e8e79 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Oct 2019 19:35:44 +0200 Subject: [PATCH 017/359] Parsing of image features of VP8X header --- .../Formats/WebP/WebPDecoderCore.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index b8bbf7599..60e2b66bb 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -151,8 +151,26 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Read(this.buffer, 0, 4); uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + // This byte contains information about the image features used. + // The first two bit should and the last bit should be 0. + // TODO: should an exception be thrown if its not the case, or just ignore it? byte imageFeatures = (byte)this.currentStream.ReadByte(); + // If bit 3 is set, a ICC Profile Chunk should be present. + bool isIccPresent = (imageFeatures & (1 << 5)) != 0; + + // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk). + bool isAlphaPresent = (imageFeatures & (1 << 4)) != 0; + + // If bit 5 is set, a EXIF metadata should be present. + bool isExifPresent = (imageFeatures & (1 << 3)) != 0; + + // If bit 6 is set, XMP metadata should be present. + bool isXmpPresent = (imageFeatures & (1 << 2)) != 0; + + // If bit 7 is set, animation should be present. + bool isAnimationPresent = (imageFeatures & (1 << 7)) != 0; + // 3 reserved bytes should follow which are supposed to be zero. this.currentStream.Read(this.buffer, 0, 3); @@ -166,6 +184,8 @@ namespace SixLabors.ImageSharp.Formats.WebP this.buffer[3] = 0; int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; + // TODO: optional chunks ICCP and ANIM can follow here. + return new WebPImageInfo() { Width = width, From bc80cf1c6b861cad85f97d37ed380cb7a5e01e87 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Oct 2019 20:30:06 +0200 Subject: [PATCH 018/359] Use the width and height from the VP8X information, if present --- .../Formats/WebP/WebPDecoderCore.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 60e2b66bb..4f574e7d7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -70,8 +70,6 @@ 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); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) @@ -83,6 +81,8 @@ namespace SixLabors.ImageSharp.Formats.WebP ReadSimpleLossy(pixels, image.Width, image.Height); } + // TODO: there can be optional chunks after the image data, like EXIF, XMP etc. + return image; } @@ -121,19 +121,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return chunkSize; } - private WebPImageInfo ReadVp8Info() + private WebPImageInfo ReadVp8Info(int vpxWidth = 0, int vpxHeight = 0) { // Read VP8 chunk header. this.currentStream.Read(this.buffer, 0, 4); if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header)) { - return this.ReadVp8Header(); + return this.ReadVp8Header(vpxWidth, vpxHeight); } if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader)) { - return this.ReadVp8LHeader(); + return this.ReadVp8LHeader(vpxWidth, vpxHeight); } if (this.buffer.SequenceEqual(WebPConstants.Vp8XHeader)) @@ -184,18 +184,13 @@ namespace SixLabors.ImageSharp.Formats.WebP this.buffer[3] = 0; int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; - // TODO: optional chunks ICCP and ANIM can follow here. + // TODO: optional chunks ICCP and ANIM can follow here. Ignoring them for now. - return new WebPImageInfo() - { - Width = width, - Height = height, - IsLossLess = false, // note: this is maybe incorrect here - DataSize = chunkSize - }; + // A VP8 or VP8L chunk will follow here. + return this.ReadVp8Info(width, height); } - private WebPImageInfo ReadVp8Header() + private WebPImageInfo ReadVp8Header(int vpxWidth = 0, int vpxHeight = 0) { // VP8 data size. this.currentStream.Read(this.buffer, 0, 3); @@ -227,16 +222,19 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; + // Use the width and height from the VP8X information, if its provided, because its 3 bytes instead of 14 bits. + bool isVpxDimensionsPresent = vpxHeight != 0 || vpxWidth != 0; + return new WebPImageInfo() { - Width = width, - Height = height, + Width = isVpxDimensionsPresent ? vpxWidth : width, + Height = isVpxDimensionsPresent ? vpxHeight : height, IsLossLess = false, DataSize = dataSize }; } - private WebPImageInfo ReadVp8LHeader() + private WebPImageInfo ReadVp8LHeader(int vpxWidth = 0, int vpxHeight = 0) { // VP8 data size. this.currentStream.Read(this.buffer, 0, 4); @@ -314,10 +312,13 @@ namespace SixLabors.ImageSharp.Formats.WebP transformPresent = bitReader.ReadBit(); } + // Use the width and height from the VP8X information, if its provided, because its 3 bytes instead of 14 bits. + bool isVpxDimensionsPresent = vpxHeight != 0 || vpxWidth != 0; + return new WebPImageInfo() { - Width = (int)width, - Height = (int)height, + Width = isVpxDimensionsPresent ? vpxWidth : (int)width, + Height = isVpxDimensionsPresent ? vpxHeight : (int)height, IsLossLess = true, DataSize = dataSize }; From 3c419fcfdcfd15ffb9eb02a0b7a8e5ef1d1ef336 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Oct 2019 21:33:12 +0200 Subject: [PATCH 019/359] Refactor determining chunk types --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 16 +-- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 56 ++++++++++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 101 ------------------ .../Formats/WebP/WebPDecoderCore.cs | 58 ++++++---- 4 files changed, 102 insertions(+), 129 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPChunkType.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 39ee78cd6..036f441d0 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -15,11 +15,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Initializes a new instance of the class. /// - /// The stream to read from. - public Vp8LBitReader(Stream stream) + /// The input stream to read from. + public Vp8LBitReader(Stream inputStream) { this.stream = new MemoryStream(); - stream.CopyTo(this.stream); + long inputStreamPos = inputStream.Position; + inputStream.CopyTo(this.stream); + inputStream.Position = inputStreamPos; this.Offset = 0; this.Bit = 0; } @@ -29,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int Bit { get; set; } /// - /// Gets a value indicating whether the offset is inside the stream length. + /// Gets a value indicating whether the offset is inside the inputStream length. /// private bool ValidPosition { @@ -40,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reads a unsigned short value from the stream. The bits of each byte are read in least-significant-bit-first order. + /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. /// /// The number of bits to read (should not exceed 16). /// A ushort value. @@ -67,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (!this.ValidPosition) { - WebPThrowHelper.ThrowImageFormatException("The image stream does not contain enough data"); + WebPThrowHelper.ThrowImageFormatException("The image inputStream does not contain enough data"); } this.stream.Seek(this.Offset, SeekOrigin.Begin); @@ -79,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Advances the stream by one Bit. + /// Advances the inputStream by one Bit. /// public void AdvanceBit() { diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs new file mode 100644 index 000000000..ba7fb9fd6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Contains a list of different webp chunk types. + /// + internal enum WebPChunkType : uint + { + /// + /// Header signaling the use of VP8 video format. + /// + Vp8 = 0x56503820U, + + /// + /// Header for a extended-VP8 chunk. + /// + Vp8L = 0x5650384CU, + + /// + /// Header for a extended-VP8 chunk. + /// + Vp8X = 0x56503858U, + + /// + /// Chunk contains information about the alpha channel. + /// + Alpha = 0x414C5048U, + + /// + /// Chunk which contains a color profile. + /// + Iccp = 0x49434350U, + + /// + /// Chunk which contains EXIF metadata about the image. + /// + Exif = 0x45584946U, + + /// + /// Chunk contains XMP metadata about the image. + /// + Xmp = 0x584D5020U, + + /// + /// For an animated image, this chunk contains the global parameters of the animation. + /// + AnimationParameter = 0x414E494D, + + /// + /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. + /// + Animation = 0x414E4D46, + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index f07a79814..d1c451799 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -58,106 +58,5 @@ namespace SixLabors.ImageSharp.Formats.WebP 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 - 0x58, // 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 - }; - - /// - /// Chunk contains information about the alpha channel. - /// - public static readonly byte[] AlphaHeader = - { - 0x41, // A - 0x4C, // L - 0x50, // P - 0x48, // H - }; - - /// - /// Chunk which contains a color profile. - /// - public static readonly byte[] IccpHeader = - { - 0x49, // I - 0x43, // C - 0x43, // C - 0x50, // P - }; - - /// - /// Chunk which contains EXIF metadata about the image. - /// - public static readonly byte[] ExifHeader = - { - 0x45, // E - 0x58, // X - 0x49, // I - 0x46, // F - }; - - /// - /// Chunk contains XMP metadata about the image. - /// - public static readonly byte[] XmpHeader = - { - 0x58, // X - 0x4D, // M - 0x50, // P - 0x20, // Space - }; - - /// - /// For an animated image, this chunk contains the global parameters of the animation. - /// - public static readonly byte[] AnimationParameterHeader = - { - 0x41, // A - 0x4E, // N - 0x49, // I - 0x4D, // M - }; - - /// - /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. - /// - public static readonly byte[] AnimationHeader = - { - 0x41, // A - 0x4E, // N - 0x4D, // M - 0x46, // F - }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 4f574e7d7..f3b70aaec 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -4,7 +4,6 @@ using System; using System.Buffers.Binary; using System.IO; -using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -74,14 +73,15 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - ReadSimpleLossless(pixels, image.Width, image.Height); + ReadSimpleLossless(pixels, image.Width, image.Height, (int)imageInfo.DataSize); } else { - ReadSimpleLossy(pixels, image.Width, image.Height); + ReadSimpleLossy(pixels, image.Width, image.Height, (int)imageInfo.DataSize); } // TODO: there can be optional chunks after the image data, like EXIF, XMP etc. + this.ParseOptionalChunks(); return image; } @@ -123,22 +123,16 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Info(int vpxWidth = 0, int vpxHeight = 0) { - // Read VP8 chunk header. - this.currentStream.Read(this.buffer, 0, 4); - - if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8Header)) - { - return this.ReadVp8Header(vpxWidth, vpxHeight); - } + WebPChunkType type = this.ReadChunkType(); - if (this.buffer.AsSpan().SequenceEqual(WebPConstants.Vp8LHeader)) + switch (type) { - return this.ReadVp8LHeader(vpxWidth, vpxHeight); - } - - if (this.buffer.SequenceEqual(WebPConstants.Vp8XHeader)) - { - return this.ReadVp8XHeader(); + case WebPChunkType.Vp8: + return this.ReadVp8Header(vpxWidth, vpxHeight); + case WebPChunkType.Vp8L: + return this.ReadVp8LHeader(vpxWidth, vpxHeight); + case WebPChunkType.Vp8X: + return this.ReadVp8XHeader(); } WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); @@ -185,6 +179,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // TODO: optional chunks ICCP and ANIM can follow here. Ignoring them for now. + this.ParseOptionalChunks(); // A VP8 or VP8L chunk will follow here. return this.ReadVp8Info(width, height); @@ -324,16 +319,24 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } - private void ReadSimpleLossy(Buffer2D pixels, int width, int height) + private void ParseOptionalChunks() + { + // Read VP8 chunk header. + this.currentStream.Read(this.buffer, 0, 4); + } + + private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int chunkSize) where TPixel : struct, IPixel { - // TODO: implement decoding + // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. + this.currentStream.Skip(chunkSize); } - private void ReadSimpleLossless(Buffer2D pixels, int width, int height) + private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int chunkSize) where TPixel : struct, IPixel { - // TODO: implement decoding + // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. + this.currentStream.Skip(chunkSize); } private void ReadExtended(Buffer2D pixels, int width, int height) @@ -341,5 +344,18 @@ namespace SixLabors.ImageSharp.Formats.WebP { // TODO: implement decoding } + + /// + /// Identifies the chunk type from the chunk. + /// + /// + /// Thrown if the input stream is not valid. + /// + private WebPChunkType ReadChunkType() + { + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) + : throw new ImageFormatException("Invalid WebP data."); + } } } From 95f89503ced86f119f802557ef9ba4808d5b956b Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Mon, 21 Oct 2019 23:01:50 +0200 Subject: [PATCH 020/359] Some Lossy files added to WebPDecoder Identify test. Test is broken with new sample files. --- ImageSharp.sln | 183 +++++++++++++++--- .../Formats/WebP/WebPDecoderTests.cs | 3 + .../Formats/WebP/WebPMetaDataTests.cs | 11 +- tests/ImageSharp.Tests/TestImages.cs | 12 +- 4 files changed, 169 insertions(+), 40 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index 4ffb55399..df634863b 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -356,33 +356,159 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{7BEE9435-1833-4686-8B36-C4EE2F13D908}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lossy", "Lossy", "{41D10A70-59CE-4634-9145-CE9B60050371}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\WebP\Lossy\1.webp = tests\Images\Input\WebP\Lossy\1.webp - tests\Images\Input\WebP\Lossy\2.webp = tests\Images\Input\WebP\Lossy\2.webp - tests\Images\Input\WebP\Lossy\3.webp = tests\Images\Input\WebP\Lossy\3.webp - tests\Images\Input\WebP\Lossy\4.webp = tests\Images\Input\WebP\Lossy\4.webp - tests\Images\Input\WebP\Lossy\5.webp = tests\Images\Input\WebP\Lossy\5.webp - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lossless", "Lossless", "{F19E6F18-4102-4134-8A96-FEC2AB67F794}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\WebP\Lossless\1_webp_ll.webp = tests\Images\Input\WebP\Lossless\1_webp_ll.webp - tests\Images\Input\WebP\Lossless\2_webp_ll.webp = tests\Images\Input\WebP\Lossless\2_webp_ll.webp - tests\Images\Input\WebP\Lossless\3_webp_ll.webp = tests\Images\Input\WebP\Lossless\3_webp_ll.webp - tests\Images\Input\WebP\Lossless\4_webp_ll.webp = tests\Images\Input\WebP\Lossless\4_webp_ll.webp - tests\Images\Input\WebP\Lossless\5_webp_ll.webp = tests\Images\Input\WebP\Lossless\5_webp_ll.webp - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Alpha", "Alpha", "{E6B81E19-B27F-4656-9169-4B8415D064A2}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}" ProjectSection(SolutionItems) = preProject - tests\Images\Input\WebP\Lossy\Alpha\1_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\1_webp_a.webp - tests\Images\Input\WebP\Lossy\Alpha\2_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\2_webp_a.webp - tests\Images\Input\WebP\Lossy\Alpha\3_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\3_webp_a.webp - tests\Images\Input\WebP\Lossy\Alpha\4_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\4_webp_a.webp - tests\Images\Input\WebP\Lossy\Alpha\5_webp_a.webp = tests\Images\Input\WebP\Lossy\Alpha\5_webp_a.webp + tests\Images\Input\WebP\alpha_color_cache.webp = tests\Images\Input\WebP\alpha_color_cache.webp + tests\Images\Input\WebP\alpha_filter_0_method_0.webp = tests\Images\Input\WebP\alpha_filter_0_method_0.webp + tests\Images\Input\WebP\alpha_filter_0_method_1.webp = tests\Images\Input\WebP\alpha_filter_0_method_1.webp + tests\Images\Input\WebP\alpha_filter_1.webp = tests\Images\Input\WebP\alpha_filter_1.webp + tests\Images\Input\WebP\alpha_filter_1_method_0.webp = tests\Images\Input\WebP\alpha_filter_1_method_0.webp + tests\Images\Input\WebP\alpha_filter_1_method_1.webp = tests\Images\Input\WebP\alpha_filter_1_method_1.webp + tests\Images\Input\WebP\alpha_filter_2.webp = tests\Images\Input\WebP\alpha_filter_2.webp + tests\Images\Input\WebP\alpha_filter_2_method_0.webp = tests\Images\Input\WebP\alpha_filter_2_method_0.webp + tests\Images\Input\WebP\alpha_filter_2_method_1.webp = tests\Images\Input\WebP\alpha_filter_2_method_1.webp + tests\Images\Input\WebP\alpha_filter_3.webp = tests\Images\Input\WebP\alpha_filter_3.webp + tests\Images\Input\WebP\alpha_filter_3_method_0.webp = tests\Images\Input\WebP\alpha_filter_3_method_0.webp + tests\Images\Input\WebP\alpha_filter_3_method_1.webp = tests\Images\Input\WebP\alpha_filter_3_method_1.webp + tests\Images\Input\WebP\alpha_no_compression.webp = tests\Images\Input\WebP\alpha_no_compression.webp + tests\Images\Input\WebP\bad_palette_index.webp = tests\Images\Input\WebP\bad_palette_index.webp + tests\Images\Input\WebP\big_endian_bug_393.webp = tests\Images\Input\WebP\big_endian_bug_393.webp + tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp + tests\Images\Input\WebP\bug3.webp = tests\Images\Input\WebP\bug3.webp + tests\Images\Input\WebP\color_cache_bits_11.webp = tests\Images\Input\WebP\color_cache_bits_11.webp + tests\Images\Input\WebP\grid.bmp = tests\Images\Input\WebP\grid.bmp + tests\Images\Input\WebP\grid.pam = tests\Images\Input\WebP\grid.pam + tests\Images\Input\WebP\grid.pgm = tests\Images\Input\WebP\grid.pgm + tests\Images\Input\WebP\grid.png = tests\Images\Input\WebP\grid.png + tests\Images\Input\WebP\grid.ppm = tests\Images\Input\WebP\grid.ppm + tests\Images\Input\WebP\grid.tiff = tests\Images\Input\WebP\grid.tiff + tests\Images\Input\WebP\libwebp_tests.md5 = tests\Images\Input\WebP\libwebp_tests.md5 + tests\Images\Input\WebP\lossless1.webp = tests\Images\Input\WebP\lossless1.webp + tests\Images\Input\WebP\lossless2.webp = tests\Images\Input\WebP\lossless2.webp + tests\Images\Input\WebP\lossless3.webp = tests\Images\Input\WebP\lossless3.webp + tests\Images\Input\WebP\lossless4.webp = tests\Images\Input\WebP\lossless4.webp + tests\Images\Input\WebP\lossless_big_random_alpha.webp = tests\Images\Input\WebP\lossless_big_random_alpha.webp + tests\Images\Input\WebP\lossless_color_transform.bmp = tests\Images\Input\WebP\lossless_color_transform.bmp + tests\Images\Input\WebP\lossless_color_transform.pam = tests\Images\Input\WebP\lossless_color_transform.pam + tests\Images\Input\WebP\lossless_color_transform.pgm = tests\Images\Input\WebP\lossless_color_transform.pgm + tests\Images\Input\WebP\lossless_color_transform.ppm = tests\Images\Input\WebP\lossless_color_transform.ppm + tests\Images\Input\WebP\lossless_color_transform.tiff = tests\Images\Input\WebP\lossless_color_transform.tiff + tests\Images\Input\WebP\lossless_color_transform.webp = tests\Images\Input\WebP\lossless_color_transform.webp + tests\Images\Input\WebP\lossless_vec_1_0.webp = tests\Images\Input\WebP\lossless_vec_1_0.webp + tests\Images\Input\WebP\lossless_vec_1_1.webp = tests\Images\Input\WebP\lossless_vec_1_1.webp + tests\Images\Input\WebP\lossless_vec_1_10.webp = tests\Images\Input\WebP\lossless_vec_1_10.webp + tests\Images\Input\WebP\lossless_vec_1_11.webp = tests\Images\Input\WebP\lossless_vec_1_11.webp + tests\Images\Input\WebP\lossless_vec_1_12.webp = tests\Images\Input\WebP\lossless_vec_1_12.webp + tests\Images\Input\WebP\lossless_vec_1_13.webp = tests\Images\Input\WebP\lossless_vec_1_13.webp + tests\Images\Input\WebP\lossless_vec_1_14.webp = tests\Images\Input\WebP\lossless_vec_1_14.webp + tests\Images\Input\WebP\lossless_vec_1_15.webp = tests\Images\Input\WebP\lossless_vec_1_15.webp + tests\Images\Input\WebP\lossless_vec_1_2.webp = tests\Images\Input\WebP\lossless_vec_1_2.webp + tests\Images\Input\WebP\lossless_vec_1_3.webp = tests\Images\Input\WebP\lossless_vec_1_3.webp + tests\Images\Input\WebP\lossless_vec_1_4.webp = tests\Images\Input\WebP\lossless_vec_1_4.webp + tests\Images\Input\WebP\lossless_vec_1_5.webp = tests\Images\Input\WebP\lossless_vec_1_5.webp + tests\Images\Input\WebP\lossless_vec_1_6.webp = tests\Images\Input\WebP\lossless_vec_1_6.webp + tests\Images\Input\WebP\lossless_vec_1_7.webp = tests\Images\Input\WebP\lossless_vec_1_7.webp + tests\Images\Input\WebP\lossless_vec_1_8.webp = tests\Images\Input\WebP\lossless_vec_1_8.webp + tests\Images\Input\WebP\lossless_vec_1_9.webp = tests\Images\Input\WebP\lossless_vec_1_9.webp + tests\Images\Input\WebP\lossless_vec_2_0.webp = tests\Images\Input\WebP\lossless_vec_2_0.webp + tests\Images\Input\WebP\lossless_vec_2_1.webp = tests\Images\Input\WebP\lossless_vec_2_1.webp + tests\Images\Input\WebP\lossless_vec_2_10.webp = tests\Images\Input\WebP\lossless_vec_2_10.webp + tests\Images\Input\WebP\lossless_vec_2_11.webp = tests\Images\Input\WebP\lossless_vec_2_11.webp + tests\Images\Input\WebP\lossless_vec_2_12.webp = tests\Images\Input\WebP\lossless_vec_2_12.webp + tests\Images\Input\WebP\lossless_vec_2_13.webp = tests\Images\Input\WebP\lossless_vec_2_13.webp + tests\Images\Input\WebP\lossless_vec_2_14.webp = tests\Images\Input\WebP\lossless_vec_2_14.webp + tests\Images\Input\WebP\lossless_vec_2_15.webp = tests\Images\Input\WebP\lossless_vec_2_15.webp + tests\Images\Input\WebP\lossless_vec_2_2.webp = tests\Images\Input\WebP\lossless_vec_2_2.webp + tests\Images\Input\WebP\lossless_vec_2_3.webp = tests\Images\Input\WebP\lossless_vec_2_3.webp + tests\Images\Input\WebP\lossless_vec_2_4.webp = tests\Images\Input\WebP\lossless_vec_2_4.webp + tests\Images\Input\WebP\lossless_vec_2_5.webp = tests\Images\Input\WebP\lossless_vec_2_5.webp + tests\Images\Input\WebP\lossless_vec_2_6.webp = tests\Images\Input\WebP\lossless_vec_2_6.webp + tests\Images\Input\WebP\lossless_vec_2_7.webp = tests\Images\Input\WebP\lossless_vec_2_7.webp + tests\Images\Input\WebP\lossless_vec_2_8.webp = tests\Images\Input\WebP\lossless_vec_2_8.webp + tests\Images\Input\WebP\lossless_vec_2_9.webp = tests\Images\Input\WebP\lossless_vec_2_9.webp + tests\Images\Input\WebP\lossless_vec_list.txt = tests\Images\Input\WebP\lossless_vec_list.txt + tests\Images\Input\WebP\lossy_alpha1.webp = tests\Images\Input\WebP\lossy_alpha1.webp + tests\Images\Input\WebP\lossy_alpha2.webp = tests\Images\Input\WebP\lossy_alpha2.webp + tests\Images\Input\WebP\lossy_alpha3.webp = tests\Images\Input\WebP\lossy_alpha3.webp + tests\Images\Input\WebP\lossy_alpha4.webp = tests\Images\Input\WebP\lossy_alpha4.webp + tests\Images\Input\WebP\lossy_extreme_probabilities.webp = tests\Images\Input\WebP\lossy_extreme_probabilities.webp + tests\Images\Input\WebP\lossy_q0_f100.webp = tests\Images\Input\WebP\lossy_q0_f100.webp + tests\Images\Input\WebP\near_lossless_75.webp = tests\Images\Input\WebP\near_lossless_75.webp + tests\Images\Input\WebP\peak.bmp = tests\Images\Input\WebP\peak.bmp + tests\Images\Input\WebP\peak.pam = tests\Images\Input\WebP\peak.pam + tests\Images\Input\WebP\peak.pgm = tests\Images\Input\WebP\peak.pgm + tests\Images\Input\WebP\peak.png = tests\Images\Input\WebP\peak.png + tests\Images\Input\WebP\peak.ppm = tests\Images\Input\WebP\peak.ppm + tests\Images\Input\WebP\peak.tiff = tests\Images\Input\WebP\peak.tiff + tests\Images\Input\WebP\segment01.webp = tests\Images\Input\WebP\segment01.webp + tests\Images\Input\WebP\segment02.webp = tests\Images\Input\WebP\segment02.webp + tests\Images\Input\WebP\segment03.webp = tests\Images\Input\WebP\segment03.webp + tests\Images\Input\WebP\small_13x1.webp = tests\Images\Input\WebP\small_13x1.webp + tests\Images\Input\WebP\small_1x1.webp = tests\Images\Input\WebP\small_1x1.webp + tests\Images\Input\WebP\small_1x13.webp = tests\Images\Input\WebP\small_1x13.webp + tests\Images\Input\WebP\small_31x13.webp = tests\Images\Input\WebP\small_31x13.webp + tests\Images\Input\WebP\test-nostrong.webp = tests\Images\Input\WebP\test-nostrong.webp + tests\Images\Input\WebP\test.webp = tests\Images\Input\WebP\test.webp + tests\Images\Input\WebP\test_cwebp.sh = tests\Images\Input\WebP\test_cwebp.sh + tests\Images\Input\WebP\test_dwebp.sh = tests\Images\Input\WebP\test_dwebp.sh + tests\Images\Input\WebP\test_lossless.sh = tests\Images\Input\WebP\test_lossless.sh + tests\Images\Input\WebP\very_short.webp = tests\Images\Input\WebP\very_short.webp + tests\Images\Input\WebP\vp80-00-comprehensive-001.webp = tests\Images\Input\WebP\vp80-00-comprehensive-001.webp + tests\Images\Input\WebP\vp80-00-comprehensive-002.webp = tests\Images\Input\WebP\vp80-00-comprehensive-002.webp + tests\Images\Input\WebP\vp80-00-comprehensive-003.webp = tests\Images\Input\WebP\vp80-00-comprehensive-003.webp + tests\Images\Input\WebP\vp80-00-comprehensive-004.webp = tests\Images\Input\WebP\vp80-00-comprehensive-004.webp + tests\Images\Input\WebP\vp80-00-comprehensive-005.webp = tests\Images\Input\WebP\vp80-00-comprehensive-005.webp + tests\Images\Input\WebP\vp80-00-comprehensive-006.webp = tests\Images\Input\WebP\vp80-00-comprehensive-006.webp + tests\Images\Input\WebP\vp80-00-comprehensive-007.webp = tests\Images\Input\WebP\vp80-00-comprehensive-007.webp + tests\Images\Input\WebP\vp80-00-comprehensive-008.webp = tests\Images\Input\WebP\vp80-00-comprehensive-008.webp + tests\Images\Input\WebP\vp80-00-comprehensive-009.webp = tests\Images\Input\WebP\vp80-00-comprehensive-009.webp + tests\Images\Input\WebP\vp80-00-comprehensive-010.webp = tests\Images\Input\WebP\vp80-00-comprehensive-010.webp + tests\Images\Input\WebP\vp80-00-comprehensive-011.webp = tests\Images\Input\WebP\vp80-00-comprehensive-011.webp + tests\Images\Input\WebP\vp80-00-comprehensive-012.webp = tests\Images\Input\WebP\vp80-00-comprehensive-012.webp + tests\Images\Input\WebP\vp80-00-comprehensive-013.webp = tests\Images\Input\WebP\vp80-00-comprehensive-013.webp + tests\Images\Input\WebP\vp80-00-comprehensive-014.webp = tests\Images\Input\WebP\vp80-00-comprehensive-014.webp + tests\Images\Input\WebP\vp80-00-comprehensive-015.webp = tests\Images\Input\WebP\vp80-00-comprehensive-015.webp + tests\Images\Input\WebP\vp80-00-comprehensive-016.webp = tests\Images\Input\WebP\vp80-00-comprehensive-016.webp + tests\Images\Input\WebP\vp80-00-comprehensive-017.webp = tests\Images\Input\WebP\vp80-00-comprehensive-017.webp + tests\Images\Input\WebP\vp80-01-intra-1400.webp = tests\Images\Input\WebP\vp80-01-intra-1400.webp + tests\Images\Input\WebP\vp80-01-intra-1411.webp = tests\Images\Input\WebP\vp80-01-intra-1411.webp + tests\Images\Input\WebP\vp80-01-intra-1416.webp = tests\Images\Input\WebP\vp80-01-intra-1416.webp + tests\Images\Input\WebP\vp80-01-intra-1417.webp = tests\Images\Input\WebP\vp80-01-intra-1417.webp + tests\Images\Input\WebP\vp80-02-inter-1402.webp = tests\Images\Input\WebP\vp80-02-inter-1402.webp + tests\Images\Input\WebP\vp80-02-inter-1412.webp = tests\Images\Input\WebP\vp80-02-inter-1412.webp + tests\Images\Input\WebP\vp80-02-inter-1418.webp = tests\Images\Input\WebP\vp80-02-inter-1418.webp + tests\Images\Input\WebP\vp80-02-inter-1424.webp = tests\Images\Input\WebP\vp80-02-inter-1424.webp + tests\Images\Input\WebP\vp80-03-segmentation-1401.webp = tests\Images\Input\WebP\vp80-03-segmentation-1401.webp + tests\Images\Input\WebP\vp80-03-segmentation-1403.webp = tests\Images\Input\WebP\vp80-03-segmentation-1403.webp + tests\Images\Input\WebP\vp80-03-segmentation-1407.webp = tests\Images\Input\WebP\vp80-03-segmentation-1407.webp + tests\Images\Input\WebP\vp80-03-segmentation-1408.webp = tests\Images\Input\WebP\vp80-03-segmentation-1408.webp + tests\Images\Input\WebP\vp80-03-segmentation-1409.webp = tests\Images\Input\WebP\vp80-03-segmentation-1409.webp + tests\Images\Input\WebP\vp80-03-segmentation-1410.webp = tests\Images\Input\WebP\vp80-03-segmentation-1410.webp + tests\Images\Input\WebP\vp80-03-segmentation-1413.webp = tests\Images\Input\WebP\vp80-03-segmentation-1413.webp + tests\Images\Input\WebP\vp80-03-segmentation-1414.webp = tests\Images\Input\WebP\vp80-03-segmentation-1414.webp + tests\Images\Input\WebP\vp80-03-segmentation-1415.webp = tests\Images\Input\WebP\vp80-03-segmentation-1415.webp + tests\Images\Input\WebP\vp80-03-segmentation-1425.webp = tests\Images\Input\WebP\vp80-03-segmentation-1425.webp + tests\Images\Input\WebP\vp80-03-segmentation-1426.webp = tests\Images\Input\WebP\vp80-03-segmentation-1426.webp + tests\Images\Input\WebP\vp80-03-segmentation-1427.webp = tests\Images\Input\WebP\vp80-03-segmentation-1427.webp + tests\Images\Input\WebP\vp80-03-segmentation-1432.webp = tests\Images\Input\WebP\vp80-03-segmentation-1432.webp + tests\Images\Input\WebP\vp80-03-segmentation-1435.webp = tests\Images\Input\WebP\vp80-03-segmentation-1435.webp + tests\Images\Input\WebP\vp80-03-segmentation-1436.webp = tests\Images\Input\WebP\vp80-03-segmentation-1436.webp + tests\Images\Input\WebP\vp80-03-segmentation-1437.webp = tests\Images\Input\WebP\vp80-03-segmentation-1437.webp + tests\Images\Input\WebP\vp80-03-segmentation-1441.webp = tests\Images\Input\WebP\vp80-03-segmentation-1441.webp + tests\Images\Input\WebP\vp80-03-segmentation-1442.webp = tests\Images\Input\WebP\vp80-03-segmentation-1442.webp + tests\Images\Input\WebP\vp80-04-partitions-1404.webp = tests\Images\Input\WebP\vp80-04-partitions-1404.webp + tests\Images\Input\WebP\vp80-04-partitions-1405.webp = tests\Images\Input\WebP\vp80-04-partitions-1405.webp + tests\Images\Input\WebP\vp80-04-partitions-1406.webp = tests\Images\Input\WebP\vp80-04-partitions-1406.webp + tests\Images\Input\WebP\vp80-05-sharpness-1428.webp = tests\Images\Input\WebP\vp80-05-sharpness-1428.webp + tests\Images\Input\WebP\vp80-05-sharpness-1429.webp = tests\Images\Input\WebP\vp80-05-sharpness-1429.webp + tests\Images\Input\WebP\vp80-05-sharpness-1430.webp = tests\Images\Input\WebP\vp80-05-sharpness-1430.webp + tests\Images\Input\WebP\vp80-05-sharpness-1431.webp = tests\Images\Input\WebP\vp80-05-sharpness-1431.webp + tests\Images\Input\WebP\vp80-05-sharpness-1433.webp = tests\Images\Input\WebP\vp80-05-sharpness-1433.webp + tests\Images\Input\WebP\vp80-05-sharpness-1434.webp = tests\Images\Input\WebP\vp80-05-sharpness-1434.webp + tests\Images\Input\WebP\vp80-05-sharpness-1438.webp = tests\Images\Input\WebP\vp80-05-sharpness-1438.webp + tests\Images\Input\WebP\vp80-05-sharpness-1439.webp = tests\Images\Input\WebP\vp80-05-sharpness-1439.webp + tests\Images\Input\WebP\vp80-05-sharpness-1440.webp = tests\Images\Input\WebP\vp80-05-sharpness-1440.webp + tests\Images\Input\WebP\vp80-05-sharpness-1443.webp = tests\Images\Input\WebP\vp80-05-sharpness-1443.webp EndProjectSection EndProject Global @@ -482,10 +608,7 @@ Global {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {7BEE9435-1833-4686-8B36-C4EE2F13D908} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {41D10A70-59CE-4634-9145-CE9B60050371} = {7BEE9435-1833-4686-8B36-C4EE2F13D908} - {F19E6F18-4102-4134-8A96-FEC2AB67F794} = {7BEE9435-1833-4686-8B36-C4EE2F13D908} - {E6B81E19-B27F-4656-9169-4B8415D064A2} = {41D10A70-59CE-4634-9145-CE9B60050371} + {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 49e85319b..d9c65cc2e 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -24,6 +24,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [InlineData(Lossless.Lossless1, 1000, 307)] + [InlineData(Lossless.Lossless2, 1000, 307)] + [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] + [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index d1b130102..44163da17 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP { + using static SixLabors.ImageSharp.Tests.TestImages.WebP; using static TestImages.Bmp; public class WebPMetaDataTests @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Fact] public void CloneIsDeep() { - /* TODO: + /*TODO: var meta = new WebPMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; var clone = (WebPMetadata)meta.DeepClone(); @@ -28,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [InlineData(TestImages.WebP.Lossy.SampleWebpOne, BmpInfoHeaderType.WinVersion2)] + [InlineData(Lossless.Lossless1, BmpInfoHeaderType.WinVersion2)] public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) { var testFile = TestFile.Create(imagePath); @@ -36,9 +37,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); - WebPMetadata webpMetaData = imageInfo.Metadata.GetFormatMetadata(WebPFormat.Instance); - Assert.NotNull(webpMetaData); - //TODO: + Assert.Equal(24, imageInfo.PixelType.BitsPerPixel); + //var webpMetaData = imageInfo.Metadata.GetFormatMetadata(WebPFormat.Instance); + //Assert.NotNull(webpMetaData); //Assert.Equal(expectedInfoHeaderType, webpMetaData.InfoHeaderType); } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6f310f470..77c58758b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -372,6 +372,9 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { public const string Lossless1 = "WebP/lossless1.webp"; + public const string Lossless2 = "WebP/lossles2.webp"; + public const string Lossless3 = "WebP/lossless3.webp"; + public const string Lossless4 = "WebP/lossless4.webp"; } public static class Lossy @@ -384,11 +387,10 @@ namespace SixLabors.ImageSharp.Tests public static class Alpha { - public const string SampleWebpOne = "WebP/Lossy/Alpha/1_webp_a.webp"; - public const string SampleWebpTwo = "WebP/Lossy/Alpha/2_webp_a.webp"; - public const string SampleWebpThree = "WebP/Lossy/Alpha/3_webp_a.webp"; - public const string SampleWebpFour = "WebP/Lossy/Alpha/4_webp_a.webp"; - public const string SampleWebpFive = "WebP/Lossy/Alpha/5_webp_a.webp"; + public const string LossyAlpha1 = "WebP/lossy_alpha1.webp"; + public const string LossyAlpha2 = "WebP/lossy_alpha2.webp"; + public const string LossyAlpha3 = "WebP/lossy_alpha3.webp"; + public const string LossyAlpha4 = "WebP/lossy_alpha4.webp"; } } From a7965898cf92ce25d1cbe0e77b0f1022ccf21291 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Mon, 21 Oct 2019 23:18:23 +0200 Subject: [PATCH 021/359] Animated webp sample added to Identify test --- ImageSharp.sln | 1 + tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 9 +++++---- tests/ImageSharp.Tests/TestImages.cs | 6 +++++- tests/Images/Input/WebP/animated-webp.webp | 3 +++ 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 tests/Images/Input/WebP/animated-webp.webp diff --git a/ImageSharp.sln b/ImageSharp.sln index df634863b..d6982ee25 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -371,6 +371,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\alpha_filter_3_method_0.webp = tests\Images\Input\WebP\alpha_filter_3_method_0.webp tests\Images\Input\WebP\alpha_filter_3_method_1.webp = tests\Images\Input\WebP\alpha_filter_3_method_1.webp tests\Images\Input\WebP\alpha_no_compression.webp = tests\Images\Input\WebP\alpha_no_compression.webp + tests\Images\Input\WebP\animated-webp.webp = tests\Images\Input\WebP\animated-webp.webp tests\Images\Input\WebP\bad_palette_index.webp = tests\Images\Input\WebP\bad_palette_index.webp tests\Images\Input\WebP\big_endian_bug_393.webp = tests\Images\Input\WebP\big_endian_bug_393.webp tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index d9c65cc2e..e61c3c3c6 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -23,10 +23,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP }; [Theory] - [InlineData(Lossless.Lossless1, 1000, 307)] - [InlineData(Lossless.Lossless2, 1000, 307)] - [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] - [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] + //[InlineData(Lossless.Lossless1, 1000, 307)] + //[InlineData(Lossless.Lossless2, 1000, 307)] + //[InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] + //[InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] + [InlineData(Animated.Animated1, 400, 400)] public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 77c58758b..cde3d37a5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -368,7 +368,11 @@ namespace SixLabors.ImageSharp.Tests public static class WebP { - //TODO: actualize it with fresh sample images + public static class Animated + { + public const string Animated1 = "WebP/animated-webp.webp"; + } + public static class Lossless { public const string Lossless1 = "WebP/lossless1.webp"; diff --git a/tests/Images/Input/WebP/animated-webp.webp b/tests/Images/Input/WebP/animated-webp.webp new file mode 100644 index 000000000..d221bc0ca --- /dev/null +++ b/tests/Images/Input/WebP/animated-webp.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf633cfad0fba9b53ef84f0319db15537868bbe75c7b3cd0f31add9c0d25addf +size 37341 From c113c8ea8c75ce6ff7691fed3fc161ea36040a87 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 22 Oct 2019 20:07:07 +0200 Subject: [PATCH 022/359] Skipping over optional chunks with VP8X images (still does not seem to work) --- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 5 ++ .../Formats/WebP/WebPDecoderCore.cs | 65 +++++++++++++++---- .../Formats/WebP/WebPDecoderTests.cs | 8 +-- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index ba7fb9fd6..85ee888bf 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -52,5 +52,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. /// Animation = 0x414E4D46, + + /// + /// TODO: not sure what this is for yet. + /// + FRGM = 0x4652474D, } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f3b70aaec..896034664 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -112,8 +112,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Read Chunk size. // The size of the file in bytes starting at offset 8. // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. - this.currentStream.Read(this.buffer, 0, 4); - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint chunkSize = this.ReadChunkSize(); // Skip 'WEBP' from the header. this.currentStream.Skip(4); @@ -123,9 +122,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Info(int vpxWidth = 0, int vpxHeight = 0) { - WebPChunkType type = this.ReadChunkType(); + WebPChunkType chunkType = this.ReadChunkType(); - switch (type) + switch (chunkType) { case WebPChunkType.Vp8: return this.ReadVp8Header(vpxWidth, vpxHeight); @@ -142,8 +141,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8XHeader() { - this.currentStream.Read(this.buffer, 0, 4); - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint chunkSize = this.ReadChunkSize(); // This byte contains information about the image features used. // The first two bit should and the last bit should be 0. @@ -163,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool isXmpPresent = (imageFeatures & (1 << 2)) != 0; // If bit 7 is set, animation should be present. - bool isAnimationPresent = (imageFeatures & (1 << 7)) != 0; + bool isAnimationPresent = (imageFeatures & (1 << 1)) != 0; // 3 reserved bytes should follow which are supposed to be zero. this.currentStream.Read(this.buffer, 0, 3); @@ -178,8 +176,37 @@ namespace SixLabors.ImageSharp.Formats.WebP this.buffer[3] = 0; int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; - // TODO: optional chunks ICCP and ANIM can follow here. Ignoring them for now. - this.ParseOptionalChunks(); + // Optional chunks ALPH, ICCP and ANIM can follow here. Ignoring them for now. + if (isIccPresent) + { + WebPChunkType chunkType = this.ReadChunkType(); + uint iccpChunkSize = this.ReadChunkSize(); + this.currentStream.Skip((int)iccpChunkSize); + } + + if (isAnimationPresent) + { + // ANIM chunk will be followed by n ANMF chunks + WebPChunkType chunkType = this.ReadChunkType(); + uint animationParameterChunkSize = this.ReadChunkSize(); + this.currentStream.Skip((int)animationParameterChunkSize); + chunkType = this.ReadChunkType(); + while (chunkType == WebPChunkType.Animation) + { + uint animationChunkSize = this.ReadChunkSize(); + this.currentStream.Skip((int)animationChunkSize); + chunkType = this.ReadChunkType(); + } + + // TODO: there seems to follow something here after the last ANMF, im not sure yet how to parse. + } + + if (isAlphaPresent) + { + WebPChunkType chunkType = this.ReadChunkType(); + uint alphaChunkSize = this.ReadChunkSize(); + this.currentStream.Skip((int)alphaChunkSize); + } // A VP8 or VP8L chunk will follow here. return this.ReadVp8Info(width, height); @@ -232,8 +259,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8LHeader(int vpxWidth = 0, int vpxHeight = 0) { // VP8 data size. - this.currentStream.Read(this.buffer, 0, 4); - uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint dataSize = this.ReadChunkSize(); // One byte signature, should be 0x2f. byte signature = (byte)this.currentStream.ReadByte(); @@ -322,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ParseOptionalChunks() { // Read VP8 chunk header. - this.currentStream.Read(this.buffer, 0, 4); + // WebPChunkType chunkType = this.ReadChunkType(); } private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int chunkSize) @@ -330,6 +356,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. this.currentStream.Skip(chunkSize); + + this.ParseOptionalChunks(); } private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int chunkSize) @@ -337,6 +365,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. this.currentStream.Skip(chunkSize); + + this.ParseOptionalChunks(); } private void ReadExtended(Buffer2D pixels, int width, int height) @@ -357,5 +387,16 @@ namespace SixLabors.ImageSharp.Formats.WebP ? (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) : throw new ImageFormatException("Invalid WebP data."); } + + /// + /// Reads the chunk size. + /// + /// The chunk size in bytes. + private uint ReadChunkSize() + { + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? BinaryPrimitives.ReadUInt32LittleEndian(this.buffer) + : throw new ImageFormatException("Invalid WebP data."); + } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index e61c3c3c6..d75d30d91 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -23,10 +23,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP }; [Theory] - //[InlineData(Lossless.Lossless1, 1000, 307)] - //[InlineData(Lossless.Lossless2, 1000, 307)] - //[InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] - //[InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] + [InlineData(Lossless.Lossless1, 1000, 307)] + [InlineData(Lossless.Lossless2, 1000, 307)] + [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] + [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] [InlineData(Animated.Animated1, 400, 400)] public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight) { From b86d4d0f735d1d9e2c41d176be6a655055ff6ba1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 22 Oct 2019 20:47:07 +0200 Subject: [PATCH 023/359] Fix mistake with parsing VP8X header --- .../Formats/WebP/WebPDecoderCore.cs | 24 ++++++++++--------- tests/ImageSharp.Tests/TestImages.cs | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 896034664..a064ca326 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -120,16 +120,16 @@ namespace SixLabors.ImageSharp.Formats.WebP return chunkSize; } - private WebPImageInfo ReadVp8Info(int vpxWidth = 0, int vpxHeight = 0) + private WebPImageInfo ReadVp8Info() { WebPChunkType chunkType = this.ReadChunkType(); switch (chunkType) { case WebPChunkType.Vp8: - return this.ReadVp8Header(vpxWidth, vpxHeight); + return this.ReadVp8Header(); case WebPChunkType.Vp8L: - return this.ReadVp8LHeader(vpxWidth, vpxHeight); + return this.ReadVp8LHeader(); case WebPChunkType.Vp8X: return this.ReadVp8XHeader(); } @@ -208,11 +208,16 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Skip((int)alphaChunkSize); } - // A VP8 or VP8L chunk will follow here. - return this.ReadVp8Info(width, height); + return new WebPImageInfo() + { + Width = width, + Height = height, + IsLossLess = false, + DataSize = chunkSize + }; } - private WebPImageInfo ReadVp8Header(int vpxWidth = 0, int vpxHeight = 0) + private WebPImageInfo ReadVp8Header() { // VP8 data size. this.currentStream.Read(this.buffer, 0, 3); @@ -244,13 +249,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; - // Use the width and height from the VP8X information, if its provided, because its 3 bytes instead of 14 bits. - bool isVpxDimensionsPresent = vpxHeight != 0 || vpxWidth != 0; - return new WebPImageInfo() { - Width = isVpxDimensionsPresent ? vpxWidth : width, - Height = isVpxDimensionsPresent ? vpxHeight : height, + Width = width, + Height = height, IsLossLess = false, DataSize = dataSize }; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index cde3d37a5..541bc0063 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -376,7 +376,7 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { public const string Lossless1 = "WebP/lossless1.webp"; - public const string Lossless2 = "WebP/lossles2.webp"; + public const string Lossless2 = "WebP/lossless2.webp"; public const string Lossless3 = "WebP/lossless3.webp"; public const string Lossless4 = "WebP/lossless4.webp"; } From 375d332b65af2372ac903aec209e924f0be939ae Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Wed, 23 Oct 2019 12:05:00 +0200 Subject: [PATCH 024/359] BitsPerPixel Assert added to webp Identify_DetectsCorrectDimensions test --- .../Formats/WebP/WebPDecoderTests.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index d75d30d91..c0636cb19 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -8,27 +8,17 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP { - using SixLabors.ImageSharp.Metadata; using static SixLabors.ImageSharp.Tests.TestImages.WebP; - using static TestImages.Bmp; public class WebPDecoderTests { - public static readonly TheoryData RatioFiles = - new TheoryData - { - { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } - }; - [Theory] - [InlineData(Lossless.Lossless1, 1000, 307)] - [InlineData(Lossless.Lossless2, 1000, 307)] - [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307)] - [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307)] - [InlineData(Animated.Animated1, 400, 400)] - public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight) + [InlineData(Lossless.Lossless1, 1000, 307, 24)] + [InlineData(Lossless.Lossless2, 1000, 307, 24)] + [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] + [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 24)] + [InlineData(Animated.Animated1, 400, 400, 24)] + public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -37,6 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.NotNull(imageInfo); Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedHeight, imageInfo.Height); + Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); } } } From 506967a86e2a5dfd0c1ff0efb8775cdcc5b64f45 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 23 Oct 2019 19:20:57 +0200 Subject: [PATCH 025/359] Initialize metadata --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index a064ca326..301b71076 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -92,8 +92,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The containing image data. public IImageInfo Identify(Stream stream) { - var metadata = new ImageMetadata(); - WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); this.currentStream = stream; this.ReadImageHeader(); @@ -122,6 +120,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Info() { + var metadata = new ImageMetadata(); + WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); + this.metadata = new ImageMetadata(); + WebPChunkType chunkType = this.ReadChunkType(); switch (chunkType) From d88ba617e1a7c0ab086e8059da0e5c664c99916f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 23 Oct 2019 21:43:17 +0200 Subject: [PATCH 026/359] Add parsing optional chunks at the end --- .../Formats/WebP/WebPDecoderCore.cs | 100 ++++++++++-------- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 5 +- .../Formats/WebP/WebPDecoderTests.cs | 2 +- 3 files changed, 62 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 301b71076..aa5117159 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -66,21 +66,21 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); this.currentStream = stream; - uint chunkSize = this.ReadImageHeader(); + uint fileSize = this.ReadImageHeader(); WebPImageInfo imageInfo = this.ReadVp8Info(); var image = new Image(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - ReadSimpleLossless(pixels, image.Width, image.Height, (int)imageInfo.DataSize); + ReadSimpleLossless(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); } else { - ReadSimpleLossy(pixels, image.Width, image.Height, (int)imageInfo.DataSize); + ReadSimpleLossy(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); } - // TODO: there can be optional chunks after the image data, like EXIF, XMP etc. + // There can be optional chunks after the image data, like EXIF, XMP etc. this.ParseOptionalChunks(); return image; @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Skip FourCC header, we already know its a RIFF file at this point. this.currentStream.Skip(4); - // Read Chunk size. + // Read file size. // The size of the file in bytes starting at offset 8. // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. uint chunkSize = this.ReadChunkSize(); @@ -179,9 +179,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // Optional chunks ALPH, ICCP and ANIM can follow here. Ignoring them for now. + WebPChunkType chunkType; if (isIccPresent) { - WebPChunkType chunkType = this.ReadChunkType(); + chunkType = this.ReadChunkType(); uint iccpChunkSize = this.ReadChunkSize(); this.currentStream.Skip((int)iccpChunkSize); } @@ -189,34 +190,42 @@ namespace SixLabors.ImageSharp.Formats.WebP if (isAnimationPresent) { // ANIM chunk will be followed by n ANMF chunks - WebPChunkType chunkType = this.ReadChunkType(); + chunkType = this.ReadChunkType(); uint animationParameterChunkSize = this.ReadChunkSize(); this.currentStream.Skip((int)animationParameterChunkSize); chunkType = this.ReadChunkType(); + + // TODO: not sure yet how to determine how many animation chunks there will be. while (chunkType == WebPChunkType.Animation) { uint animationChunkSize = this.ReadChunkSize(); this.currentStream.Skip((int)animationChunkSize); chunkType = this.ReadChunkType(); } - - // TODO: there seems to follow something here after the last ANMF, im not sure yet how to parse. } if (isAlphaPresent) { - WebPChunkType chunkType = this.ReadChunkType(); + chunkType = this.ReadChunkType(); uint alphaChunkSize = this.ReadChunkSize(); this.currentStream.Skip((int)alphaChunkSize); } - return new WebPImageInfo() - { - Width = width, - Height = height, - IsLossLess = false, - DataSize = chunkSize - }; + // A VP8 or VP8L chunk should follow here. + chunkType = this.ReadChunkType(); + + // TOOD: image width and height from VP8X should overrule VP8 or VP8L info, because its 3 bytes instead of just 14 bit. + switch (chunkType) + { + case WebPChunkType.Vp8: + return this.ReadVp8Header(); + case WebPChunkType.Vp8L: + return this.ReadVp8LHeader(); + } + + WebPThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + + return new WebPImageInfo(); } private WebPImageInfo ReadVp8Header() @@ -256,11 +265,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Width = width, Height = height, IsLossLess = false, - DataSize = dataSize + ImageDataSize = dataSize }; } - private WebPImageInfo ReadVp8LHeader(int vpxWidth = 0, int vpxHeight = 0) + private WebPImageInfo ReadVp8LHeader() { // VP8 data size. uint dataSize = this.ReadChunkSize(); @@ -337,40 +346,27 @@ namespace SixLabors.ImageSharp.Formats.WebP transformPresent = bitReader.ReadBit(); } - // Use the width and height from the VP8X information, if its provided, because its 3 bytes instead of 14 bits. - bool isVpxDimensionsPresent = vpxHeight != 0 || vpxWidth != 0; - return new WebPImageInfo() { - Width = isVpxDimensionsPresent ? vpxWidth : (int)width, - Height = isVpxDimensionsPresent ? vpxHeight : (int)height, + Width = (int)width, + Height = (int)height, IsLossLess = true, - DataSize = dataSize + ImageDataSize = dataSize }; } - private void ParseOptionalChunks() - { - // Read VP8 chunk header. - // WebPChunkType chunkType = this.ReadChunkType(); - } - - private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int chunkSize) + private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. - this.currentStream.Skip(chunkSize); - - this.ParseOptionalChunks(); + this.currentStream.Skip(imageDataSize - 10); // 10 bytes because of VP8X header. } - private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int chunkSize) + private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. - this.currentStream.Skip(chunkSize); - - this.ParseOptionalChunks(); + this.currentStream.Skip(imageDataSize - 10); // 10 bytes because of VP8X header. } private void ReadExtended(Buffer2D pixels, int width, int height) @@ -379,6 +375,19 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: implement decoding } + private void ParseOptionalChunks() + { + while (this.currentStream.Position < this.currentStream.Length) + { + // Read chunk header. + WebPChunkType chunkType = this.ReadChunkType(); + uint chunkLength = this.ReadChunkSize(); + + // Skip chunk data for now. + this.currentStream.Skip((int)chunkLength); + } + } + /// /// Identifies the chunk type from the chunk. /// @@ -393,14 +402,19 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reads the chunk size. + /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, + /// so the chunk size will be increased by 1 in those cases. /// /// The chunk size in bytes. private uint ReadChunkSize() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? BinaryPrimitives.ReadUInt32LittleEndian(this.buffer) - : throw new ImageFormatException("Invalid WebP data."); + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; + } + + throw new ImageFormatException("Invalid WebP data."); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 49aa31f5e..10b6aa182 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -20,6 +20,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool IsLossLess { get; set; } - public uint DataSize { get; set; } + /// + /// The bytes of the image payload. + /// + public uint ImageDataSize { get; set; } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index c0636cb19..f12ba3231 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [InlineData(Lossless.Lossless2, 1000, 307, 24)] [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 24)] - [InlineData(Animated.Animated1, 400, 400, 24)] + //[InlineData(Animated.Animated1, 400, 400, 24)] public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); From 857002ae810d55d7630fe9af00a025c3d42f511c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 24 Oct 2019 19:14:23 +0200 Subject: [PATCH 027/359] Add to the metadata, if the image is lossy or lossless and also if a animation is present --- .../Formats/WebP/WebPDecoderCore.cs | 14 +++++++-- src/ImageSharp/Formats/WebP/WebPFormatType.cs | 26 ++++++++++++++++ src/ImageSharp/Formats/WebP/WebPMetadata.cs | 31 +++++++++++++++++-- 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPFormatType.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index aa5117159..5d03db558 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private ImageMetadata metadata; + /// + /// The webp specific metadata. + /// + private WebPMetadata webpMetadata; + /// /// Initializes a new instance of the class. /// @@ -120,9 +125,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Info() { - var metadata = new ImageMetadata(); - WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); this.metadata = new ImageMetadata(); + this.webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); WebPChunkType chunkType = this.ReadChunkType(); @@ -189,6 +193,8 @@ namespace SixLabors.ImageSharp.Formats.WebP if (isAnimationPresent) { + this.webpMetadata.Animated = true; + // ANIM chunk will be followed by n ANMF chunks chunkType = this.ReadChunkType(); uint animationParameterChunkSize = this.ReadChunkSize(); @@ -230,6 +236,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Header() { + this.webpMetadata.Format = WebPFormatType.Lossy; + // VP8 data size. this.currentStream.Read(this.buffer, 0, 3); this.buffer[3] = 0; @@ -271,6 +279,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8LHeader() { + this.webpMetadata.Format = WebPFormatType.Lossless; + // VP8 data size. uint dataSize = this.ReadChunkSize(); diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs new file mode 100644 index 000000000..291281d00 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Info about the webp format used. + /// + public enum WebPFormatType + { + /// + /// Unknown webp format. + /// + Unknown, + + /// + /// The lossless webp format. + /// + Lossless, + + /// + /// The lossy webp format. + /// + Lossy, + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 95f50fe27..88c687827 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; - namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -10,7 +8,34 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public class WebPMetadata : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public WebPMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private WebPMetadata(WebPMetadata other) + { + this.Animated = other.Animated; + this.Format = other.Format; + } + /// - public IDeepCloneable DeepClone() => throw new NotImplementedException(); + public IDeepCloneable DeepClone() => new WebPMetadata(this); + + /// + /// The webp format used. Either lossless or lossy. + /// + public WebPFormatType Format { get; set; } + + /// + /// Indicates, if the webp file contains a animation. + /// + public bool Animated { get; set; } = false; } } From 1de13422ed39f6d1538e220635c1fe2fb585af11 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 24 Oct 2019 20:00:45 +0200 Subject: [PATCH 028/359] Throwing not supported exception for animated images --- .../Formats/WebP/WebPDecoderCore.cs | 23 ++++++++----------- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 5 ++++ .../Formats/WebP/WebPThrowHelper.cs | 11 +++++++++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 5d03db558..c04a3f242 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -73,6 +73,10 @@ namespace SixLabors.ImageSharp.Formats.WebP uint fileSize = this.ReadImageHeader(); WebPImageInfo imageInfo = this.ReadVp8Info(); + if (imageInfo.IsAnimation) + { + WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); + } var image = new Image(this.configuration, imageInfo.Width, imageInfo.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -195,19 +199,12 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.webpMetadata.Animated = true; - // ANIM chunk will be followed by n ANMF chunks - chunkType = this.ReadChunkType(); - uint animationParameterChunkSize = this.ReadChunkSize(); - this.currentStream.Skip((int)animationParameterChunkSize); - chunkType = this.ReadChunkType(); - - // TODO: not sure yet how to determine how many animation chunks there will be. - while (chunkType == WebPChunkType.Animation) - { - uint animationChunkSize = this.ReadChunkSize(); - this.currentStream.Skip((int)animationChunkSize); - chunkType = this.ReadChunkType(); - } + return new WebPImageInfo() + { + Width = width, + Height = height, + IsAnimation = true + }; } if (isAlphaPresent) diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 10b6aa182..b03244dc7 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -20,6 +20,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool IsLossLess { get; set; } + /// + /// Gets or sets whether this image is a animation. + /// + public bool IsAnimation { get; set; } + /// /// The bytes of the image payload. /// diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs index fabbc9bc3..7cc4df246 100644 --- a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.WebP @@ -16,5 +17,15 @@ namespace SixLabors.ImageSharp.Formats.WebP { throw new ImageFormatException(errorMessage); } + + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string errorMessage) + { + throw new NotSupportedException(errorMessage); + } } } From 4cd0ce4e748912b5a81c05b1c652a835dd061af2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 24 Oct 2019 20:18:39 +0200 Subject: [PATCH 029/359] Add all found chunk types to the metadata Note: this does not seem to work in all cases at the moment --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 5 +--- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 2 +- .../Formats/WebP/WebPDecoderCore.cs | 26 +++++++++---------- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 8 ++++++ .../Formats/WebP/WebPDecoderTests.cs | 2 +- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 036f441d0..b801a4c33 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -18,10 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The input stream to read from. public Vp8LBitReader(Stream inputStream) { - this.stream = new MemoryStream(); - long inputStreamPos = inputStream.Position; - inputStream.CopyTo(this.stream); - inputStream.Position = inputStreamPos; + this.stream = inputStream; this.Offset = 0; this.Bit = 0; } diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index 85ee888bf..9af98a394 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Contains a list of different webp chunk types. /// - internal enum WebPChunkType : uint + public enum WebPChunkType : uint { /// internal class Vp8Decoder { - public Vp8Decoder() + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8FilterHeader filterHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, Vp8Io io) { + this.FrameHeader = frameHeader; + this.PictureHeader = pictureHeader; + this.FilterHeader = filterHeader; + this.SegmentHeader = segmentHeader; + this.Probabilities = probabilities; this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; + this.IntraL = new byte[4]; + + this.Init(io); } + public Vp8FrameHeader FrameHeader { get; } + + public Vp8PictureHeader PictureHeader { get; } + + public Vp8FilterHeader FilterHeader { get; } + + public Vp8SegmentHeader SegmentHeader { get; } + + public bool Dither { get; set; } + + /// + /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). + /// + public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } + + public bool UseSkipProba { get; set; } + + public byte SkipProbability { get; set; } + + public Vp8Proba Probabilities { get; set; } + + // top intra modes values: 4 * MbWidth + public byte[] IntraT { get; set; } + + // left intra modes values + public byte[] IntraL { get; } + + /// + /// Gets or sets the width in macroblock units. + /// + public int MbWidth { get; set; } + + /// + /// Gets or sets the height in macroblock units. + /// + public int MbHeight { get; set; } + + /// + /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbX { get; set; } + + /// + /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbY { get; set; } + + /// + /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. + /// + public int BotomRightMbX { get; set; } + + /// + /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. + /// + public int BottomRightMbY { get; set; } + + /// + /// Gets or sets the current x position in macroblock units. + /// + public int MbX { get; set; } + + /// + /// Gets or sets the current y position in macroblock units. + /// + public int MbY { get; set; } + + /// + /// Gets or sets the parsed reconstruction data. + /// + public Vp8MacroBlockData[] MacroBlockData { get; set; } + + /// + /// Gets or sets contextual macroblock infos. + /// + public Vp8MacroBlock[] MacroBlockInfo { get; set; } + + public int MacroBlockIdx { get; set; } + + public LoopFilter Filter { get; set; } + + public Vp8FilterInfo[,] FilterStrength { get; } + + /// + /// Gets or sets filter strength info. + /// + public Vp8FilterInfo FilterInfo { get; set; } + public void Init(Vp8Io io) { + this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); + this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); + int intraPredModeSize = 4 * this.MbWidth; + this.IntraT = new byte[intraPredModeSize]; + + io.Width = (int)this.PictureHeader.Width; + io.Height = (int)this.PictureHeader.Height; + io.UseCropping = false; + io.CropTop = 0; + io.CropLeft = 0; + io.CropRight = io.Width; + io.CropBottom = io.Height; + io.UseScaling = false; + io.ScaledWidth = io.Width; + io.ScaledHeight = io.ScaledHeight; + io.MbW = io.Width; + io.MbH = io.Height; + int extraPixels = WebPConstants.FilterExtraRows[(int)this.Filter]; if (this.Filter is LoopFilter.Complex) { @@ -128,94 +242,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - info.Limit = 0; // no filtering + info.Limit = 0; // no filtering. } info.InnerLevel = (byte)i4x4; } } } - - public Vp8FrameHeader FrameHeader { get; set; } - - public Vp8PictureHeader PictureHeader { get; set; } - - public Vp8FilterHeader FilterHeader { get; set; } - - public Vp8SegmentHeader SegmentHeader { get; set; } - - public bool Dither { get; set; } - - /// - /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). - /// - public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } - - public bool UseSkipProba { get; set; } - - public byte SkipProbability { get; set; } - - public Vp8Proba Probabilities { get; set; } - - /// - /// Gets or sets the width in macroblock units. - /// - public int MbWidth { get; set; } - - /// - /// Gets or sets the height in macroblock units. - /// - public int MbHeight { get; set; } - - /// - /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbX { get; set; } - - /// - /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbY { get; set; } - - /// - /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. - /// - public int BotomRightMbX { get; set; } - - /// - /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. - /// - public int BottomRightMbY { get; set; } - - /// - /// Gets or sets the current x position in macroblock units. - /// - public int MbX { get; set; } - - /// - /// Gets or sets the current y position in macroblock units. - /// - public int MbY { get; set; } - - /// - /// Gets or sets the parsed reconstruction data. - /// - public Vp8MacroBlockData[] MacroBlockData { get; set; } - - /// - /// Gets or sets contextual macroblock infos. - /// - public Vp8MacroBlock[] MacroBlockInfo { get; set; } - - public int MacroBlockPos { get; set; } - - public LoopFilter Filter { get; set; } - - public Vp8FilterInfo[,] FilterStrength { get; } - - /// - /// Gets or sets filter strength info. - /// - public Vp8FilterInfo FilterInfo { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index dfcf73a48..8ede28c08 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -62,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public int UvStride { get; set; } + public bool UseCropping { get; set; } + public int CropLeft { get; set; } public int CropRight { get; set; } @@ -70,6 +72,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public int CropBottom { get; set; } + public bool UseScaling { get; set; } + + public int ScaledWidth { get; set; } + + public int ScaledHeight { get; set; } + /// /// User data /// diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index 35d8803af..5dbeb2358 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8MacroBlockData { + public Vp8MacroBlockData() + { + this.Modes = new byte[16]; + } + /// /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4. /// @@ -21,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. /// - public byte Modes { get; set; } + public byte[] Modes { get; } /// /// Gets or sets the chroma prediction mode. diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 5db5a0bbc..755b387e1 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -121,6 +121,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int NumCtx = 3; + // intra prediction modes (TODO: maybe use an enum for this) + public const int DcPred = 0; + public const int TmPred = 1; + public const int VPred = 2; + public const int HPred = 3; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter: up to 2 luma samples are read and 1 is written. @@ -183,6 +189,124 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + public static readonly sbyte[] YModesIntra4 = + { + -0, 1, + -1, 2, + -2, 3, + 4, 6, + -3, 5, + -4, -5, + -6, 7, + -7, 8, + -8, -9 + }; + + // Paragraph 11.5 + public static readonly byte[,,] BModesProba = + { + { { 231, 120, 48, 89, 115, 113, 120, 152, 112 }, + { 152, 179, 64, 126, 170, 118, 46, 70, 95 }, + { 175, 69, 143, 80, 85, 82, 72, 155, 103 }, + { 56, 58, 10, 171, 218, 189, 17, 13, 152 }, + { 114, 26, 17, 163, 44, 195, 21, 10, 173 }, + { 121, 24, 80, 195, 26, 62, 44, 64, 85 }, + { 144, 71, 10, 38, 171, 213, 144, 34, 26 }, + { 170, 46, 55, 19, 136, 160, 33, 206, 71 }, + { 63, 20, 8, 114, 114, 208, 12, 9, 226 }, + { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, + { { 134, 183, 89, 137, 98, 101, 106, 165, 148 }, + { 72, 187, 100, 130, 157, 111, 32, 75, 80 }, + { 66, 102, 167, 99, 74, 62, 40, 234, 128 }, + { 41, 53, 9, 178, 241, 141, 26, 8, 107 }, + { 74, 43, 26, 146, 73, 166, 49, 23, 157 }, + { 65, 38, 105, 160, 51, 52, 31, 115, 128 }, + { 104, 79, 12, 27, 217, 255, 87, 17, 7 }, + { 87, 68, 71, 44, 114, 51, 15, 186, 23 }, + { 47, 41, 14, 110, 182, 183, 21, 17, 194 }, + { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, + { { 88, 88, 147, 150, 42, 46, 45, 196, 205 }, + { 43, 97, 183, 117, 85, 38, 35, 179, 61 }, + { 39, 53, 200, 87, 26, 21, 43, 232, 171 }, + { 56, 34, 51, 104, 114, 102, 29, 93, 77 }, + { 39, 28, 85, 171, 58, 165, 90, 98, 64 }, + { 34, 22, 116, 206, 23, 34, 43, 166, 73 }, + { 107, 54, 32, 26, 51, 1, 81, 43, 31 }, + { 68, 25, 106, 22, 64, 171, 36, 225, 114 }, + { 34, 19, 21, 102, 132, 188, 16, 76, 124 }, + { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, + { { 193, 101, 35, 159, 215, 111, 89, 46, 111 }, + { 60, 148, 31, 172, 219, 228, 21, 18, 111 }, + { 112, 113, 77, 85, 179, 255, 38, 120, 114 }, + { 40, 42, 1, 196, 245, 209, 10, 25, 109 }, + { 88, 43, 29, 140, 166, 213, 37, 43, 154 }, + { 61, 63, 30, 155, 67, 45, 68, 1, 209 }, + { 100, 80, 8, 43, 154, 1, 51, 26, 71 }, + { 142, 78, 78, 16, 255, 128, 34, 197, 171 }, + { 41, 40, 5, 102, 211, 183, 4, 1, 221 }, + { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, + { { 138, 31, 36, 171, 27, 166, 38, 44, 229 }, + { 67, 87, 58, 169, 82, 115, 26, 59, 179 }, + { 63, 59, 90, 180, 59, 166, 93, 73, 154 }, + { 40, 40, 21, 116, 143, 209, 34, 39, 175 }, + { 47, 15, 16, 183, 34, 223, 49, 45, 183 }, + { 46, 17, 33, 183, 6, 98, 15, 32, 183 }, + { 57, 46, 22, 24, 128, 1, 54, 17, 37 }, + { 65, 32, 73, 115, 28, 128, 23, 128, 205 }, + { 40, 3, 9, 115, 51, 192, 18, 6, 223 }, + { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, + { { 104, 55, 44, 218, 9, 54, 53, 130, 226 }, + { 64, 90, 70, 205, 40, 41, 23, 26, 57 }, + { 54, 57, 112, 184, 5, 41, 38, 166, 213 }, + { 30, 34, 26, 133, 152, 116, 10, 32, 134 }, + { 39, 19, 53, 221, 26, 114, 32, 73, 255 }, + { 31, 9, 65, 234, 2, 15, 1, 118, 73 }, + { 75, 32, 12, 51, 192, 255, 160, 43, 51 }, + { 88, 31, 35, 67, 102, 85, 55, 186, 85 }, + { 56, 21, 23, 111, 59, 205, 45, 37, 192 }, + { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, + { { 125, 98, 42, 88, 104, 85, 117, 175, 82 }, + { 95, 84, 53, 89, 128, 100, 113, 101, 45 }, + { 75, 79, 123, 47, 51, 128, 81, 171, 1 }, + { 57, 17, 5, 71, 102, 57, 53, 41, 49 }, + { 38, 33, 13, 121, 57, 73, 26, 1, 85 }, + { 41, 10, 67, 138, 77, 110, 90, 47, 114 }, + { 115, 21, 2, 10, 102, 255, 166, 23, 6 }, + { 101, 29, 16, 10, 85, 128, 101, 196, 26 }, + { 57, 18, 10, 102, 102, 213, 34, 20, 43 }, + { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, + { { 102, 61, 71, 37, 34, 53, 31, 243, 192 }, + { 69, 60, 71, 38, 73, 119, 28, 222, 37 }, + { 68, 45, 128, 34, 1, 47, 11, 245, 171 }, + { 62, 17, 19, 70, 146, 85, 55, 62, 70 }, + { 37, 43, 37, 154, 100, 163, 85, 160, 1 }, + { 63, 9, 92, 136, 28, 64, 32, 201, 85 }, + { 75, 15, 9, 9, 64, 255, 184, 119, 16 }, + { 86, 6, 28, 5, 64, 255, 25, 248, 1 }, + { 56, 8, 17, 132, 137, 255, 55, 116, 128 }, + { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, + { { 164, 50, 31, 137, 154, 133, 25, 35, 218 }, + { 51, 103, 44, 131, 131, 123, 31, 6, 158 }, + { 86, 40, 64, 135, 148, 224, 45, 183, 128 }, + { 22, 26, 17, 131, 240, 154, 14, 1, 209 }, + { 45, 16, 21, 91, 64, 222, 7, 1, 197 }, + { 56, 21, 39, 155, 60, 138, 23, 102, 213 }, + { 83, 12, 13, 54, 192, 255, 68, 47, 28 }, + { 85, 26, 85, 85, 128, 128, 32, 146, 171 }, + { 18, 11, 7, 63, 144, 171, 4, 4, 246 }, + { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, + { { 190, 80, 35, 99, 180, 80, 126, 54, 45 }, + { 85, 126, 47, 87, 176, 51, 41, 20, 32 }, + { 101, 75, 128, 139, 118, 146, 116, 128, 85 }, + { 56, 41, 15, 176, 236, 85, 37, 9, 62 }, + { 71, 30, 17, 119, 118, 255, 17, 18, 138 }, + { 101, 38, 60, 138, 55, 70, 43, 26, 142 }, + { 146, 36, 19, 30, 171, 255, 97, 27, 20 }, + { 138, 45, 61, 62, 219, 1, 81, 188, 64 }, + { 32, 41, 20, 117, 151, 142, 20, 21, 163 }, + { 112, 19, 12, 61, 195, 128, 48, 4, 24 } } + }; + // Paragraph 13 public static readonly byte[,,,] CoeffsUpdateProba = { diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 060ec0f0f..22a370592 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator); - lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo.Vp8Profile); + lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo); } // There can be optional chunks after the image data, like EXIF and XMP. diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 364b4b3be..1c102a06b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitReader = bitReader; } - public void Decode(Buffer2D pixels, int width, int height, int vp8Version) + public void Decode(Buffer2D pixels, int width, int height, WebPImageInfo info) where TPixel : struct, IPixel { // we need buffers for Y U and V in size of the image @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // those prediction values are the base, the values from DCT processing are added to that // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V - Vp8Profile vp8Profile = this.DecodeProfile(vp8Version); + Vp8Profile vp8Profile = this.DecodeProfile(info.Vp8Profile); // Paragraph 9.3: Parse the segment header. var proba = new Vp8Proba(); @@ -57,6 +57,115 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 13.4: Parse probabilities. this.ParseProbabilities(proba); + + var vp8Io = default(Vp8Io); + var decoder = new Vp8Decoder(info.Vp8FrameHeader, info.Vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); + this.ParseFrame(decoder, vp8Io); + } + + private void ParseFrame(Vp8Decoder dec, Vp8Io io) + { + for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) + { + // Parse intra mode mode row. + for (int mbX = 0; mbX < dec.MbWidth; ++mbX) + { + this.ParseIntraMode(dec, mbX); + } + + for (; dec.MbX < dec.MbWidth; ++dec.MbX) + { + this.DecodeMacroBlock(dec); + } + + // Prepare for next scanline. + this.InitScanline(dec); + + // TODO: Reconstruct, filter and emit the row. + } + } + + private void InitScanline(Vp8Decoder dec) + { + Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; + left.NoneZeroAcDcCoeffs = 0; + left.NoneZeroDcCoeffs = 0; + for (int i = 0; i < dec.IntraL.Length; i++) + { + dec.IntraL[i] = 0; + } + + dec.MbX = 0; + } + + private void ParseIntraMode(Vp8Decoder dec, int mbX) + { + Vp8MacroBlockData block = dec.MacroBlockData[mbX]; + byte[] left = dec.IntraL; + byte[] top = dec.IntraT; + + if (dec.SegmentHeader.UpdateMap) + { + // Hardcoded tree parsing. + block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) != 0 + ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) + : (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[2]); + } + else + { + // default for intra + block.Segment = 0; + } + + if (dec.UseSkipProba) + { + block.Skip = (byte)this.bitReader.GetBit(dec.SkipProbability); + } + + block.IsI4x4 = this.bitReader.GetBit(145) != 0; + if (!block.IsI4x4) + { + // Hardcoded 16x16 intra-mode decision tree. + int yMode = this.bitReader.GetBit(156) > 0 ? + this.bitReader.GetBit(128) > 0 ? WebPConstants.TmPred : WebPConstants.HPred : + this.bitReader.GetBit(163) > 0 ? WebPConstants.VPred : WebPConstants.DcPred; + block.Modes[0] = (byte)yMode; + for (int i = 0; i < left.Length; i++) + { + left[i] = (byte)yMode; + top[i] = (byte)yMode; + } + } + else + { + byte[] modes = block.Modes; + for (int y = 0; y < 4; ++y) + { + int yMode = left[y]; + for (int x = 0; x < 4; ++x) + { + byte[] prob = null; //= WebPConstants.BModesProba[top[x], yMode]; + int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; + while (i > 0) + { + i = WebPConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; + } + + yMode = -i; + top[x] = (byte)yMode; + } + + // memcpy(modes, top, 4 * sizeof(*top)); + // modes += 4; + left[y] = (byte)yMode; + } + } + + // Hardcoded UVMode decision tree. + block.UvMode = (byte)(this.bitReader.GetBit(142) is 0 ? 0 : + this.bitReader.GetBit(114) is 0 ? 2 : + this.bitReader.GetBit(183) > 0 ? 1 : 3); + } private Vp8Profile DecodeProfile(int version) @@ -81,9 +190,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private void DecodeMacroBlock(Vp8Decoder dec) { - Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockPos - 1]; // TODO: not sure if this - 1 is correct here - Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockPos + dec.MbX]; - Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockPos + dec.MbX]; + Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; // TODO: not sure if this - 1 is correct here + Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockIdx + dec.MbX]; + Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockIdx + dec.MbX]; int skip = dec.UseSkipProba ? blockData.Skip : 0; if (skip is 0) From 4495eb7aeaafbc2d063ec2cfb4408d025dc6d893 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 3 Feb 2020 19:38:27 +0100 Subject: [PATCH 104/359] Finish ParseIntraMode (still untested) --- src/ImageSharp/Formats/WebP/WebPConstants.cs | 205 +++++++++--------- .../Formats/WebP/WebPLossyDecoder.cs | 8 +- 2 files changed, 106 insertions(+), 107 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 755b387e1..f06310d23 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -203,109 +203,108 @@ namespace SixLabors.ImageSharp.Formats.WebP }; // Paragraph 11.5 - public static readonly byte[,,] BModesProba = - { - { { 231, 120, 48, 89, 115, 113, 120, 152, 112 }, - { 152, 179, 64, 126, 170, 118, 46, 70, 95 }, - { 175, 69, 143, 80, 85, 82, 72, 155, 103 }, - { 56, 58, 10, 171, 218, 189, 17, 13, 152 }, - { 114, 26, 17, 163, 44, 195, 21, 10, 173 }, - { 121, 24, 80, 195, 26, 62, 44, 64, 85 }, - { 144, 71, 10, 38, 171, 213, 144, 34, 26 }, - { 170, 46, 55, 19, 136, 160, 33, 206, 71 }, - { 63, 20, 8, 114, 114, 208, 12, 9, 226 }, - { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, - { { 134, 183, 89, 137, 98, 101, 106, 165, 148 }, - { 72, 187, 100, 130, 157, 111, 32, 75, 80 }, - { 66, 102, 167, 99, 74, 62, 40, 234, 128 }, - { 41, 53, 9, 178, 241, 141, 26, 8, 107 }, - { 74, 43, 26, 146, 73, 166, 49, 23, 157 }, - { 65, 38, 105, 160, 51, 52, 31, 115, 128 }, - { 104, 79, 12, 27, 217, 255, 87, 17, 7 }, - { 87, 68, 71, 44, 114, 51, 15, 186, 23 }, - { 47, 41, 14, 110, 182, 183, 21, 17, 194 }, - { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, - { { 88, 88, 147, 150, 42, 46, 45, 196, 205 }, - { 43, 97, 183, 117, 85, 38, 35, 179, 61 }, - { 39, 53, 200, 87, 26, 21, 43, 232, 171 }, - { 56, 34, 51, 104, 114, 102, 29, 93, 77 }, - { 39, 28, 85, 171, 58, 165, 90, 98, 64 }, - { 34, 22, 116, 206, 23, 34, 43, 166, 73 }, - { 107, 54, 32, 26, 51, 1, 81, 43, 31 }, - { 68, 25, 106, 22, 64, 171, 36, 225, 114 }, - { 34, 19, 21, 102, 132, 188, 16, 76, 124 }, - { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, - { { 193, 101, 35, 159, 215, 111, 89, 46, 111 }, - { 60, 148, 31, 172, 219, 228, 21, 18, 111 }, - { 112, 113, 77, 85, 179, 255, 38, 120, 114 }, - { 40, 42, 1, 196, 245, 209, 10, 25, 109 }, - { 88, 43, 29, 140, 166, 213, 37, 43, 154 }, - { 61, 63, 30, 155, 67, 45, 68, 1, 209 }, - { 100, 80, 8, 43, 154, 1, 51, 26, 71 }, - { 142, 78, 78, 16, 255, 128, 34, 197, 171 }, - { 41, 40, 5, 102, 211, 183, 4, 1, 221 }, - { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, - { { 138, 31, 36, 171, 27, 166, 38, 44, 229 }, - { 67, 87, 58, 169, 82, 115, 26, 59, 179 }, - { 63, 59, 90, 180, 59, 166, 93, 73, 154 }, - { 40, 40, 21, 116, 143, 209, 34, 39, 175 }, - { 47, 15, 16, 183, 34, 223, 49, 45, 183 }, - { 46, 17, 33, 183, 6, 98, 15, 32, 183 }, - { 57, 46, 22, 24, 128, 1, 54, 17, 37 }, - { 65, 32, 73, 115, 28, 128, 23, 128, 205 }, - { 40, 3, 9, 115, 51, 192, 18, 6, 223 }, - { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, - { { 104, 55, 44, 218, 9, 54, 53, 130, 226 }, - { 64, 90, 70, 205, 40, 41, 23, 26, 57 }, - { 54, 57, 112, 184, 5, 41, 38, 166, 213 }, - { 30, 34, 26, 133, 152, 116, 10, 32, 134 }, - { 39, 19, 53, 221, 26, 114, 32, 73, 255 }, - { 31, 9, 65, 234, 2, 15, 1, 118, 73 }, - { 75, 32, 12, 51, 192, 255, 160, 43, 51 }, - { 88, 31, 35, 67, 102, 85, 55, 186, 85 }, - { 56, 21, 23, 111, 59, 205, 45, 37, 192 }, - { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, - { { 125, 98, 42, 88, 104, 85, 117, 175, 82 }, - { 95, 84, 53, 89, 128, 100, 113, 101, 45 }, - { 75, 79, 123, 47, 51, 128, 81, 171, 1 }, - { 57, 17, 5, 71, 102, 57, 53, 41, 49 }, - { 38, 33, 13, 121, 57, 73, 26, 1, 85 }, - { 41, 10, 67, 138, 77, 110, 90, 47, 114 }, - { 115, 21, 2, 10, 102, 255, 166, 23, 6 }, - { 101, 29, 16, 10, 85, 128, 101, 196, 26 }, - { 57, 18, 10, 102, 102, 213, 34, 20, 43 }, - { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, - { { 102, 61, 71, 37, 34, 53, 31, 243, 192 }, - { 69, 60, 71, 38, 73, 119, 28, 222, 37 }, - { 68, 45, 128, 34, 1, 47, 11, 245, 171 }, - { 62, 17, 19, 70, 146, 85, 55, 62, 70 }, - { 37, 43, 37, 154, 100, 163, 85, 160, 1 }, - { 63, 9, 92, 136, 28, 64, 32, 201, 85 }, - { 75, 15, 9, 9, 64, 255, 184, 119, 16 }, - { 86, 6, 28, 5, 64, 255, 25, 248, 1 }, - { 56, 8, 17, 132, 137, 255, 55, 116, 128 }, - { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, - { { 164, 50, 31, 137, 154, 133, 25, 35, 218 }, - { 51, 103, 44, 131, 131, 123, 31, 6, 158 }, - { 86, 40, 64, 135, 148, 224, 45, 183, 128 }, - { 22, 26, 17, 131, 240, 154, 14, 1, 209 }, - { 45, 16, 21, 91, 64, 222, 7, 1, 197 }, - { 56, 21, 39, 155, 60, 138, 23, 102, 213 }, - { 83, 12, 13, 54, 192, 255, 68, 47, 28 }, - { 85, 26, 85, 85, 128, 128, 32, 146, 171 }, - { 18, 11, 7, 63, 144, 171, 4, 4, 246 }, - { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, - { { 190, 80, 35, 99, 180, 80, 126, 54, 45 }, - { 85, 126, 47, 87, 176, 51, 41, 20, 32 }, - { 101, 75, 128, 139, 118, 146, 116, 128, 85 }, - { 56, 41, 15, 176, 236, 85, 37, 9, 62 }, - { 71, 30, 17, 119, 118, 255, 17, 18, 138 }, - { 101, 38, 60, 138, 55, 70, 43, 26, 142 }, - { 146, 36, 19, 30, 171, 255, 97, 27, 20 }, - { 138, 45, 61, 62, 219, 1, 81, 188, 64 }, - { 32, 41, 20, 117, 151, 142, 20, 21, 163 }, - { 112, 19, 12, 61, 195, 128, 48, 4, 24 } } - }; + public static readonly byte[,][] BModesProba = { + { new byte[] { 0, 0 }, new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 } }, + { new byte[] { 0, 1 }, new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 } }, + { new byte[] { 0, 2 }, new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 } }, + { new byte[] { 0, 3 }, new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 } }, + { new byte[] { 0, 4 }, new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 } }, + { new byte[] { 0, 5 }, new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 } }, + { new byte[] { 0, 6 }, new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 } }, + { new byte[] { 0, 7 }, new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 } }, + { new byte[] { 0, 8 }, new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 } }, + { new byte[] { 0, 9 }, new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, + { new byte[] { 1, 0 }, new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 } }, + { new byte[] { 1, 1 }, new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 } }, + { new byte[] { 1, 2 }, new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 } }, + { new byte[] { 1, 3 }, new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 } }, + { new byte[] { 1, 4 }, new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 } }, + { new byte[] { 1, 5 }, new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 } }, + { new byte[] { 1, 6 }, new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 } }, + { new byte[] { 1, 7 }, new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 } }, + { new byte[] { 1, 8 }, new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 } }, + { new byte[] { 1, 9 }, new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, + { new byte[] { 2, 0 }, new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 } }, + { new byte[] { 2, 1 }, new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 } }, + { new byte[] { 2, 2 }, new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 } }, + { new byte[] { 2, 3 }, new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 } }, + { new byte[] { 2, 4 }, new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 } }, + { new byte[] { 2, 5 }, new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 } }, + { new byte[] { 2, 6 }, new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 } }, + { new byte[] { 2, 7 }, new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 } }, + { new byte[] { 2, 8 }, new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 } }, + { new byte[] { 2, 9 }, new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, + { new byte[] { 3, 0 }, new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 } }, + { new byte[] { 3, 1 }, new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 } }, + { new byte[] { 3, 2 }, new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 } }, + { new byte[] { 3, 3 }, new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 } }, + { new byte[] { 3, 4 }, new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 } }, + { new byte[] { 3, 5 }, new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 } }, + { new byte[] { 3, 6 }, new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 } }, + { new byte[] { 3, 7 }, new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 } }, + { new byte[] { 3, 8 }, new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 } }, + { new byte[] { 3, 9 }, new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, + { new byte[] { 4, 0 }, new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 } }, + { new byte[] { 4, 1 }, new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 } }, + { new byte[] { 4, 2 }, new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 } }, + { new byte[] { 4, 3 }, new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 } }, + { new byte[] { 4, 4 }, new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 } }, + { new byte[] { 4, 5 }, new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 } }, + { new byte[] { 4, 6 }, new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 } }, + { new byte[] { 4, 7 }, new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 } }, + { new byte[] { 4, 8 }, new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 } }, + { new byte[] { 4, 9 }, new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, + { new byte[] { 5, 0 }, new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 } }, + { new byte[] { 5, 1 }, new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 } }, + { new byte[] { 5, 2 }, new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 } }, + { new byte[] { 5, 3 }, new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 } }, + { new byte[] { 5, 4 }, new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 } }, + { new byte[] { 5, 5 }, new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 } }, + { new byte[] { 5, 6 }, new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 } }, + { new byte[] { 5, 7 }, new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 } }, + { new byte[] { 5, 8 }, new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 } }, + { new byte[] { 5, 9 }, new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, + { new byte[] { 6, 0 }, new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 } }, + { new byte[] { 6, 1 }, new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 } }, + { new byte[] { 6, 2 }, new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 } }, + { new byte[] { 6, 3 }, new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 } }, + { new byte[] { 6, 4 }, new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 } }, + { new byte[] { 6, 5 }, new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 } }, + { new byte[] { 6, 6 }, new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 } }, + { new byte[] { 6, 7 }, new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 } }, + { new byte[] { 6, 8 }, new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 } }, + { new byte[] { 6, 9 }, new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, + { new byte[] { 7, 0 }, new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 } }, + { new byte[] { 7, 1 }, new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 } }, + { new byte[] { 7, 2 }, new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 } }, + { new byte[] { 7, 3 }, new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 } }, + { new byte[] { 7, 4 }, new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 } }, + { new byte[] { 7, 5 }, new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 } }, + { new byte[] { 7, 6 }, new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 } }, + { new byte[] { 7, 7 }, new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 } }, + { new byte[] { 7, 8 }, new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 } }, + { new byte[] { 7, 9 }, new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, + { new byte[] { 8, 0 }, new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 } }, + { new byte[] { 8, 1 }, new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 } }, + { new byte[] { 8, 2 }, new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 } }, + { new byte[] { 8, 3 }, new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 } }, + { new byte[] { 8, 4 }, new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 } }, + { new byte[] { 8, 5 }, new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 } }, + { new byte[] { 8, 6 }, new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 } }, + { new byte[] { 8, 7 }, new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 } }, + { new byte[] { 8, 8 }, new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 } }, + { new byte[] { 8, 9 }, new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, + { new byte[] { 9, 0 }, new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 } }, + { new byte[] { 9, 1 }, new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 } }, + { new byte[] { 9, 2 }, new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 } }, + { new byte[] { 9, 3 }, new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 } }, + { new byte[] { 9, 4 }, new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 } }, + { new byte[] { 9, 5 }, new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 } }, + { new byte[] { 9, 6 }, new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 } }, + { new byte[] { 9, 7 }, new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 } }, + { new byte[] { 9, 8 }, new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 } }, + { new byte[] { 9, 9 }, new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 } }, + }; // Paragraph 13 public static readonly byte[,,,] CoeffsUpdateProba = diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 1c102a06b..f1d06531e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -138,13 +138,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - byte[] modes = block.Modes; + Span modes = block.Modes.AsSpan(); for (int y = 0; y < 4; ++y) { int yMode = left[y]; for (int x = 0; x < 4; ++x) { - byte[] prob = null; //= WebPConstants.BModesProba[top[x], yMode]; + byte[] prob = WebPConstants.BModesProba[top[x], yMode]; int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; while (i > 0) { @@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Formats.WebP top[x] = (byte)yMode; } - // memcpy(modes, top, 4 * sizeof(*top)); - // modes += 4; + top.CopyTo(modes); + modes = modes.Slice(4); left[y] = (byte)yMode; } } From 9838e2e512a918b9bf9762cd800610f9f331ffd5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 5 Feb 2020 20:57:40 +0100 Subject: [PATCH 105/359] Implement Parse Partitions --- src/ImageSharp/Formats/WebP/BitReaderBase.cs | 4 +- src/ImageSharp/Formats/WebP/VP8BandProbas.cs | 4 + src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 44 +++- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 60 +++-- .../Formats/WebP/Vp8MacroBlockData.cs | 3 +- src/ImageSharp/Formats/WebP/Vp8Proba.cs | 16 ++ src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 6 +- src/ImageSharp/Formats/WebP/Vp8TopSamples.cs | 14 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 3 + .../Formats/WebP/WebPDecoderCore.cs | 22 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 9 +- .../Formats/WebP/WebPLossyDecoder.cs | 227 +++++++++++++++--- 12 files changed, 325 insertions(+), 87 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8TopSamples.cs diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs index 0a62299ea..8ef93a4d7 100644 --- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.WebP internal abstract class BitReaderBase { /// - /// Gets raw encoded image data. + /// Gets or sets the raw encoded image data. /// - protected byte[] Data { get; private set; } + public byte[] Data { get; set; } /// /// Copies the raw encoded image data from the stream into a byte array. diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs index 6a48eef55..a9c9420d1 100644 --- a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs @@ -11,6 +11,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8BandProbas() { this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx]; + for (int i = 0; i < WebPConstants.NumCtx; i++) + { + this.Probabilities[i] = new Vp8ProbaArray(); + } } public Vp8ProbaArray[] Probabilities { get; } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 2fdd4959f..d7b1fe100 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -32,9 +32,11 @@ namespace SixLabors.ImageSharp.Formats.WebP private int bits; /// - /// The next byte to be read. + /// Max packed-read position on buffer. /// - private byte buf; + private uint bufferMax; + + private uint bufferEnd; /// /// True if input is exhausted. @@ -53,10 +55,20 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. /// Start index in the data array. Defaults to 0. - public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, int startPos = 0) + public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) { + this.ImageDataSize = imageDataSize; + this.PartitionLength = partitionLength; this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); - this.InitBitreader(startPos); + this.InitBitreader(partitionLength, startPos); + } + + public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0) + { + this.Data = imageData; + this.ImageDataSize = (uint)imageData.Length; + this.PartitionLength = partitionLength; + this.InitBitreader(partitionLength, startPos); } public int Pos @@ -64,6 +76,12 @@ namespace SixLabors.ImageSharp.Formats.WebP get { return (int)this.pos; } } + public uint ImageDataSize { get; } + + public uint PartitionLength { get; } + + public uint Remaining { get; set; } + public int GetBit(int prob) { Guard.MustBeGreaterThan(prob, 0, nameof(prob)); @@ -123,26 +141,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return this.ReadValue(1) != 0 ? -value : value; } - private void InitBitreader(int pos = 0) + private void InitBitreader(uint size, int pos = 0) { this.range = 255 - 1; this.value = 0; - this.bits = -8; // to load the very first 8bits. + this.bits = -8; // to load the very first 8 bits. this.eof = false; - this.pos = 0; + this.pos = pos; + this.bufferEnd = (uint)(pos + size); + this.bufferMax = (uint)(size > 8 ? pos + size - 8 + 1 : pos); this.LoadNewBytes(); } private void LoadNewBytes() { - if (this.pos < this.Data.Length) + if (this.pos < this.bufferMax) { - ulong bits; - ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan().Slice((int)this.pos, 8)); + ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8)); this.pos += BitsCount >> 3; - this.buf = this.Data[this.pos]; - bits = this.ByteSwap64(inBits); + ulong bits = this.ByteSwap64(inBits); bits >>= 64 - BitsCount; this.value = bits | (this.value << BitsCount); this.bits += BitsCount; @@ -156,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void LoadFinalBytes() { // Only read 8bits at a time. - if (this.pos < this.Data.Length) + if (this.pos < this.bufferEnd) { this.bits += 8; this.value = this.Data[this.pos++] | (this.value << 8); diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 5643b798b..24d329e5b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -15,10 +15,32 @@ namespace SixLabors.ImageSharp.Formats.WebP this.FilterHeader = filterHeader; this.SegmentHeader = segmentHeader; this.Probabilities = probabilities; + this.IntraL = new byte[4]; + this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; + this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); + this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); + this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth]; + this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; + this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; + for (int i = 0; i < this.MbWidth; i++) + { + this.MacroBlockInfo[i] = new Vp8MacroBlock(); + this.MacroBlockData[i] = new Vp8MacroBlockData(); + this.YuvTopSamples[i] = new Vp8TopSamples(); + } + this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; - this.IntraL = new byte[4]; + for (int i = 0; i < WebPConstants.NumMbSegments; i++) + { + this.DeQuantMatrices[i] = new Vp8QuantMatrix(); + for (int j = 0; j < 2; j++) + { + this.FilterStrength[i, j] = new Vp8FilterInfo(); + } + } + this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; this.Init(io); } @@ -30,14 +52,20 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8SegmentHeader SegmentHeader { get; } + // number of partitions minus one. + public uint NumPartsMinusOne { get; } + + // per-partition boolean decoders. + public Vp8BitReader[] Vp8BitReaders { get; } + public bool Dither { get; set; } /// /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). /// - public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } + public Vp8QuantMatrix[] DeQuantMatrices { get; } - public bool UseSkipProba { get; set; } + public bool UseSkipProbability { get; set; } public byte SkipProbability { get; set; } @@ -52,12 +80,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the width in macroblock units. /// - public int MbWidth { get; set; } + public int MbWidth { get; } /// /// Gets or sets the height in macroblock units. /// - public int MbHeight { get; set; } + public int MbHeight { get; } /// /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. @@ -72,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. /// - public int BotomRightMbX { get; set; } + public int BottomRightMbX { get; set; } /// /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. @@ -90,14 +118,14 @@ namespace SixLabors.ImageSharp.Formats.WebP public int MbY { get; set; } /// - /// Gets or sets the parsed reconstruction data. + /// Gets the parsed reconstruction data. /// - public Vp8MacroBlockData[] MacroBlockData { get; set; } + public Vp8MacroBlockData[] MacroBlockData { get; } /// - /// Gets or sets contextual macroblock infos. + /// Gets contextual macroblock infos. /// - public Vp8MacroBlock[] MacroBlockInfo { get; set; } + public Vp8MacroBlock[] MacroBlockInfo { get; } public int MacroBlockIdx { get; set; } @@ -105,6 +133,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8FilterInfo[,] FilterStrength { get; } + public byte[] YuvBuffer { get; } + + public Vp8TopSamples[] YuvTopSamples { get; } + /// /// Gets or sets filter strength info. /// @@ -112,8 +144,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Init(Vp8Io io) { - this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); - this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); int intraPredModeSize = 4 * this.MbWidth; this.IntraT = new byte[intraPredModeSize]; @@ -157,10 +187,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // We need some 'extra' pixels on the right/bottom. this.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; - this.BotomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; - if (this.BotomRightMbX > this.MbWidth) + this.BottomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; + if (this.BottomRightMbX > this.MbWidth) { - this.BotomRightMbX = this.MbWidth; + this.BottomRightMbX = this.MbWidth; } if (this.BottomRightMbY > this.MbHeight) diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index 5dbeb2358..f2ab8ff7f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -11,12 +11,13 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8MacroBlockData() { this.Modes = new byte[16]; + this.Coeffs = new short[384]; } /// /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4. /// - public short Coeffs { get; set; } + public short[] Coeffs { get; set; } /// /// Gets or sets a value indicating whether its intra4x4. diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs index a884bd3c7..ae34f936d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs @@ -15,6 +15,22 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Segments = new uint[MbFeatureTreeProbs]; this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1]; + + for (int i = 0; i < WebPConstants.NumTypes; i++) + { + for (int j = 0; j < WebPConstants.NumBands; j++) + { + this.Bands[i, j] = new Vp8BandProbas(); + } + } + + for (int i = 0; i < WebPConstants.NumTypes; i++) + { + for (int j = 0; j < 17; j++) + { + this.BandsPtr[i, j] = new Vp8BandProbas(); + } + } } public uint[] Segments { get; } diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs index a5d718a48..ca9f4b69e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -5,11 +5,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8QuantMatrix { - public int[] Y1Mat { get; set; } + public int[] Y1Mat { get; } = new int[2]; - public int[] Y2Mat { get; set; } + public int[] Y2Mat { get; } = new int[2]; - public int[] UvMat { get; set; } + public int[] UvMat { get; } = new int[2]; /// /// Gets or sets the U/V quantizer value. diff --git a/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs new file mode 100644 index 000000000..c6382b5c6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8TopSamples + { + public byte[] Y { get; } = new byte[16]; + + public byte[] U { get; } = new byte[8]; + + public byte[] V { get; } = new byte[8]; + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index f06310d23..854b9674b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -121,6 +121,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int NumCtx = 3; + // this is the common stride for enc/dec + public const int Bps = 32; + // intra prediction modes (TODO: maybe use an enum for this) public const int DcPred = 0; public const int TmPred = 1; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 22a370592..6e9631729 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // remaining counts the available image data payload. uint remaining = dataSize; - // See paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 + // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 // Frame tag that contains four fields: // - A 1-bit frame type (0 for key frames, 1 for interframes). // - A 3-bit version number. @@ -359,31 +359,21 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8BitReader( this.currentStream, remaining, - this.memoryAllocator); - - // Paragraph 9.2: color space and clamp type follow. - sbyte colorSpace = (sbyte)bitReader.ReadValue(1); - sbyte clampType = (sbyte)bitReader.ReadValue(1); - var vp8PictureHeader = new Vp8PictureHeader() - { - Width = (uint)width, - Height = (uint)height, - XScale = xScale, - YScale = yScale, - ColorSpace = colorSpace, - ClampType = clampType - }; + this.memoryAllocator, + partitionLength); + bitReader.Remaining = remaining; return new WebPImageInfo() { Width = width, Height = height, + XScale = xScale, + YScale = yScale, BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, IsLossLess = false, Features = features, Vp8Profile = (sbyte)version, Vp8FrameHeader = vp8FrameHeader, - Vp8PictureHeader = vp8PictureHeader, Vp8BitReader = bitReader }; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index dace37aad..19a72c0c2 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -15,6 +15,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public uint Height { get; set; } + public sbyte XScale { get; set; } + + public sbyte YScale { get; set; } + /// /// Gets or sets the bits per pixel. /// @@ -40,11 +44,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8FrameHeader Vp8FrameHeader { get; set; } - /// - /// Gets or sets the VP8 picture header. - /// - public Vp8PictureHeader Vp8PictureHeader { get; set; } - /// /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f1d06531e..852b94c0f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -36,6 +36,19 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V Vp8Profile vp8Profile = this.DecodeProfile(info.Vp8Profile); + // Paragraph 9.2: color space and clamp type follow. + sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); + sbyte clampType = (sbyte)this.bitReader.ReadValue(1); + var vp8PictureHeader = new Vp8PictureHeader() + { + Width = (uint)width, + Height = (uint)height, + XScale = info.XScale, + YScale = info.YScale, + ColorSpace = colorSpace, + ClampType = clampType + }; + // Paragraph 9.3: Parse the segment header. var proba = new Vp8Proba(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); @@ -43,23 +56,21 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.4: Parse the filter specs. Vp8FilterHeader vp8FilterHeader = this.ParseFilterHeader(); - // TODO: Review Paragraph 9.5: ParsePartitions. - int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; - int lastPart = numPartsMinusOne; - // TODO: check if we have enough data available here, throw exception if not - int partStart = this.bitReader.Pos + (lastPart * 3); + var vp8Io = default(Vp8Io); + var decoder = new Vp8Decoder(info.Vp8FrameHeader, vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); + + // Paragraph 9.5: Parse partitions. + this.ParsePartitions(decoder); // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(vp8SegmentHeader); + this.ParseDequantizationIndices(decoder.SegmentHeader); // Ignore the value of update_proba this.bitReader.ReadBool(); // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(proba); + this.ParseProbabilities(decoder, decoder.Probabilities); - var vp8Io = default(Vp8Io); - var decoder = new Vp8Decoder(info.Vp8FrameHeader, info.Vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); this.ParseFrame(decoder, vp8Io); } @@ -81,21 +92,9 @@ namespace SixLabors.ImageSharp.Formats.WebP // Prepare for next scanline. this.InitScanline(dec); - // TODO: Reconstruct, filter and emit the row. - } - } - - private void InitScanline(Vp8Decoder dec) - { - Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; - left.NoneZeroAcDcCoeffs = 0; - left.NoneZeroDcCoeffs = 0; - for (int i = 0; i < dec.IntraL.Length; i++) - { - dec.IntraL[i] = 0; + // Reconstruct, filter and emit the row. + this.ProcessRow(dec); } - - dec.MbX = 0; } private void ParseIntraMode(Vp8Decoder dec, int mbX) @@ -117,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.WebP block.Segment = 0; } - if (dec.UseSkipProba) + if (dec.UseSkipProbability) { block.Skip = (byte)this.bitReader.GetBit(dec.SkipProbability); } @@ -165,7 +164,144 @@ namespace SixLabors.ImageSharp.Formats.WebP block.UvMode = (byte)(this.bitReader.GetBit(142) is 0 ? 0 : this.bitReader.GetBit(114) is 0 ? 2 : this.bitReader.GetBit(183) > 0 ? 1 : 3); + } + private void InitScanline(Vp8Decoder dec) + { + Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; + left.NoneZeroAcDcCoeffs = 0; + left.NoneZeroDcCoeffs = 0; + for (int i = 0; i < dec.IntraL.Length; i++) + { + dec.IntraL[i] = 0; + } + + dec.MbX = 0; + } + + private void ProcessRow(Vp8Decoder dec) + { + bool filterRow = (dec.Filter != LoopFilter.None) && + (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); + + this.ReconstructRow(dec, filterRow); + } + + private void ReconstructRow(Vp8Decoder dec, bool filterRow) + { + int mby = dec.MbY; + + int yOff = (WebPConstants.Bps * 1) + 8; + int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; + int vOff = uOff + 16; + + Span yDst = dec.YuvBuffer.AsSpan(yOff); + Span uDst = dec.YuvBuffer.AsSpan(uOff); + Span vDst = dec.YuvBuffer.AsSpan(vOff); + + // Initialize left-most block. + for (int i = 0; i < 16; ++i) + { + yDst[(i * WebPConstants.Bps) - 1] = 129; + } + + for (int i = 0; i < 8; ++i) + { + uDst[(i * WebPConstants.Bps) - 1] = 129; + vDst[(i * WebPConstants.Bps) - 1] = 129; + } + + // Init top-left sample on left column too. + if (mby > 0) + { + yDst[-1 - WebPConstants.Bps] = uDst[-1 - WebPConstants.Bps] = vDst[-1 - WebPConstants.Bps] = 129; + } + else + { + // We only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + Span tmp = dec.YuvBuffer.AsSpan(yOff - WebPConstants.Bps - 1, 16 + 4 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + + tmp = dec.YuvBuffer.AsSpan(uOff - WebPConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + + tmp = dec.YuvBuffer.AsSpan(vOff - WebPConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + } + + // Reconstruct one row. + for (int mbx = 0; mbx < dec.MbWidth; ++mbx) + { + Vp8MacroBlockData block = dec.MacroBlockData[mbx]; + + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mbx > 0) + { + for (int i = -1; i < 16; ++i) + { + // Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + } + + for (int i = -1; i < 8; ++i) + { + // Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); + // Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + } + + // Bring top samples into the cache. + Vp8TopSamples topSamples = dec.YuvTopSamples[mbx]; + short[] coeffs = block.Coeffs; + uint bits = block.NonZeroY; + if (mby > 0) + { + //memcpy(y_dst - BPS, top_yuv[0].y, 16); + //memcpy(u_dst - BPS, top_yuv[0].u, 8); + //memcpy(v_dst - BPS, top_yuv[0].v, 8); + } + + // Predict and add residuals. + if (block.IsI4x4) + { + if (mby > 0) + { + if (mbx >= dec.MbWidth - 1) + { + // On rightmost border. + //memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } + else + { + // memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } + } + + // Replicate the top-right pixels below. + + + // Predict and add residuals for all 4x4 blocks in turn. + for (int n = 0; n < 16; ++n, bits <<= 2) + { + + } + } + else + { + // 16x16 + + } + } + } } private Vp8Profile DecodeProfile(int version) @@ -193,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; // TODO: not sure if this - 1 is correct here Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockIdx + dec.MbX]; Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockIdx + dec.MbX]; - int skip = dec.UseSkipProba ? blockData.Skip : 0; + int skip = dec.UseSkipProbability ? blockData.Skip : 0; if (skip is 0) { @@ -555,6 +691,34 @@ namespace SixLabors.ImageSharp.Formats.WebP return vp8FilterHeader; } + private void ParsePartitions(Vp8Decoder dec) + { + uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; + int startIdx = (int)this.bitReader.PartitionLength; + Span sz = this.bitReader.Data.AsSpan(startIdx); + int sizeLeft = (int)size; + int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; + int lastPart = numPartsMinusOne; + + int partStart = startIdx + (lastPart * 3); + sizeLeft -= lastPart * 3; + for (int p = 0; p < lastPart; ++p) + { + int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (pSize > sizeLeft) + { + pSize = sizeLeft; + } + + dec.Vp8BitReaders[p] = new Vp8BitReader(this.bitReader.Data, (uint)pSize, partStart); + partStart += pSize; + sizeLeft -= pSize; + sz = sz.Slice(3); + } + + dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); + } + private void ParseDequantizationIndices(Vp8SegmentHeader vp8SegmentHeader) { int baseQ0 = (int)this.bitReader.ReadValue(7); @@ -613,7 +777,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void ParseProbabilities(Vp8Proba proba) + private void ParseProbabilities(Vp8Decoder dec, Vp8Proba proba) { for (int t = 0; t < WebPConstants.NumTypes; ++t) { @@ -623,8 +787,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int p = 0; p < WebPConstants.NumProbas; ++p) { - var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; - int v = this.bitReader.GetBit(prob) == 0 + byte prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; + int v = this.bitReader.GetBit(prob) > 0 ? (int)this.bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; @@ -638,11 +802,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // TODO: those values needs to be stored somewhere - bool useSkipProba = this.bitReader.ReadBool(); - if (useSkipProba) + dec.UseSkipProbability = this.bitReader.ReadBool(); + if (dec.UseSkipProbability) { - uint skipP = this.bitReader.ReadValue(8); + dec.SkipProbability = (byte)this.bitReader.ReadValue(8); } } From f9a167e8c7bb11cdb124731444c21ab58faf35ec Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 7 Feb 2020 18:45:15 +0100 Subject: [PATCH 106/359] Fix BitsLog2Floor --- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 17 ++++++--------- src/ImageSharp/Formats/WebP/WebPConstants.cs | 21 +++++++++++++++++++ .../Formats/WebP/WebPLossyDecoder.cs | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index d7b1fe100..bec618ce8 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -199,22 +199,17 @@ namespace SixLabors.ImageSharp.Formats.WebP return x; } + // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). private int BitsLog2Floor(uint n) { - // Search the mask data from most significant bit (MSB) to least significant bit (LSB) for a set bit (1). - // https://docs.microsoft.com/en-us/cpp/intrinsics/bitscanreverse-bitscanreverse64?view=vs-2019 - uint mask = 1; - for (int y = 0; y < 32; y++) + int logValue = 0; + while (n >= 256) { - if ((mask & n) == mask) - { - return y; - } - - mask <<= 1; + logValue += 8; + n >>= 8; } - return 0; + return logValue + WebPConstants.LogTable8bit[n]; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 854b9674b..f3a135ba6 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -143,6 +143,27 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 }; + // 31 ^ clz(i) + public static readonly byte[] LogTable8bit = + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; + // Paragraph 14.1 public static readonly int[] DcTable = { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 852b94c0f..ed5a7a0b9 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.WebP block.Skip = (byte)this.bitReader.GetBit(dec.SkipProbability); } - block.IsI4x4 = this.bitReader.GetBit(145) != 0; + block.IsI4x4 = this.bitReader.GetBit(145) is 0; if (!block.IsI4x4) { // Hardcoded 16x16 intra-mode decision tree. From 38bee8203df72aeb12bd2b8f788b2fef04d586f9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 7 Feb 2020 19:49:33 +0100 Subject: [PATCH 107/359] Store filter info --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 12 ++-- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 2 +- .../Formats/WebP/WebPLossyDecoder.cs | 56 +++++++++++-------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 24d329e5b..872ddf36e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -19,16 +19,20 @@ namespace SixLabors.ImageSharp.Formats.WebP this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); - this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth]; + this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; + this.FilterInfo = new Vp8FilterInfo[this.MbWidth]; for (int i = 0; i < this.MbWidth; i++) { this.MacroBlockInfo[i] = new Vp8MacroBlock(); this.MacroBlockData[i] = new Vp8MacroBlockData(); this.YuvTopSamples[i] = new Vp8TopSamples(); + this.FilterInfo[i] = new Vp8FilterInfo(); } + this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); + this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; for (int i = 0; i < WebPConstants.NumMbSegments; i++) @@ -123,12 +127,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8MacroBlockData[] MacroBlockData { get; } /// - /// Gets contextual macroblock infos. + /// Gets contextual contextual macroblock info (mbw + 1). /// public Vp8MacroBlock[] MacroBlockInfo { get; } - public int MacroBlockIdx { get; set; } - public LoopFilter Filter { get; set; } public Vp8FilterInfo[,] FilterStrength { get; } @@ -140,7 +142,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets filter strength info. /// - public Vp8FilterInfo FilterInfo { get; set; } + public Vp8FilterInfo[] FilterInfo { get; set; } public void Init(Vp8Io io) { diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index 4204386bc..bb6c6da17 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets a value indicating whether to do inner filtering. /// - public bool InnerFiltering { get; set; } + public byte InnerFiltering { get; set; } /// /// Gets or sets the high edge variance threshold in [0..2]. diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index ed5a7a0b9..8c8915e00 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -78,6 +78,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) { + // Parse bitstream for this row. + long bitreaderIdx = dec.MbY & dec.NumPartsMinusOne; + Vp8BitReader bitreader = dec.Vp8BitReaders[bitreaderIdx]; + // Parse intra mode mode row. for (int mbX = 0; mbX < dec.MbWidth; ++mbX) { @@ -86,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (; dec.MbX < dec.MbWidth; ++dec.MbX) { - this.DecodeMacroBlock(dec); + this.DecodeMacroBlock(dec, bitreader); } // Prepare for next scanline. @@ -168,7 +172,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void InitScanline(Vp8Decoder dec) { - Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; + Vp8MacroBlock left = dec.MacroBlockInfo[0]; left.NoneZeroAcDcCoeffs = 0; left.NoneZeroDcCoeffs = 0; for (int i = 0; i < dec.IntraL.Length; i++) @@ -324,16 +328,16 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodeMacroBlock(Vp8Decoder dec) + private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) { - Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; // TODO: not sure if this - 1 is correct here - Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockIdx + dec.MbX]; - Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockIdx + dec.MbX]; + Vp8MacroBlock left = dec.MacroBlockInfo[0]; + Vp8MacroBlock macroBlock = dec.MacroBlockInfo[1 + dec.MbX]; + Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MbX]; int skip = dec.UseSkipProbability ? blockData.Skip : 0; if (skip is 0) { - this.ParseResiduals(dec, macroBlock); + this.ParseResiduals(dec, bitreader, macroBlock); } else { @@ -348,29 +352,33 @@ namespace SixLabors.ImageSharp.Formats.WebP blockData.Dither = 0; } - // TODO: store filter info + // Store filter info. + if (dec.Filter != LoopFilter.None) + { + dec.FilterInfo[dec.MbX] = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; + dec.FilterInfo[dec.MbX].InnerFiltering |= (byte)(skip is 0 ? 1 : 0); + } } - private bool ParseResiduals(Vp8Decoder decoder, Vp8MacroBlock mb) + private bool ParseResiduals(Vp8Decoder dec, Vp8BitReader br, Vp8MacroBlock mb) { - byte tnz, lnz; uint nonZeroY = 0; uint nonZeroUv = 0; int first; var dst = new short[384]; - var dstOffset = 0; - Vp8MacroBlockData block = decoder.MacroBlockData[decoder.MbX]; - Vp8QuantMatrix q = decoder.DeQuantMatrices[block.Segment]; - Vp8BandProbas[,] bands = decoder.Probabilities.BandsPtr; + int dstOffset = 0; + Vp8MacroBlockData block = dec.MacroBlockData[dec.MbX]; + Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; + Vp8BandProbas[,] bands = dec.Probabilities.BandsPtr; Vp8BandProbas[] acProba; - Vp8MacroBlock leftMb = null; // TODO: this value needs to be set + Vp8MacroBlock leftMb = dec.MacroBlockInfo[0]; if (!block.IsI4x4) { // Parse DC var dc = new short[16]; int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); - int nz = this.GetCoeffs(GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc); + int nz = this.GetCoeffs(br, GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc); mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); if (nz > 0) { @@ -395,8 +403,8 @@ namespace SixLabors.ImageSharp.Formats.WebP acProba = GetBandsRow(bands, 3); } - tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); - lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); + byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); + byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); for (int y = 0; y < 4; ++y) { @@ -405,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int x = 0; x < 4; ++x) { int ctx = l + (tnz & 1); - int nz = this.GetCoeffs(acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); + int nz = this.GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); l = (nz > first) ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 7)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); @@ -431,7 +439,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int x = 0; x < 2; ++x) { int ctx = l + (tnz & 1); - int nz = this.GetCoeffs(GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); + int nz = this.GetCoeffs(br, GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); l = (nz > 0) ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 3)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); @@ -462,7 +470,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return (nonZeroY | nonZeroUv) is 0; } - private int GetCoeffs(Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) + private int GetCoeffs(Vp8BitReader br, Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) { // Returns the position of the last non - zero coeff plus one. Vp8ProbaArray p = prob[n].Probabilities[ctx]; @@ -475,7 +483,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Sequence of zero coeffs. - while (this.bitReader.GetBit((int)p.Probabilities[1]) is 0) + while (br.GetBit((int)p.Probabilities[1]) is 0) { p = prob[++n].Probabilities[0]; if (n is 16) @@ -486,7 +494,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Non zero coeffs. int v; - if (this.bitReader.GetBit((int)p.Probabilities[2]) is 0) + if (br.GetBit((int)p.Probabilities[2]) is 0) { v = 1; p = prob[n + 1].Probabilities[1]; @@ -498,7 +506,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } int idx = n > 0 ? 1 : 0; - coeffs[WebPConstants.Zigzag[n]] = (short)(this.bitReader.ReadSignedValue(v) * dq[idx]); + coeffs[WebPConstants.Zigzag[n]] = (short)(br.ReadSignedValue(v) * dq[idx]); } return 16; From 804f20969b3a06420d11d3b80a280fb257d91a38 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 12 Feb 2020 19:09:35 +0100 Subject: [PATCH 108/359] Start implementing ReconstructRow --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 336 ++++++++++++++++++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 8 + .../Formats/WebP/WebPLossyDecoder.cs | 227 ++++++++++-- 3 files changed, 537 insertions(+), 34 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/LossyUtils.cs diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs new file mode 100644 index 000000000..a4c488b61 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -0,0 +1,336 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class LossyUtils + { + private static void Put16(int v, Span dst) + { + for (int j = 0; j < 16; ++j) + { + Span tmp = dst.Slice(j * WebPConstants.Bps); + for (int i = 0; i < 16; i++) + { + tmp[i] = (byte)v; + } + } + } + + public static void DC16_C(Span dst, byte[] yuv, int offset) + { + int dc = 16; + int j; + for (j = 0; j < 16; ++j) + { + // DC += dst[-1 + j * BPS] + dst[j - BPS]; + dc += yuv[-1 + (j * WebPConstants.Bps) + offset] + yuv[j - WebPConstants.Bps + offset]; + } + + Put16(dc >> 5, dst); + } + + public static void TM16_C(Span dst) + { + + } + + public static void VE16_C(Span dst, byte[] yuv, int offset) + { + // vertical + Span src = yuv.AsSpan(offset - WebPConstants.Bps, 16); + for (int j = 0; j < 16; ++j) + { + // memcpy(dst + j * BPS, dst - BPS, 16); + src.CopyTo(dst.Slice(j * WebPConstants.Bps)); + } + } + + public static void HE16_C(Span dst, byte[] yuv, int offset) + { + // horizontal + for (int j = 16; j > 0; --j) + { + // memset(dst, dst[-1], 16); + dst = dst.Slice(WebPConstants.Bps); + byte v = yuv[offset - 1]; + for (int i = 0; i < 16; i++) + { + dst[i] = v; + } + + offset += WebPConstants.Bps; + } + } + + public static void DC16NoTop_C(Span dst, byte[] yuv, int offset) + { + // DC with top samples not available. + int dc = 8; + for (int j = 0; j < 16; ++j) + { + // DC += dst[-1 + j * BPS]; + dc += yuv[-1 + (j * WebPConstants.Bps) + offset]; + } + + Put16(dc >> 4, dst); + } + + public static void DC16NoLeft_C(Span dst, byte[] yuv, int offset) + { + // DC with left samples not available. + int dc = 8; + for (int i = 0; i < 16; ++i) + { + // DC += dst[i - BPS]; + dc += yuv[i - WebPConstants.Bps + offset]; + } + + Put16(dc >> 4, dst); + } + + public static void DC16NoTopLeft_C(Span dst) + { + // DC with no top and left samples. + Put16(0x80, dst); + } + + public static void DC8uv_C(Span dst, byte[] yuv, int offset) + { + int dc0 = 8; + for (int i = 0; i < 8; ++i) + { + // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; + dc0 += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; + } + + Put8x8uv((byte)(dc0 >> 4), dst); + } + + public static void TM8uv_C(Span dst) + { + // TrueMotion + } + + public static void VE8uv_C(Span dst, Span src) + { + // vertical + for (int j = 0; j < 8; ++j) + { + // memcpy(dst + j * BPS, dst - BPS, 8); + src.CopyTo(dst.Slice(j * WebPConstants.Bps)); + } + } + + public static void HE8uv_C(Span dst, byte[] yuv, int offset) + { + // horizontal + for (int j = 0; j < 8; ++j) + { + // memset(dst, dst[-1], 8); + byte v = yuv[offset - 1]; + for (int i = 0; i < 8; i++) + { + yuv[offset + i] = v; + } + } + } + + public static void DC8uvNoTop_C(Span dst, byte[] yuv, int offset) + { + // DC with no top samples. + int dc0 = 4; + for (int i = 0; i < 8; ++i) + { + // dc0 += dst[-1 + i * BPS]; + dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)]; + } + + Put8x8uv((byte)(dc0 >> 3), dst); + } + + public static void DC8uvNoLeft_C(Span dst, byte[] yuv, int offset) + { + // DC with no left samples. + int dc0 = 4; + for (int i = 0; i < 8; ++i) + { + // dc0 += dst[i - BPS]; + dc0 += yuv[offset + i - WebPConstants.Bps]; + } + + Put8x8uv((byte)(dc0 >> 3), dst); + } + + public static void DC8uvNoTopLeft_C(Span dst, byte[] yuv, int offset) + { + // DC with no top samples. + int dc0 = 4; + for (int i = 0; i < 8; ++i) + { + // dc0 += dst[-1 + i * BPS]; + dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)]; + } + + Put8x8uv((byte)(dc0 >> 3), dst); + } + + public static void Transform(Span src, Span dst, bool doTwo) + { + TransformOne(src, dst); + if (doTwo) + { + TransformOne(src, dst); + } + } + + public static void TransformOne(Span src, Span dst) + { + var tmp = new int[4 * 4]; + int tmpOffset = 0; + int srcOffset = 0; + for (int i = 0; i < 4; ++i) + { + // vertical pass + int a = src[srcOffset] + src[srcOffset + 8]; // [-4096, 4094] + int b = src[srcOffset] - src[srcOffset + 8]; // [-4095, 4095] + int c = Mul2(src[4]) - Mul1(src[12]); // [-3783, 3783] + int d = Mul1(src[4]) + Mul2(src[12]); // [-3785, 3781] + tmp[tmpOffset] = a + d; // [-7881, 7875] + tmp[tmpOffset + 1] = b + c; // [-7878, 7878] + tmp[tmpOffset + 2] = b - c; // [-7878, 7878] + tmp[tmpOffset + 3] = a - d; // [-7877, 7879] + tmpOffset += 4; + srcOffset++; + } + + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value + // in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as + // [-60713, 60968]. + tmpOffset = 0; + for (int i = 0; i < 4; ++i) + { + // horizontal pass + int dc = tmp[tmpOffset] + 4; + int a = dc + tmp[tmpOffset + 8]; + int b = dc - tmp[tmpOffset + 8]; + int c = Mul2(tmp[tmpOffset + 4]) - Mul1(tmp[tmpOffset + 12]); + int d = Mul1(tmp[tmpOffset + 4]) + Mul2(tmp[tmpOffset + 12]); + Store(dst, 0, 0, a + d); + Store(dst, 1, 0, b + c); + Store(dst, 2, 0, b - c); + Store(dst, 3, 0, a - d); + tmpOffset++; + dst = dst.Slice(WebPConstants.Bps); + } + } + + public static void TransformDc(Span src, Span dst) + { + int dc = src[0] + 4; + for (int j = 0; j < 4; ++j) + { + for (int i = 0; i < 4; ++i) + { + Store(dst, i, j, dc); + } + } + } + + // Simplified transform when only in[0], in[1] and in[4] are non-zero + public static void TransformAc3(Span src, Span dst) + { + int a = src[0] + 4; + int c4 = Mul2(src[4]); + int d4 = Mul1(src[4]); + int c1 = Mul2(src[1]); + int d1 = Mul1(src[1]); + Store2(dst, 0, a + d4, d1, c1); + Store2(dst, 1, a + c4, d1, c1); + Store2(dst, 2, a - c4, d1, c1); + Store2(dst, 3, a - d4, d1, c1); + } + + public static void TransformUv(Span src, Span dst) + { + Transform(src.Slice(0 * 16), dst, true); + Transform(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps), true); + } + + public static void TransformDcuv(Span src, Span dst) + { + if (src[0 * 16] > 0) + { + TransformDc(src.Slice(0 * 16), dst); + } + + if (src[1 * 16] > 0) + { + TransformDc(src.Slice(1 * 16), dst.Slice(4)); + } + + if (src[2 * 16] > 0) + { + TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); + } + + if (src[3 * 16] > 0) + { + TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebPConstants.Bps) + 4)); + } + } + + private static void Store(Span dst, int x, int y, int v) + { + dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3)); + } + + private static void Store2(Span dst, int y, int dc, int d, int c) + { + Store(dst, 0, y, dc + d); + Store(dst, 1, y, dc + c); + Store(dst, 2, y, dc - c); + Store(dst, 3, y, dc - d); + } + + private static int Mul1(int a) + { + return ((a * 20091) >> 16) + a; + } + + private static int Mul2(int a) + { + return (a * 35468) >> 16; + } + + private static byte Clip8B(int v) + { + return (byte)((v & ~0xff) > 0 ? v : (v < 0) ? 0 : 255); + } + + private static void Put8x8uv(byte value, Span dst) + { + // memset(dst + j * BPS, value, 8); + for (int j = 0; j < 8; ++j) + { + dst[j * WebPConstants.Bps] = value; + } + } + + private static byte Avg2(byte a, byte b) + { + return (byte)((a + b + 1) >> 1); + } + + private static byte Avg3(byte a, byte b, byte c) + { + return (byte)((a + (2 * b) + c + 2) >> 2); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index f3a135ba6..e22ae8d34 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -143,6 +143,14 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 }; + public static readonly short[] KScan = + { + 0 + 0 * Bps, 4 + 0 * Bps, 8 + 0 * Bps, 12 + 0 * Bps, + 0 + 4 * Bps, 4 + 4 * Bps, 8 + 4 * Bps, 12 + 4 * Bps, + 0 + 8 * Bps, 4 + 8 * Bps, 8 + 8 * Bps, 12 + 8 * Bps, + 0 + 12 * Bps, 4 + 12 * Bps, 8 + 12 * Bps, 12 + 12 * Bps + }; + // 31 ^ clz(i) public static readonly byte[] LogTable8bit = { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 8c8915e00..725b5857a 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -199,6 +199,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; int vOff = uOff + 16; + byte[] yuv = dec.YuvBuffer; Span yDst = dec.YuvBuffer.AsSpan(yOff); Span uDst = dec.YuvBuffer.AsSpan(uOff); Span vDst = dec.YuvBuffer.AsSpan(vOff); @@ -206,19 +207,20 @@ namespace SixLabors.ImageSharp.Formats.WebP // Initialize left-most block. for (int i = 0; i < 16; ++i) { - yDst[(i * WebPConstants.Bps) - 1] = 129; + yuv[(i * WebPConstants.Bps) - 1 + yOff] = 129; } for (int i = 0; i < 8; ++i) { - uDst[(i * WebPConstants.Bps) - 1] = 129; - vDst[(i * WebPConstants.Bps) - 1] = 129; + yuv[(i * WebPConstants.Bps) - 1 + uOff] = 129; + yuv[(i * WebPConstants.Bps) - 1 + vOff] = 129; } // Init top-left sample on left column too. if (mby > 0) { - yDst[-1 - WebPConstants.Bps] = uDst[-1 - WebPConstants.Bps] = vDst[-1 - WebPConstants.Bps] = 129; + // TODO: + // yDst[-1 - WebPConstants.Bps] = uDst[-1 - WebPConstants.Bps] = vDst[-1 - WebPConstants.Bps] = 129; } else { @@ -254,56 +256,193 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int i = -1; i < 16; ++i) { - // Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + int srcIdx = (i * WebPConstants.Bps) + 12 + yOff; + int dstIdx = (i * WebPConstants.Bps) - 4 + yOff; + yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); } for (int i = -1; i < 8; ++i) { - // Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); - // Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + int srcIdx = (i * WebPConstants.Bps) + 4 + uOff; + int dstIdx = (i * WebPConstants.Bps) - 4 + uOff; + yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); + srcIdx = (i * WebPConstants.Bps) + 4 + vOff; + dstIdx = (i * WebPConstants.Bps) - 4 + vOff; + yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); } + } - // Bring top samples into the cache. - Vp8TopSamples topSamples = dec.YuvTopSamples[mbx]; - short[] coeffs = block.Coeffs; - uint bits = block.NonZeroY; + // Bring top samples into the cache. + Vp8TopSamples topYuv = dec.YuvTopSamples[mbx]; + short[] coeffs = block.Coeffs; + uint bits = block.NonZeroY; + if (mby > 0) + { + topYuv.Y.CopyTo(yuv.AsSpan(yOff - WebPConstants.Bps)); + topYuv.U.CopyTo(yuv.AsSpan(uOff - WebPConstants.Bps)); + topYuv.V.CopyTo(yuv.AsSpan(vOff - WebPConstants.Bps)); + } + + // Predict and add residuals. + if (block.IsI4x4) + { if (mby > 0) { - //memcpy(y_dst - BPS, top_yuv[0].y, 16); - //memcpy(u_dst - BPS, top_yuv[0].u, 8); - //memcpy(v_dst - BPS, top_yuv[0].v, 8); + if (mbx >= dec.MbWidth - 1) + { + // On rightmost border. + //memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } + else + { + // memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } } - // Predict and add residuals. - if (block.IsI4x4) + // Replicate the top-right pixels below. + + + // Predict and add residuals for all 4x4 blocks in turn. + for (int n = 0; n < 16; ++n, bits <<= 2) { - if (mby > 0) + // uint8_t * const dst = y_dst + kScan[n]; + byte lumaMode = block.Modes[n]; + switch (lumaMode) { - if (mbx >= dec.MbWidth - 1) - { - // On rightmost border. - //memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); - } - else - { - // memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); - } + case 0: + break; + case 1: + break; + case 2: + break; + case 3: + break; + case 4: + break; + case 5: + break; + case 6: + break; + case 7: + break; + case 8: + break; + case 9: + break; } - // Replicate the top-right pixels below. - + //DoTransform(bits, coeffs + n * 16, dst); + } + } + else + { + // 16x16 + int mode = CheckMode(mbx, mby, block.Modes[0]); + switch (mode) + { + case 0: + LossyUtils.DC16_C(yDst, yuv, yOff); + break; + case 1: + LossyUtils.TM16_C(yDst); + break; + case 2: + LossyUtils.VE16_C(yDst, yuv, yOff); + break; + case 3: + LossyUtils.HE16_C(yDst, yuv, yOff); + break; + case 4: + LossyUtils.DC16NoTop_C(yDst, yuv, yOff); + break; + case 5: + LossyUtils.DC16NoLeft_C(yDst, yuv, yOff); + break; + case 6: + LossyUtils.DC16NoTopLeft_C(yDst); + break; + } - // Predict and add residuals for all 4x4 blocks in turn. + if (bits != 0) + { for (int n = 0; n < 16; ++n, bits <<= 2) { - + this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebPConstants.KScan[n])); } } - else - { - // 16x16 + } - } + // Chroma + uint bitsUv = block.NonZeroUv; + int chromaMode = CheckMode(mbx, mby, block.UvMode); + switch (chromaMode) + { + case 0: + LossyUtils.DC8uv_C(uDst, yuv, uOff); + LossyUtils.DC8uv_C(vDst, yuv, vOff); + break; + case 1: + LossyUtils.TM8uv_C(uDst); + LossyUtils.TM8uv_C(vDst); + break; + case 2: + LossyUtils.VE8uv_C(uDst, yuv.AsSpan(uOff - WebPConstants.Bps, 8)); + LossyUtils.VE8uv_C(vDst, yuv.AsSpan(vOff - WebPConstants.Bps, 8)); + break; + case 3: + LossyUtils.HE8uv_C(uDst, yuv, uOff); + LossyUtils.HE8uv_C(vDst, yuv, vOff); + break; + case 4: + LossyUtils.DC8uvNoTop_C(uDst, yuv, uOff); + LossyUtils.DC8uvNoTop_C(vDst, yuv, vOff); + break; + case 5: + LossyUtils.DC8uvNoLeft_C(uDst, yuv, uOff); + LossyUtils.DC8uvNoLeft_C(vDst, yuv, vOff); + break; + case 6: + LossyUtils.DC8uvNoTopLeft_C(uDst, yuv, uOff); + LossyUtils.DC8uvNoTopLeft_C(vDst, yuv, vOff); + break; + } + + this.DoUVTransform(bitsUv >> 0, coeffs.AsSpan(16 * 16), uDst); + this.DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst); + } + } + + private void DoTransform(uint bits, Span src, Span dst) + { + switch (bits >> 30) + { + case 3: + LossyUtils.Transform(src, dst, false); + break; + case 2: + LossyUtils.TransformAc3(src, dst); + break; + case 1: + LossyUtils.TransformDc(src, dst); + break; + default: + break; + } + } + + private void DoUVTransform(uint bits, Span src, Span dst) + { + // any non-zero coeff at all? + if ((bits & 0xff) > 0) + { + // any non-zero AC coefficient? + if ((bits & 0xaa) > 0) + { + LossyUtils.TransformUv(src, dst); // note we don't use the AC3 variant for U/V. + } + else + { + LossyUtils.TransformDcuv(src, dst); } } } @@ -862,6 +1001,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return bandsRow; } + private static int CheckMode(int mbx, int mby, int mode) + { + // B_DC_PRED + if (mode is 0) + { + if (mbx is 0) + { + return (mby is 0) + ? 6 // B_DC_PRED_NOTOPLEFT + : 5; // B_DC_PRED_NOLEFT + } + + return (mby is 0) + ? 4 // B_DC_PRED_NOTOP + : 0; // B_DC_PRED + } + + return mode; + } + private static int Clip(int value, int max) { return value < 0 ? 0 : value > max ? max : value; From 6a1b61819f6f44f8d16bc3442f37770b33149787 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Feb 2020 16:20:27 +0100 Subject: [PATCH 109/359] Fix some parsing mistakes --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 52 ++++++++- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 20 ++++ src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 18 +++ .../Formats/WebP/WebPLossyDecoder.cs | 104 ++++++++++++------ 4 files changed, 162 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index a4c488b61..6d824793e 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -177,6 +177,56 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } + public static void DC4_C(Span dst) + { + + } + + public static void TM4_C(Span dst) + { + + } + + public static void VE4_C(Span dst) + { + + } + + public static void HE4_C(Span dst) + { + + } + + public static void RD4_C(Span dst) + { + + } + + public static void VR4_C(Span dst) + { + + } + + public static void LD4_C(Span dst) + { + + } + + public static void VL4_C(Span dst) + { + + } + + public static void HD4_C(Span dst) + { + + } + + public static void HU4_C(Span dst) + { + + } + public static void Transform(Span src, Span dst, bool doTwo) { TransformOne(src, dst); @@ -311,7 +361,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private static byte Clip8B(int v) { - return (byte)((v & ~0xff) > 0 ? v : (v < 0) ? 0 : 255); + return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255); } private static void Put8x8uv(byte value, Span dst) diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index bec618ce8..1dabc349c 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -115,6 +115,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return bit ? 1 : 0; } + // simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) + public int GetSigned(int v) + { + if (this.bits < 0) + { + this.LoadNewBytes(); + } + + int pos = this.bits; + uint split = this.range >> 1; + ulong value = this.value >> pos; + ulong mask = (split - value) >> 31; // -1 or 0 + this.bits -= 1; + this.range += (uint)mask; + this.range |= 1; + this.value -= ((split + 1) & mask) << pos; + + return (v ^ (int)mask) - (int)mask; + } + public bool ReadBool() { return this.ReadValue(1) is 1; diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 872ddf36e..469eba3e9 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -44,6 +44,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + uint width = pictureHeader.Width; + uint height = pictureHeader.Height; + + // TODO: use memory allocator + this.Y = new byte[width * height]; + this.U = new byte[width * height]; + this.V = new byte[width * height]; + this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; this.Init(io); } @@ -139,6 +147,16 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8TopSamples[] YuvTopSamples { get; } + public byte[] Y { get; } + + public byte[] U { get; } + + public byte[] V { get; } + + public int YStride { get; } + + public int UvStride { get; } + /// /// Gets or sets filter strength info. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 725b5857a..d5b4d711a 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -63,14 +63,15 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ParsePartitions(decoder); // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(decoder.SegmentHeader); + this.ParseDequantizationIndices(decoder); // Ignore the value of update_proba this.bitReader.ReadBool(); // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(decoder, decoder.Probabilities); + this.ParseProbabilities(decoder); + // Decode image data. this.ParseFrame(decoder, vp8Io); } @@ -219,8 +220,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Init top-left sample on left column too. if (mby > 0) { - // TODO: - // yDst[-1 - WebPConstants.Bps] = uDst[-1 - WebPConstants.Bps] = vDst[-1 - WebPConstants.Bps] = 129; + yuv[yOff - 1 - WebPConstants.Bps] = yuv[uOff - 1 - WebPConstants.Bps] = yuv[vOff - 1 - WebPConstants.Bps] = 129; } else { @@ -286,12 +286,13 @@ namespace SixLabors.ImageSharp.Formats.WebP // Predict and add residuals. if (block.IsI4x4) { + // uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); if (mby > 0) { if (mbx >= dec.MbWidth - 1) { // On rightmost border. - //memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + // memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); } else { @@ -300,38 +301,49 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Replicate the top-right pixels below. - + // top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) { // uint8_t * const dst = y_dst + kScan[n]; + Span dst = yDst.Slice(WebPConstants.KScan[n]); byte lumaMode = block.Modes[n]; switch (lumaMode) { case 0: + LossyUtils.DC4_C(dst); break; case 1: + LossyUtils.TM4_C(dst); break; case 2: + LossyUtils.VE4_C(dst); break; case 3: + LossyUtils.HE4_C(dst); break; case 4: + LossyUtils.RD4_C(dst); break; case 5: + LossyUtils.VR4_C(dst); break; case 6: + LossyUtils.LD4_C(dst); break; case 7: + LossyUtils.VL4_C(dst); break; case 8: + LossyUtils.HD4_C(dst); break; case 9: + LossyUtils.HU4_C(dst); break; } - //DoTransform(bits, coeffs + n * 16, dst); + this.DoTransform(bits, coeffs.AsSpan(n * 16), dst); } } else @@ -409,6 +421,32 @@ namespace SixLabors.ImageSharp.Formats.WebP this.DoUVTransform(bitsUv >> 0, coeffs.AsSpan(16 * 16), uDst); this.DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst); + + // Stash away top samples for next block. + if (mby < dec.MbHeight - 1) + { + yDst.Slice(15 * WebPConstants.Bps, 16).CopyTo(topYuv.Y); + uDst.Slice(7 * WebPConstants.Bps, 8).CopyTo(topYuv.U); + vDst.Slice(7 * WebPConstants.Bps, 8).CopyTo(topYuv.V); + } + + // Transfer reconstructed samples from yuv_b_ cache to final destination. + int cacheId = 0; // TODO: what should be cacheId? + int yOffset = cacheId * 16 * dec.YStride; + int uvOffset = cacheId * 8 * dec.UvStride; + Span yOut = dec.Y.AsSpan((mbx * 16) + yOffset); + Span uOut = dec.U.AsSpan((mbx * 8) + uvOffset); + Span vOut = dec.V.AsSpan((mbx * 8) + uvOffset); + for (int j = 0; j < 16; ++j) + { + yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.YStride)); + } + + for (int j = 0; j < 8; ++j) + { + uDst.Slice(j * WebPConstants.Bps, 8).CopyTo(uOut); + vDst.Slice(j * WebPConstants.Bps, 8).CopyTo(vOut); + } } } @@ -504,13 +542,13 @@ namespace SixLabors.ImageSharp.Formats.WebP uint nonZeroY = 0; uint nonZeroUv = 0; int first; - var dst = new short[384]; int dstOffset = 0; Vp8MacroBlockData block = dec.MacroBlockData[dec.MbX]; Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; Vp8BandProbas[,] bands = dec.Probabilities.BandsPtr; Vp8BandProbas[] acProba; Vp8MacroBlock leftMb = dec.MacroBlockInfo[0]; + short[] dst = block.Coeffs; if (!block.IsI4x4) { @@ -519,7 +557,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); int nz = this.GetCoeffs(br, GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc); mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); - if (nz > 0) + if (nz > 1) { // More than just the DC -> perform the full transform. this.TransformWht(dc, dst); @@ -534,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } first = 1; - acProba = GetBandsRow(bands, 1); + acProba = GetBandsRow(bands, 0); } else { @@ -615,14 +653,14 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8ProbaArray p = prob[n].Probabilities[ctx]; for (; n < 16; ++n) { - if (this.bitReader.GetBit((int)p.Probabilities[0]) is 0) + if (br.GetBit(p.Probabilities[0]) is 0) { // Previous coeff was last non - zero coeff. return n; } // Sequence of zero coeffs. - while (br.GetBit((int)p.Probabilities[1]) is 0) + while (br.GetBit(p.Probabilities[1]) is 0) { p = prob[++n].Probabilities[0]; if (n is 16) @@ -633,57 +671,57 @@ namespace SixLabors.ImageSharp.Formats.WebP // Non zero coeffs. int v; - if (br.GetBit((int)p.Probabilities[2]) is 0) + if (br.GetBit(p.Probabilities[2]) is 0) { v = 1; p = prob[n + 1].Probabilities[1]; } else { - v = this.GetLargeValue(p.Probabilities); + v = this.GetLargeValue(br, p.Probabilities); p = prob[n + 1].Probabilities[2]; } int idx = n > 0 ? 1 : 0; - coeffs[WebPConstants.Zigzag[n]] = (short)(br.ReadSignedValue(v) * dq[idx]); + coeffs[WebPConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); } return 16; } - private int GetLargeValue(byte[] p) + private int GetLargeValue(Vp8BitReader br, byte[] p) { // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 int v; - if (this.bitReader.GetBit(p[3]) is 0) + if (br.GetBit(p[3]) is 0) { - if (this.bitReader.GetBit(p[4]) is 0) + if (br.GetBit(p[4]) is 0) { v = 2; } else { - v = 3 + this.bitReader.GetBit(p[5]); + v = 3 + br.GetBit(p[5]); } } else { - if (this.bitReader.GetBit(p[6]) is 0) + if (br.GetBit(p[6]) is 0) { - if (this.bitReader.GetBit(p[7]) is 0) + if (br.GetBit(p[7]) is 0) { - v = 5 + this.bitReader.GetBit(159); + v = 5 + br.GetBit(159); } else { - v = 7 + (2 * this.bitReader.GetBit(165)); - v += this.bitReader.GetBit(145); + v = 7 + (2 * br.GetBit(165)); + v += br.GetBit(145); } } else { - int bit1 = this.bitReader.GetBit(p[8]); - int bit0 = this.bitReader.GetBit(p[9] + bit1); + int bit1 = br.GetBit(p[8]); + int bit0 = br.GetBit(p[9] + bit1); int cat = (2 * bit1) + bit0; v = 0; byte[] tab = null; @@ -708,7 +746,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < tab.Length; i++) { - v += v + this.bitReader.GetBit(tab[i]); + v += v + br.GetBit(tab[i]); } v += 3 + (8 << cat); @@ -866,8 +904,10 @@ namespace SixLabors.ImageSharp.Formats.WebP dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); } - private void ParseDequantizationIndices(Vp8SegmentHeader vp8SegmentHeader) + private void ParseDequantizationIndices(Vp8Decoder decoder) { + Vp8SegmentHeader vp8SegmentHeader = decoder.SegmentHeader; + int baseQ0 = (int)this.bitReader.ReadValue(7); bool hasValue = this.bitReader.ReadBool(); int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; @@ -894,7 +934,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (i > 0) { - // dec->dqm_[i] = dec->dqm_[0]; + decoder.DeQuantMatrices[i] = decoder.DeQuantMatrices[0]; continue; } else @@ -903,7 +943,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - var m = new Vp8QuantMatrix(); + Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; m.Y1Mat[0] = WebPConstants.DcTable[Clip(q + dqy1Dc, 127)]; m.Y1Mat[1] = WebPConstants.AcTable[Clip(q + 0, 127)]; m.Y2Mat[0] = WebPConstants.DcTable[Clip(q + dqy2Dc, 127)] * 2; @@ -924,8 +964,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void ParseProbabilities(Vp8Decoder dec, Vp8Proba proba) + private void ParseProbabilities(Vp8Decoder dec) { + Vp8Proba proba = dec.Probabilities; + for (int t = 0; t < WebPConstants.NumTypes; ++t) { for (int b = 0; b < WebPConstants.NumBands; ++b) From 73611ea1f2305333274a89c332c12497dd2c712f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Feb 2020 18:09:33 +0100 Subject: [PATCH 110/359] Use memset equivalent in LossyUtils --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 45 ++++++++----------- .../Formats/WebP/WebPLossyDecoder.cs | 4 +- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 6d824793e..635e8bee1 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -11,11 +11,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int j = 0; j < 16; ++j) { - Span tmp = dst.Slice(j * WebPConstants.Bps); - for (int i = 0; i < 16; i++) - { - tmp[i] = (byte)v; - } + Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16); } } @@ -54,14 +50,10 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int j = 16; j > 0; --j) { // memset(dst, dst[-1], 16); - dst = dst.Slice(WebPConstants.Bps); byte v = yuv[offset - 1]; - for (int i = 0; i < 16; i++) - { - dst[i] = v; - } - + Memset(dst, v, 0, 16); offset += WebPConstants.Bps; + dst = dst.Slice(WebPConstants.Bps); } } @@ -131,10 +123,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { // memset(dst, dst[-1], 8); byte v = yuv[offset - 1]; - for (int i = 0; i < 8; i++) - { - yuv[offset + i] = v; - } + Memset(dst, v, 0, 8); + dst = dst.Slice(WebPConstants.Bps); } } @@ -164,17 +154,10 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } - public static void DC8uvNoTopLeft_C(Span dst, byte[] yuv, int offset) + public static void DC8uvNoTopLeft_C(Span dst) { - // DC with no top samples. - int dc0 = 4; - for (int i = 0; i < 8; ++i) - { - // dc0 += dst[-1 + i * BPS]; - dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)]; - } - - Put8x8uv((byte)(dc0 >> 3), dst); + // DC with nothing. + Put8x8uv(0x80, dst); } public static void DC4_C(Span dst) @@ -366,10 +349,18 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void Put8x8uv(byte value, Span dst) { - // memset(dst + j * BPS, value, 8); for (int j = 0; j < 8; ++j) { - dst[j * WebPConstants.Bps] = value; + // memset(dst + j * BPS, value, 8); + Memset(dst, value, j * WebPConstants.Bps, 8); + } + } + + private static void Memset(Span dst, byte value, int startIdx, int count) + { + for (int i = 0; i < count; i++) + { + dst[startIdx + i] = value; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index d5b4d711a..a3c8e409b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -414,8 +414,8 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.DC8uvNoLeft_C(vDst, yuv, vOff); break; case 6: - LossyUtils.DC8uvNoTopLeft_C(uDst, yuv, uOff); - LossyUtils.DC8uvNoTopLeft_C(vDst, yuv, vOff); + LossyUtils.DC8uvNoTopLeft_C(uDst); + LossyUtils.DC8uvNoTopLeft_C(vDst); break; } From f756d533b76c2cc1937d52a7d48341225d946b52 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Feb 2020 18:22:59 +0100 Subject: [PATCH 111/359] Fix a bug in TransformDcuv --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 635e8bee1..6ef3e8d82 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -298,22 +298,22 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void TransformDcuv(Span src, Span dst) { - if (src[0 * 16] > 0) + if (src[0 * 16] != 0) { TransformDc(src.Slice(0 * 16), dst); } - if (src[1 * 16] > 0) + if (src[1 * 16] != 0) { TransformDc(src.Slice(1 * 16), dst.Slice(4)); } - if (src[2 * 16] > 0) + if (src[2 * 16] != 0) { TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); } - if (src[3 * 16] > 0) + if (src[3 * 16] != 0) { TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebPConstants.Bps) + 4)); } From c3be7a66040846a7bebc94bf676f0a02a50fb4b9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 15 Feb 2020 18:44:43 +0100 Subject: [PATCH 112/359] Implementing FinishRow, EmitRgb --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 39 +++ src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 71 +++-- src/ImageSharp/Formats/WebP/Vp8Io.cs | 7 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 10 +- .../Formats/WebP/WebPLossyDecoder.cs | 285 ++++++++++++++++-- 5 files changed, 349 insertions(+), 63 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 6ef3e8d82..0c301f549 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -319,6 +319,39 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + // We process u and v together stashed into 32bit(16bit each). + public static uint LoadUv(byte u, byte v) + { + return (uint)(u | (v << 16)); + } + + public static void YuvToBgr(int y, int u, int v, Span bgr) + { + bgr[0] = (byte)YuvToB(y, u); + bgr[1] = (byte)YuvToG(y, u, v); + bgr[2] = (byte)YuvToR(y, v); + } + + public static int YuvToR(int y, int v) + { + return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); + } + + public static int YuvToG(int y, int u, int v) + { + return Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); + } + + public static int YuvToB(int y, int u) + { + return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); + } + + private static int MultHi(int v, int coeff) + { + return (v * coeff) >> 8; + } + private static void Store(Span dst, int x, int y, int v) { dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3)); @@ -347,6 +380,12 @@ namespace SixLabors.ImageSharp.Formats.WebP return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255); } + private static byte Clip8(int v) + { + int yuvMask = (256 << 6) - 1; + return (byte)(((v & ~yuvMask) is 0) ? (v >> 6) : (v < 0) ? 0 : 255); + } + private static void Put8x8uv(byte value, Span dst) { for (int j = 0; j < 8; ++j) diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 469eba3e9..3915e39d5 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -8,17 +8,19 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8Decoder { - public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8FilterHeader filterHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, Vp8Io io) + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, Vp8Io io) { + this.FilterHeader = new Vp8FilterHeader(); this.FrameHeader = frameHeader; this.PictureHeader = pictureHeader; - this.FilterHeader = filterHeader; this.SegmentHeader = segmentHeader; this.Probabilities = probabilities; this.IntraL = new byte[4]; this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); + this.CacheYStride = 16 * this.MbWidth; + this.CacheUvStride = 8 * this.MbWidth; this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; @@ -48,9 +50,13 @@ namespace SixLabors.ImageSharp.Formats.WebP uint height = pictureHeader.Height; // TODO: use memory allocator - this.Y = new byte[width * height]; - this.U = new byte[width * height]; - this.V = new byte[width * height]; + this.CacheY = new byte[width * height]; // TODO: this is way too much mem, figure out what the min req is. + this.CacheU = new byte[width * height]; + this.CacheV = new byte[width * height]; + this.TmpYBuffer = new byte[width * height]; // TODO: figure out min buffer length + this.TmpUBuffer = new byte[width * height]; // TODO: figure out min buffer length + this.TmpVBuffer = new byte[width * height]; // TODO: figure out min buffer length + this.Bgr = new byte[width * height * 4]; this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; this.Init(io); @@ -147,39 +153,58 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8TopSamples[] YuvTopSamples { get; } - public byte[] Y { get; } + public byte[] CacheY { get; } - public byte[] U { get; } + public byte[] CacheU { get; } - public byte[] V { get; } + public byte[] CacheV { get; } - public int YStride { get; } + public int CacheYStride { get; } - public int UvStride { get; } + public int CacheUvStride { get; } + + public byte[] TmpYBuffer { get; } + + public byte[] TmpUBuffer { get; } + + public byte[] TmpVBuffer { get; } + + public byte[] Bgr { get; } /// /// Gets or sets filter strength info. /// public Vp8FilterInfo[] FilterInfo { get; set; } + public Vp8MacroBlock CurrentMacroBlock + { + get + { + return this.MacroBlockInfo[this.MbX + 1]; + } + } + + public Vp8MacroBlock LeftMacroBlock + { + get + { + return this.MacroBlockInfo[this.MbX]; + } + } + + public Vp8MacroBlockData CurrentBlockData + { + get + { + return this.MacroBlockData[this.MbX]; + } + } + public void Init(Vp8Io io) { int intraPredModeSize = 4 * this.MbWidth; this.IntraT = new byte[intraPredModeSize]; - io.Width = (int)this.PictureHeader.Width; - io.Height = (int)this.PictureHeader.Height; - io.UseCropping = false; - io.CropTop = 0; - io.CropLeft = 0; - io.CropRight = io.Width; - io.CropBottom = io.Height; - io.UseScaling = false; - io.ScaledWidth = io.Width; - io.ScaledHeight = io.ScaledHeight; - io.MbW = io.Width; - io.MbH = io.Height; - int extraPixels = WebPConstants.FilterExtraRows[(int)this.Filter]; if (this.Filter is LoopFilter.Complex) { diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index 8ede28c08..60d97ad2b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -5,7 +5,6 @@ using System; namespace SixLabors.ImageSharp.Formats.WebP { - // from public ref struct Vp8Io { /// @@ -40,17 +39,17 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Rows to copy (in YUV format) /// - private Span Y { get; set; } + public Span Y { get; set; } /// /// Rows to copy (in YUV format) /// - private Span U { get; set; } + public Span U { get; set; } /// /// Rows to copy (in YUV format) /// - private Span V { get; set; } + public Span V { get; set; } /// /// Row stride for luma diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index e22ae8d34..906d89638 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -143,12 +143,12 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 }; - public static readonly short[] KScan = + public static readonly short[] Scan = { - 0 + 0 * Bps, 4 + 0 * Bps, 8 + 0 * Bps, 12 + 0 * Bps, - 0 + 4 * Bps, 4 + 4 * Bps, 8 + 4 * Bps, 12 + 4 * Bps, - 0 + 8 * Bps, 4 + 8 * Bps, 8 + 8 * Bps, 12 + 8 * Bps, - 0 + 12 * Bps, 4 + 12 * Bps, 8 + 12 * Bps, 12 + 12 * Bps + 0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps), + 0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps), + 0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps), + 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) }; // 31 ^ clz(i) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index a3c8e409b..219dc2145 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.2: color space and clamp type follow. sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1); - var vp8PictureHeader = new Vp8PictureHeader() + var pictureHeader = new Vp8PictureHeader() { Width = (uint)width, Height = (uint)height, @@ -53,11 +54,11 @@ namespace SixLabors.ImageSharp.Formats.WebP var proba = new Vp8Proba(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - // Paragraph 9.4: Parse the filter specs. - Vp8FilterHeader vp8FilterHeader = this.ParseFilterHeader(); + Vp8Io io = InitializeVp8Io(pictureHeader); + var decoder = new Vp8Decoder(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba, io); - var vp8Io = default(Vp8Io); - var decoder = new Vp8Decoder(info.Vp8FrameHeader, vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); + // Paragraph 9.4: Parse the filter specs. + this.ParseFilterHeader(decoder); // Paragraph 9.5: Parse partitions. this.ParsePartitions(decoder); @@ -72,7 +73,28 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ParseProbabilities(decoder); // Decode image data. - this.ParseFrame(decoder, vp8Io); + this.ParseFrame(decoder, io); + + this.DecodePixelValues(width, height, decoder.Bgr, pixels); + } + + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels) + where TPixel : struct, IPixel + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + int idx = ((y * width) + x) * 3; + byte b = pixelData[idx]; + byte g = pixelData[idx + 1]; + byte r = pixelData[idx + 2]; + color.FromRgba32(new Rgba32(r, g, b, 255)); + pixelRow[x] = color; + } + } } private void ParseFrame(Vp8Decoder dec, Vp8Io io) @@ -98,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.InitScanline(dec); // Reconstruct, filter and emit the row. - this.ProcessRow(dec); + this.ProcessRow(dec, io); } } @@ -184,12 +206,13 @@ namespace SixLabors.ImageSharp.Formats.WebP dec.MbX = 0; } - private void ProcessRow(Vp8Decoder dec) + private void ProcessRow(Vp8Decoder dec, Vp8Io io) { bool filterRow = (dec.Filter != LoopFilter.None) && (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); this.ReconstructRow(dec, filterRow); + this.FinishRow(dec, io); } private void ReconstructRow(Vp8Decoder dec, bool filterRow) @@ -307,7 +330,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int n = 0; n < 16; ++n, bits <<= 2) { // uint8_t * const dst = y_dst + kScan[n]; - Span dst = yDst.Slice(WebPConstants.KScan[n]); + Span dst = yDst.Slice(WebPConstants.Scan[n]); byte lumaMode = block.Modes[n]; switch (lumaMode) { @@ -379,7 +402,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int n = 0; n < 16; ++n, bits <<= 2) { - this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebPConstants.KScan[n])); + this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebPConstants.Scan[n])); } } } @@ -431,25 +454,209 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Transfer reconstructed samples from yuv_b_ cache to final destination. - int cacheId = 0; // TODO: what should be cacheId? - int yOffset = cacheId * 16 * dec.YStride; - int uvOffset = cacheId * 8 * dec.UvStride; - Span yOut = dec.Y.AsSpan((mbx * 16) + yOffset); - Span uOut = dec.U.AsSpan((mbx * 8) + uvOffset); - Span vOut = dec.V.AsSpan((mbx * 8) + uvOffset); + int cacheId = 0; // TODO: what should be cacheId, always 0? + int yOffset = cacheId * 16 * dec.CacheYStride; + int uvOffset = cacheId * 8 * dec.CacheUvStride; + Span yOut = dec.CacheY.AsSpan((mbx * 16) + yOffset); + Span uOut = dec.CacheU.AsSpan((mbx * 8) + uvOffset); + Span vOut = dec.CacheV.AsSpan((mbx * 8) + uvOffset); for (int j = 0; j < 16; ++j) { - yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.YStride)); + yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.CacheYStride)); } for (int j = 0; j < 8; ++j) { - uDst.Slice(j * WebPConstants.Bps, 8).CopyTo(uOut); - vDst.Slice(j * WebPConstants.Bps, 8).CopyTo(vOut); + uDst.Slice(j * WebPConstants.Bps, 8).CopyTo(uOut.Slice(j * dec.CacheUvStride)); + vDst.Slice(j * WebPConstants.Bps, 8).CopyTo(vOut.Slice(j * dec.CacheUvStride)); } } } + private void FinishRow(Vp8Decoder dec, Vp8Io io) + { + int cacheId = 0; + int yBps = dec.CacheYStride; + int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; + int ySize = extraYRows * dec.CacheYStride; + int uvSize = (extraYRows / 2) * dec.CacheUvStride; + int yOffset = cacheId * 16 * dec.CacheYStride; + int uvOffset = cacheId * 8 * dec.CacheUvStride; + Span yDst = dec.CacheY.AsSpan(); + Span uDst = dec.CacheU.AsSpan(); + Span vDst = dec.CacheV.AsSpan(); + int mby = dec.MbY; + bool isFirstRow = mby is 0; + bool isLastRow = mby >= dec.BottomRightMbY - 1; + + // TODO: Filter row + //FilterRow(dec); + + int yStart = mby * 16; + int yEnd = (mby + 1) * 16; + if (!isFirstRow) + { + yStart -= extraYRows; + io.Y = yDst; + io.U = uDst; + io.V = vDst; + } + else + { + io.Y = dec.CacheY.AsSpan(yOffset); + io.U = dec.CacheU.AsSpan(uvOffset); + io.V = dec.CacheV.AsSpan(uvOffset); + } + + if (!isLastRow) + { + yEnd -= extraYRows; + } + + if (yStart < yEnd) + { + io.Y = io.Y.Slice(io.CropLeft); + io.U = io.U.Slice(io.CropLeft); + io.V = io.V.Slice(io.CropLeft); + + io.MbY = yStart - io.CropTop; + io.MbW = io.CropRight - io.CropLeft; + io.MbH = yEnd - yStart; + this.EmitRgb(dec, io); + } + + // Rotate top samples if needed. + if (!isLastRow) + { + // TODO: double check this. + yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY); + uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU); + vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV); + } + } + + private int EmitRgb(Vp8Decoder dec, Vp8Io io) + { + byte[] buf = dec.Bgr; + int numLinesOut = io.MbH; // a priori guess. + Span curY = io.Y; + Span curU = io.U; + Span curV = io.V; + byte[] tmpYBuffer = dec.TmpYBuffer; + byte[] tmpUBuffer = dec.TmpUBuffer; + byte[] tmpVBuffer = dec.TmpVBuffer; + Span topU = tmpUBuffer.AsSpan(); + Span topV = tmpVBuffer.AsSpan(); + int bpp = 3; + int bufferStride = bpp * io.Width; + int dstStartIdx = io.MbY * bufferStride; + Span dst = buf.AsSpan(dstStartIdx); + int yEnd = io.MbY + io.MbH; + int mbw = io.MbW; + int uvw = (mbw + 1) / 2; + int y = io.MbY; + + if (y is 0) + { + // First line is special cased. We mirror the u/v samples at boundary. + this.UpSample(curY, null, curU, curV, curU, curV, dst, null, mbw); + } + else + { + // We can finish the left-over line from previous call. + this.UpSample(tmpYBuffer.AsSpan(), curY, topU, topV, curU, curV, buf.AsSpan(dstStartIdx - bufferStride), dst, mbw); + numLinesOut++; + } + + // Loop over each output pairs of row. + for (; y + 2 < yEnd; y += 2) + { + topU = curU; + topV = curV; + curU = curU.Slice(io.UvStride); + curV = curV.Slice(io.UvStride); + this.UpSample(curY.Slice(io.YStride), curY, topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(2 * bufferStride), mbw); + curY = curY.Slice(2 * io.YStride); + dst = dst.Slice(2 * bufferStride); + } + + // Move to last row. + curY = curY.Slice(io.YStride); + if (io.CropTop + yEnd < io.CropBottom) + { + // Save the unfinished samples for next call (as we're not done yet). + curY.Slice(0, mbw).CopyTo(tmpYBuffer); + curU.Slice(0, uvw).CopyTo(tmpUBuffer); + curV.Slice(0, uvw).CopyTo(tmpVBuffer); + + // The upsampler leaves a row unfinished behind (except for the very last row). + numLinesOut--; + } + else + { + // Process the very last row of even-sized picture. + if ((yEnd & 1) is 0) + { + this.UpSample(curY, null, curU, curV, curU, curV, dst.Slice(bufferStride), null, mbw); + } + } + + return numLinesOut; + } + + private void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + { + int xStep = 3; + int lastPixelPair = (len - 1) >> 1; + uint tluv = LossyUtils.LoadUv(topU[0], topV[0]); // top-left sample + uint luv = LossyUtils.LoadUv(curU[0], curV[0]); // left-sample + uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + LossyUtils.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); + + if (bottomY != null) + { + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + LossyUtils.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); + } + + for (int x = 1; x <= lastPixelPair; ++x) + { + uint tuv = LossyUtils.LoadUv(topU[x], topV[x]); // top sample + uint uv = LossyUtils.LoadUv(curU[x], curV[x]); // sample + + // Precompute invariant values associated with first and second diagonals. + uint avg = tluv + tuv + luv + uv + 0x00080008u; + uint diag12 = (avg + (2 * (tuv + luv))) >> 3; + uint diag03 = (avg + (2 * (tluv + uv))) >> 3; + uv0 = (diag12 + tluv) >> 1; + uint uv1 = (diag03 + tuv) >> 1; + LossyUtils.YuvToBgr(topY[(2 * x) - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice(((2 * x) - 1) * xStep)); + LossyUtils.YuvToBgr(topY[(2 * x) - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice(((2 * x) - 0) * xStep)); + + if (bottomY != null) + { + uv0 = (diag03 + luv) >> 1; + uv1 = (diag12 + uv) >> 1; + LossyUtils.YuvToBgr(bottomY[(2 * x) - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice(((2 * x) - 1) * xStep)); + LossyUtils.YuvToBgr(bottomY[(2 * x) + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice(((2 * x) + 0) * xStep)); + } + + tluv = tuv; + luv = uv; + } + + /*if ((len & 1) is 0) + { + uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + LossyUtils.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); + if (bottomY != null) + { + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + LossyUtils.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); + } + }*/ + } + private void DoTransform(uint bits, Span src, Span dst) { switch (bits >> 30) @@ -507,9 +714,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) { - Vp8MacroBlock left = dec.MacroBlockInfo[0]; - Vp8MacroBlock macroBlock = dec.MacroBlockInfo[1 + dec.MbX]; - Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MbX]; + Vp8MacroBlock left = dec.LeftMacroBlock; + Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; + Vp8MacroBlockData blockData = dec.CurrentBlockData; int skip = dec.UseSkipProbability ? blockData.Skip : 0; if (skip is 0) @@ -543,11 +750,11 @@ namespace SixLabors.ImageSharp.Formats.WebP uint nonZeroUv = 0; int first; int dstOffset = 0; - Vp8MacroBlockData block = dec.MacroBlockData[dec.MbX]; + Vp8MacroBlockData block = dec.CurrentBlockData; Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; Vp8BandProbas[,] bands = dec.Probabilities.BandsPtr; Vp8BandProbas[] acProba; - Vp8MacroBlock leftMb = dec.MacroBlockInfo[0]; + Vp8MacroBlock leftMb = dec.LeftMacroBlock; short[] dst = block.Coeffs; if (!block.IsI4x4) @@ -593,7 +800,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int nz = this.GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); l = (nz > first) ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 7)); - nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); + nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); dstOffset += 16; } @@ -619,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int nz = this.GetCoeffs(br, GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); l = (nz > 0) ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 3)); - nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); + nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); dstOffset += 16; } @@ -836,17 +1043,15 @@ namespace SixLabors.ImageSharp.Formats.WebP return vp8SegmentHeader; } - private Vp8FilterHeader ParseFilterHeader() + private Vp8FilterHeader ParseFilterHeader(Vp8Decoder dec) { - var vp8FilterHeader = new Vp8FilterHeader(); + Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; vp8FilterHeader.Level = (int)this.bitReader.ReadValue(6); vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); - // TODO: use enum here? - // 0 = 0ff, 1 = simple, 2 = complex - int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; + dec.Filter = (vp8FilterHeader.Level is 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; if (vp8FilterHeader.UseLfDelta) { // Update lf-delta? @@ -998,6 +1203,24 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static Vp8Io InitializeVp8Io(Vp8PictureHeader pictureHeader) + { + var io = default(Vp8Io); + io.Width = (int)pictureHeader.Width; + io.Height = (int)pictureHeader.Height; + io.UseCropping = false; + io.CropTop = 0; + io.CropLeft = 0; + io.CropRight = io.Width; + io.CropBottom = io.Height; + io.UseScaling = false; + io.ScaledWidth = io.Width; + io.ScaledHeight = io.ScaledHeight; + io.MbW = io.Width; + io.MbH = io.Height; + return io; + } + static bool Is8bOptimizable(Vp8LMetadata hdr) { int i; From 15b2fd81cd4be4bb9a6ce1b45beb6dd2b0e8ad2e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 16 Feb 2020 17:36:26 +0100 Subject: [PATCH 113/359] Fix modes probabilities --- src/ImageSharp/Formats/WebP/WebPConstants.cs | 104 ---------------- .../Formats/WebP/WebPLossyDecoder.cs | 114 +++++++++++++++++- 2 files changed, 111 insertions(+), 107 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 906d89638..64afc2fa0 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -234,110 +234,6 @@ namespace SixLabors.ImageSharp.Formats.WebP -8, -9 }; - // Paragraph 11.5 - public static readonly byte[,][] BModesProba = { - { new byte[] { 0, 0 }, new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 } }, - { new byte[] { 0, 1 }, new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 } }, - { new byte[] { 0, 2 }, new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 } }, - { new byte[] { 0, 3 }, new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 } }, - { new byte[] { 0, 4 }, new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 } }, - { new byte[] { 0, 5 }, new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 } }, - { new byte[] { 0, 6 }, new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 } }, - { new byte[] { 0, 7 }, new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 } }, - { new byte[] { 0, 8 }, new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 } }, - { new byte[] { 0, 9 }, new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, - { new byte[] { 1, 0 }, new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 } }, - { new byte[] { 1, 1 }, new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 } }, - { new byte[] { 1, 2 }, new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 } }, - { new byte[] { 1, 3 }, new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 } }, - { new byte[] { 1, 4 }, new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 } }, - { new byte[] { 1, 5 }, new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 } }, - { new byte[] { 1, 6 }, new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 } }, - { new byte[] { 1, 7 }, new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 } }, - { new byte[] { 1, 8 }, new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 } }, - { new byte[] { 1, 9 }, new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, - { new byte[] { 2, 0 }, new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 } }, - { new byte[] { 2, 1 }, new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 } }, - { new byte[] { 2, 2 }, new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 } }, - { new byte[] { 2, 3 }, new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 } }, - { new byte[] { 2, 4 }, new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 } }, - { new byte[] { 2, 5 }, new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 } }, - { new byte[] { 2, 6 }, new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 } }, - { new byte[] { 2, 7 }, new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 } }, - { new byte[] { 2, 8 }, new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 } }, - { new byte[] { 2, 9 }, new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, - { new byte[] { 3, 0 }, new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 } }, - { new byte[] { 3, 1 }, new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 } }, - { new byte[] { 3, 2 }, new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 } }, - { new byte[] { 3, 3 }, new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 } }, - { new byte[] { 3, 4 }, new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 } }, - { new byte[] { 3, 5 }, new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 } }, - { new byte[] { 3, 6 }, new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 } }, - { new byte[] { 3, 7 }, new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 } }, - { new byte[] { 3, 8 }, new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 } }, - { new byte[] { 3, 9 }, new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, - { new byte[] { 4, 0 }, new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 } }, - { new byte[] { 4, 1 }, new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 } }, - { new byte[] { 4, 2 }, new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 } }, - { new byte[] { 4, 3 }, new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 } }, - { new byte[] { 4, 4 }, new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 } }, - { new byte[] { 4, 5 }, new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 } }, - { new byte[] { 4, 6 }, new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 } }, - { new byte[] { 4, 7 }, new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 } }, - { new byte[] { 4, 8 }, new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 } }, - { new byte[] { 4, 9 }, new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, - { new byte[] { 5, 0 }, new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 } }, - { new byte[] { 5, 1 }, new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 } }, - { new byte[] { 5, 2 }, new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 } }, - { new byte[] { 5, 3 }, new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 } }, - { new byte[] { 5, 4 }, new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 } }, - { new byte[] { 5, 5 }, new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 } }, - { new byte[] { 5, 6 }, new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 } }, - { new byte[] { 5, 7 }, new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 } }, - { new byte[] { 5, 8 }, new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 } }, - { new byte[] { 5, 9 }, new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, - { new byte[] { 6, 0 }, new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 } }, - { new byte[] { 6, 1 }, new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 } }, - { new byte[] { 6, 2 }, new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 } }, - { new byte[] { 6, 3 }, new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 } }, - { new byte[] { 6, 4 }, new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 } }, - { new byte[] { 6, 5 }, new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 } }, - { new byte[] { 6, 6 }, new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 } }, - { new byte[] { 6, 7 }, new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 } }, - { new byte[] { 6, 8 }, new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 } }, - { new byte[] { 6, 9 }, new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, - { new byte[] { 7, 0 }, new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 } }, - { new byte[] { 7, 1 }, new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 } }, - { new byte[] { 7, 2 }, new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 } }, - { new byte[] { 7, 3 }, new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 } }, - { new byte[] { 7, 4 }, new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 } }, - { new byte[] { 7, 5 }, new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 } }, - { new byte[] { 7, 6 }, new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 } }, - { new byte[] { 7, 7 }, new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 } }, - { new byte[] { 7, 8 }, new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 } }, - { new byte[] { 7, 9 }, new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, - { new byte[] { 8, 0 }, new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 } }, - { new byte[] { 8, 1 }, new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 } }, - { new byte[] { 8, 2 }, new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 } }, - { new byte[] { 8, 3 }, new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 } }, - { new byte[] { 8, 4 }, new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 } }, - { new byte[] { 8, 5 }, new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 } }, - { new byte[] { 8, 6 }, new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 } }, - { new byte[] { 8, 7 }, new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 } }, - { new byte[] { 8, 8 }, new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 } }, - { new byte[] { 8, 9 }, new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, - { new byte[] { 9, 0 }, new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 } }, - { new byte[] { 9, 1 }, new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 } }, - { new byte[] { 9, 2 }, new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 } }, - { new byte[] { 9, 3 }, new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 } }, - { new byte[] { 9, 4 }, new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 } }, - { new byte[] { 9, 5 }, new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 } }, - { new byte[] { 9, 6 }, new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 } }, - { new byte[] { 9, 7 }, new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 } }, - { new byte[] { 9, 8 }, new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 } }, - { new byte[] { 9, 9 }, new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 } }, - }; - // Paragraph 13 public static readonly byte[,,,] CoeffsUpdateProba = { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 219dc2145..a572915c0 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -18,10 +17,13 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly MemoryAllocator memoryAllocator; + private readonly byte[,][] bModesProba = new byte[10, 10][]; + public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.bitReader = bitReader; + this.InitializeModesProbabilities(); } public void Decode(Buffer2D pixels, int width, int height, WebPImageInfo info) @@ -170,7 +172,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int yMode = left[y]; for (int x = 0; x < 4; ++x) { - byte[] prob = WebPConstants.BModesProba[top[x], yMode]; + byte[] prob = this.bModesProba[top[x], yMode]; int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; while (i > 0) { @@ -1182,7 +1184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int p = 0; p < WebPConstants.NumProbas; ++p) { byte prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; - int v = this.bitReader.GetBit(prob) > 0 + int v = this.bitReader.GetBit(prob) != 0 ? (int)this.bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; @@ -1290,6 +1292,112 @@ namespace SixLabors.ImageSharp.Formats.WebP { return value < 0 ? 0 : value > max ? max : value; } + + private void InitializeModesProbabilities() + { + // Paragraph 11.5 + this.bModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; + this.bModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; + this.bModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; + this.bModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; + this.bModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; + this.bModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; + this.bModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; + this.bModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; + this.bModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; + this.bModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; + this.bModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; + this.bModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; + this.bModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; + this.bModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; + this.bModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; + this.bModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; + this.bModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; + this.bModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; + this.bModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; + this.bModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; + this.bModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; + this.bModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; + this.bModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; + this.bModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; + this.bModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; + this.bModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; + this.bModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; + this.bModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; + this.bModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; + this.bModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; + this.bModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; + this.bModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; + this.bModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; + this.bModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; + this.bModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; + this.bModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; + this.bModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; + this.bModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; + this.bModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; + this.bModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; + this.bModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; + this.bModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; + this.bModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; + this.bModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; + this.bModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; + this.bModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; + this.bModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; + this.bModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; + this.bModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; + this.bModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; + this.bModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; + this.bModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; + this.bModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; + this.bModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; + this.bModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; + this.bModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; + this.bModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; + this.bModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; + this.bModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; + this.bModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; + this.bModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; + this.bModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; + this.bModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; + this.bModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; + this.bModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; + this.bModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; + this.bModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; + this.bModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; + this.bModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; + this.bModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; + this.bModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; + this.bModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; + this.bModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; + this.bModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; + this.bModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; + this.bModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; + this.bModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; + this.bModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; + this.bModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; + this.bModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; + this.bModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; + this.bModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; + this.bModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; + this.bModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; + this.bModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; + this.bModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; + this.bModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; + this.bModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; + this.bModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; + this.bModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; + this.bModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; + this.bModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; + this.bModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; + this.bModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; + this.bModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; + this.bModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; + this.bModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; + this.bModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; + this.bModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; + this.bModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; + } + } struct YUVPixel From c55334bad6fa1ce2c3d2fff3724cfa2635cf3cfc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 17 Feb 2020 16:27:03 +0100 Subject: [PATCH 114/359] Implement functions for luma modes --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 309 +++++++++++++++--- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 2 - src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 25 +- .../Formats/WebP/WebPLossyDecoder.cs | 57 ++-- 4 files changed, 319 insertions(+), 74 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 0c301f549..2c1ae62f9 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; namespace SixLabors.ImageSharp.Formats.WebP { @@ -18,11 +19,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void DC16_C(Span dst, byte[] yuv, int offset) { int dc = 16; - int j; - for (j = 0; j < 16; ++j) + for (int j = 0; j < 16; ++j) { // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[-1 + (j * WebPConstants.Bps) + offset] + yuv[j - WebPConstants.Bps + offset]; + dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps]; } Put16(dc >> 5, dst); @@ -106,9 +106,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // TrueMotion } - public static void VE8uv_C(Span dst, Span src) + public static void VE8uv_C(Span dst, byte[] yuv, int offset) { // vertical + Span src = yuv.AsSpan(offset - WebPConstants.Bps, 8); + for (int j = 0; j < 8; ++j) { // memcpy(dst + j * BPS, dst - BPS, 8); @@ -122,9 +124,11 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int j = 0; j < 8; ++j) { // memset(dst, dst[-1], 8); + // dst += BPS; byte v = yuv[offset - 1]; Memset(dst, v, 0, 8); dst = dst.Slice(WebPConstants.Bps); + offset += WebPConstants.Bps; } } @@ -160,9 +164,19 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv(0x80, dst); } - public static void DC4_C(Span dst) + public static void DC4_C(Span dst, byte[] yuv, int offset) { + int dc = 4; + for (int i = 0; i < 4; ++i) + { + dc += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; + } + dc >>= 3; + for (int i = 0; i < 4; ++i) + { + Memset(dst, (byte)dc, i * WebPConstants.Bps, 4); + } } public static void TM4_C(Span dst) @@ -170,44 +184,249 @@ namespace SixLabors.ImageSharp.Formats.WebP } - public static void VE4_C(Span dst) - { - - } - - public static void HE4_C(Span dst) - { - - } - - public static void RD4_C(Span dst) - { - - } - - public static void VR4_C(Span dst) + public static void VE4_C(Span dst, byte[] yuv, int offset) { + // vertical + int topOffset = offset - WebPConstants.Bps; + byte[] vals = + { + Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]), + Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]), + Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]), + Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]) + }; + for (int i = 0; i < 4; ++i) + { + vals.CopyTo(dst.Slice(i * WebPConstants.Bps)); + } } - public static void LD4_C(Span dst) - { - - } - - public static void VL4_C(Span dst) - { - - } - - public static void HD4_C(Span dst) - { - - } - - public static void HU4_C(Span dst) + public static void HE4_C(Span dst, byte[] yuv, int offset) { - + // horizontal + byte A = yuv[offset - 1 - WebPConstants.Bps]; + byte B = yuv[offset - 1]; + byte C = yuv[offset - 1 + WebPConstants.Bps]; + byte D = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte E = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + uint val = 0x01010101U * Avg3(A, B, C); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * Avg3(B, C, D); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebPConstants.Bps), val); + val = 0x01010101U * Avg3(C, D, E); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + val = 0x01010101U * Avg3(D, E, E); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); + } + + public static void RD4_C(Span dst, byte[] yuv, int offset) + { + // Down-right + byte I = yuv[offset - 1]; + byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte X = yuv[offset - 1 - WebPConstants.Bps]; + byte A = yuv[offset - WebPConstants.Bps]; + byte B = yuv[offset + 1 - WebPConstants.Bps]; + byte C = yuv[offset + 2 - WebPConstants.Bps]; + byte D = yuv[offset + 3 - WebPConstants.Bps]; + + Dst(dst, 0, 3, Avg3(J, K, L)); + byte ijk = Avg3(I, J, K); + Dst(dst, 1, 3, ijk); + Dst(dst, 0, 2, ijk); + byte xij = Avg3(X, I, J); + Dst(dst, 2, 3, xij); + Dst(dst, 1, 2, xij); + Dst(dst, 0, 1, xij); + byte axi = Avg3(A, X, I); + Dst(dst, 3, 3, axi); + Dst(dst, 2, 2, axi); + Dst(dst, 1, 1, axi); + Dst(dst, 0, 0, axi); + byte bax = Avg3(B, A, X); + Dst(dst, 3, 2, bax); + Dst(dst, 2, 1, bax); + Dst(dst, 1, 0, bax); + byte cba = Avg3(C, B, A); + Dst(dst, 3, 1, cba); + Dst(dst, 2, 0, cba); + Dst(dst, 3, 0, Avg3(D, C, B)); + } + + public static void VR4_C(Span dst, byte[] yuv, int offset) + { + // Vertical-Right + byte I = yuv[offset - 1]; + byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte X = yuv[offset - 1 - WebPConstants.Bps]; + byte A = yuv[offset - WebPConstants.Bps]; + byte B = yuv[offset + 1 - WebPConstants.Bps]; + byte C = yuv[offset + 2 - WebPConstants.Bps]; + byte D = yuv[offset + 3 - WebPConstants.Bps]; + + byte xa = Avg2(X, A); + Dst(dst, 0, 0, xa); + Dst(dst, 1, 2, xa); + byte ab = Avg2(A, B); + Dst(dst, 1, 0, ab); + Dst(dst, 2, 2, ab); + byte bc = Avg2(B, C); + Dst(dst, 2, 0, bc); + Dst(dst, 3, 2, bc); + Dst(dst, 3, 0, Avg2(C, D)); + Dst(dst, 0, 3, Avg3(K, I, J)); + Dst(dst, 0, 2, Avg3(J, I, X)); + byte ixa = Avg3(I, X, A); + Dst(dst, 0, 1, ixa); + Dst(dst, 1, 3, ixa); + byte xab = Avg3(X, A, B); + Dst(dst, 1, 1, xab); + Dst(dst, 2, 3, xab); + byte abc = Avg3(A, B, C); + Dst(dst, 2, 1, abc); + Dst(dst, 3, 3, abc); + Dst(dst, 3, 1, Avg3(B, C, D)); + } + + public static void LD4_C(Span dst, byte[] yuv, int offset) + { + // Down-Left + byte A = yuv[offset - WebPConstants.Bps]; + byte B = yuv[offset + 1 - WebPConstants.Bps]; + byte C = yuv[offset + 2 - WebPConstants.Bps]; + byte D = yuv[offset + 3 - WebPConstants.Bps]; + byte E = yuv[offset + 4 - WebPConstants.Bps]; + byte F = yuv[offset + 5 - WebPConstants.Bps]; + byte G = yuv[offset + 6 - WebPConstants.Bps]; + byte H = yuv[offset + 7 - WebPConstants.Bps]; + + Dst(dst, 0, 0, Avg3(A, B, C)); + byte bcd = Avg3(B, C, D); + Dst(dst, 1, 0, bcd); + Dst(dst, 0, 1, bcd); + byte cde = Avg3(C, D, E); + Dst(dst, 2, 0, cde); + Dst(dst, 1, 1, cde); + Dst(dst, 0, 2, cde); + byte def = Avg3(D, E, F); + Dst(dst, 3, 0, def); + Dst(dst, 2, 1, def); + Dst(dst, 1, 2, def); + Dst(dst, 0, 3, def); + byte efg = Avg3(E, F, G); + Dst(dst, 3, 1, efg); + Dst(dst, 2, 2, efg); + Dst(dst, 1, 3, efg); + byte fgh = Avg3(F, G, H); + Dst(dst, 3, 2, fgh); + Dst(dst, 2, 3, fgh); + Dst(dst, 3, 3, Avg3(G, H, H)); + } + + public static void VL4_C(Span dst, byte[] yuv, int offset) + { + // Vertical-Left + byte A = yuv[offset - WebPConstants.Bps]; + byte B = yuv[offset + 1 - WebPConstants.Bps]; + byte C = yuv[offset + 2 - WebPConstants.Bps]; + byte D = yuv[offset + 3 - WebPConstants.Bps]; + byte E = yuv[offset + 4 - WebPConstants.Bps]; + byte F = yuv[offset + 5 - WebPConstants.Bps]; + byte G = yuv[offset + 6 - WebPConstants.Bps]; + byte H = yuv[offset + 7 - WebPConstants.Bps]; + + Dst(dst, 0, 0, Avg2(A, B)); + byte bc = Avg2(B, C); + Dst(dst, 1, 0, bc); + Dst(dst, 0, 2, bc); + byte cd = Avg2(C, D); + Dst(dst, 2, 0, cd); + Dst(dst, 1, 2, cd); + byte de = Avg2(D, E); + Dst(dst, 3, 0, de); + Dst(dst, 2, 2, de); + Dst(dst, 0, 1, Avg3(A, B, C)); + byte bcd = Avg3(B, C, D); + Dst(dst, 1, 1, bcd); + Dst(dst, 0, 3, bcd); + byte cde = Avg3(C, D, E); + Dst(dst, 2, 1, cde); + Dst(dst, 1, 3, cde); + byte def = Avg3(D, E, F); + Dst(dst, 3, 1, def); + Dst(dst, 2, 3, def); + Dst(dst, 3, 2, Avg3(E, F, G)); + Dst(dst, 3, 3, Avg3(F, G, H)); + } + + public static void HD4_C(Span dst, byte[] yuv, int offset) + { + // Horizontal-Down + byte I = yuv[offset - 1]; + byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte X = yuv[offset - 1 - WebPConstants.Bps]; + byte A = yuv[offset - WebPConstants.Bps]; + byte B = yuv[offset + 1 - WebPConstants.Bps]; + byte C = yuv[offset + 2 - WebPConstants.Bps]; + + byte ix = Avg2(I, X); + Dst(dst, 0, 0, ix); + Dst(dst, 2, 1, ix); + byte ji = Avg2(J, I); + Dst(dst, 0, 1, ji); + Dst(dst, 2, 2, ji); + byte kj = Avg2(K, J); + Dst(dst, 0, 2, kj); + Dst(dst, 2, 3, kj); + Dst(dst, 0, 3, Avg2(L, K)); + Dst(dst, 3, 0, Avg3(A, B, C)); + Dst(dst, 2, 0, Avg3(X, A, B)); + byte ixa = Avg3(I, X, A); + Dst(dst, 1, 0, ixa); + Dst(dst, 3, 1, ixa); + byte jix = Avg3(J, I, X); + Dst(dst, 1, 1, jix); + Dst(dst, 3, 2, jix); + byte kji = Avg3(K, J, I); + Dst(dst, 1, 2, kji); + Dst(dst, 3, 3, kji); + Dst(dst, 1, 3, Avg3(L, K, J)); + } + + public static void HU4_C(Span dst, byte[] yuv, int offset) + { + // Horizontal-Up + byte I = yuv[offset - 1]; + byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + + Dst(dst, 0, 0, Avg2(I, J)); + byte jk = Avg2(J, K); + Dst(dst, 2, 0, jk); + Dst(dst, 0, 1, jk); + byte kl = Avg2(K, L); + Dst(dst, 2, 1, kl); + Dst(dst, 0, 2, kl); + Dst(dst, 1, 0, Avg3(I, J, K)); + byte jkl = Avg3(J, K, L); + Dst(dst, 3, 0, jkl); + Dst(dst, 1, 1, jkl); + byte kll = Avg3(K, L, L); + Dst(dst, 3, 1, kll); + Dst(dst, 1, 2, kll); + Dst(dst, 3, 2, L); + Dst(dst, 2, 2, L); + Dst(dst, 0, 3, L); + Dst(dst, 1, 3, L); + Dst(dst, 2, 3, L); + Dst(dst, 3, 3, L); } public static void Transform(Span src, Span dst, bool doTwo) @@ -215,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.WebP TransformOne(src, dst); if (doTwo) { - TransformOne(src, dst); + TransformOne(src.Slice(16), dst.Slice(4)); } } @@ -229,8 +448,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // vertical pass int a = src[srcOffset] + src[srcOffset + 8]; // [-4096, 4094] int b = src[srcOffset] - src[srcOffset + 8]; // [-4095, 4095] - int c = Mul2(src[4]) - Mul1(src[12]); // [-3783, 3783] - int d = Mul1(src[4]) + Mul2(src[12]); // [-3785, 3781] + int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]); // [-3783, 3783] + int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]); // [-3785, 3781] tmp[tmpOffset] = a + d; // [-7881, 7875] tmp[tmpOffset + 1] = b + c; // [-7878, 7878] tmp[tmpOffset + 2] = b - c; // [-7878, 7878] @@ -276,7 +495,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // Simplified transform when only in[0], in[1] and in[4] are non-zero + // Simplified transform when only src[0], src[1] and src[4] are non-zero public static void TransformAc3(Span src, Span dst) { int a = src[0] + 4; @@ -330,6 +549,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bgr[0] = (byte)YuvToB(y, u); bgr[1] = (byte)YuvToG(y, u, v); bgr[2] = (byte)YuvToR(y, v); + int tmp = 0; } public static int YuvToR(int y, int v) @@ -412,5 +632,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { return (byte)((a + (2 * b) + c + 2) >> 2); } + + private static void Dst(Span dst, int x, int y, byte v) + { + dst[x + (y * WebPConstants.Bps)] = v; + } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 1dabc349c..85c762ea0 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -84,8 +84,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public int GetBit(int prob) { - Guard.MustBeGreaterThan(prob, 0, nameof(prob)); - uint range = this.range; if (this.bits < 0) { diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 3915e39d5..57e163ccf 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.SegmentHeader = segmentHeader; this.Probabilities = probabilities; this.IntraL = new byte[4]; - this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; + this.YuvBuffer = new byte[2000]; // new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); this.CacheYStride = 16 * this.MbWidth; @@ -58,6 +58,18 @@ namespace SixLabors.ImageSharp.Formats.WebP this.TmpVBuffer = new byte[width * height]; // TODO: figure out min buffer length this.Bgr = new byte[width * height * 4]; + for (int i = 0; i < this.YuvBuffer.Length; i++) + { + this.YuvBuffer[i] = 205; + } + + for (int i = 0; i < this.CacheY.Length; i++) + { + this.CacheY[i] = 205; + this.CacheU[i] = 205; + this.CacheV[i] = 205; + } + this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; this.Init(io); } @@ -176,11 +188,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8FilterInfo[] FilterInfo { get; set; } + private Vp8MacroBlock leftMacroBlock; + public Vp8MacroBlock CurrentMacroBlock { get { - return this.MacroBlockInfo[this.MbX + 1]; + return this.MacroBlockInfo[this.MbX]; } } @@ -188,7 +202,12 @@ namespace SixLabors.ImageSharp.Formats.WebP { get { - return this.MacroBlockInfo[this.MbX]; + if (this.leftMacroBlock is null) + { + this.leftMacroBlock = new Vp8MacroBlock(); + } + + return this.leftMacroBlock; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index a572915c0..04f10fc5d 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -129,15 +130,15 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ParseIntraMode(Vp8Decoder dec, int mbX) { Vp8MacroBlockData block = dec.MacroBlockData[mbX]; + Span top = dec.IntraT.AsSpan(4 * mbX, 4); byte[] left = dec.IntraL; - byte[] top = dec.IntraT; if (dec.SegmentHeader.UpdateMap) { // Hardcoded tree parsing. - block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) != 0 + block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) is 0 ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) - : (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[2]); + : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); } else { @@ -154,9 +155,9 @@ namespace SixLabors.ImageSharp.Formats.WebP if (!block.IsI4x4) { // Hardcoded 16x16 intra-mode decision tree. - int yMode = this.bitReader.GetBit(156) > 0 ? - this.bitReader.GetBit(128) > 0 ? WebPConstants.TmPred : WebPConstants.HPred : - this.bitReader.GetBit(163) > 0 ? WebPConstants.VPred : WebPConstants.DcPred; + int yMode = this.bitReader.GetBit(156) != 0 ? + this.bitReader.GetBit(128) != 0 ? WebPConstants.TmPred : WebPConstants.HPred : + this.bitReader.GetBit(163) != 0 ? WebPConstants.VPred : WebPConstants.DcPred; block.Modes[0] = (byte)yMode; for (int i = 0; i < left.Length; i++) { @@ -192,12 +193,12 @@ namespace SixLabors.ImageSharp.Formats.WebP // Hardcoded UVMode decision tree. block.UvMode = (byte)(this.bitReader.GetBit(142) is 0 ? 0 : this.bitReader.GetBit(114) is 0 ? 2 : - this.bitReader.GetBit(183) > 0 ? 1 : 3); + this.bitReader.GetBit(183) != 0 ? 1 : 3); } private void InitScanline(Vp8Decoder dec) { - Vp8MacroBlock left = dec.MacroBlockInfo[0]; + Vp8MacroBlock left = dec.LeftMacroBlock; left.NoneZeroAcDcCoeffs = 0; left.NoneZeroDcCoeffs = 0; for (int i = 0; i < dec.IntraL.Length; i++) @@ -312,6 +313,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (block.IsI4x4) { // uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); + Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); if (mby > 0) { if (mbx >= dec.MbWidth - 1) @@ -326,45 +328,45 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Replicate the top-right pixels below. - // top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; + //topRight[WebPConstants.Bps] = topRight[2 * WebPConstants.Bps] = topRight[3 * WebPConstants.Bps] = topRight[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) { - // uint8_t * const dst = y_dst + kScan[n]; - Span dst = yDst.Slice(WebPConstants.Scan[n]); + int offset = yOff + WebPConstants.Scan[n]; + Span dst = yuv.AsSpan(offset); byte lumaMode = block.Modes[n]; switch (lumaMode) { case 0: - LossyUtils.DC4_C(dst); + LossyUtils.DC4_C(dst, yuv, offset); break; case 1: LossyUtils.TM4_C(dst); break; case 2: - LossyUtils.VE4_C(dst); + LossyUtils.VE4_C(dst, yuv, offset); break; case 3: - LossyUtils.HE4_C(dst); + LossyUtils.HE4_C(dst, yuv, offset); break; case 4: - LossyUtils.RD4_C(dst); + LossyUtils.RD4_C(dst, yuv, offset); break; case 5: - LossyUtils.VR4_C(dst); + LossyUtils.VR4_C(dst, yuv, offset); break; case 6: - LossyUtils.LD4_C(dst); + LossyUtils.LD4_C(dst, yuv, offset); break; case 7: - LossyUtils.VL4_C(dst); + LossyUtils.VL4_C(dst, yuv, offset); break; case 8: - LossyUtils.HD4_C(dst); + LossyUtils.HD4_C(dst, yuv, offset); break; case 9: - LossyUtils.HU4_C(dst); + LossyUtils.HU4_C(dst, yuv, offset); break; } @@ -423,8 +425,8 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.TM8uv_C(vDst); break; case 2: - LossyUtils.VE8uv_C(uDst, yuv.AsSpan(uOff - WebPConstants.Bps, 8)); - LossyUtils.VE8uv_C(vDst, yuv.AsSpan(vOff - WebPConstants.Bps, 8)); + LossyUtils.VE8uv_C(uDst, yuv, uOff); + LossyUtils.VE8uv_C(vDst, yuv, vOff); break; case 3: LossyUtils.HE8uv_C(uDst, yuv, uOff); @@ -647,7 +649,7 @@ namespace SixLabors.ImageSharp.Formats.WebP luv = uv; } - /*if ((len & 1) is 0) + if ((len & 1) is 0) { uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; LossyUtils.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); @@ -656,7 +658,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; LossyUtils.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); } - }*/ + } } private void DoTransform(uint bits, Span src, Span dst) @@ -773,6 +775,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { + // Only DC is non-zero -> inlined simplified transform. int dc0 = (dc[0] + 3) >> 3; for (int i = 0; i < 16 * 16; i += 16) { @@ -930,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { int bit1 = br.GetBit(p[8]); - int bit0 = br.GetBit(p[9] + bit1); + int bit0 = br.GetBit(p[9 + bit1]); int cat = (2 * bit1) + bit0; v = 0; byte[] tab = null; @@ -1016,14 +1019,14 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) { hasValue = this.bitReader.ReadBool(); - uint quantizeValue = hasValue ? this.bitReader.ReadValue(7) : 0; + int quantizeValue = hasValue ? this.bitReader.ReadSignedValue(7) : 0; vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; } for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) { hasValue = this.bitReader.ReadBool(); - uint filterStrengthValue = hasValue ? this.bitReader.ReadValue(6) : 0; + int filterStrengthValue = hasValue ? this.bitReader.ReadSignedValue(6) : 0; vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; } From 788058525fab0d14af8a59323f029930dc4237ef Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 20 Feb 2020 08:58:56 +0100 Subject: [PATCH 115/359] Fix setting Y/UV-Stride --- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 04f10fc5d..a5dc5dd50 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -579,7 +579,7 @@ namespace SixLabors.ImageSharp.Formats.WebP topV = curV; curU = curU.Slice(io.UvStride); curV = curV.Slice(io.UvStride); - this.UpSample(curY.Slice(io.YStride), curY, topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(2 * bufferStride), mbw); + this.UpSample(curY.Slice(io.YStride), curY.Slice(2 * io.YStride), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(2 * bufferStride), mbw); curY = curY.Slice(2 * io.YStride); dst = dst.Slice(2 * bufferStride); } @@ -1223,6 +1223,8 @@ namespace SixLabors.ImageSharp.Formats.WebP io.ScaledHeight = io.ScaledHeight; io.MbW = io.Width; io.MbH = io.Height; + io.YStride = (int)(16 * ((pictureHeader.Width + 15) >> 4)); + io.UvStride = (int)(8 * ((pictureHeader.Width + 15) >> 4)); return io; } From 37c9a7313785514efa39d97d84fd2faa683d5b41 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 21 Feb 2020 13:27:37 +0100 Subject: [PATCH 116/359] Implement TrueMotion --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 39 ++++++++++++++++--- src/ImageSharp/Formats/WebP/WebPConstants.cs | 8 ++-- .../Formats/WebP/WebPLossyDecoder.cs | 8 ++-- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 2c1ae62f9..b880e6781 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -22,15 +22,15 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int j = 0; j < 16; ++j) { // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps]; + dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps]; } Put16(dc >> 5, dst); } - public static void TM16_C(Span dst) + public static void TM16_C(Span dst, byte[] yuv, int offset) { - + TrueMotion(dst, yuv, offset, 16); } public static void VE16_C(Span dst, byte[] yuv, int offset) @@ -101,9 +101,10 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 4), dst); } - public static void TM8uv_C(Span dst) + public static void TM8uv_C(Span dst, byte[] yuv, int offset) { // TrueMotion + TrueMotion(dst, yuv, offset, 8); } public static void VE8uv_C(Span dst, byte[] yuv, int offset) @@ -179,9 +180,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void TM4_C(Span dst) + public static void TM4_C(Span dst, byte[] yuv, int offset) { - + TrueMotion(dst, yuv, offset, 4); } public static void VE4_C(Span dst, byte[] yuv, int offset) @@ -538,6 +539,27 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static void TrueMotion(Span dst, byte[] yuv, int offset, int size) + { + // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. + int topOffset = offset - WebPConstants.Bps; + Span top = yuv.AsSpan(topOffset); + byte p = yuv[topOffset - 1]; + int leftOffset = offset - 1; + byte left = yuv[leftOffset]; + for (int y = 0; y < size; ++y) + { + for (int x = 0; x < size; ++x) + { + dst[x] = (byte)Clamp255(left + top[x] - p); + } + + leftOffset += WebPConstants.Bps; + left = yuv[leftOffset]; + dst = dst.Slice(WebPConstants.Bps); + } + } + // We process u and v together stashed into 32bit(16bit each). public static uint LoadUv(byte u, byte v) { @@ -637,5 +659,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { dst[x + (y * WebPConstants.Bps)] = v; } + + private static int Clamp255(int x) + { + return x < 0 ? 0 : (x > 255 ? 255 : x); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 64afc2fa0..252adc952 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -125,10 +125,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int Bps = 32; // intra prediction modes (TODO: maybe use an enum for this) - public const int DcPred = 0; - public const int TmPred = 1; - public const int VPred = 2; - public const int HPred = 3; + public const int DcPred = 0; // predict DC using row above and column to the left + public const int TmPred = 1; // propagate second differences a la "True Motion" + public const int VPred = 2; // predict rows using row above + public const int HPred = 3; // predict columns using column to the left /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index a5dc5dd50..ffeca660c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -342,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.DC4_C(dst, yuv, offset); break; case 1: - LossyUtils.TM4_C(dst); + LossyUtils.TM4_C(dst, yuv, offset); break; case 2: LossyUtils.VE4_C(dst, yuv, offset); @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.DC16_C(yDst, yuv, yOff); break; case 1: - LossyUtils.TM16_C(yDst); + LossyUtils.TM16_C(yDst, yuv, yOff); break; case 2: LossyUtils.VE16_C(yDst, yuv, yOff); @@ -421,8 +421,8 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.DC8uv_C(vDst, yuv, vOff); break; case 1: - LossyUtils.TM8uv_C(uDst); - LossyUtils.TM8uv_C(vDst); + LossyUtils.TM8uv_C(uDst, yuv, uOff); + LossyUtils.TM8uv_C(vDst, yuv, vOff); break; case 2: LossyUtils.VE8uv_C(uDst, yuv, uOff); From 524da752ad66791444ffca3474d4f304d7747581 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 21 Feb 2020 20:55:39 +0100 Subject: [PATCH 117/359] Implement macroblock filtering (still not working: the extra rows in yuv buffer for filtering are missing) --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 274 +++++++++++++++++- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 51 +--- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 3 +- .../Formats/WebP/Vp8LookupTables.cs | 64 ++++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 4 +- .../Formats/WebP/WebPLossyDecoder.cs | 157 +++++++++- 6 files changed, 473 insertions(+), 80 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8LookupTables.cs diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index b880e6781..760f1bb1a 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 2, 0, bc); Dst(dst, 3, 2, bc); Dst(dst, 3, 0, Avg2(C, D)); - Dst(dst, 0, 3, Avg3(K, I, J)); + Dst(dst, 0, 3, Avg3(K, J, I)); Dst(dst, 0, 2, Avg3(J, I, X)); byte ixa = Avg3(I, X, A); Dst(dst, 0, 1, ixa); @@ -447,14 +447,14 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < 4; ++i) { // vertical pass - int a = src[srcOffset] + src[srcOffset + 8]; // [-4096, 4094] - int b = src[srcOffset] - src[srcOffset + 8]; // [-4095, 4095] - int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]); // [-3783, 3783] - int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]); // [-3785, 3781] - tmp[tmpOffset] = a + d; // [-7881, 7875] - tmp[tmpOffset + 1] = b + c; // [-7878, 7878] - tmp[tmpOffset + 2] = b - c; // [-7878, 7878] - tmp[tmpOffset + 3] = a - d; // [-7877, 7879] + int a = src[srcOffset] + src[srcOffset + 8]; + int b = src[srcOffset] - src[srcOffset + 8]; + int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]); + int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]); + tmp[tmpOffset] = a + d; + tmp[tmpOffset + 1] = b + c; + tmp[tmpOffset + 2] = b - c; + tmp[tmpOffset + 3] = a - d; tmpOffset += 4; srcOffset++; } @@ -462,10 +462,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // Each pass is expanding the dynamic range by ~3.85 (upper bound). // The exact value is (2. + (20091 + 35468) / 65536). // After the second pass, maximum interval is [-3794, 3794], assuming - // an input in [-2048, 2047] interval. We then need to add a dst value - // in the [0, 255] range. - // In the worst case scenario, the input to clip_8b() can be as large as - // [-60713, 60968]. + // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. tmpOffset = 0; for (int i = 0; i < 4; ++i) { @@ -560,9 +558,105 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // We process u and v together stashed into 32bit(16bit each). + // Simple In-loop filtering (Paragraph 15.2) + public static void SimpleVFilter16(byte[] p, int offset, int stride, int thresh) + { + int thresh2 = (2 * thresh) + 1; + for (int i = 0; i < 16; ++i) + { + if (NeedsFilter(p, offset + i, stride, thresh2)) + { + DoFilter2(p, offset + i, stride); + } + } + } + + public static void SimpleHFilter16(byte[] p, int offset, int stride, int thresh) + { + int thresh2 = (2 * thresh) + 1; + for (int i = 0; i < 16; ++i) + { + if (NeedsFilter(p, offset + (i * stride), 1, thresh2)) + { + DoFilter2(p, offset + (i * stride), 1); + } + } + } + + public static void SimpleVFilter16i(byte[] p, int offset, int stride, int thresh) + { + for (int k = 3; k > 0; --k) + { + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); + } + } + + public static void SimpleHFilter16i(byte[] p, int offset, int stride, int thresh) + { + for (int k = 3; k > 0; --k) + { + offset += stride; + SimpleHFilter16(p, offset, stride, thresh); + } + } + + public static void VFilter16(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } + + public static void HFilter16(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } + + public static void VFilter16i(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + for (int k = 3; k > 0; --k) + { + offset += 4 * stride; + FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } + } + + public static void HFilter16i(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + for (int k = 3; k > 0; --k) + { + offset += 4; + FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } + } + + // 8-pixels wide variant, for chroma filtering. + public static void VFilter8(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); + } + + public static void HFilter8(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); + } + + public static void VFilter8i(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); + } + + public static void HFilter8i(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); + } + public static uint LoadUv(byte u, byte v) { + // We process u and v together stashed into 32bit(16bit each). return (uint)(u | (v << 16)); } @@ -571,7 +665,6 @@ namespace SixLabors.ImageSharp.Formats.WebP bgr[0] = (byte)YuvToB(y, u); bgr[1] = (byte)YuvToG(y, u, v); bgr[2] = (byte)YuvToR(y, v); - int tmp = 0; } public static int YuvToR(int y, int v) @@ -589,6 +682,157 @@ namespace SixLabors.ImageSharp.Formats.WebP return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); } + // Complex In-loop filtering (Paragraph 15.3) + private static void FilterLoop24( + byte[] p, + int offset, + int hStride, + int vStride, + int size, + int thresh, + int ithresh, + int hevThresh) + { + int thresh2 = (2 * thresh) + 1; + while (size-- > 0) + { + if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) + { + if (Hev(p, offset, hStride, hevThresh)) + { + DoFilter2(p, offset, hStride); + } + else + { + DoFilter4(p, offset, hStride); + } + } + + offset += vStride; + } + } + + private static void FilterLoop26( + byte[] p, + int offset, + int hStride, + int vStride, + int size, + int thresh, + int ithresh, + int hevThresh) + { + int thresh2 = (2 * thresh) + 1; + while (size-- > 0) + { + if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) + { + if (Hev(p, offset, hStride, hevThresh)) + { + DoFilter2(p, offset, hStride); + } + else + { + DoFilter6(p, offset, hStride); + } + } + + offset += vStride; + } + } + + private static void DoFilter2(byte[] p, int offset, int step) + { + // 4 pixels in, 2 pixels out + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int a = (3 * (q0 - p0)) + Vp8LookupTables.Sclip1(p1 - q1); + int a1 = Vp8LookupTables.Sclip2((a + 4) >> 3); + int a2 = Vp8LookupTables.Sclip2((a + 3) >> 3); + p[offset - step] = Vp8LookupTables.Clip1(p0 + a2); + p[offset] = Vp8LookupTables.Clip1(q0 - a1); + } + + private static void DoFilter4(byte[] p, int offset, int step) + { + // 4 pixels in, 4 pixels out + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int a = 3 * (q0 - p0); + int a1 = Vp8LookupTables.Sclip2((a + 4) >> 3); + int a2 = Vp8LookupTables.Sclip2((a + 3) >> 3); + int a3 = (a1 + 1) >> 1; + p[offset - (2 * step)] = Vp8LookupTables.Clip1(p1 + a3); + p[offset - step] = Vp8LookupTables.Clip1(p0 + a2); + p[offset] = Vp8LookupTables.Clip1(q0 - a1); + p[offset + step] = Vp8LookupTables.Clip1(q1 - a3); + } + + private static void DoFilter6(byte[] p, int offset, int step) + { + // 6 pixels in, 6 pixels out + int p2 = p[offset - (3 * step)]; + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int q2 = p[offset + (2 * step)]; + int a = Vp8LookupTables.Clip1((3 * (q0 - p0)) + Vp8LookupTables.Clip1(p1 - q1)); + + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[offset - (3 * step)] = Vp8LookupTables.Clip1(p2 + a3); + p[offset - (2 * step)] = Vp8LookupTables.Clip1(p1 + a2); + p[offset - step] = Vp8LookupTables.Clip1(p0 + a1); + p[offset] = Vp8LookupTables.Clip1(q0 - a1); + p[offset + step] = Vp8LookupTables.Clip1(q1 - a2); + p[offset + (2 * step)] = Vp8LookupTables.Clip1(q2 - a3); + } + + private static bool NeedsFilter(byte[] p, int offset, int step, int thresh) + { + int p1 = p[offset + (-2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + return (Vp8LookupTables.Abs0(p1 - p0) > thresh) || (Vp8LookupTables.Abs0(q1 - q0) > thresh); + } + + private static bool NeedsFilter2(byte[] p, int offset, int step, int t, int it) + { + int p3 = p[offset - (4 * step)]; + int p2 = p[offset - (3 * step)]; + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int q2 = p[offset + (2 * step)]; + int q3 = p[offset + (3 * step)]; + if (((4 * Vp8LookupTables.Abs0(p0 - q0)) + Vp8LookupTables.Abs0(p1 - q1)) > t) + { + return false; + } + + return Vp8LookupTables.Abs0(p3 - p2) <= it && Vp8LookupTables.Abs0(p2 - p1) <= it && + Vp8LookupTables.Abs0(p1 - p0) <= it && Vp8LookupTables.Abs0(q3 - q2) <= it && + Vp8LookupTables.Abs0(q2 - q1) <= it && Vp8LookupTables.Abs0(q1 - q0) <= it; + } + + private static bool Hev(byte[] p, int offset, int step, int thresh) + { + int p1 = p[offset -(2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + return (Vp8LookupTables.Abs0(p1 - p0) > thresh) || (Vp8LookupTables.Abs0(q1 - q0) > thresh); + } + private static int MultHi(int v, int coeff) { return (v * coeff) >> 8; diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 57e163ccf..6f0f07374 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8Decoder { - public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, Vp8Io io) + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities) { this.FilterHeader = new Vp8FilterHeader(); this.FrameHeader = frameHeader; @@ -71,7 +71,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; - this.Init(io); } public Vp8FrameHeader FrameHeader { get; } @@ -219,53 +218,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public void Init(Vp8Io io) - { - int intraPredModeSize = 4 * this.MbWidth; - this.IntraT = new byte[intraPredModeSize]; - - int extraPixels = WebPConstants.FilterExtraRows[(int)this.Filter]; - if (this.Filter is LoopFilter.Complex) - { - // For complex filter, we need to preserve the dependency chain. - this.TopLeftMbX = 0; - this.TopLeftMbY = 0; - } - else - { - // For simple filter, we can filter only the cropped region. We include 'extraPixels' on - // the other side of the boundary, since vertical or horizontal filtering of the previous - // macroblock can modify some abutting pixels. - this.TopLeftMbX = (io.CropLeft - extraPixels) >> 4; - this.TopLeftMbY = (io.CropTop - extraPixels) >> 4; - if (this.TopLeftMbX < 0) - { - this.TopLeftMbX = 0; - } - - if (this.TopLeftMbY < 0) - { - this.TopLeftMbY = 0; - } - } - - // We need some 'extra' pixels on the right/bottom. - this.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; - this.BottomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; - if (this.BottomRightMbX > this.MbWidth) - { - this.BottomRightMbX = this.MbWidth; - } - - if (this.BottomRightMbY > this.MbHeight) - { - this.BottomRightMbY = this.MbHeight; - } - - this.PrecomputeFilterStrengths(); - } - - private void PrecomputeFilterStrengths() + public void PrecomputeFilterStrengths() { if (this.Filter is LoopFilter.None) { diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index bb6c6da17..a95e0ba92 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -20,8 +20,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets a value indicating whether to do inner filtering. + /// TODO: can this be a bool? /// - public byte InnerFiltering { get; set; } + public byte UseInnerFiltering { get; set; } /// /// Gets or sets the high edge variance threshold in [0..2]. diff --git a/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs b/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs new file mode 100644 index 000000000..ec20f2cad --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class Vp8LookupTables + { + private static readonly byte[] abs0; + + private static readonly byte[] clip1; + + private static readonly sbyte[] sclip1; + + private static readonly sbyte[] sclip2; + + static Vp8LookupTables() + { + // TODO: maybe use hashset here + abs0 = new byte[511]; + for (int i = -255; i <= 255; ++i) + { + abs0[255 + i] = (byte)((i < 0) ? -i : i); + } + + clip1 = new byte[766]; + for (int i = -255; i <= 255 + 255; ++i) + { + clip1[255 + i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); + } + + sclip1 = new sbyte[2041]; + for (int i = -1020; i <= 1020; ++i) + { + sclip1[1020 + i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); + } + + sclip2 = new sbyte[225]; + for (int i = -112; i <= 112; ++i) + { + sclip2[112 + i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); + } + } + + public static byte Abs0(int v) + { + return abs0[v + 255]; + } + + public static byte Clip1(int v) + { + return clip1[v + 255]; + } + + public static sbyte Sclip1(int v) + { + return sclip1[v + 1020]; + } + + public static sbyte Sclip2(int v) + { + return sclip2[v + 112]; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 252adc952..179d6d77a 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. - /// Simple filter: up to 2 luma samples are read and 1 is written. - /// Complex filter: up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). + /// Simple filter(1): up to 2 luma samples are read and 1 is written. + /// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). /// public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index ffeca660c..43d723917 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -57,11 +57,12 @@ namespace SixLabors.ImageSharp.Formats.WebP var proba = new Vp8Proba(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - Vp8Io io = InitializeVp8Io(pictureHeader); - var decoder = new Vp8Decoder(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba, io); + var decoder = new Vp8Decoder(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba); + Vp8Io io = InitializeVp8Io(decoder, pictureHeader); // Paragraph 9.4: Parse the filter specs. this.ParseFilterHeader(decoder); + decoder.PrecomputeFilterStrengths(); // Paragraph 9.5: Parse partitions. this.ParsePartitions(decoder); @@ -94,7 +95,9 @@ namespace SixLabors.ImageSharp.Formats.WebP byte b = pixelData[idx]; byte g = pixelData[idx + 1]; byte r = pixelData[idx + 2]; - color.FromRgba32(new Rgba32(r, g, b, 255)); + + // TODO: use bulk conversion here. + color.FromBgr24(new Bgr24(r, g, b)); pixelRow[x] = color; } } @@ -214,11 +217,16 @@ namespace SixLabors.ImageSharp.Formats.WebP bool filterRow = (dec.Filter != LoopFilter.None) && (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); - this.ReconstructRow(dec, filterRow); + this.ReconstructRow(dec); + if (filterRow) + { + this.FilterRow(dec); + } + this.FinishRow(dec, io); } - private void ReconstructRow(Vp8Decoder dec, bool filterRow) + private void ReconstructRow(Vp8Decoder dec) { int mby = dec.MbY; @@ -313,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (block.IsI4x4) { // uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); - Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); + //Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); if (mby > 0) { if (mbx >= dec.MbWidth - 1) @@ -457,7 +465,7 @@ namespace SixLabors.ImageSharp.Formats.WebP vDst.Slice(7 * WebPConstants.Bps, 8).CopyTo(topYuv.V); } - // Transfer reconstructed samples from yuv_b_ cache to final destination. + // Transfer reconstructed samples from yuv_buffer cache to final destination. int cacheId = 0; // TODO: what should be cacheId, always 0? int yOffset = cacheId * 16 * dec.CacheYStride; int uvOffset = cacheId * 8 * dec.CacheUvStride; @@ -477,6 +485,82 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private void FilterRow(Vp8Decoder dec) + { + int mby = dec.MbY; + for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) + { + //this.DoFilter(dec, mbx, mby); + } + } + + private void DoFilter(Vp8Decoder dec, int mbx, int mby) + { + int yBps = dec.CacheYStride; + Vp8FilterInfo filterInfo = dec.FilterInfo[dec.MbX]; + int iLevel = filterInfo.InnerLevel; + int limit = filterInfo.Limit; + + if (limit is 0) + { + return; + } + + if (dec.Filter is LoopFilter.Simple) + { + int offset = mbx * 16; + if (mbx > 0) + { + LossyUtils.SimpleHFilter16(dec.CacheY, offset, yBps, limit + 4); + } + + if (filterInfo.UseInnerFiltering > 0) + { + LossyUtils.SimpleHFilter16i(dec.CacheY, offset, yBps, limit); + } + + if (mby > 0) + { + LossyUtils.SimpleVFilter16(dec.CacheY, offset, yBps, limit + 4); + } + + if (filterInfo.UseInnerFiltering > 0) + { + LossyUtils.SimpleVFilter16i(dec.CacheY, offset, yBps, limit); + } + } + else if (dec.Filter is LoopFilter.Complex) + { + int uvBps = dec.CacheUvStride; + int yOffset = mbx * 16; + int uvOffset = mbx * 8; + int hevThresh = filterInfo.HighEdgeVarianceThreshold; + if (mbx > 0) + { + LossyUtils.HFilter16(dec.CacheY, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.HFilter8(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + } + + if (filterInfo.UseInnerFiltering > 0) + { + LossyUtils.HFilter16i(dec.CacheY, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.HFilter8i(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit, iLevel, hevThresh); + } + + if (mby > 0) + { + LossyUtils.VFilter16(dec.CacheY, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.VFilter8(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + } + + if (filterInfo.UseInnerFiltering > 0) + { + LossyUtils.VFilter16i(dec.CacheY, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.VFilter8i(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit, iLevel, hevThresh); + } + } + } + private void FinishRow(Vp8Decoder dec, Vp8Io io) { int cacheId = 0; @@ -532,10 +616,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // Rotate top samples if needed. if (!isLastRow) { - // TODO: double check this. - yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY); - uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU); - vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV); + // TODO: double check this. Cache needs extra rows for filtering! + //yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY); + //uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU); + //vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV); } } @@ -744,7 +828,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (dec.Filter != LoopFilter.None) { dec.FilterInfo[dec.MbX] = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; - dec.FilterInfo[dec.MbX].InnerFiltering |= (byte)(skip is 0 ? 1 : 0); + dec.FilterInfo[dec.MbX].UseInnerFiltering |= (byte)(skip is 0 ? 1 : 0); } } @@ -760,6 +844,10 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8BandProbas[] acProba; Vp8MacroBlock leftMb = dec.LeftMacroBlock; short[] dst = block.Coeffs; + for (int i = 0; i < dst.Length; i++) + { + dst[i] = 0; + } if (!block.IsI4x4) { @@ -1208,7 +1296,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static Vp8Io InitializeVp8Io(Vp8PictureHeader pictureHeader) + private static Vp8Io InitializeVp8Io(Vp8Decoder dec, Vp8PictureHeader pictureHeader) { var io = default(Vp8Io); io.Width = (int)pictureHeader.Width; @@ -1225,6 +1313,48 @@ namespace SixLabors.ImageSharp.Formats.WebP io.MbH = io.Height; io.YStride = (int)(16 * ((pictureHeader.Width + 15) >> 4)); io.UvStride = (int)(8 * ((pictureHeader.Width + 15) >> 4)); + + int intraPredModeSize = 4 * dec.MbWidth; + dec.IntraT = new byte[intraPredModeSize]; + + int extraPixels = WebPConstants.FilterExtraRows[(int)dec.Filter]; + if (dec.Filter is LoopFilter.Complex) + { + // For complex filter, we need to preserve the dependency chain. + dec.TopLeftMbX = 0; + dec.TopLeftMbY = 0; + } + else + { + // For simple filter, we can filter only the cropped region. We include 'extraPixels' on + // the other side of the boundary, since vertical or horizontal filtering of the previous + // macroblock can modify some abutting pixels. + dec.TopLeftMbX = (io.CropLeft - extraPixels) >> 4; + dec.TopLeftMbY = (io.CropTop - extraPixels) >> 4; + if (dec.TopLeftMbX < 0) + { + dec.TopLeftMbX = 0; + } + + if (dec.TopLeftMbY < 0) + { + dec.TopLeftMbY = 0; + } + } + + // We need some 'extra' pixels on the right/bottom. + dec.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; + dec.BottomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; + if (dec.BottomRightMbX > dec.MbWidth) + { + dec.BottomRightMbX = dec.MbWidth; + } + + if (dec.BottomRightMbY > dec.MbHeight) + { + dec.BottomRightMbY = dec.MbHeight; + } + return io; } @@ -1298,6 +1428,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return value < 0 ? 0 : value > max ? max : value; } + // TODO: move to LookupTables private void InitializeModesProbabilities() { // Paragraph 11.5 From 6e85bf4752dcf827aef4fdd25b6d9254a09b16db Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Feb 2020 13:52:44 +0100 Subject: [PATCH 118/359] Add lossy webp specification --- .../WebP/rfc6386_lossy_specification.pdf | Bin 0 -> 590738 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/rfc6386_lossy_specification.pdf diff --git a/src/ImageSharp/Formats/WebP/rfc6386_lossy_specification.pdf b/src/ImageSharp/Formats/WebP/rfc6386_lossy_specification.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d421b34cce220ce5c7fe93e4ec6394ef6619f63a GIT binary patch literal 590738 zcma&ObC6}tv+q6K)3$Bf=Cp0wwrx+_wr$(CZQJgCXP$HKx%Z9dMBLwBdq?eBnYpS~ zd_P&0wHL9hpfEK(4HE?M@I`bU1T!8To{hda1Q!>rq>;6WqbVNCpB8yqVKYldBYQkr zVM{$nBS9kr8$%-=9ta0Vdm}w72-l24<#qctQ8=$RGTFx^YpYUo(o#B_~( zU>^+gcv0^&=QBEM68K@D-to`#jKa}&G=w?Bt z9MurIxc$6YFL_~~O|)`8CDNX+{qq@5ZObOlfT}3_C=2tEjBY20Y=AsVmE_1KRhRpB zj}b#T#EP*=c)1l7&FVwAygX2V(Id#MBg$; z3_P(kObm~f%%Y6P_^ayEIziaiCQ9hlZ5ba{QfZDsDhIJpfQs;sbtALY18dK<{T-8Z03gx2cZ9fc(2uAG(wJ;m-<5&-g(Cwj z>$)bGtK#`vo>K?3;B#7Rbuf)PTd|)tLdAltxf@yx1La#nbr zc|dp-Ru|q+T06p@mT$gHXKtyV2uV@1lMP{$?XlA<6utP}Wk8R8lI=8Tqof&Mn}t1( zPlsq$8?4v%lI-^@zsGt0hK*IRM)jd?SKS&K%7}Hjp3HHQ31npMnI*O~GN4hbiTkM+ z|CuatKM*~xgOV&(S)hECZbZ(oblLBPb&TW3gQ8N={$5`YTbY7Q<8#L2BhumF@k99( zu_a*}fZWN46v)ezsoMsRpPQRCq{PG&(E!?~>Ch!crztYgG#9cafk*pSI)A z7a=xdwqEpY1yShE4^Y(s#NfuUj6sz6tpGDttZJ|6?2+@a5B@!$jKR-G#M8{mWZ zS#Prnw}KM~Sv;Ppvn~trx>0XHp}~7b0}l`J`vT(M^Rk#Moe%i>ec}Z zh3r@OcjNF+MZR#cj2BW!o#ZOiEoiHora^1w8dp`55{cL6M(!i$S?aT=W*Rn`;CX>v z=5y}@R2^AlSkgiFP-a$=f>wM+UcW(Cc&!hS@IM>~J+e>KsuEk;ArR#8BKBn+5y=AB ze&c$h^u}C$gVOI&Hyc?S{x7lm^X;#|{gt)9pY-%>tPuaJjh^*i>POG^pUB@}x{kuS zFw)@mBl%P1sATJ+xr+KJyr^huTp$7|?4=_^pLT#h6?>f||Mr~Bmey!e^?h+|Yw`k7 z|BtNxSs4|RYpt+n0w8k!@7g!6EZ}ZUG`mKyf*agPCAi_RncVm6GsP4(2_b_3vz_hD z&krv=PT<-%rmV31ScOqRkW@6O0Btph{V#N2284XI8&NKtPRN2lCg?)6OyFo5=_sMR zxr1Ao-g&tjkD2nzd&mMw{HQ=NBEhzA;`HV8dqu9%b0Nh%v5ZK(l7&MHzNC*myX z@UYA6=e(}QdWrQ=vV=Jgt{D*hRHMCLgE3?-ANgW8rU^Pp$_om1nkto*Ye7C11wBN1 z3)oTdeIj+ZWlTW}-{OcIzdmyvwXeWQ=4Ue5h(Pxm%+uZLu$qOmz*~kb#%H{Ti|fH6 zhP;_}Z#4CYOW(EYc#eTgEbc_<^zot(bhZ3wZLAzLR$$-zi{nMS^$FbK`BUuUw!tW1 z?l9SHQO_;6RtEc|2gKs1E6_G4FZH7ldwzNE55tX;f(vHsfO3xwvf1J;=qj9QX~Pu% z7`_cKz`ZCCJgWs6{Ov_3+KL2vh1qI#bJ@5_<$5f|?M%Aco;dx(d!LjrZW-P*(JoKq zH#n$A0VvFq{sz*$wU^Te?09(_`P%tvm79QGHc5?@h&-w)GCx**pUCe7BCH)kZa5Jq zt+fsW8LwoLdoj(M-^?YvauBL|`=Q4$BMC_x6bF8fp(Y zC$WfPnq~$rMkaBn+)GTqPH9z6YVkt*?b;nTWK8borZyB)u6Z46>DSh4364F(#S>D4EW&kht{0huGKyqA4bFk zB|VyT1qM21^d}PtFKc&L-imP@V11TJLZWck!%EDmwQx)-Vgr!K^M~QsMPZfSLA%#H zv#a8edFt{@+^Y4vhetJ@MHvmku!v4DCvJ~(eL5?wo!OE9*ak=UD)^{kpmi@utrdhp zcqq40W8GWi!d>!?%lHsfX+^IFA~-l9gmv~1@28=Ks~B~YeO3FJz z)onLE?3pwdDl|Vi{zL))Y}X+6OWgQD^sntdlXJpKzfF5zM*Vfw{yJ|iAK}trz%LS= zL8fm~jR+m9|7R8%bI>&NMf~w-E*2VDm-p1Pq%~ikp^Irh`|GSO93LY=`Twi?KA7S} z1ddsoso}3^Al8%VJE8VH&xQ z9s#+$^1`W<{AwhJa+&GMoqcfGJgo62+Q0}Nar`4~mIkQyXU2UV-B0`xoV~;!08_t7nvCI%IhCK4&!_g3o zxk-ZYnveiwvqzj@-KGN`+#+eqEgH=BcUd8k=ndJ~aw>nSBf)57GvMvqVSBvbG?eN4 zT8WoBob8s9=BoCKw+Tqjs`OD-nIGuf&iUk+L^xg)_=ehswMCfir)~kjd=oU=G}gag z*K2i5pVvAI;zfi<4iJ+ECoHIz91q0OO}O2N!1gTJ#|Q7->y{NvBoAWY8D3X>^|cLZ z|HQn;c|J-1|Gq&5@l9~(-xIbH$_1U`; zcjlj)3u|W;Cl58~m%uUOxGTu4&*DP(=s3D1TjcHjiuvP0YNJCgHgEuK>Ij5fTz=?z z5<2cYyJEUn%OyZP4OjJHL`o8?NHN84G$kCzY%DatmCyg%#vveLU?7TT49YMrnd?vV zr<2eqSpHu)LNX-Lkujs3gR=DTB56@vovpq8Vy88GgZiUnfpyJIZ~9H}7nekP!UV+LGvV zKVstG@57RJr64R&OunLa`_cV^> zZC1hEudNwQmx~>ln`I7~-t}kgj}_56L7U;V4HL*aqEt<~R8-dJRy{=rn}!2L@qoW0 z2%JFD2)Br8h6V0-FWW5N-m>ouN_9ML3$OQdHDqqaCJZaDV@$aZHMVbc>xTo++ zhA2IqO5D00wXl|MD^Ov+tKb@?V@x+|DiLwib*PT@;M2S}&MzC!Z2CmlkA3Q-|5$2< zK_Ip83K2Z7Sm06LzU49DHvahDexrdI9!;Nhm-Dq$--e%cn{w$I^lMY4MbTKVZj)WC z@45_jcgIn7YJ|y_VD&s)r^7lviyU&0{Go9OxCseho^+k-h0i3*+juF@U46{Wq7O=9 zuXf(9i*x zPYf@rzBJEGQ#}hZom7%(+Qxtk4p9m_{@rKw)$^VR&Ls874uP_w#;s5A93Zm3%Vv2W7?(0Q+FRKjT z?Sy6gJ5^dGeH)N)9aBK-WKDe2}*=*SmB@BrL9E@wK}xd zer9U*am$Aclj%Wzr<7=6UOKp?(ykjq$U7qHmx?52doj(nxn|kO_GoJfOt+OswDB3S z_UKzs&Kp6%b56>)v83pxHDP@@)n8@FJ?(yj0V^q{G$>!jsBlaqmQxi3;GS!Yu&Vgkp#g5Fnx|~<6X=W;pV^PKM&w-&la=U}V|WI3s% z;`B5d5}0GRPDM40`N`nId}3`>U;Z0GzyjDmzuH`o({16jx_^{)LEh7UQ?0-Hv33Lh z?Y4MKo}#JBt(WKSZ6f2hp6^c8JU;XIn5(2L_{TwcoX{hB)g;F#heT)^ z9Mi5e_p3B_<-5{$kbN%A7OR(=iw=4S4b9;bTWNr1+Z0046M|FH557)o~u_isxR-2=qIU` z2{_^+Zl zrdQN$^GF0|mC_6*l_Wr-X+n;;cppLmA^7;Hg{|xS5pM!`CWg007eZkGb@Dl#ScEDk131fGU&~q=+z0W`6uA=^D$4j`J|^EcX=%fTK!ED}_OppP{I?Mv6KEzd<~M?fe0>5dW*eh~=rYd8e_$eN6DNf|De`9Rf&6rT(j~qAY)& zTZbE3P{X)SObZ|kUiq(cr%;wM(qDOBD~_}|SwMNz_u$tH#yLUbR7OLwznSOL04I15 z76_=mBvKP?W%g#0+zhi($P4r!h@Mo`G7OeEjQyFhZUt=EoPs!OMO#Fy3kBCgyRFHf@YT&`qhj%Nzdt;16b zb_y*1v6uVTUe4PS3sZUgf_fWM|HSi}!8i4XO+Wb`n^KJd9=|u*c=;+fHPnZkY`9tc z`mX+Q*7(=iqxC6GvX+OFWh1h!OmY&vMI{YYf$$3V-#}ac8lL^!H0m&7Xs_76mz84Y zy?}=M+t1;-^GTQ0=3u_A)k>xLkr3HE=v>aD8aMrX*JrU*L$myC;XGR9Fg<4?dp-0f zQ`X03kHd$)mLnJ`JD+kvKhB`K4F6I7kh*TY&IT8J{fOkSjAGjoM}2O)T679P*0wp&-l<9jp`K^N5_b}(2$Osn ze+@;fVV?#_PjtZnJ*Rp%ooe%i9yZ2$qxOTx%Zays?ER7>5|z5{SGi}k1}RTk_R zJHUf>TnQ+m>@{+9jnAlJ2gTirsT9d%s!hbKHOz55Ti_?I8`NHds=o z?vq#xEP_PRev1fr7pl~l7oOP0Fq7f1R}R~O_gGA-|2ti+W5ISlo1QlV^|N+dO9-Ov zH8ST_Bl4gR&sIoS;i%}9+&~fbbti!w?tGNT87LnJ?YCy<`{y0R)`)z@-V=DvvsvM( z=+Sr#HA(Q61j3^!;8`a@0POWNEswTV29pj+-&RQN&G1x;?Npx-2la~|No+JJ9&e1{ z&FSIU-u~Iy_b=X1gWc#>ExaelZ0cmtC&D}lgru2Z-4eDdlDTI!Pom)+al^=@C!8rI zD_fVm*2I@?Qe{2BrvTHT`r%A9B$+Yq9JVXrIwRf~16y(y{%{UNnL%#mcRnUapMx** zK6?0K#OVybBhRTJs~k^fsu5dQX$?m=!Z&fQN;t$ETp$LFPX=f;49RfslCYEE%UrNe z*_hsU_j|^V;)Y(=hyGt24<@hYXn30!Cb+-%K$598>S@c33s%ie1=gLW>3d3q_p2Wuh4@F=caxXy zEYD3pw6A%;yk4NLnP_uBv99R``&6*~5t-7y3n~VTi4$`4dlHp<5=sy}%}vnT?_(c=lo|+5vv; zq|RaJ(J|!tHZ}~I_fk@L(y-JGDJ(rl)66RkQ41GVCwj%8y`JnmIUmB_ss9pEq+sCN zbaS_{FAE!O%0n2S%;ajmR!f*6D}J{sQMiWf|(ZD;=nO{tb=JVS_RwiCIsa< z+|`0?B=)IyWien@5MEN+J`YpI%aO|B+vL+&qp0Kwx-NddCY#;oVvoM9dO_T!tx%9* zXdAYCOnE#Y;Qqp1kC=KuY#8bQqTxho9cYbjAfd4CeN?!b)w$1J-7@ta^hEtKe#q0U z@%igy2qH^CJ_FFT;3c_?yeSkxV0SrXvm(GdI=cc21M0ZY_8$6^aKvTLT7`m&HqKOJ zv|o?3<_n`fPdo*?Es8;LeX0aaqA=o8(%4YW;I}333Ps^F2}*k81W_cGJxuIcL(y>D zO=mmXSxRtnf?tKMy!1Zru2ut{A4idiFSL=-A-)06sc;*JSNopZLFItoIsqh|(7r;SpW zMj(nov5<`orsZ~&pbCfhR3oEOrDE`f;AW^?a>KSMDu*VRCaKtZjK>-??k&V?94qi+ zqQoQ^1`tC&&%^-C_5}?`BEJKf%V7Vh0;=&-y;Q{7u5KNJLzER1Z3e6C*l$jWFuVh1 z9YhH?G6yB&Lv zjSS27Ab``<=vbXFtP;P)Z&%K*WstmHroa3{n{92^wK{3G-R*v%(sYo6>z8yKdaiz} zyQ{c&+Y|A%l)O#dbv)8ES9|GUnoGH$=lg0y)~d1X3pENm>L zHtn1^Aiamq>5R>uDOn5|3Pij>l=~yckouDLy7dRyXtWdtXBZC9lvk$5^rQ=iv^>Yr z_Dh;l=C<`vLrrGF%Q6*5|86MA@Czrpq$yX1^vH$CJ3JXx0^a%Vb#k@&o! zczqsXi~`2~^b3*LJ5xDF_gwmFcP5h*N&J3-F>0oc4Nph$?5N?xQS#Jk#k~n>&S$6} zQzlVAPo#+194-*ydv^6QA_ubc45$^wuIK`L=9uJhnfA=E@F7FSd&P2HnuNgTK9dn{ zG|0YTigdA~BZH(cX{b+<$H97=qDx#w)lGDnB01EmB{`+pX)iVLDFf)#;p3_ZMTgw_ zJ}c3+P$R{m!L=JiUb-_eqY_!qj^U9h^%w~5OPSwrqf^mn#)Mhnm5~`@9__9$$(a4R z03do8cg^Lq^y%8qo95zc6P}N5o=MX}Xx{n)$nT4uA6-$ht--JAgl0yitT-YI8fQns z$W;=lrc@oNHsH@^rckay@Hm4c@W;YEfv0W)d77sA8+8OmF7U?-s#PL>J$L!Y8FERz zikAocX<+#(bD=wp9n<2PYUB3dOcO~x>S*Met%dxCWs1^lFH<1Pa~BG^;Q>@eYdvx0 zg^y-%35>L|9D_z>hL?TBQ>kQ?B(+Nv%~tk`04qg~BGUEat$T6g>!AqVgz1p;WW$RY z63wx7B@&8vGfYbrPEiBeW{T2uCnx_7c19z9IFg#!w+YdiA8r zj1+6mhmrImSBe|ZVlYpU^p5g~%EkXdpkr_mu7Ye#@;VPcW0xdiPNUOBbG{X;#@aKWBrz4;gG*}xs2K(Gp-L_(EzVir3Xb8q- zkpPD56aB5UJ696AHQDnZP_}1RRUiE7|6|$$?FeSv z(6m&&1!RjZro-i;%*OnR*6|hRL`@z|smd_(9g}&oQNx{!RCdalE@v<@1V)WBv>N%-HY{xeC2>!=-9XIp0|>dPk1+2`Wn;p*|cuk-5@rZ!u{)#BsD$JzaQI88H(i8bpF zfhDiTUSVt42%>;K2XJ7M8DrteTL4yA;)`LXJ42@2-kR(TC{pbnRit+_4x_@C>@qgd zkc!_SB@fSt9k>p5Gq|Jy$I@RF(fMKwUKp>uqcSgS#;mnal0TKdNi{K}; zt>INZe{!tVa&DV(q$`{Q-=lTom`|>Op#^T;pC+6Mqw4kn{pta2G+F z9X$TTc_nHvoL02mikh-jBKZ?7Su*Y$2gu>{TrL@~PQK*(+f!eH5Eh?Ml0J4`1Kdw{ zva~u+pr%Emejzs_!>&fI7EEnMHYrQK6*3@tLFdJ~>WaY?Y1E5Oy2=5K2;p-*n2}M`p*4$fv=@X#QBv9lJ7b7}t3$=CzKO%1e>cea%|V^+i{RCp73N zSPq7a4Zy!UHC}9S-DnDz$49KeAk7<1_=D6e-JmDkMu!ErM(R9?ZI*?r`{hT+dorGD z#SiL&4z+>jbEo7L8xs7Vdz|f>au^aGjMhGuOx2bg^qRU-8MVyo&u2rYaQpSx!bf{G z>fNQ_su>g+oe3L8Z=c$TwY%84`lV*V+i^oCcLNW{ZEROeYdSj}3~Z2$2SXcH0zEy^=@JOm^XEz&!A%ez z^2x70)IVBmA#Jo@7KxN_zQ4i&w|#!2gT6{HxU6lF&m?>|$IZlN-*dd6G$k&3e{~)0 za()ym-N-TINhwCAM`zlN`2&cFiYPh-f9`%6tiJl+e*@S<70Uk`i~qpxA1t!cv;Xgn zV+Q8`0gKH4Ar`l7)>+`Zmv!uJ#?^b|s8^FNhQGA^tWMOMh3KGuQqQ8GhB=Cj)jlVE z9JUjZOE}K~Ze|pPQJ-G5Uw6Gny+XvZ`GI={WeXv4zsK}134+fgZwcSKfz*S0(;7pL z9FGH=;#d*1t!sT_5Yqq#;tKtwRYo%cA9n>WmpULMjoI(^#2_WFNIL=N()z?E7VA)z zL{R-i;C{V1A~u{CdU_~6d8BybzMJ(X;)|o&bAxb88aSOGJ=MwHrrklVhuR|Bq=S@9 zy7`S-hGY+EqakPSvu$HOozA=*BOMn=r#*S+nnjgt%bYp9igd1%NK;2ATVkx@Fn&&$ z(5H$I35i*WQG*QHSkEt}+%JvZK3}#oB+JlIZyLFKNc2?3YS>mIEYJTpdd!&e zLyD3tMGJD1e_em4Kt}NP@Q-qFG{Y$U2F`vs6s_eA)u3;7E{_U1e6FP#(BC|W7zrpE zd}OyQO3gs*Fn(KNJsT-|%BOP?T5x5m3i#qYglNSsx=}-SAP^*aQll@_w0=%GXc0@y zl-9bHFoYD*7FU1}trlLaiWZyIu6~Qn7pA^zKpM5j41(yo1#up1;sS(CiNxk5iK<&A zC2aMCs2nvi-~vR1O^OZUvgI07cT_A%)|4Hpbp2ktUbQI__S(*gvqDWMjWr_Tm}D-v zAw(~wHAfnf#q(^_Bxc3FE_3#gE{nsp^A*$OP)`wz34*$#jqs!WE23=kY1n*LbQ?iA z7|N!NRZ4aSH5JDJ&jmUlM;7N`umHNGFdZx_xD#N@xdZY{n)30H=hAW5aTB*^Htz@C z-=1#kdo?Es59j3PN|VnW!Eq#VcsB~5a#UR;N=TI~wj%WDOR9 zwqgc&7;=o4L6MpL>LtLrILSgYFVJmAcieOSIF+=9^X9A|#mWg;FROYWMOxV_UY*Bp zFP$hYZo+3(@54iM0$(6Dr5EUN>`)(?IGn4&G9gS`&`8n((TV9OiTtS7epcvl$fC%_ zd>FDKZ^G27yFgA;6swUjdUu6`vfM#lTG6&I8xK4pavm`BqLJK#xtJawz4Q|>A})j@ zOqOU1kOW$j!Dkxzw9ti@s`uVY_fK#O9<;%J{i=~Tz2mj4{vhKPYsOe`FM(OpRlKlr zZpGHNo=Z%RWyF|$CMhnP@bzW%`O?u$a}`^8s4Hc5!t-J2W|Y5Ch%Y`#xC>+zTO`l| zF(3==WSzy6^sr;TK;+LdP|D-MG+hP(cz0OH0tcy)-A`S&UxY%PQFyp_&jyY-*Zg2#1XW?A;+-N5JtX|sOpT3;9x=Di~>bp*Z|5o&_TJ8e;oG0EyGnGnS5CFkHjX{|^(uB#E9 zk;j@hgm`jwlWb~xvBi%g~56rF~uSZ2x^cWKhk!75{4JlQm;(2ht31AJQzB1-HQ!4ljZ@ zIq&w}6veam`3(qRFD3JDl>7sTe^A0q&-~vgVPX9D3XA10UjCmetW0Gohd(IkyjQt+ znKK+R7F>U`btt>v9c8*~i7HB#hJ^zkD-rVr_{Gk#*4<$P5Qjiq%vfp|Nd)P-^89p3 z(=|slY1%h^#a$iG>f)f@PDJRv=ERUWbz<+oyl2mMf4b;jAvqSP60+S*qV_B2in{vL z9Xx@uO}yGvjRc-OpZ)6y)y!FXoWo71I{BMOEr|vtJ|ZbQUd07>d-g(~-s4l@PA~N% zR@urVBO;7(gz43IXzocdKnAd5AYywng%M7&;-ydNr^H)Jxqp*c_Ek%+>C z3{@#;9`~SW8(K&PeXn^Pmi3^pGLa=w%|<46_EV5?Lv7<8Ymb}_y2oAdYpVh0ivqrE zirDSv0yTQ{%09iP?+?6uw%5i(Bb(7}!u+w!J%PaRGc?GG4bx+*bPtiL$?JAg&d@|n z%F0#1LS|-iDh_f(^0l$-nEO77(9iQI1B1!nN_{?*bD^yL6qs}HVRmv{-zUjPxiwHB z;9HoVOq4>I`nR=Bb6o}0Y)BN(V<+GEx0j;&BTS-POU<=oj%)n9%OjeJzkjYcH{94Y zgszbs75*Y}TMP!&-?Z3gb7@`hHVJPKXpL1@7d|8evOtPY#0;nAarceg5zh77u<+Nx zD1}^6bdlX&f7(RjuaFclU)Hw^K|BrApPLo$TDzh`(iLiCK}qYyh-$di)%F9Oky`CV zT4I7Oh_vM;%#{3f7Fj2}j0J}9Tu#^imI*Bj*U@)L}h%%g*fu>*&RW-%TPLbL0JOcw`kqnjRTChb3Rq)o;woj0T*}$4? zd%h<;mIa$#2h_9f0q_{@g3)z|yG-7~+dMgjX(u_kjjF4P{_8X2e%Qf|8CU7wQvz(v zj*5y>V!$#6U_HLL*)v9`!V_jHBb6YQb@I5hw z>dh=!Q&ulMJbGBT%txIWgIAnmONb8G^7mL}EEs!%5)=_>h)t|Z4{GxvU6=2&tlpKH zkQ$0$8LY}AT_LOMd>414R`*j4=kNK!RcA$Unoa~rQ9@4nF1mH8aNB=QQ_bJ9C+)Lw zk<2-7t_Pz9l(ra*+I0OiPX|15AsUwtCQ!3_zYuGW)oY0E{|c(+zSz`GE-JlY7Q`$9 zvk(4OBb=$dWtX;@(jSq+TXT8n9X`ojTZHgp&kIeC10k?knV0QA(8LCMw^1SRIT;P~ z#=&sje~&HVbB;jdr0j#|0&1a^C%$kOorjCMj*b8oegSY`%C9au4YWzs1YhRoMSISX z76Z%`H+y&~7`F|G>fb3G)@jzKHv4k%iO=5MdRy7_yta;W8%oyJw5Vra9DVR?dsipc zsrk}58GOX~w+#OyuK&m|v;GgI9M=D+l#{5k{%4~L&g)p^LV|QOVj{H8Ng@lO zy>-HHj!9|$)bZ_yUrZnbkg4VYoy}9S3o4$_m@^3?JGuywt@Cq6J>&t#kQ;!(o0-o& zg~yj0j7Bo@FJ9+#sba*{&}f?x09DmU&yWD=@J5~@~p zfto#7-xJgK`hevW9uU*Y-$p{Qv?%+4%D8pK+1r|Z7oPMUPekudiJw`-4cXwbNa3Cw zey-LO04fuVt_GHaTy$D}i91CJ?vOSCR6^>{vsi+SC@3WRc{y0oqaU7) z@n$^XZkicCJ~fr@)w;MumOL7;cA5ifK*_CpEO+J<8{aoExYCCzQEge+r;qFD z8FYl@l{o?AmwN+Pdk9#G?fi@+LSQpmVE7&QZaPA?Rh7X(SvmIyyk6lJRRMceJVVMq zq(#?x;xRfZfJnZ!T77A?b~ttI;1hZoXd{PoOSl0F+45hKFvDjp^7Cb>66PZET&re1 zO(K)F=d6u^ErU>!$QK)aH8O)LHU&B%_L>i-G4Ff8z2Vu}5IfofS|y>0hW(t6Mb_N% zPso&jizI}{>qIoaM{qt8&Vk7n&7&$EgLc4KMfA|*B_QCg?;6DS4%04eg-AfE^%uJY zB8-YL?~O_9G(`Ylzv4W5+AUfCMMhe#&iw067eCaM@bgOK4*!gMGxSGAC6wxB=O)rc z%lFbA>tzQtR={TOwvYEIy+V*cB|=!`G>2?M52Z-MOP25R+VBa{eo0W?@gC(qj`^z+ z85^BYmzhfjQJj4!!qNy!VPvrP9Py!f@1gMBNn8e-;E{O008+PC`Uiw5EFuAaowmwb z7%icWhFgyh@MKxz1_2SLe$|Sj!yy5Gxi|sTwb|+V&@WdXD2_2 zJApV(g#iDEIP9YImbqJr>Q9RZ-2{HdVII9U0PH<5L8`D?%dxA&9MZNi5T$~%xDbV^ z!jd2wdSXlILOQ1~@+Pd{HbjoZ*>;}Aib0ia-T}7Scv0esHmd>D2K$);5sXkmb&C?E z+r?nu!&;Go@eR_-xkif zpgDy;$P1NHM8*SNzSdl?6MLei~_I%9Y9l@^eH zDyu^}JGLSszG2QE!dMu+Y;C3+(N}9kb#XY8GW#qnZW1H1-|C8VlPF;$3X+fHCm2CC zvhXEOjNvvM{P+aR7#V)hQssnS@9R7P>+jeqG^2ea!I^L9zdqrSU#5u4U4Xrm%y)|z zuKXPWz$Xc}7hhw7yuT!9ES)pQW-(Ia%28;HMdM}oCcs51M83{vSeEib0fo_OO8Qul zi2g|8Iq+xv?@Fa%jE74u%i-Y-PcFa}jBAb4g6n&(@FyPf3EL=v$PDA#P$57Zls&dI z)-Bd9LJa6w>n7L;-$yr+M=}g129$VFF)gjCtnWwbM>-5v{ea@%10>G29@sZ)c-?Y* zrX)s9aTV;AlkGGN-ibyhY!3G2b|m;#Y{6=3PXV3FL}tm-Jqxv6r3$_%L3SCh31Er3 zQY%+Hr?r&GuLT9_GS!ytR_uD013Uf95-$h>0Gq1LBP0vX|M zJk&|!BXbr-&qJ5QQ{)fLW4j0UT`H*Ht{3;yq)o^*FU{|`y-?TPte4Pl_v@D0Gl%Ha z!-G&|mK>>1I!aHVRo}SeGQb7@7W98)`o9F7o$eP)-`$nH}G$9Tz0=s@ve#4_QG;_8=7Ae4Gx3Y1l z_k6@=rm3-B=F(O)VMKvk%S9=V=+k663DAo&$?wv!|&57W7+W?r`n$x!W!NWOK)3t4tR>&Tj)|C0y(g_X8CvRTp*GyDQuPMpflADkcS?H2m6yD+1{L_XsAJmXd_H(oIQQxuC21Hlo@TqWex$qKp;sSnC7u^KgxToU`B(RCfXj z^=}-f;3H}l@1~~NRq*s9f?I0-aN ztR?8>V!RRe#idY- zr^uZTI-(8(Ry9lzByR|Kw3|Qw*}1jlDcxE=t=C5$Xi(RbOBfccr?Mj_cp|;cBIaCh zSQ(vMhaPaxs9Xt~#YtSRw6GXwXP#Ohfp4`^W|^X?9XsJ;Oyn64N}&xdoXTI9C!-pD z34e|1z3^*zO29~u)mbmWG(cisZ19;tuLyd-W!nW2g3vjRl5zknl5ttLknpsGeBq)( zgLT5clQK}?VtCGx#ziPnNgU0i@(w%^p6MWKaF;5rKY&||=BV%C9T+|2`xS8b-f~~b z;gMI+wO-nVDLDF4?lT`~Q_Swneh&88hTqPWUi&q_WQe;qT($M#J;#DyJd|LMiu+|n zP~)f1*Da4cq0RW*xNc!(OJ2WfTAOtU%c@Gj%Yri_tT!sR5c92+BFhC{+iUaut}9}c zhF)l&JhP(nEIc1PhWRC!+&DHgfF6yd?Ux=CwqrmXybD5c$@u2xfRG7BB+ed1D({Y$ zXi^IX`XcIDP4wbO1j7ZPyCZKB8T@og+v1b5^skvABpMuESOxT=gSGu?))d&4&&^uv z%&J`^@cSpHy3tU|kzh+`sk*f4;Ap2inCEXup0ixsf0OAS-u%~&4?EL;lZox$r_LGJ z{-aapiON#(d%{qi?eEDAw$&ZQ!CasF6nCanBHzg zt00Uql|aD&>u02=r(&Brx(K9nyDra}Nwekezz00yzS}g>#`WwQQYZBt8h!dr>i+#? z;VbG{K4A!azE_mgyOU|M$~C`LYNql~*!_S%sjc@7As`GiUaK4NzAe3eC@cXdsRAQMRB~~p z<>mNzL-zJbRX#umQo9FA#T%Wh;8|uDOY5>0t)bhn0B`-W>eAme!ww45`6*ujAznUP z>MP5PkTvPv4t+(Pjsz?>teR?tl`}P4kK7spla{B%KJ??f@^**aIUb#XQR!DQyOKVr z9OnYbVS;YGiRt?Y*DS1SU6lXruC1}+%0q9zD^#>byft4dPH7#$oiMu)jVvQ&5T9PJ zqDk#6!r(1MAM=4gpWc8jCs|>mz;*8JSqhYCnVBTW{JJZBwxkEoHNA>7s^yklj`+GF zpDQ8W6v?J~^rC1M&MBw~E_Vs^sIP3cQkcUwUaXxdsu&Owo1vofSk%1#!qSnTSB+Eq zM6m9-Kr#gTDKyZ*V?Ca{1{3jU*vTF!urZ%(#%S|^XM02c8?7AO57bMbxulwVm)~cN zOdz!I$k2rL$mh@L-W*tUh5U;J;zh4mxeAq`aHR<=)P=aD+)iv>(_D6vU87>Nejo1l ztZoxzh*1vZ5FV!Q)&ai2iBa!@KS$>tL@i4qWiYSRst)QJ2sW^bdO3J88hY@e;Ol4k zUW^%&Ry5|5K@*!kSZ9%?A(nB8z!nN6WbnKyZ9oJd`pzo$Mi;5l2u{n7_`7 z5hc4X1be!nb7`XL7>q$zY7EIM#SSSY?&nq_U(HtlTv)gAHOpsyZrf5X$3Lqc8@1k$d)I7g=>QqrB4&)*`_eF zGMcj5(hHW9;uqBjBPo~h|Zx+57E=aLq-kEJXq*Eu!v_^td)Q4!; zY_~8~#AHwpYvqGzx!$kZ(d?lrt|aq=LlS7lvy3CEjs5bDLeMyou46M1og7 zA~WU%MxS1bdVC|0V-uET8cXNWSg`!I?liHP-zKL}=haY_rskb4_Tcul0bt6S{1ZJ` zG>TyeK_*?|XMq{IcUua*ez{noC6{k5F-)q+rghV|av>k06E>kXde{@aIr8lIY85{&2qK(x1JYPMv4&$v*OtPNl_7F+wRa9rIIX*qc4D>Vme}JKA#h4dtFOsPBIOH zvB?kbLi6isZeKE?81UTNJWg}4(wxc?jZ<+Jk}XymN@b1hE5JD@?E?*{n;=K0G${tR z!w$YZdvmJ1YX;A*G7PBeSg6qB*s(92832LnVXdE9!*MvKrq6lt{G5cR8PCuz4c^Iic$?{hno!hALrVYPg@3rrt{|DXv-)jeYMi#dJ)@}BGzwv;9{Xcr=!5`iJzjo?{ z^@Y63n}?+5CL9?`%M12TAAZ2+gr*Uh{%9!JIMCVB`jJoqYf=eZ`4LJJri&m6)QHT@VM z$}M1$+MkjyU4Ql}nRIXsRX*#D^Q%n)(XA(ssP3!B!_ikiwWP@X>3VkF^Y}^hh{!uH zc$^`QOfX$K$*t{0JeJOO*Tim=8VGIzO)9XmT?=CAUY&aXu(18^v6u36o+dbSKVKZ& z(3dNUOxz&gTfP_bt}6hWL@%*IU0(ioNDp$#tK#Vy$T@Y93fJH2&ASTLP=o@ zbLz^MYjD^lg?oK$J*_2RkHQiY>wO{ z!(u>2Y`;zBduyn#wJTDOg6NJgu=E@TJ*-tB3Ec>HuAISztF%Tz_A_S(OTz${ZEqK9 z#tM z7eOOaaLg?QNYTt^ps^^Mnfn|T6^L7$L5+zxuVL;LK=V}6XF+S|k<-=f|ZW_F$LZ?HMwuJHdY~r$N!e_tfn!`%Gr)spSsrvsIJE!PO zw{=^`wr$(CZ6_7mwr#s&+jc58E4EXyot&(>&t7YvHg{`h-u_qr&3MK;dhc)l!gdMK z9QEgiLky^$)2MykRR~i);G8FQC@f;+6^JWqnmzK6n7K{TSF8v!Y?Dq+@S{qM>-^ju zCZ}4RB%G?vPpDmUG}KZG11w?3vIW}9KtIc^>$y{@Fvc|q@nWXMWQT{~ASlO18|W0C zfIxRA6>LQXy~<3j%s{#kMm0*ZMVLhPDy(R42Y>13%DmA@^eS-tq&n^AfeF#LjG%{P zFJ5!^?Cwzxk*=tl$w$ES$Xga#(ME?uZ*zWd$9J53cU-LjDc!2@L@&B@?DWI|z?rYm zJg%s(9c3-T%L#!@ho9tFtOe_|X7AQ*j-1nE3jLHg4s)ti8;>1?^CEArCGXPO)VyRo z^6eFkSgx?QidDGHb8zh=m@|0SxS?4a%oZ0KazXEtIJmX!t&(oY2BGR8 zm$~1->P1>LT5f_b>}NdIo^@1Eku@s_bdvy73+A{3Qj>u0e06>d;P}^@8Rg z+KJjOJQ>4WfKOFLF%c-Qjq#SA`324tWln9aH9IqOH4H|@Rh?XOG|q~4`E7CaQy62g z+nYH*BH;|YYvZfsdQZx34|fT@7c5lGrelI2IhhGnis97B{b*dy?=+|>%^4%WFV(`k zeMc(9P4GN&#vg3h?5R-AK`dnO6wrmcfZ+6lP`U3jYZB( zQ8;Gr_^u`5xA*=9u=LOx@priXh0MQ(VkY)~_bD(l{7Y;9f8`oxhJRV3|6dz4MNRr& zaP9fFL94&z_w z&Np5(c;{?%XIlPKyH(R#z8Uk2hQNc7n#M6>sn%up8M|YapBqzSjxFQHF3+EY)IuOq ze&eaS^azKsm$%$nRSg*Jd6>Rt>>i9IqCday%cxpS#e=&S7s05w)u@>(-{R9L@7LSX z^9!Ok-l)0H89-6S8FHgc*(h&sYooKKri!|~Z;=C%@QGk99D zjZ}q;%U2!MnP^w9(z*X=tR8+g_=@Og{%)9v*y2m=x@lr!8>F%-tlzHsRhoo60QM_k zqe|?y*rZ8b$U;0jU#eCOJ?w0vDs9|B^qG8U%W|O;gL5cR?FEaMTf2izc1RdXLWOnf zmdba!-pM;@ret|PzXiV|1V(y6GP~O{lhbs_ zd-Hs;SiqYKA7vLVF^1N5L#-?+H>5PtVq(Xkm?T`y(odEB*__$Stpc+gEmf^XYZ(uG zEd?wdSL}rlj#yz#y8OgnI)=;zJvUh1Mk=EfRp1EOzW0t>dIzs290U2plv1Pc*^i+p zn|xb%OYi8QVojAX15W$S*0yOvpc_PD2{1s_7Bhc%MEI@sE*-9)wm^-QNS5~okPF&m z3#Dx9kvV8BhueM4YN%qw;X3D@7ewg z$>7q>F@&5BSnZatR>y^anYH>4apVw_yy+u58K@}V16oL@VkXhfkn-4@+E zPZ$*=hMH$VG|0C;jq4-(Bsl=*faCcZ%T}^lg*M0uIL=lz7`l_ljnr~RCZXM~Yy*$e zMvndiL~da;5SO6pRkoAA3_$^TW_w+KbrP`d=6=%Tl^B?nXw#%BJ98QQNbI;(fRc60 zR&^Qh5KaZGcVB0DLFD7o2Xg}3Z4m=4S2__?GKXWE-M4qIRWV$cm<^3sETW-8Ixln~ zuuU0mS?Ys3k8!X--wTDpy~)A=AIp*i+|Ob)h}G3`Y`epeW5bv{tDI*Cc1WXI*ueo6 zuX^Pj#u6_KDUy<&XY9l>WLEb!YRUbqKcs6xXqd@-ECJbKa;1|8#BRziCFTaGjiR4U zjg1_`n%-$S&i9(WG6wb@ZP{tIZZIIlu3Lm6;?BTV?-}B%hYR9^fUDEwD+Gl$B@LCB zUpe#yCx>fqU`*0`IM8BYx-}0b&Peq;mU01#h5H6L?+YJC&u7Ws+HkNS zA%QB_SDD;RWB8~{(Xsh6rLdu>{_I=~c#bDPo0QQ&_ti*oQC-sYhJp;q_IjN^50-ab zp-sEgHoG03JVl~^Iwmcwo(Z=?xt(wT=6HO3NLFV=-tZ6n@XeIBNVC1q-8O3!qPuIf z(48!sBHJIkF-r33fAs?t)kX^c8}$DR<$pn+h3VfhO6GrW)%{1KXJ-6I8hy6PqoO@1ERfq8t^5AoyE;bi_pm+P%| z>~79Erq0v+H{7*}?90qr?IgsX^_9j<8S_J&pHuWlEI)Oo$Ht3f+}$*Kr5p4E_SALf zIzg*+=1n{$qnJ)<%vc7ltg}pswN%EaJ-eUD#~PgZ_-D*-yl(%z?t5j%9xp8SlJwFg z@+VHO&FI9N+&ME9tg=Xpqi>SF&9v4!y%Kcg#SN?dN-BeXC2&$M^Aye1QXRG3Mt7pG zEN)`qnqhjpIX6s|YnYJc!j_*u#kir5)PJeCUmrGXO*vKqqd#NsLZcA%QZYW8q zDn`{syI;}JEX%@oc(~g!pkrSeuj^6t#kKZz?{&ZXXaxmut1v%YhzCiL+ShVj^H`lF zzFHV&@sFu?o{%rXi71(aI_iQPHoiznQPENyOEwQsm$ky)C&@^Z%QT?ofE`)K0d%nU zuCLQgA$n^26&57DuoO}!#U*mV4#AVj2@(PrS9!2z;*DL)w}Rm_@PG=d+s2}wt3|%5 zMtAMM@80;-8sOIbTTq5x8?!6M^&Lx>nw5`>&VCp9++qx$_5H`U5UkWxnuu|W{+@ooqJ-5elAYgYC2M<N7O|Xs3;~9Os~(4A%%tiVD?uv7*?n;wKC0pGbwXxM z>0KUt>X5_Q0(Ec_q{Y$VYLhv_8wfYjI)O`1wTx1UcDN$J(IJ6>gPn{*TN|yZchZ;m zTG4JOjVnq}uqXf(%-z!hD3>>+6uUC+g}^7=B!$4Nbke(*8qt1X5B(DVz?~OJ=sGH3 zD~_gxi1i3HdNlR0rs%GdKq?i`$HvwQx}b(V&f$jSHEV!$o3pMcilZ(Y9Xgh6zvMBq z*wVSl7=usq`X7<+sT*h=>g^J}b;uPOWlSVRn!M+EHnb&cBH=yNO23RY=t)-G^S7xvM&_3o_CX+#L)5)C4|@ zke)wkq-u)FY&FCQF1j^EhZ2o&`*XYUIU$y6AUqBs6!6q}1*&c@OS#F>hA49i{tzka z4B|2k94Q&{b1mqCjX`UEPE?y!%U83C+prE6B~>lhdQ3;1hc51<5>w(`99D@*h5y05 z@{oiz3q})2Z%UaeMLm;EZrMAo4ZFFKxJcny;xI1D`^VJ4^#_0`f^;dVCImX^aK z&ER=88r>v4cf&7v zYx~gV8%mTZF~e&Exy5)!7%a{;O8*T5;I$|pnzGRqi?x|=g&;QeB!CO^9(V!*&3YdfEpqIMrrdl1WwQuEvoTJ#~OHr&uAGuoH0Vk zXKIe3;-_&RKy_$P`(VtH_MHC30v&rTN~6QI@XZ66*;FxSbH))ab0O)QTtp-h#OpAR-mN!b4qD#c+f2Qp|&J z5V-KA9EsZAa>3L)R zXGL$tYTx^B90*-^)FqFJ;*!y2;G{Nk5#5r-k0~xHUXq6Z0R$nekQ4wGq9X?rt>7f2 ztERuz8k|uk2v*WhFEe({G6x}LpG)6pD^G@|5~1)(2l;El(V~)%kiFIJP-)T6P&rW# z5wGB$_{Al^!N^Cgiv^Nmydiiv}jze)0^?B*7J zva2UgtDQEI&(Z%psVx(I%K=U_Mx`%ll1XysvV-T;^Xt`{Yc2!ruCGC2g-n`jPGnms z$!m?7R_VNg_bRQZcKlwJgB;<=q2Cv6&|PqE317gx-8Kf)R8JXiDpU1{j@ZIPQqoF; zBu2$`?k@B!{RoGPbiDAE&3J6M$tkLST%u5Z3qt`|q(~``wv@&MFFjGirF1J$%IQ%< zV_+*Ckek4TER{&!Qrf=>X5)|#;1L^p9jw#*snw3Qt39fvMLa=v7S!ZL%Ue#barY`` zkPqDM&d`H$*r_VNz+x*Z&$*AU;+8RNW%?Bjwp5#vCU<3J1AF{hK~p8Hrkdy41tC9a z!IM2S;mmZA;&qr&8J`Ck+@O@KxvHXp5fCV35OijuQs-ThWoV+Ux#91;#RiA{{21;J z4*Q`inT;)TK!*YBmXm0@z9BpeKSeuYzkLh#B(mKApO(!#S ztLMOk@7FbJ3e9qZO54ZN;Y<;)m%`k!0(r+jZp)W3bo!2WUdo}eY|VDa4Ig#p=4xpE z!|W2igJ#t)e!bN@)YV1Nx$F?WMd@R@f0_b^r=;DH<$bV6xEGYD!=enW)|gtP>?EMT zXEnmKy{)s1DccA=wzpokegN>gqzzQ(8mU#o`%kuVG&#?Ja5da0`^f+;Q0~5;FO4sd zd34soK)CE-UC%J^$eexFAm~W3QUoz<3bGz)Wc>M~-1;zlx-C}guosN26CFAXEPQDv z5W71~3ocmqXe+Y*&Y)DD(f5O}NKLPU=rBTbnD2yslp~t7#Gar-6FuAfPqxdaBD`3B z3T_VvqE4YzeUSG$cvz6J5U=TyD14a$Fl#I7rF!}@jFAt;TEMKWN&uJ5s*Mkc8(Pi9 z6n3Tt^@fRhP+1*5(-s~+_%P{Qzz^p`X6=4+iLab?;m0R~Tvy1gr;n`;Uly1(3xY9q z`_J;YP)R2^=#G#%jXr93<+Xa1$H1PE(}xS4EHV{;r3aHRcsrr|9~CWA>qr+&&O7_M zu$u1CQM#2x9B?1T+FBmiYF}@E#(-F+4=`K|1PV7G(RFRq=ue04d{x2iLLwxn*Z#~l z*zet;$0c15Vmm>uW)aAr@qaAxeGycCdcf~5dpjof@N^f$g(VklV=Mc^3NpSbsC;PC z5_h2;Exl^<5`}nD8+EvKGkdt3goZL4^*!A1hLV!r$$wl+?I*)+R<(Oz(<0+ByZZ@C zs5Yh3P5Y(JfuDb&w@p_4FK>kZbaXK@{@X2OX8P~?&HwR6VER}4=06aIzB@L={uRuG zc~68U(Mqn0+D8%G*Fngmm{&2!i!9Jc5O%O~OxvX+trW#Sr@zV|C0(u8E=q=tIJnGU zHk*cd2@!J;B7V8wwm{AH&0JU$g&Zr@6kj-vo)&x7%c3>su}AN221Quf7NU;PN8~E~pS; z?3`lIza_*)-?3T={^Ro?EMv#NBV}l5F%$M@pGFZy@-ZyXVvOu6=lU#KAaVVYq-ZZ| zMEtL85I*z{fJ6hITVyHre&z2!b(d z34LBn@WqZ@6>i!gR!wp{wGHU$E1#ONj!)y++nl0uJQf4?Q1 zuT}yH6G8lt8{J6zdBC{Rg`YJWKeb<>oe|b%Lv`m*P+u=WH%INNJVT=B4@NGAw#7wH zy~tS)l&Y=sV0MLcGh8jsgr?cx)zy|#d(GyPDb6FMJAHK4XD4BWnZda^Zu+FEoy)sZ zmD-JBIubL~xYm4nt=6wXZZMqNr$(+@Q}? ztN)#XOs&iwe+rb@*t&`%e+hjaN}^o6g0_MoUhVM@6ozx75(eVf=`&iP!)EisP91(4 zolxGItz8XxQ|E<42WV`KpmwL6Q+~LMeq#^;zT?Jw?6ARGP3|sgR8`HhoRdDBDM#IH>3xsq218gDRH4vF1HxR(Mo)ccv2i z+p0y;Yyuf*C$rWnhV9|b^_(iSFbc^RJS{$dxOq0Y59n#Zg!fz_Yy}#O8v3{8AH9Jm zSqhkey17~k)%|KQag9GYgMoXu6PORSph>(6*{fv{^7KkL*bJh&xBC5S><0#9{2JIM zwXut8nofW_x?D!KbEB}TV4e7C6rtSZaJdGU+I@a>&zyV7Qp?Sz&bdx058kzlyKjkj z>4Yk>)H*Aq@da)jEr?oA70y~PY|TF-glbOX`xh^v85vmOs|+{Ux1o9dE ziupPG!59OU#Hg7^ibcKwFP}V&O-H{DsB9!DTv|xgi02*`6-616!S0Z{FsVnbC zCRDWVKxY1`ef#HZM9;#V6|d)$^o2q8Q-Wws8VG_>4xASYR&?!bgkWSQS8w=U*HaQd zhPH@Y^Lsrh^JfU1`hEElH0slbEn7u8rF6Fp#F0C}-L17(kTZsRbPixQP30UzwvONf zv6hsxl}wGXB)*iLY;#No#e_jeP+hP5=9VfKmT9~fVse{9)2yv7cPUzt^sSZXcgb~= zrNp%TI$_Aryj8~Wdfl{M71SOr!Is68|DP_^27-&aG)|f_SDNx7%?ZV39N+SE4e}H^*vTylI>s17`qS`2|r_>Y%?QYh}k6n3%xD6g3Y_ zt;4eN#V%4fpg|S^RQHn8xWblleMaTDVDZ9n`0gp=FHPh|{ispQMHn2<@ z;vu!B3MR#UaqF^6; zu|5Zz>fl6n!{h*n3X9?7IPLwUmAvOyAFOOxcx0O_fT?zum>{YgAAICxT8{r9!V@!f zus|~8zqPb}fdK7j=&y%6HQ`q8(pZeIVDO)K$leQJMcD1~6{P456`}bZYqBJm)b0sV zWcI+NK@7c+} z2r0?;i0l62bYaH?j7F@`dA{&7FHIMAxSyjpz{+C$%V3z7Q4)NH!OuQ{EmE91buIKV z&HNM0auTMCwuxEbuA3A^tu)7$_a@quy>6U?(dFcV&bPRgJ*f^>*g&VV zK}0?12eH3GREHo4Jeo&z!@zE;twNXaKqFQvWTziwbd1VQ#SlJ8z#>b@45SK@3yU zC#B4jbtIUfb-XwQcN~#c4v}GIXy%ix95W2@+pHwA!vgUSJb?PmqJ z2Y+&J#HrE(D^rAE3d$o#yvxi-kDEuh3w$2(ao%O?E6c2%pK5SD;J-RoWUzc^L2Bt^ zN<#oI(MSUs*H459LufY;z7oWb4e#IzmEQ;yWMotG%jM^R0b%x1bpQdns7Gm!z4M40 zbiXujmx<1B8iy>b{`|%wnWsViSXk7~aspgoh%F41 zM~b@%ouWV0iPF~G!gMRW(G3fJ`Ehi5e0NhHmR13sd!@IMFnB$(6&&UcXrs0K{z{1n z$tKg48;OWImdDQFJC$U&@&cSnLQs zxwR*)E0EEc8W?3O<+p1Q6RPFO`|(|ES;^@Z0%w>OG>{s%V0t;WlFWT{4k^fw!=hY1 z9dGvukIXRhFktVfUuZ73=RR=MKaKnJOjDvqklQe4XYNg*UfkJ0gT|P4e}CT1NFE^) zd{SDrWh0Nnn4ZEMO^^_n#V@$=Ni}{KVH`m6WN(vdrplED!5s0E%Wr+%Ly;Ap9-Oj| z#oZk&ljQlx`qTU@*(W?mqQTt?CMX@jzbd)b;sE5gP=*Emf8(^Nwvaj0iMnkb}Y zrm$FtrXAcnck(%~2wBDGNr={vC|3Yl>}JIR0TmK(zFK#D1NDO)tU5*O=9Sig&Y=Ey zrp!G0*Y1~)sHP&&ypRDzEdnaX1tJ!Uw45I5Xmo_W6n9u5OD;?(ZHmVva7duPh~}$6 z7D7JM3KVh3t>yxe=k5}#Qc+zFBYx4765tlPhN?uTltq-RL=YgIs}noi2DYp>tjgM2 zq*CllwtAmIz1r;^*aae#VZDIHAJ~hcKYkqObIawMgM z6AlfudzIxplw#`^B@;_djQ8;%BSl0aX_^`m^=^TXLRs8RnA`&iVrK!1(?qjc#4yBx z$T4g?wlI(ahlnAt4W&pY`J}+Fu=Ba50*ZC0u-?+B*d*pGex|r6XT$eyDg;#R!!B~<&f<3YpO5ljcXP+b z++W3B+vEvqzOauZJ-Y@QxT@>yqK$FRCTU0s$By7W;0fc>QVvu=ozsLtKUkQ19v1v! z5t`|ac)=`X{r-Cb`3bIKXmpDOn=XLlzm1d$;jNn-wqOc+UFiOhc4{ZhwW5?4+m*v< z(s&fz>yHH8qmAmUHu`{zx_e5!1{(!y8BpA_KDJCGhy{+iN?A4ZN&Bn0&ksWK6VacP zRY1l*yJ2Cw2-m{By$V3g_EPVJepsaxUYbHv5UX=lZz`d{tL;-L9B@Z|h{ zvOKO~i~jAkTJJPU_eM?YZeal@Tug~sZ7!0gBpG%e47cTj9ZJk0*Mpr`46XLu27VFI zgH}}tbePJi2ug*RvIgu3#|OF^9uFIzEp4aMxLYY_6!mXFY@P=1~pJ#=29 z6#JT;)sa&&G3JO~RWK~zPz@7pd@IM|m(|aC{tV~`p2ga(`RYqB2|j7K(3A`JLE^l@ z%hA0`@M7WSrz6j1)r)|6;=2Tq0PQ1D1Q2O8Iyv8wX%~;9lXR)@*S=UG)ES6s35Y+| zb1&Yknr9t$X>%5yW&F4c=%iO zKaF2T9u{GIE6(fYsKP6md5 z&(y4cUomB7{YNXN*=qfX-)-aFXVe{S0I1p;Tg}z52LOZ$DvFdqa&+Z5!}BXSc^Qbz zh0Rm2Jv_B$Bt(*UGMDF zkzF+8ptRejaa+BwDM#vu?>7VIttOA+sk6Q$0*c8m)>2U(3G%hd@e60QZlv8Pg0s|T zO%Nn9vh*erz$G)wtO0`9Wr$rPTBR3C3xv1>yEIpZrchrl$Rm8V9m*bgv zGUU9J!jAM6vp5viq#pUC7lXTIo{eOJGrsw}yoIQgGimgY0LH&L$z=iL;xE-$7$>qx zc1b{`S;Ys*wKUgf)h(@t8VzX-QC4{#)=de-2>+(vGbYl%u#4ZuWz)LIgs&@ZmEhdO)7AE5ryELH6 z4H8?ODKX0E{aOjrP>o$!sPz8McY`aXw#7nGpYpEKv5W(#Rn3zB={nFS7e2`F>TllI?}*FJLk@uQpA17bCFWJfG3?X{gR>UJU=){8HR&w5lolT0 zo-3R21_=u@#tg$AK4<%7W1LG$KGy8HW>cNU8-BIcqR8RRj%nV!wx?ZGjw6UHQKUBc zSYbrxV=WY?D7=%0fmGfiwYArRTPj%qO3@QFv}cnKqZ+^lTjE?Q`OKU=K~pTLN}(KJ zMSo_;aw;E`6FN=c?u3zVz3EEGR+K_nD3E4@NqU&^_KnfY6gkU)Tl(8!Z$MYtN_&#d z)E*#maSIB572=l;dvKOJVQ{$1KzLD7LL5ktcH-$5<*;Ng5?Hjop`^Mt0JO$F7DFbx z5M?A`h(8z+T`=|h zS(meBx@`R-+3w%;EYl`kVI>@LUTT1>{+-jf&=Q0@oFAGq+kZ@fTW+|Q=qn1ar0=1X zKFS-W)M2wDM20vl2?a{~M!;H~{rhGXi=z~3=ByG#r-FHGj%$Vwbv+=*@+1!LT%lr< zcW2zt%7_AutV1UW*WNX@-NboUjfw*zcnG_l@3o(gU(b`jOSP7K*~3G1IHq;9v`hW- z7>stJ>p;~l7Gi!I_r-V1nuco4(waY~HT5MzyFsH!dqkYDls2OKZC0E(A&;fp*=$oh z_fee_j2b<4`AG6>E@*DXjz(%GXyX<|)}6Qc-HhueudIwXcgbCK_$F|Fp(w)dKL}Si zOY=#m*3-W%yKu9O`)2DJ-`@7%o2 z(ouc?%r1oy>YK#}!!Ip$f!uf_ejJ~4eSoz$-4+@u#fI~vJ>!BGn_!)35VO58?4B15 zgu`LCToISZLKnrczxh8@u-_Or0F0O>3zdSC=X8@h;Jy6XL2_FWT{akOkbCq)6I;x) zx~A>HHN*TfTUg|so2UV<#vSvo;zLH!#<3V)P8!fKTg7F-9V=o2-<(mc1)#!E*q^d;j5yJD{Ix+z)I$k%$O#FA+|HbhCqCL}p6cpC~&XE5P-J0zm zWm~?};&K1#bvaXqd7Lk*N1f+v z;JD+vi^Q8JxrqhMiafsuZe2RPN~}vAqamf+XDuPnP|Pq1(2+8#KI<^5soF1FQlz8aV~0LQ_Yi4_xC~CJ_y<5@AX6XJlxBSZ+!Rfk?HzGfzj^0{JAiN#_Al#Y zJ7@+lUPpyu;c@Ni_9k)?K}qB8dh^9Kz@auntIJ7gRn-da;ACoxDl$-eWQ3%K=IWz5 zQ`HIBsxf(RZNj?>7fA;Sp@#9FAX%(pA2v@=SHrD0EVwaVFI?_`+9VIe?jic%wcB|w zXN4&?y^layj^>TfZBeRGT8$;U^B(Y_YV90>z5}}aBkG1ez|tSm&8o`5yaaDt%#@I6f$HRpIK`YK&i24_g>_x)j$pg5B%3wTOx zz;n#;(pCG768qBm_>idF63tjx)l8`oQW(^ZdKVG^XRydok(i|4^@RS17{HAiDH8c~ zA&KLL0Bo}w%-#JBfm#++WT6j0q!YAgh^a18ZC@9FJ;A#qCd;k(7+OJqHnJN zZwz@dbaopADn<#9huhY!F!Kgv$|DP{((qp6V#;9n6-K{^!;_A5d+boh0NL%;H6R1sv7BZ+sz8GRtcw{n2T#QO=sj0Ch z6SkwEDcr~@F z)_8gQ*IPMqUh5B95ck2VdmwR+aUiYZhR7h|#52_cb5+MZ=7r|sv+#WNxH0642#+?D@pgVF*Jlw4kTy)!U*4D`2g?YH zwDM(P;pfk?z4^o0Ryn)W&}Yz5VWAVw@TJG63Ib>t(3|22Kj=ao zD^|p!(Jk!9!ij^rvov0IZS10BS(YA03N=4N2)=$elZ)4|ekkY}+`*-gDO-wXeb}yL z@vQ#>4nTymw0XsOSlL=b1%1MFjC?X$zy&lX{|rE7Ez#9d3<1QclN)B}vtv4*J%+6; z>tB`sgR4A%>SvmNe#mJDhmykWs^|ps7*_E)XwPAmUKnbGxpa`?{i$MThEvj0sbOl} zSF#>zaW=!nx~>3zIq(qUkUDXNOK{SspCB(QD06g^y?z}#MnCS22)sk$14CZxrPiXw~x|c@96uCA&bn#BEP^PnIHQdK-_JK#GCu%pGqzJdw)Ok zU*!ESrG@1`N(|hi|*$l8j}U zB6~M#idi&_j*jQSxVQFxa)$E&J@9+T)}{(*&JyX&Ho$Nlge!T^TVM<|RR#UaSd>Q) zHAs~u()z^W>GplgJX)~+JP+GRGr1O@&G(rAR7?($lsV;r+Sha&y?MCt5wit`Ks4^JQ*r(3O`=$UDVoU?{%V++gXwerFusOVEeKSP+UIT~4u6@q5)Mq-3MA$l_)PO5@ zP}oCWwJ^R=%tExd$JNB5$SEtDp0qoULz0KD^fx)=DDzPAI9qJNiy~8WR0lk%Pu`+n z@CT;A)t$e_ZA+j#wq*-y+3vWn;NI^~jrr^qy^4)u5m`Sn;{hNn2VN&-yO5!7+=TEs z{0HLl2EEsEZOk8Xzo`+Md{UO8xlFy$G#VVWBXC$efjtmfHvQl3qVS$Y0D-Ys_+IJr zY(02HhQwX{D>%b%o&DVExBUWA)2!nC9JLJARKGKoGkvrDrU;G7s7aweG^&ess75rO z)6wXz5q<+A9s(~HRFJgUu~poUBNZK97PY(Q9wzX;0!tgg z51o5zjSNKK${UZ}|9GswXqUu#FwqPQY;a1^b7Aav9ZMAvX=h9B~Ta2I7b>0M%&Oy`PELX@!mi2nDFY zgwNXvbL5e>RaVnbmF!Obyj*#|Ln#^JZ~q>K3$XS`b*bNSA_v)s7+Hl638{ax{w{y$ zva8-S9+?aKw65mDPRA~T2e+$Ujb!C_gK-rzKJWf;2XFl)r?Rtpti(M(?Dcp#TK2v{ zC4t?vm8*)#x8{^sY|(s0kM7l!G5~-(uIYHuw))A-VI%CeZ5M9A19?O9ooQu|Qlj65 zcmLc0a3KA}zf7kis=EVt;k#$sVRkUVA#Jp0yCZWay^pc5T?{9AN{+|t`rabJXF)`5 z^4NVi_6j&ck^kfxvze@zZDPmzi&)3v;VV=NmW_9{r{SQX9q61TCB&H!V<{BocZenT1QL7=tT__kVqR7 zM1B^Wmqqce^mYj=HDt#m6gVP@3s3*dcnTeWmC*r0{y6PPn#bf1j$eiY{77iue*>H( zd&Zt3g81+NN#x8Zbo==bBMbxNtS1TS1}%~?u#r7Lh-mqV04Drw$~u1GZo~)Vs=VM@aWs)C zPZjD)lSoPT@Vb1LvhmFqSzB#bCXk&_BplYLOuBI)FR%4hkVu zP$&FgPUGusu5W7*HYma@QoQX3fKLTWLTK2eTAx9T>ORtPnZ^WjQHm`h(9WEXe7b1! zT1u1wAx4Y+&stt}3J7VQp*+-{8si5N^NM5*N~PTOlE+#mo$u82{MUC; zN=`#JYqUt^u5-UbeXau2=4LPi>n(Nr&+`;5J$ftA-}{7P=yYZ6E;XL@n7^OAgO`=SsQ){=JiuLD; zaa%_TCsKy4rP^kMWymX9;#?E%1FZ$L3m}>&VoYTsWsbm>0*yRlbV*?rmSE6*emug; zO+jDi2oK@%)D{%ap{H_HT=~V247MD?NcvL@9uyEd>A5*Ki?3Q&lhq&c1vAi^C55^U zK6#NR%b|{(=K$daJQmrsHly;v`QZ(>%{mX&ugyp!=_~NUIWXJkuCT@+h7}(5$3K>J z9zQ1O-N^{8Dh824Nw8sME7z#ps1>objIizkE7hFPm`Sb*Qok4|S?IkUn@l08Y>y}V z38H!V-9wO(x3V(Q%ntE3VTWhsqKpD-=R;7JtYp?Gn#G>TV;6MU&$n{o1NwRTFv?-> zX&HEOsUhVwaDYI^;{Z79Jql*ok33>6FjHi^Ums7@XFkr5vfr|Q^g$2dgO^P`fYH}d zuUtKiIPm~Ea+G*_LC*PRu{)Vvbs`K73~Ve5PXl-y?rdM5(T>Sfz|eSHhx?=E2;iauB2BrsWzx@?WX~c za+;%rKT2~$3-5=VE7ymfo%UwFoAU0|V|2hEJG#u7o9^g>t)rv`b3(_qV!LC^p5@O9 zg)qHC2&qGw&|p8)qg^7%^Ah3i(ncD`aWacFF>D@%0sH`(F`!H3K7-w&`)bB;`epyC z?tRMB>FtKsoBn?L)9|CC>9bC&=?mGvem0{W!uzOgflb2iRB+z_FZLTzP8;}TIpeU45NEU-(^88)EDx%IYf>2A@Y zNTKBR@k}m5MAX2vK>yhe7=Jv%d8O#x$2zblhuVxZ)&|rZSZR)PavX#%S4JH0_`agcnr?-JV{UTEj9{8x}p{1EU;4R zeL|Lik+Ibq*7d#&dSl3u`y8bbeXESCcTSdT`2`-3XN2_Z?DnOyq}|K@&7$Jit9`bX z`7APd+=PzGf)$s$tQpBOLGaXhJG%+tvC(yh`5@I3C>hTsb7XIp{CIDx=UaGskN$bv zcPt@W<4BSzqFNQvmY>7K*?a;fh?H^U-+NV63yoV)MO}-91~GGWS0b+pUjhXDiQ-nRcF72eJ3Gb@!=j4exM>+4 z*mA;AD06*uJG|nj@l6GKz8mPwYv{|Wi5Q+<;kFa?Hovs=mEcWVrkK01g0=NMA1W#n z4h7PE_87CV(MMG^$5~@^jHiB;LYaoEPKYR#oc=2QmWHsEtzn#_L4Eq@?i{Q|#2 z#W4K)S^n#Y{_8Ar{ErRZzbb70_2d5#yg2`<;PrnaLogj-EiHu-j`s!}WBIuhvpTZTcs*uKc%_n9cD>E@N6%$=KL76J&`A~XM@``kA64H z5ah$x0En(eQA9Z*mQ<||Kusv3FjoxFG9*jb$INv^KFhgnvC!j*FDRiG%Nqe$pQii}sf12N8#NZ|JpqDjYpmY>PJ$Jkz(-E(hYNyfrKv(%+FIgh;n68dC@XKjw@jSrq z(71*JyG`;_xjxqrW)q!($S78MDVAix8369gtv*dUTowp(nK+msyAdoMnlQ(~F$`dW zl}#f3t?+1ksc74}iQrH=6e|iY#^y$)g_v`~Xn>!Cl)v4TlKLP`qun*KXK$$~kBq0G zO4yEGc_0cEYx(N~PL<>ttJKv{#<8&F&=TiO6RZn=dopU$Q}XNX$^cp^^=mN|pM16b zSXW51p4?)6f&Km@jqj-yO21{TTZUzbDqz2dN0ZiRg>pcYZ8Oq_jQW0CMYlNy*`2J{ z*9!7LO%BqZ!-XoJmRRZyim*z` zb-R5)Yw_LbbF}TqmcT28-f}@PptJI!w~b=C%g|PvSoDb zUL$kx0i}UV8wD#Q?w`1;+f7N|W$iJ7aT6ZO(&ztCnC3iZY*<%0ASW$3azJ!Y5e8{T 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

yg(|ZY@(X#GG?}^XdHVC2Zx9;(Po%aUf7VB^C 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�Mz3AVC;s6+`8t4qx>kA+$ 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

7P3KIDY2N@uTh34YO8b6)qjjC%eV~ zukr1RXes(BR&y>G%An@c+SXCcV0mYG+N3%|^@A_rtzoi=Q(3Aic6>n%Z%`I&Fs7eu z*XXuFtH)=cR69D{uF~nO9$6~dG(xlO<3np(^$>5oQHcN{UfyMSEri@y`P=WeQLyoi zcfO>qJ#*?wc_`dFK1H9##m9nsmdH5cjPdI4FDZ7Uh~Ku|bA8uOY*4b=ac!PGvxw$W z#w8<8`DudQQWo*6Kgst$jCu4Qz5Jl-{+h%Pu)n2T_@AqJFnhVs|5w@QzJ`uniXffO z{55ZFW>*!(0FI007OLa@2-RehAoY}-<0k~$cuLfaYE8zwzjbe)is2n`aS5#cYX5SF zzkU1W0temx=c*lQD}!zzYa83i*C9>ssISb3 zUW}(L-=xLO(-caXo}LmOxJ(?F_k~EGUlbCpYHc9#a`m_XzkuF z3|IrsTRC`lY(8pc(e9kLCE#~HbE^65$pJaZHp+#&iRa=>>@{8o4OWZ!u?#>?+&q<& zCtB1aDz06SH*uf5`{Je3#(j}Jg3e}9R|wn_cL}*-+1Vr?aDJpJ2u`G0QgXA#aY|L6 zc`)to6r+Mq#}nZ2a<=Z{;(%01(Sj$~rD_T#at8N;>X zq9Hsl%Tg~UJU6dRo4?vjWStp!n;>REPOGet|ClTuR(fD+KQ@tmc;evdXGH5aLRm_> zKGwhugZW2yov1r4l(Zy<<>qj^87`5us>;=zH|cvv9#`J7j1F8P-}@By@Z(iYSgw18#td6>Y0#r;eX-y- zzSDXhzU~b=n`~`YWnFKc-^D(4PcZgOh6R0>5$D2{%$HNM^6Y~$euGOx%9Uq1BQ_`t z_KNNv?^=xA_F9p&IlII7mPDXMr_V3w#xqUw`x0xWgRP=GSGsv?|iB#3y!)hPzVZZ}_lZ2q!PPw54st!vkkNBzk)CJNnV%NLIOXYE11 z7)wfSm6V)zfw|5y=5bfQXs^oyPCN~;)> z(*IJT>~qai>rzg<1lWldbXZvXsVaMxq;&k&f7{roci<7k6aT{ClO8ZGt zQzXn^e&K`Oq{~qIlegm0ql^Np3y|sBNJmD5Y(nY-Zy( zeM+)-t|c4gzv(G)83@Q5E>HHebF#VpRlI;h_CLNrg&)ZAU~vooyG;oW`;V`!;IRL% z@=%((%Kq^c6#K_lR2y6zp%pTE5pRqoYtWJR#^!SDnD(|H2^L=(bvyOq)wsH4o89;4 z6^f{kzFL>$`x$rMxYb{|^hWBzPQTm8UYf?+F=$BX7jOFnu7>?{??-dZURDut~1RYO5EejuYNp2 zc5ZY=Y=KeJ3P!Q%GqqBJjJ&>Z?)WA?YXrW!UvgE?R94|D)z*@KgFEOF0cD91wihDr1vsJ!QU%E{6p$=UO>rshRuY2KTS zQ~LG(YfL9K7oRVi`c~AZ<{%RW#?~#jk_gkeKURx~rUv@ShjO2M&RW%FETtW3rP!tA z`u)Qg(PcLs+mS}@y~LHgfh;ULd6(A=27E4+7V~@E?&DNblg_L@$Z!5OQn)^}L8~dd zQ?yIl*S*_1kL5heUMq^{NhBWoA|)V4IC!3OprSkO^pj~f+g_Z=opamiaWp4mOlm!y zGNW^9PUtqb*lWZr*IP%)=ecl|5PytIQ}{t_s=d-bNG8Y6=e9aP?N#1YK{=Qi;t$8$ z7*EExP$M5K?!^&5Wk~25<`&pm-4*m+xQhy96+HJTAfc#jSjG6k=z8kY;1DZz{lMmG zd$9>av3r4J4@_H_9;$!Ar`1(ZiD8({oJ`3zCxwKQ7Q#N5ub!6Zu-6@=YLID-4rtjU zeP*tn$sROTK770#&yKgHi@ss(;h7Wd*(nBF2BxKm(i_IZ^J+R#711`*7=Xg^FUVz62$o65f1xp3}a57 zu>Y@h;eGWNwg-pM^L&WMHP`B{q{WeCHI_+vB*)0Q7?_#ZS>AR#f=$IW_UYPfN7{3> zai!8?-+E{5QK?>Zg3S0UWs5@ZeNtuaj-->XsJ+*x2ohxKKOJp!J5Zj4W}8;O+x6#^ zSEHK$6@}bD

-r3uA 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 S^6P*2>KDKG;a~pn)&BwyJ(l4B literal 0 HcmV?d00001 From 40100f1d8ec46ad00fe7ec53bef3f16f4fb658fb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Feb 2020 13:53:52 +0100 Subject: [PATCH 119/359] Replicate the top-right pixels in 4x4 block --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 8 ++++++++ src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 760f1bb1a..a10c0f417 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -682,6 +682,14 @@ namespace SixLabors.ImageSharp.Formats.WebP return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); } + public static void Memset(Span dst, uint value, int startIdx, int count) + { + for (int i = 0; i < count; i++) + { + dst[startIdx + i] = value; + } + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( byte[] p, diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 43d723917..2826c624b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -320,23 +320,23 @@ namespace SixLabors.ImageSharp.Formats.WebP // Predict and add residuals. if (block.IsI4x4) { - // uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); - //Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); + Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); if (mby > 0) { if (mbx >= dec.MbWidth - 1) { // On rightmost border. - // memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + LossyUtils.Memset(topRight, topYuv.Y[15],0, 4); } else { - // memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + Span topYuvSamples = MemoryMarshal.Cast(dec.YuvTopSamples[mbx + 1].Y.AsSpan()); + topYuvSamples.Slice(0, 4).CopyTo(topRight); } } // Replicate the top-right pixels below. - //topRight[WebPConstants.Bps] = topRight[2 * WebPConstants.Bps] = topRight[3 * WebPConstants.Bps] = topRight[0]; + topRight[WebPConstants.Bps] = topRight[2 * WebPConstants.Bps] = topRight[3 * WebPConstants.Bps] = topRight[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) From be3fd5bc9c93892728b81c1a6b75094243ad3cda Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Feb 2020 14:08:25 +0100 Subject: [PATCH 120/359] Move lookup tables to separate file --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 50 +- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 4 +- .../Formats/WebP/Vp8LookupTables.cs | 487 +++++++++++++++++- src/ImageSharp/Formats/WebP/WebPConstants.cs | 340 ------------ .../Formats/WebP/WebPLossyDecoder.cs | 149 +----- 5 files changed, 496 insertions(+), 534 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index a10c0f417..a3f5f5102 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -756,11 +756,11 @@ namespace SixLabors.ImageSharp.Formats.WebP int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - int a = (3 * (q0 - p0)) + Vp8LookupTables.Sclip1(p1 - q1); - int a1 = Vp8LookupTables.Sclip2((a + 4) >> 3); - int a2 = Vp8LookupTables.Sclip2((a + 3) >> 3); - p[offset - step] = Vp8LookupTables.Clip1(p0 + a2); - p[offset] = Vp8LookupTables.Clip1(q0 - a1); + int a = (3 * (q0 - p0)) + Vp8LookupTables.Sclip1[p1 - q1]; + int a1 = Vp8LookupTables.Sclip2[(a + 4) >> 3]; + int a2 = Vp8LookupTables.Sclip2[(a + 3) >> 3]; + p[offset - step] = Vp8LookupTables.Clip1[p0 + a2]; + p[offset] = Vp8LookupTables.Clip1[q0 - a1]; } private static void DoFilter4(byte[] p, int offset, int step) @@ -771,13 +771,13 @@ namespace SixLabors.ImageSharp.Formats.WebP int q0 = p[offset]; int q1 = p[offset + step]; int a = 3 * (q0 - p0); - int a1 = Vp8LookupTables.Sclip2((a + 4) >> 3); - int a2 = Vp8LookupTables.Sclip2((a + 3) >> 3); + int a1 = Vp8LookupTables.Sclip2[(a + 4) >> 3]; + int a2 = Vp8LookupTables.Sclip2[(a + 3) >> 3]; int a3 = (a1 + 1) >> 1; - p[offset - (2 * step)] = Vp8LookupTables.Clip1(p1 + a3); - p[offset - step] = Vp8LookupTables.Clip1(p0 + a2); - p[offset] = Vp8LookupTables.Clip1(q0 - a1); - p[offset + step] = Vp8LookupTables.Clip1(q1 - a3); + p[offset - (2 * step)] = Vp8LookupTables.Clip1[p1 + a3]; + p[offset - step] = Vp8LookupTables.Clip1[p0 + a2]; + p[offset] = Vp8LookupTables.Clip1[q0 - a1]; + p[offset + step] = Vp8LookupTables.Clip1[q1 - a3]; } private static void DoFilter6(byte[] p, int offset, int step) @@ -789,18 +789,18 @@ namespace SixLabors.ImageSharp.Formats.WebP int q0 = p[offset]; int q1 = p[offset + step]; int q2 = p[offset + (2 * step)]; - int a = Vp8LookupTables.Clip1((3 * (q0 - p0)) + Vp8LookupTables.Clip1(p1 - q1)); + int a = Vp8LookupTables.Clip1[(3 * (q0 - p0)) + Vp8LookupTables.Clip1[p1 - q1]]; // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - (3 * step)] = Vp8LookupTables.Clip1(p2 + a3); - p[offset - (2 * step)] = Vp8LookupTables.Clip1(p1 + a2); - p[offset - step] = Vp8LookupTables.Clip1(p0 + a1); - p[offset] = Vp8LookupTables.Clip1(q0 - a1); - p[offset + step] = Vp8LookupTables.Clip1(q1 - a2); - p[offset + (2 * step)] = Vp8LookupTables.Clip1(q2 - a3); + p[offset - (3 * step)] = Vp8LookupTables.Clip1[p2 + a3]; + p[offset - (2 * step)] = Vp8LookupTables.Clip1[p1 + a2]; + p[offset - step] = Vp8LookupTables.Clip1[p0 + a1]; + p[offset] = Vp8LookupTables.Clip1[q0 - a1]; + p[offset + step] = Vp8LookupTables.Clip1[q1 - a2]; + p[offset + (2 * step)] = Vp8LookupTables.Clip1[q2 - a3]; } private static bool NeedsFilter(byte[] p, int offset, int step, int thresh) @@ -809,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (Vp8LookupTables.Abs0(p1 - p0) > thresh) || (Vp8LookupTables.Abs0(q1 - q0) > thresh); + return (Vp8LookupTables.Abs0[p1 - p0] > thresh) || (Vp8LookupTables.Abs0[q1 - q0] > thresh); } private static bool NeedsFilter2(byte[] p, int offset, int step, int t, int it) @@ -822,23 +822,23 @@ namespace SixLabors.ImageSharp.Formats.WebP int q1 = p[offset + step]; int q2 = p[offset + (2 * step)]; int q3 = p[offset + (3 * step)]; - if (((4 * Vp8LookupTables.Abs0(p0 - q0)) + Vp8LookupTables.Abs0(p1 - q1)) > t) + if (((4 * Vp8LookupTables.Abs0[p0 - q0]) + Vp8LookupTables.Abs0[p1 - q1]) > t) { return false; } - return Vp8LookupTables.Abs0(p3 - p2) <= it && Vp8LookupTables.Abs0(p2 - p1) <= it && - Vp8LookupTables.Abs0(p1 - p0) <= it && Vp8LookupTables.Abs0(q3 - q2) <= it && - Vp8LookupTables.Abs0(q2 - q1) <= it && Vp8LookupTables.Abs0(q1 - q0) <= it; + return Vp8LookupTables.Abs0[p3 - p2] <= it && Vp8LookupTables.Abs0[p2 - p1] <= it && + Vp8LookupTables.Abs0[p1 - p0] <= it && Vp8LookupTables.Abs0[q3 - q2] <= it && + Vp8LookupTables.Abs0[q2 - q1] <= it && Vp8LookupTables.Abs0[q1 - q0] <= it; } private static bool Hev(byte[] p, int offset, int step, int thresh) { - int p1 = p[offset -(2 * step)]; + int p1 = p[offset - (2 * step)]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (Vp8LookupTables.Abs0(p1 - p0) > thresh) || (Vp8LookupTables.Abs0(q1 - q0) > thresh); + return (Vp8LookupTables.Abs0[p1 - p0] > thresh) || (Vp8LookupTables.Abs0[q1 - q0] > thresh); } private static int MultHi(int v, int coeff) diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 85c762ea0..2d3568a2b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return bit ? 1 : 0; } - // simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) + // Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) public int GetSigned(int v) { if (this.bits < 0) @@ -227,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.WebP n >>= 8; } - return logValue + WebPConstants.LogTable8bit[n]; + return logValue + Vp8LookupTables.LogTable8bit[n]; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs b/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs index ec20f2cad..4f0a9127e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs @@ -1,64 +1,495 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; + namespace SixLabors.ImageSharp.Formats.WebP { internal static class Vp8LookupTables { - private static readonly byte[] abs0; + public static readonly Dictionary Abs0; + + public static readonly Dictionary Clip1; + + public static readonly Dictionary Sclip1; + + public static readonly Dictionary Sclip2; + + public static readonly byte[,][] ModesProba = new byte[10, 10][]; + + // 31 ^ clz(i) + public static readonly byte[] LogTable8bit = + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; - private static readonly byte[] clip1; + // Paragraph 14.1 + public static readonly int[] DcTable = + { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 + }; + + // Paragraph 14.1 + public static readonly int[] AcTable = + { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 + }; - private static readonly sbyte[] sclip1; + // Paragraph 13 + public static readonly byte[,,,] CoeffsUpdateProba = + { + { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } + }; - private static readonly sbyte[] sclip2; + // Paragraph 13.5: Default Token Probability Table. + public static readonly byte[,,,] DefaultCoeffsProba = + { + { + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } + }; static Vp8LookupTables() { // TODO: maybe use hashset here - abs0 = new byte[511]; + Abs0 = new Dictionary(); for (int i = -255; i <= 255; ++i) { - abs0[255 + i] = (byte)((i < 0) ? -i : i); + Abs0[i] = (byte)((i < 0) ? -i : i); } - clip1 = new byte[766]; + Clip1 = new Dictionary(); for (int i = -255; i <= 255 + 255; ++i) { - clip1[255 + i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); + Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); } - sclip1 = new sbyte[2041]; + Sclip1 = new Dictionary(); for (int i = -1020; i <= 1020; ++i) { - sclip1[1020 + i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); + Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); } - sclip2 = new sbyte[225]; + Sclip2 = new Dictionary(); for (int i = -112; i <= 112; ++i) { - sclip2[112 + i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); + Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); } - } - public static byte Abs0(int v) - { - return abs0[v + 255]; - } - - public static byte Clip1(int v) - { - return clip1[v + 255]; - } - - public static sbyte Sclip1(int v) - { - return sclip1[v + 1020]; + InitializeModesProbabilities(); } - public static sbyte Sclip2(int v) + private static void InitializeModesProbabilities() { - return sclip2[v + 112]; + // Paragraph 11.5 + ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; + ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; + ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; + ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; + ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; + ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; + ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; + ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; + ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; + ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; + ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; + ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; + ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; + ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; + ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; + ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; + ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; + ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; + ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; + ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; + ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; + ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; + ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; + ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; + ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; + ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; + ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; + ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; + ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; + ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; + ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; + ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; + ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; + ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; + ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; + ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; + ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; + ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; + ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; + ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; + ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; + ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; + ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; + ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; + ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; + ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; + ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; + ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; + ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; + ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; + ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; + ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; + ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; + ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; + ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; + ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; + ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; + ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; + ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; + ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; + ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; + ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; + ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; + ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; + ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; + ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; + ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; + ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; + ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; + ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; + ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; + ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; + ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; + ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; + ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; + ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; + ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; + ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; + ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; + ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; + ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; + ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; + ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; + ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; + ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; + ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; + ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; + ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; + ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; + ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; + ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; + ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; + ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; + ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; + ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; + ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; + ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; + ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; + ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; + ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 179d6d77a..4908c133b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -151,69 +151,6 @@ namespace SixLabors.ImageSharp.Formats.WebP 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) }; - // 31 ^ clz(i) - public static readonly byte[] LogTable8bit = - { - 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - }; - - // Paragraph 14.1 - public static readonly int[] DcTable = - { - 4, 5, 6, 7, 8, 9, 10, 10, - 11, 12, 13, 14, 15, 16, 17, 17, - 18, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 25, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, - 37, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, - 91, 93, 95, 96, 98, 100, 101, 102, - 104, 106, 108, 110, 112, 114, 116, 118, - 122, 124, 126, 128, 130, 132, 134, 136, - 138, 140, 143, 145, 148, 151, 154, 157 - }; - - // Paragraph 14.1 - public static readonly int[] AcTable = - { - 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 60, - 62, 64, 66, 68, 70, 72, 74, 76, - 78, 80, 82, 84, 86, 88, 90, 92, - 94, 96, 98, 100, 102, 104, 106, 108, - 110, 112, 114, 116, 119, 122, 125, 128, - 131, 134, 137, 140, 143, 146, 149, 152, - 155, 158, 161, 164, 167, 170, 173, 177, - 181, 185, 189, 193, 197, 201, 205, 209, - 213, 217, 221, 225, 229, 234, 239, 245, - 249, 254, 259, 264, 269, 274, 279, 284 - }; - // Residual decoding (Paragraph 13.2 / 13.3) public static readonly byte[] Cat3 = { 173, 148, 140 }; public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; @@ -233,282 +170,5 @@ namespace SixLabors.ImageSharp.Formats.WebP -7, 8, -8, -9 }; - - // Paragraph 13 - public static readonly byte[,,,] CoeffsUpdateProba = - { - { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, - { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } - }, - { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - } - }; - - // Paragraph 13.5: Default Token Probability Table. - public static readonly byte[,,,] DefaultCoeffsProba = - { - { - { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, - { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, - { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } - }, - { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, - { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, - { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, - }, - { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, - { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, - { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, - }, - { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, - { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, - { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } - }, - { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, - { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, - { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } - }, - { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, - { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, - { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } - }, - { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, - { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, - { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } - }, - { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, - { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, - { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } - }, - { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, - { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, - { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } - }, - { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, - { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, - { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } - }, - { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, - { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, - { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } - }, - { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, - { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, - { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } - }, - { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, - { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, - { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } - }, - { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } - } - }, - { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, - { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, - { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } - }, - { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, - { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, - { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } - }, - { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, - { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, - { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } - }, - { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, - { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, - { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, - { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } - }, - { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, - { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, - { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } - }, - { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, - { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, - { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } - }, - { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, - { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, - { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } - }, - { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, - { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, - { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } - }, - { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, - { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, - { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } - }, - { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, - { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, - { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } - }, - { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - } - } - }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 2826c624b..6cefbc119 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -18,28 +18,15 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly MemoryAllocator memoryAllocator; - private readonly byte[,][] bModesProba = new byte[10, 10][]; - public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.bitReader = bitReader; - this.InitializeModesProbabilities(); } public void Decode(Buffer2D pixels, int width, int height, WebPImageInfo info) where TPixel : struct, IPixel { - // we need buffers for Y U and V in size of the image - // TODO: increase size to enable using all prediction blocks? (see https://tools.ietf.org/html/rfc6386#page-9 ) - Buffer2D yuvBufferCurrentFrame = this.memoryAllocator.Allocate2D(width, height); - - // TODO: var predictionBuffer - macro-block-sized with approximation of the portion of the image being reconstructed. - // those prediction values are the base, the values from DCT processing are added to that - - // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V - Vp8Profile vp8Profile = this.DecodeProfile(info.Vp8Profile); - // Paragraph 9.2: color space and clamp type follow. sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1); @@ -176,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int yMode = left[y]; for (int x = 0; x < 4; ++x) { - byte[] prob = this.bModesProba[top[x], yMode]; + byte[] prob = Vp8LookupTables.ModesProba[top[x], yMode]; int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; while (i > 0) { @@ -326,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (mbx >= dec.MbWidth - 1) { // On rightmost border. - LossyUtils.Memset(topRight, topYuv.Y[15],0, 4); + LossyUtils.Memset(topRight, topYuv.Y[15], 0, 4); } else { @@ -1242,20 +1229,20 @@ namespace SixLabors.ImageSharp.Formats.WebP } Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; - m.Y1Mat[0] = WebPConstants.DcTable[Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebPConstants.AcTable[Clip(q + 0, 127)]; - m.Y2Mat[0] = WebPConstants.DcTable[Clip(q + dqy2Dc, 127)] * 2; + m.Y1Mat[0] = Vp8LookupTables.DcTable[Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = Vp8LookupTables.AcTable[Clip(q + 0, 127)]; + m.Y2Mat[0] = Vp8LookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebPConstants.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; + m.Y2Mat[1] = (Vp8LookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; if (m.Y2Mat[1] < 8) { m.Y2Mat[1] = 8; } - m.UvMat[0] = WebPConstants.DcTable[Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebPConstants.AcTable[Clip(q + dquvAc, 127)]; + m.UvMat[0] = Vp8LookupTables.DcTable[Clip(q + dquvDc, 117)]; + m.UvMat[1] = Vp8LookupTables.AcTable[Clip(q + dquvAc, 127)]; // For dithering strength evaluation. m.UvQuant = q + dquvAc; @@ -1274,10 +1261,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int p = 0; p < WebPConstants.NumProbas; ++p) { - byte prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; + byte prob = Vp8LookupTables.CoeffsUpdateProba[t, b, c, p]; int v = this.bitReader.GetBit(prob) != 0 ? (int)this.bitReader.ReadValue(8) - : WebPConstants.DefaultCoeffsProba[t, b, c, p]; + : Vp8LookupTables.DefaultCoeffsProba[t, b, c, p]; proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; } } @@ -1427,121 +1414,5 @@ namespace SixLabors.ImageSharp.Formats.WebP { return value < 0 ? 0 : value > max ? max : value; } - - // TODO: move to LookupTables - private void InitializeModesProbabilities() - { - // Paragraph 11.5 - this.bModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; - this.bModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; - this.bModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; - this.bModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; - this.bModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; - this.bModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; - this.bModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; - this.bModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; - this.bModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; - this.bModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; - this.bModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; - this.bModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; - this.bModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; - this.bModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; - this.bModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; - this.bModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; - this.bModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; - this.bModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; - this.bModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; - this.bModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; - this.bModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; - this.bModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; - this.bModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; - this.bModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; - this.bModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; - this.bModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; - this.bModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; - this.bModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; - this.bModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; - this.bModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; - this.bModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; - this.bModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; - this.bModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; - this.bModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; - this.bModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; - this.bModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; - this.bModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; - this.bModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; - this.bModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; - this.bModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; - this.bModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; - this.bModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; - this.bModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; - this.bModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; - this.bModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; - this.bModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; - this.bModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; - this.bModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; - this.bModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; - this.bModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; - this.bModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; - this.bModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; - this.bModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; - this.bModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; - this.bModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; - this.bModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; - this.bModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; - this.bModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; - this.bModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; - this.bModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; - this.bModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; - this.bModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; - this.bModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; - this.bModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; - this.bModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; - this.bModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; - this.bModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; - this.bModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; - this.bModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; - this.bModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; - this.bModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; - this.bModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; - this.bModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; - this.bModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; - this.bModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; - this.bModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; - this.bModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; - this.bModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; - this.bModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; - this.bModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; - this.bModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; - this.bModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; - this.bModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; - this.bModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; - this.bModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; - this.bModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; - this.bModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; - this.bModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; - this.bModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; - this.bModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; - this.bModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; - this.bModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; - this.bModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; - this.bModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; - this.bModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; - this.bModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; - this.bModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; - this.bModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; - this.bModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; - this.bModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; - } - - } - - struct YUVPixel - { - public byte Y { get; } - - public byte U { get; } - - public byte V { get; } } } From 3266b150ab618f2385470a9dacf294ba3c619a6e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Feb 2020 17:42:43 +0100 Subject: [PATCH 121/359] Add webp lossy tests --- .../Formats/WebP/WebPDecoderTests.cs | 81 ++++++++++++------- tests/ImageSharp.Tests/TestImages.cs | 18 +++-- tests/Images/Input/WebP/bike_lossy.webp | 3 + 3 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 tests/Images/Input/WebP/bike_lossy.webp diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index d31343ef3..77768639c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -17,12 +17,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public class WebPDecoderTests { + private static WebPDecoder WebpDecoder => new WebPDecoder(); + private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + [Theory] [InlineData(Lossless.Lossless1, 1000, 307, 24)] [InlineData(Lossless.Lossless2, 1000, 307, 24)] - [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] - [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 24)] - [InlineData(Animated.Animated1, 400, 400, 24)] + [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 32)] + [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 32)] public void Identify_DetectsCorrectDimensions( string imagePath, int expectedWidth, @@ -41,19 +43,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] - public void DecodeLossyImage_Tmp( - string imagePath, - int expectedWidth, - int expectedHeight, - int expectedBitsPerPixel) + [WithFile(Lossy.Bike, PixelTypes.Rgba32)] + [WithFile(Lossy.LenaIccp, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy01, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy02, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy03, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy04, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy05, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy06, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy07, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy08, PixelTypes.Rgba32)] + [WithFile(Lossy.Lossy09, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy(TestImageProvider provider) + where TPixel : struct, IPixel { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) + using (Image image = provider.GetImage(WebpDecoder)) { - var image = Image.Load(stream); - Assert.Equal(expectedWidth, image.Width); - Assert.Equal(expectedHeight, image.Height); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Theory] + [WithFile(Lossy.Alpha.LossyAlpha1, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha.LossyAlpha2, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha.LossyAlpha3, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha.LossyAlpha4, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha.LossyAlphaNoCompression, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -63,10 +86,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -81,10 +104,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -97,10 +120,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -110,10 +133,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -123,10 +146,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -147,10 +170,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -166,10 +189,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new WebPDecoder())) + using (Image image = provider.GetImage(WebpDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ReferenceDecoder); } } @@ -181,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) where TPixel : struct, IPixel { - Assert.Throws(() => { using (provider.GetImage(new WebPDecoder())) { } }); + Assert.Throws(() => { using (provider.GetImage(WebpDecoder)) { } }); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index aa057c6b8..28613b5c8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -428,11 +428,18 @@ namespace SixLabors.ImageSharp.Tests public static class Lossy { - public const string SampleWebpOne = "WebP/Lossy/1.webp"; - public const string SampleWebpTwo = "WebP/Lossy/2.webp"; - public const string SampleWebpThree = "WebP/Lossy/3.webp"; - public const string SampleWebpFour = "WebP/Lossy/4.webp"; - public const string SampleWebpFive = "WebP/Lossy/5.webp"; + public const string Bike = "WebP/bike_lossy.webp"; + public const string LenaIccp = "WebP/lossy_iccp.webp"; + public const string VeryShort = "WebP/very_short.webp"; + public const string Lossy01 = "WebP/vp80-01-intra-1400.webp"; + public const string Lossy02 = "WebP/vp80-00-comprehensive-010.webp"; + public const string Lossy03 = "WebP/vp80-01-intra-1417.webp"; + public const string Lossy04 = "WebP/vp80-02-inter-1402.webp"; + public const string Lossy05 = "WebP/vp80-03-segmentation-1401.webp"; + public const string Lossy06 = "WebP/vp80-02-inter-1418.webp"; + public const string Lossy07 = "WebP/vp80-03-segmentation-1403.webp"; + public const string Lossy08 = "WebP/vp80-03-segmentation-1407.webp"; + public const string Lossy09 = "WebP/test.webp"; public static class Alpha { @@ -440,6 +447,7 @@ namespace SixLabors.ImageSharp.Tests public const string LossyAlpha2 = "WebP/lossy_alpha2.webp"; public const string LossyAlpha3 = "WebP/lossy_alpha3.webp"; public const string LossyAlpha4 = "WebP/lossy_alpha4.webp"; + public const string LossyAlphaNoCompression = "WebP/alpha_no_compression.webp"; } } diff --git a/tests/Images/Input/WebP/bike_lossy.webp b/tests/Images/Input/WebP/bike_lossy.webp new file mode 100644 index 000000000..73eabf363 --- /dev/null +++ b/tests/Images/Input/WebP/bike_lossy.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21612476f2d7668f773ce286af1a2a4c33da8718352c5a5c1dd839a4643de823 +size 9396 From 07d50976eeffdb05a8bae5883ab59bbfbf0ca11b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 27 Feb 2020 20:49:35 +0100 Subject: [PATCH 122/359] Fix webp lossy decoding bug --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 8 -------- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 14 ++++++++------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index a3f5f5102..c72a8df4d 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -682,14 +682,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); } - public static void Memset(Span dst, uint value, int startIdx, int count) - { - for (int i = 0; i < count; i++) - { - dst[startIdx + i] = value; - } - } - // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( byte[] p, diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 6f0f07374..236765a96 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8MacroBlockData[] MacroBlockData { get; } ///

- /// Gets contextual contextual macroblock info (mbw + 1). + /// Gets the contextual macroblock info. /// public Vp8MacroBlock[] MacroBlockInfo { get; } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 6cefbc119..03b8e11ad 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -216,7 +216,6 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReconstructRow(Vp8Decoder dec) { int mby = dec.MbY; - int yOff = (WebPConstants.Bps * 1) + 8; int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; int vOff = uOff + 16; @@ -307,23 +306,26 @@ namespace SixLabors.ImageSharp.Formats.WebP // Predict and add residuals. if (block.IsI4x4) { - Span topRight = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); + Span topRight = yuv.AsSpan(yOff - WebPConstants.Bps + 16); if (mby > 0) { if (mbx >= dec.MbWidth - 1) { // On rightmost border. - LossyUtils.Memset(topRight, topYuv.Y[15], 0, 4); + topRight[0] = topYuv.Y[15]; + topRight[1] = topYuv.Y[15]; + topRight[2] = topYuv.Y[15]; + topRight[3] = topYuv.Y[15]; } else { - Span topYuvSamples = MemoryMarshal.Cast(dec.YuvTopSamples[mbx + 1].Y.AsSpan()); - topYuvSamples.Slice(0, 4).CopyTo(topRight); + dec.YuvTopSamples[mbx + 1].Y.AsSpan(0, 4).CopyTo(topRight); } } // Replicate the top-right pixels below. - topRight[WebPConstants.Bps] = topRight[2 * WebPConstants.Bps] = topRight[3 * WebPConstants.Bps] = topRight[0]; + Span topRightUint = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); + topRightUint[WebPConstants.Bps] = topRightUint[2 * WebPConstants.Bps] = topRightUint[3 * WebPConstants.Bps] = topRightUint[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) From 4342f3eba7ba3c116b01eface4a1757fbe30a9f8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 28 Feb 2020 17:42:06 +0100 Subject: [PATCH 123/359] Additional webp lossy tests --- .../Formats/WebP/WebPDecoderTests.cs | 63 +++++++++++++------ tests/ImageSharp.Tests/TestImages.cs | 46 ++++++++------ tests/Images/Input/WebP/bike_lossy.webp | 4 +- .../Input/WebP/bike_lossy_complex_filter.webp | 3 + 4 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 tests/Images/Input/WebP/bike_lossy_complex_filter.webp diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 77768639c..8538071a0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -23,8 +23,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [InlineData(Lossless.Lossless1, 1000, 307, 24)] [InlineData(Lossless.Lossless2, 1000, 307, 24)] - [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 32)] - [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 32)] + [InlineData(Lossy.Alpha1, 1000, 307, 32)] + [InlineData(Lossy.Alpha2, 1000, 307, 32)] public void Identify_DetectsCorrectDimensions( string imagePath, int expectedWidth, @@ -44,17 +44,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [WithFile(Lossy.Bike, PixelTypes.Rgba32)] - [WithFile(Lossy.LenaIccp, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy01, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy02, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy03, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy04, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy05, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy06, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy07, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy08, PixelTypes.Rgba32)] - [WithFile(Lossy.Lossy09, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy(TestImageProvider provider) + [WithFile(Lossy.NoFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter07, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter08, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter09, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(WebpDecoder)) @@ -65,11 +64,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [WithFile(Lossy.Alpha.LossyAlpha1, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha.LossyAlpha2, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha.LossyAlpha3, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha.LossyAlpha4, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha.LossyAlphaNoCompression, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Theory] + [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] + [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Theory] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha4, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 28613b5c8..c15a5ab81 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -428,27 +428,35 @@ namespace SixLabors.ImageSharp.Tests public static class Lossy { + // Lossy images without macroblock filtering. public const string Bike = "WebP/bike_lossy.webp"; - public const string LenaIccp = "WebP/lossy_iccp.webp"; + public const string NoFilter01 = "WebP/vp80-01-intra-1400.webp"; + public const string NoFilter02 = "WebP/vp80-00-comprehensive-010.webp"; + public const string NoFilter03 = "WebP/vp80-00-comprehensive-005.webp"; + public const string NoFilter04 = "WebP/vp80-01-intra-1417.webp"; + public const string NoFilter05 = "WebP/vp80-02-inter-1402.webp"; + public const string NoFilter06 = "WebP/vp80-03-segmentation-1401.webp"; + public const string NoFilter07 = "WebP/vp80-03-segmentation-1403.webp"; + public const string NoFilter08 = "WebP/vp80-03-segmentation-1407.webp"; + public const string NoFilter09 = "WebP/test.webp"; + + // Lossy images with a simple filter. + public const string SimpleFilter01 = "WebP/segment01.webp"; + public const string SimpleFilter02 = "WebP/segment02.webp"; + + // Losyy images with a complex filter. + public const string IccpComplexFilter = "WebP/lossy_iccp.webp"; public const string VeryShort = "WebP/very_short.webp"; - public const string Lossy01 = "WebP/vp80-01-intra-1400.webp"; - public const string Lossy02 = "WebP/vp80-00-comprehensive-010.webp"; - public const string Lossy03 = "WebP/vp80-01-intra-1417.webp"; - public const string Lossy04 = "WebP/vp80-02-inter-1402.webp"; - public const string Lossy05 = "WebP/vp80-03-segmentation-1401.webp"; - public const string Lossy06 = "WebP/vp80-02-inter-1418.webp"; - public const string Lossy07 = "WebP/vp80-03-segmentation-1403.webp"; - public const string Lossy08 = "WebP/vp80-03-segmentation-1407.webp"; - public const string Lossy09 = "WebP/test.webp"; - - public static class Alpha - { - public const string LossyAlpha1 = "WebP/lossy_alpha1.webp"; - public const string LossyAlpha2 = "WebP/lossy_alpha2.webp"; - public const string LossyAlpha3 = "WebP/lossy_alpha3.webp"; - public const string LossyAlpha4 = "WebP/lossy_alpha4.webp"; - public const string LossyAlphaNoCompression = "WebP/alpha_no_compression.webp"; - } + public const string BikeComplexFilter = "WebP/bike_lossy_complex_filter.webp"; + public const string ComplexFilter01 = "WebP/vp80-02-inter-1418.webp"; + public const string ComplexFilter02 = "WebP/vp80-02-inter-1418.webp"; + + // Lossy images with an alpha channel. + public const string Alpha1 = "WebP/lossy_alpha1.webp"; + public const string Alpha2 = "WebP/lossy_alpha2.webp"; + public const string Alpha3 = "WebP/lossy_alpha3.webp"; + public const string Alpha4 = "WebP/lossy_alpha4.webp"; + public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; } public static readonly string[] All = diff --git a/tests/Images/Input/WebP/bike_lossy.webp b/tests/Images/Input/WebP/bike_lossy.webp index 73eabf363..a9e2fc6a8 100644 --- a/tests/Images/Input/WebP/bike_lossy.webp +++ b/tests/Images/Input/WebP/bike_lossy.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21612476f2d7668f773ce286af1a2a4c33da8718352c5a5c1dd839a4643de823 -size 9396 +oid sha256:9f93883b8ba4ebc9c048c598b9294736baddfa756c4884e85f0d3b8e7f9d996c +size 39244 diff --git a/tests/Images/Input/WebP/bike_lossy_complex_filter.webp b/tests/Images/Input/WebP/bike_lossy_complex_filter.webp new file mode 100644 index 000000000..73eabf363 --- /dev/null +++ b/tests/Images/Input/WebP/bike_lossy_complex_filter.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21612476f2d7668f773ce286af1a2a4c33da8718352c5a5c1dd839a4643de823 +size 9396 From 1dc01205e9d41d0893ffceff9eb1630f5416d8ac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 28 Feb 2020 20:23:55 +0100 Subject: [PATCH 124/359] Enable macroblock filtering --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 4 ++ .../Formats/WebP/WebPLossyDecoder.cs | 58 ++++++++----------- .../Formats/WebP/WebPDecoderTests.cs | 1 + tests/Images/Input/WebP/lossless_vec_list.txt | 42 -------------- 4 files changed, 30 insertions(+), 75 deletions(-) delete mode 100644 tests/Images/Input/WebP/lossless_vec_list.txt diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 236765a96..e4705f7e9 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -170,6 +170,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public byte[] CacheV { get; } + public int CacheYOffset { get; set; } + + public int CacheUvOffset { get; set; } + public int CacheYStride { get; } public int CacheUvStride { get; } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 03b8e11ad..740b97eee 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -201,15 +201,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ProcessRow(Vp8Decoder dec, Vp8Io io) { - bool filterRow = (dec.Filter != LoopFilter.None) && - (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); - this.ReconstructRow(dec); - if (filterRow) - { - this.FilterRow(dec); - } - this.FinishRow(dec, io); } @@ -455,12 +447,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Transfer reconstructed samples from yuv_buffer cache to final destination. - int cacheId = 0; // TODO: what should be cacheId, always 0? - int yOffset = cacheId * 16 * dec.CacheYStride; - int uvOffset = cacheId * 8 * dec.CacheUvStride; - Span yOut = dec.CacheY.AsSpan((mbx * 16) + yOffset); - Span uOut = dec.CacheU.AsSpan((mbx * 8) + uvOffset); - Span vOut = dec.CacheV.AsSpan((mbx * 8) + uvOffset); + Span yOut = dec.CacheY.AsSpan(dec.CacheYOffset + (mbx * 16)); + Span uOut = dec.CacheU.AsSpan(dec.CacheUvOffset + (mbx * 8)); + Span vOut = dec.CacheV.AsSpan(dec.CacheUvOffset + (mbx * 8)); for (int j = 0; j < 16; ++j) { yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.CacheYStride)); @@ -479,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int mby = dec.MbY; for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) { - //this.DoFilter(dec, mbx, mby); + this.DoFilter(dec, mbx, mby); } } @@ -497,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (dec.Filter is LoopFilter.Simple) { - int offset = mbx * 16; + int offset = dec.CacheYOffset + (mbx * 16); if (mbx > 0) { LossyUtils.SimpleHFilter16(dec.CacheY, offset, yBps, limit + 4); @@ -521,8 +510,8 @@ namespace SixLabors.ImageSharp.Formats.WebP else if (dec.Filter is LoopFilter.Complex) { int uvBps = dec.CacheUvStride; - int yOffset = mbx * 16; - int uvOffset = mbx * 8; + int yOffset = dec.CacheYOffset + (mbx * 16); + int uvOffset = dec.CacheUvOffset + (mbx * 8); int hevThresh = filterInfo.HighEdgeVarianceThreshold; if (mbx > 0) { @@ -552,22 +541,22 @@ namespace SixLabors.ImageSharp.Formats.WebP private void FinishRow(Vp8Decoder dec, Vp8Io io) { - int cacheId = 0; - int yBps = dec.CacheYStride; int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; int ySize = extraYRows * dec.CacheYStride; int uvSize = (extraYRows / 2) * dec.CacheUvStride; - int yOffset = cacheId * 16 * dec.CacheYStride; - int uvOffset = cacheId * 8 * dec.CacheUvStride; Span yDst = dec.CacheY.AsSpan(); Span uDst = dec.CacheU.AsSpan(); Span vDst = dec.CacheV.AsSpan(); int mby = dec.MbY; bool isFirstRow = mby is 0; bool isLastRow = mby >= dec.BottomRightMbY - 1; + bool filterRow = (dec.Filter != LoopFilter.None) && + (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); - // TODO: Filter row - //FilterRow(dec); + if (filterRow) + { + this.FilterRow(dec); + } int yStart = mby * 16; int yEnd = (mby + 1) * 16; @@ -580,9 +569,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - io.Y = dec.CacheY.AsSpan(yOffset); - io.U = dec.CacheU.AsSpan(uvOffset); - io.V = dec.CacheV.AsSpan(uvOffset); + io.Y = dec.CacheY.AsSpan(dec.CacheYOffset); + io.U = dec.CacheU.AsSpan(dec.CacheUvOffset); + io.V = dec.CacheV.AsSpan(dec.CacheUvOffset); } if (!isLastRow) @@ -605,10 +594,9 @@ namespace SixLabors.ImageSharp.Formats.WebP // Rotate top samples if needed. if (!isLastRow) { - // TODO: double check this. Cache needs extra rows for filtering! - //yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY); - //uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU); - //vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV); + yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.AsSpan()); + uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.AsSpan()); + vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.AsSpan()); } } @@ -1125,7 +1113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return vp8SegmentHeader; } - private Vp8FilterHeader ParseFilterHeader(Vp8Decoder dec) + private void ParseFilterHeader(Vp8Decoder dec) { Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; @@ -1160,7 +1148,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - return vp8FilterHeader; + int extraRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; + int extraY = extraRows * dec.CacheYStride; + int extraUv = (extraRows / 2) * dec.CacheUvStride; + dec.CacheYOffset = extraY; + dec.CacheUvOffset = extraUv; } private void ParsePartitions(Vp8Decoder dec) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 8538071a0..24324ffe3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -81,6 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/Images/Input/WebP/lossless_vec_list.txt b/tests/Images/Input/WebP/lossless_vec_list.txt deleted file mode 100644 index d72bb0c71..000000000 --- a/tests/Images/Input/WebP/lossless_vec_list.txt +++ /dev/null @@ -1,42 +0,0 @@ -List of features used in each test vector. -All the 'lossless_vec_1_*.webp' WebP files should decode to an image comparable to equivalently 'grid.png'. -This synthetic picture is made of 16x16 grid-alternating pixels with RGBA values equal to -blue B=(0,0,255,255) and half-transparent red R=(255,0,0,128), according to -the pattern: -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB -BRBRBRBRBRBRBRBR -RBRBRBRBRBRBRBRB - -The 'lossless_vec_2_*.webp' WebP files should decode to an image comparable -to equivalently 'peak.png'. Their alpha channel is fully opaque. - -Feature list: -lossless_vec_?_0.webp: none -lossless_vec_?_1.webp: PALETTE -lossless_vec_?_2.webp: PREDICTION -lossless_vec_?_3.webp: PREDICTION PALETTE -lossless_vec_?_4.webp: SUBTRACT-GREEN -lossless_vec_?_5.webp: SUBTRACT-GREEN PALETTE -lossless_vec_?_6.webp: PREDICTION SUBTRACT-GREEN -lossless_vec_?_7.webp: PREDICTION SUBTRACT-GREEN PALETTE -lossless_vec_?_8.webp: CROSS-COLOR-TRANSFORM -lossless_vec_?_9.webp: CROSS-COLOR-TRANSFORM PALETTE -lossless_vec_?_10.webp: PREDICTION CROSS-COLOR-TRANSFORM -lossless_vec_?_11.webp: PREDICTION CROSS-COLOR-TRANSFORM PALETTE -lossless_vec_?_12.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN -lossless_vec_?_13.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE -lossless_vec_?_14_.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN -lossless_vec_?_15.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE From 9567e17fa57bd464727bd65b3eeecfe1e74d513a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 29 Feb 2020 21:01:08 +0100 Subject: [PATCH 125/359] Fix some lossy decoding bugs --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 8 ++++---- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index c72a8df4d..68f61a639 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -596,7 +596,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int k = 3; k > 0; --k) { - offset += stride; + offset += 4; SimpleHFilter16(p, offset, stride, thresh); } } @@ -781,7 +781,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int q0 = p[offset]; int q1 = p[offset + step]; int q2 = p[offset + (2 * step)]; - int a = Vp8LookupTables.Clip1[(3 * (q0 - p0)) + Vp8LookupTables.Clip1[p1 - q1]]; + int a = Vp8LookupTables.Sclip1[(3 * (q0 - p0)) + Vp8LookupTables.Sclip1[p1 - q1]]; // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 @@ -795,13 +795,13 @@ namespace SixLabors.ImageSharp.Formats.WebP p[offset + (2 * step)] = Vp8LookupTables.Clip1[q2 - a3]; } - private static bool NeedsFilter(byte[] p, int offset, int step, int thresh) + private static bool NeedsFilter(byte[] p, int offset, int step, int t) { int p1 = p[offset + (-2 * step)]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (Vp8LookupTables.Abs0[p1 - p0] > thresh) || (Vp8LookupTables.Abs0[q1 - q0] > thresh); + return ((4 * Vp8LookupTables.Abs0[p0 - q0]) + Vp8LookupTables.Abs0[p1 - q1]) <= t; } private static bool NeedsFilter2(byte[] p, int offset, int step, int t, int it) diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index e4705f7e9..cb4ef199d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { int baseLevel; - // First, compute the initial level + // First, compute the initial level. if (this.SegmentHeader.UseSegment) { baseLevel = this.SegmentHeader.FilterStrength[s]; @@ -296,7 +296,7 @@ namespace SixLabors.ImageSharp.Formats.WebP info.Limit = 0; // no filtering. } - info.InnerLevel = (byte)i4x4; + info.UseInnerFiltering = (byte)i4x4; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 740b97eee..0ddc75c6f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.6: Dequantization Indices. this.ParseDequantizationIndices(decoder); - // Ignore the value of update_proba + // Ignore the value of update probabilities. this.bitReader.ReadBool(); // Paragraph 13.4: Parse probabilities. @@ -475,7 +475,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void DoFilter(Vp8Decoder dec, int mbx, int mby) { int yBps = dec.CacheYStride; - Vp8FilterInfo filterInfo = dec.FilterInfo[dec.MbX]; + Vp8FilterInfo filterInfo = dec.FilterInfo[mbx]; int iLevel = filterInfo.InnerLevel; int limit = filterInfo.Limit; From f5d91a6ce126a48b35f9783d7b72902d132df869 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 29 Feb 2020 22:30:28 +0100 Subject: [PATCH 126/359] Add more webp lossy test cases --- .../Formats/WebP/WebPDecoderTests.cs | 23 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 19 ++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 24324ffe3..26f12fc19 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -66,6 +66,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] [WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter05, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) where TPixel : struct, IPixel { @@ -76,12 +79,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } } + [Theory] + [WithFile(Lossy.Small01, PixelTypes.Rgba32)] + [WithFile(Lossy.Small02, PixelTypes.Rgba32)] + [WithFile(Lossy.Small03, PixelTypes.Rgba32)] + [WithFile(Lossy.Small04, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_VerySmall(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + [Theory] [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c15a5ab81..5b5a3f6f3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -443,13 +443,29 @@ namespace SixLabors.ImageSharp.Tests // Lossy images with a simple filter. public const string SimpleFilter01 = "WebP/segment01.webp"; public const string SimpleFilter02 = "WebP/segment02.webp"; + public const string SimpleFilter03 = "WebP/vp80-00-comprehensive-003.webp"; + public const string SimpleFilter04 = "WebP/vp80-00-comprehensive-007.webp"; + public const string SimpleFilter05 = "WebP/test-nostrong.webp"; - // Losyy images with a complex filter. + // Lossy images with a complex filter. public const string IccpComplexFilter = "WebP/lossy_iccp.webp"; public const string VeryShort = "WebP/very_short.webp"; public const string BikeComplexFilter = "WebP/bike_lossy_complex_filter.webp"; public const string ComplexFilter01 = "WebP/vp80-02-inter-1418.webp"; public const string ComplexFilter02 = "WebP/vp80-02-inter-1418.webp"; + public const string ComplexFilter03 = "WebP/vp80-00-comprehensive-002.webp"; + public const string ComplexFilter04 = "WebP/vp80-00-comprehensive-006.webp"; + public const string ComplexFilter05 = "WebP/vp80-00-comprehensive-009.webp"; + public const string ComplexFilter06 = "WebP/vp80-00-comprehensive-012.webp"; + public const string ComplexFilter07 = "WebP/vp80-00-comprehensive-015.webp"; + public const string ComplexFilter08 = "WebP/vp80-00-comprehensive-016.webp"; + public const string ComplexFilter09 = "WebP/vp80-00-comprehensive-017.webp"; + + // Very small images (all with complex filter). + public const string Small01 = "WebP/small_13x1.webp"; + public const string Small02 = "WebP/small_1x1.webp"; + public const string Small03 = "WebP/small_1x13.webp"; + public const string Small04 = "WebP/small_31x13.webp"; // Lossy images with an alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; @@ -457,6 +473,7 @@ namespace SixLabors.ImageSharp.Tests public const string Alpha3 = "WebP/lossy_alpha3.webp"; public const string Alpha4 = "WebP/lossy_alpha4.webp"; public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; + } public static readonly string[] All = From 0af68d8a6bf33a6566127563fd4249a600e39b52 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 1 Mar 2020 14:02:00 +0100 Subject: [PATCH 127/359] Fix decoding very small lossy webp images --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 21 ++++++++++++------- .../Formats/WebP/WebPLossyDecoder.cs | 20 +++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index cb4ef199d..848994d4e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -50,12 +50,15 @@ namespace SixLabors.ImageSharp.Formats.WebP uint height = pictureHeader.Height; // TODO: use memory allocator - this.CacheY = new byte[width * height]; // TODO: this is way too much mem, figure out what the min req is. - this.CacheU = new byte[width * height]; - this.CacheV = new byte[width * height]; - this.TmpYBuffer = new byte[width * height]; // TODO: figure out min buffer length - this.TmpUBuffer = new byte[width * height]; // TODO: figure out min buffer length - this.TmpVBuffer = new byte[width * height]; // TODO: figure out min buffer length + int extraRows = WebPConstants.FilterExtraRows[2]; // TODO: assuming worst case: complex filter + int extraY = extraRows * this.CacheYStride; + int extraUv = (extraRows / 2) * this.CacheUvStride; + this.CacheY = new byte[width * height + extraY + 256]; // TODO: this is way too much mem, figure out what the min req is. + this.CacheU = new byte[width * height + extraUv + 256]; + this.CacheV = new byte[width * height + extraUv + 256]; + this.TmpYBuffer = new byte[width * height + extraY]; // TODO: figure out min buffer length + this.TmpUBuffer = new byte[width * height + extraUv]; // TODO: figure out min buffer length + this.TmpVBuffer = new byte[width * height + extraUv]; // TODO: figure out min buffer length this.Bgr = new byte[width * height * 4]; for (int i = 0; i < this.YuvBuffer.Length; i++) @@ -66,6 +69,10 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < this.CacheY.Length; i++) { this.CacheY[i] = 205; + } + + for (int i = 0; i < this.CacheU.Length; i++) + { this.CacheU[i] = 205; this.CacheV[i] = 205; } @@ -82,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8SegmentHeader SegmentHeader { get; } // number of partitions minus one. - public uint NumPartsMinusOne { get; } + public int NumPartsMinusOne { get; set; } // per-partition boolean decoders. public Vp8BitReader[] Vp8BitReaders { get; } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 0ddc75c6f..82c9b9bd0 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -452,13 +452,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Span vOut = dec.CacheV.AsSpan(dec.CacheUvOffset + (mbx * 8)); for (int j = 0; j < 16; ++j) { - yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.CacheYStride)); + yDst.Slice(j * WebPConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut.Slice(j * dec.CacheYStride)); } for (int j = 0; j < 8; ++j) { - uDst.Slice(j * WebPConstants.Bps, 8).CopyTo(uOut.Slice(j * dec.CacheUvStride)); - vDst.Slice(j * WebPConstants.Bps, 8).CopyTo(vOut.Slice(j * dec.CacheUvStride)); + uDst.Slice(j * WebPConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(j * dec.CacheUvStride)); + vDst.Slice(j * WebPConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut.Slice(j * dec.CacheUvStride)); } } } @@ -579,6 +579,11 @@ namespace SixLabors.ImageSharp.Formats.WebP yEnd -= extraYRows; } + if (yEnd > io.CropBottom) + { + yEnd = io.CropBottom; // make sure we don't overflow on last row. + } + if (yStart < yEnd) { io.Y = io.Y.Slice(io.CropLeft); @@ -791,7 +796,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0; - if (blockData.IsI4x4) + if (!blockData.IsI4x4) { left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0; } @@ -917,8 +922,7 @@ namespace SixLabors.ImageSharp.Formats.WebP block.NonZeroUv = nonZeroUv; // We look at the mode-code of each block and check if some blocks have less - // than three non-zero coeffs (code < 2). This is to avoid dithering flat and - // empty blocks. + // than three non-zero coeffs (code < 2). This is to avoid dithering flat and empty blocks. block.Dither = (byte)((nonZeroUv & 0xaaaa) > 0 ? 0 : q.Dither); return (nonZeroY | nonZeroUv) is 0; @@ -1161,8 +1165,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int startIdx = (int)this.bitReader.PartitionLength; Span sz = this.bitReader.Data.AsSpan(startIdx); int sizeLeft = (int)size; - int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; - int lastPart = numPartsMinusOne; + dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; + int lastPart = dec.NumPartsMinusOne; int partStart = startIdx + (lastPart * 3); sizeLeft -= lastPart * 3; From c32b3f716da299533e14a9d879e44b943b3119f7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 1 Mar 2020 17:09:53 +0100 Subject: [PATCH 128/359] Additional test cases for webp lossy images --- .../Formats/WebP/WebPDecoderTests.cs | 81 +++++++++++++++---- tests/ImageSharp.Tests/TestImages.cs | 31 ++++++- 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 26f12fc19..052188904 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -49,10 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] [WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] [WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter07, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter08, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter09, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter03, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) where TPixel : struct, IPixel { @@ -79,6 +78,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } } + [Theory] + [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] + [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter08, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter09, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + [Theory] [WithFile(Lossy.Small01, PixelTypes.Rgba32)] [WithFile(Lossy.Small02, PixelTypes.Rgba32)] @@ -95,17 +117,46 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] - [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) + [WithFile(Lossy.SegmentationNoFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter05, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithPartitions(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Theory] + [WithFile(Lossy.Partitions01, PixelTypes.Rgba32)] + [WithFile(Lossy.Partitions02, PixelTypes.Rgba32)] + [WithFile(Lossy.Partitions03, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSegmentation(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Theory] + [WithFile(Lossy.Sharpness01, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness02, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness03, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness04, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness05, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness06, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(WebpDecoder)) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5b5a3f6f3..87fdf520a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -435,10 +435,7 @@ namespace SixLabors.ImageSharp.Tests public const string NoFilter03 = "WebP/vp80-00-comprehensive-005.webp"; public const string NoFilter04 = "WebP/vp80-01-intra-1417.webp"; public const string NoFilter05 = "WebP/vp80-02-inter-1402.webp"; - public const string NoFilter06 = "WebP/vp80-03-segmentation-1401.webp"; - public const string NoFilter07 = "WebP/vp80-03-segmentation-1403.webp"; - public const string NoFilter08 = "WebP/vp80-03-segmentation-1407.webp"; - public const string NoFilter09 = "WebP/test.webp"; + public const string NoFilter06 = "WebP/test.webp"; // Lossy images with a simple filter. public const string SimpleFilter01 = "WebP/segment01.webp"; @@ -461,6 +458,32 @@ namespace SixLabors.ImageSharp.Tests public const string ComplexFilter08 = "WebP/vp80-00-comprehensive-016.webp"; public const string ComplexFilter09 = "WebP/vp80-00-comprehensive-017.webp"; + // Lossy with partitions. + public const string Partitions01 = "WebP/vp80-04-partitions-1404.webp"; + public const string Partitions02 = "WebP/vp80-04-partitions-1405.webp"; + public const string Partitions03 = "WebP/vp80-04-partitions-1406.webp"; + + // Lossy with segmentation. + public const string SegmentationNoFilter01 = "WebP/vp80-03-segmentation-1401.webp"; + public const string SegmentationNoFilter02 = "WebP/vp80-03-segmentation-1403.webp"; + public const string SegmentationNoFilter03 = "WebP/vp80-03-segmentation-1407.webp"; + public const string SegmentationNoFilter04 = "WebP/vp80-03-segmentation-1408.webp"; + public const string SegmentationNoFilter05 = "WebP/vp80-03-segmentation-1409.webp"; + public const string SegmentationNoFilter06 = "WebP/vp80-03-segmentation-1410.webp"; + public const string SegmentationComplexFilter01 = "WebP/vp80-03-segmentation-1413.webp"; + public const string SegmentationComplexFilter02 = "WebP/vp80-03-segmentation-1425.webp"; + public const string SegmentationComplexFilter03 = "WebP/vp80-03-segmentation-1426.webp"; + public const string SegmentationComplexFilter04 = "WebP/vp80-03-segmentation-1427.webp"; + public const string SegmentationComplexFilter05 = "WebP/vp80-03-segmentation-1432.webp"; + + // Lossy with sharpness level. + public const string Sharpness01 = "WebP/vp80-05-sharpness-1428.webp"; + public const string Sharpness02 = "WebP/vp80-05-sharpness-1429.webp"; + public const string Sharpness03 = "WebP/vp80-05-sharpness-1430.webp"; + public const string Sharpness04 = "WebP/vp80-05-sharpness-1431.webp"; + public const string Sharpness05 = "WebP/vp80-05-sharpness-1433.webp"; + public const string Sharpness06 = "WebP/vp80-05-sharpness-1434.webp"; + // Very small images (all with complex filter). public const string Small01 = "WebP/small_13x1.webp"; public const string Small02 = "WebP/small_1x1.webp"; From 263ff75d724d089f08f7f2b59de809e0b0576c6e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Mar 2020 11:56:05 +0100 Subject: [PATCH 129/359] Add decode webp benchmark --- .../Codecs/DecodeWebp.cs | 87 +++++++++++++++++++ .../ImageSharp.Benchmarks.csproj | 3 +- .../Formats/WebP/WebPDecoderTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 2 +- 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs new file mode 100644 index 000000000..ff9386286 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using BenchmarkDotNet.Attributes; + +using ImageMagick; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeWebp : BenchmarkBase + { + private byte[] webpLossyBytes; + private byte[] webpLosslessBytes; + + private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); + private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); + + [Params(TestImages.WebP.Lossy.Bike)] + public string TestImageLossy { get; set; } + + [Params(TestImages.WebP.Lossless.BikeThreeTransforms)] + public string TestImageLossless { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.webpLossyBytes is null) + { + this.webpLossyBytes = File.ReadAllBytes(this.TestImageLossyFullPath); + } + + if (this.webpLosslessBytes is null) + { + this.webpLosslessBytes = File.ReadAllBytes(this.TestImageLosslessFullPath); + } + } + + [Benchmark(Baseline = true, Description = "Magick Lossy WebP")] + public int WebpLossyMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + using (var image = new MagickImage(new MemoryStream(this.webpLossyBytes), settings)) + { + return image.Width; + } + } + + [Benchmark(Description = "ImageSharp Lossy Webp")] + public int WebpLossy() + { + using (var memoryStream = new MemoryStream(this.webpLossyBytes)) + { + using (var image = Image.Load(memoryStream)) + { + return image.Height; + } + } + } + + [Benchmark(Baseline = true, Description = "Magick Lossless WebP")] + public int WebpLosslessMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + using (var image = new MagickImage(new MemoryStream(this.webpLosslessBytes), settings)) + { + return image.Width; + } + } + + [Benchmark(Description = "ImageSharp Lossless Webp")] + public int WebpLossless() + { + using (var memoryStream = new MemoryStream(this.webpLosslessBytes)) + { + using (var image = Image.Load(memoryStream)) + { + return image.Height; + } + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 14ad5635c..e355ad024 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,4 +1,4 @@ - + @@ -16,6 +16,7 @@ + diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 052188904..fea51bf73 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -287,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms8, PixelTypes.Rgba32)] + [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 87fdf520a..66c936ad5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -416,7 +416,7 @@ namespace SixLabors.ImageSharp.Tests public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms8 = "Webp/bike_lossless.webp"; // substract_green, predictor, cross_color + public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; // substract_green, predictor, cross_color // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." From 7396db5cfeab6a5ceccda07e5aaac4411ceed575 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Mar 2020 13:59:48 +0100 Subject: [PATCH 130/359] A little cleanup and refactoring --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 15 +- .../Formats/WebP/Filters/WebPFilterBase.cs | 30 +- .../WebP/Filters/WebPFilterGradient.cs | 41 +- .../WebP/Filters/WebPFilterHorizontal.cs | 71 ++- .../Formats/WebP/Filters/WebPFilterNone.cs | 29 +- .../WebP/Filters/WebPFilterVertical.cs | 48 +- src/ImageSharp/Formats/WebP/LossyUtils.cs | 332 +++++----- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 43 +- src/ImageSharp/Formats/WebP/Vp8Io.cs | 22 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 4 +- .../Formats/WebP/Vp8LookupTables.cs | 495 --------------- .../Formats/WebP/Vp8MacroBlockData.cs | 7 +- .../Formats/WebP/WebPDecoderBase.cs | 108 ---- .../Formats/WebP/WebPLookupTables.cs | 580 ++++++++++++++++++ .../Formats/WebP/WebPLosslessDecoder.cs | 108 +++- .../Formats/WebP/WebPLossyDecoder.cs | 92 ++- .../Codecs/DecodeWebp.cs | 4 +- .../Formats/WebP/WebPDecoderTests.cs | 8 +- .../Formats/WebP/WebPMetaDataTests.cs | 19 +- tests/ImageSharp.Tests/TestImages.cs | 7 - 21 files changed, 1090 insertions(+), 975 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/Vp8LookupTables.cs delete mode 100644 src/ImageSharp/Formats/WebP/WebPDecoderBase.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPLookupTables.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index fb5663464..d2c8d583f 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -37,8 +37,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // Taken from vp8l_dec.c AlphaApplyFilter public void AlphaApplyFilter( - int firstRow, int lastRow, - Span output, int outputOffset, + int firstRow, + int lastRow, + Span output, + int outputOffset, int stride) { if (!(this.Filter is WebPFilterNone)) @@ -50,9 +52,12 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.Filter .Unfilter( - prevLine, prevLineOffset, - output, outputOffset, - output, outputOffset, + prevLine, + prevLineOffset, + output, + outputOffset, + output, + outputOffset, stride); prevLineOffset = outputOffset; outputOffset += stride; diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs index 9e1a03125..4ff2ae568 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -39,9 +39,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters int width); public abstract void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset); + Span input, + int inputOffset, + int width, + int height, + int stride, + Span output, + int outputOffset); protected static void SanityCheck( Span input, Span output, int width, int numRows, int height, int stride, int row) @@ -57,10 +61,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters } protected static void PredictLine( - Span src, int srcOffset, - Span pred, int predOffset, - Span dst, int dstOffset, - int length, bool inverse) + Span src, + int srcOffset, + Span pred, + int predOffset, + Span dst, + int dstOffset, + int length, + bool inverse) { if (inverse) { @@ -80,8 +88,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters protected void UnfilterHorizontalOrVerticalCore( byte pred, - Span input, int inputOffset, - Span output, int outputOffset, + Span input, + int inputOffset, + Span output, + int outputOffset, int width) { for (int i = 0; i < width; i++) diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs index ce751b1f5..27bca5770 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs @@ -1,10 +1,12 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; namespace SixLabors.ImageSharp.Formats.WebP.Filters { class WebPFilterGradient : WebPFilterBase { - public override void Unfilter( Span prevLine, int? prevLineOffsetNullable, @@ -34,9 +36,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters } public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) + Span input, + int inputOffset, + int width, + int height, + int stride, + Span output, + int outputOffset) { // calling (input, width, height, stride, 0, height, 0, output int row = 0; @@ -65,20 +71,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters { output[outputOffset] = input[inputOffset]; PredictLine( - input, inputOffset+1, - preds, predsOffset, - output, outputOffset+1, - width-1, + input, + inputOffset + 1, + preds, + predsOffset, + output, + outputOffset + 1, + width - 1, inverse); } while (row < lastRow) { PredictLine( - input, inputOffset, - preds, predsOffset-stride, - output, outputOffset, - 1, inverse); + input, + inputOffset, + preds, + predsOffset - stride, + output, + outputOffset, + 1, + inverse); for (int w = 1; w < width; w++) { @@ -100,4 +113,4 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs index 61c384d7d..4328332a5 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs @@ -1,13 +1,19 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; namespace SixLabors.ImageSharp.Formats.WebP.Filters { class WebPFilterHorizontal : WebPFilterBase { public override void Unfilter( - Span prevLine, int? prevLineOffsetNullable, - Span input, int inputOffset, - Span output, int outputOffset, + Span prevLine, + int? prevLineOffsetNullable, + Span input, + int inputOffset, + Span output, + int outputOffset, int width) { byte pred = prevLineOffsetNullable is int prevLineOffset @@ -16,15 +22,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters this.UnfilterHorizontalOrVerticalCore( pred, - input, inputOffset, - output, outputOffset, + input, + inputOffset, + output, + outputOffset, width); } public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) + Span input, + int inputOffset, + int width, + int height, + int stride, + Span output, + int outputOffset) { int numRows = height; int row = 0; @@ -51,16 +63,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters predsOffset = inputOffset; } - if (row == 0) { // leftmost pixel is the same as Input for topmost scanline output[0] = input[0]; PredictLine( - input, inputOffset + 1, - preds, predsOffset, - output, outputOffset + 1, - width - 1, inverse); + input, + inputOffset + 1, + preds, + predsOffset, + output, + outputOffset + 1, + width - 1, + inverse); row = 1; predsOffset += stride; @@ -68,19 +83,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters outputOffset += stride; } - // filter line by line + // Filter line by line. while (row < lastRow) { PredictLine( - input, inputOffset, - preds, predsOffset - stride, - output, 0, - 1, inverse); + input, + inputOffset, + preds, + predsOffset - stride, + output, + 0, + 1, + inverse); PredictLine( - input, inputOffset, - preds, predsOffset, - output,outputOffset + 1, - width - 1, inverse); + input, + inputOffset, + preds, + predsOffset, + output, + outputOffset + 1, + width - 1, + inverse); row++; predsOffset += stride; @@ -89,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs index fcecadfb0..04dfafe24 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs @@ -1,14 +1,33 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; namespace SixLabors.ImageSharp.Formats.WebP.Filters { // TODO: check if this is a filter or just a placeholder from the C implementation details class WebPFilterNone : WebPFilterBase { - public override void Unfilter(Span prevLine, int? prevLineOffset, Span input, int inputOffset, Span output, int outputOffset, int width) - { } + public override void Unfilter( + Span prevLine, + int? prevLineOffset, + Span input, + int inputOffset, + Span output, + int outputOffset, + int width) + { + } - public override void Filter(Span input, int inputOffset, int width, int height, int stride, Span output, int outputOffset) - { } + public override void Filter( + Span input, + int inputOffset, + int width, + int height, + int stride, + Span output, + int outputOffset) + { + } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs index 59ab12caf..04eb2a587 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; namespace SixLabors.ImageSharp.Formats.WebP.Filters { @@ -20,9 +23,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters } public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) + Span input, + int inputOffset, + int width, + int height, + int stride, + Span output, + int outputOffset) { int row = 0; bool inverse = false; @@ -49,14 +56,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters if (row == 0) { - // very first top-left pixel is copied. + // Very first top-left pixel is copied. output[0] = input[0]; - // rest of top scan-line is left-predicted: + + // Rest of top scan-line is left-predicted: PredictLine( - input, inputOffset + 1, - preds, predsOffset, - output, outputOffset + 1, - width - 1, inverse); + input, + inputOffset + 1, + preds, + predsOffset, + output, + outputOffset + 1, + width - 1, + inverse); row = 1; inputOffset += stride; outputOffset += stride; @@ -66,14 +78,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters predsOffset -= stride; } - // filter line-by-line + // Filter line-by-line. while (row < lastRow) { PredictLine( - input, inputOffset, - preds, predsOffset, - output, outputOffset, - width, inverse); + input, + inputOffset, + preds, + predsOffset, + output, + outputOffset, + width, + inverse); row++; predsOffset += stride; inputOffset += stride; @@ -81,4 +97,4 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 68f61a639..a8f6eb98d 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC16_C(Span dst, byte[] yuv, int offset) + public static void DC16(Span dst, byte[] yuv, int offset) { int dc = 16; for (int j = 0; j < 16; ++j) @@ -28,12 +28,12 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 5, dst); } - public static void TM16_C(Span dst, byte[] yuv, int offset) + public static void TM16(Span dst, byte[] yuv, int offset) { TrueMotion(dst, yuv, offset, 16); } - public static void VE16_C(Span dst, byte[] yuv, int offset) + public static void VE16(Span dst, byte[] yuv, int offset) { // vertical Span src = yuv.AsSpan(offset - WebPConstants.Bps, 16); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE16_C(Span dst, byte[] yuv, int offset) + public static void HE16(Span dst, byte[] yuv, int offset) { // horizontal for (int j = 16; j > 0; --j) @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC16NoTop_C(Span dst, byte[] yuv, int offset) + public static void DC16NoTop(Span dst, byte[] yuv, int offset) { // DC with top samples not available. int dc = 8; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 4, dst); } - public static void DC16NoLeft_C(Span dst, byte[] yuv, int offset) + public static void DC16NoLeft(Span dst, byte[] yuv, int offset) { // DC with left samples not available. int dc = 8; @@ -83,13 +83,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 4, dst); } - public static void DC16NoTopLeft_C(Span dst) + public static void DC16NoTopLeft(Span dst) { // DC with no top and left samples. Put16(0x80, dst); } - public static void DC8uv_C(Span dst, byte[] yuv, int offset) + public static void DC8uv(Span dst, byte[] yuv, int offset) { int dc0 = 8; for (int i = 0; i < 8; ++i) @@ -101,13 +101,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 4), dst); } - public static void TM8uv_C(Span dst, byte[] yuv, int offset) + public static void TM8uv(Span dst, byte[] yuv, int offset) { // TrueMotion TrueMotion(dst, yuv, offset, 8); } - public static void VE8uv_C(Span dst, byte[] yuv, int offset) + public static void VE8uv(Span dst, byte[] yuv, int offset) { // vertical Span src = yuv.AsSpan(offset - WebPConstants.Bps, 8); @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE8uv_C(Span dst, byte[] yuv, int offset) + public static void HE8uv(Span dst, byte[] yuv, int offset) { // horizontal for (int j = 0; j < 8; ++j) @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC8uvNoTop_C(Span dst, byte[] yuv, int offset) + public static void DC8uvNoTop(Span dst, byte[] yuv, int offset) { // DC with no top samples. int dc0 = 4; @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } - public static void DC8uvNoLeft_C(Span dst, byte[] yuv, int offset) + public static void DC8uvNoLeft(Span dst, byte[] yuv, int offset) { // DC with no left samples. int dc0 = 4; @@ -159,13 +159,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } - public static void DC8uvNoTopLeft_C(Span dst) + public static void DC8uvNoTopLeft(Span dst) { // DC with nothing. Put8x8uv(0x80, dst); } - public static void DC4_C(Span dst, byte[] yuv, int offset) + public static void DC4(Span dst, byte[] yuv, int offset) { int dc = 4; for (int i = 0; i < 4; ++i) @@ -180,12 +180,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void TM4_C(Span dst, byte[] yuv, int offset) + public static void TM4(Span dst, byte[] yuv, int offset) { TrueMotion(dst, yuv, offset, 4); } - public static void VE4_C(Span dst, byte[] yuv, int offset) + public static void VE4(Span dst, byte[] yuv, int offset) { // vertical int topOffset = offset - WebPConstants.Bps; @@ -203,231 +203,231 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE4_C(Span dst, byte[] yuv, int offset) + public static void HE4(Span dst, byte[] yuv, int offset) { // horizontal - byte A = yuv[offset - 1 - WebPConstants.Bps]; - byte B = yuv[offset - 1]; - byte C = yuv[offset - 1 + WebPConstants.Bps]; - byte D = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte E = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - uint val = 0x01010101U * Avg3(A, B, C); + byte a = yuv[offset - 1 - WebPConstants.Bps]; + byte b = yuv[offset - 1]; + byte c = yuv[offset - 1 + WebPConstants.Bps]; + byte d = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte e = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + uint val = 0x01010101U * Avg3(a, b, c); BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * Avg3(B, C, D); + val = 0x01010101U * Avg3(b, c, d); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebPConstants.Bps), val); - val = 0x01010101U * Avg3(C, D, E); + val = 0x01010101U * Avg3(c, d, e); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); - val = 0x01010101U * Avg3(D, E, E); + val = 0x01010101U * Avg3(d, e, e); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); } - public static void RD4_C(Span dst, byte[] yuv, int offset) + public static void RD4(Span dst, byte[] yuv, int offset) { // Down-right - byte I = yuv[offset - 1]; - byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - byte X = yuv[offset - 1 - WebPConstants.Bps]; - byte A = yuv[offset - WebPConstants.Bps]; - byte B = yuv[offset + 1 - WebPConstants.Bps]; - byte C = yuv[offset + 2 - WebPConstants.Bps]; - byte D = yuv[offset + 3 - WebPConstants.Bps]; - - Dst(dst, 0, 3, Avg3(J, K, L)); - byte ijk = Avg3(I, J, K); + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte x = yuv[offset - 1 - WebPConstants.Bps]; + byte a = yuv[offset - WebPConstants.Bps]; + byte b = yuv[offset + 1 - WebPConstants.Bps]; + byte c = yuv[offset + 2 - WebPConstants.Bps]; + byte d = yuv[offset + 3 - WebPConstants.Bps]; + + Dst(dst, 0, 3, Avg3(j, k, l)); + byte ijk = Avg3(i, j, k); Dst(dst, 1, 3, ijk); Dst(dst, 0, 2, ijk); - byte xij = Avg3(X, I, J); + byte xij = Avg3(x, i, j); Dst(dst, 2, 3, xij); Dst(dst, 1, 2, xij); Dst(dst, 0, 1, xij); - byte axi = Avg3(A, X, I); + byte axi = Avg3(a, x, i); Dst(dst, 3, 3, axi); Dst(dst, 2, 2, axi); Dst(dst, 1, 1, axi); Dst(dst, 0, 0, axi); - byte bax = Avg3(B, A, X); + byte bax = Avg3(b, a, x); Dst(dst, 3, 2, bax); Dst(dst, 2, 1, bax); Dst(dst, 1, 0, bax); - byte cba = Avg3(C, B, A); + byte cba = Avg3(c, b, a); Dst(dst, 3, 1, cba); Dst(dst, 2, 0, cba); - Dst(dst, 3, 0, Avg3(D, C, B)); + Dst(dst, 3, 0, Avg3(d, c, b)); } - public static void VR4_C(Span dst, byte[] yuv, int offset) + public static void VR4(Span dst, byte[] yuv, int offset) { // Vertical-Right - byte I = yuv[offset - 1]; - byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte X = yuv[offset - 1 - WebPConstants.Bps]; - byte A = yuv[offset - WebPConstants.Bps]; - byte B = yuv[offset + 1 - WebPConstants.Bps]; - byte C = yuv[offset + 2 - WebPConstants.Bps]; - byte D = yuv[offset + 3 - WebPConstants.Bps]; - - byte xa = Avg2(X, A); + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte x = yuv[offset - 1 - WebPConstants.Bps]; + byte a = yuv[offset - WebPConstants.Bps]; + byte b = yuv[offset + 1 - WebPConstants.Bps]; + byte c = yuv[offset + 2 - WebPConstants.Bps]; + byte d = yuv[offset + 3 - WebPConstants.Bps]; + + byte xa = Avg2(x, a); Dst(dst, 0, 0, xa); Dst(dst, 1, 2, xa); - byte ab = Avg2(A, B); + byte ab = Avg2(a, b); Dst(dst, 1, 0, ab); Dst(dst, 2, 2, ab); - byte bc = Avg2(B, C); + byte bc = Avg2(b, c); Dst(dst, 2, 0, bc); Dst(dst, 3, 2, bc); - Dst(dst, 3, 0, Avg2(C, D)); - Dst(dst, 0, 3, Avg3(K, J, I)); - Dst(dst, 0, 2, Avg3(J, I, X)); - byte ixa = Avg3(I, X, A); + Dst(dst, 3, 0, Avg2(c, d)); + Dst(dst, 0, 3, Avg3(k, j, i)); + Dst(dst, 0, 2, Avg3(j, i, x)); + byte ixa = Avg3(i, x, a); Dst(dst, 0, 1, ixa); Dst(dst, 1, 3, ixa); - byte xab = Avg3(X, A, B); + byte xab = Avg3(x, a, b); Dst(dst, 1, 1, xab); Dst(dst, 2, 3, xab); - byte abc = Avg3(A, B, C); + byte abc = Avg3(a, b, c); Dst(dst, 2, 1, abc); Dst(dst, 3, 3, abc); - Dst(dst, 3, 1, Avg3(B, C, D)); + Dst(dst, 3, 1, Avg3(b, c, d)); } - public static void LD4_C(Span dst, byte[] yuv, int offset) + public static void LD4(Span dst, byte[] yuv, int offset) { // Down-Left - byte A = yuv[offset - WebPConstants.Bps]; - byte B = yuv[offset + 1 - WebPConstants.Bps]; - byte C = yuv[offset + 2 - WebPConstants.Bps]; - byte D = yuv[offset + 3 - WebPConstants.Bps]; - byte E = yuv[offset + 4 - WebPConstants.Bps]; - byte F = yuv[offset + 5 - WebPConstants.Bps]; - byte G = yuv[offset + 6 - WebPConstants.Bps]; - byte H = yuv[offset + 7 - WebPConstants.Bps]; - - Dst(dst, 0, 0, Avg3(A, B, C)); - byte bcd = Avg3(B, C, D); + byte a = yuv[offset - WebPConstants.Bps]; + byte b = yuv[offset + 1 - WebPConstants.Bps]; + byte c = yuv[offset + 2 - WebPConstants.Bps]; + byte d = yuv[offset + 3 - WebPConstants.Bps]; + byte e = yuv[offset + 4 - WebPConstants.Bps]; + byte f = yuv[offset + 5 - WebPConstants.Bps]; + byte g = yuv[offset + 6 - WebPConstants.Bps]; + byte h = yuv[offset + 7 - WebPConstants.Bps]; + + Dst(dst, 0, 0, Avg3(a, b, c)); + byte bcd = Avg3(b, c, d); Dst(dst, 1, 0, bcd); Dst(dst, 0, 1, bcd); - byte cde = Avg3(C, D, E); + byte cde = Avg3(c, d, e); Dst(dst, 2, 0, cde); Dst(dst, 1, 1, cde); Dst(dst, 0, 2, cde); - byte def = Avg3(D, E, F); + byte def = Avg3(d, e, f); Dst(dst, 3, 0, def); Dst(dst, 2, 1, def); Dst(dst, 1, 2, def); Dst(dst, 0, 3, def); - byte efg = Avg3(E, F, G); + byte efg = Avg3(e, f, g); Dst(dst, 3, 1, efg); Dst(dst, 2, 2, efg); Dst(dst, 1, 3, efg); - byte fgh = Avg3(F, G, H); + byte fgh = Avg3(f, g, h); Dst(dst, 3, 2, fgh); Dst(dst, 2, 3, fgh); - Dst(dst, 3, 3, Avg3(G, H, H)); + Dst(dst, 3, 3, Avg3(g, h, h)); } - public static void VL4_C(Span dst, byte[] yuv, int offset) + public static void VL4(Span dst, byte[] yuv, int offset) { // Vertical-Left - byte A = yuv[offset - WebPConstants.Bps]; - byte B = yuv[offset + 1 - WebPConstants.Bps]; - byte C = yuv[offset + 2 - WebPConstants.Bps]; - byte D = yuv[offset + 3 - WebPConstants.Bps]; - byte E = yuv[offset + 4 - WebPConstants.Bps]; - byte F = yuv[offset + 5 - WebPConstants.Bps]; - byte G = yuv[offset + 6 - WebPConstants.Bps]; - byte H = yuv[offset + 7 - WebPConstants.Bps]; - - Dst(dst, 0, 0, Avg2(A, B)); - byte bc = Avg2(B, C); + byte a = yuv[offset - WebPConstants.Bps]; + byte b = yuv[offset + 1 - WebPConstants.Bps]; + byte c = yuv[offset + 2 - WebPConstants.Bps]; + byte d = yuv[offset + 3 - WebPConstants.Bps]; + byte e = yuv[offset + 4 - WebPConstants.Bps]; + byte f = yuv[offset + 5 - WebPConstants.Bps]; + byte g = yuv[offset + 6 - WebPConstants.Bps]; + byte h = yuv[offset + 7 - WebPConstants.Bps]; + + Dst(dst, 0, 0, Avg2(a, b)); + byte bc = Avg2(b, c); Dst(dst, 1, 0, bc); Dst(dst, 0, 2, bc); - byte cd = Avg2(C, D); + byte cd = Avg2(c, d); Dst(dst, 2, 0, cd); Dst(dst, 1, 2, cd); - byte de = Avg2(D, E); + byte de = Avg2(d, e); Dst(dst, 3, 0, de); Dst(dst, 2, 2, de); - Dst(dst, 0, 1, Avg3(A, B, C)); - byte bcd = Avg3(B, C, D); + Dst(dst, 0, 1, Avg3(a, b, c)); + byte bcd = Avg3(b, c, d); Dst(dst, 1, 1, bcd); Dst(dst, 0, 3, bcd); - byte cde = Avg3(C, D, E); + byte cde = Avg3(c, d, e); Dst(dst, 2, 1, cde); Dst(dst, 1, 3, cde); - byte def = Avg3(D, E, F); + byte def = Avg3(d, e, f); Dst(dst, 3, 1, def); Dst(dst, 2, 3, def); - Dst(dst, 3, 2, Avg3(E, F, G)); - Dst(dst, 3, 3, Avg3(F, G, H)); + Dst(dst, 3, 2, Avg3(e, f, g)); + Dst(dst, 3, 3, Avg3(f, g, h)); } - public static void HD4_C(Span dst, byte[] yuv, int offset) + public static void HD4(Span dst, byte[] yuv, int offset) { // Horizontal-Down - byte I = yuv[offset - 1]; - byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - byte X = yuv[offset - 1 - WebPConstants.Bps]; - byte A = yuv[offset - WebPConstants.Bps]; - byte B = yuv[offset + 1 - WebPConstants.Bps]; - byte C = yuv[offset + 2 - WebPConstants.Bps]; - - byte ix = Avg2(I, X); + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte x = yuv[offset - 1 - WebPConstants.Bps]; + byte a = yuv[offset - WebPConstants.Bps]; + byte b = yuv[offset + 1 - WebPConstants.Bps]; + byte c = yuv[offset + 2 - WebPConstants.Bps]; + + byte ix = Avg2(i, x); Dst(dst, 0, 0, ix); Dst(dst, 2, 1, ix); - byte ji = Avg2(J, I); + byte ji = Avg2(j, i); Dst(dst, 0, 1, ji); Dst(dst, 2, 2, ji); - byte kj = Avg2(K, J); + byte kj = Avg2(k, j); Dst(dst, 0, 2, kj); Dst(dst, 2, 3, kj); - Dst(dst, 0, 3, Avg2(L, K)); - Dst(dst, 3, 0, Avg3(A, B, C)); - Dst(dst, 2, 0, Avg3(X, A, B)); - byte ixa = Avg3(I, X, A); + Dst(dst, 0, 3, Avg2(l, k)); + Dst(dst, 3, 0, Avg3(a, b, c)); + Dst(dst, 2, 0, Avg3(x, a, b)); + byte ixa = Avg3(i, x, a); Dst(dst, 1, 0, ixa); Dst(dst, 3, 1, ixa); - byte jix = Avg3(J, I, X); + byte jix = Avg3(j, i, x); Dst(dst, 1, 1, jix); Dst(dst, 3, 2, jix); - byte kji = Avg3(K, J, I); + byte kji = Avg3(k, j, i); Dst(dst, 1, 2, kji); Dst(dst, 3, 3, kji); - Dst(dst, 1, 3, Avg3(L, K, J)); + Dst(dst, 1, 3, Avg3(l, k, j)); } - public static void HU4_C(Span dst, byte[] yuv, int offset) + public static void HU4(Span dst, byte[] yuv, int offset) { // Horizontal-Up - byte I = yuv[offset - 1]; - byte J = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte K = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte L = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - Dst(dst, 0, 0, Avg2(I, J)); - byte jk = Avg2(J, K); + Dst(dst, 0, 0, Avg2(i, j)); + byte jk = Avg2(j, k); Dst(dst, 2, 0, jk); Dst(dst, 0, 1, jk); - byte kl = Avg2(K, L); + byte kl = Avg2(k, l); Dst(dst, 2, 1, kl); Dst(dst, 0, 2, kl); - Dst(dst, 1, 0, Avg3(I, J, K)); - byte jkl = Avg3(J, K, L); + Dst(dst, 1, 0, Avg3(i, j, k)); + byte jkl = Avg3(j, k, l); Dst(dst, 3, 0, jkl); Dst(dst, 1, 1, jkl); - byte kll = Avg3(K, L, L); + byte kll = Avg3(k, l, l); Dst(dst, 3, 1, kll); Dst(dst, 1, 2, kll); - Dst(dst, 3, 2, L); - Dst(dst, 2, 2, L); - Dst(dst, 0, 3, L); - Dst(dst, 1, 3, L); - Dst(dst, 2, 3, L); - Dst(dst, 3, 3, L); + Dst(dst, 3, 2, l); + Dst(dst, 2, 2, l); + Dst(dst, 0, 3, l); + Dst(dst, 1, 3, l); + Dst(dst, 2, 3, l); + Dst(dst, 3, 3, l); } public static void Transform(Span src, Span dst, bool doTwo) @@ -743,56 +743,56 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void DoFilter2(byte[] p, int offset, int step) { - // 4 pixels in, 2 pixels out + // 4 pixels in, 2 pixels out. int p1 = p[offset - (2 * step)]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - int a = (3 * (q0 - p0)) + Vp8LookupTables.Sclip1[p1 - q1]; - int a1 = Vp8LookupTables.Sclip2[(a + 4) >> 3]; - int a2 = Vp8LookupTables.Sclip2[(a + 3) >> 3]; - p[offset - step] = Vp8LookupTables.Clip1[p0 + a2]; - p[offset] = Vp8LookupTables.Clip1[q0 - a1]; + int a = (3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]; + int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; + int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; + p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; + p[offset] = WebPLookupTables.Clip1[q0 - a1]; } private static void DoFilter4(byte[] p, int offset, int step) { - // 4 pixels in, 4 pixels out + // 4 pixels in, 4 pixels out. int p1 = p[offset - (2 * step)]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; int a = 3 * (q0 - p0); - int a1 = Vp8LookupTables.Sclip2[(a + 4) >> 3]; - int a2 = Vp8LookupTables.Sclip2[(a + 3) >> 3]; + int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; + int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; int a3 = (a1 + 1) >> 1; - p[offset - (2 * step)] = Vp8LookupTables.Clip1[p1 + a3]; - p[offset - step] = Vp8LookupTables.Clip1[p0 + a2]; - p[offset] = Vp8LookupTables.Clip1[q0 - a1]; - p[offset + step] = Vp8LookupTables.Clip1[q1 - a3]; + p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a3]; + p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; + p[offset] = WebPLookupTables.Clip1[q0 - a1]; + p[offset + step] = WebPLookupTables.Clip1[q1 - a3]; } private static void DoFilter6(byte[] p, int offset, int step) { - // 6 pixels in, 6 pixels out + // 6 pixels in, 6 pixels out. int p2 = p[offset - (3 * step)]; int p1 = p[offset - (2 * step)]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; int q2 = p[offset + (2 * step)]; - int a = Vp8LookupTables.Sclip1[(3 * (q0 - p0)) + Vp8LookupTables.Sclip1[p1 - q1]]; + int a = WebPLookupTables.Sclip1[(3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]]; // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - (3 * step)] = Vp8LookupTables.Clip1[p2 + a3]; - p[offset - (2 * step)] = Vp8LookupTables.Clip1[p1 + a2]; - p[offset - step] = Vp8LookupTables.Clip1[p0 + a1]; - p[offset] = Vp8LookupTables.Clip1[q0 - a1]; - p[offset + step] = Vp8LookupTables.Clip1[q1 - a2]; - p[offset + (2 * step)] = Vp8LookupTables.Clip1[q2 - a3]; + p[offset - (3 * step)] = WebPLookupTables.Clip1[p2 + a3]; + p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a2]; + p[offset - step] = WebPLookupTables.Clip1[p0 + a1]; + p[offset] = WebPLookupTables.Clip1[q0 - a1]; + p[offset + step] = WebPLookupTables.Clip1[q1 - a2]; + p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3]; } private static bool NeedsFilter(byte[] p, int offset, int step, int t) @@ -801,7 +801,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return ((4 * Vp8LookupTables.Abs0[p0 - q0]) + Vp8LookupTables.Abs0[p1 - q1]) <= t; + return ((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) <= t; } private static bool NeedsFilter2(byte[] p, int offset, int step, int t, int it) @@ -814,14 +814,14 @@ namespace SixLabors.ImageSharp.Formats.WebP int q1 = p[offset + step]; int q2 = p[offset + (2 * step)]; int q3 = p[offset + (3 * step)]; - if (((4 * Vp8LookupTables.Abs0[p0 - q0]) + Vp8LookupTables.Abs0[p1 - q1]) > t) + if (((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) > t) { return false; } - return Vp8LookupTables.Abs0[p3 - p2] <= it && Vp8LookupTables.Abs0[p2 - p1] <= it && - Vp8LookupTables.Abs0[p1 - p0] <= it && Vp8LookupTables.Abs0[q3 - q2] <= it && - Vp8LookupTables.Abs0[q2 - q1] <= it && Vp8LookupTables.Abs0[q1 - q0] <= it; + return WebPLookupTables.Abs0[p3 - p2] <= it && WebPLookupTables.Abs0[p2 - p1] <= it && + WebPLookupTables.Abs0[p1 - p0] <= it && WebPLookupTables.Abs0[q3 - q2] <= it && + WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it; } private static bool Hev(byte[] p, int offset, int step, int thresh) @@ -830,7 +830,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (Vp8LookupTables.Abs0[p1 - p0] > thresh) || (Vp8LookupTables.Abs0[q1 - q0] > thresh); + return (WebPLookupTables.Abs0[p1 - p0] > thresh) || (WebPLookupTables.Abs0[q1 - q0] > thresh); } private static int MultHi(int v, int coeff) diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 2d3568a2b..21835c3d4 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -227,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.WebP n >>= 8; } - return logValue + Vp8LookupTables.LogTable8bit[n]; + return logValue + WebPLookupTables.LogTable8bit[n]; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 848994d4e..7dab51e33 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
internal class Vp8Decoder { + private Vp8MacroBlock leftMacroBlock; + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities) { this.FilterHeader = new Vp8FilterHeader(); @@ -53,12 +55,12 @@ namespace SixLabors.ImageSharp.Formats.WebP int extraRows = WebPConstants.FilterExtraRows[2]; // TODO: assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = (extraRows / 2) * this.CacheUvStride; - this.CacheY = new byte[width * height + extraY + 256]; // TODO: this is way too much mem, figure out what the min req is. - this.CacheU = new byte[width * height + extraUv + 256]; - this.CacheV = new byte[width * height + extraUv + 256]; - this.TmpYBuffer = new byte[width * height + extraY]; // TODO: figure out min buffer length - this.TmpUBuffer = new byte[width * height + extraUv]; // TODO: figure out min buffer length - this.TmpVBuffer = new byte[width * height + extraUv]; // TODO: figure out min buffer length + this.CacheY = new byte[(width * height) + extraY + 256]; // TODO: this is way too much mem, figure out what the min req is. + this.CacheU = new byte[(width * height) + extraUv + 256]; + this.CacheV = new byte[(width * height) + extraUv + 256]; + this.TmpYBuffer = new byte[(width * height) + extraY]; // TODO: figure out min buffer length + this.TmpUBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length + this.TmpVBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length this.Bgr = new byte[width * height * 4]; for (int i = 0; i < this.YuvBuffer.Length; i++) @@ -88,38 +90,47 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8SegmentHeader SegmentHeader { get; } - // number of partitions minus one. + /// + /// Gets or sets the number of partitions minus one. + /// public int NumPartsMinusOne { get; set; } - // per-partition boolean decoders. + /// + /// Gets the per-partition boolean decoders. + /// public Vp8BitReader[] Vp8BitReaders { get; } - public bool Dither { get; set; } - /// - /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). + /// Gets the dequantization matrices (one set of DC/AC dequant factor per segment). /// public Vp8QuantMatrix[] DeQuantMatrices { get; } + /// + /// Gets or sets a value indicating whether to use the skip probabilities. + /// public bool UseSkipProbability { get; set; } public byte SkipProbability { get; set; } public Vp8Proba Probabilities { get; set; } - // top intra modes values: 4 * MbWidth + /// + /// Gets or sets the top intra modes values: 4 * MbWidth. + /// public byte[] IntraT { get; set; } - // left intra modes values + /// + /// Gets the left intra modes values. + /// public byte[] IntraL { get; } /// - /// Gets or sets the width in macroblock units. + /// Gets the width in macroblock units. /// public int MbWidth { get; } /// - /// Gets or sets the height in macroblock units. + /// Gets the height in macroblock units. /// public int MbHeight { get; } @@ -198,8 +209,6 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public Vp8FilterInfo[] FilterInfo { get; set; } - private Vp8MacroBlock leftMacroBlock; - public Vp8MacroBlock CurrentMacroBlock { get diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index 60d97ad2b..82a8cbba8 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -5,59 +5,59 @@ using System; namespace SixLabors.ImageSharp.Formats.WebP { - public ref struct Vp8Io + internal ref struct Vp8Io { /// - /// Picture Width in pixels (invariable). + /// Gets or sets the picture width in pixels (invariable). /// Original, uncropped dimensions. /// The actual area passed to put() is stored in /> field. /// public int Width { get; set; } /// - /// Picture Width in pixels (invariable). + /// Gets or sets the picture height in pixels (invariable). /// Original, uncropped dimensions. /// The actual area passed to put() is stored in /> field. /// public int Height { get; set; } /// - /// Position of the current Rows (in pixels) + /// Gets or sets the y-position of the current macroblock. /// public int MbY { get; set; } /// - /// number of columns in the sample + /// Gets or sets the macroblock width. /// public int MbW { get; set; } /// - /// Number of Rows in the sample + /// Gets or sets the macroblock height. /// public int MbH { get; set; } /// - /// Rows to copy (in YUV format) + /// Rows to copy (in YUV format). /// public Span Y { get; set; } /// - /// Rows to copy (in YUV format) + /// Rows to copy (in YUV format). /// public Span U { get; set; } /// - /// Rows to copy (in YUV format) + /// Rows to copy (in YUV format). /// public Span V { get; set; } /// - /// Row stride for luma + /// Gets or sets the row stride for luma. /// public int YStride { get; set; } /// - /// Row stride for chroma + /// Gets or sets the row stride for chroma. /// public int UvStride { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 97a1c242f..9dba6c91d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
private const int Vp8LWbits = 32; - private readonly uint[] BitMask = + private readonly uint[] bitMask = { 0, 0x000001, 0x000003, 0x000007, 0x00000f, @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (!this.eos && nBits <= Vp8LMaxNumBitRead) { - ulong val = this.PrefetchBits() & this.BitMask[nBits]; + ulong val = this.PrefetchBits() & this.bitMask[nBits]; int newBits = this.bitPos + nBits; this.bitPos = newBits; this.ShiftBytes(); diff --git a/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs b/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs deleted file mode 100644 index 4f0a9127e..000000000 --- a/src/ImageSharp/Formats/WebP/Vp8LookupTables.cs +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - internal static class Vp8LookupTables - { - public static readonly Dictionary Abs0; - - public static readonly Dictionary Clip1; - - public static readonly Dictionary Sclip1; - - public static readonly Dictionary Sclip2; - - public static readonly byte[,][] ModesProba = new byte[10, 10][]; - - // 31 ^ clz(i) - public static readonly byte[] LogTable8bit = - { - 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - }; - - // Paragraph 14.1 - public static readonly int[] DcTable = - { - 4, 5, 6, 7, 8, 9, 10, 10, - 11, 12, 13, 14, 15, 16, 17, 17, - 18, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 25, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, - 37, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, - 91, 93, 95, 96, 98, 100, 101, 102, - 104, 106, 108, 110, 112, 114, 116, 118, - 122, 124, 126, 128, 130, 132, 134, 136, - 138, 140, 143, 145, 148, 151, 154, 157 - }; - - // Paragraph 14.1 - public static readonly int[] AcTable = - { - 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 60, - 62, 64, 66, 68, 70, 72, 74, 76, - 78, 80, 82, 84, 86, 88, 90, 92, - 94, 96, 98, 100, 102, 104, 106, 108, - 110, 112, 114, 116, 119, 122, 125, 128, - 131, 134, 137, 140, 143, 146, 149, 152, - 155, 158, 161, 164, 167, 170, 173, 177, - 181, 185, 189, 193, 197, 201, 205, 209, - 213, 217, 221, 225, 229, 234, 239, 245, - 249, 254, 259, 264, 269, 274, 279, 284 - }; - - // Paragraph 13 - public static readonly byte[,,,] CoeffsUpdateProba = - { - { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, - { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } - }, - { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } - }, - { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - } - }; - - // Paragraph 13.5: Default Token Probability Table. - public static readonly byte[,,,] DefaultCoeffsProba = - { - { - { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, - { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, - { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } - }, - { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, - { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, - { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, - }, - { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, - { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, - { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, - }, - { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, - { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, - { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } - }, - { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, - { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, - { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } - }, - { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, - { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, - { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } - }, - { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, - { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, - { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } - }, - { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, - { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, - { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } - }, - { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, - { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, - { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } - }, - { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, - { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, - { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } - }, - { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, - { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, - { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } - }, - { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, - { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, - { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } - }, - { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, - { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, - { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } - }, - { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } - } - }, - { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, - { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, - { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } - }, - { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, - { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, - { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } - }, - { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, - { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, - { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } - }, - { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, - { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, - { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, - { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } - }, - { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, - { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, - { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } - }, - { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, - { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, - { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } - }, - { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, - { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, - { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } - }, - { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, - { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, - { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } - }, - { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, - { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, - { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } - }, - { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, - { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, - { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } - }, - { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - } - } - }; - - static Vp8LookupTables() - { - // TODO: maybe use hashset here - Abs0 = new Dictionary(); - for (int i = -255; i <= 255; ++i) - { - Abs0[i] = (byte)((i < 0) ? -i : i); - } - - Clip1 = new Dictionary(); - for (int i = -255; i <= 255 + 255; ++i) - { - Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); - } - - Sclip1 = new Dictionary(); - for (int i = -1020; i <= 1020; ++i) - { - Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); - } - - Sclip2 = new Dictionary(); - for (int i = -112; i <= 112; ++i) - { - Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); - } - - InitializeModesProbabilities(); - } - - private static void InitializeModesProbabilities() - { - // Paragraph 11.5 - ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; - ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; - ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; - ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; - ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; - ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; - ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; - ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; - ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; - ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; - ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; - ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; - ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; - ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; - ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; - ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; - ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; - ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; - ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; - ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; - ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; - ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; - ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; - ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; - ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; - ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; - ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; - ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; - ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; - ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; - ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; - ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; - ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; - ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; - ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; - ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; - ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; - ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; - ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; - ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; - ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; - ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; - ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; - ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; - ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; - ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; - ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; - ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; - ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; - ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; - ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; - ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; - ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; - ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; - ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; - ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; - ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; - ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; - ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; - ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; - ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; - ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; - ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; - ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; - ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; - ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; - ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; - ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; - ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; - ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; - ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; - ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; - ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; - ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; - ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; - ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; - ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; - ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; - ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; - ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; - ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; - ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; - ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; - ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; - ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; - ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; - ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; - ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; - ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; - ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; - ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; - ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; - ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; - ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; - ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; - ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; - ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; - ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; - ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; - ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index f2ab8ff7f..cfac56d3c 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool IsI4x4 { get; set; } /// - /// Gets or sets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. + /// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. /// public byte[] Modes { get; } @@ -38,11 +38,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint NonZeroUv { get; set; } - /// - /// Gets or sets the local dithering strength (deduced from NonZero_*). - /// - public byte Dither { get; set; } - public byte Skip { get; set; } public byte Segment { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs deleted file mode 100644 index b9c82c7ec..000000000 --- a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - abstract class WebPDecoderBase - { - protected HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) - { - uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); - return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); - } - - private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) - { - if (bits is 0) - { - return 0; - } - - Span huffmanImageSpan = huffmanImage.GetSpan(); - return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; - } - - /// - /// Decodes the next Huffman code from bit-stream. - /// FillBitWindow(br) needs to be called at minimum every second call - /// to ReadSymbol, in order to pre-fetch enough bits. - /// - protected uint ReadSymbol(Span table, Vp8LBitReader bitReader) - { - // TODO: if the bitReader field is moved to this base class we could omit the parameter. - uint val = (uint)bitReader.PrefetchBits(); - Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); - int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; - if (nBits > 0) - { - bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); - val = (uint)bitReader.PrefetchBits(); - tableSpan = tableSpan.Slice((int)tableSpan[0].Value); - tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); - } - - bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); - - return tableSpan[0].Value; - } - - protected int GetCopyDistance(int distanceSymbol, Vp8LBitReader bitReader) - { - if (distanceSymbol < 4) - { - return distanceSymbol + 1; - } - - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; - - return (int)(offset + bitReader.ReadValue(extraBits) + 1); - } - - protected int GetCopyLength(int lengthSymbol, Vp8LBitReader bitReader) - { - // Length and distance prefixes are encoded the same way. - return this.GetCopyDistance(lengthSymbol, bitReader); - } - - // TODO: copied from LosslessDecoder - protected static readonly int[] KCodeToPlane = - { - 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, - 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, - 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, - 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, - 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, - 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, - 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, - 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, - 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, - 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, - 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, - 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 - }; - - protected static readonly int CodeToPlaneCodes = KCodeToPlane.Length; - - protected int PlaneCodeToDistance(int xSize, int planeCode) - { - if (planeCode > CodeToPlaneCodes) - { - return planeCode - CodeToPlaneCodes; - } - - int distCode = KCodeToPlane[planeCode - 1]; - int yOffset = distCode >> 4; - int xOffset = 8 - (distCode & 0xf); - int dist = (yOffset * xSize) + xOffset; - - // dist < 1 can happen if xSize is very small. - return (dist >= 1) ? dist : 1; - } - } -} diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs new file mode 100644 index 000000000..26c4be773 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -0,0 +1,580 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class WebPLookupTables + { + public static readonly Dictionary Abs0; + + public static readonly Dictionary Clip1; + + public static readonly Dictionary Sclip1; + + public static readonly Dictionary Sclip2; + + public static readonly byte[,][] ModesProba = new byte[10, 10][]; + + public static readonly int[] CodeToPlane = + { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; + + // 31 ^ clz(i) + public static readonly byte[] LogTable8bit = + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; + + // Paragraph 14.1 + public static readonly int[] DcTable = + { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 + }; + + // Paragraph 14.1 + public static readonly int[] AcTable = + { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 + }; + + // Paragraph 13 + public static readonly byte[,,,] CoeffsUpdateProba = + { + { + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } + }; + + // Paragraph 13.5: Default Token Probability Table. + public static readonly byte[,,,] DefaultCoeffsProba = + { + { + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { + { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { + { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { + { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { + { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { + { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { + { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { + { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { + { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { + { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { + { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { + { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { + { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { + { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { + { + { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { + { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { + { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { + { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { + { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { + { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { + { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { + { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { + { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { + { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { + { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } + }; + + static WebPLookupTables() + { + // TODO: maybe use hashset here + Abs0 = new Dictionary(); + for (int i = -255; i <= 255; ++i) + { + Abs0[i] = (byte)((i < 0) ? -i : i); + } + + Clip1 = new Dictionary(); + for (int i = -255; i <= 255 + 255; ++i) + { + Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); + } + + Sclip1 = new Dictionary(); + for (int i = -1020; i <= 1020; ++i) + { + Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); + } + + Sclip2 = new Dictionary(); + for (int i = -112; i <= 112; ++i) + { + Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); + } + + InitializeModesProbabilities(); + } + + private static void InitializeModesProbabilities() + { + // Paragraph 11.5 + ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; + ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; + ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; + ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; + ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; + ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; + ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; + ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; + ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; + ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; + ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; + ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; + ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; + ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; + ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; + ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; + ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; + ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; + ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; + ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; + ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; + ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; + ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; + ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; + ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; + ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; + ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; + ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; + ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; + ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; + ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; + ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; + ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; + ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; + ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; + ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; + ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; + ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; + ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; + ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; + ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; + ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; + ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; + ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; + ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; + ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; + ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; + ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; + ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; + ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; + ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; + ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; + ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; + ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; + ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; + ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; + ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; + ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; + ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; + ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; + ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; + ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; + ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; + ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; + ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; + ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; + ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; + ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; + ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; + ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; + ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; + ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; + ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; + ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; + ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; + ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; + ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; + ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; + ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; + ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; + ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; + ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; + ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; + ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; + ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; + ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; + ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; + ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; + ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; + ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; + ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; + ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; + ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; + ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; + ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; + ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; + ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; + ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; + ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; + ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 86fd34bae..98fe46cbc 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The lossless specification can be found here: /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification /// - internal sealed class WebPLosslessDecoder : WebPDecoderBase + internal sealed class WebPLosslessDecoder { private readonly Vp8LBitReader bitReader; @@ -27,11 +27,11 @@ namespace SixLabors.ImageSharp.Formats.WebP private static readonly uint PackedNonLiteralCode = 0; - private static readonly int NumArgbCacheRows = 16; + private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; private static readonly int FixedTableSize = (630 * 3) + 410; - private static readonly int[] KTableSize = + private static readonly int[] TableSize = { FixedTableSize + 654, FixedTableSize + 656, @@ -47,10 +47,11 @@ namespace SixLabors.ImageSharp.Formats.WebP FixedTableSize + 2704 }; - private static readonly byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - private static readonly int NumCodeLengthCodes = KCodeLengthCodeOrder.Length; + private static readonly byte[] CodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - private static readonly byte[] KLiteralMap = + private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; + + private static readonly byte[] LiteralMap = { 0, 1, 1, 1, 0 }; @@ -235,7 +236,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green], this.bitReader); + code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); } if (this.bitReader.IsEndOfStream()) @@ -252,10 +253,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red], this.bitReader); + uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); this.bitReader.FillBitWindow(); - uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue], this.bitReader); - uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha], this.bitReader); + uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); + uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); if (this.bitReader.IsEndOfStream()) { break; @@ -271,10 +272,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { // Backward reference is used. int lengthSym = code - WebPConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym, this.bitReader); - uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist], this.bitReader); + int length = this.GetCopyLength(lengthSym); + uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance((int)distSymbol, this.bitReader); + int distCode = this.GetCopyDistance((int)distSymbol); int dist = this.PlaneCodeToDistance(width, distCode); if (this.bitReader.IsEndOfStream()) { @@ -392,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - int tableSize = KTableSize[colorCacheBits]; + int tableSize = TableSize[colorCacheBits]; var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; var hTreeGroups = new HTreeGroup[numHTreeGroups]; Span huffmanTable = huffmanTables.AsSpan(); @@ -420,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.WebP hTreeGroup.HTrees.Add(huffmanTable.ToArray()); - if (isTrivialLiteral && KLiteralMap[j] == 1) + if (isTrivialLiteral && LiteralMap[j] == 1) { isTrivialLiteral = huffmanTable[0].BitsUsed == 0; } @@ -515,7 +516,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); + codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); } this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); @@ -750,6 +751,81 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Decodes the next Huffman code from bit-stream. + /// FillBitWindow(br) needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. + /// + private uint ReadSymbol(Span table) + { + // TODO: if the bitReader field is moved to this base class we could omit the parameter. + uint val = (uint)this.bitReader.PrefetchBits(); + Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); + int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; + if (nBits > 0) + { + this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); + val = (uint)this.bitReader.PrefetchBits(); + tableSpan = tableSpan.Slice((int)tableSpan[0].Value); + tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); + } + + this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); + + return tableSpan[0].Value; + } + + private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); + } + + private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) + { + if (bits is 0) + { + return 0; + } + + Span huffmanImageSpan = huffmanImage.GetSpan(); + return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; + } + + private int PlaneCodeToDistance(int xSize, int planeCode) + { + if (planeCode > CodeToPlaneCodes) + { + return planeCode - CodeToPlaneCodes; + } + + int distCode = WebPLookupTables.CodeToPlane[planeCode - 1]; + int yOffset = distCode >> 4; + int xOffset = 8 - (distCode & 0xf); + int dist = (yOffset * xSize) + xOffset; + + // dist < 1 can happen if xSize is very small. + return (dist >= 1) ? dist : 1; + } + + private int GetCopyDistance(int distanceSymbol) + { + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); + } + + private int GetCopyLength(int lengthSymbol) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol); + } + private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 82c9b9bd0..42402e025 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -12,7 +12,7 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - internal sealed class WebPLossyDecoder : WebPDecoderBase + internal sealed class WebPLossyDecoder { private readonly Vp8BitReader bitReader; @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int yMode = left[y]; for (int x = 0; x < 4; ++x) { - byte[] prob = Vp8LookupTables.ModesProba[top[x], yMode]; + byte[] prob = WebPLookupTables.ModesProba[top[x], yMode]; int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; while (i > 0) { @@ -328,34 +328,34 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (lumaMode) { case 0: - LossyUtils.DC4_C(dst, yuv, offset); + LossyUtils.DC4(dst, yuv, offset); break; case 1: - LossyUtils.TM4_C(dst, yuv, offset); + LossyUtils.TM4(dst, yuv, offset); break; case 2: - LossyUtils.VE4_C(dst, yuv, offset); + LossyUtils.VE4(dst, yuv, offset); break; case 3: - LossyUtils.HE4_C(dst, yuv, offset); + LossyUtils.HE4(dst, yuv, offset); break; case 4: - LossyUtils.RD4_C(dst, yuv, offset); + LossyUtils.RD4(dst, yuv, offset); break; case 5: - LossyUtils.VR4_C(dst, yuv, offset); + LossyUtils.VR4(dst, yuv, offset); break; case 6: - LossyUtils.LD4_C(dst, yuv, offset); + LossyUtils.LD4(dst, yuv, offset); break; case 7: - LossyUtils.VL4_C(dst, yuv, offset); + LossyUtils.VL4(dst, yuv, offset); break; case 8: - LossyUtils.HD4_C(dst, yuv, offset); + LossyUtils.HD4(dst, yuv, offset); break; case 9: - LossyUtils.HU4_C(dst, yuv, offset); + LossyUtils.HU4(dst, yuv, offset); break; } @@ -369,25 +369,25 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (mode) { case 0: - LossyUtils.DC16_C(yDst, yuv, yOff); + LossyUtils.DC16(yDst, yuv, yOff); break; case 1: - LossyUtils.TM16_C(yDst, yuv, yOff); + LossyUtils.TM16(yDst, yuv, yOff); break; case 2: - LossyUtils.VE16_C(yDst, yuv, yOff); + LossyUtils.VE16(yDst, yuv, yOff); break; case 3: - LossyUtils.HE16_C(yDst, yuv, yOff); + LossyUtils.HE16(yDst, yuv, yOff); break; case 4: - LossyUtils.DC16NoTop_C(yDst, yuv, yOff); + LossyUtils.DC16NoTop(yDst, yuv, yOff); break; case 5: - LossyUtils.DC16NoLeft_C(yDst, yuv, yOff); + LossyUtils.DC16NoLeft(yDst, yuv, yOff); break; case 6: - LossyUtils.DC16NoTopLeft_C(yDst); + LossyUtils.DC16NoTopLeft(yDst); break; } @@ -406,32 +406,32 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (chromaMode) { case 0: - LossyUtils.DC8uv_C(uDst, yuv, uOff); - LossyUtils.DC8uv_C(vDst, yuv, vOff); + LossyUtils.DC8uv(uDst, yuv, uOff); + LossyUtils.DC8uv(vDst, yuv, vOff); break; case 1: - LossyUtils.TM8uv_C(uDst, yuv, uOff); - LossyUtils.TM8uv_C(vDst, yuv, vOff); + LossyUtils.TM8uv(uDst, yuv, uOff); + LossyUtils.TM8uv(vDst, yuv, vOff); break; case 2: - LossyUtils.VE8uv_C(uDst, yuv, uOff); - LossyUtils.VE8uv_C(vDst, yuv, vOff); + LossyUtils.VE8uv(uDst, yuv, uOff); + LossyUtils.VE8uv(vDst, yuv, vOff); break; case 3: - LossyUtils.HE8uv_C(uDst, yuv, uOff); - LossyUtils.HE8uv_C(vDst, yuv, vOff); + LossyUtils.HE8uv(uDst, yuv, uOff); + LossyUtils.HE8uv(vDst, yuv, vOff); break; case 4: - LossyUtils.DC8uvNoTop_C(uDst, yuv, uOff); - LossyUtils.DC8uvNoTop_C(vDst, yuv, vOff); + LossyUtils.DC8uvNoTop(uDst, yuv, uOff); + LossyUtils.DC8uvNoTop(vDst, yuv, vOff); break; case 5: - LossyUtils.DC8uvNoLeft_C(uDst, yuv, uOff); - LossyUtils.DC8uvNoLeft_C(vDst, yuv, vOff); + LossyUtils.DC8uvNoLeft(uDst, yuv, uOff); + LossyUtils.DC8uvNoLeft(vDst, yuv, vOff); break; case 6: - LossyUtils.DC8uvNoTopLeft_C(uDst); - LossyUtils.DC8uvNoTopLeft_C(vDst); + LossyUtils.DC8uvNoTopLeft(uDst); + LossyUtils.DC8uvNoTopLeft(vDst); break; } @@ -803,7 +803,6 @@ namespace SixLabors.ImageSharp.Formats.WebP blockData.NonZeroY = 0; blockData.NonZeroUv = 0; - blockData.Dither = 0; } // Store filter info. @@ -921,10 +920,6 @@ namespace SixLabors.ImageSharp.Formats.WebP block.NonZeroY = nonZeroY; block.NonZeroUv = nonZeroUv; - // We look at the mode-code of each block and check if some blocks have less - // than three non-zero coeffs (code < 2). This is to avoid dithering flat and empty blocks. - block.Dither = (byte)((nonZeroUv & 0xaaaa) > 0 ? 0 : q.Dither); - return (nonZeroY | nonZeroUv) is 0; } @@ -1227,20 +1222,20 @@ namespace SixLabors.ImageSharp.Formats.WebP } Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; - m.Y1Mat[0] = Vp8LookupTables.DcTable[Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = Vp8LookupTables.AcTable[Clip(q + 0, 127)]; - m.Y2Mat[0] = Vp8LookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; + m.Y1Mat[0] = WebPLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebPLookupTables.AcTable[Clip(q + 0, 127)]; + m.Y2Mat[0] = WebPLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (Vp8LookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; + m.Y2Mat[1] = (WebPLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; if (m.Y2Mat[1] < 8) { m.Y2Mat[1] = 8; } - m.UvMat[0] = Vp8LookupTables.DcTable[Clip(q + dquvDc, 117)]; - m.UvMat[1] = Vp8LookupTables.AcTable[Clip(q + dquvAc, 127)]; + m.UvMat[0] = WebPLookupTables.DcTable[Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebPLookupTables.AcTable[Clip(q + dquvAc, 127)]; // For dithering strength evaluation. m.UvQuant = q + dquvAc; @@ -1259,10 +1254,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int p = 0; p < WebPConstants.NumProbas; ++p) { - byte prob = Vp8LookupTables.CoeffsUpdateProba[t, b, c, p]; + byte prob = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; int v = this.bitReader.GetBit(prob) != 0 ? (int)this.bitReader.ReadValue(8) - : Vp8LookupTables.DefaultCoeffsProba[t, b, c, p]; + : WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; } } @@ -1343,9 +1338,8 @@ namespace SixLabors.ImageSharp.Formats.WebP return io; } - static bool Is8bOptimizable(Vp8LMetadata hdr) + private static bool Is8bOptimizable(Vp8LMetadata hdr) { - int i; if (hdr.ColorCacheSize > 0) { return false; @@ -1353,7 +1347,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // When the Huffman tree contains only one symbol, we can skip the // call to ReadSymbol() for red/blue/alpha channels. - for (i = 0; i < hdr.NumHTreeGroups; ++i) + for (int i = 0; i < hdr.NumHTreeGroups; ++i) { List htrees = hdr.HTreeGroups[i].HTrees; if (htrees[HuffIndex.Red][0].Value > 0) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index ff9386286..5ec22cbad 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } - [Benchmark(Baseline = true, Description = "Magick Lossy WebP")] + [Benchmark(Description = "Magick Lossy WebP")] public int WebpLossyMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } - [Benchmark(Baseline = true, Description = "Magick Lossless WebP")] + [Benchmark(Description = "Magick Lossless WebP")] public int WebpLosslessMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index fea51bf73..a6898093a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -21,11 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); [Theory] - [InlineData(Lossless.Lossless1, 1000, 307, 24)] - [InlineData(Lossless.Lossless2, 1000, 307, 24)] + [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] + [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] + [InlineData(Lossless.NoTransform2, 128, 128, 32)] [InlineData(Lossy.Alpha1, 1000, 307, 32)] [InlineData(Lossy.Alpha2, 1000, 307, 32)] - public void Identify_DetectsCorrectDimensions( + [InlineData(Lossy.Bike, 250, 195, 24)] + public void Identify_DetectsCorrectDimensionsAndBitDepth( string imagePath, int expectedWidth, int expectedHeight, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index 44163da17..485f740a9 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -12,8 +12,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP { using static SixLabors.ImageSharp.Tests.TestImages.WebP; - using static TestImages.Bmp; - + public class WebPMetaDataTests { [Fact] @@ -27,21 +26,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel));*/ } - - [Theory] - [InlineData(Lossless.Lossless1, BmpInfoHeaderType.WinVersion2)] - public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(24, imageInfo.PixelType.BitsPerPixel); - //var webpMetaData = imageInfo.Metadata.GetFormatMetadata(WebPFormat.Instance); - //Assert.NotNull(webpMetaData); - //Assert.Equal(expectedInfoHeaderType, webpMetaData.InfoHeaderType); - } - } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 66c936ad5..e892d87af 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -378,8 +378,6 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { - public const string Lossless1 = "WebP/lossless1.webp"; - public const string Lossless2 = "WebP/lossless2.webp"; public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; public const string NoTransform2 = "WebP/lossless_vec_2_0.webp"; public const string GreenTransform1 = "WebP/lossless1.webp"; @@ -498,11 +496,6 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; } - - public static readonly string[] All = - { - Lossless.Lossless1 - }; } } } From 3b3769248ab3dc834b7561afba0e0c1b4aca930b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Mar 2020 19:55:23 +0100 Subject: [PATCH 131/359] Start implementing alpha decoding: uncompressed alpha without filtering works so far --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 58 +++++++++++++++---- .../Formats/WebP/Filters/WebPFilterType.cs | 16 +++++ src/ImageSharp/Formats/WebP/LoopFilter.cs | 2 + .../Formats/WebP/WebPDecoderCore.cs | 13 ++++- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 7 ++- .../Formats/WebP/WebPLossyDecoder.cs | 35 ++++++++--- 6 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index d2c8d583f..f6ee5ec16 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -4,24 +4,27 @@ using System; using SixLabors.ImageSharp.Formats.WebP.Filters; +using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats.WebP { - internal ref struct AlphaDecoder + internal class AlphaDecoder { public int Width { get; set; } public int Height { get; set; } - public int Method { get; set; } - public WebPFilterBase Filter { get; set; } - public int PreProcessing { get; set; } + private WebPFilterType FilterType { get; } + + private int PreProcessing { get; } + + private bool Compressed { get; } - public Vp8LDecoder Vp8LDec { get; set; } + private byte[] Data { get; } - public Vp8Io Io { get; set; } + private Vp8LDecoder Vp8LDec { get; set; } /// /// Although Alpha Channel requires only 1 byte per pixel, @@ -30,22 +33,57 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool Use8BDecode { get; set; } - // last output row (or null) - private Span PrevLine { get; set; } + public AlphaDecoder(int width, int height, byte[] data) + { + this.Width = width; + this.Height = height; + this.Data = data; + + // Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format) + int method = data[0] & 0x03; + if (method < 0 || method > 1) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found"); + } + + this.Compressed = !(method is 0); + + // The filtering method used. Only values between 0 and 3 are valid. + int filter = (data[0] >> 2) & 0x03; + if (filter < 0 || filter > 3) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); + } + + this.FilterType = (WebPFilterType)filter; + + // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. + // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. + // 0: no pre-processing, 1: level reduction + this.PreProcessing = (data[0] >> 4) & 0x03; + } private int PrevLineOffset { get; set; } + public void Decode(Vp8Decoder dec, Span dst) + { + if (this.Compressed is false) + { + this.Data.AsSpan(1, this.Width * this.Height).CopyTo(dst); + } + } + // Taken from vp8l_dec.c AlphaApplyFilter public void AlphaApplyFilter( int firstRow, int lastRow, + Span prevLine, Span output, int outputOffset, int stride) { if (!(this.Filter is WebPFilterNone)) { - Span prevLine = this.PrevLine; int prevLineOffset = this.PrevLineOffset; for (int y = firstRow; y < lastRow; y++) @@ -62,8 +100,6 @@ namespace SixLabors.ImageSharp.Formats.WebP prevLineOffset = outputOffset; outputOffset += stride; } - - this.PrevLine = prevLine; } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs new file mode 100644 index 000000000..4b79ea5f5 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + internal enum WebPFilterType + { + None = 0, + + Horizontal = 1, + + Vertical = 2, + + Gradient = 3, + } +} diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs index 2f6766108..e48d89cea 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -6,7 +6,9 @@ namespace SixLabors.ImageSharp.Formats.WebP internal enum LoopFilter { None = 0, + Simple = 1, + Complex = 2, } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 6e9631729..f55bc0ed0 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -168,6 +168,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// - A 'VP8X' chunk with information about features used in the file. /// - An optional 'ICCP' chunk with color profile. /// - An optional 'ANIM' chunk with animation control data. + /// - An optional 'ALPH' chunk with alpha channel data. /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. ///
/// Information about this webp image. @@ -240,19 +241,25 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + byte[] alphaData = null; if (isAlphaPresent) { chunkType = this.ReadChunkType(); - uint alphaChunkSize = this.ReadChunkSize(); + if (chunkType != WebPChunkType.Alpha) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing"); + } - // ALPH chunks will be skipped for now. - this.currentStream.Skip((int)alphaChunkSize); + uint alphaChunkSize = this.ReadChunkSize(); + alphaData = new byte[alphaChunkSize]; + this.currentStream.Read(alphaData, 0, (int)alphaChunkSize); } var features = new WebPFeatures() { Animation = isAnimationPresent, Alpha = isAlphaPresent, + AlphaData = alphaData, ExifProfile = isExifPresent, IccProfile = isIccPresent, XmpMetaData = isXmpPresent diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 6ae4f1e9d..f0d728402 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Image features of a VP8X image. /// - public class WebPFeatures + internal class WebPFeatures { /// /// Gets or sets a value indicating whether this image has a ICC Profile. @@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool Alpha { get; set; } + /// + /// Gets or sets the alpha data, if an ALPH chunk is present. + /// + public byte[] AlphaData { get; set; } + /// /// Gets or sets a value indicating whether this image has a EXIF Profile. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 42402e025..34ddade1e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -66,25 +66,46 @@ namespace SixLabors.ImageSharp.Formats.WebP // Decode image data. this.ParseFrame(decoder, io); - this.DecodePixelValues(width, height, decoder.Bgr, pixels); + byte[] decodedAlpha = null; + if (info.Features?.Alpha is true) + { + var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData); + + // TODO: use memory allocator. + decodedAlpha = new byte[width * height]; + alphaDecoder.Decode(decoder, decodedAlpha.AsSpan()); + } + + this.DecodePixelValues(width, height, decoder.Bgr, decodedAlpha, pixels); } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels) + private void DecodePixelValues(int width, int height, Span pixelData, byte[] alpha, Buffer2D pixels) where TPixel : struct, IPixel { TPixel color = default; + bool hasAlpha = alpha != null; for (int y = 0; y < height; y++) { Span pixelRow = pixels.GetRowSpan(y); for (int x = 0; x < width; x++) { - int idx = ((y * width) + x) * 3; - byte b = pixelData[idx]; - byte g = pixelData[idx + 1]; - byte r = pixelData[idx + 2]; + int offset = (y * width) + x; + int idxBgr = offset * 3; + byte b = pixelData[idxBgr]; + byte g = pixelData[idxBgr + 1]; + byte r = pixelData[idxBgr + 2]; // TODO: use bulk conversion here. - color.FromBgr24(new Bgr24(r, g, b)); + if (hasAlpha) + { + byte a = alpha[offset]; + color.FromBgra32(new Bgra32(r, g, b, a)); + } + else + { + color.FromBgr24(new Bgr24(r, g, b)); + } + pixelRow[x] = color; } } From 7592ebda72cceeca5dc57cc1cb8476463e2a085a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Mar 2020 12:28:03 +0100 Subject: [PATCH 132/359] Start implementing compressed alpha decoding --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 100 +++++-- .../Formats/WebP/Filters/WebPFilterBase.cs | 2 +- .../WebP/Filters/WebPFilterHorizontal.cs | 4 +- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 4 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 34 ++- src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 27 +- .../Formats/WebP/WebPDecoderCore.cs | 7 +- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 5 + .../Formats/WebP/WebPLosslessDecoder.cs | 269 ++++++++++++++++-- .../Formats/WebP/WebPLossyDecoder.cs | 51 +--- .../Formats/WebP/WebPDecoderTests.cs | 7 +- tests/ImageSharp.Tests/TestImages.cs | 11 +- 12 files changed, 404 insertions(+), 117 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index f6ee5ec16..bf382c425 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -4,19 +4,27 @@ using System; using SixLabors.ImageSharp.Formats.WebP.Filters; -using SixLabors.ImageSharp.Processing; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { internal class AlphaDecoder { - public int Width { get; set; } + public int Width { get; } - public int Height { get; set; } + public int Height { get; } - public WebPFilterBase Filter { get; set; } + public WebPFilterBase Filter { get; } - private WebPFilterType FilterType { get; } + public WebPFilterType FilterType { get; } + + public int CropTop { get; } + + public int LastRow { get; set; } + + public Vp8LDecoder Vp8LDec { get; } + + public byte[] Alpha { get; } private int PreProcessing { get; } @@ -24,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private byte[] Data { get; } - private Vp8LDecoder Vp8LDec { get; set; } + private WebPLosslessDecoder LosslessDecoder { get; } /// /// Although Alpha Channel requires only 1 byte per pixel, @@ -33,14 +41,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool Use8BDecode { get; set; } - public AlphaDecoder(int width, int height, byte[] data) + public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator) { this.Width = width; this.Height = height; this.Data = data; + this.LastRow = 0; // Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format) - int method = data[0] & 0x03; + int method = alphaChunkHeader & 0x03; if (method < 0 || method > 1) { WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found"); @@ -49,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Compressed = !(method is 0); // The filtering method used. Only values between 0 and 3 are valid. - int filter = (data[0] >> 2) & 0x03; + int filter = (alphaChunkHeader >> 2) & 0x03; if (filter < 0 || filter > 3) { WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); @@ -60,16 +69,49 @@ namespace SixLabors.ImageSharp.Formats.WebP // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. // 0: no pre-processing, 1: level reduction - this.PreProcessing = (data[0] >> 4) & 0x03; + this.PreProcessing = (alphaChunkHeader >> 4) & 0x03; + + this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); + + // TODO: use memory allocator + this.Alpha = new byte[width * height]; + + if (this.Compressed) + { + var bitReader = new Vp8LBitReader(data); + this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator); + this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); + } } private int PrevLineOffset { get; set; } - public void Decode(Vp8Decoder dec, Span dst) + public void Decode() { if (this.Compressed is false) { - this.Data.AsSpan(1, this.Width * this.Height).CopyTo(dst); + if (this.Data.Length < (this.Width * this.Height)) + { + WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); + } + + switch (this.FilterType) + { + case WebPFilterType.None: + this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); + break; + case WebPFilterType.Horizontal: + + break; + case WebPFilterType.Vertical: + break; + case WebPFilterType.Gradient: + break; + } + } + else + { + this.LosslessDecoder.DecodeAlphaData(this); } } @@ -82,24 +124,26 @@ namespace SixLabors.ImageSharp.Formats.WebP int outputOffset, int stride) { - if (!(this.Filter is WebPFilterNone)) + if (this.Filter is WebPFilterNone) { - int prevLineOffset = this.PrevLineOffset; + return; + } - for (int y = firstRow; y < lastRow; y++) - { - this.Filter - .Unfilter( - prevLine, - prevLineOffset, - output, - outputOffset, - output, - outputOffset, - stride); - prevLineOffset = outputOffset; - outputOffset += stride; - } + int prevLineOffset = this.PrevLineOffset; + + for (int y = firstRow; y < lastRow; y++) + { + this.Filter + .Unfilter( + prevLine, + prevLineOffset, + output, + outputOffset, + output, + outputOffset, + stride); + prevLineOffset = outputOffset; + outputOffset += stride; } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs index 4ff2ae568..74b69f43a 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters // Fast // } - abstract class WebPFilterBase + internal abstract class WebPFilterBase { /// /// diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs index 4328332a5..fdec37e5d 100644 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs @@ -5,7 +5,7 @@ using System; namespace SixLabors.ImageSharp.Formats.WebP.Filters { - class WebPFilterHorizontal : WebPFilterBase + internal class WebPFilterHorizontal : WebPFilterBase { public override void Unfilter( Span prevLine, @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Filters predsOffset = inputOffset; } - if (row == 0) + if (row is 0) { // leftmost pixel is the same as Input for topmost scanline output[0] = input[0]; diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 7dab51e33..8de3144f6 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.TmpYBuffer = new byte[(width * height) + extraY]; // TODO: figure out min buffer length this.TmpUBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length this.TmpVBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length - this.Bgr = new byte[width * height * 4]; + this.Pixels = new byte[width * height * 4]; for (int i = 0; i < this.YuvBuffer.Length; i++) { @@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public byte[] TmpVBuffer { get; } - public byte[] Bgr { get; } + public byte[] Pixels { get; } /// /// Gets or sets filter strength info. diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 9dba6c91d..f4114f5be 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -61,7 +61,29 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// True if a bit was read past the end of buffer. /// - private bool eos; + public bool Eos; + + /// + /// Initializes a new instance of the class. + /// + /// Lossless compressed image data. + public Vp8LBitReader(byte[] data) + { + this.Data = data; + this.len = data.Length; + this.value = 0; + this.bitPos = 0; + this.Eos = false; + + ulong currentValue = 0; + for (int i = 0; i < 8; ++i) + { + currentValue |= (ulong)this.Data[i] << (8 * i); + } + + this.value = currentValue; + this.pos = 8; + } /// /// Initializes a new instance of the class. @@ -78,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.len = length; this.value = 0; this.bitPos = 0; - this.eos = false; + this.Eos = false; if (length > sizeof(long)) { @@ -104,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - if (!this.eos && nBits <= Vp8LMaxNumBitRead) + if (!this.Eos && nBits <= Vp8LMaxNumBitRead) { ulong val = this.PrefetchBits() & this.bitMask[nBits]; int newBits = this.bitPos + nBits; @@ -139,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Return the pre-fetched bits, so they can be looked up. /// - /// Prefetched bits. + /// The pre-fetched bits. public ulong PrefetchBits() { return this.value >> (this.bitPos & (Vp8LLbits - 1)); @@ -162,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// True, if end of buffer was reached. public bool IsEndOfStream() { - return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); + return this.Eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); } private void DoFillBitWindow() @@ -191,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void SetEndOfStream() { - this.eos = true; + this.Eos = true; this.bitPos = 0; // To avoid undefined behaviour with shifts. } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index bf8d949b2..968d98b9d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -1,25 +1,31 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Collections.Generic; +using SixLabors.Memory; + namespace SixLabors.ImageSharp.Formats.WebP { /// /// Holds information for decoding a lossless webp image. /// - internal class Vp8LDecoder + internal class Vp8LDecoder : IDisposable { /// /// Initializes a new instance of the class. /// /// The width of the image. /// The height of the image. - public Vp8LDecoder(int width, int height) + /// Used for allocating memory for the pixel data output. + public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator) { this.Width = width; this.Height = height; this.Metadata = new Vp8LMetadata(); + this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean); } /// @@ -41,5 +47,22 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets the transformations which needs to be reversed. /// public List Transforms { get; set; } + + /// + /// Gets the pixel data. + /// + public IMemoryOwner Pixels { get; } + + /// + public void Dispose() + { + this.Pixels.Dispose(); + foreach (Vp8LTransform transform in this.Transforms) + { + transform.Data?.Dispose(); + } + + this.Metadata?.HuffmanImage?.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f55bc0ed0..a9312f19c 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -242,6 +242,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } byte[] alphaData = null; + byte alphaChunkHeader = 0; if (isAlphaPresent) { chunkType = this.ReadChunkType(); @@ -251,8 +252,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } uint alphaChunkSize = this.ReadChunkSize(); - alphaData = new byte[alphaChunkSize]; - this.currentStream.Read(alphaData, 0, (int)alphaChunkSize); + alphaChunkHeader = (byte)this.currentStream.ReadByte(); + alphaData = new byte[alphaChunkSize - 1]; + this.currentStream.Read(alphaData, 0, alphaData.Length); } var features = new WebPFeatures() @@ -260,6 +262,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Animation = isAnimationPresent, Alpha = isAlphaPresent, AlphaData = alphaData, + AlphaChunkHeader = alphaChunkHeader, ExifProfile = isExifPresent, IccProfile = isIccPresent, XmpMetaData = isXmpPresent diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index f0d728402..3fd035076 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public byte[] AlphaData { get; set; } + /// + /// Gets or sets the alpha chunk header. + /// + public byte AlphaChunkHeader { get; set; } + /// /// Gets or sets a value indicating whether this image has a EXIF Profile. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 98fe46cbc..b64cc0a17 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -5,7 +5,9 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -25,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private static readonly int BitsSpecialMarker = 0x100; + private static readonly int NumArgbCacheRows = 16; + private static readonly uint PackedNonLiteralCode = 0; private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; @@ -82,22 +86,18 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Decode(Buffer2D pixels, int width, int height) where TPixel : struct, IPixel { - var decoder = new Vp8LDecoder(width, height); - IMemoryOwner pixelData = this.DecodeImageStream(decoder, width, height, true); - this.DecodePixelValues(decoder, pixelData.GetSpan(), pixels); - - // Free up allocated memory. - pixelData.Dispose(); - foreach (Vp8LTransform transform in decoder.Transforms) + using (var decoder = new Vp8LDecoder(width, height, this.memoryAllocator)) { - transform.Data?.Dispose(); + this.DecodeImageStream(decoder, width, height, true); + this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); + this.DecodePixelValues(decoder, pixels); } - - decoder.Metadata?.HuffmanImage?.Dispose(); } - private IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) + public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { + int transformXSize = xSize; + int transformYSize = ySize; int numberOfTransformsPresent = 0; if (isLevel0) { @@ -111,7 +111,12 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); } - this.ReadTransformation(xSize, ySize, decoder); + this.ReadTransformation(transformXSize, transformYSize, decoder); + if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform) + { + transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits); + } + numberOfTransformsPresent++; } } @@ -135,14 +140,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Read the Huffman codes (may recurse). - this.ReadHuffmanCodes(decoder, xSize, ySize, colorCacheBits, isLevel0); + this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0); decoder.Metadata.ColorCacheSize = colorCacheSize; - // Finish setting up the color-cache - ColorCache colorCache = null; + // Finish setting up the color-cache. if (colorCachePresent) { - colorCache = new ColorCache(); + decoder.Metadata.ColorCache = new ColorCache(); colorCacheSize = 1 << colorCacheBits; decoder.Metadata.ColorCacheSize = colorCacheSize; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) @@ -150,24 +154,32 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } - colorCache.Init(colorCacheBits); + decoder.Metadata.ColorCache.Init(colorCacheBits); } else { decoder.Metadata.ColorCacheSize = 0; } - this.UpdateDecoder(decoder, xSize, ySize); + this.UpdateDecoder(decoder, transformXSize, transformYSize); + if (isLevel0) + { + // level 0 complete. + return null; + } + // Use the Huffman trees to decode the LZ77 encoded data. IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); - this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache); + this.DecodeImageData(decoder, pixelData.GetSpan()); return pixelData; } - private void DecodePixelValues(Vp8LDecoder decoder, Span pixelData, Buffer2D pixels) + private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels) where TPixel : struct, IPixel { + Span pixelData = decoder.Pixels.GetSpan(); + // Apply reverse transformations, if any are present. this.ApplyInverseTransforms(decoder, pixelData); @@ -189,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodeImageData(Vp8LDecoder decoder, Span pixelData, int colorCacheSize, ColorCache colorCache) + private void DecodeImageData(Vp8LDecoder decoder, Span pixelData) { int lastPixel = 0; int width = decoder.Width; @@ -197,6 +209,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int row = lastPixel / width; int col = lastPixel % width; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + int colorCacheSize = decoder.Metadata.ColorCacheSize; + ColorCache colorCache = decoder.Metadata.ColorCache; int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); @@ -207,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (decodedPixels < totalPixels) { int code; - if ((col & mask) == 0) + if ((col & mask) is 0) { hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); } @@ -621,7 +635,6 @@ namespace SixLabors.ImageSharp.Formats.WebP : (numColors > 4) ? 1 : (numColors > 2) ? 2 : 3; - transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); transform.Bits = bits; using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) { @@ -683,6 +696,208 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + public void DecodeAlphaData(AlphaDecoder dec) + { + Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; + Span data = MemoryMarshal.Cast(pixelData); + int row = 0; + int col = 0; + Vp8LDecoder vp8LDec = dec.Vp8LDec; + int width = vp8LDec.Width; + int height = vp8LDec.Height; + Vp8LMetadata hdr = vp8LDec.Metadata; + int pos = 0; // Current position. + int end = width * height; // End of data. + int last = end; // Last pixel to decode. + int lastRow = height; + int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + int mask = hdr.HuffmanMask; + HTreeGroup[] htreeGroup = (pos < last) ? this.GetHTreeGroupForPos(hdr, col, row) : null; + while (!this.bitReader.Eos && pos < last) + { + // Only update when changing tile. + if ((col & mask) is 0) + { + htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + } + + this.bitReader.FillBitWindow(); + int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); + if (code < WebPConstants.NumLiteralCodes) + { + // Literal + data[pos] = (byte)code; + ++pos; + ++col; + + if (col >= width) + { + col = 0; + ++row; + if (row <= lastRow && (row % NumArgbCacheRows is 0)) + { + this.ExtractPalettedAlphaRows(dec, row); + } + } + } + else if (code < lenCodeLimit) + { + // Backward reference + int lengthSym = code - WebPConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance(distSymbol); + int dist = this.PlaneCodeToDistance(width, distCode); + if (pos >= dist && end - pos >= length) + { + //CopyBlock8b(data + pos, dist, length); + } + else + { + // TODO: error? + break; + } + + pos += length; + col += length; + while (col >= width) + { + col -= width; + ++row; + if (row <= lastRow && (row % NumArgbCacheRows is 0)) + { + this.ExtractPalettedAlphaRows(dec, row); + } + } + + if (pos < last && (col & mask) > 0) + { + htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + } + } + else + { + WebPThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); + } + + this.bitReader.Eos = this.bitReader.IsEndOfStream(); + } + + // Process the remaining rows corresponding to last row-block. + this.ExtractPalettedAlphaRows(dec, row > lastRow ? lastRow : row); + } + + private void ExtractPalettedAlphaRows(AlphaDecoder dec, int lastRow) + { + // For vertical and gradient filtering, we need to decode the part above the + // cropTop row, in order to have the correct spatial predictors. + int topRow = (dec.FilterType is WebPFilterType.None || dec.FilterType is WebPFilterType.Horizontal) + ? dec.CropTop + : dec.LastRow; + int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; + if (lastRow > firstRow) + { + // Special method for paletted alpha data. We only process the cropped area. + Span output = dec.Alpha.AsSpan(); + Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; + Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); + Span dst = output.Slice(dec.Width * firstRow); + Span input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow); + + // TODO: check if any and the correct transform is present + Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; + this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); + //dec.AlphaApplyFilter(firstRow, lastRow, dst, width); + } + + dec.LastRow = lastRow; + } + + private void ColorIndexInverseTransformAlpha( + Vp8LTransform transform, + int yStart, + int yEnd, + Span src, + Span dst) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + Span colorMap = transform.Data.Memory.Span; + int srcOffset = 0; + int dstOffset = 0; + if (bitsPerPixel < 8) + { + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; + for (int y = yStart; y < yEnd; ++y) + { + int packedPixels = 0; + for (int x = 0; x < width; ++x) + { + if ((x & countMask) is 0) + { + packedPixels = src[srcOffset]; + srcOffset++; + } + + dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); + dstOffset++; + packedPixels >>= bitsPerPixel; + } + } + } + else + { + MapAlpha(src, colorMap, dst, yStart, yEnd, width); + } + } + + private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) + { + int offset = 0; + for (int y = yStart; y < yEnd; ++y) + { + for (int x = 0; x < width; ++x) + { + dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); + offset++; + } + } + } + + private static bool Is8bOptimizable(Vp8LMetadata hdr) + { + if (hdr.ColorCacheSize > 0) + { + return false; + } + + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (int i = 0; i < hdr.NumHTreeGroups; ++i) + { + List htrees = hdr.HTreeGroups[i].HTrees; + if (htrees[HuffIndex.Red][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Blue][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Alpha][0].Value > 0) + { + return false; + } + } + + return true; + } + private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) { int numBits = decoder.Metadata.HuffmanSubSampleBits; @@ -752,12 +967,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Decodes the next Huffman code from bit-stream. + /// Decodes the next Huffman code from the bit-stream. /// FillBitWindow(br) needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. /// private uint ReadSymbol(Span table) { - // TODO: if the bitReader field is moved to this base class we could omit the parameter. uint val = (uint)this.bitReader.PrefetchBits(); Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; @@ -832,5 +1046,10 @@ namespace SixLabors.ImageSharp.Formats.WebP huff.Value |= hCode.Value << shift; return hCode.BitsUsed; } + + private static byte GetAlphaValue(int val) + { + return (byte)((val >> 8) & 0xff); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 34ddade1e..9c906a828 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -66,20 +65,19 @@ namespace SixLabors.ImageSharp.Formats.WebP // Decode image data. this.ParseFrame(decoder, io); - byte[] decodedAlpha = null; if (info.Features?.Alpha is true) { - var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData); - - // TODO: use memory allocator. - decodedAlpha = new byte[width * height]; - alphaDecoder.Decode(decoder, decodedAlpha.AsSpan()); + var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator); + alphaDecoder.Decode(); + this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha); + } + else + { + this.DecodePixelValues(width, height, decoder.Pixels, pixels); } - - this.DecodePixelValues(width, height, decoder.Bgr, decodedAlpha, pixels); } - private void DecodePixelValues(int width, int height, Span pixelData, byte[] alpha, Buffer2D pixels) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, byte[] alpha = null) where TPixel : struct, IPixel { TPixel color = default; @@ -628,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int EmitRgb(Vp8Decoder dec, Vp8Io io) { - byte[] buf = dec.Bgr; + byte[] buf = dec.Pixels; int numLinesOut = io.MbH; // a priori guess. Span curY = io.Y; Span curU = io.U; @@ -1359,37 +1357,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return io; } - private static bool Is8bOptimizable(Vp8LMetadata hdr) - { - if (hdr.ColorCacheSize > 0) - { - return false; - } - - // When the Huffman tree contains only one symbol, we can skip the - // call to ReadSymbol() for red/blue/alpha channels. - for (int i = 0; i < hdr.NumHTreeGroups; ++i) - { - List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Blue][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Alpha][0].Value > 0) - { - return false; - } - } - - return true; - } - private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) { nzCoeffs <<= 2; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index a6898093a..1d9c41660 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -169,11 +169,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha4, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e892d87af..32ee475a7 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -491,10 +491,15 @@ namespace SixLabors.ImageSharp.Tests // Lossy images with an alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha2 = "WebP/lossy_alpha2.webp"; - public const string Alpha3 = "WebP/lossy_alpha3.webp"; - public const string Alpha4 = "WebP/lossy_alpha4.webp"; public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; - + public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; + public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp"; + public const string AlphaNoCompressionHorizontalFilter = "WebP/alpha_filter_1_method_0.webp"; + public const string AlphaCompressedHorizontalFilter = "WebP/alpha_filter_1_method_1.webp"; + public const string AlphaNoCompressionVerticalFilter = "WebP/alpha_filter_2_method_0.webp"; + public const string AlphaCompressedVerticalFilter = "WebP/alpha_filter_2_method_1.webp"; + public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp"; + public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; } } } From a75bb568ea3c9219f38fa375ff3d2dc3a925f44e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Mar 2020 12:11:10 +0100 Subject: [PATCH 133/359] Add filtering for uncompressed alpha --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 90 ++++++++++++++++--- .../Formats/WebP/WebPDecoderTests.cs | 2 + 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index bf382c425..cf92e3876 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -95,18 +95,33 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } - switch (this.FilterType) + if (this.FilterType == WebPFilterType.None) { - case WebPFilterType.None: - this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); - break; - case WebPFilterType.Horizontal: - - break; - case WebPFilterType.Vertical: - break; - case WebPFilterType.Gradient: - break; + this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); + return; + } + + Span deltas = this.Data.AsSpan(); + Span dst = this.Alpha.AsSpan(); + Span prev = null; + for (int y = 0; y < this.Height; ++y) + { + switch (this.FilterType) + { + case WebPFilterType.Horizontal: + HorizontalUnfilter(prev, deltas, dst, this.Width); + break; + case WebPFilterType.Vertical: + VerticalUnfilter(prev, deltas, dst, this.Width); + break; + case WebPFilterType.Gradient: + GradientUnfilter(prev, deltas, dst, this.Width); + break; + } + + prev = dst; + deltas = deltas.Slice(this.Width); + dst = dst.Slice(this.Width); } } else @@ -115,6 +130,59 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) + { + byte pred = (byte)(prev == null ? 0 : prev[0]); + + for (int i = 0; i < width; ++i) + { + dst[i] = (byte)(pred + input[i]); + pred = dst[i]; + } + } + + private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev == null) + { + HorizontalUnfilter(null, input, dst, width); + } + else + { + for (int i = 0; i < width; ++i) + { + dst[i] = (byte)(prev[i] + input[i]); + } + } + } + + private static void GradientUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev == null) + { + HorizontalUnfilter(null, input, dst, width); + } + else + { + byte top = prev[0]; + byte topLeft = top; + byte left = top; + for (int i = 0; i < width; ++i) + { + top = prev[i]; + left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + dst[i] = left; + } + } + } + + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b - c; + return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit + } + // Taken from vp8l_dec.c AlphaApplyFilter public void AlphaApplyFilter( int firstRow, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 1d9c41660..5041f35ae 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -173,6 +173,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { From 08c4278ba4cec5265c570865092489381f90c43b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Mar 2020 13:19:08 +0100 Subject: [PATCH 134/359] Add filtering for compressed alpha --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 233 ++++++++++++------ .../Formats/WebP/Filters/WebPFilterBase.cs | 104 -------- .../WebP/Filters/WebPFilterGradient.cs | 116 --------- .../WebP/Filters/WebPFilterHorizontal.cs | 115 --------- .../Formats/WebP/Filters/WebPFilterNone.cs | 33 --- .../Formats/WebP/Filters/WebPFilterType.cs | 16 -- .../WebP/Filters/WebPFilterVertical.cs | 100 -------- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 9 +- src/ImageSharp/Formats/WebP/Vp8Io.cs | 11 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 24 +- src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 2 +- .../Formats/WebP/WebPAlphaFilterType.cs | 31 +++ .../Formats/WebP/WebPLosslessDecoder.cs | 46 +--- .../Formats/WebP/WebPLossyDecoder.cs | 45 ++-- .../Formats/WebP/WebPDecoderTests.cs | 3 + 15 files changed, 250 insertions(+), 638 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs delete mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index cf92e3876..8a276cebc 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -2,45 +2,26 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; +using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - internal class AlphaDecoder + /// + /// Implements decoding for lossy alpha chunks which may be compressed. + /// + internal class AlphaDecoder : IDisposable { - public int Width { get; } - - public int Height { get; } - - public WebPFilterBase Filter { get; } - - public WebPFilterType FilterType { get; } - - public int CropTop { get; } - - public int LastRow { get; set; } - - public Vp8LDecoder Vp8LDec { get; } - - public byte[] Alpha { get; } - - private int PreProcessing { get; } - - private bool Compressed { get; } - - private byte[] Data { get; } - - private WebPLosslessDecoder LosslessDecoder { get; } - /// - /// Although Alpha Channel requires only 1 byte per pixel, - /// sometimes Vp8LDecoder may need to allocate - /// 4 bytes per pixel internally during decode. + /// Initializes a new instance of the class. /// - public bool Use8BDecode { get; set; } - + /// The width of the image. + /// The height of the image. + /// The (maybe compressed) alpha data. + /// The first byte of the alpha image stream contains information on ow to decode the stream. + /// Used for allocating memory during decoding. public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator) { this.Width = width; @@ -64,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } - this.FilterType = (WebPFilterType)filter; + this.AlphaFilterType = (WebPAlphaFilterType)filter; // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. @@ -73,8 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); - // TODO: use memory allocator - this.Alpha = new byte[width * height]; + this.Alpha = memoryAllocator.Allocate(width * height); if (this.Compressed) { @@ -84,8 +64,74 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private int PrevLineOffset { get; set; } + /// + /// Gets the the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets the used filter type. + /// + public WebPAlphaFilterType AlphaFilterType { get; } + + /// + /// Gets or sets the last decoded row. + /// + public int LastRow { get; set; } + + /// + /// Gets or sets the row before the last decoded row. + /// + public int PrevRow { get; set; } + + /// + /// Gets information for decoding Vp8L compressed alpha data. + /// + public Vp8LDecoder Vp8LDec { get; } + + /// + /// Gets the decoded alpha data. + /// + public IMemoryOwner Alpha { get; } + + public int CropTop { get; } + + /// + /// Gets a value indicating whether pre-processing was used during compression. + /// 0: no pre-processing, 1: level reduction. + /// + private int PreProcessing { get; } + + /// + /// Gets a value indicating whether the alpha channel uses compression. + /// + private bool Compressed { get; } + + /// + /// Gets the (maybe compressed) alpha data. + /// + private byte[] Data { get; } + + /// + /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. + /// + private WebPLosslessDecoder LosslessDecoder { get; } + + /// + /// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding. + /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate + /// 4 bytes per pixel internally during decode. + /// + public bool Use8BDecode { get; set; } + /// + /// Decodes and filters the maybe compressed alpha data. + /// public void Decode() { if (this.Compressed is false) @@ -95,26 +141,27 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } - if (this.FilterType == WebPFilterType.None) + Span alphaSpan = this.Alpha.Memory.Span; + if (this.AlphaFilterType == WebPAlphaFilterType.None) { - this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); + this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan); return; } Span deltas = this.Data.AsSpan(); - Span dst = this.Alpha.AsSpan(); + Span dst = alphaSpan; Span prev = null; for (int y = 0; y < this.Height; ++y) { - switch (this.FilterType) + switch (this.AlphaFilterType) { - case WebPFilterType.Horizontal: + case WebPAlphaFilterType.Horizontal: HorizontalUnfilter(prev, deltas, dst, this.Width); break; - case WebPFilterType.Vertical: + case WebPAlphaFilterType.Vertical: VerticalUnfilter(prev, deltas, dst, this.Width); break; - case WebPFilterType.Gradient: + case WebPAlphaFilterType.Gradient: GradientUnfilter(prev, deltas, dst, this.Width); break; } @@ -130,6 +177,44 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Applies filtering to a set of rows. + /// + /// The first row index to start filtering. + /// The last row index for filtering. + /// The destination to store the filtered data. + /// The stride to use. + public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) + { + if (this.AlphaFilterType is WebPAlphaFilterType.None) + { + return; + } + + Span alphaSpan = this.Alpha.Memory.Span; + Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); + for (int y = firstRow; y < lastRow; ++y) + { + switch (this.AlphaFilterType) + { + case WebPAlphaFilterType.Horizontal: + HorizontalUnfilter(prev, dst, dst, this.Width); + break; + case WebPAlphaFilterType.Vertical: + VerticalUnfilter(prev, dst, dst, this.Width); + break; + case WebPAlphaFilterType.Gradient: + GradientUnfilter(prev, dst, dst, this.Width); + break; + } + + prev = dst; + dst = dst.Slice(stride); + } + + this.PrevRow = lastRow - 1; + } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { byte pred = (byte)(prev == null ? 0 : prev[0]); @@ -177,42 +262,48 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static bool Is8bOptimizable(Vp8LMetadata hdr) + { + if (hdr.ColorCacheSize > 0) + { + return false; + } + + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (int i = 0; i < hdr.NumHTreeGroups; ++i) + { + List htrees = hdr.HTreeGroups[i].HTrees; + if (htrees[HuffIndex.Red][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Blue][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Alpha][0].Value > 0) + { + return false; + } + } + + return true; + } + private static int GradientPredictor(byte a, byte b, byte c) { int g = a + b - c; return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit } - // Taken from vp8l_dec.c AlphaApplyFilter - public void AlphaApplyFilter( - int firstRow, - int lastRow, - Span prevLine, - Span output, - int outputOffset, - int stride) + /// + public void Dispose() { - if (this.Filter is WebPFilterNone) - { - return; - } - - int prevLineOffset = this.PrevLineOffset; - - for (int y = firstRow; y < lastRow; y++) - { - this.Filter - .Unfilter( - prevLine, - prevLineOffset, - output, - outputOffset, - output, - outputOffset, - stride); - prevLineOffset = outputOffset; - outputOffset += stride; - } + this.Vp8LDec?.Dispose(); + this.Alpha?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs deleted file mode 100644 index 74b69f43a..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - // TODO from dsp.h - // public enum WebPFilterType - // { - // None = 0, - // Horizontal, - // Vertical, - // Gradient, - // Last = Gradient + 1, // end marker - // Best, // meta types - // Fast - // } - - internal abstract class WebPFilterBase - { - /// - /// - /// - /// nullable as prevLine is nullable in the original but Span'T can't be null. - /// - /// - /// - /// - /// - public abstract void Unfilter( - Span prevLine, - int? prevLineOffset, - Span preds, - int predsOffset, - Span currentLine, - int currentLineOffset, - int width); - - public abstract void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset); - - protected static void SanityCheck( - Span input, Span output, int width, int numRows, int height, int stride, int row) - { - Debug.Assert(input != null); - Debug.Assert(output != null); - Debug.Assert(width > 0); - Debug.Assert(height > 0); - Debug.Assert(stride > width); - Debug.Assert(row >= 0); - Debug.Assert(height > 0); - Debug.Assert(row + numRows <= height); - } - - protected static void PredictLine( - Span src, - int srcOffset, - Span pred, - int predOffset, - Span dst, - int dstOffset, - int length, - bool inverse) - { - if (inverse) - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] + pred[i]); - } - } - else - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] - pred[i]); - } - } - } - - protected void UnfilterHorizontalOrVerticalCore( - byte pred, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); - pred = output[i]; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs deleted file mode 100644 index 27bca5770..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - class WebPFilterGradient : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffsetNullable, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - byte top = prevLine[prevLineOffset]; - byte topLeft = top; - byte left = top; - for (int i = 0; i < width; i++) - { - top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out - left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); - topLeft = top; - output[outputOffset + i] = left; - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - // calling (input, width, height, stride, 0, height, 0, output - int row = 0; - int numRows = height; - bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + numRows; - SanityCheck(input, output, width, numRows, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - output[outputOffset] = input[inputOffset]; - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - } - - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset - stride, - output, - outputOffset, - 1, - inverse); - - for (int w = 1; w < width; w++) - { - int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); - int signedPred = inverse ? pred : -pred; - output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); - } - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - - private static int GradientPredictor(byte a, byte b, byte c) - { - int g = a + b + c; - return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs deleted file mode 100644 index fdec37e5d..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - internal class WebPFilterHorizontal : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffsetNullable, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - byte pred = prevLineOffsetNullable is int prevLineOffset - ? prevLine[prevLineOffset] - : (byte)0; - - this.UnfilterHorizontalOrVerticalCore( - pred, - input, - inputOffset, - output, - outputOffset, - width); - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - int numRows = height; - int row = 0; - - const bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, numRows, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row is 0) - { - // leftmost pixel is the same as Input for topmost scanline - output[0] = input[0]; - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - - row = 1; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - - // Filter line by line. - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset - stride, - output, - 0, - 1, - inverse); - PredictLine( - input, - inputOffset, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs deleted file mode 100644 index 04dfafe24..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - // TODO: check if this is a filter or just a placeholder from the C implementation details - class WebPFilterNone : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffset, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs deleted file mode 100644 index 4b79ea5f5..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - internal enum WebPFilterType - { - None = 0, - - Horizontal = 1, - - Vertical = 2, - - Gradient = 3, - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs deleted file mode 100644 index 04eb2a587..000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - class WebPFilterVertical : WebPFilterBase - { - public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - int row = 0; - bool inverse = false; - - // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - // Very first top-left pixel is copied. - output[0] = input[0]; - - // Rest of top scan-line is left-predicted: - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - row = 1; - inputOffset += stride; - outputOffset += stride; - } - else - { - predsOffset -= stride; - } - - // Filter line-by-line. - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset, - output, - outputOffset, - width, - inverse); - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 21835c3d4..e004d86f2 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int bits; /// - /// Max packed-read position on buffer. + /// Max packed-read position of the buffer. /// private uint bufferMax; @@ -54,6 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The input stream to read from. /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. + /// The partition length. /// Start index in the data array. Defaults to 0. public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) { @@ -63,6 +64,12 @@ namespace SixLabors.ImageSharp.Formats.WebP this.InitBitreader(partitionLength, startPos); } + /// + /// Initializes a new instance of the class. + /// + /// The raw encoded image data. + /// The partition length. + /// Start index in the data array. Defaults to 0. public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0) { this.Data = imageData; diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index 82a8cbba8..36b561847 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Formats.WebP public int MbH { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the luma component. /// public Span Y { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the U chroma component. /// public Span U { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the V chroma component. /// public Span V { get; set; } @@ -76,10 +76,5 @@ namespace SixLabors.ImageSharp.Formats.WebP public int ScaledWidth { get; set; } public int ScaledHeight { get; set; } - - /// - /// User data - /// - private object Opaque { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index f4114f5be..38c5ffb64 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -8,7 +8,7 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// A bit reader for VP8L streams. + /// A bit reader for reading lossless webp streams. /// internal class Vp8LBitReader : BitReaderBase { @@ -20,12 +20,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Number of bits prefetched. /// - private const int Vp8LLbits = 64; + private const int Lbits = 64; /// /// Minimum number of bytes ready after VP8LFillBitWindow. /// - private const int Vp8LWbits = 32; + private const int Wbits = 32; private readonly uint[] bitMask = { @@ -58,11 +58,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private int bitPos; - /// - /// True if a bit was read past the end of buffer. - /// - public bool Eos; - /// /// Initializes a new instance of the class. /// @@ -117,6 +112,11 @@ namespace SixLabors.ImageSharp.Formats.WebP this.pos = length; } + /// + /// Gets or sets a value indicating whether a bit was read past the end of buffer. + /// + public bool Eos { get; set; } + /// /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. /// @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The pre-fetched bits. public ulong PrefetchBits() { - return this.value >> (this.bitPos & (Vp8LLbits - 1)); + return this.value >> (this.bitPos & (Lbits - 1)); } /// @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public void FillBitWindow() { - if (this.bitPos >= Vp8LWbits) + if (this.bitPos >= Wbits) { this.DoFillBitWindow(); } @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// True, if end of buffer was reached. public bool IsEndOfStream() { - return this.Eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); + return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); } private void DoFillBitWindow() @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; - this.value |= (ulong)this.Data[this.pos] << (Vp8LLbits - 8); + this.value |= (ulong)this.Data[this.pos] << (Lbits - 8); ++this.pos; this.bitPos -= 8; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index 968d98b9d..69b3c3ed7 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public IMemoryOwner Pixels { get; } - /// + /// public void Dispose() { this.Pixels.Dispose(); diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs new file mode 100644 index 000000000..dfdc8281c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Enum for the different alpha filter types. + /// + internal enum WebPAlphaFilterType + { + /// + /// No filtering. + /// + None = 0, + + /// + /// Horizontal filter. + /// + Horizontal = 1, + + /// + /// Vertical filter. + /// + Vertical = 2, + + /// + /// Gradient filter. + /// + Gradient = 3, + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index b64cc0a17..6ca54690f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -23,6 +22,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal sealed class WebPLosslessDecoder { + /// + /// A bit reader for reading lossless webp streams. + /// private readonly Vp8LBitReader bitReader; private static readonly int BitsSpecialMarker = 0x100; @@ -751,12 +753,11 @@ namespace SixLabors.ImageSharp.Formats.WebP int dist = this.PlaneCodeToDistance(width, distCode); if (pos >= dist && end - pos >= length) { - //CopyBlock8b(data + pos, dist, length); + data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); } else { - // TODO: error? - break; + WebPThrowHelper.ThrowImageFormatException("error while decoding alpha data"); } pos += length; @@ -792,14 +793,14 @@ namespace SixLabors.ImageSharp.Formats.WebP { // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. - int topRow = (dec.FilterType is WebPFilterType.None || dec.FilterType is WebPFilterType.Horizontal) + int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal) ? dec.CropTop : dec.LastRow; int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { // Special method for paletted alpha data. We only process the cropped area. - Span output = dec.Alpha.AsSpan(); + Span output = dec.Alpha.Memory.Span; Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); Span dst = output.Slice(dec.Width * firstRow); @@ -808,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: check if any and the correct transform is present Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); - //dec.AlphaApplyFilter(firstRow, lastRow, dst, width); + dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); } dec.LastRow = lastRow; @@ -867,37 +868,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static bool Is8bOptimizable(Vp8LMetadata hdr) - { - if (hdr.ColorCacheSize > 0) - { - return false; - } - - // When the Huffman tree contains only one symbol, we can skip the - // call to ReadSymbol() for red/blue/alpha channels. - for (int i = 0; i < hdr.NumHTreeGroups; ++i) - { - List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Blue][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Alpha][0].Value > 0) - { - return false; - } - } - - return true; - } - private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) { int numBits = decoder.Metadata.HuffmanSubSampleBits; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 9c906a828..2a2effd92 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Linq; using System.Runtime.InteropServices; @@ -13,10 +14,21 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal sealed class WebPLossyDecoder { + /// + /// A bit reader for reading lossy webp streams. + /// private readonly Vp8BitReader bitReader; + /// + /// Used for allocating memory during processing operations. + /// private readonly MemoryAllocator memoryAllocator; + /// + /// Initializes a new instance of the class. + /// + /// Bitreader to read from the stream. + /// Used for allocating memory during processing operations. public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; @@ -77,11 +89,18 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, byte[] alpha = null) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha = null) where TPixel : struct, IPixel { TPixel color = default; - bool hasAlpha = alpha != null; + bool hasAlpha = false; + Span alphaSpan = null; + if (alpha != null) + { + hasAlpha = true; + alphaSpan = alpha.Memory.Span; + } + for (int y = 0; y < height; y++) { Span pixelRow = pixels.GetRowSpan(y); @@ -96,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: use bulk conversion here. if (hasAlpha) { - byte a = alpha[offset]; + byte a = alphaSpan[offset]; color.FromBgra32(new Bgra32(r, g, b, a)); } else @@ -781,26 +800,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private Vp8Profile DecodeProfile(int version) - { - switch (version) - { - case 0: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Complex }; - case 1: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.Simple }; - case 2: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.None }; - case 3: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.None, LoopFilter = LoopFilter.None }; - default: - // Reserved for future use in Spec. - // https://tools.ietf.org/html/rfc6386#page-30 - WebPThrowHelper.ThrowNotSupportedException($"unsupported VP8 version {version} found"); - return new Vp8Profile(); - } - } - private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) { Vp8MacroBlock left = dec.LeftMacroBlock; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 5041f35ae..80362ec72 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -175,6 +175,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { From 7ee79f594c1a5f1c7b221a6c9a045eac9bccd562 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2020 13:16:44 +0100 Subject: [PATCH 135/359] Fix decoding issues - Lossless: Check if transforms are present on dispose - Lossy: Set filterinfo properly --- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 24 ++++++++++++++++++- src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 11 +++++---- .../Formats/WebP/WebPLossyDecoder.cs | 13 ++++++---- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index a95e0ba92..760e1b5c5 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -6,8 +6,27 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Filter information. /// - internal class Vp8FilterInfo + internal class Vp8FilterInfo : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public Vp8FilterInfo() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The filter info to create an instance from. + public Vp8FilterInfo(Vp8FilterInfo other) + { + this.Limit = other.Limit; + this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold; + this.InnerLevel = other.InnerLevel; + this.UseInnerFiltering = other.UseInnerFiltering; + } + /// /// Gets or sets the filter limit in [3..189], or 0 if no filtering. /// @@ -28,5 +47,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets the high edge variance threshold in [0..2]. ///
public byte HighEdgeVarianceThreshold { get; set; } + + /// + public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index 69b3c3ed7..162106f8e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -57,12 +57,15 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Dispose() { this.Pixels.Dispose(); - foreach (Vp8LTransform transform in this.Transforms) + this.Metadata?.HuffmanImage?.Dispose(); + + if (this.Transforms != null) { - transform.Data?.Dispose(); + foreach (Vp8LTransform transform in this.Transforms) + { + transform.Data?.Dispose(); + } } - - this.Metadata?.HuffmanImage?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 2a2effd92..8b9a3569c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -79,9 +79,11 @@ namespace SixLabors.ImageSharp.Formats.WebP if (info.Features?.Alpha is true) { - var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator); - alphaDecoder.Decode(); - this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha); + using (var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator)) + { + alphaDecoder.Decode(); + this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha); + } } else { @@ -100,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.WebP hasAlpha = true; alphaSpan = alpha.Memory.Span; } - + for (int y = 0; y < height; y++) { Span pixelRow = pixels.GetRowSpan(y); @@ -826,7 +828,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // Store filter info. if (dec.Filter != LoopFilter.None) { - dec.FilterInfo[dec.MbX] = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; + Vp8FilterInfo precomputedFilterInfo = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; + dec.FilterInfo[dec.MbX] = (Vp8FilterInfo)precomputedFilterInfo.DeepClone(); dec.FilterInfo[dec.MbX].UseInnerFiltering |= (byte)(skip is 0 ? 1 : 0); } } From fa0fa9ef5f683921a867833e71ea52414af97be8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2020 18:28:15 +0100 Subject: [PATCH 136/359] Vp8Decoder reserves only needed memory --- src/ImageSharp/Formats/WebP/LoopFilter.cs | 12 ++++++ src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 48 +++++++++++++++++++---- src/ImageSharp/Formats/WebP/Vp8Io.cs | 4 +- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs index e48d89cea..8330ca593 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -3,12 +3,24 @@ namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// Enum for the different loop filters used. VP8 supports two types of loop filters. + /// internal enum LoopFilter { + /// + /// No filter is used. + /// None = 0, + /// + /// Simple loop filter. + /// Simple = 1, + /// + /// Complex loop filter. + /// Complex = 2, } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 8de3144f6..91a508b96 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp.Formats.WebP { private Vp8MacroBlock leftMacroBlock; + /// + /// Initializes a new instance of the class. + /// + /// The frame header. + /// The picture header. + /// The segment header. + /// The probabilities. public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities) { this.FilterHeader = new Vp8FilterHeader(); @@ -18,7 +25,6 @@ namespace SixLabors.ImageSharp.Formats.WebP this.SegmentHeader = segmentHeader; this.Probabilities = probabilities; this.IntraL = new byte[4]; - this.YuvBuffer = new byte[2000]; // new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); this.CacheYStride = 16 * this.MbWidth; @@ -52,15 +58,16 @@ namespace SixLabors.ImageSharp.Formats.WebP uint height = pictureHeader.Height; // TODO: use memory allocator - int extraRows = WebPConstants.FilterExtraRows[2]; // TODO: assuming worst case: complex filter + int extraRows = WebPConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = (extraRows / 2) * this.CacheUvStride; - this.CacheY = new byte[(width * height) + extraY + 256]; // TODO: this is way too much mem, figure out what the min req is. - this.CacheU = new byte[(width * height) + extraUv + 256]; - this.CacheV = new byte[(width * height) + extraUv + 256]; - this.TmpYBuffer = new byte[(width * height) + extraY]; // TODO: figure out min buffer length - this.TmpUBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length - this.TmpVBuffer = new byte[(width * height) + extraUv]; // TODO: figure out min buffer length + this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY]; + this.CacheY = new byte[(16 * this.CacheYStride) + extraY]; + this.CacheU = new byte[(16 * this.CacheUvStride) + extraUv]; + this.CacheV = new byte[(16 * this.CacheUvStride) + extraUv]; + this.TmpYBuffer = new byte[width]; + this.TmpUBuffer = new byte[width]; + this.TmpVBuffer = new byte[width]; this.Pixels = new byte[width * height * 4]; for (int i = 0; i < this.YuvBuffer.Length; i++) @@ -82,12 +89,24 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; } + /// + /// Gets the frame header. + /// public Vp8FrameHeader FrameHeader { get; } + /// + /// Gets the picture header. + /// public Vp8PictureHeader PictureHeader { get; } + /// + /// Gets the filter header. + /// public Vp8FilterHeader FilterHeader { get; } + /// + /// Gets the segment header. + /// public Vp8SegmentHeader SegmentHeader { get; } /// @@ -110,8 +129,14 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool UseSkipProbability { get; set; } + /// + /// Gets or sets the skip probability. + /// public byte SkipProbability { get; set; } + /// + /// Gets or sets the Probabilities. + /// public Vp8Proba Probabilities { get; set; } /// @@ -174,8 +199,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8MacroBlock[] MacroBlockInfo { get; } + /// + /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce) + /// visually objectionable artifacts. + /// public LoopFilter Filter { get; set; } + /// + /// Gets or sets the filter strengths. + /// public Vp8FilterInfo[,] FilterStrength { get; } public byte[] YuvBuffer { get; } diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index 36b561847..feb763129 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -27,12 +27,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public int MbY { get; set; } /// - /// Gets or sets the macroblock width. + /// Gets or sets number of columns in the sample. /// public int MbW { get; set; } /// - /// Gets or sets the macroblock height. + /// Gets or sets number of rows in the sample. /// public int MbH { get; set; } From c0bbd0631e76d5bec285dddb5aaca2b0b36cc2ad Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2020 20:18:15 +0100 Subject: [PATCH 137/359] Vp8Decoder now uses memory allocator --- src/ImageSharp/Formats/WebP/LossyUtils.cs | 92 +++++------ src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 82 +++++----- .../Formats/WebP/WebPLossyDecoder.cs | 151 +++++++++--------- 3 files changed, 171 insertions(+), 154 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index a8f6eb98d..7222cfa7b 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC16(Span dst, byte[] yuv, int offset) + public static void DC16(Span dst, Span yuv, int offset) { int dc = 16; for (int j = 0; j < 16; ++j) @@ -28,15 +28,15 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 5, dst); } - public static void TM16(Span dst, byte[] yuv, int offset) + public static void TM16(Span dst, Span yuv, int offset) { TrueMotion(dst, yuv, offset, 16); } - public static void VE16(Span dst, byte[] yuv, int offset) + public static void VE16(Span dst, Span yuv, int offset) { // vertical - Span src = yuv.AsSpan(offset - WebPConstants.Bps, 16); + Span src = yuv.Slice(offset - WebPConstants.Bps, 16); for (int j = 0; j < 16; ++j) { // memcpy(dst + j * BPS, dst - BPS, 16); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE16(Span dst, byte[] yuv, int offset) + public static void HE16(Span dst, Span yuv, int offset) { // horizontal for (int j = 16; j > 0; --j) @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC16NoTop(Span dst, byte[] yuv, int offset) + public static void DC16NoTop(Span dst, Span yuv, int offset) { // DC with top samples not available. int dc = 8; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 4, dst); } - public static void DC16NoLeft(Span dst, byte[] yuv, int offset) + public static void DC16NoLeft(Span dst, Span yuv, int offset) { // DC with left samples not available. int dc = 8; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(0x80, dst); } - public static void DC8uv(Span dst, byte[] yuv, int offset) + public static void DC8uv(Span dst, Span yuv, int offset) { int dc0 = 8; for (int i = 0; i < 8; ++i) @@ -101,16 +101,16 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 4), dst); } - public static void TM8uv(Span dst, byte[] yuv, int offset) + public static void TM8uv(Span dst, Span yuv, int offset) { // TrueMotion TrueMotion(dst, yuv, offset, 8); } - public static void VE8uv(Span dst, byte[] yuv, int offset) + public static void VE8uv(Span dst, Span yuv, int offset) { // vertical - Span src = yuv.AsSpan(offset - WebPConstants.Bps, 8); + Span src = yuv.Slice(offset - WebPConstants.Bps, 8); for (int j = 0; j < 8; ++j) { @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE8uv(Span dst, byte[] yuv, int offset) + public static void HE8uv(Span dst, Span yuv, int offset) { // horizontal for (int j = 0; j < 8; ++j) @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void DC8uvNoTop(Span dst, byte[] yuv, int offset) + public static void DC8uvNoTop(Span dst, Span yuv, int offset) { // DC with no top samples. int dc0 = 4; @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } - public static void DC8uvNoLeft(Span dst, byte[] yuv, int offset) + public static void DC8uvNoLeft(Span dst, Span yuv, int offset) { // DC with no left samples. int dc0 = 4; @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv(0x80, dst); } - public static void DC4(Span dst, byte[] yuv, int offset) + public static void DC4(Span dst, Span yuv, int offset) { int dc = 4; for (int i = 0; i < 4; ++i) @@ -180,12 +180,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void TM4(Span dst, byte[] yuv, int offset) + public static void TM4(Span dst, Span yuv, int offset) { TrueMotion(dst, yuv, offset, 4); } - public static void VE4(Span dst, byte[] yuv, int offset) + public static void VE4(Span dst, Span yuv, int offset) { // vertical int topOffset = offset - WebPConstants.Bps; @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HE4(Span dst, byte[] yuv, int offset) + public static void HE4(Span dst, Span yuv, int offset) { // horizontal byte a = yuv[offset - 1 - WebPConstants.Bps]; @@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.WebP BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); } - public static void RD4(Span dst, byte[] yuv, int offset) + public static void RD4(Span dst, Span yuv, int offset) { // Down-right byte i = yuv[offset - 1]; @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 3, 0, Avg3(d, c, b)); } - public static void VR4(Span dst, byte[] yuv, int offset) + public static void VR4(Span dst, Span yuv, int offset) { // Vertical-Right byte i = yuv[offset - 1]; @@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 3, 1, Avg3(b, c, d)); } - public static void LD4(Span dst, byte[] yuv, int offset) + public static void LD4(Span dst, Span yuv, int offset) { // Down-Left byte a = yuv[offset - WebPConstants.Bps]; @@ -328,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 3, 3, Avg3(g, h, h)); } - public static void VL4(Span dst, byte[] yuv, int offset) + public static void VL4(Span dst, Span yuv, int offset) { // Vertical-Left byte a = yuv[offset - WebPConstants.Bps]; @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 3, 3, Avg3(f, g, h)); } - public static void HD4(Span dst, byte[] yuv, int offset) + public static void HD4(Span dst, Span yuv, int offset) { // Horizontal-Down byte i = yuv[offset - 1]; @@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 1, 3, Avg3(l, k, j)); } - public static void HU4(Span dst, byte[] yuv, int offset) + public static void HU4(Span dst, Span yuv, int offset) { // Horizontal-Up byte i = yuv[offset - 1]; @@ -537,11 +537,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void TrueMotion(Span dst, byte[] yuv, int offset, int size) + private static void TrueMotion(Span dst, Span yuv, int offset, int size) { // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. int topOffset = offset - WebPConstants.Bps; - Span top = yuv.AsSpan(topOffset); + Span top = yuv.Slice(topOffset); byte p = yuv[topOffset - 1]; int leftOffset = offset - 1; byte left = yuv[leftOffset]; @@ -559,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Simple In-loop filtering (Paragraph 15.2) - public static void SimpleVFilter16(byte[] p, int offset, int stride, int thresh) + public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) { int thresh2 = (2 * thresh) + 1; for (int i = 0; i < 16; ++i) @@ -571,7 +571,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void SimpleHFilter16(byte[] p, int offset, int stride, int thresh) + public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) { int thresh2 = (2 * thresh) + 1; for (int i = 0; i < 16; ++i) @@ -583,7 +583,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void SimpleVFilter16i(byte[] p, int offset, int stride, int thresh) + public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) { for (int k = 3; k > 0; --k) { @@ -592,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void SimpleHFilter16i(byte[] p, int offset, int stride, int thresh) + public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) { for (int k = 3; k > 0; --k) { @@ -601,17 +601,17 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void VFilter16(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); } - public static void HFilter16(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); } - public static void VFilter16i(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { for (int k = 3; k > 0; --k) { @@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void HFilter16i(byte[] p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { for (int k = 3; k > 0; --k) { @@ -630,25 +630,25 @@ namespace SixLabors.ImageSharp.Formats.WebP } // 8-pixels wide variant, for chroma filtering. - public static void VFilter8(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); } - public static void HFilter8(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); } - public static void VFilter8i(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); } - public static void HFilter8i(byte[] u, byte[] v, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); @@ -684,7 +684,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( - byte[] p, + Span p, int offset, int hStride, int vStride, @@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } private static void FilterLoop26( - byte[] p, + Span p, int offset, int hStride, int vStride, @@ -741,7 +741,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void DoFilter2(byte[] p, int offset, int step) + private static void DoFilter2(Span p, int offset, int step) { // 4 pixels in, 2 pixels out. int p1 = p[offset - (2 * step)]; @@ -755,7 +755,7 @@ namespace SixLabors.ImageSharp.Formats.WebP p[offset] = WebPLookupTables.Clip1[q0 - a1]; } - private static void DoFilter4(byte[] p, int offset, int step) + private static void DoFilter4(Span p, int offset, int step) { // 4 pixels in, 4 pixels out. int p1 = p[offset - (2 * step)]; @@ -772,7 +772,7 @@ namespace SixLabors.ImageSharp.Formats.WebP p[offset + step] = WebPLookupTables.Clip1[q1 - a3]; } - private static void DoFilter6(byte[] p, int offset, int step) + private static void DoFilter6(Span p, int offset, int step) { // 6 pixels in, 6 pixels out. int p2 = p[offset - (3 * step)]; @@ -795,7 +795,7 @@ namespace SixLabors.ImageSharp.Formats.WebP p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3]; } - private static bool NeedsFilter(byte[] p, int offset, int step, int t) + private static bool NeedsFilter(Span p, int offset, int step, int t) { int p1 = p[offset + (-2 * step)]; int p0 = p[offset - step]; @@ -804,7 +804,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return ((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) <= t; } - private static bool NeedsFilter2(byte[] p, int offset, int step, int t, int it) + private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) { int p3 = p[offset - (4 * step)]; int p2 = p[offset - (3 * step)]; @@ -824,7 +824,7 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it; } - private static bool Hev(byte[] p, int offset, int step, int thresh) + private static bool Hev(Span p, int offset, int step, int thresh) { int p1 = p[offset - (2 * step)]; int p0 = p[offset - step]; diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 91a508b96..584f24b89 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -1,12 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; + +using SixLabors.Memory; + namespace SixLabors.ImageSharp.Formats.WebP { /// /// Holds information for decoding a lossy webp image. /// - internal class Vp8Decoder + internal class Vp8Decoder : IDisposable { private Vp8MacroBlock leftMacroBlock; @@ -17,7 +22,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The picture header. /// The segment header. /// The probabilities. - public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities) + /// Used for allocating memory for the pixel data output and the temporary buffers. + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator) { this.FilterHeader = new Vp8FilterHeader(); this.FrameHeader = frameHeader; @@ -57,34 +63,22 @@ namespace SixLabors.ImageSharp.Formats.WebP uint width = pictureHeader.Width; uint height = pictureHeader.Height; - // TODO: use memory allocator int extraRows = WebPConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = (extraRows / 2) * this.CacheUvStride; - this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY]; - this.CacheY = new byte[(16 * this.CacheYStride) + extraY]; - this.CacheU = new byte[(16 * this.CacheUvStride) + extraUv]; - this.CacheV = new byte[(16 * this.CacheUvStride) + extraUv]; - this.TmpYBuffer = new byte[width]; - this.TmpUBuffer = new byte[width]; - this.TmpVBuffer = new byte[width]; - this.Pixels = new byte[width * height * 4]; - - for (int i = 0; i < this.YuvBuffer.Length; i++) - { - this.YuvBuffer[i] = 205; - } - - for (int i = 0; i < this.CacheY.Length; i++) - { - this.CacheY[i] = 205; - } - - for (int i = 0; i < this.CacheU.Length; i++) - { - this.CacheU[i] = 205; - this.CacheV[i] = 205; - } + this.YuvBuffer = memoryAllocator.Allocate((WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY); + this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); + this.CacheU = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv); + this.CacheV = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv); + this.TmpYBuffer = memoryAllocator.Allocate((int)width); + this.TmpUBuffer = memoryAllocator.Allocate((int)width); + this.TmpVBuffer = memoryAllocator.Allocate((int)width); + this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); + + this.YuvBuffer.Memory.Span.Fill(205); + this.CacheY.Memory.Span.Fill(205); + this.CacheU.Memory.Span.Fill(205); + this.CacheV.Memory.Span.Fill(205); this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; } @@ -206,19 +200,19 @@ namespace SixLabors.ImageSharp.Formats.WebP public LoopFilter Filter { get; set; } /// - /// Gets or sets the filter strengths. + /// Gets the filter strengths. /// public Vp8FilterInfo[,] FilterStrength { get; } - public byte[] YuvBuffer { get; } + public IMemoryOwner YuvBuffer { get; } public Vp8TopSamples[] YuvTopSamples { get; } - public byte[] CacheY { get; } + public IMemoryOwner CacheY { get; } - public byte[] CacheU { get; } + public IMemoryOwner CacheU { get; } - public byte[] CacheV { get; } + public IMemoryOwner CacheV { get; } public int CacheYOffset { get; set; } @@ -228,13 +222,16 @@ namespace SixLabors.ImageSharp.Formats.WebP public int CacheUvStride { get; } - public byte[] TmpYBuffer { get; } + public IMemoryOwner TmpYBuffer { get; } - public byte[] TmpUBuffer { get; } + public IMemoryOwner TmpUBuffer { get; } - public byte[] TmpVBuffer { get; } + public IMemoryOwner TmpVBuffer { get; } - public byte[] Pixels { get; } + /// + /// Gets the pixel buffer where the decoded pixel data will be stored. + /// + public IMemoryOwner Pixels { get; } /// /// Gets or sets filter strength info. @@ -348,5 +345,18 @@ namespace SixLabors.ImageSharp.Formats.WebP } } } + + /// + public void Dispose() + { + this.YuvBuffer.Dispose(); + this.CacheY.Dispose(); + this.CacheU.Dispose(); + this.CacheV.Dispose(); + this.TmpYBuffer.Dispose(); + this.TmpUBuffer.Dispose(); + this.TmpVBuffer.Dispose(); + this.Pixels.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 8b9a3569c..03faaaa7c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -55,39 +55,46 @@ namespace SixLabors.ImageSharp.Formats.WebP var proba = new Vp8Proba(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - var decoder = new Vp8Decoder(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba); - Vp8Io io = InitializeVp8Io(decoder, pictureHeader); + using (var decoder = new Vp8Decoder(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba, this.memoryAllocator)) + { + Vp8Io io = InitializeVp8Io(decoder, pictureHeader); - // Paragraph 9.4: Parse the filter specs. - this.ParseFilterHeader(decoder); - decoder.PrecomputeFilterStrengths(); + // Paragraph 9.4: Parse the filter specs. + this.ParseFilterHeader(decoder); + decoder.PrecomputeFilterStrengths(); - // Paragraph 9.5: Parse partitions. - this.ParsePartitions(decoder); + // Paragraph 9.5: Parse partitions. + this.ParsePartitions(decoder); - // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(decoder); + // Paragraph 9.6: Dequantization Indices. + this.ParseDequantizationIndices(decoder); - // Ignore the value of update probabilities. - this.bitReader.ReadBool(); + // Ignore the value of update probabilities. + this.bitReader.ReadBool(); - // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(decoder); + // Paragraph 13.4: Parse probabilities. + this.ParseProbabilities(decoder); - // Decode image data. - this.ParseFrame(decoder, io); + // Decode image data. + this.ParseFrame(decoder, io); - if (info.Features?.Alpha is true) - { - using (var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData, info.Features.AlphaChunkHeader, this.memoryAllocator)) + if (info.Features?.Alpha is true) { - alphaDecoder.Decode(); - this.DecodePixelValues(width, height, decoder.Pixels, pixels, alphaDecoder.Alpha); + using (var alphaDecoder = new AlphaDecoder( + width, + height, + info.Features.AlphaData, + info.Features.AlphaChunkHeader, + this.memoryAllocator)) + { + alphaDecoder.Decode(); + this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); + } + } + else + { + this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); } - } - else - { - this.DecodePixelValues(width, height, decoder.Pixels, pixels); } } @@ -252,10 +259,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; int vOff = uOff + 16; - byte[] yuv = dec.YuvBuffer; - Span yDst = dec.YuvBuffer.AsSpan(yOff); - Span uDst = dec.YuvBuffer.AsSpan(uOff); - Span vDst = dec.YuvBuffer.AsSpan(vOff); + Span yuv = dec.YuvBuffer.Memory.Span; + Span yDst = yuv.Slice(yOff); + Span uDst = yuv.Slice(uOff); + Span vDst = yuv.Slice(vOff); // Initialize left-most block. for (int i = 0; i < 16; ++i) @@ -278,19 +285,19 @@ namespace SixLabors.ImageSharp.Formats.WebP { // We only need to do this init once at block (0,0). // Afterward, it remains valid for the whole topmost row. - Span tmp = dec.YuvBuffer.AsSpan(yOff - WebPConstants.Bps - 1, 16 + 4 + 1); + Span tmp = yuv.Slice(yOff - WebPConstants.Bps - 1, 16 + 4 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; } - tmp = dec.YuvBuffer.AsSpan(uOff - WebPConstants.Bps - 1, 8 + 1); + tmp = yuv.Slice(uOff - WebPConstants.Bps - 1, 8 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; } - tmp = dec.YuvBuffer.AsSpan(vOff - WebPConstants.Bps - 1, 8 + 1); + tmp = yuv.Slice(vOff - WebPConstants.Bps - 1, 8 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; @@ -310,17 +317,17 @@ namespace SixLabors.ImageSharp.Formats.WebP { int srcIdx = (i * WebPConstants.Bps) + 12 + yOff; int dstIdx = (i * WebPConstants.Bps) - 4 + yOff; - yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); + yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); } for (int i = -1; i < 8; ++i) { int srcIdx = (i * WebPConstants.Bps) + 4 + uOff; int dstIdx = (i * WebPConstants.Bps) - 4 + uOff; - yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); + yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); srcIdx = (i * WebPConstants.Bps) + 4 + vOff; dstIdx = (i * WebPConstants.Bps) - 4 + vOff; - yuv.AsSpan(srcIdx, 4).CopyTo(yuv.AsSpan(dstIdx)); + yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); } } @@ -330,15 +337,15 @@ namespace SixLabors.ImageSharp.Formats.WebP uint bits = block.NonZeroY; if (mby > 0) { - topYuv.Y.CopyTo(yuv.AsSpan(yOff - WebPConstants.Bps)); - topYuv.U.CopyTo(yuv.AsSpan(uOff - WebPConstants.Bps)); - topYuv.V.CopyTo(yuv.AsSpan(vOff - WebPConstants.Bps)); + topYuv.Y.CopyTo(yuv.Slice(yOff - WebPConstants.Bps)); + topYuv.U.CopyTo(yuv.Slice(uOff - WebPConstants.Bps)); + topYuv.V.CopyTo(yuv.Slice(vOff - WebPConstants.Bps)); } // Predict and add residuals. if (block.IsI4x4) { - Span topRight = yuv.AsSpan(yOff - WebPConstants.Bps + 16); + Span topRight = yuv.Slice(yOff - WebPConstants.Bps + 16); if (mby > 0) { if (mbx >= dec.MbWidth - 1) @@ -356,14 +363,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Replicate the top-right pixels below. - Span topRightUint = MemoryMarshal.Cast(yuv.AsSpan(yOff - WebPConstants.Bps + 16)); + Span topRightUint = MemoryMarshal.Cast(yuv.Slice(yOff - WebPConstants.Bps + 16)); topRightUint[WebPConstants.Bps] = topRightUint[2 * WebPConstants.Bps] = topRightUint[3 * WebPConstants.Bps] = topRightUint[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) { int offset = yOff + WebPConstants.Scan[n]; - Span dst = yuv.AsSpan(offset); + Span dst = yuv.Slice(offset); byte lumaMode = block.Modes[n]; switch (lumaMode) { @@ -487,9 +494,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Transfer reconstructed samples from yuv_buffer cache to final destination. - Span yOut = dec.CacheY.AsSpan(dec.CacheYOffset + (mbx * 16)); - Span uOut = dec.CacheU.AsSpan(dec.CacheUvOffset + (mbx * 8)); - Span vOut = dec.CacheV.AsSpan(dec.CacheUvOffset + (mbx * 8)); + Span yOut = dec.CacheY.Memory.Span.Slice(dec.CacheYOffset + (mbx * 16)); + Span uOut = dec.CacheU.Memory.Span.Slice(dec.CacheUvOffset + (mbx * 8)); + Span vOut = dec.CacheV.Memory.Span.Slice(dec.CacheUvOffset + (mbx * 8)); for (int j = 0; j < 16; ++j) { yDst.Slice(j * WebPConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut.Slice(j * dec.CacheYStride)); @@ -529,22 +536,22 @@ namespace SixLabors.ImageSharp.Formats.WebP int offset = dec.CacheYOffset + (mbx * 16); if (mbx > 0) { - LossyUtils.SimpleHFilter16(dec.CacheY, offset, yBps, limit + 4); + LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } if (filterInfo.UseInnerFiltering > 0) { - LossyUtils.SimpleHFilter16i(dec.CacheY, offset, yBps, limit); + LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } if (mby > 0) { - LossyUtils.SimpleVFilter16(dec.CacheY, offset, yBps, limit + 4); + LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } if (filterInfo.UseInnerFiltering > 0) { - LossyUtils.SimpleVFilter16i(dec.CacheY, offset, yBps, limit); + LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } } else if (dec.Filter is LoopFilter.Complex) @@ -555,26 +562,26 @@ namespace SixLabors.ImageSharp.Formats.WebP int hevThresh = filterInfo.HighEdgeVarianceThreshold; if (mbx > 0) { - LossyUtils.HFilter16(dec.CacheY, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.HFilter8(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } if (filterInfo.UseInnerFiltering > 0) { - LossyUtils.HFilter16i(dec.CacheY, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.HFilter8i(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit, iLevel, hevThresh); + LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); } if (mby > 0) { - LossyUtils.VFilter16(dec.CacheY, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.VFilter8(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } if (filterInfo.UseInnerFiltering > 0) { - LossyUtils.VFilter16i(dec.CacheY, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.VFilter8i(dec.CacheU, dec.CacheV, uvOffset, uvBps, limit, iLevel, hevThresh); + LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); } } } @@ -584,9 +591,9 @@ namespace SixLabors.ImageSharp.Formats.WebP int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; int ySize = extraYRows * dec.CacheYStride; int uvSize = (extraYRows / 2) * dec.CacheUvStride; - Span yDst = dec.CacheY.AsSpan(); - Span uDst = dec.CacheU.AsSpan(); - Span vDst = dec.CacheV.AsSpan(); + Span yDst = dec.CacheY.Memory.Span; + Span uDst = dec.CacheU.Memory.Span; + Span vDst = dec.CacheV.Memory.Span; int mby = dec.MbY; bool isFirstRow = mby is 0; bool isLastRow = mby >= dec.BottomRightMbY - 1; @@ -609,9 +616,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - io.Y = dec.CacheY.AsSpan(dec.CacheYOffset); - io.U = dec.CacheU.AsSpan(dec.CacheUvOffset); - io.V = dec.CacheV.AsSpan(dec.CacheUvOffset); + io.Y = dec.CacheY.Memory.Span.Slice(dec.CacheYOffset); + io.U = dec.CacheU.Memory.Span.Slice(dec.CacheUvOffset); + io.V = dec.CacheV.Memory.Span.Slice(dec.CacheUvOffset); } if (!isLastRow) @@ -639,28 +646,28 @@ namespace SixLabors.ImageSharp.Formats.WebP // Rotate top samples if needed. if (!isLastRow) { - yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.AsSpan()); - uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.AsSpan()); - vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.AsSpan()); + yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.Memory.Span); + uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.Memory.Span); + vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.Memory.Span); } } private int EmitRgb(Vp8Decoder dec, Vp8Io io) { - byte[] buf = dec.Pixels; + Span buf = dec.Pixels.Memory.Span; int numLinesOut = io.MbH; // a priori guess. Span curY = io.Y; Span curU = io.U; Span curV = io.V; - byte[] tmpYBuffer = dec.TmpYBuffer; - byte[] tmpUBuffer = dec.TmpUBuffer; - byte[] tmpVBuffer = dec.TmpVBuffer; - Span topU = tmpUBuffer.AsSpan(); - Span topV = tmpVBuffer.AsSpan(); + Span tmpYBuffer = dec.TmpYBuffer.Memory.Span; + Span tmpUBuffer = dec.TmpUBuffer.Memory.Span; + Span tmpVBuffer = dec.TmpVBuffer.Memory.Span; + Span topU = tmpUBuffer; + Span topV = tmpVBuffer; int bpp = 3; int bufferStride = bpp * io.Width; int dstStartIdx = io.MbY * bufferStride; - Span dst = buf.AsSpan(dstStartIdx); + Span dst = buf.Slice(dstStartIdx); int yEnd = io.MbY + io.MbH; int mbw = io.MbW; int uvw = (mbw + 1) / 2; @@ -674,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { // We can finish the left-over line from previous call. - this.UpSample(tmpYBuffer.AsSpan(), curY, topU, topV, curU, curV, buf.AsSpan(dstStartIdx - bufferStride), dst, mbw); + this.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf.Slice(dstStartIdx - bufferStride), dst, mbw); numLinesOut++; } From fd92556fe05a169ab0064769401828dd07bc5f7f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2020 20:48:41 +0100 Subject: [PATCH 138/359] Use bulk pixel conversion --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 5 +- .../Formats/WebP/WebPDecoderCore.cs | 4 +- .../Formats/WebP/WebPLosslessDecoder.cs | 41 ++++++------ .../Formats/WebP/WebPLossyDecoder.cs | 63 ++++++++++--------- 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 8a276cebc..f386c42da 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -22,7 +22,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The (maybe compressed) alpha data. /// The first byte of the alpha image stream contains information on ow to decode the stream. /// Used for allocating memory during decoding. - public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator) + /// The configuration. + public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) { this.Width = width; this.Height = height; @@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.Compressed) { var bitReader = new Vp8LBitReader(data); - this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator); + this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index a9312f19c..f188d7d7b 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -89,12 +89,12 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator); + var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo); } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 6ca54690f..5bb7befaf 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -27,6 +27,16 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private readonly Vp8LBitReader bitReader; + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + private static readonly int BitsSpecialMarker = 0x100; private static readonly int NumArgbCacheRows = 16; @@ -62,20 +72,17 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 1, 1, 0 }; - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - /// /// Initializes a new instance of the class. /// /// Bitreader to read from the stream. /// Used for allocating memory during processing operations. - public WebPLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator) + /// The configuration. + public WebPLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) { this.bitReader = bitReader; this.memoryAllocator = memoryAllocator; + this.configuration = configuration; } /// @@ -181,25 +188,21 @@ namespace SixLabors.ImageSharp.Formats.WebP where TPixel : struct, IPixel { Span pixelData = decoder.Pixels.GetSpan(); + int width = decoder.Width; // Apply reverse transformations, if any are present. this.ApplyInverseTransforms(decoder, pixelData); - TPixel color = default; + Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); for (int y = 0; y < decoder.Height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = 0; x < decoder.Width; x++) - { - int idx = (y * decoder.Width) + x; - uint pixel = pixelData[idx]; - byte a = (byte)((pixel & 0xFF000000) >> 24); - byte r = (byte)((pixel & 0xFF0000) >> 16); - byte g = (byte)((pixel & 0xFF00) >> 8); - byte b = (byte)(pixel & 0xFF); - color.FromRgba32(new Rgba32(r, g, b, a)); - pixelRow[x] = color; - } + Span row = pixelDataAsBytes.Slice(y * width * 4, width * 4); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row, + pixelSpan, + width); } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 03faaaa7c..4e06b441f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -24,15 +24,22 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private readonly MemoryAllocator memoryAllocator; + /// + /// The global configuration. + /// + private readonly Configuration configuration; + /// /// Initializes a new instance of the class. /// /// Bitreader to read from the stream. /// Used for allocating memory during processing operations. - public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) + /// The configuration. + public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) { - this.memoryAllocator = memoryAllocator; this.bitReader = bitReader; + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; } public void Decode(Buffer2D pixels, int width, int height, WebPImageInfo info) @@ -85,7 +92,8 @@ namespace SixLabors.ImageSharp.Formats.WebP height, info.Features.AlphaData, info.Features.AlphaChunkHeader, - this.memoryAllocator)) + this.memoryAllocator, + this.configuration)) { alphaDecoder.Decode(); this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); @@ -101,39 +109,38 @@ namespace SixLabors.ImageSharp.Formats.WebP private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha = null) where TPixel : struct, IPixel { - TPixel color = default; - bool hasAlpha = false; - Span alphaSpan = null; if (alpha != null) { - hasAlpha = true; - alphaSpan = alpha.Memory.Span; - } - - for (int y = 0; y < height; y++) - { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = 0; x < width; x++) + TPixel color = default; + Span alphaSpan = alpha.Memory.Span; + for (int y = 0; y < height; y++) { - int offset = (y * width) + x; - int idxBgr = offset * 3; - byte b = pixelData[idxBgr]; - byte g = pixelData[idxBgr + 1]; - byte r = pixelData[idxBgr + 2]; - - // TODO: use bulk conversion here. - if (hasAlpha) + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < width; x++) { + int offset = (y * width) + x; + int idxBgr = offset * 3; + byte b = pixelData[idxBgr]; + byte g = pixelData[idxBgr + 1]; + byte r = pixelData[idxBgr + 2]; byte a = alphaSpan[offset]; color.FromBgra32(new Bgra32(r, g, b, a)); + pixelRow[x] = color; } - else - { - color.FromBgr24(new Bgr24(r, g, b)); - } - - pixelRow[x] = color; } + + return; + } + + for (int y = 0; y < height; y++) + { + Span row = pixelData.Slice(y * width * 3, width * 3); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgr24Bytes( + this.configuration, + row, + pixelSpan, + width); } } From ae73f849fcbe92d115208100163d35f0055cd8f8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 13 Mar 2020 14:15:47 +0100 Subject: [PATCH 139/359] Fix solution file due to merge error --- ImageSharp.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index fcf7dd513..8fc463c72 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -579,7 +579,6 @@ Global {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution From 39a28668cf58c1f16842eb8211c495155a6d0777 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 13 Mar 2020 16:31:13 +0100 Subject: [PATCH 140/359] Add tests for reading webp metadata, fix some test images with metadata --- .../Formats/WebP/WebPDecoderCore.cs | 17 ++++-- .../Formats/WebP/WebPDecoderTests.cs | 1 - .../Formats/WebP/WebPFileHeaderTests.cs | 21 ------- .../Formats/WebP/WebPMetaDataTests.cs | 57 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 11 +++- .../Input/WebP/{exif.webp => exif_lossy.webp} | 0 ...{lossless_iccp.webp => iccp_lossless.webp} | 0 .../WebP/{lossy_iccp.webp => iccp_lossy.webp} | 0 8 files changed, 77 insertions(+), 30 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs rename tests/Images/Input/WebP/{exif.webp => exif_lossy.webp} (100%) rename tests/Images/Input/WebP/{lossless_iccp.webp => iccp_lossless.webp} (100%) rename tests/Images/Input/WebP/{lossy_iccp.webp => iccp_lossy.webp} (100%) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 8f8f50cc0..2971b2638 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -215,12 +215,19 @@ namespace SixLabors.ImageSharp.Formats.WebP if (chunkType is WebPChunkType.Iccp) { uint iccpChunkSize = this.ReadChunkSize(); - var iccpData = new byte[iccpChunkSize]; - this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); - var profile = new IccProfile(iccpData); - if (profile.CheckIsValid()) + if (!this.IgnoreMetadata) { - this.Metadata.IccProfile = profile; + var iccpData = new byte[iccpChunkSize]; + this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); + var profile = new IccProfile(iccpData); + if (profile.CheckIsValid()) + { + this.Metadata.IccProfile = profile; + } + } + else + { + this.currentStream.Skip((int)iccpChunkSize); } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index dc7c6f084..f2c2d9c2d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.WebP { using static SixLabors.ImageSharp.Tests.TestImages.WebP; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs deleted file mode 100644 index bcc177d34..000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPFileHeaderTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using SixLabors.ImageSharp.Formats.Bmp; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.WebP -{ - public class WebPFileHeaderTests - { - [Fact] - public void TestWrite() - { - var header = new BmpFileHeader(1, 2, 3, 4); - - var buffer = new byte[14]; - - header.WriteTo(buffer); - - Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs new file mode 100644 index 000000000..44f46c05a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + public class WebPMetadataTests + { + [Theory] + [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, true)] + public void IgnoreMetadata_ControlsWhetherExifIsParsed(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + var decoder = new WebPDecoder { IgnoreMetadata = ignoreMetadata }; + + using (Image image = provider.GetImage(decoder)) + { + if (ignoreMetadata) + { + Assert.Null(image.Metadata.ExifProfile); + } + else + { + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotEmpty(image.Metadata.ExifProfile.Values); + } + } + } + + [Theory] + [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossy.WithIccp, PixelTypes.Rgba32, true)] + public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + var decoder = new WebPDecoder { IgnoreMetadata = ignoreMetadata }; + + using (Image image = provider.GetImage(decoder)) + { + if (ignoreMetadata) + { + Assert.Null(image.Metadata.IccProfile); + } + else + { + Assert.NotNull(image.Metadata.IccProfile); + Assert.NotEmpty(image.Metadata.IccProfile.Entries); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index af8be150f..517fe38f1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -390,8 +390,8 @@ namespace SixLabors.ImageSharp.Tests public const string Bit32Rle = "Tga/targa_32bit_rle.tga"; public const string Bit16Pal = "Tga/targa_16bit_pal.tga"; public const string Bit24Pal = "Tga/targa_24bit_pal.tga"; - } - + } + public static class WebP { public static class Animated @@ -404,6 +404,8 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { + public const string WithExif = "WebP/exif_lossless.webp"; + public const string WithIccp = "WebP/iccp_lossless.webp"; public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; public const string NoTransform2 = "WebP/lossless_vec_2_0.webp"; public const string GreenTransform1 = "WebP/lossless1.webp"; @@ -452,6 +454,9 @@ namespace SixLabors.ImageSharp.Tests public static class Lossy { + public const string WithExif = "WebP/exif_lossy.webp"; + public const string WithIccp = "WebP/iccp_lossy.webp"; + // Lossy images without macroblock filtering. public const string Bike = "WebP/bike_lossy.webp"; public const string NoFilter01 = "WebP/vp80-01-intra-1400.webp"; @@ -469,7 +474,7 @@ namespace SixLabors.ImageSharp.Tests public const string SimpleFilter05 = "WebP/test-nostrong.webp"; // Lossy images with a complex filter. - public const string IccpComplexFilter = "WebP/lossy_iccp.webp"; + public const string IccpComplexFilter = "WebP/iccp_lossy.webp"; public const string VeryShort = "WebP/very_short.webp"; public const string BikeComplexFilter = "WebP/bike_lossy_complex_filter.webp"; public const string ComplexFilter01 = "WebP/vp80-02-inter-1418.webp"; diff --git a/tests/Images/Input/WebP/exif.webp b/tests/Images/Input/WebP/exif_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/exif.webp rename to tests/Images/Input/WebP/exif_lossy.webp diff --git a/tests/Images/Input/WebP/lossless_iccp.webp b/tests/Images/Input/WebP/iccp_lossless.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_iccp.webp rename to tests/Images/Input/WebP/iccp_lossless.webp diff --git a/tests/Images/Input/WebP/lossy_iccp.webp b/tests/Images/Input/WebP/iccp_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_iccp.webp rename to tests/Images/Input/WebP/iccp_lossy.webp From e31f8d53cb14ae3aad0ed12f7c2bf23054b68123 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 16 Mar 2020 14:12:15 +0100 Subject: [PATCH 141/359] Fix lossy decoding issue: return value of ParseResiduals was not used --- .../Formats/WebP/IWebPDecoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 3 +-- .../Formats/WebP/Vp8MacroBlockData.cs | 7 +++++-- .../Formats/WebP/WebPLookupTables.cs | 1 - .../Formats/WebP/WebPLossyDecoder.cs | 20 +++++++++---------- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs index 1ddbdbdc6..13bf817c4 100644 --- a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Image decoder for generating an image out of a webp stream. + /// Image decoder options for generating an image out of a webp stream. /// internal interface IWebPDecoderOptions { diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index f47100ef3..49d642d48 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Formats.WebP info.Limit = 0; // no filtering. } - info.UseInnerFiltering = (byte)i4x4; + info.UseInnerFiltering = i4x4 is 1; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index 760e1b5c5..16c9d9c0e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -39,9 +39,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets a value indicating whether to do inner filtering. - /// TODO: can this be a bool? /// - public byte UseInnerFiltering { get; set; } + public bool UseInnerFiltering { get; set; } /// /// Gets or sets the high edge variance threshold in [0..2]. diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index cfac56d3c..e0536e6b4 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8MacroBlockData { + /// + /// Initializes a new instance of the class. + /// public Vp8MacroBlockData() { this.Modes = new byte[16]; @@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4. + /// Gets or sets the coefficients. 384 coeffs = (16+4+4) * 4*4. /// public short[] Coeffs { get; set; } @@ -38,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint NonZeroUv { get; set; } - public byte Skip { get; set; } + public bool Skip { get; set; } public byte Segment { get; set; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 26c4be773..a2aa76999 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -444,7 +444,6 @@ namespace SixLabors.ImageSharp.Formats.WebP static WebPLookupTables() { - // TODO: maybe use hashset here Abs0 = new Dictionary(); for (int i = -255; i <= 255; ++i) { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index c24e60812..db1e254fa 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -191,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (dec.UseSkipProbability) { - block.Skip = (byte)this.bitReader.GetBit(dec.SkipProbability); + block.Skip = this.bitReader.GetBit(dec.SkipProbability) is 1; } block.IsI4x4 = this.bitReader.GetBit(145) is 0; @@ -488,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - this.DoUVTransform(bitsUv >> 0, coeffs.AsSpan(16 * 16), uDst); + this.DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst); this.DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst); // Stash away top samples for next block. @@ -545,7 +545,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } - if (filterInfo.UseInnerFiltering > 0) + if (filterInfo.UseInnerFiltering) { LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } @@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } - if (filterInfo.UseInnerFiltering > 0) + if (filterInfo.UseInnerFiltering) { LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } @@ -572,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } - if (filterInfo.UseInnerFiltering > 0) + if (filterInfo.UseInnerFiltering) { LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); @@ -584,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } - if (filterInfo.UseInnerFiltering > 0) + if (filterInfo.UseInnerFiltering) { LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); @@ -820,11 +820,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8MacroBlock left = dec.LeftMacroBlock; Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; Vp8MacroBlockData blockData = dec.CurrentBlockData; - int skip = dec.UseSkipProbability ? blockData.Skip : 0; + bool skip = dec.UseSkipProbability ? blockData.Skip : false; - if (skip is 0) + if (!skip) { - this.ParseResiduals(dec, bitreader, macroBlock); + skip = this.ParseResiduals(dec, bitreader, macroBlock); } else { @@ -843,7 +843,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { Vp8FilterInfo precomputedFilterInfo = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; dec.FilterInfo[dec.MbX] = (Vp8FilterInfo)precomputedFilterInfo.DeepClone(); - dec.FilterInfo[dec.MbX].UseInnerFiltering |= (byte)(skip is 0 ? 1 : 0); + dec.FilterInfo[dec.MbX].UseInnerFiltering |= !skip; } } From 0c2e5df78a53e1fef87ba3f8540b2a46914ca0eb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 16 Mar 2020 15:13:59 +0100 Subject: [PATCH 142/359] Update external, change expectedDefaultConfigurationCount to be 6 instead of 5 --- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- tests/Images/External | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index a68baf93f..0708aac33 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 5; + private readonly int expectedDefaultConfigurationCount = 6; public ConfigurationTests() { diff --git a/tests/Images/External b/tests/Images/External index 99a2bc523..1fea1ceab 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57 +Subproject commit 1fea1ceab89e87cc5f11376fa46164d3d27566c0 From 8703dac09adc10ddcf5fa411a2f512c31212449c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Mar 2020 16:46:23 +0100 Subject: [PATCH 143/359] Remove cropping options --- src/ImageSharp/Formats/WebP/Vp8Io.cs | 12 ------- .../Formats/WebP/WebPLossyDecoder.cs | 32 +++++++------------ 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index feb763129..527ef02f0 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -9,14 +9,12 @@ namespace SixLabors.ImageSharp.Formats.WebP { /// /// Gets or sets the picture width in pixels (invariable). - /// Original, uncropped dimensions. /// The actual area passed to put() is stored in /> field. /// public int Width { get; set; } /// /// Gets or sets the picture height in pixels (invariable). - /// Original, uncropped dimensions. /// The actual area passed to put() is stored in /> field. /// public int Height { get; set; } @@ -61,16 +59,6 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public int UvStride { get; set; } - public bool UseCropping { get; set; } - - public int CropLeft { get; set; } - - public int CropRight { get; set; } - - public int CropTop { get; set; } - - public int CropBottom { get; set; } - public bool UseScaling { get; set; } public int ScaledWidth { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index db1e254fa..4de0beaf8 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -632,19 +632,15 @@ namespace SixLabors.ImageSharp.Formats.WebP yEnd -= extraYRows; } - if (yEnd > io.CropBottom) + if (yEnd > io.Height) { - yEnd = io.CropBottom; // make sure we don't overflow on last row. + yEnd = io.Height; // make sure we don't overflow on last row. } if (yStart < yEnd) { - io.Y = io.Y.Slice(io.CropLeft); - io.U = io.U.Slice(io.CropLeft); - io.V = io.V.Slice(io.CropLeft); - - io.MbY = yStart - io.CropTop; - io.MbW = io.CropRight - io.CropLeft; + io.MbY = yStart; + io.MbW = io.Width; io.MbH = yEnd - yStart; this.EmitRgb(dec, io); } @@ -705,7 +701,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Move to last row. curY = curY.Slice(io.YStride); - if (io.CropTop + yEnd < io.CropBottom) + if (yEnd < io.Height) { // Save the unfinished samples for next call (as we're not done yet). curY.Slice(0, mbw).CopyTo(tmpYBuffer); @@ -1315,11 +1311,6 @@ namespace SixLabors.ImageSharp.Formats.WebP var io = default(Vp8Io); io.Width = (int)pictureHeader.Width; io.Height = (int)pictureHeader.Height; - io.UseCropping = false; - io.CropTop = 0; - io.CropLeft = 0; - io.CropRight = io.Width; - io.CropBottom = io.Height; io.UseScaling = false; io.ScaledWidth = io.Width; io.ScaledHeight = io.ScaledHeight; @@ -1340,11 +1331,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - // For simple filter, we can filter only the cropped region. We include 'extraPixels' on - // the other side of the boundary, since vertical or horizontal filtering of the previous - // macroblock can modify some abutting pixels. - dec.TopLeftMbX = (io.CropLeft - extraPixels) >> 4; - dec.TopLeftMbY = (io.CropTop - extraPixels) >> 4; + // For simple filter, we include 'extraPixels' on the other side of the boundary, + // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. + dec.TopLeftMbX = (-extraPixels) >> 4; + dec.TopLeftMbY = (-extraPixels) >> 4; if (dec.TopLeftMbX < 0) { dec.TopLeftMbX = 0; @@ -1357,8 +1347,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } // We need some 'extra' pixels on the right/bottom. - dec.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; - dec.BottomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; + dec.BottomRightMbY = (io.Height + 15 + extraPixels) >> 4; + dec.BottomRightMbX = (io.Width + 15 + extraPixels) >> 4; if (dec.BottomRightMbX > dec.MbWidth) { dec.BottomRightMbX = dec.MbWidth; From 91fe575fb1fe9f49bc550cc83861b49b2b7cb99f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Mar 2020 17:05:07 +0100 Subject: [PATCH 144/359] Add AggressiveInlining where it seemed appropriate --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 2 + src/ImageSharp/Formats/WebP/LosslessUtils.cs | 66 +++++++++++++------ src/ImageSharp/Formats/WebP/LossyUtils.cs | 33 ++++++++++ src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 4 ++ src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 4 ++ .../Formats/WebP/WebPLosslessDecoder.cs | 6 ++ .../Formats/WebP/WebPLossyDecoder.cs | 5 ++ 7 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 5d0ba360a..3a5f0ca4f 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -293,6 +294,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return true; } + [MethodImpl(InliningOptions.ShortMethod)] private static int GradientPredictor(byte a, byte b, byte c) { int g = a + b - c; diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 600338e8e..720fb57d9 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -109,7 +110,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (y < yEnd) { int predRowIdx = predRowIdxStart; - Vp8LMultipliers m = default(Vp8LMultipliers); + var m = default(Vp8LMultipliers); int srcSafeEnd = pixelPos + safeWidth; int srcEnd = pixelPos + width; while (pixelPos < srcSafeEnd) @@ -273,6 +274,24 @@ namespace SixLabors.ImageSharp.Formats.WebP output.CopyTo(pixelData); } + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) + { + newColorMap[0] = transformData[0]; + Span data = MemoryMarshal.Cast(transformData); + Span newData = MemoryMarshal.Cast(newColorMap); + int i; + for (i = 4; i < 4 * numColors; ++i) + { + // Equivalent to AddPixelEq(), on a byte-basis. + newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); + } + + for (; i < 4 * newColorMap.Length; ++i) + { + newData[i] = 0; // black tail. + } + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; @@ -425,79 +444,93 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor0() { return WebPConstants.ArgbBlack; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor1(uint left, Span top) { return left; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor2(uint left, Span top, int idx) { return top[idx]; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor3(uint left, Span top, int idx) { return top[idx + 1]; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor4(uint left, Span top, int idx) { return top[idx - 1]; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor5(uint left, Span top, int idx) { uint pred = Average3(left, top[idx], top[idx + 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor6(uint left, Span top, int idx) { uint pred = Average2(left, top[idx - 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor7(uint left, Span top, int idx) { uint pred = Average2(left, top[idx]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor8(uint left, Span top, int idx) { uint pred = Average2(top[idx - 1], top[idx]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor9(uint left, Span top, int idx) { uint pred = Average2(top[idx], top[idx + 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor10(uint left, Span top, int idx) { uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor11(uint left, Span top, int idx) { uint pred = Select(top[idx], left, top[idx - 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor12(uint left, Span top, int idx) { uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Predictor13(uint left, Span top, int idx) { uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); @@ -529,16 +562,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; } + [MethodImpl(InliningOptions.ShortMethod)] private static int AddSubtractComponentHalf(int a, int b) { return (int)Clip255((uint)(a + ((a - b) / 2))); } + [MethodImpl(InliningOptions.ShortMethod)] private static int AddSubtractComponentFull(int a, int b, int c) { return (int)Clip255((uint)(a + b - c)); } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Clip255(uint a) { if (a < 256) @@ -559,6 +595,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return (paMinusPb <= 0) ? a : b; } + [MethodImpl(InliningOptions.ShortMethod)] private static int Sub3(int a, int b, int c) { int pb = b - c; @@ -566,16 +603,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return Math.Abs(pb) - Math.Abs(pa); } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Average2(uint a0, uint a1) { return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Average3(uint a0, uint a1, uint a2) { return Average2(Average2(a0, a2), a1); } + [MethodImpl(InliningOptions.ShortMethod)] private static uint Average4(uint a0, uint a1, uint a2, uint a3) { return Average2(Average2(a0, a1), Average2(a2, a3)); @@ -584,6 +624,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// + [MethodImpl(InliningOptions.ShortMethod)] public static int SubSampleSize(int size, int samplingBits) { return (size + (1 << samplingBits) - 1) >> samplingBits; @@ -592,6 +633,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Sum of each component, mod 256. /// + [MethodImpl(InliningOptions.ShortMethod)] private static uint AddPixels(uint a, uint b) { uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); @@ -602,6 +644,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Difference of each component, mod 256. /// + [MethodImpl(InliningOptions.ShortMethod)] private static uint SubPixels(uint a, uint b) { uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); @@ -609,34 +652,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } + [MethodImpl(InliningOptions.ShortMethod)] private static uint GetArgbIndex(uint idx) { return (idx >> 8) & 0xff; } - public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) - { - newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); - Span newData = MemoryMarshal.Cast(newColorMap); - int i; - for (i = 4; i < 4 * numColors; ++i) - { - // Equivalent to AddPixelEq(), on a byte-basis. - newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); - } - - for (; i < 4 * newColorMap.Length; ++i) - { - newData[i] = 0; // black tail. - } - } - + [MethodImpl(InliningOptions.ShortMethod)] private static int ColorTransformDelta(sbyte colorPred, sbyte color) { return ((int)colorPred * color) >> 5; } + [MethodImpl(InliningOptions.ShortMethod)] private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) { m.GreenToRed = (byte)(colorCode & 0xff); diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 7222cfa7b..837897e78 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -3,11 +3,13 @@ using System; using System.Buffers.Binary; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.WebP { internal static class LossyUtils { + [MethodImpl(InliningOptions.ShortMethod)] private static void Put16(int v, Span dst) { for (int j = 0; j < 16; ++j) @@ -28,6 +30,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 5, dst); } + [MethodImpl(InliningOptions.ShortMethod)] public static void TM16(Span dst, Span yuv, int offset) { TrueMotion(dst, yuv, offset, 16); @@ -83,6 +86,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put16(dc >> 4, dst); } + [MethodImpl(InliningOptions.ShortMethod)] public static void DC16NoTopLeft(Span dst) { // DC with no top and left samples. @@ -101,6 +105,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 4), dst); } + [MethodImpl(InliningOptions.ShortMethod)] public static void TM8uv(Span dst, Span yuv, int offset) { // TrueMotion @@ -159,6 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Put8x8uv((byte)(dc0 >> 3), dst); } + [MethodImpl(InliningOptions.ShortMethod)] public static void DC8uvNoTopLeft(Span dst) { // DC with nothing. @@ -180,6 +186,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] public static void TM4(Span dst, Span yuv, int offset) { TrueMotion(dst, yuv, offset, 4); @@ -601,11 +608,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); } + [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); @@ -630,36 +639,42 @@ namespace SixLabors.ImageSharp.Formats.WebP } // 8-pixels wide variant, for chroma filtering. + [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); } + [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); } + [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); } + [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); } + [MethodImpl(InliningOptions.ShortMethod)] public static uint LoadUv(byte u, byte v) { // We process u and v together stashed into 32bit(16bit each). return (uint)(u | (v << 16)); } + [MethodImpl(InliningOptions.ShortMethod)] public static void YuvToBgr(int y, int u, int v, Span bgr) { bgr[0] = (byte)YuvToB(y, u); @@ -667,16 +682,19 @@ namespace SixLabors.ImageSharp.Formats.WebP bgr[2] = (byte)YuvToR(y, v); } + [MethodImpl(InliningOptions.ShortMethod)] public static int YuvToR(int y, int v) { return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); } + [MethodImpl(InliningOptions.ShortMethod)] public static int YuvToG(int y, int u, int v) { return Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); } + [MethodImpl(InliningOptions.ShortMethod)] public static int YuvToB(int y, int u) { return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); @@ -795,6 +813,7 @@ namespace SixLabors.ImageSharp.Formats.WebP p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3]; } + [MethodImpl(InliningOptions.ShortMethod)] private static bool NeedsFilter(Span p, int offset, int step, int t) { int p1 = p[offset + (-2 * step)]; @@ -824,6 +843,7 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it; } + [MethodImpl(InliningOptions.ShortMethod)] private static bool Hev(Span p, int offset, int step, int thresh) { int p1 = p[offset - (2 * step)]; @@ -833,16 +853,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return (WebPLookupTables.Abs0[p1 - p0] > thresh) || (WebPLookupTables.Abs0[q1 - q0] > thresh); } + [MethodImpl(InliningOptions.ShortMethod)] private static int MultHi(int v, int coeff) { return (v * coeff) >> 8; } + [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, int x, int y, int v) { dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3)); } + [MethodImpl(InliningOptions.ShortMethod)] private static void Store2(Span dst, int y, int dc, int d, int c) { Store(dst, 0, y, dc + d); @@ -851,27 +874,32 @@ namespace SixLabors.ImageSharp.Formats.WebP Store(dst, 3, y, dc - d); } + [MethodImpl(InliningOptions.ShortMethod)] private static int Mul1(int a) { return ((a * 20091) >> 16) + a; } + [MethodImpl(InliningOptions.ShortMethod)] private static int Mul2(int a) { return (a * 35468) >> 16; } + [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8B(int v) { return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255); } + [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8(int v) { int yuvMask = (256 << 6) - 1; return (byte)(((v & ~yuvMask) is 0) ? (v >> 6) : (v < 0) ? 0 : 255); } + [MethodImpl(InliningOptions.ShortMethod)] private static void Put8x8uv(byte value, Span dst) { for (int j = 0; j < 8; ++j) @@ -881,6 +909,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] private static void Memset(Span dst, byte value, int startIdx, int count) { for (int i = 0; i < count; i++) @@ -889,21 +918,25 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] private static byte Avg2(byte a, byte b) { return (byte)((a + b + 1) >> 1); } + [MethodImpl(InliningOptions.ShortMethod)] private static byte Avg3(byte a, byte b, byte c) { return (byte)((a + (2 * b) + c + 2) >> 2); } + [MethodImpl(InliningOptions.ShortMethod)] private static void Dst(Span dst, int x, int y, byte v) { dst[x + (y * WebPConstants.Bps)] = v; } + [MethodImpl(InliningOptions.ShortMethod)] private static int Clamp255(int x) { return x < 0 ? 0 : (x > 255 ? 255 : x); diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 365ab3bdd..742125963 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -139,6 +140,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return (v ^ (int)mask) - (int)mask; } + [MethodImpl(InliningOptions.ShortMethod)] public bool ReadBool() { return this.ReadValue(1) is 1; @@ -215,6 +217,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + [MethodImpl(InliningOptions.ShortMethod)] private ulong ByteSwap64(ulong x) { x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); @@ -224,6 +227,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). + [MethodImpl(InliningOptions.ShortMethod)] private int BitsLog2Floor(uint n) { int logValue = 0; diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 6f3e4610a..8990de769 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -142,6 +143,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Reads a single bit from the stream. ///
/// True if the bit read was 1, false otherwise. + [MethodImpl(InliningOptions.ShortMethod)] public bool ReadBit() { uint bit = this.ReadValue(1); @@ -152,6 +154,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow. /// /// The number of bits to advance the position. + [MethodImpl(InliningOptions.ShortMethod)] public void AdvanceBitPosition(int numberOfBits) { this.bitPos += numberOfBits; @@ -161,6 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Return the pre-fetched bits, so they can be looked up. /// /// The pre-fetched bits. + [MethodImpl(InliningOptions.ShortMethod)] public ulong PrefetchBits() { return this.value >> (this.bitPos & (Lbits - 1)); diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 1f0d36329..c25d11f39 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -960,12 +961,14 @@ namespace SixLabors.ImageSharp.Formats.WebP return tableSpan[0].Value; } + [MethodImpl(InliningOptions.ShortMethod)] private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) { uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); } + [MethodImpl(InliningOptions.ShortMethod)] private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) { if (bits is 0) @@ -1006,12 +1009,14 @@ namespace SixLabors.ImageSharp.Formats.WebP return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); } + [MethodImpl(InliningOptions.ShortMethod)] private int GetCopyLength(int lengthSymbol) { // Length and distance prefixes are encoded the same way. return this.GetCopyDistance(lengthSymbol); } + [MethodImpl(InliningOptions.ShortMethod)] private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; @@ -1019,6 +1024,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return hCode.BitsUsed; } + [MethodImpl(InliningOptions.ShortMethod)] private static byte GetAlphaValue(int val) { return (byte)((val >> 8) & 0xff); diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 4de0beaf8..c16ee86fd 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -1362,6 +1363,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return io; } + [MethodImpl(InliningOptions.ShortMethod)] private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) { nzCoeffs <<= 2; @@ -1369,12 +1371,14 @@ namespace SixLabors.ImageSharp.Formats.WebP return nzCoeffs; } + [MethodImpl(InliningOptions.ShortMethod)] private static Vp8BandProbas[] GetBandsRow(Vp8BandProbas[,] bands, int rowIdx) { Vp8BandProbas[] bandsRow = Enumerable.Range(0, bands.GetLength(1)).Select(x => bands[rowIdx, x]).ToArray(); return bandsRow; } + [MethodImpl(InliningOptions.ShortMethod)] private static int CheckMode(int mbx, int mby, int mode) { // B_DC_PRED @@ -1395,6 +1399,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return mode; } + [MethodImpl(InliningOptions.ShortMethod)] private static int Clip(int value, int max) { return value < 0 ? 0 : value > max ? max : value; From aaa9e07416d3d8590cb94be49d0b2e81ef439135 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 18 Mar 2020 07:29:50 +0100 Subject: [PATCH 145/359] Refactor parsing VP8X header --- .../Formats/WebP/WebPDecoderCore.cs | 150 ++++++++++-------- .../Formats/WebP/WebPLossyDecoder.cs | 6 + .../Codecs/DecodeWebp.cs | 3 +- 3 files changed, 93 insertions(+), 66 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 2971b2638..e6f5dfe3e 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -14,7 +14,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Performs the bitmap decoding operation. + /// Performs the webp decoding operation. /// internal sealed class WebPDecoderCore { @@ -140,6 +140,10 @@ namespace SixLabors.ImageSharp.Formats.WebP return chunkSize; } + /// + /// Reads information present in the image header, about the image content and how to decode the image. + /// + /// Information about the webp image. private WebPImageInfo ReadVp8Info() { this.Metadata = new ImageMetadata(); @@ -173,29 +177,39 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Information about this webp image. private WebPImageInfo ReadVp8XHeader() { + var features = new WebPFeatures(); uint chunkSize = this.ReadChunkSize(); // The first byte contains information about the image features used. - // The first two bit of it are reserved and should be 0. TODO: should an exception be thrown if its not the case, or just ignore it? byte imageFeatures = (byte)this.currentStream.ReadByte(); + // The first two bit of it are reserved and should be 0. + if (imageFeatures >> 6 != 0) + { + WebPThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); + } + // If bit 3 is set, a ICC Profile Chunk should be present. - bool isIccPresent = (imageFeatures & (1 << 5)) != 0; + features.IccProfile = (imageFeatures & (1 << 5)) != 0; // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk). - bool isAlphaPresent = (imageFeatures & (1 << 4)) != 0; + features.Alpha = (imageFeatures & (1 << 4)) != 0; // If bit 5 is set, a EXIF metadata should be present. - bool isExifPresent = (imageFeatures & (1 << 3)) != 0; + features.ExifProfile = (imageFeatures & (1 << 3)) != 0; // If bit 6 is set, XMP metadata should be present. - bool isXmpPresent = (imageFeatures & (1 << 2)) != 0; + features.XmpMetaData = (imageFeatures & (1 << 2)) != 0; // If bit 7 is set, animation should be present. - bool isAnimationPresent = (imageFeatures & (1 << 1)) != 0; + features.Animation = (imageFeatures & (1 << 1)) != 0; // 3 reserved bytes should follow which are supposed to be zero. this.currentStream.Read(this.buffer, 0, 3); + if (this.buffer[0] != 0 || this.buffer[1] != 0 | this.buffer[2] != 0) + { + WebPThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); + } // 3 bytes for the width. this.currentStream.Read(this.buffer, 0, 3); @@ -208,76 +222,25 @@ namespace SixLabors.ImageSharp.Formats.WebP uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // Optional chunks ICCP, ALPH and ANIM can follow here. - WebPChunkType chunkType; - if (isIccPresent) + WebPChunkType chunkType = this.ReadChunkType(); + while (IsOptionalVp8XChunk(chunkType)) { + this.ParseOptionalExtendedChunks(chunkType, features); chunkType = this.ReadChunkType(); - if (chunkType is WebPChunkType.Iccp) - { - uint iccpChunkSize = this.ReadChunkSize(); - if (!this.IgnoreMetadata) - { - var iccpData = new byte[iccpChunkSize]; - this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); - var profile = new IccProfile(iccpData); - if (profile.CheckIsValid()) - { - this.Metadata.IccProfile = profile; - } - } - else - { - this.currentStream.Skip((int)iccpChunkSize); - } - } } - if (isAnimationPresent) + if (features.Animation) { - this.webpMetadata.Animated = true; - + // TODO: Animations are not yet supported. return new WebPImageInfo() { Width = width, Height = height, - Features = new WebPFeatures() - { - Animation = true - } + Features = features }; } - byte[] alphaData = null; - byte alphaChunkHeader = 0; - if (isAlphaPresent) - { - chunkType = this.ReadChunkType(); - if (chunkType != WebPChunkType.Alpha) - { - WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing"); - } - - uint alphaChunkSize = this.ReadChunkSize(); - alphaChunkHeader = (byte)this.currentStream.ReadByte(); - alphaData = new byte[alphaChunkSize - 1]; - this.currentStream.Read(alphaData, 0, alphaData.Length); - } - - var features = new WebPFeatures() - { - Animation = isAnimationPresent, - Alpha = isAlphaPresent, - AlphaData = alphaData, - AlphaChunkHeader = alphaChunkHeader, - ExifProfile = isExifPresent, - IccProfile = isIccPresent, - XmpMetaData = isXmpPresent - }; - - // A VP8 or VP8L chunk should follow here. - chunkType = this.ReadChunkType(); - - // TOOD: check if VP8 or VP8L info about the dimensions match VP8X info + // TODO: check if VP8 or VP8L info about the dimensions match VP8X info switch (chunkType) { case WebPChunkType.Vp8: @@ -446,6 +409,47 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + /// + /// Parses optional VP8X chunks, which can be ICCP, ANIM or ALPH chunks. + /// + /// The chunk type. + /// The webp image features. + private void ParseOptionalExtendedChunks(WebPChunkType chunkType, WebPFeatures features) + { + switch (chunkType) + { + case WebPChunkType.Iccp: + uint iccpChunkSize = this.ReadChunkSize(); + if (!this.IgnoreMetadata) + { + var iccpData = new byte[iccpChunkSize]; + this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); + var profile = new IccProfile(iccpData); + if (profile.CheckIsValid()) + { + this.Metadata.IccProfile = profile; + } + } + else + { + this.currentStream.Skip((int)iccpChunkSize); + } + + break; + + case WebPChunkType.Animation: + this.webpMetadata.Animated = true; + break; + + case WebPChunkType.Alpha: + uint alphaChunkSize = this.ReadChunkSize(); + features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); + features.AlphaData = new byte[alphaChunkSize - 1]; + this.currentStream.Read(features.AlphaData, 0, features.AlphaData.Length); + break; + } + } + /// /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). /// If there are more such chunks, readers MAY ignore all except the first one. @@ -512,5 +516,21 @@ namespace SixLabors.ImageSharp.Formats.WebP throw new ImageFormatException("Invalid WebP data."); } + + /// + /// Determines if the chunk type is an optional VP8X chunk. + /// + /// The chunk type. + /// True, if its an optional chunk type. + private static bool IsOptionalVp8XChunk(WebPChunkType chunkType) + { + return chunkType switch + { + WebPChunkType.Alpha => true, + WebPChunkType.Animation => true, + WebPChunkType.Iccp => true, + _ => false + }; + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index c16ee86fd..0b26727d5 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -12,6 +12,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp + /// + /// + /// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386 + /// internal sealed class WebPLossyDecoder { /// diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 5ec22cbad..ff1307b54 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -18,9 +18,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private byte[] webpLosslessBytes; private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); + private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); - [Params(TestImages.WebP.Lossy.Bike)] + [Params(TestImages.WebP.Lossy.Alpha1)] public string TestImageLossy { get; set; } [Params(TestImages.WebP.Lossless.BikeThreeTransforms)] From cd8431f1222322d947277b11f575f2bf213dce20 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 18 Mar 2020 08:05:20 +0100 Subject: [PATCH 146/359] Add bigger webp benchmark files --- .../Codecs/DecodeWebp.cs | 28 +++++++++++++++++-- tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/WebP/earth_lossless.webp | 3 ++ tests/Images/Input/WebP/earth_lossy.webp | 3 ++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/WebP/earth_lossless.webp create mode 100644 tests/Images/Input/WebP/earth_lossy.webp diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index ff1307b54..0bd0c4e8d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); - [Params(TestImages.WebP.Lossy.Alpha1)] + [Params(TestImages.WebP.Lossy.Earth)] public string TestImageLossy { get; set; } - [Params(TestImages.WebP.Lossless.BikeThreeTransforms)] + [Params(TestImages.WebP.Lossless.Earth)] public string TestImageLossless { get; set; } [GlobalSetup] @@ -84,5 +84,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } } + + /* Results 18.03.2020 + * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 + Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores + .NET Core SDK=3.1.200 + [Host] : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + Job-TLYXIR : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT + Job-HPKRXU : .NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT + Job-OBFQMR : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + | Method | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |-------------- |--------------------- |--------------------- |----------:|----------:|---------:|-----------:|----------:|----------:|-------------:| + | 'Magick Lossy WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 70.37 ms | 9.234 ms | 0.506 ms | - | - | - | 32.05 KB | + | 'ImageSharp Lossy Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 211.77 ms | 8.055 ms | 0.442 ms | 19000.0000 | - | - | 82297.31 KB | + | 'Magick Lossless WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.35 ms | 1.099 ms | 0.060 ms | - | - | - | 15.32 KB | + | 'ImageSharp Lossless Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 494.34 ms | 5.505 ms | 0.302 ms | 2000.0000 | 1000.0000 | 1000.0000 | 151801.78 KB | + | 'Magick Lossy WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 70.21 ms | 1.440 ms | 0.079 ms | - | - | - | 14.8 KB | + | 'ImageSharp Lossy Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 142.32 ms | 6.046 ms | 0.331 ms | 9000.0000 | - | - | 40610.23 KB | + | 'Magick Lossless WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.44 ms | 0.258 ms | 0.014 ms | - | - | - | 14.3 KB | + | 'ImageSharp Lossless Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 206.45 ms | 11.093 ms | 0.608 ms | 2666.6667 | 1666.6667 | 1000.0000 | 151758.87 KB | + | 'Magick Lossy WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 69.69 ms | 1.147 ms | 0.063 ms | - | - | - | 14.42 KB | + | 'ImageSharp Lossy Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 121.72 ms | 2.373 ms | 0.130 ms | 9000.0000 | - | - | 40050.06 KB | + | 'Magick Lossless WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.37 ms | 1.865 ms | 0.102 ms | - | - | - | 14.27 KB | + | 'ImageSharp Lossless Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 194.03 ms | 37.759 ms | 2.070 ms | 2000.0000 | 1000.0000 | 1000.0000 | 151756.38 KB | + */ } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 517fe38f1..e57d26817 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -404,6 +404,7 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { + public const string Earth = "WebP/earth_lossless.webp"; public const string WithExif = "WebP/exif_lossless.webp"; public const string WithIccp = "WebP/iccp_lossless.webp"; public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; @@ -454,6 +455,7 @@ namespace SixLabors.ImageSharp.Tests public static class Lossy { + public const string Earth = "WebP/earth_lossy.webp"; public const string WithExif = "WebP/exif_lossy.webp"; public const string WithIccp = "WebP/iccp_lossy.webp"; diff --git a/tests/Images/Input/WebP/earth_lossless.webp b/tests/Images/Input/WebP/earth_lossless.webp new file mode 100644 index 000000000..8729ece9c --- /dev/null +++ b/tests/Images/Input/WebP/earth_lossless.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ea7fad78cd68e27c1616f4a99de52397f5485259c7adbd674a5c9a362885216 +size 2447273 diff --git a/tests/Images/Input/WebP/earth_lossy.webp b/tests/Images/Input/WebP/earth_lossy.webp new file mode 100644 index 000000000..8f1cebc3f --- /dev/null +++ b/tests/Images/Input/WebP/earth_lossy.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71263c0db7b8cd2e787b8554fd40aff1d46a753646fb2062966ca07ac040e841 +size 852402 From 842f2b2354d893278103904d1734f352a65e1de7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 18 Mar 2020 15:41:19 +0100 Subject: [PATCH 147/359] Use memory allocator for alpha chunk and for the bitreader data --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 12 +++++++----- src/ImageSharp/Formats/WebP/BitReaderBase.cs | 17 +++++++++++------ src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 10 +++++----- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 14 +++++++++----- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 17 +++++++---------- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 13 +++++++++++-- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 16 +++++++++++++--- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 2 +- 8 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 3a5f0ca4f..6737c4d09 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The first byte of the alpha image stream contains information on ow to decode the stream. /// Used for allocating memory during decoding. /// The configuration. - public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) + public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) { this.Width = width; this.Height = height; @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets the (maybe compressed) alpha data. /// - private byte[] Data { get; } + private IMemoryOwner Data { get; } /// /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. @@ -137,7 +137,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (this.Compressed is false) { - if (this.Data.Length < (this.Width * this.Height)) + Span dataSpan = this.Data.Memory.Span; + if (dataSpan.Length < (this.Width * this.Height)) { WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } @@ -145,11 +146,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Span alphaSpan = this.Alpha.Memory.Span; if (this.AlphaFilterType == WebPAlphaFilterType.None) { - this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan); + dataSpan.Slice(0, this.Width * this.Height).CopyTo(alphaSpan); return; } - Span deltas = this.Data.AsSpan(); + Span deltas = dataSpan; Span dst = alphaSpan; Span prev = null; for (int y = 0; y < this.Height; ++y) @@ -305,6 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Dispose() { this.Vp8LDec?.Dispose(); + this.Data.Dispose(); this.Alpha?.Dispose(); } } diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs index 3a5bf4f4a..f35368c15 100644 --- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using SixLabors.ImageSharp.Memory; @@ -11,12 +12,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Base class for VP8 and VP8L bitreader. /// - internal abstract class BitReaderBase + internal abstract class BitReaderBase : IDisposable { /// /// Gets or sets the raw encoded image data. /// - public byte[] Data { get; set; } + public IMemoryOwner Data { get; set; } /// /// Copies the raw encoded image data from the stream into a byte array. @@ -26,24 +27,28 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Used for allocating memory during reading data from the stream. protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) { - using (var ms = new MemoryStream()) + this.Data = memoryAllocator.Allocate(bytesToRead); + Span dataSpan = this.Data.Memory.Span; + 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) { - ms.Write(buffer.Array, 0, read); + buffer.Array.AsSpan(0, read).CopyTo(dataSpan); bytesToRead -= read; + dataSpan = dataSpan.Slice(read); } if (bytesToRead > 0) { WebPThrowHelper.ThrowImageFormatException("image file has insufficient data"); } - - this.Data = ms.ToArray(); } } + + /// + public void Dispose() => this.Data?.Dispose(); } } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 742125963..66681b89d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The raw encoded image data. /// The partition length. /// Start index in the data array. Defaults to 0. - public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0) + public Vp8BitReader(IMemoryOwner imageData, uint partitionLength, int startPos = 0) { this.Data = imageData; - this.ImageDataSize = (uint)imageData.Length; + this.ImageDataSize = (uint)imageData.Memory.Length; this.PartitionLength = partitionLength; this.InitBitreader(partitionLength, startPos); } @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (this.pos < this.bufferMax) { - ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8)); + ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.Memory.Span.Slice((int)this.pos, 8)); this.pos += BitsCount >> 3; ulong bits = this.ByteSwap64(inBits); bits >>= 64 - BitsCount; @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.pos < this.bufferEnd) { this.bits += 8; - this.value = this.Data[this.pos++] | (this.value << 8); + this.value = this.Data.Memory.Span[(int)this.pos++] | (this.value << 8); } else if (!this.eof) { diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 8990de769..f90023b89 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -62,18 +63,19 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Initializes a new instance of the class. /// /// Lossless compressed image data. - public Vp8LBitReader(byte[] data) + public Vp8LBitReader(IMemoryOwner data) { this.Data = data; - this.len = data.Length; + this.len = data.Memory.Length; this.value = 0; this.bitPos = 0; this.Eos = false; ulong currentValue = 0; + System.Span dataSpan = this.Data.Memory.Span; for (int i = 0; i < 8; ++i) { - currentValue |= (ulong)this.Data[i] << (8 * i); + currentValue |= (ulong)dataSpan[i] << (8 * i); } this.value = currentValue; @@ -103,9 +105,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } ulong currentValue = 0; + System.Span dataSpan = this.Data.Memory.Span; for (int i = 0; i < length; ++i) { - currentValue |= (ulong)this.Data[i] << (8 * i); + currentValue |= (ulong)dataSpan[i] << (8 * i); } this.value = currentValue; @@ -200,10 +203,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private void ShiftBytes() { + System.Span dataSpan = this.Data.Memory.Span; while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; - this.value |= (ulong)this.Data[this.pos] << (Lbits - 8); + this.value |= (ulong)dataSpan[(int)this.pos] << (Lbits - 8); ++this.pos; this.bitPos -= 8; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index e6f5dfe3e..f0060edaf 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream = stream; uint fileSize = this.ReadImageHeader(); - WebPImageInfo imageInfo = this.ReadVp8Info(); + using var imageInfo = this.ReadVp8Info(); if (imageInfo.Features != null && imageInfo.Features.Animation) { WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); @@ -186,7 +186,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // The first two bit of it are reserved and should be 0. if (imageFeatures >> 6 != 0) { - WebPThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); + WebPThrowHelper.ThrowImageFormatException( + "first two bits of the VP8X header are expected to be zero"); } // If bit 3 is set, a ICC Profile Chunk should be present. @@ -232,12 +233,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (features.Animation) { // TODO: Animations are not yet supported. - return new WebPImageInfo() - { - Width = width, - Height = height, - Features = features - }; + return new WebPImageInfo() { Width = width, Height = height, Features = features }; } // TODO: check if VP8 or VP8L info about the dimensions match VP8X info @@ -444,8 +440,9 @@ namespace SixLabors.ImageSharp.Formats.WebP case WebPChunkType.Alpha: uint alphaChunkSize = this.ReadChunkSize(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); - features.AlphaData = new byte[alphaChunkSize - 1]; - this.currentStream.Read(features.AlphaData, 0, features.AlphaData.Length); + var alphaDataSize = (int)(alphaChunkSize - 1); + features.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); + this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); break; } } diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 3fd035076..cf1053057 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -1,12 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; + namespace SixLabors.ImageSharp.Formats.WebP { /// /// Image features of a VP8X image. /// - internal class WebPFeatures + internal class WebPFeatures : IDisposable { /// /// Gets or sets a value indicating whether this image has a ICC Profile. @@ -21,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the alpha data, if an ALPH chunk is present. /// - public byte[] AlphaData { get; set; } + public IMemoryOwner AlphaData { get; set; } /// /// Gets or sets the alpha chunk header. @@ -42,5 +45,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets a value indicating whether this image is a animation. /// public bool Animation { get; set; } + + /// + public void Dispose() + { + this.AlphaData?.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 19a72c0c2..b7aa4b36a 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -1,17 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Formats.WebP { - internal class WebPImageInfo + internal class WebPImageInfo : IDisposable { /// - /// Gets or sets the bitmap width in pixels (signed integer). + /// Gets or sets the bitmap width in pixels. /// public uint Width { get; set; } /// - /// Gets or sets the bitmap height in pixels (signed integer). + /// Gets or sets the bitmap height in pixels. /// public uint Height { get; set; } @@ -53,5 +55,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Gets or sets the VP8 bitreader. Will be null, if its not a lossy image. /// public Vp8BitReader Vp8BitReader { get; set; } = null; + + /// + public void Dispose() + { + this.Vp8BitReader?.Dispose(); + this.Vp8LBitReader?.Dispose(); + this.Features?.AlphaData?.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 0b26727d5..a741ad42d 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1195,7 +1195,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; int startIdx = (int)this.bitReader.PartitionLength; - Span sz = this.bitReader.Data.AsSpan(startIdx); + Span sz = this.bitReader.Data.Slice(startIdx); int sizeLeft = (int)size; dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; int lastPart = dec.NumPartsMinusOne; From 53f42bc5af7dfcaa3347cbb2ddb791d5562157dc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2020 12:24:24 +0100 Subject: [PATCH 148/359] Review changes for the AlphaDecoder --- .../Formats/WebP/AlphaCompressionMethod.cs | 15 ++++++ src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 52 +++++++------------ .../Formats/WebP/WebPAlphaFilterType.cs | 2 +- 3 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs new file mode 100644 index 000000000..52e3e3b61 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -0,0 +1,15 @@ +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum AlphaCompressionMethod + { + /// + /// No compression. + /// + NoCompression = 0, + + /// + /// Compressed using the WebP lossless format. + /// + WebPLosslessCompression = 1 + } +} diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 6737c4d09..9d0b68e79 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The width of the image. /// The height of the image. /// The (maybe compressed) alpha data. - /// The first byte of the alpha image stream contains information on ow to decode the stream. + /// The first byte of the alpha image stream contains information on how to decode the stream. /// Used for allocating memory during decoding. /// The configuration. public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) @@ -30,32 +30,24 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Data = data; this.LastRow = 0; - // Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format) - int method = alphaChunkHeader & 0x03; - if (method < 0 || method > 1) + var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03); + if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression) { - WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found"); + WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); } - this.Compressed = !(method is 0); + this.Compressed = compression is AlphaCompressionMethod.WebPLosslessCompression; - // The filtering method used. Only values between 0 and 3 are valid. + // The filtering method used. Only values between 0 and 3 are valid. int filter = (alphaChunkHeader >> 2) & 0x03; - if (filter < 0 || filter > 3) + if (filter < (int)WebPAlphaFilterType.None || filter > (int)WebPAlphaFilterType.Gradient) { WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } this.AlphaFilterType = (WebPAlphaFilterType)filter; - - // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. - // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. - // 0: no pre-processing, 1: level reduction - this.PreProcessing = (alphaChunkHeader >> 4) & 0x03; - - this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); - this.Alpha = memoryAllocator.Allocate(width * height); + this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); if (this.Compressed) { @@ -102,12 +94,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public int CropTop { get; } - /// - /// Gets a value indicating whether pre-processing was used during compression. - /// 0: no pre-processing, 1: level reduction. - /// - private int PreProcessing { get; } - /// /// Gets a value indicating whether the alpha channel uses compression. /// @@ -138,15 +124,16 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.Compressed is false) { Span dataSpan = this.Data.Memory.Span; - if (dataSpan.Length < (this.Width * this.Height)) + var pixelCount = this.Width * this.Height; + if (dataSpan.Length < pixelCount) { WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } Span alphaSpan = this.Alpha.Memory.Span; - if (this.AlphaFilterType == WebPAlphaFilterType.None) + if (this.AlphaFilterType is WebPAlphaFilterType.None) { - dataSpan.Slice(0, this.Width * this.Height).CopyTo(alphaSpan); + dataSpan.Slice(0, pixelCount).CopyTo(alphaSpan); return; } @@ -194,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } Span alphaSpan = this.Alpha.Memory.Span; - Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); + Span prev = this.PrevRow is 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); for (int y = firstRow; y < lastRow; ++y) { switch (this.AlphaFilterType) @@ -223,8 +210,9 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < width; ++i) { - dst[i] = (byte)(pred + input[i]); - pred = dst[i]; + byte val = (byte)(pred + input[i]); + pred = val; + dst[i] = val; } } @@ -251,12 +239,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - byte top = prev[0]; - byte topLeft = top; - byte left = top; + byte prev0 = prev[0]; + byte topLeft = prev0; + byte left = prev0; for (int i = 0; i < width; ++i) { - top = prev[i]; + byte top = prev[i]; left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); topLeft = top; dst[i] = left; diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs index dfdc8281c..62e3de07b 100644 --- a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Enum for the different alpha filter types. /// - internal enum WebPAlphaFilterType + internal enum WebPAlphaFilterType : int { /// /// No filtering. From 83d82360eb33c3e32c16123af3944b9fe11348df Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2020 14:19:18 +0100 Subject: [PATCH 149/359] Fix issue with DecodeAlphaData when copying a block --- .../Formats/WebP/WebPLosslessDecoder.cs | 130 +++++++++++------- .../Formats/WebP/WebPDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + 3 files changed, 80 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index c25d11f39..6783c3f58 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ColorCache colorCache = decoder.Metadata.ColorCache; int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; - HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + HTreeGroup[] hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); int totalPixels = width * height; int decodedPixels = 0; @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int code; if ((col & mask) is 0) { - hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } if (hTreeGroup[0].IsTrivialCode) @@ -295,13 +295,13 @@ namespace SixLabors.ImageSharp.Formats.WebP uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); int distCode = this.GetCopyDistance((int)distSymbol); - int dist = this.PlaneCodeToDistance(width, distCode); + int dist = PlaneCodeToDistance(width, distCode); if (this.bitReader.IsEndOfStream()) { break; } - this.CopyBlock(pixelData, decodedPixels, dist, length); + CopyBlock(pixelData, decodedPixels, dist, length); decodedPixels += length; col += length; while (col >= width) @@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if ((col & mask) != 0) { - hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } if (colorCache != null) @@ -701,6 +701,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// The alpha channel of a lossy webp image can be compressed using the lossless webp compression. + /// This method will undo the compression. + /// + /// The alpha decoder. public void DecodeAlphaData(AlphaDecoder dec) { Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; @@ -717,13 +722,13 @@ namespace SixLabors.ImageSharp.Formats.WebP int lastRow = height; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int mask = hdr.HuffmanMask; - HTreeGroup[] htreeGroup = (pos < last) ? this.GetHTreeGroupForPos(hdr, col, row) : null; + HTreeGroup[] htreeGroup = (pos < last) ? GetHTreeGroupForPos(hdr, col, row) : null; while (!this.bitReader.Eos && pos < last) { // Only update when changing tile. if ((col & mask) is 0) { - htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } this.bitReader.FillBitWindow(); @@ -753,10 +758,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); int distCode = this.GetCopyDistance(distSymbol); - int dist = this.PlaneCodeToDistance(width, distCode); + int dist = PlaneCodeToDistance(width, distCode); if (pos >= dist && end - pos >= length) { - data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); + CopyBlock8B(data, pos, dist, length); } else { @@ -777,7 +782,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (pos < last && (col & mask) > 0) { - htreeGroup = this.GetHTreeGroupForPos(hdr, col, row); + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } } else @@ -802,14 +807,18 @@ namespace SixLabors.ImageSharp.Formats.WebP int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { - // Special method for paletted alpha data. We only process the cropped area. + // Special method for paletted alpha data. Span output = dec.Alpha.Memory.Span; Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); Span dst = output.Slice(dec.Width * firstRow); Span input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow); - // TODO: check if any and the correct transform is present + if (dec.Vp8LDec.Transforms.Count is 0 || dec.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) + { + WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); + } + Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); @@ -896,25 +905,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return code.Value; } - private void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) - { - if (dist >= length) - { - Span src = pixelData.Slice(decodedPixels - dist, length); - Span dest = pixelData.Slice(decodedPixels); - src.CopyTo(dest); - } - else - { - Span src = pixelData.Slice(decodedPixels - dist); - Span dest = pixelData.Slice(decodedPixels); - for (int i = 0; i < length; ++i) - { - dest[i] = src[i]; - } - } - } - private void BuildPackedTable(HTreeGroup hTreeGroup) { for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) @@ -931,10 +921,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { huff.BitsUsed = 0; huff.Value = 0; - bits >>= this.AccumulateHCode(hCode, 8, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); - bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + bits >>= AccumulateHCode(hCode, 8, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); } } } @@ -962,14 +952,34 @@ namespace SixLabors.ImageSharp.Formats.WebP } [MethodImpl(InliningOptions.ShortMethod)] - private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + private int GetCopyLength(int lengthSymbol) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol); + } + + private int GetCopyDistance(int distanceSymbol) { - uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + uint metaIndex = GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); } [MethodImpl(InliningOptions.ShortMethod)] - private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) + private static uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) { if (bits is 0) { @@ -980,7 +990,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } - private int PlaneCodeToDistance(int xSize, int planeCode) + private static int PlaneCodeToDistance(int xSize, int planeCode) { if (planeCode > CodeToPlaneCodes) { @@ -996,28 +1006,44 @@ namespace SixLabors.ImageSharp.Formats.WebP return (dist >= 1) ? dist : 1; } - private int GetCopyDistance(int distanceSymbol) + private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { - if (distanceSymbol < 4) + if (dist >= length) { - return distanceSymbol + 1; + Span src = pixelData.Slice(decodedPixels - dist, length); + Span dest = pixelData.Slice(decodedPixels); + src.CopyTo(dest); + } + else + { + Span src = pixelData.Slice(decodedPixels - dist); + Span dest = pixelData.Slice(decodedPixels); + for (int i = 0; i < length; ++i) + { + dest[i] = src[i]; + } } - - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; - - return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); } - [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyLength(int lengthSymbol) + private static void CopyBlock8B(Span data, int pos, int dist, int length) { - // Length and distance prefixes are encoded the same way. - return this.GetCopyDistance(lengthSymbol); + if (dist >= length) + { + data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); + } + else + { + Span dst = data.Slice(pos); + Span src = data.Slice(pos - dist); + for (int i = 0; i < length; ++i) + { + dst[i] = src[i]; + } + } } [MethodImpl(InliningOptions.ShortMethod)] - private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + private static int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; huff.Value |= hCode.Value << shift; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index f2c2d9c2d..60ff49049 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -178,6 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e57d26817..7c2f4ebbf 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -524,6 +524,7 @@ namespace SixLabors.ImageSharp.Tests // Lossy images with an alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha2 = "WebP/lossy_alpha2.webp"; + public const string Alpha3 = "WebP/lossy_alpha3.webp"; public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp"; From f53671ad1fa1aa7f2713d50076f575ca9f091e14 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 21 Mar 2020 13:03:07 +0100 Subject: [PATCH 150/359] alpha_color_cache.webp now decodes correctly --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 64 ++++++++++++++++--- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 + .../Formats/WebP/WebPLosslessDecoder.cs | 19 +++--- .../Formats/WebP/WebPDecoderTests.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 2 +- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 9d0b68e79..0163d74c0 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -14,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class AlphaDecoder : IDisposable { + private readonly MemoryAllocator memoryAllocator; + /// /// Initializes a new instance of the class. /// @@ -28,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Width = width; this.Height = height; this.Data = data; + this.memoryAllocator = memoryAllocator; this.LastRow = 0; + int totalPixels = width * height; var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03); if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression) @@ -45,8 +50,8 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } + this.Alpha = memoryAllocator.Allocate(totalPixels); this.AlphaFilterType = (WebPAlphaFilterType)filter; - this.Alpha = memoryAllocator.Allocate(width * height); this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); if (this.Compressed) @@ -54,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8LBitReader(data); this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); + this.Use8BDecode = Is8BOptimizable(this.Vp8LDec.Metadata); } } @@ -110,11 +116,11 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPLosslessDecoder LosslessDecoder { get; } /// - /// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding. + /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate /// 4 bytes per pixel internally during decode. /// - public bool Use8BDecode { get; set; } + public bool Use8BDecode { get; } /// /// Decodes and filters the maybe compressed alpha data. @@ -162,7 +168,15 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - this.LosslessDecoder.DecodeAlphaData(this); + if (this.Use8BDecode) + { + this.LosslessDecoder.DecodeAlphaData(this); + } + else + { + this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); + this.ExtractAlphaRows(this.Vp8LDec); + } } } @@ -204,6 +218,25 @@ namespace SixLabors.ImageSharp.Formats.WebP this.PrevRow = lastRow - 1; } + /// + /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. + /// + /// The VP8L decoder. + private void ExtractAlphaRows(Vp8LDecoder dec) + { + int numRowsToProcess = dec.Height; + int width = dec.Width; + Span pixels = dec.Pixels.Memory.Span; + Span input = pixels; + Span output = this.Alpha.Memory.Span; + + // Extract alpha (which is stored in the green plane). + int pixelCount = width * numRowsToProcess; + WebPLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); + this.AlphaApplyFilter(0, numRowsToProcess, output, width); + ExtractGreen(input, output, pixelCount); + } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { byte pred = (byte)(prev == null ? 0 : prev[0]); @@ -252,7 +285,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static bool Is8bOptimizable(Vp8LMetadata hdr) + /// + /// Row-processing for the special case when alpha data contains only one + /// transform (color indexing), and trivial non-green literals. + /// + /// The VP8L meta data. + /// True, if alpha channel has one byte per pixel, otherwise 4. + private static bool Is8BOptimizable(Vp8LMetadata hdr) { if (hdr.ColorCacheSize > 0) { @@ -264,17 +303,17 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < hdr.NumHTreeGroups; ++i) { List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].Value > 0) + if (htrees[HuffIndex.Red][0].BitsUsed > 0) { return false; } - if (htrees[HuffIndex.Blue][0].Value > 0) + if (htrees[HuffIndex.Blue][0].BitsUsed > 0) { return false; } - if (htrees[HuffIndex.Alpha][0].Value > 0) + if (htrees[HuffIndex.Alpha][0].BitsUsed > 0) { return false; } @@ -290,6 +329,15 @@ namespace SixLabors.ImageSharp.Formats.WebP return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit } + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExtractGreen(Span argb, Span alpha, int size) + { + for (int i = 0; i < size; ++i) + { + alpha[i] = (byte)(argb[i] >> 8); + } + } + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 4908c133b..78519da82 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -85,6 +85,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const uint ArgbBlack = 0xff000000; + public const int NumArgbCacheRows = 16; + public const int NumLiteralCodes = 256; public const int NumLengthCodes = 24; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 6783c3f58..2c0b69911 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Formats.WebP private static readonly int BitsSpecialMarker = 0x100; - private static readonly int NumArgbCacheRows = 16; - private static readonly uint PackedNonLiteralCode = 0; private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; @@ -191,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = decoder.Width; // Apply reverse transformations, if any are present. - this.ApplyInverseTransforms(decoder, pixelData); + ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); for (int y = 0; y < decoder.Height; y++) @@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodeImageData(Vp8LDecoder decoder, Span pixelData) + public void DecodeImageData(Vp8LDecoder decoder, Span pixelData) { int lastPixel = 0; int width = decoder.Width; @@ -673,7 +671,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. - private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData) + /// The memory allocator is needed to allocate memory during the predictor transform. + public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator) { List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) @@ -682,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (transformType) { case Vp8LTransformType.PredictorTransform: - using (IMemoryOwner output = this.memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) + using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) { LosslessUtils.PredictorInverseTransform(transforms[i], pixelData, output.GetSpan()); } @@ -744,7 +743,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col = 0; ++row; - if (row <= lastRow && (row % NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { this.ExtractPalettedAlphaRows(dec, row); } @@ -774,7 +773,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col -= width; ++row; - if (row <= lastRow && (row % NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { this.ExtractPalettedAlphaRows(dec, row); } @@ -802,8 +801,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal) - ? dec.CropTop - : dec.LastRow; + ? dec.CropTop + : dec.LastRow; int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 60ff49049..4c96b1e41 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -179,6 +179,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7c2f4ebbf..55a9e5e94 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -524,7 +524,7 @@ namespace SixLabors.ImageSharp.Tests // Lossy images with an alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha2 = "WebP/lossy_alpha2.webp"; - public const string Alpha3 = "WebP/lossy_alpha3.webp"; + public const string Alpha3 = "WebP/alpha_color_cache.webp"; public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp"; From d14b9819a5eb2429060d533bb418e84663f0dd97 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 21 Mar 2020 13:14:10 +0100 Subject: [PATCH 151/359] Move alpha decoding related methods into the AlphaDecoder class --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 93 +++++++++++++++++- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 2 +- .../Formats/WebP/WebPLosslessDecoder.cs | 95 +------------------ 3 files changed, 93 insertions(+), 97 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 0163d74c0..fc0bb92af 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -5,7 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP @@ -98,8 +98,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public IMemoryOwner Alpha { get; } - public int CropTop { get; } - /// /// Gets a value indicating whether the alpha channel uses compression. /// @@ -218,6 +216,34 @@ namespace SixLabors.ImageSharp.Formats.WebP this.PrevRow = lastRow - 1; } + public void ExtractPalettedAlphaRows(int lastRow) + { + // For vertical and gradient filtering, we need to decode the part above the + // cropTop row, in order to have the correct spatial predictors. + int topRow = (this.AlphaFilterType is WebPAlphaFilterType.None || this.AlphaFilterType is WebPAlphaFilterType.Horizontal) ? 0 : this.LastRow; + int firstRow = (this.LastRow < topRow) ? topRow : this.LastRow; + if (lastRow > firstRow) + { + // Special method for paletted alpha data. + Span output = this.Alpha.Memory.Span; + Span pixelData = this.Vp8LDec.Pixels.Memory.Span; + Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); + Span dst = output.Slice(this.Width * firstRow); + Span input = pixelDataAsBytes.Slice(this.Vp8LDec.Width * firstRow); + + if (this.Vp8LDec.Transforms.Count is 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) + { + WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); + } + + Vp8LTransform transform = this.Vp8LDec.Transforms[0]; + ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); + this.AlphaApplyFilter(firstRow, lastRow, dst, this.Width); + } + + this.LastRow = lastRow; + } + /// /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. /// @@ -237,6 +263,46 @@ namespace SixLabors.ImageSharp.Formats.WebP ExtractGreen(input, output, pixelCount); } + private static void ColorIndexInverseTransformAlpha( + Vp8LTransform transform, + int yStart, + int yEnd, + Span src, + Span dst) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + Span colorMap = transform.Data.Memory.Span; + int srcOffset = 0; + int dstOffset = 0; + if (bitsPerPixel < 8) + { + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; + for (int y = yStart; y < yEnd; ++y) + { + int packedPixels = 0; + for (int x = 0; x < width; ++x) + { + if ((x & countMask) is 0) + { + packedPixels = src[srcOffset]; + srcOffset++; + } + + dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); + dstOffset++; + packedPixels >>= bitsPerPixel; + } + } + } + else + { + MapAlpha(src, colorMap, dst, yStart, yEnd, width); + } + } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { byte pred = (byte)(prev == null ? 0 : prev[0]); @@ -290,7 +356,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// transform (color indexing), and trivial non-green literals. /// /// The VP8L meta data. - /// True, if alpha channel has one byte per pixel, otherwise 4. + /// True, if alpha channel needs one byte per pixel, otherwise 4. private static bool Is8BOptimizable(Vp8LMetadata hdr) { if (hdr.ColorCacheSize > 0) @@ -322,6 +388,25 @@ namespace SixLabors.ImageSharp.Formats.WebP return true; } + private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) + { + int offset = 0; + for (int y = yStart; y < yEnd; ++y) + { + for (int x = 0; x < width; ++x) + { + dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); + offset++; + } + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte GetAlphaValue(int val) + { + return (byte)((val >> 8) & 0xff); + } + [MethodImpl(InliningOptions.ShortMethod)] private static int GradientPredictor(byte a, byte b, byte c) { diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 720fb57d9..8ad2ef19d 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. + /// If there are not many unique pixel values, it is more efficient to create a color index array and replace the pixel values by the array's indices. /// This will reverse the color index transform. /// /// The transform data contains color table size and the entries in the color table. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 2c0b69911..88c1cd0d1 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -745,7 +745,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ++row; if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { - this.ExtractPalettedAlphaRows(dec, row); + dec.ExtractPalettedAlphaRows(row); } } } @@ -775,7 +775,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ++row; if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) { - this.ExtractPalettedAlphaRows(dec, row); + dec.ExtractPalettedAlphaRows(row); } } @@ -793,90 +793,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Process the remaining rows corresponding to last row-block. - this.ExtractPalettedAlphaRows(dec, row > lastRow ? lastRow : row); - } - - private void ExtractPalettedAlphaRows(AlphaDecoder dec, int lastRow) - { - // For vertical and gradient filtering, we need to decode the part above the - // cropTop row, in order to have the correct spatial predictors. - int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal) - ? dec.CropTop - : dec.LastRow; - int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; - if (lastRow > firstRow) - { - // Special method for paletted alpha data. - Span output = dec.Alpha.Memory.Span; - Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; - Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - Span dst = output.Slice(dec.Width * firstRow); - Span input = pixelDataAsBytes.Slice(dec.Vp8LDec.Width * firstRow); - - if (dec.Vp8LDec.Transforms.Count is 0 || dec.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) - { - WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); - } - - Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; - this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); - dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); - } - - dec.LastRow = lastRow; - } - - private void ColorIndexInverseTransformAlpha( - Vp8LTransform transform, - int yStart, - int yEnd, - Span src, - Span dst) - { - int bitsPerPixel = 8 >> transform.Bits; - int width = transform.XSize; - Span colorMap = transform.Data.Memory.Span; - int srcOffset = 0; - int dstOffset = 0; - if (bitsPerPixel < 8) - { - int pixelsPerByte = 1 << transform.Bits; - int countMask = pixelsPerByte - 1; - int bitMask = (1 << bitsPerPixel) - 1; - for (int y = yStart; y < yEnd; ++y) - { - int packedPixels = 0; - for (int x = 0; x < width; ++x) - { - if ((x & countMask) is 0) - { - packedPixels = src[srcOffset]; - srcOffset++; - } - - dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); - dstOffset++; - packedPixels >>= bitsPerPixel; - } - } - } - else - { - MapAlpha(src, colorMap, dst, yStart, yEnd, width); - } - } - - private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) - { - int offset = 0; - for (int y = yStart; y < yEnd; ++y) - { - for (int x = 0; x < width; ++x) - { - dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); - offset++; - } - } + dec.ExtractPalettedAlphaRows(row > lastRow ? lastRow : row); } private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) @@ -1048,11 +965,5 @@ namespace SixLabors.ImageSharp.Formats.WebP huff.Value |= hCode.Value << shift; return hCode.BitsUsed; } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte GetAlphaValue(int val) - { - return (byte)((val >> 8) & 0xff); - } } } From 0a5daadda16cea60ce6da5ace19061943985f212 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 28 Mar 2020 15:55:48 +0100 Subject: [PATCH 152/359] add missing file header --- src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index 52e3e3b61..abbb7d775 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Formats.WebP { internal enum AlphaCompressionMethod From 58fb9ffb74094c1344064edca47be68504478164 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 28 Mar 2020 18:05:33 +0100 Subject: [PATCH 153/359] fix build warnings --- tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 4c96b1e41..ae85ecb40 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -209,6 +209,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] + // TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work. // [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform( @@ -315,7 +316,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { - Assert.Throws(() => { using (provider.GetImage(WebpDecoder)) { } }); + Assert.Throws( + () => + { + using (provider.GetImage(WebpDecoder)) + { + } + }); } } } From b28ca48f6c44e58ff35e1a9eef8efb025f6e8365 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 28 Mar 2020 21:04:20 +0100 Subject: [PATCH 154/359] improve comments --- src/ImageSharp/Formats/WebP/HTreeGroup.cs | 2 +- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 2 +- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index c311601bb..42e0d9d93 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Gets the Huffman trees. This has a maximum of HuffmanCodesPerMetaCode (5) entry's. + /// Gets the Huffman trees. This has a maximum of (5) entry's. /// public List HTrees { get; } diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 8ad2ef19d..c8b4fcfed 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // We need to load fresh 'packed_pixels' once every // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte - // is a power of 2, so can just use a mask for that, instead of + // is a power of 2, so we can just use a mask for that, instead of // decrementing a counter. if ((x & countMask) is 0) { diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 4788d2b0b..66deea255 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Queue ChunkTypes { get; set; } = new Queue(); /// - /// Gets or sets a value indicating whether the webp file contains a animation. + /// Gets or sets a value indicating whether the webp file contains an animation. /// public bool Animated { get; set; } = false; From 6f4c38fe724ef388397946ee5acaf5f89a66734e Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 28 Mar 2020 21:05:36 +0100 Subject: [PATCH 155/359] add local variables to store values at index once to spare additional indexer access. --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index 3cab68dd1..e9ac2e9fb 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -35,12 +35,13 @@ namespace SixLabors.ImageSharp.Formats.WebP // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) { - if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) + var codeLengthOfSymbol = codeLengths[symbol]; + if (codeLengthOfSymbol > WebPConstants.MaxAllowedCodeLength) { return 0; } - count[codeLengths[symbol]]++; + count[codeLengthOfSymbol]++; } // Error, all code lengths are zeros. @@ -53,19 +54,20 @@ namespace SixLabors.ImageSharp.Formats.WebP offset[1] = 0; for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) { - if (count[len] > (1 << len)) + int codesOfLength = count[len]; + if (codesOfLength > (1 << len)) { return 0; } - offset[len + 1] = offset[len] + count[len]; + offset[len + 1] = offset[len] + codesOfLength; } // Sort symbols by length, by symbol order within each length. for (symbol = 0; symbol < codeLengthsSize; ++symbol) { int symbolCodeLength = codeLengths[symbol]; - if (codeLengths[symbol] > 0) + if (symbolCodeLength > 0) { sorted[offset[symbolCodeLength]++] = symbol; } From 988b9744530ee143b361db442eadaafc52bb1735 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 6 Apr 2020 19:52:33 +0200 Subject: [PATCH 156/359] Review changes --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 20 +- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 33 +-- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 116 ++++----- src/ImageSharp/Formats/WebP/LossyUtils.cs | 203 ++++++++------- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 12 +- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 20 +- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 4 +- src/ImageSharp/Formats/WebP/Vp8Profile.cs | 16 +- src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 12 +- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 10 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 6 +- .../Formats/WebP/WebPDecoderCore.cs | 44 ++-- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 8 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 +- .../Formats/WebP/WebPLosslessDecoder.cs | 90 ++++--- .../Formats/WebP/WebPLossyDecoder.cs | 234 ++++++++++-------- 17 files changed, 450 insertions(+), 384 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index fc0bb92af..756c1d645 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); } - this.Compressed = compression is AlphaCompressionMethod.WebPLosslessCompression; + this.Compressed = compression == AlphaCompressionMethod.WebPLosslessCompression; // The filtering method used. Only values between 0 and 3 are valid. int filter = (alphaChunkHeader >> 2) & 0x03; @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public void Decode() { - if (this.Compressed is false) + if (this.Compressed == false) { Span dataSpan = this.Data.Memory.Span; var pixelCount = this.Width * this.Height; @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } Span alphaSpan = this.Alpha.Memory.Span; - if (this.AlphaFilterType is WebPAlphaFilterType.None) + if (this.AlphaFilterType == WebPAlphaFilterType.None) { dataSpan.Slice(0, pixelCount).CopyTo(alphaSpan); return; @@ -187,13 +187,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The stride to use. public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) { - if (this.AlphaFilterType is WebPAlphaFilterType.None) + if (this.AlphaFilterType == WebPAlphaFilterType.None) { return; } Span alphaSpan = this.Alpha.Memory.Span; - Span prev = this.PrevRow is 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); + Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); for (int y = firstRow; y < lastRow; ++y) { switch (this.AlphaFilterType) @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. - int topRow = (this.AlphaFilterType is WebPAlphaFilterType.None || this.AlphaFilterType is WebPAlphaFilterType.Horizontal) ? 0 : this.LastRow; + int topRow = (this.AlphaFilterType == WebPAlphaFilterType.None || this.AlphaFilterType == WebPAlphaFilterType.Horizontal) ? 0 : this.LastRow; int firstRow = (this.LastRow < topRow) ? topRow : this.LastRow; if (lastRow > firstRow) { @@ -231,7 +231,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Span dst = output.Slice(this.Width * firstRow); Span input = pixelDataAsBytes.Slice(this.Vp8LDec.Width * firstRow); - if (this.Vp8LDec.Transforms.Count is 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) + if (this.Vp8LDec.Transforms.Count == 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) { WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); } @@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int packedPixels = 0; for (int x = 0; x < width; ++x) { - if ((x & countMask) is 0) + if ((x & countMask) == 0) { packedPixels = src[srcOffset]; srcOffset++; @@ -364,8 +364,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return false; } - // When the Huffman tree contains only one symbol, we can skip the - // call to ReadSymbol() for red/blue/alpha channels. for (int i = 0; i < hdr.NumHTreeGroups; ++i) { List htrees = hdr.HTreeGroups[i].HTrees; @@ -411,7 +409,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int GradientPredictor(byte a, byte b, byte c) { int g = a + b - c; - return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index e9ac2e9fb..ee2e5ea6d 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int totalSize = 1 << rootBits; // total size root table + 2nd level table. int len; // current code length. int symbol; // symbol index in original or sorted table. - var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. + var counts = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + var offsets = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) @@ -41,26 +41,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - count[codeLengthOfSymbol]++; + counts[codeLengthOfSymbol]++; } // Error, all code lengths are zeros. - if (count[0] == codeLengthsSize) + if (counts[0] == codeLengthsSize) { return 0; } // Generate offsets into sorted symbol table by code length. - offset[1] = 0; + offsets[1] = 0; for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) { - int codesOfLength = count[len]; + int codesOfLength = counts[len]; if (codesOfLength > (1 << len)) { return 0; } - offset[len + 1] = offset[len] + codesOfLength; + offsets[len + 1] = offsets[len] + codesOfLength; } // Sort symbols by length, by symbol order within each length. @@ -69,12 +69,12 @@ namespace SixLabors.ImageSharp.Formats.WebP int symbolCodeLength = codeLengths[symbol]; if (symbolCodeLength > 0) { - sorted[offset[symbolCodeLength]++] = symbol; + sorted[offsets[symbolCodeLength]++] = symbol; } } // Special case code with only one value. - if (offset[WebPConstants.MaxAllowedCodeLength] is 1) + if (offsets[WebPConstants.MaxAllowedCodeLength] == 1) { var huffmanCode = new HuffmanCode() { @@ -98,15 +98,16 @@ namespace SixLabors.ImageSharp.Formats.WebP // Fill in root table. for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) { + var countsLen = counts[len]; numOpen <<= 1; numNodes += numOpen; - numOpen -= count[len]; + numOpen -= counts[len]; if (numOpen < 0) { return 0; } - for (; count[len] > 0; --count[len]) + for (; countsLen > 0; countsLen--) { var huffmanCode = new HuffmanCode() { @@ -116,6 +117,8 @@ namespace SixLabors.ImageSharp.Formats.WebP ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); } + + counts[len] = countsLen; } // Fill in 2nd level tables and add pointers to root table. @@ -125,19 +128,19 @@ namespace SixLabors.ImageSharp.Formats.WebP { numOpen <<= 1; numNodes += numOpen; - numOpen -= count[len]; + numOpen -= counts[len]; if (numOpen < 0) { return 0; } - for (; count[len] > 0; --count[len]) + for (; counts[len] > 0; --counts[len]) { if ((key & mask) != low) { tableSpan = tableSpan.Slice(tableSize); tablePos += tableSize; - tableBits = NextTableBitSize(count, len, rootBits); + tableBits = NextTableBitSize(counts, len, rootBits); tableSize = 1 << tableBits; totalSize += tableSize; low = key & mask; @@ -185,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Stores code in table[0], table[step], table[2*step], ..., table[end]. + /// Stores code in table[0], table[step], table[2*step], ..., table[end-step]. /// Assumes that end is an integer multiple of step. /// private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index c8b4fcfed..f49000c6e 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal static class LosslessUtils { + private const uint Predictor0 = WebPConstants.ArgbBlack; + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte // is a power of 2, so we can just use a mask for that, instead of // decrementing a counter. - if ((x & countMask) is 0) + if ((x & countMask) == 0) { packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } @@ -72,17 +74,17 @@ namespace SixLabors.ImageSharp.Formats.WebP } decodedPixelData.AsSpan().CopyTo(pixelData); - - return; } - - for (int y = 0; y < height; ++y) + else { - for (int x = 0; x < width; ++x) + for (int y = 0; y < height; ++y) { - uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); - pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; - decodedPixels++; + for (int x = 0; x < width; ++x) + { + uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); + pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; + decodedPixels++; + } } } } @@ -130,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } ++y; - if ((y & mask) is 0) + if ((y & mask) == 0) { predRowIdxStart += tilesPerRow; } @@ -154,9 +156,9 @@ namespace SixLabors.ImageSharp.Formats.WebP uint red = argb >> 16; int newRed = (int)(red & 0xff); int newBlue = (int)argb & 0xff; - newRed += ColorTransformDelta((sbyte)m.GreenToRed, (sbyte)green); + newRed += ColorTransformDelta((sbyte)m.GreenToRed, green); newRed &= 0xff; - newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, (sbyte)green); + newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, green); newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; @@ -176,7 +178,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) { int processedPixels = 0; - int yStart = 0; int width = transform.XSize; Span transformData = transform.Data.GetSpan(); @@ -184,9 +185,8 @@ namespace SixLabors.ImageSharp.Formats.WebP PredictorAdd0(pixelData, processedPixels, 1, output); PredictorAdd1(pixelData, 1, width - 1, output); processedPixels += width; - yStart++; - int y = yStart; + int y = 1; int yEnd = transform.YSize; int tileWidth = 1 << transform.Bits; int mask = tileWidth - 1; @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Formats.WebP processedPixels += width; ++y; - if ((y & mask) is 0) + if ((y & mask) == 0) { // Use the same mask, since tiles are squares. predictorModeIdxBase += tilesPerRow; @@ -295,9 +295,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; + uint pred = Predictor0; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor0(); output[x] = AddPixels(input[x], pred); } } @@ -315,10 +315,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd2(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor2(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor2(output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -326,10 +326,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd3(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor3(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor3(output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -337,10 +337,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd4(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor4(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor4(output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -348,10 +348,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd5(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor5(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor5(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -359,10 +359,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd6(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor6(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor6(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -370,10 +370,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd7(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor7(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor7(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -381,10 +381,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd8(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor8(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor8(output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -392,10 +392,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd9(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor9(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor9(output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -403,10 +403,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd10(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor10(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor10(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -414,10 +414,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd11(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor11(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor11(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -425,10 +425,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd12(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor12(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor12(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } @@ -436,40 +436,28 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd13(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; - int offset = 0; + int offset = startIdx - width; for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor13(output[x - 1], output, startIdx - width + offset++); + uint pred = Predictor13(output[x - 1], output, offset++); output[x] = AddPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor0() - { - return WebPConstants.ArgbBlack; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor1(uint left, Span top) - { - return left; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor2(uint left, Span top, int idx) + private static uint Predictor2(Span top, int idx) { return top[idx]; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor3(uint left, Span top, int idx) + private static uint Predictor3(Span top, int idx) { return top[idx + 1]; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor4(uint left, Span top, int idx) + private static uint Predictor4(Span top, int idx) { return top[idx - 1]; } @@ -496,14 +484,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor8(uint left, Span top, int idx) + private static uint Predictor8(Span top, int idx) { uint pred = Average2(top[idx - 1], top[idx]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor9(uint left, Span top, int idx) + private static uint Predictor9(Span top, int idx) { uint pred = Average2(top[idx], top[idx + 1]); return pred; @@ -539,7 +527,10 @@ namespace SixLabors.ImageSharp.Formats.WebP private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { - int a = AddSubtractComponentFull((int)(c0 >> 24), (int)(c1 >> 24), (int)(c2 >> 24)); + int a = AddSubtractComponentFull( + (int)(c0 >> 24), + (int)(c1 >> 24), + (int)(c2 >> 24)); int r = AddSubtractComponentFull( (int)((c0 >> 16) & 0xff), (int)((c1 >> 16) & 0xff), @@ -577,12 +568,7 @@ namespace SixLabors.ImageSharp.Formats.WebP [MethodImpl(InliningOptions.ShortMethod)] private static uint Clip255(uint a) { - if (a < 256) - { - return a; - } - - return ~a >> 24; + return a < 256 ? a : ~a >> 24; } private static uint Select(uint a, uint b, uint c) diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs index 837897e78..2d71938a1 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.WebP { internal static class LossyUtils @@ -20,11 +21,13 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void DC16(Span dst, Span yuv, int offset) { + int offsetMinus1 = offset - 1; + int offsetMinusBps = offset - WebPConstants.Bps; int dc = 16; for (int j = 0; j < 16; ++j) { // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps]; + dc += yuv[offsetMinus1 + (j * WebPConstants.Bps)] + yuv[offsetMinusBps + j]; } Put16(dc >> 5, dst); @@ -50,10 +53,11 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void HE16(Span dst, Span yuv, int offset) { // horizontal + offset--; for (int j = 16; j > 0; --j) { // memset(dst, dst[-1], 16); - byte v = yuv[offset - 1]; + byte v = yuv[offset]; Memset(dst, v, 0, 16); offset += WebPConstants.Bps; dst = dst.Slice(WebPConstants.Bps); @@ -96,10 +100,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void DC8uv(Span dst, Span yuv, int offset) { int dc0 = 8; + int offsetMinus1 = offset - 1; + int offsetMinusBps = offset - WebPConstants.Bps; for (int i = 0; i < 8; ++i) { // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; - dc0 += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; + dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebPConstants.Bps)]; } Put8x8uv((byte)(dc0 >> 4), dst); @@ -117,21 +123,23 @@ namespace SixLabors.ImageSharp.Formats.WebP // vertical Span src = yuv.Slice(offset - WebPConstants.Bps, 8); - for (int j = 0; j < 8; ++j) + int endIdx = 8 * WebPConstants.Bps; + for (int j = 0; j < endIdx; j += WebPConstants.Bps) { // memcpy(dst + j * BPS, dst - BPS, 8); - src.CopyTo(dst.Slice(j * WebPConstants.Bps)); + src.CopyTo(dst.Slice(j)); } } public static void HE8uv(Span dst, Span yuv, int offset) { // horizontal + offset--; for (int j = 0; j < 8; ++j) { // memset(dst, dst[-1], 8); // dst += BPS; - byte v = yuv[offset - 1]; + byte v = yuv[offset]; Memset(dst, v, 0, 8); dst = dst.Slice(WebPConstants.Bps); offset += WebPConstants.Bps; @@ -142,10 +150,12 @@ namespace SixLabors.ImageSharp.Formats.WebP { // DC with no top samples. int dc0 = 4; - for (int i = 0; i < 8; ++i) + int offsetMinusOne = offset - 1; + int endIdx = 8 * WebPConstants.Bps; + for (int i = 0; i < endIdx; i += WebPConstants.Bps) { // dc0 += dst[-1 + i * BPS]; - dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)]; + dc0 += yuv[offsetMinusOne + i]; } Put8x8uv((byte)(dc0 >> 3), dst); @@ -154,11 +164,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void DC8uvNoLeft(Span dst, Span yuv, int offset) { // DC with no left samples. + int offsetMinusBps = offset - WebPConstants.Bps; int dc0 = 4; for (int i = 0; i < 8; ++i) { // dc0 += dst[i - BPS]; - dc0 += yuv[offset + i - WebPConstants.Bps]; + dc0 += yuv[offsetMinusBps + i]; } Put8x8uv((byte)(dc0 >> 3), dst); @@ -174,15 +185,18 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void DC4(Span dst, Span yuv, int offset) { int dc = 4; + int offsetMinusBps = offset - WebPConstants.Bps; + int offsetMinusOne = offset - 1; for (int i = 0; i < 4; ++i) { - dc += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; + dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebPConstants.Bps)]; } dc >>= 3; - for (int i = 0; i < 4; ++i) + int endIndx = 4 * WebPConstants.Bps; + for (int i = 0; i < endIndx; i += WebPConstants.Bps) { - Memset(dst, (byte)dc, i * WebPConstants.Bps, 4); + Memset(dst, (byte)dc, i, 4); } } @@ -204,20 +218,22 @@ namespace SixLabors.ImageSharp.Formats.WebP Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]) }; - for (int i = 0; i < 4; ++i) + int endIdx = 4 * WebPConstants.Bps; + for (int i = 0; i < endIdx; i += WebPConstants.Bps) { - vals.CopyTo(dst.Slice(i * WebPConstants.Bps)); + vals.CopyTo(dst.Slice(i)); } } public static void HE4(Span dst, Span yuv, int offset) { // horizontal - byte a = yuv[offset - 1 - WebPConstants.Bps]; - byte b = yuv[offset - 1]; - byte c = yuv[offset - 1 + WebPConstants.Bps]; - byte d = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte e = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + int offsetMinusOne = offset - 1; + byte a = yuv[offsetMinusOne - WebPConstants.Bps]; + byte b = yuv[offsetMinusOne]; + byte c = yuv[offsetMinusOne + WebPConstants.Bps]; + byte d = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; + byte e = yuv[offsetMinusOne + (3 * WebPConstants.Bps)]; uint val = 0x01010101U * Avg3(a, b, c); BinaryPrimitives.WriteUInt32BigEndian(dst, val); val = 0x01010101U * Avg3(b, c, d); @@ -231,11 +247,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void RD4(Span dst, Span yuv, int offset) { // Down-right - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - byte x = yuv[offset - 1 - WebPConstants.Bps]; + int offsetMinusOne = offset - 1; + byte i = yuv[offsetMinusOne]; + byte j = yuv[offsetMinusOne + (1 * WebPConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; + byte l = yuv[offsetMinusOne + (3 * WebPConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebPConstants.Bps]; byte a = yuv[offset - WebPConstants.Bps]; byte b = yuv[offset + 1 - WebPConstants.Bps]; byte c = yuv[offset + 2 - WebPConstants.Bps]; @@ -267,10 +284,11 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void VR4(Span dst, Span yuv, int offset) { // Vertical-Right - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte x = yuv[offset - 1 - WebPConstants.Bps]; + int offsetMinusOne = offset - 1; + byte i = yuv[offsetMinusOne]; + byte j = yuv[offsetMinusOne + (1 * WebPConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebPConstants.Bps]; byte a = yuv[offset - WebPConstants.Bps]; byte b = yuv[offset + 1 - WebPConstants.Bps]; byte c = yuv[offset + 2 - WebPConstants.Bps]; @@ -437,33 +455,30 @@ namespace SixLabors.ImageSharp.Formats.WebP Dst(dst, 3, 3, l); } - public static void Transform(Span src, Span dst, bool doTwo) + public static void TransformTwo(Span src, Span dst) { TransformOne(src, dst); - if (doTwo) - { - TransformOne(src.Slice(16), dst.Slice(4)); - } + TransformOne(src.Slice(16), dst.Slice(4)); } public static void TransformOne(Span src, Span dst) { var tmp = new int[4 * 4]; int tmpOffset = 0; - int srcOffset = 0; - for (int i = 0; i < 4; ++i) + for (int srcOffset = 0; srcOffset < 4; srcOffset++) { // vertical pass - int a = src[srcOffset] + src[srcOffset + 8]; - int b = src[srcOffset] - src[srcOffset + 8]; - int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]); - int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]); - tmp[tmpOffset] = a + d; - tmp[tmpOffset + 1] = b + c; - tmp[tmpOffset + 2] = b - c; - tmp[tmpOffset + 3] = a - d; - tmpOffset += 4; - srcOffset++; + int srcOffsetPlus4 = srcOffset + 4; + int srcOffsetPlus8 = srcOffset + 8; + int srcOffsetPlus12 = srcOffset + 12; + int a = src[srcOffset] + src[srcOffsetPlus8]; + int b = src[srcOffset] - src[srcOffsetPlus8]; + int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); + int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); + tmp[tmpOffset++] = a + d; + tmp[tmpOffset++] = b + c; + tmp[tmpOffset++] = b - c; + tmp[tmpOffset++] = a - d; } // Each pass is expanding the dynamic range by ~3.85 (upper bound). @@ -475,11 +490,14 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < 4; ++i) { // horizontal pass + int tmpOffsetPlus4 = tmpOffset + 4; + int tmpOffsetPlus8 = tmpOffset + 8; + int tmpOffsetPlus12 = tmpOffset + 12; int dc = tmp[tmpOffset] + 4; - int a = dc + tmp[tmpOffset + 8]; - int b = dc - tmp[tmpOffset + 8]; - int c = Mul2(tmp[tmpOffset + 4]) - Mul1(tmp[tmpOffset + 12]); - int d = Mul1(tmp[tmpOffset + 4]) + Mul2(tmp[tmpOffset + 12]); + int a = dc + tmp[tmpOffsetPlus8]; + int b = dc - tmp[tmpOffsetPlus8]; + int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); + int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); Store(dst, 0, 0, a + d); Store(dst, 1, 0, b + c); Store(dst, 2, 0, b - c); @@ -517,8 +535,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void TransformUv(Span src, Span dst) { - Transform(src.Slice(0 * 16), dst, true); - Transform(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps), true); + TransformTwo(src.Slice(0 * 16), dst); + TransformTwo(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); } public static void TransformDcuv(Span src, Span dst) @@ -569,11 +587,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) { int thresh2 = (2 * thresh) + 1; - for (int i = 0; i < 16; ++i) + int end = 16 + offset; + for (int i = offset; i < end; ++i) { - if (NeedsFilter(p, offset + i, stride, thresh2)) + if (NeedsFilter(p, i, stride, thresh2)) { - DoFilter2(p, offset + i, stride); + DoFilter2(p, i, stride); } } } @@ -581,11 +600,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) { int thresh2 = (2 * thresh) + 1; - for (int i = 0; i < 16; ++i) + int end = offset + (16 * stride); + for (int i = offset; i < end; i += stride) { - if (NeedsFilter(p, offset + (i * stride), 1, thresh2)) + if (NeedsFilter(p, i, 1, thresh2)) { - DoFilter2(p, offset + (i * stride), 1); + DoFilter2(p, i, 1); } } } @@ -656,15 +676,17 @@ namespace SixLabors.ImageSharp.Formats.WebP [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); + int offset4mulstride = offset + (4 * stride); + FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); } [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); + int offsetPlus4 = offset + 4; + FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); } [MethodImpl(InliningOptions.ShortMethod)] @@ -683,9 +705,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToR(int y, int v) + public static int YuvToB(int y, int u) { - return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); + return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); } [MethodImpl(InliningOptions.ShortMethod)] @@ -695,9 +717,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToB(int y, int u) + public static int YuvToR(int y, int v) { - return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); + return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); } // Complex In-loop filtering (Paragraph 15.3) @@ -776,7 +798,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void DoFilter4(Span p, int offset, int step) { // 4 pixels in, 4 pixels out. - int p1 = p[offset - (2 * step)]; + int offsetMinus2Step = offset - (2 * step); + int p1 = p[offsetMinus2Step]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; @@ -784,7 +807,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; int a3 = (a1 + 1) >> 1; - p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a3]; + p[offsetMinus2Step] = WebPLookupTables.Clip1[p1 + a3]; p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; p[offset] = WebPLookupTables.Clip1[q0 - a1]; p[offset + step] = WebPLookupTables.Clip1[q1 - a3]; @@ -793,24 +816,27 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void DoFilter6(Span p, int offset, int step) { // 6 pixels in, 6 pixels out. - int p2 = p[offset - (3 * step)]; - int p1 = p[offset - (2 * step)]; - int p0 = p[offset - step]; + int step2 = 2 * step; + int step3 = 3 * step; + int offsetMinusStep = offset - step; + int p2 = p[offset - step3]; + int p1 = p[offset - step2]; + int p0 = p[offsetMinusStep]; int q0 = p[offset]; int q1 = p[offset + step]; - int q2 = p[offset + (2 * step)]; + int q2 = p[offset + step2]; int a = WebPLookupTables.Sclip1[(3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]]; // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - (3 * step)] = WebPLookupTables.Clip1[p2 + a3]; - p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a2]; - p[offset - step] = WebPLookupTables.Clip1[p0 + a1]; + p[offset - step3] = WebPLookupTables.Clip1[p2 + a3]; + p[offset - step2] = WebPLookupTables.Clip1[p1 + a2]; + p[offsetMinusStep] = WebPLookupTables.Clip1[p0 + a1]; p[offset] = WebPLookupTables.Clip1[q0 - a1]; p[offset + step] = WebPLookupTables.Clip1[q1 - a2]; - p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3]; + p[offset + step2] = WebPLookupTables.Clip1[q2 - a3]; } [MethodImpl(InliningOptions.ShortMethod)] @@ -825,14 +851,16 @@ namespace SixLabors.ImageSharp.Formats.WebP private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) { + int step2 = 2 * step; + int step3 = 3 * step; int p3 = p[offset - (4 * step)]; - int p2 = p[offset - (3 * step)]; - int p1 = p[offset - (2 * step)]; + int p2 = p[offset - step3]; + int p1 = p[offset - step2]; int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - int q2 = p[offset + (2 * step)]; - int q3 = p[offset + (3 * step)]; + int q2 = p[offset + step2]; + int q3 = p[offset + step3]; if (((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) > t) { return false; @@ -862,7 +890,8 @@ namespace SixLabors.ImageSharp.Formats.WebP [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, int x, int y, int v) { - dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3)); + var index = x + (y * WebPConstants.Bps); + dst[index] = Clip8B(dst[index] + (v >> 3)); } [MethodImpl(InliningOptions.ShortMethod)] @@ -889,32 +918,34 @@ namespace SixLabors.ImageSharp.Formats.WebP [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8B(int v) { - return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255); + return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); } [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8(int v) { int yuvMask = (256 << 6) - 1; - return (byte)(((v & ~yuvMask) is 0) ? (v >> 6) : (v < 0) ? 0 : 255); + return (byte)(((v & ~yuvMask) == 0) ? (v >> 6) : (v < 0) ? 0 : 255); } [MethodImpl(InliningOptions.ShortMethod)] private static void Put8x8uv(byte value, Span dst) { - for (int j = 0; j < 8; ++j) + int end = 8 * WebPConstants.Bps; + for (int j = 0; j < end; j += WebPConstants.Bps) { // memset(dst + j * BPS, value, 8); - Memset(dst, value, j * WebPConstants.Bps, 8); + Memset(dst, value, j, 8); } } [MethodImpl(InliningOptions.ShortMethod)] private static void Memset(Span dst, byte value, int startIdx, int count) { - for (int i = 0; i < count; i++) + int end = startIdx + count; + for (int i = startIdx; i < end; i++) { - dst[startIdx + i] = value; + dst[i] = value; } } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 66681b89d..1601c7339 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -58,6 +58,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Start index in the data array. Defaults to 0. public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) { + Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); + this.ImageDataSize = imageDataSize; this.PartitionLength = partitionLength; this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); @@ -133,8 +135,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ulong value = this.value >> pos; ulong mask = (split - value) >> 31; // -1 or 0 this.bits -= 1; - this.range += (uint)mask; - this.range |= 1; + this.range = (this.range + (uint)mask) | 1; this.value -= ((split + 1) & mask) << pos; return (v ^ (int)mask) - (int)mask; @@ -149,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint ReadValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + Guard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); uint v = 0; while (nBits-- > 0) @@ -162,6 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int ReadSignedValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + Guard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); int value = (int)this.ReadValue(nBits); return this.ReadValue(1) != 0 ? -value : value; @@ -169,13 +172,14 @@ namespace SixLabors.ImageSharp.Formats.WebP private void InitBitreader(uint size, int pos = 0) { + var posPlusSize = pos + size; this.range = 255 - 1; this.value = 0; this.bits = -8; // to load the very first 8 bits. this.eof = false; this.pos = pos; - this.bufferEnd = (uint)(pos + size); - this.bufferMax = (uint)(size > 8 ? pos + size - 8 + 1 : pos); + this.bufferEnd = (uint)posPlusSize; + this.bufferMax = (uint)(size > 8 ? posPlusSize - 8 + 1 : pos); this.LoadNewBytes(); } diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 49d642d48..aa69bb17e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -67,8 +67,9 @@ namespace SixLabors.ImageSharp.Formats.WebP int extraUv = (extraRows / 2) * this.CacheUvStride; this.YuvBuffer = memoryAllocator.Allocate((WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY); this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); - this.CacheU = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv); - this.CacheV = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv); + int cacheUvSize = (16 * this.CacheUvStride) + extraUv; + this.CacheU = memoryAllocator.Allocate(cacheUvSize); + this.CacheV = memoryAllocator.Allocate(cacheUvSize); this.TmpYBuffer = memoryAllocator.Allocate((int)width); this.TmpUBuffer = memoryAllocator.Allocate((int)width); this.TmpVBuffer = memoryAllocator.Allocate((int)width); @@ -199,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public LoopFilter Filter { get; set; } /// - /// Gets the filter strengths. + /// Gets the pre-calculated per-segment filter strengths. /// public Vp8FilterInfo[,] FilterStrength { get; } @@ -233,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public IMemoryOwner Pixels { get; } /// - /// Gets or sets filter strength info. + /// Gets or sets filter info. /// public Vp8FilterInfo[] FilterInfo { get; set; } @@ -249,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { get { - if (this.leftMacroBlock is null) + if (this.leftMacroBlock == null) { this.leftMacroBlock = new Vp8MacroBlock(); } @@ -268,7 +269,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public void PrecomputeFilterStrengths() { - if (this.Filter is LoopFilter.None) + if (this.Filter == LoopFilter.None) { return; } @@ -320,9 +321,10 @@ namespace SixLabors.ImageSharp.Formats.WebP iLevel >>= 1; } - if (iLevel > 9 - hdr.Sharpness) + int iLevelCap = 9 - hdr.Sharpness; + if (iLevel > iLevelCap) { - iLevel = 9 - hdr.Sharpness; + iLevel = iLevelCap; } } @@ -340,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.WebP info.Limit = 0; // no filtering. } - info.UseInnerFiltering = i4x4 is 1; + info.UseInnerFiltering = i4x4 == 1; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index 16c9d9c0e..6b53532ba 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Initializes a new instance of the class. /// - /// The filter info to create an instance from. + /// The filter info to create a copy from. public Vp8FilterInfo(Vp8FilterInfo other) { this.Limit = other.Limit; diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index f90023b89..10b7307b3 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -132,8 +132,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (!this.Eos && nBits <= Vp8LMaxNumBitRead) { ulong val = this.PrefetchBits() & this.bitMask[nBits]; - int newBits = this.bitPos + nBits; - this.bitPos = newBits; + this.bitPos += nBits; this.ShiftBytes(); return (uint)val; } @@ -193,6 +192,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); } + [MethodImpl(InliningOptions.ShortMethod)] private void DoFillBitWindow() { this.ShiftBytes(); diff --git a/src/ImageSharp/Formats/WebP/Vp8Profile.cs b/src/ImageSharp/Formats/WebP/Vp8Profile.cs index b1d757cb5..bc3a3ac06 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Profile.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Profile.cs @@ -4,7 +4,21 @@ namespace SixLabors.ImageSharp.Formats.WebP { /// - /// The version number setting enables or disables certain features in the bitstream. + /// The version number of the frame header enables or disables certain features in the bitstream. + /// +---------+-------------------------+-------------+ + /// | Version | Reconstruction Filter | Loop Filter | + /// +---------+-------------------------+-------------+ + /// | 0 | Bicubic | Normal | + /// | | | | + /// | 1 | Bilinear | Simple | + /// | | | | + /// | 2 | Bilinear | None | + /// | | | | + /// | 3 | None | None | + /// | | | | + /// | Other | Reserved for future use | | + /// +---------+-------------------------+-------------+ + /// See paragraph 9, https://tools.ietf.org/html/rfc6386. /// internal class Vp8Profile { diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs index ca9f4b69e..5fd288abe 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -5,6 +5,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8QuantMatrix { + private int dither; + public int[] Y1Mat { get; } = new int[2]; public int[] Y2Mat { get; } = new int[2]; @@ -19,6 +21,14 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the dithering amplitude (0 = off, max=255). /// - public int Dither { get; set; } + public int Dither + { + get => this.dither; + set + { + Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.dither)); + this.dither = value; + } + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index a03f6bfb1..4743935e1 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -6,15 +6,16 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Contains a list of different webp chunk types. /// + /// See WebP Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container public enum WebPChunkType : uint { /// - /// Header signaling the use of VP8 format. + /// Header signaling the use of the VP8 format. /// Vp8 = 0x56503820U, /// - /// Header for a extended-VP8 chunk. + /// Header signaling the image uses lossless encoding. /// Vp8L = 0x5650384CU, @@ -52,10 +53,5 @@ namespace SixLabors.ImageSharp.Formats.WebP /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. /// Animation = 0x414E4D46, - - /// - /// TODO: not sure what this is for yet. - /// - FRGM = 0x4652474D, } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 78519da82..29cec2b47 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -105,9 +105,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly int[] AlphabetSize = { - NumLiteralCodes + NumLengthCodes, - NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, - NumDistanceCodes + NumLiteralCodes + NumLengthCodes, + NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, + NumDistanceCodes }; // VP8 constants from here on: diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f0060edaf..180c7c960 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream = stream; uint fileSize = this.ReadImageHeader(); - using var imageInfo = this.ReadVp8Info(); + using WebPImageInfo imageInfo = this.ReadVp8Info(); if (imageInfo.Features != null && imageInfo.Features.Animation) { WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var image = new Image(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (imageInfo.IsLossLess) + if (imageInfo.IsLossless) { var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); @@ -159,11 +159,10 @@ namespace SixLabors.ImageSharp.Formats.WebP return this.ReadVp8LHeader(); case WebPChunkType.Vp8X: return this.ReadVp8XHeader(); + default: + WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + return new WebPImageInfo(); // this return will never be reached, because throw helper will throw an exception. } - - WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); - - return new WebPImageInfo(); } /// @@ -275,8 +274,8 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Read(this.buffer, 0, 3); uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); remaining -= 3; - bool isKeyFrame = (frameTag & 0x1) is 0; - if (!isKeyFrame) + bool isNoKeyFrame = (frameTag & 0x1) == 1; + if (isNoKeyFrame) { WebPThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); } @@ -287,8 +286,8 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); } - bool showFrame = ((frameTag >> 4) & 0x1) is 1; - if (!showFrame) + bool invisibleFrame = ((frameTag >> 4) & 0x1) == 0; + if (invisibleFrame) { WebPThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); } @@ -314,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint height = tmp & 0x3fff; sbyte yScale = (sbyte)(tmp >> 6); remaining -= 7; - if (width is 0 || height is 0) + if (width == 0 || height == 0) { WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); } @@ -344,8 +343,8 @@ namespace SixLabors.ImageSharp.Formats.WebP Height = height, XScale = xScale, YScale = yScale, - BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, - IsLossLess = false, + BitsPerPixel = features?.Alpha == true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, + IsLossless = false, Features = features, Vp8Profile = (sbyte)version, Vp8FrameHeader = vp8FrameHeader, @@ -377,9 +376,9 @@ namespace SixLabors.ImageSharp.Formats.WebP // The first 28 bits of the bitstream specify the width and height of the image. uint width = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; - if (width is 0 || height is 0) + if (width == 1 || height == 1) { - WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); + WebPThrowHelper.ThrowImageFormatException("invalid width or height read"); } // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. @@ -399,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Width = width, Height = height, BitsPerPixel = WebPBitsPerPixel.Pixel32, - IsLossLess = true, + IsLossless = true, Features = features, Vp8LBitReader = bitReader }; @@ -455,18 +454,19 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The webp features. private void ParseOptionalChunks(WebPFeatures features) { - if (this.IgnoreMetadata || (features.ExifProfile is false && features.XmpMetaData is false)) + if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) { return; } - while (this.currentStream.Position < this.currentStream.Length) + var streamLength = this.currentStream.Length; + while (this.currentStream.Position < streamLength) { // Read chunk header. WebPChunkType chunkType = this.ReadChunkType(); uint chunkLength = this.ReadChunkSize(); - if (chunkType is WebPChunkType.Exif) + if (chunkType == WebPChunkType.Exif) { var exifData = new byte[chunkLength]; this.currentStream.Read(exifData, 0, (int)chunkLength); @@ -488,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private WebPChunkType ReadChunkType() { - if (this.currentStream.Read(this.buffer, 0, 4) is 4) + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); this.webpMetadata.ChunkTypes.Enqueue(chunkType); @@ -505,10 +505,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The chunk size in bytes. private uint ReadChunkSize() { - if (this.currentStream.Read(this.buffer, 0, 4) is 4) + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - return (chunkSize % 2 is 0) ? chunkSize : chunkSize + 1; + return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; } throw new ImageFormatException("Invalid WebP data."); diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index cf1053057..c4fc0ccba 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -12,12 +12,12 @@ namespace SixLabors.ImageSharp.Formats.WebP internal class WebPFeatures : IDisposable { /// - /// Gets or sets a value indicating whether this image has a ICC Profile. + /// Gets or sets a value indicating whether this image has an ICC Profile. /// public bool IccProfile { get; set; } /// - /// Gets or sets a value indicating whether this image has a alpha channel. + /// Gets or sets a value indicating whether this image has an alpha channel. /// public bool Alpha { get; set; } @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public byte AlphaChunkHeader { get; set; } /// - /// Gets or sets a value indicating whether this image has a EXIF Profile. + /// Gets or sets a value indicating whether this image has an EXIF Profile. /// public bool ExifProfile { get; set; } @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool XmpMetaData { get; set; } /// - /// Gets or sets a value indicating whether this image is a animation. + /// Gets or sets a value indicating whether this image is an animation. /// public bool Animation { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index b7aa4b36a..cf4a6fef7 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets a value indicating whether this image uses lossless compression. /// - public bool IsLossLess { get; set; } + public bool IsLossless { get; set; } /// /// Gets or sets additional features present in a VP8X image. @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8FrameHeader Vp8FrameHeader { get; set; } /// - /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image. + /// Gets or sets the VP8L bitreader. Will be null, if its not a lossless image. /// public Vp8LBitReader Vp8LBitReader { get; set; } = null; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 88c1cd0d1..6811c9dd9 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -37,13 +37,18 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private readonly MemoryAllocator memoryAllocator; - private static readonly int BitsSpecialMarker = 0x100; + private const int BitsSpecialMarker = 0x100; - private static readonly uint PackedNonLiteralCode = 0; + private const uint PackedNonLiteralCode = 0; private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; - private static readonly int FixedTableSize = (630 * 3) + 410; + // Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha and distance alphabets are constant (256 for red, blue and alpha, 40 for + // distance) and lookup table sizes for them in worst case are 630 and 410 respectively. Size of green alphabet depends on color cache size and is equal + // to 256 (green component values) + 24 (length prefix values) + color_cache_size (between 0 and 2048). + // All values computed for 8-bit first level lookup with Mark Adler's tool: + // http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c + private const int FixedTableSize = (630 * 3) + 410; private static readonly int[] TableSize = { @@ -133,14 +138,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Color cache. - bool colorCachePresent = this.bitReader.ReadBit(); + bool isColorCachePresent = this.bitReader.ReadBit(); int colorCacheBits = 0; int colorCacheSize = 0; - if (colorCachePresent) + if (isColorCachePresent) { colorCacheBits = (int)this.bitReader.ReadValue(4); - bool coloCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits; - if (!coloCacheBitsIsValid) + bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits; + if (!colorCacheBitsIsValid) { WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } @@ -151,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.WebP decoder.Metadata.ColorCacheSize = colorCacheSize; // Finish setting up the color-cache. - if (colorCachePresent) + if (isColorCachePresent) { decoder.Metadata.ColorCache = new ColorCache(); colorCacheSize = 1 << colorCacheBits; @@ -192,9 +197,10 @@ namespace SixLabors.ImageSharp.Formats.WebP ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); + int widthMul4 = width * 4; for (int y = 0; y < decoder.Height; y++) { - Span row = pixelDataAsBytes.Slice(y * width * 4, width * 4); + Span row = pixelDataAsBytes.Slice(y * widthMul4, widthMul4); Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes( this.configuration, @@ -211,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int height = decoder.Height; int row = lastPixel / width; int col = lastPixel % width; - int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + const int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int colorCacheSize = decoder.Metadata.ColorCacheSize; ColorCache colorCache = decoder.Metadata.ColorCache; int colorCacheLimit = lenCodeLimit + colorCacheSize; @@ -224,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (decodedPixels < totalPixels) { int code; - if ((col & mask) is 0) + if ((col & mask) == 0) { hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } @@ -279,8 +285,7 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - int pixelIdx = decodedPixels; - pixelData[pixelIdx] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); + pixelData[decodedPixels] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); } this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); @@ -305,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (col >= width) { col -= width; - ++row; + row++; } if ((col & mask) != 0) @@ -344,12 +349,12 @@ namespace SixLabors.ImageSharp.Formats.WebP private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) { - ++col; + col++; decodedPixels++; if (col >= width) { col = 0; - ++row; + row++; if (colorCache != null) { @@ -373,13 +378,13 @@ namespace SixLabors.ImageSharp.Formats.WebP if (allowRecursion && this.bitReader.ReadBit()) { // Use meta Huffman codes. - uint huffmanPrecision = this.bitReader.ReadValue(3) + 2; - int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision); - int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision); + int huffmanPrecision = (int)(this.bitReader.ReadValue(3) + 2); + int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); + int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); int huffmanPixels = huffmanXSize * huffmanYSize; IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); Span huffmanImageSpan = huffmanImage.GetSpan(); - decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision; + decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; for (int i = 0; i < huffmanPixels; ++i) { // The huffman data is stored in red and green bytes. @@ -431,19 +436,20 @@ namespace SixLabors.ImageSharp.Formats.WebP } int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); - if (size is 0) + if (size == 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } hTreeGroup.HTrees.Add(huffmanTable.ToArray()); + HuffmanCode huffTableZero = huffmanTable[0]; if (isTrivialLiteral && LiteralMap[j] == 1) { - isTrivialLiteral = huffmanTable[0].BitsUsed == 0; + isTrivialLiteral = huffTableZero.BitsUsed == 0; } - totalSize += huffmanTable[0].BitsUsed; + totalSize += huffTableZero.BitsUsed; huffmanTable = huffmanTable.Slice(size); if (j <= HuffIndex.Alpha) @@ -452,9 +458,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int k; for (k = 1; k < alphabetSize; ++k) { - if (codeLengths[k] > localMaxBits) + var codeLengthK = codeLengths[k]; + if (codeLengthK > localMaxBits) { - localMaxBits = codeLengths[k]; + localMaxBits = codeLengthK; } } @@ -501,19 +508,19 @@ namespace SixLabors.ImageSharp.Formats.WebP if (simpleCode) { // (i) Simple Code Length Code. - // This variant is used in the special case when only 1 or 2 Huffman code lengths are non - zero, - // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros. + // This variant is used in the special case when only 1 or 2 Huffman code lengths are non-zero, + // and are in the range of[0, 255]. All other Huffman code lengths are implicitly zeros. // Read symbols, codes & code lengths directly. uint numSymbols = this.bitReader.ReadValue(1) + 1; uint firstSymbolLenCode = this.bitReader.ReadValue(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadValue((firstSymbolLenCode is 0) ? 1 : 8); + uint symbol = this.bitReader.ReadValue((firstSymbolLenCode == 0) ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. - if (numSymbols is 2) + if (numSymbols == 2) { symbol = this.bitReader.ReadValue(8); codeLengths[symbol] = 1; @@ -550,7 +557,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); - if (size is 0) + if (size == 0) { WebPThrowHelper.ThrowImageFormatException("Error building huffman table"); } @@ -567,7 +574,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (symbol < numSymbols) { - if (maxSymbol-- is 0) + if (maxSymbol-- == 0) { break; } @@ -667,7 +674,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// A WebP lossless image can go through four different types of transformation before being entropy encoded. - /// This will reverses the transformations, if any are present. + /// This will reverse the transformations, if any are present. /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. @@ -677,13 +684,14 @@ namespace SixLabors.ImageSharp.Formats.WebP List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) { - Vp8LTransformType transformType = transforms[i].TransformType; + Vp8LTransform transform = transforms[i]; + Vp8LTransformType transformType = transform.TransformType; switch (transformType) { case Vp8LTransformType.PredictorTransform: using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) { - LosslessUtils.PredictorInverseTransform(transforms[i], pixelData, output.GetSpan()); + LosslessUtils.PredictorInverseTransform(transform, pixelData, output.GetSpan()); } break; @@ -691,10 +699,10 @@ namespace SixLabors.ImageSharp.Formats.WebP LosslessUtils.AddGreenToBlueAndRed(pixelData); break; case Vp8LTransformType.CrossColorTransform: - LosslessUtils.ColorSpaceInverseTransform(transforms[i], pixelData); + LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); break; case Vp8LTransformType.ColorIndexingTransform: - LosslessUtils.ColorIndexInverseTransform(transforms[i], pixelData); + LosslessUtils.ColorIndexInverseTransform(transform, pixelData); break; } } @@ -719,13 +727,13 @@ namespace SixLabors.ImageSharp.Formats.WebP int end = width * height; // End of data. int last = end; // Last pixel to decode. int lastRow = height; - int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + const int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int mask = hdr.HuffmanMask; HTreeGroup[] htreeGroup = (pos < last) ? GetHTreeGroupForPos(hdr, col, row) : null; while (!this.bitReader.Eos && pos < last) { // Only update when changing tile. - if ((col & mask) is 0) + if ((col & mask) == 0) { htreeGroup = GetHTreeGroupForPos(hdr, col, row); } @@ -743,7 +751,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col = 0; ++row; - if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows == 0)) { dec.ExtractPalettedAlphaRows(row); } @@ -773,7 +781,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { col -= width; ++row; - if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0)) + if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows == 0)) { dec.ExtractPalettedAlphaRows(row); } @@ -802,7 +810,7 @@ namespace SixLabors.ImageSharp.Formats.WebP decoder.Width = width; decoder.Height = height; decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); - decoder.Metadata.HuffmanMask = (numBits is 0) ? ~0 : (1 << numBits) - 1; + decoder.Metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; } private uint ReadPackedSymbols(HTreeGroup[] group, Span pixelData, int decodedPixels) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index a741ad42d..b82a79dae 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Decode image data. this.ParseFrame(decoder, io); - if (info.Features?.Alpha is true) + if (info.Features?.Alpha == true) { using (var alphaDecoder = new AlphaDecoder( width, @@ -112,35 +112,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha = null) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels) where TPixel : unmanaged, IPixel { - if (alpha != null) - { - TPixel color = default; - Span alphaSpan = alpha.Memory.Span; - for (int y = 0; y < height; y++) - { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = 0; x < width; x++) - { - int offset = (y * width) + x; - int idxBgr = offset * 3; - byte b = pixelData[idxBgr]; - byte g = pixelData[idxBgr + 1]; - byte r = pixelData[idxBgr + 2]; - byte a = alphaSpan[offset]; - color.FromBgra32(new Bgra32(r, g, b, a)); - pixelRow[x] = color; - } - } - - return; - } - + int widthMul3 = width * 3; for (int y = 0; y < height; y++) { - Span row = pixelData.Slice(y * width * 3, width * 3); + Span row = pixelData.Slice(y * widthMul3, widthMul3); Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes( this.configuration, @@ -150,6 +128,29 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + Span alphaSpan = alpha.Memory.Span; + for (int y = 0; y < height; y++) + { + int yMulWidth = y * width; + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + int offset = yMulWidth + x; + int idxBgr = offset * 3; + byte b = pixelData[idxBgr]; + byte g = pixelData[idxBgr + 1]; + byte r = pixelData[idxBgr + 2]; + byte a = alphaSpan[offset]; + color.FromBgra32(new Bgra32(r, g, b, a)); + pixelRow[x] = color; + } + } + } + private void ParseFrame(Vp8Decoder dec, Vp8Io io) { for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) @@ -186,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (dec.SegmentHeader.UpdateMap) { // Hardcoded tree parsing. - block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) is 0 + block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) == 0 ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); } @@ -198,10 +199,10 @@ namespace SixLabors.ImageSharp.Formats.WebP if (dec.UseSkipProbability) { - block.Skip = this.bitReader.GetBit(dec.SkipProbability) is 1; + block.Skip = this.bitReader.GetBit(dec.SkipProbability) == 1; } - block.IsI4x4 = this.bitReader.GetBit(145) is 0; + block.IsI4x4 = this.bitReader.GetBit(145) == 0; if (!block.IsI4x4) { // Hardcoded 16x16 intra-mode decision tree. @@ -241,8 +242,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Hardcoded UVMode decision tree. - block.UvMode = (byte)(this.bitReader.GetBit(142) is 0 ? 0 : - this.bitReader.GetBit(114) is 0 ? 2 : + block.UvMode = (byte)(this.bitReader.GetBit(142) == 0 ? 0 : + this.bitReader.GetBit(114) == 0 ? 2 : this.bitReader.GetBit(183) != 0 ? 1 : 3); } @@ -268,9 +269,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReconstructRow(Vp8Decoder dec) { int mby = dec.MbY; - int yOff = (WebPConstants.Bps * 1) + 8; - int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; - int vOff = uOff + 16; + const int yOff = (WebPConstants.Bps * 1) + 8; + const int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; + const int vOff = uOff + 16; Span yuv = dec.YuvBuffer.Memory.Span; Span yDst = yuv.Slice(yOff); @@ -278,15 +279,17 @@ namespace SixLabors.ImageSharp.Formats.WebP Span vDst = yuv.Slice(vOff); // Initialize left-most block. - for (int i = 0; i < 16; ++i) + var end = 16 * WebPConstants.Bps; + for (int i = 0; i < end; i += WebPConstants.Bps) { - yuv[(i * WebPConstants.Bps) - 1 + yOff] = 129; + yuv[i - 1 + yOff] = 129; } - for (int i = 0; i < 8; ++i) + end = 8 * WebPConstants.Bps; + for (int i = 0; i < end; i += WebPConstants.Bps) { - yuv[(i * WebPConstants.Bps) - 1 + uOff] = 129; - yuv[(i * WebPConstants.Bps) - 1 + vOff] = 129; + yuv[i - 1 + uOff] = 129; + yuv[i - 1 + vOff] = 129; } // Init top-left sample on left column too. @@ -364,10 +367,11 @@ namespace SixLabors.ImageSharp.Formats.WebP if (mbx >= dec.MbWidth - 1) { // On rightmost border. - topRight[0] = topYuv.Y[15]; - topRight[1] = topYuv.Y[15]; - topRight[2] = topYuv.Y[15]; - topRight[3] = topYuv.Y[15]; + var topYuv15 = topYuv.Y[15]; + topRight[0] = topYuv15; + topRight[1] = topYuv15; + topRight[2] = topYuv15; + topRight[3] = topYuv15; } else { @@ -517,8 +521,9 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int j = 0; j < 8; ++j) { - uDst.Slice(j * WebPConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(j * dec.CacheUvStride)); - vDst.Slice(j * WebPConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut.Slice(j * dec.CacheUvStride)); + int jUvStride = j * dec.CacheUvStride; + uDst.Slice(j * WebPConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(jUvStride)); + vDst.Slice(j * WebPConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut.Slice(jUvStride)); } } } @@ -539,12 +544,12 @@ namespace SixLabors.ImageSharp.Formats.WebP int iLevel = filterInfo.InnerLevel; int limit = filterInfo.Limit; - if (limit is 0) + if (limit == 0) { return; } - if (dec.Filter is LoopFilter.Simple) + if (dec.Filter == LoopFilter.Simple) { int offset = dec.CacheYOffset + (mbx * 16); if (mbx > 0) @@ -567,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } } - else if (dec.Filter is LoopFilter.Complex) + else if (dec.Filter == LoopFilter.Complex) { int uvBps = dec.CacheUvStride; int yOffset = dec.CacheYOffset + (mbx * 16); @@ -608,7 +613,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Span uDst = dec.CacheU.Memory.Span; Span vDst = dec.CacheV.Memory.Span; int mby = dec.MbY; - bool isFirstRow = mby is 0; + bool isFirstRow = mby == 0; bool isLastRow = mby >= dec.BottomRightMbY - 1; bool filterRow = (dec.Filter != LoopFilter.None) && (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); @@ -682,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int uvw = (mbw + 1) / 2; int y = io.MbY; - if (y is 0) + if (y == 0) { // First line is special cased. We mirror the u/v samples at boundary. this.UpSample(curY, null, curU, curV, curU, curV, dst, null, mbw); @@ -721,7 +726,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { // Process the very last row of even-sized picture. - if ((yEnd & 1) is 0) + if ((yEnd & 1) == 0) { this.UpSample(curY, null, curU, curV, curU, curV, dst.Slice(bufferStride), null, mbw); } @@ -756,22 +761,23 @@ namespace SixLabors.ImageSharp.Formats.WebP uint diag03 = (avg + (2 * (tluv + uv))) >> 3; uv0 = (diag12 + tluv) >> 1; uint uv1 = (diag03 + tuv) >> 1; - LossyUtils.YuvToBgr(topY[(2 * x) - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice(((2 * x) - 1) * xStep)); - LossyUtils.YuvToBgr(topY[(2 * x) - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice(((2 * x) - 0) * xStep)); + int xMul2 = x * 2; + LossyUtils.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); + LossyUtils.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); if (bottomY != null) { uv0 = (diag03 + luv) >> 1; uv1 = (diag12 + uv) >> 1; - LossyUtils.YuvToBgr(bottomY[(2 * x) - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice(((2 * x) - 1) * xStep)); - LossyUtils.YuvToBgr(bottomY[(2 * x) + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice(((2 * x) + 0) * xStep)); + LossyUtils.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); + LossyUtils.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); } tluv = tuv; luv = uv; } - if ((len & 1) is 0) + if ((len & 1) == 0) { uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; LossyUtils.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); @@ -788,7 +794,7 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (bits >> 30) { case 3: - LossyUtils.Transform(src, dst, false); + LossyUtils.TransformOne(src, dst); break; case 2: LossyUtils.TransformAc3(src, dst); @@ -823,7 +829,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8MacroBlock left = dec.LeftMacroBlock; Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; Vp8MacroBlockData blockData = dec.CurrentBlockData; - bool skip = dec.UseSkipProbability ? blockData.Skip : false; + bool skip = dec.UseSkipProbability && blockData.Skip; if (!skip) { @@ -867,7 +873,12 @@ namespace SixLabors.ImageSharp.Formats.WebP dst[i] = 0; } - if (!block.IsI4x4) + if (block.IsI4x4) + { + first = 0; + acProba = GetBandsRow(bands, 3); + } + else { // Parse DC var dc = new short[16]; @@ -892,11 +903,6 @@ namespace SixLabors.ImageSharp.Formats.WebP first = 1; acProba = GetBandsRow(bands, 0); } - else - { - first = 0; - acProba = GetBandsRow(bands, 3); - } byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); @@ -926,8 +932,9 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int ch = 0; ch < 4; ch += 2) { uint nzCoeffs = 0; - tnz = (byte)(mb.NoneZeroAcDcCoeffs >> (4 + ch)); - lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> (4 + ch)); + var chPlus4 = 4 + ch; + tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); + lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); for (int y = 0; y < 2; ++y) { int l = lnz & 1; @@ -957,26 +964,26 @@ namespace SixLabors.ImageSharp.Formats.WebP block.NonZeroY = nonZeroY; block.NonZeroUv = nonZeroUv; - return (nonZeroY | nonZeroUv) is 0; + return (nonZeroY | nonZeroUv) == 0; } private int GetCoeffs(Vp8BitReader br, Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) { - // Returns the position of the last non - zero coeff plus one. + // Returns the position of the last non-zero coeff plus one. Vp8ProbaArray p = prob[n].Probabilities[ctx]; for (; n < 16; ++n) { - if (br.GetBit(p.Probabilities[0]) is 0) + if (br.GetBit(p.Probabilities[0]) == 0) { - // Previous coeff was last non - zero coeff. + // Previous coeff was last non-zero coeff. return n; } // Sequence of zero coeffs. - while (br.GetBit(p.Probabilities[1]) is 0) + while (br.GetBit(p.Probabilities[1]) == 0) { p = prob[++n].Probabilities[0]; - if (n is 16) + if (n == 16) { return 16; } @@ -984,7 +991,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Non zero coeffs. int v; - if (br.GetBit(p.Probabilities[2]) is 0) + if (br.GetBit(p.Probabilities[2]) == 0) { v = 1; p = prob[n + 1].Probabilities[1]; @@ -1006,9 +1013,9 @@ namespace SixLabors.ImageSharp.Formats.WebP { // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 int v; - if (br.GetBit(p[3]) is 0) + if (br.GetBit(p[3]) == 0) { - if (br.GetBit(p[4]) is 0) + if (br.GetBit(p[4]) == 0) { v = 2; } @@ -1019,9 +1026,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - if (br.GetBit(p[6]) is 0) + if (br.GetBit(p[6]) == 0) { - if (br.GetBit(p[7]) is 0) + if (br.GetBit(p[7]) == 0) { v = 5 + br.GetBit(159); } @@ -1077,24 +1084,28 @@ namespace SixLabors.ImageSharp.Formats.WebP var tmp = new int[16]; for (int i = 0; i < 4; ++i) { - int a0 = input[0 + i] + input[12 + i]; - int a1 = input[4 + i] + input[8 + i]; - int a2 = input[4 + i] - input[8 + i]; - int a3 = input[0 + i] - input[12 + i]; - tmp[0 + i] = a0 + a1; - tmp[8 + i] = a0 - a1; - tmp[4 + i] = a3 + a2; - tmp[12 + i] = a3 - a2; + int iPlus4 = 4 + i; + int iPlus8 = 8 + i; + int iPlus12 = 12 + i; + int a0 = input[i] + input[iPlus12]; + int a1 = input[iPlus4] + input[iPlus8]; + int a2 = input[iPlus4] - input[iPlus8]; + int a3 = input[i] - input[iPlus12]; + tmp[i] = a0 + a1; + tmp[iPlus8] = a0 - a1; + tmp[iPlus4] = a3 + a2; + tmp[iPlus12] = a3 - a2; } int outputOffset = 0; for (int i = 0; i < 4; ++i) { - int dc = tmp[0 + (i * 4)] + 3; - int a0 = dc + tmp[3 + (i * 4)]; - int a1 = tmp[1 + (i * 4)] + tmp[2 + (i * 4)]; - int a2 = tmp[1 + (i * 4)] - tmp[2 + (i * 4)]; - int a3 = dc - tmp[3 + (i * 4)]; + int imul4 = i * 4; + int dc = tmp[0 + imul4] + 3; + int a0 = dc + tmp[3 + imul4]; + int a1 = tmp[1 + imul4] + tmp[2 + imul4]; + int a2 = tmp[1 + imul4] - tmp[2 + imul4]; + int a3 = dc - tmp[3 + imul4]; output[outputOffset + 0] = (short)((a0 + a1) >> 3); output[outputOffset + 16] = (short)((a3 + a2) >> 3); output[outputOffset + 32] = (short)((a0 - a1) >> 3); @@ -1120,15 +1131,15 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) { hasValue = this.bitReader.ReadBool(); - int quantizeValue = hasValue ? this.bitReader.ReadSignedValue(7) : 0; - vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; + byte quantizeValue = (byte)(hasValue ? this.bitReader.ReadSignedValue(7) : 0); + vp8SegmentHeader.Quantizer[i] = quantizeValue; } for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) { hasValue = this.bitReader.ReadBool(); - int filterStrengthValue = hasValue ? this.bitReader.ReadSignedValue(6) : 0; - vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; + byte filterStrengthValue = (byte)(hasValue ? this.bitReader.ReadSignedValue(6) : 0); + vp8SegmentHeader.FilterStrength[i] = filterStrengthValue; } if (vp8SegmentHeader.UpdateMap) @@ -1157,7 +1168,7 @@ namespace SixLabors.ImageSharp.Formats.WebP vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); - dec.Filter = (vp8FilterHeader.Level is 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; + dec.Filter = (vp8FilterHeader.Level == 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; if (vp8FilterHeader.UseLfDelta) { // Update lf-delta? @@ -1200,8 +1211,9 @@ namespace SixLabors.ImageSharp.Formats.WebP dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; int lastPart = dec.NumPartsMinusOne; - int partStart = startIdx + (lastPart * 3); - sizeLeft -= lastPart * 3; + int lastPartMul3 = lastPart * 3; + int partStart = startIdx + lastPartMul3; + sizeLeft -= lastPartMul3; for (int p = 0; p < lastPart; ++p) { int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); @@ -1292,10 +1304,10 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int p = 0; p < WebPConstants.NumProbas; ++p) { byte prob = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; - int v = this.bitReader.GetBit(prob) != 0 - ? (int)this.bitReader.ReadValue(8) - : WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; + byte v = (byte)(this.bitReader.GetBit(prob) != 0 + ? this.bitReader.ReadValue(8) + : WebPLookupTables.DefaultCoeffsProba[t, b, c, p]); + proba.Bands[t, b].Probabilities[c].Probabilities[p] = v; } } } @@ -1323,14 +1335,15 @@ namespace SixLabors.ImageSharp.Formats.WebP io.ScaledHeight = io.ScaledHeight; io.MbW = io.Width; io.MbH = io.Height; - io.YStride = (int)(16 * ((pictureHeader.Width + 15) >> 4)); - io.UvStride = (int)(8 * ((pictureHeader.Width + 15) >> 4)); + uint strideLength = (pictureHeader.Width + 15) >> 4; + io.YStride = (int)(16 * strideLength); + io.UvStride = (int)(8 * strideLength); int intraPredModeSize = 4 * dec.MbWidth; dec.IntraT = new byte[intraPredModeSize]; int extraPixels = WebPConstants.FilterExtraRows[(int)dec.Filter]; - if (dec.Filter is LoopFilter.Complex) + if (dec.Filter == LoopFilter.Complex) { // For complex filter, we need to preserve the dependency chain. dec.TopLeftMbX = 0; @@ -1340,8 +1353,9 @@ namespace SixLabors.ImageSharp.Formats.WebP { // For simple filter, we include 'extraPixels' on the other side of the boundary, // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. - dec.TopLeftMbX = (-extraPixels) >> 4; - dec.TopLeftMbY = (-extraPixels) >> 4; + var extraShift4 = (-extraPixels) >> 4; + dec.TopLeftMbX = extraShift4; + dec.TopLeftMbY = extraShift4; if (dec.TopLeftMbX < 0) { dec.TopLeftMbX = 0; @@ -1388,16 +1402,16 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int CheckMode(int mbx, int mby, int mode) { // B_DC_PRED - if (mode is 0) + if (mode == 0) { - if (mbx is 0) + if (mbx == 0) { - return (mby is 0) + return (mby == 0) ? 6 // B_DC_PRED_NOTOPLEFT : 5; // B_DC_PRED_NOLEFT } - return (mby is 0) + return (mby == 0) ? 4 // B_DC_PRED_NOTOP : 0; // B_DC_PRED } From 3f37966fb912603b0b0aa2a9ef98473f62a3659d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Apr 2020 20:29:54 +0200 Subject: [PATCH 157/359] Fix broken test image --- tests/ImageSharp.Tests/TestImages.cs | 6 +++--- .../WebP/{iccp_lossless.webp => lossless_with_iccp.webp} | 0 .../Input/WebP/{iccp_lossy.webp => lossy_with_iccp.webp} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename tests/Images/Input/WebP/{iccp_lossless.webp => lossless_with_iccp.webp} (100%) rename tests/Images/Input/WebP/{iccp_lossy.webp => lossy_with_iccp.webp} (100%) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 55a9e5e94..650fc7546 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Tests { public const string Earth = "WebP/earth_lossless.webp"; public const string WithExif = "WebP/exif_lossless.webp"; - public const string WithIccp = "WebP/iccp_lossless.webp"; + public const string WithIccp = "WebP/lossless_with_iccp.webp"; public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; public const string NoTransform2 = "WebP/lossless_vec_2_0.webp"; public const string GreenTransform1 = "WebP/lossless1.webp"; @@ -457,7 +457,7 @@ namespace SixLabors.ImageSharp.Tests { public const string Earth = "WebP/earth_lossy.webp"; public const string WithExif = "WebP/exif_lossy.webp"; - public const string WithIccp = "WebP/iccp_lossy.webp"; + public const string WithIccp = "WebP/lossy_with_iccp.webp"; // Lossy images without macroblock filtering. public const string Bike = "WebP/bike_lossy.webp"; @@ -476,7 +476,7 @@ namespace SixLabors.ImageSharp.Tests public const string SimpleFilter05 = "WebP/test-nostrong.webp"; // Lossy images with a complex filter. - public const string IccpComplexFilter = "WebP/iccp_lossy.webp"; + public const string IccpComplexFilter = WithIccp; public const string VeryShort = "WebP/very_short.webp"; public const string BikeComplexFilter = "WebP/bike_lossy_complex_filter.webp"; public const string ComplexFilter01 = "WebP/vp80-02-inter-1418.webp"; diff --git a/tests/Images/Input/WebP/iccp_lossless.webp b/tests/Images/Input/WebP/lossless_with_iccp.webp similarity index 100% rename from tests/Images/Input/WebP/iccp_lossless.webp rename to tests/Images/Input/WebP/lossless_with_iccp.webp diff --git a/tests/Images/Input/WebP/iccp_lossy.webp b/tests/Images/Input/WebP/lossy_with_iccp.webp similarity index 100% rename from tests/Images/Input/WebP/iccp_lossy.webp rename to tests/Images/Input/WebP/lossy_with_iccp.webp From ba775aefebff61d5eb86fae04863ae7e712ab4bb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Apr 2020 20:43:12 +0200 Subject: [PATCH 158/359] .gitattributes: Treat webp as binary --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 24a21b60d..c0bff6e18 100644 --- a/.gitattributes +++ b/.gitattributes @@ -82,6 +82,7 @@ *.tga binary *.ttc binary *.ttf binary +*.webp binary *.woff binary *.woff2 binary *.xls binary From 98698453ac1b534dd2b6a7ed2a3fb485d80db11d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2020 12:35:20 +0200 Subject: [PATCH 159/359] Revert ".gitattributes: Treat webp as binary" This reverts commit fe77412dcfac9686df8901bbc97154e2e9a106e8. --- .gitattributes | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index c0bff6e18..24a21b60d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -82,7 +82,6 @@ *.tga binary *.ttc binary *.ttf binary -*.webp binary *.woff binary *.woff2 binary *.xls binary From eec6a319680e7a7eead53f172cdbb4b35be77645 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2020 12:46:02 +0200 Subject: [PATCH 160/359] Update shared infrastructure external --- .gitattributes | 1 + shared-infrastructure | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 24a21b60d..c0bff6e18 100644 --- a/.gitattributes +++ b/.gitattributes @@ -82,6 +82,7 @@ *.tga binary *.ttc binary *.ttf binary +*.webp binary *.woff binary *.woff2 binary *.xls binary diff --git a/shared-infrastructure b/shared-infrastructure index ea561c249..a71135a97 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb +Subproject commit a71135a9749cb2823171d918fc936e05cd7f36a6 From 1cfdfcc00fb169488f646f10c8721b1ecfefb4d6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2020 13:16:00 +0200 Subject: [PATCH 161/359] Fix inconsequent naming of webp test image path --- tests/ImageSharp.Tests/TestImages.cs | 42 ++++++++++---------- tests/Images/Input/WebP/lossy_with_iccp.webp | 4 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b1e3ea8cb..f285dbc94 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -482,27 +482,27 @@ namespace SixLabors.ImageSharp.Tests public const string ColorIndexTransform3 = "WebP/lossless_vec_1_5.webp"; public const string ColorIndexTransform4 = "WebP/lossless_vec_2_1.webp"; public const string ColorIndexTransform5 = "WebP/lossless_vec_2_5.webp"; - public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor - public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green - public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color - public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor - public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor - public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor - public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color - public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color - public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color - public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color - public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor - public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor - public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor - public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color - public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; // substract_green, predictor, cross_color + public const string TwoTransforms1 = "WebP/lossless_vec_1_10.webp"; // cross_color, predictor + public const string TwoTransforms2 = "WebP/lossless_vec_1_12.webp"; // cross_color, substract_green + public const string TwoTransforms3 = "WebP/lossless_vec_1_13.webp"; // color_indexing, cross_color + public const string TwoTransforms4 = "WebP/lossless_vec_1_3.webp"; // color_indexing, predictor + public const string TwoTransforms5 = "WebP/lossless_vec_1_6.webp"; // substract_green, predictor + public const string TwoTransforms6 = "WebP/lossless_vec_1_7.webp"; // color_indexing, predictor + public const string TwoTransforms7 = "WebP/lossless_vec_1_9.webp"; // color_indexing, cross_color + public const string TwoTransforms8 = "WebP/lossless_vec_2_10.webp"; // predictor, cross_color + public const string TwoTransforms9 = "WebP/lossless_vec_2_12.webp"; // substract_green, cross_color + public const string TwoTransforms10 = "WebP/lossless_vec_2_13.webp"; // color_indexing, cross_color + public const string TwoTransforms11 = "WebP/lossless_vec_2_3.webp"; // color_indexing, predictor + public const string TwoTransforms12 = "WebP/lossless_vec_2_6.webp"; // substract_green, predictor + public const string TwoTransforms13 = "WebP/lossless_vec_2_9.webp"; // color_indexing, predictor + public const string ThreeTransforms1 = "WebP/color_cache_bits_11.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms2 = "WebP/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms3 = "WebP/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms4 = "WebP/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms5 = "WebP/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms6 = "WebP/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms7 = "WebP/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color + public const string BikeThreeTransforms = "WebP/bike_lossless.webp"; // substract_green, predictor, cross_color // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." diff --git a/tests/Images/Input/WebP/lossy_with_iccp.webp b/tests/Images/Input/WebP/lossy_with_iccp.webp index a87580edf..fa85b7b54 100644 --- a/tests/Images/Input/WebP/lossy_with_iccp.webp +++ b/tests/Images/Input/WebP/lossy_with_iccp.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46e70fecb9cfc72243dfad58b68baa58c14f12660f8d2c88c30d5e050856b059 -size 25241 +oid sha256:4323099f032940d9596265ac59529b6810d1fd84d2effc993e71b52778c18ebf +size 25242 From e84301c134e6d95d7954c46df58b82056f90ebb4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2020 18:56:33 +0200 Subject: [PATCH 162/359] Remove linq usage while getting band probability row, add additional guard checks. --- .../Formats/WebP/IntraPredictionMode.cs | 28 +++++++++++++ src/ImageSharp/Formats/WebP/Readme.md | 3 +- .../Formats/WebP/ReconstructionFilter.cs | 12 ------ src/ImageSharp/Formats/WebP/VP8BandProbas.cs | 6 +++ src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 4 +- .../Formats/WebP/Vp8FilterHeader.cs | 39 ++++++++++++++++--- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 38 ++++++++++++++++-- .../Formats/WebP/Vp8MacroBlockData.cs | 18 +++++++++ src/ImageSharp/Formats/WebP/Vp8Proba.cs | 12 +++--- src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs | 6 +++ src/ImageSharp/Formats/WebP/Vp8Profile.cs | 35 ----------------- src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 2 +- .../Formats/WebP/Vp8SegmentHeader.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 7 ---- .../Formats/WebP/WebPLosslessDecoder.cs | 1 - .../Formats/WebP/WebPLossyDecoder.cs | 28 +++++-------- tests/ImageSharp.Tests/TestImages.cs | 8 ++-- tests/Images/Input/WebP/grid.png | 3 -- .../Images/Input/WebP/lossless_with_iccp.webp | 4 +- tests/Images/Input/WebP/peak.png | 3 -- 20 files changed, 154 insertions(+), 107 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/IntraPredictionMode.cs delete mode 100644 src/ImageSharp/Formats/WebP/ReconstructionFilter.cs delete mode 100644 src/ImageSharp/Formats/WebP/Vp8Profile.cs delete mode 100644 tests/Images/Input/WebP/grid.png delete mode 100644 tests/Images/Input/WebP/peak.png diff --git a/src/ImageSharp/Formats/WebP/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/IntraPredictionMode.cs new file mode 100644 index 000000000..29c64c765 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/IntraPredictionMode.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum IntraPredictionMode + { + /// + /// Predict DC using row above and column to the left. + /// + DcPrediction = 0, + + /// + /// Propagate second differences a la "True Motion". + /// + TrueMotion = 1, + + /// + /// Predict rows using row above. + /// + VPrediction = 2, + + /// + /// Predict columns using column to the left. + /// + HPrediction = 3, + } +} diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md index c4c800464..f3daead83 100644 --- a/src/ImageSharp/Formats/WebP/Readme.md +++ b/src/ImageSharp/Formats/WebP/Readme.md @@ -4,6 +4,7 @@ Reference implementation, specification and stuff like that: - [google webp introduction](https://developers.google.com/speed/webp) - [WebP Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) -- [WebP VP8 chunk Spec](http://tools.ietf.org/html/rfc6386) +- [WebP VP8 Spec, Lossy](http://tools.ietf.org/html/rfc6386) +- [WebP VP8L Spec, Lossless](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) - [WebP filefront](https://wiki.fileformat.com/image/webp/) - [WebP test data](https://github.com/webmproject/libwebp-test-data/) diff --git a/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs b/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs deleted file mode 100644 index ff59e6b66..000000000 --- a/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.WebP -{ - internal enum ReconstructionFilter - { - None, - Bicubic, - Bilinear - } -} diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs index a9c9420d1..d96e9bd63 100644 --- a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs @@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8BandProbas { + /// + /// Initializes a new instance of the class. + /// public Vp8BandProbas() { this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx]; @@ -17,6 +20,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Gets the Probabilities. + /// public Vp8ProbaArray[] Probabilities { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index aa69bb17e..3c09821b7 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -285,12 +285,12 @@ namespace SixLabors.ImageSharp.Formats.WebP baseLevel = this.SegmentHeader.FilterStrength[s]; if (!this.SegmentHeader.Delta) { - baseLevel += hdr.Level; + baseLevel += hdr.FilterLevel; } } else { - baseLevel = hdr.Level; + baseLevel = hdr.FilterLevel; } for (int i4x4 = 0; i4x4 <= 1; ++i4x4) diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs index 7093b1bf0..2d854664a 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs @@ -9,6 +9,13 @@ namespace SixLabors.ImageSharp.Formats.WebP private const int NumModeLfDeltas = 4; + private int filterLevel; + + private int sharpness; + + /// + /// Initializes a new instance of the class. + /// public Vp8FilterHeader() { this.RefLfDelta = new int[NumRefLfDeltas]; @@ -20,16 +27,36 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public LoopFilter LoopFilter { get; set; } - // [0..63] - public int Level { get; set; } + /// + /// Gets or sets the filter level. Valid values are [0..63]. + /// + public int FilterLevel + { + get => this.filterLevel; + set + { + Guard.MustBeBetweenOrEqualTo(value, 0, 63, nameof(this.FilterLevel)); + this.filterLevel = value; + } + } - // [0..7] - public int Sharpness { get; set; } + /// + /// Gets or sets the filter sharpness. Valid values are [0..7]. + /// + public int Sharpness + { + get => this.sharpness; + set + { + Guard.MustBeBetweenOrEqualTo(value, 0, 7, nameof(this.Sharpness)); + this.sharpness = value; + } + } public bool UseLfDelta { get; set; } - public int[] RefLfDelta { get; private set; } + public int[] RefLfDelta { get; } - public int[] ModeLfDelta { get; private set; } + public int[] ModeLfDelta { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index 6b53532ba..42756e5e3 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -8,6 +8,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8FilterInfo : IDeepCloneable { + private byte limit; + + private byte innerLevel; + + private byte highEdgeVarianceThreshold; + /// /// Initializes a new instance of the class. /// @@ -30,12 +36,28 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the filter limit in [3..189], or 0 if no filtering. /// - public byte Limit { get; set; } + public byte Limit + { + get => this.limit; + set + { + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)189, nameof(this.Limit)); + this.limit = value; + } + } /// - /// Gets or sets the inner limit in [1..63]. + /// Gets or sets the inner limit in [1..63], or 0 if no filtering. /// - public byte InnerLevel { get; set; } + public byte InnerLevel + { + get => this.innerLevel; + set + { + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)63, nameof(this.InnerLevel)); + this.innerLevel = value; + } + } /// /// Gets or sets a value indicating whether to do inner filtering. @@ -45,7 +67,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the high edge variance threshold in [0..2]. /// - public byte HighEdgeVarianceThreshold { get; set; } + public byte HighEdgeVarianceThreshold + { + get => this.highEdgeVarianceThreshold; + set + { + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)2, nameof(this.HighEdgeVarianceThreshold)); + this.highEdgeVarianceThreshold = value; + } + } /// public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index e0536e6b4..5e473e02e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -37,8 +37,26 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public byte UvMode { get; set; } + /// + /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). + /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + /// code=0 -> no coefficient + /// code=1 -> only DC + /// code=2 -> first three coefficients are non-zero + /// code=3 -> more than three coefficients are non-zero + /// This allows to call specialized transform functions. + /// public uint NonZeroY { get; set; } + /// + /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). + /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + /// code=0 -> no coefficient + /// code=1 -> only DC + /// code=2 -> first three coefficients are non-zero + /// code=3 -> more than three coefficients are non-zero + /// This allows to call specialized transform functions. + /// public uint NonZeroUv { get; set; } public bool Skip { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs index ae34f936d..f6ff52eeb 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs @@ -10,11 +10,14 @@ namespace SixLabors.ImageSharp.Formats.WebP { private const int MbFeatureTreeProbs = 3; + /// + /// Initializes a new instance of the class. + /// public Vp8Proba() { this.Segments = new uint[MbFeatureTreeProbs]; this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; - this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1]; + this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes][]; for (int i = 0; i < WebPConstants.NumTypes; i++) { @@ -26,10 +29,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < WebPConstants.NumTypes; i++) { - for (int j = 0; j < 17; j++) - { - this.BandsPtr[i, j] = new Vp8BandProbas(); - } + this.BandsPtr[i] = new Vp8BandProbas[16 + 1]; } } @@ -37,6 +37,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8BandProbas[,] Bands { get; } - public Vp8BandProbas[,] BandsPtr { get; } + public Vp8BandProbas[][] BandsPtr { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs index 37ccc358b..6cc87a811 100644 --- a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs @@ -8,11 +8,17 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8ProbaArray { + /// + /// Initializes a new instance of the class. + /// public Vp8ProbaArray() { this.Probabilities = new byte[WebPConstants.NumProbas]; } + /// + /// Gets the probabilities. + /// public byte[] Probabilities { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Profile.cs b/src/ImageSharp/Formats/WebP/Vp8Profile.cs deleted file mode 100644 index bc3a3ac06..000000000 --- a/src/ImageSharp/Formats/WebP/Vp8Profile.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.WebP -{ - /// - /// The version number of the frame header enables or disables certain features in the bitstream. - /// +---------+-------------------------+-------------+ - /// | Version | Reconstruction Filter | Loop Filter | - /// +---------+-------------------------+-------------+ - /// | 0 | Bicubic | Normal | - /// | | | | - /// | 1 | Bilinear | Simple | - /// | | | | - /// | 2 | Bilinear | None | - /// | | | | - /// | 3 | None | None | - /// | | | | - /// | Other | Reserved for future use | | - /// +---------+-------------------------+-------------+ - /// See paragraph 9, https://tools.ietf.org/html/rfc6386. - /// - internal class Vp8Profile - { - /// - /// Gets or sets the reconstruction filter. - /// - public ReconstructionFilter ReconstructionFilter { get; set; } - - /// - /// Gets or sets the loop filter. - /// - public LoopFilter LoopFilter { get; set; } - } -} diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs index 5fd288abe..47bc96502 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP get => this.dither; set { - Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.dither)); + Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.Dither)); this.dither = value; } } diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs index 27bce1f04..c7c198bb2 100644 --- a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs @@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets quantization changes. /// - public byte[] Quantizer { get; private set; } + public byte[] Quantizer { get; } /// /// Gets the filter strength for segments. /// - public byte[] FilterStrength { get; private set; } + public byte[] FilterStrength { get; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 29cec2b47..adeb4291e 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -110,7 +110,6 @@ namespace SixLabors.ImageSharp.Formats.WebP NumDistanceCodes }; - // VP8 constants from here on: public const int NumMbSegments = 4; public const int MaxNumPartitions = 8; @@ -126,12 +125,6 @@ namespace SixLabors.ImageSharp.Formats.WebP // this is the common stride for enc/dec public const int Bps = 32; - // intra prediction modes (TODO: maybe use an enum for this) - public const int DcPred = 0; // predict DC using row above and column to the left - public const int TmPred = 1; // propagate second differences a la "True Motion" - public const int VPred = 2; // predict rows using row above - public const int HPred = 3; // predict columns using column to the left - /// public byte[] Preds { get; } + /// + /// Gets the diffusion error. + /// + public sbyte[] TopDerr { get; } + /// /// Gets a rough limit for header bits per MB. /// @@ -364,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int yStride = width; int uvStride = (yStride + 1) >> 1; - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.mbw, this.mbh); var alphas = new int[WebPConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); @@ -487,7 +499,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.proba.FinalizeTokenProbas(); } - this.proba.CalculateLevelCosts(); // finalize costs + this.proba.CalculateLevelCosts(); // Finalize costs. } private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) @@ -495,7 +507,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.Mbw, this.Mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); long size = 0; long sizeP0 = 0; long distortion = 0; @@ -1277,11 +1289,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy tmp.AsSpan((n + 1) * 16, 16)); } - /* TODO: - if (it->top_derr_ != NULL) - { - CorrectDCValues(it, &dqm->uv_, tmp, rd); - }*/ + this.CorrectDCValues(it, dqm.Uv, tmp, rd); for (n = 0; n < 8; n += 2) { @@ -1340,6 +1348,62 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + private void CorrectDCValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) + { +#pragma warning disable SA1005 // Single line comments should begin with single space + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. +#pragma warning restore SA1005 // Single line comments should begin with single space + for (int ch = 0; ch <= 1; ++ch) + { + Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); + Span left = it.LeftDerr.AsSpan(ch, 2); + int err0, err1, err2, err3; + Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); + c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); + err0 = QuantizeSingle(c, mtx); + c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); + err1 = QuantizeSingle(c.Slice(1 * 16), mtx); + c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); + err2 = QuantizeSingle(c.Slice(2 * 16), mtx); + c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); + err3 = QuantizeSingle(c.Slice(3 * 16), mtx); + + // TODO: set errors in rd + // rd->derr[ch][0] = (int8_t)err1; + // rd->derr[ch][1] = (int8_t)err2; + // rd->derr[ch][2] = (int8_t)err3; + } + } + + // Quantize as usual, but also compute and return the quantization error. + // Error is already divided by DSHIFT. + private static int QuantizeSingle(Span v, Vp8Matrix mtx) + { + int v0 = v[0]; + bool sign = v0 < 0; + if (sign) + { + v0 = -v0; + } + + if (v0 > (int)mtx.ZThresh[0]) + { + int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; + int err = v0 - qV; + v[0] = (short)(sign ? -qV : qV); + return (sign ? -err : err) >> DSCALE; + } + + v[0] = 0; + return (sign ? -v0 : v0) >> DSCALE; + } + private void FTransformWht(Span input, Span output) { var tmp = new int[16]; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3f54d206c..67ef6cef3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -497,6 +497,9 @@ namespace SixLabors.ImageSharp.Tests public static class WebP { + // Reference image as png + public const string Peak = "WebP/Peak.png"; + public static class Animated { public const string Animated1 = "WebP/animated-webp.webp"; diff --git a/tests/Images/Input/WebP/peak.png b/tests/Images/Input/WebP/peak.png new file mode 100644 index 000000000..5a417b9c0 --- /dev/null +++ b/tests/Images/Input/WebP/peak.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9b56ed5c1278664222c77f9a452b824b4f9215c819502b3f6b0e0d44270e7e7 +size 26456 From 1a005bd97a755db6dc13bce6d0b87a473c570098 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 11 Nov 2020 16:50:53 +0100 Subject: [PATCH 227/359] Scale alpha and uv alpha by total number of macroblocks --- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 3853e56e4..ed18bc670 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -379,6 +379,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.mbw, this.mbh); var alphas = new int[WebPConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); + int totalMb = this.mbw * this.mbw; + this.alpha = this.alpha / totalMb; + this.uvAlpha = this.uvAlpha / totalMb; // Analysis is done, proceed to actual encoding. this.segmentHeader = new Vp8EncSegmentHeader(4); From 86d8b6fea7bb5e61b8cee0dc39237b20b45c5695 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 12 Nov 2020 17:49:23 +0100 Subject: [PATCH 228/359] Fix issue with decoding VP8 alpha channel: Use8BDecode will only be set to true, if there is a transform present --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/1602311202.webp | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/WebP/1602311202.webp diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index edb72564e..fc071095b 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8LBitReader(data); this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); - this.Use8BDecode = Is8BOptimizable(this.Vp8LDec.Metadata); + this.Use8BDecode = this.Vp8LDec.Transforms.Count > 0 && Is8BOptimizable(this.Vp8LDec.Metadata); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 3eb51a597..87af15ef3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -181,6 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaThinkingSmiley, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 67ef6cef3..6be4d3dc7 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -640,6 +640,7 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaCompressedVerticalFilter = "WebP/alpha_filter_2_method_1.webp"; public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp"; public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; + public const string AlphaThinkingSmiley = "WebP/1602311202.webp"; } } } diff --git a/tests/Images/Input/WebP/1602311202.webp b/tests/Images/Input/WebP/1602311202.webp new file mode 100644 index 000000000..4dfd0184f --- /dev/null +++ b/tests/Images/Input/WebP/1602311202.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dac76bec0e5b23a988b0f2221e9b20e63dc207ef48f33e49a4336a874e2a915 +size 18406 From d801ca8de2b7638d37048bd216f0825ccad9b706 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 12 Nov 2020 20:49:02 +0100 Subject: [PATCH 229/359] Ensure PutBitsFlushBits only writes 32 bits to the buffer --- src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 139eebf9a..eb2502720 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// internal class Vp8LBitWriter : BitWriterBase { + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[8]; + /// /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. /// @@ -166,7 +171,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.BitWriterResize(extraSize); } - BinaryPrimitives.WriteUInt64LittleEndian(this.Buffer.AsSpan(this.cur), this.bits); + BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits); + this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur)); + this.cur += WriterBytes; this.bits >>= WriterBits; this.used -= WriterBits; From a3bf97b54e5a3223249e23035d04e7513f41b170 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 13 Nov 2020 14:33:26 +0100 Subject: [PATCH 230/359] Fix VP8 issue with quality 20 and method 2: UseSkipProba was ignored --- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 7 ++++++- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index ed18bc670..e60f5fdc1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -400,9 +400,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.InitFilter(); do { + bool dontUseSkip = !this.Proba.UseSkipProba; + var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height, false); - if (!this.Decimate(it, info, this.rdOptLevel)) + + // Warning! order is important: first call VP8Decimate() and + // *then* decide how to code the skip decision if there's one. + if (!this.Decimate(it, info, this.rdOptLevel) || dontUseSkip) { this.CodeResiduals(it, info); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs index 294e548e8..e8cee31bf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossy { + [DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] internal class Vp8MacroBlockInfo { public Vp8MacroBlockType MacroBlockType { get; set; } From 1b296c9109d75720179dc2652fe689e58242328b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 13 Nov 2020 21:17:37 +0100 Subject: [PATCH 231/359] Use tolerant comparer for lossy webp encoder tests --- .../Formats/WebP/WebPEncoderTests.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 90f3a3f42..51276962f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP @@ -67,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP using Image image = provider.GetImage(); var testOutputDetails = string.Concat("lossy", "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } [Theory] @@ -81,16 +82,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void Encode_Lossy_WithDifferentMethods_Works(TestImageProvider provider, int method) where TPixel : unmanaged, IPixel { + int quality = 75; var encoder = new WebPEncoder() { Lossy = true, Method = method, - Quality = 75 + Quality = quality }; using Image image = provider.GetImage(); var testOutputDetails = string.Concat("lossy", "_m", method); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); + } + + private static ImageComparer GetComparer(int quality) + { + float tolerance = 0.01f; // ~1.0% + + if (quality < 30) + { + tolerance = 0.02f; // ~2.0% + } + + return ImageComparer.Tolerant(tolerance); } } } From 7a6cb423aae5a194e32cefb557ad2eb30394e5c1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Nov 2020 17:00:48 +0100 Subject: [PATCH 232/359] Refactor Vp8Encoder --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 1 - .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 137 ++++ .../Formats/WebP/Lossy/Vp8EncIterator.cs | 513 +------------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 580 +-------------- .../Formats/WebP/Lossy/Vp8Encoding.cs | 664 ++++++++++++++++++ .../Formats/WebP/Lossy/YuvConversion.cs | 260 +++++++ 7 files changed, 1103 insertions(+), 1053 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 813fbb7bc..7eb5bb0d8 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -197,7 +197,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - // TODO: review again if this works as intended. Probably needs a unit test ... var neededSize = this.pos + extraSize; if (neededSize <= this.maxPos) { diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index eb2502720..ade6cb61a 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -185,7 +185,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - // TODO: review again if this works as intended. Probably needs a unit test ... int maxBytes = this.end + this.Buffer.Length; int sizeRequired = this.cur + extraSize; diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs new file mode 100644 index 000000000..e8f782602 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -0,0 +1,137 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Quantization methods. + /// + internal static class QuantEnc + { + private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + + private const int MaxLevel = 2047; + + // Diffusion weights. We under-correct a bit (15/16th of the error is actually + // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. + private const int C1 = 7; // fraction of error sent to the 4x4 block below + private const int C2 = 8; // fraction of error sent to the 4x4 block on the right + private const int DSHIFT = 4; + private const int DSCALE = 1; // storage descaling, needed to make the error fit byte + + public static int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) + { + int nz; + nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; + nz |= QuantEnc.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; + return nz; + } + + public static int QuantizeBlock(Span input, Span output, Vp8Matrix mtx) + { + int last = -1; + int n; + for (n = 0; n < 16; ++n) + { + int j = Zigzag[n]; + bool sign = input[j] < 0; + uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); + if (coeff > mtx.ZThresh[j]) + { + uint q = mtx.Q[j]; + uint iQ = mtx.IQ[j]; + uint b = mtx.Bias[j]; + int level = QuantDiv(coeff, iQ, b); + if (level > MaxLevel) + { + level = MaxLevel; + } + + if (sign) + { + level = -level; + } + + input[j] = (short)(level * (int)q); + output[n] = (short)level; + if (level != 0) + { + last = n; + } + } + else + { + output[n] = 0; + input[j] = 0; + } + } + + return (last >= 0) ? 1 : 0; + } + + // Quantize as usual, but also compute and return the quantization error. + // Error is already divided by DSHIFT. + public static int QuantizeSingle(Span v, Vp8Matrix mtx) + { + int v0 = v[0]; + bool sign = v0 < 0; + if (sign) + { + v0 = -v0; + } + + if (v0 > (int)mtx.ZThresh[0]) + { + int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; + int err = v0 - qV; + v[0] = (short)(sign ? -qV : qV); + return (sign ? -err : err) >> DSCALE; + } + + v[0] = 0; + return (sign ? -v0 : v0) >> DSCALE; + } + + public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) + { +#pragma warning disable SA1005 // Single line comments should begin with single space + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. +#pragma warning restore SA1005 // Single line comments should begin with single space + for (int ch = 0; ch <= 1; ++ch) + { + Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); + Span left = it.LeftDerr.AsSpan(ch, 2); + int err0, err1, err2, err3; + Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); + c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); + err0 = QuantEnc.QuantizeSingle(c, mtx); + c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); + err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); + c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); + err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); + c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); + err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); + + // TODO: set errors in rd + // rd->derr[ch][0] = (int8_t)err1; + // rd->derr[ch][1] = (int8_t)err2; + // rd->derr[ch][2] = (int8_t)err3; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int QuantDiv(uint n, uint iQ, uint b) + { + return (int)(((n * iQ) + b) >> WebPConstants.QFix); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 7ddc2edd0..a347e8263 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.Lossy @@ -23,8 +22,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int MaxIntra16Mode = 2; - private const int MaxIntra4Mode = 2; - private readonly int mbw; private readonly int mbh; @@ -34,50 +31,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly int predsWidth; - private const int I16DC16 = 0 * 16 * WebPConstants.Bps; - - private const int I16TM16 = I16DC16 + 16; - - private const int I16VE16 = 1 * 16 * WebPConstants.Bps; - - private const int I16HE16 = I16VE16 + 16; - - private const int C8DC8 = 2 * 16 * WebPConstants.Bps; - - private const int C8TM8 = C8DC8 + (1 * 16); - - private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); - - private const int C8HE8 = C8VE8 + (1 * 16); - - public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; - - public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; - - private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; - - private const int I4TM4 = I4DC4 + 4; - - private const int I4VE4 = I4DC4 + 8; - - private const int I4HE4 = I4DC4 + 12; - - private const int I4RD4 = I4DC4 + 16; - - private const int I4VR4 = I4DC4 + 20; - - private const int I4LD4 = I4DC4 + 24; - - private const int I4VL4 = I4DC4 + 28; - - private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); - - private const int I4HU4 = I4HD4 + 4; - - public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; - - private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] - // Array to record the position of the top sample to pass to the prediction functions. private readonly byte[] vp8TopLeftI4 = { @@ -134,11 +87,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal); - for (int i = -255; i <= 255 + 255; ++i) - { - this.clip1[255 + i] = this.Clip8b(i); - } - this.Reset(); } @@ -440,7 +388,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8I16ModeOffsets[mode]), 0, 16); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -465,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -663,19 +611,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { Span left = this.X != 0 ? this.YLeft.AsSpan() : null; Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; - this.EncPredLuma16(this.YuvP, left, top); + Vp8Encoding.EncPredLuma16(this.YuvP, left, top); } public void MakeChroma8Preds() { Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; - this.EncPredChroma8(this.YuvP, left, top); + Vp8Encoding.EncPredChroma8(this.YuvP, left, top); } public void MakeIntra4Preds() { - this.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); + Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); } public void SwapOut() @@ -767,452 +715,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - // luma 16x16 prediction (paragraph 12.3). - private void EncPredLuma16(Span dst, Span left, Span top) - { - this.DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); - this.VerticalPred(dst.Slice(I16VE16), top, 16); - this.HorizontalPred(dst.Slice(I16HE16), left, 16); - this.TrueMotion(dst.Slice(I16TM16), left, top, 16); - } - - // Chroma 8x8 prediction (paragraph 12.2). - private void EncPredChroma8(Span dst, Span left, Span top) - { - // U block. - this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); - this.VerticalPred(dst.Slice(C8VE8), top, 8); - this.HorizontalPred(dst.Slice(C8HE8), left, 8); - this.TrueMotion(dst.Slice(C8TM8), left, top, 8); - - // V block. - dst = dst.Slice(8); - if (top != null) - { - top = top.Slice(8); - } - - if (left != null) - { - left = left.Slice(16); - } - - this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); - this.VerticalPred(dst.Slice(C8VE8), top, 8); - this.HorizontalPred(dst.Slice(C8HE8), left, 8); - this.TrueMotion(dst.Slice(C8TM8), left, top, 8); - } - - // Left samples are top[-5 .. -2], top_left is top[-1], top are - // located at top[0..3], and top right is top[4..7] - private void EncPredLuma4(Span dst, Span top, int topOffset) - { - this.Dc4(dst.Slice(I4DC4), top, topOffset); - this.Tm4(dst.Slice(I4TM4), top, topOffset); - this.Ve4(dst.Slice(I4VE4), top, topOffset); - this.He4(dst.Slice(I4HE4), top, topOffset); - this.Rd4(dst.Slice(I4RD4), top, topOffset); - this.Vr4(dst.Slice(I4VR4), top, topOffset); - this.Ld4(dst.Slice(I4LD4), top, topOffset); - this.Vl4(dst.Slice(I4VL4), top, topOffset); - this.Hd4(dst.Slice(I4HD4), top, topOffset); - this.Hu4(dst.Slice(I4HU4), top, topOffset); - } - - private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) - { - int dc = 0; - int j; - if (top != null) - { - for (j = 0; j < size; ++j) - { - dc += top[j]; - } - - if (left != null) - { - // top and left present. - left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) - { - dc += left[j]; - } - } - else - { - // top, but no left. - dc += dc; - } - - dc = (dc + round) >> shift; - } - else if (left != null) - { - // left but no top. - left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) - { - dc += left[j]; - } - - dc += dc; - dc = (dc + round) >> shift; - } - else - { - // no top, no left, nothing. - dc = 0x80; - } - - this.Fill(dst, dc, size); - } - - private void VerticalPred(Span dst, Span top, int size) - { - if (top != null) - { - for (int j = 0; j < size; ++j) - { - top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); - } - } - else - { - this.Fill(dst, 127, size); - } - } - - private void HorizontalPred(Span dst, Span left, int size) - { - if (left != null) - { - left = left.Slice(1); // in the reference implementation, left starts at - 1. - for (int j = 0; j < size; ++j) - { - dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); - } - } - else - { - this.Fill(dst, 129, size); - } - } - - private void TrueMotion(Span dst, Span left, Span top, int size) - { - if (left != null) - { - if (top != null) - { - Span clip = this.clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 - for (int y = 0; y < size; ++y) - { - Span clipTable = clip.Slice(left[y + 1]); // left[y] - for (int x = 0; x < size; ++x) - { - dst[x] = clipTable[top[x]]; - } - - dst = dst.Slice(WebPConstants.Bps); - } - } - else - { - this.HorizontalPred(dst, left, size); - } - } - else - { - // true motion without left samples (hence: with default 129 value) - // is equivalent to VE prediction where you just copy the top samples. - // Note that if top samples are not available, the default value is - // then 129, and not 127 as in the VerticalPred case. - if (top != null) - { - this.VerticalPred(dst, top, size); - } - else - { - this.Fill(dst, 129, size); - } - } - } - - private void Dc4(Span dst, Span top, int topOffset) - { - uint dc = 4; - int i; - for (i = 0; i < 4; ++i) - { - dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); - } - - this.Fill(dst, (int)(dc >> 3), 4); - } - - private void Tm4(Span dst, Span top, int topOffset) - { - Span clip = this.clip1.AsSpan(255 - top[topOffset - 1]); - for (int y = 0; y < 4; ++y) - { - Span clipTable = clip.Slice(top[topOffset - 2 - y]); - for (int x = 0; x < 4; ++x) - { - dst[x] = clipTable[top[topOffset + x]]; - } - - dst = dst.Slice(WebPConstants.Bps); - } - } - - private void Ve4(Span dst, Span top, int topOffset) - { - // vertical - byte[] vals = - { - LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), - LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), - LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), - LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) - }; - - for (int i = 0; i < 4; ++i) - { - vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); - } - } - - private void He4(Span dst, Span top, int topOffset) - { - // horizontal - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * LossyUtils.Avg3(i, j, k); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(j, k, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); - } - - private void Rd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); - var ijk = LossyUtils.Avg3(i, j, k); - LossyUtils.Dst(dst, 0, 2, ijk); - LossyUtils.Dst(dst, 1, 3, ijk); - var xij = LossyUtils.Avg3(x, i, j); - LossyUtils.Dst(dst, 0, 1, xij); - LossyUtils.Dst(dst, 1, 2, xij); - LossyUtils.Dst(dst, 2, 3, xij); - var axi = LossyUtils.Avg3(a, x, i); - LossyUtils.Dst(dst, 0, 0, axi); - LossyUtils.Dst(dst, 1, 1, axi); - LossyUtils.Dst(dst, 2, 2, axi); - LossyUtils.Dst(dst, 3, 3, axi); - var bax = LossyUtils.Avg3(b, a, x); - LossyUtils.Dst(dst, 1, 0, bax); - LossyUtils.Dst(dst, 2, 1, bax); - LossyUtils.Dst(dst, 3, 2, bax); - var cba = LossyUtils.Avg3(c, b, a); - LossyUtils.Dst(dst, 2, 0, cba); - LossyUtils.Dst(dst, 3, 1, cba); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); - } - - private void Vr4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - var xa = LossyUtils.Avg2(x, a); - LossyUtils.Dst(dst, 0, 0, xa); - LossyUtils.Dst(dst, 1, 2, xa); - var ab = LossyUtils.Avg2(a, b); - LossyUtils.Dst(dst, 1, 0, ab); - LossyUtils.Dst(dst, 2, 2, ab); - var bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 2, 0, bc); - LossyUtils.Dst(dst, 3, 2, bc); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); - LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); - var ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 0, 1, ixa); - LossyUtils.Dst(dst, 1, 3, ixa); - var xab = LossyUtils.Avg3(x, a, b); - LossyUtils.Dst(dst, 1, 1, xab); - LossyUtils.Dst(dst, 2, 3, xab); - var abc = LossyUtils.Avg3(a, b, c); - LossyUtils.Dst(dst, 2, 1, abc); - LossyUtils.Dst(dst, 3, 3, abc); - LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); - } - - private void Ld4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 0, bcd); - LossyUtils.Dst(dst, 0, 1, bcd); - var cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 0, cde); - LossyUtils.Dst(dst, 1, 1, cde); - LossyUtils.Dst(dst, 0, 2, cde); - var def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 0, def); - LossyUtils.Dst(dst, 2, 1, def); - LossyUtils.Dst(dst, 1, 2, def); - LossyUtils.Dst(dst, 0, 3, def); - var efg = LossyUtils.Avg3(e, f, g); - LossyUtils.Dst(dst, 3, 1, efg); - LossyUtils.Dst(dst, 2, 2, efg); - LossyUtils.Dst(dst, 1, 3, efg); - var fgh = LossyUtils.Avg3(f, g, h); - LossyUtils.Dst(dst, 3, 2, fgh); - LossyUtils.Dst(dst, 2, 3, fgh); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); - } - - private void Vl4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); - var bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 1, 0, bc); - LossyUtils.Dst(dst, 0, 2, bc); - var cd = LossyUtils.Avg2(c, d); - LossyUtils.Dst(dst, 2, 0, cd); - LossyUtils.Dst(dst, 1, 2, cd); - var de = LossyUtils.Avg2(d, e); - LossyUtils.Dst(dst, 3, 0, de); - LossyUtils.Dst(dst, 2, 2, de); - LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 1, bcd); - LossyUtils.Dst(dst, 0, 3, bcd); - var cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 1, cde); - LossyUtils.Dst(dst, 1, 3, cde); - var def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 1, def); - LossyUtils.Dst(dst, 2, 3, def); - LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); - } - - private void Hd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - - var ix = LossyUtils.Avg2(i, x); - LossyUtils.Dst(dst, 0, 0, ix); - LossyUtils.Dst(dst, 2, 1, ix); - var ji = LossyUtils.Avg2(j, i); - LossyUtils.Dst(dst, 0, 1, ji); - LossyUtils.Dst(dst, 2, 2, ji); - var kj = LossyUtils.Avg2(k, j); - LossyUtils.Dst(dst, 0, 2, kj); - LossyUtils.Dst(dst, 2, 3, kj); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); - LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); - var ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 1, 0, ixa); - LossyUtils.Dst(dst, 3, 1, ixa); - var jix = LossyUtils.Avg3(j, i, x); - LossyUtils.Dst(dst, 1, 1, jix); - LossyUtils.Dst(dst, 3, 2, jix); - var kji = LossyUtils.Avg3(k, j, i); - LossyUtils.Dst(dst, 1, 2, kji); - LossyUtils.Dst(dst, 3, 3, kji); - LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); - } - - private void Hu4(Span dst, Span top, int topOffset) - { - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); - var jk = LossyUtils.Avg2(j, k); - LossyUtils.Dst(dst, 2, 0, jk); - LossyUtils.Dst(dst, 0, 1, jk); - var kl = LossyUtils.Avg2(k, l); - LossyUtils.Dst(dst, 2, 1, kl); - LossyUtils.Dst(dst, 0, 2, kl); - LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); - var jkl = LossyUtils.Avg3(j, k, l); - LossyUtils.Dst(dst, 3, 0, jkl); - LossyUtils.Dst(dst, 1, 1, jkl); - var kll = LossyUtils.Avg3(k, l, l); - LossyUtils.Dst(dst, 3, 1, kll); - LossyUtils.Dst(dst, 1, 2, kll); - LossyUtils.Dst(dst, 3, 2, l); - LossyUtils.Dst(dst, 2, 2, l); - LossyUtils.Dst(dst, 0, 3, l); - LossyUtils.Dst(dst, 1, 3, l); - LossyUtils.Dst(dst, 2, 3, l); - LossyUtils.Dst(dst, 3, 3, l); - } - - private void Fill(Span dst, int value, int size) - { - for (int j = 0; j < size; ++j) - { - dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); - } - } - private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) { int dstIdx = 0; @@ -1328,10 +830,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.CountDown = countDown; } - - private byte Clip8b(int v) - { - return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index e60f5fdc1..dbe9314e9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// The filter header info's. /// - private Vp8FilterHeader filterHeader; + private readonly Vp8FilterHeader filterHeader; /// /// The segment infos. /// - private Vp8SegmentInfo[] segmentInfos; + private readonly Vp8SegmentInfo[] segmentInfos; /// /// Contextual macroblock infos. @@ -106,21 +106,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private int uvAlpha; - /// - /// Fixed-point precision for RGB->YUV. - /// - private const int YuvFix = 16; - - private const int YuvHalf = 1 << (YuvFix - 1); - - private const int KC1 = 20091 + (1 << 16); - - private const int KC2 = 35468; - - private const int MaxLevel = 2047; - - private readonly byte[] zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; - private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; private const int NumMbSegments = 4; @@ -141,13 +126,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: filterStrength is hardcoded, should be configurable. private const int FilterStrength = 60; - // Diffusion weights. We under-correct a bit (15/16th of the error is actually - // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. - private const int C1 = 7; // fraction of error sent to the 4x4 block below - private const int C2 = 8; // fraction of error sent to the 4x4 block on the right - private const int DSHIFT = 4; - private const int DSCALE = 1; // storage descaling, needed to make the error fit byte - /// /// Initializes a new instance of the class. /// @@ -444,11 +422,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. - int method = this.method; bool doSearch = false; // TODO: doSearch hardcoded for now. - bool fastProbe = (method == 0 || method == 3) && !doSearch; + bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = (method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.mbw * this.mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -457,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) { - if (method == 3) + if (this.method == 3) { // We need more stats for method 3 to be reliable. nbMbs = (nbMbs > 200) ? nbMbs >> 1 : 100; @@ -996,7 +973,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); for (mode = 0; mode < numPredModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); long score = (Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) @@ -1043,7 +1020,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.MakeIntra4Preds(); for (mode = 0; mode < numBModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); long score = (Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); if (score < bestI4Score) { @@ -1092,7 +1069,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); for (mode = 0; mode < numPredModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); long score = (Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); if (score < bestUvScore) { @@ -1235,7 +1212,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; @@ -1245,25 +1222,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 16; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); + Vp8Encoding.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); } - this.FTransformWht(tmp, dcTmp); - nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; + Vp8Encoding.FTransformWht(tmp, dcTmp); + nz |= QuantEnc.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; for (n = 0; n < 16; n += 2) { // Zero-out the first coeff, so that: a) nz is correct below, and // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; + nz |= QuantEnc.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; } // Transform back. LossyUtils.TransformWht(dcTmp, tmpSpan); for (n = 0; n < 16; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); } return nz; @@ -1271,18 +1248,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); var tmp = new short[16]; - this.FTransform(src, reference, tmp); - var nz = this.QuantizeBlock(tmp, levels, dqm.Y1); - this.ITransform(reference, tmp, yuvOut, false); + Vp8Encoding.FTransform(src, reference, tmp); + var nz = QuantEnc.QuantizeBlock(tmp, levels, dqm.Y1); + Vp8Encoding.ITransform(reference, tmp, yuvOut, false); return nz; } private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); int nz = 0; int n; @@ -1290,267 +1267,38 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 8; n += 2) { - this.FTransform2( + Vp8Encoding.FTransform2( src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 16), tmp.AsSpan((n + 1) * 16, 16)); } - this.CorrectDCValues(it, dqm.Uv, tmp, rd); + QuantEnc.CorrectDcValues(it, dqm.Uv, tmp, rd); for (n = 0; n < 8; n += 2) { - nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; + nz |= QuantEnc.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; } for (n = 0; n < 8; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); } return nz << 16; } - private void FTransform2(Span src, Span reference, Span output, Span output2) - { - this.FTransform(src, reference, output); - this.FTransform(src.Slice(4), reference.Slice(4), output2); - } - - private void FTransform(Span src, Span reference, Span output) - { - int i; - var tmp = new int[16]; - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; ++i) - { - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d3 = src[srcIdx + 3] - reference[refIdx + 3]; - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - - srcIdx += WebPConstants.Bps; - refIdx += WebPConstants.Bps; - } - - for (i = 0; i < 4; ++i) - { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); - } - } - - private void CorrectDCValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) - { -#pragma warning disable SA1005 // Single line comments should begin with single space - // | top[0] | top[1] - // --------+--------+--------- - // left[0] | tmp[0] tmp[1] <-> err0 err1 - // left[1] | tmp[2] tmp[3] err2 err3 - // - // Final errors {err1,err2,err3} are preserved and later restored - // as top[]/left[] on the next block. -#pragma warning restore SA1005 // Single line comments should begin with single space - for (int ch = 0; ch <= 1; ++ch) - { - Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); - Span left = it.LeftDerr.AsSpan(ch, 2); - int err0, err1, err2, err3; - Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); - c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - err0 = QuantizeSingle(c, mtx); - c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - err1 = QuantizeSingle(c.Slice(1 * 16), mtx); - c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - err2 = QuantizeSingle(c.Slice(2 * 16), mtx); - c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - err3 = QuantizeSingle(c.Slice(3 * 16), mtx); - - // TODO: set errors in rd - // rd->derr[ch][0] = (int8_t)err1; - // rd->derr[ch][1] = (int8_t)err2; - // rd->derr[ch][2] = (int8_t)err3; - } - } - - // Quantize as usual, but also compute and return the quantization error. - // Error is already divided by DSHIFT. - private static int QuantizeSingle(Span v, Vp8Matrix mtx) - { - int v0 = v[0]; - bool sign = v0 < 0; - if (sign) - { - v0 = -v0; - } - - if (v0 > (int)mtx.ZThresh[0]) - { - int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; - int err = v0 - qV; - v[0] = (short)(sign ? -qV : qV); - return (sign ? -err : err) >> DSCALE; - } - - v[0] = 0; - return (sign ? -v0 : v0) >> DSCALE; - } - - private void FTransformWht(Span input, Span output) - { - var tmp = new int[16]; - int i; - int inputIdx = 0; - for (i = 0; i < 4; ++i) - { - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b - int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; - int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; - int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; - tmp[0 + (i * 4)] = a0 + a1; // 14b - tmp[1 + (i * 4)] = a3 + a2; - tmp[2 + (i * 4)] = a3 - a2; - tmp[3 + (i * 4)] = a0 - a1; - - inputIdx += 64; - } - - for (i = 0; i < 4; ++i) - { - int a0 = tmp[0 + i] + tmp[8 + i]; // 15b - int a1 = tmp[4 + i] + tmp[12 + i]; - int a2 = tmp[4 + i] - tmp[12 + i]; - int a3 = tmp[0 + i] - tmp[8 + i]; - int b0 = a0 + a1; // 16b - int b1 = a3 + a2; - int b2 = a3 - a2; - int b3 = a0 - a1; - output[0 + i] = (short)(b0 >> 1); // 15b - output[4 + i] = (short)(b1 >> 1); - output[8 + i] = (short)(b2 >> 1); - output[12 + i] = (short)(b3 >> 1); - } - } - - private int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) - { - int nz; - nz = this.QuantizeBlock(input, output, mtx) << 0; - nz |= this.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; - return nz; - } - - private int QuantizeBlock(Span input, Span output, Vp8Matrix mtx) - { - int last = -1; - int n; - for (n = 0; n < 16; ++n) - { - int j = this.zigzag[n]; - bool sign = input[j] < 0; - uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); - if (coeff > mtx.ZThresh[j]) - { - uint q = mtx.Q[j]; - uint iQ = mtx.IQ[j]; - uint b = mtx.Bias[j]; - int level = QuantDiv(coeff, iQ, b); - if (level > MaxLevel) - { - level = MaxLevel; - } - - if (sign) - { - level = -level; - } - - input[j] = (short)(level * (int)q); - output[n] = (short)level; - if (level != 0) - { - last = n; - } - } - else - { - output[n] = 0; - input[j] = 0; - } - } - - return (last >= 0) ? 1 : 0; - } - - private void ITransform(Span reference, Span input, Span dst, bool doTwo) - { - this.ITransformOne(reference, input, dst); - if (doTwo) - { - this.ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); - } - } - - private void ITransformOne(Span reference, Span input, Span dst) - { - int i; -#pragma warning disable SA1312 // Variable names should begin with lower-case letter - var C = new int[4 * 4]; -#pragma warning restore SA1312 // Variable names should begin with lower-case letter - Span tmp = C.AsSpan(); - for (i = 0; i < 4; ++i) - { - // vertical pass. - int a = input[0] + input[8]; - int b = input[0] - input[8]; - int c = Mul(input[4], KC2) - Mul(input[12], KC1); - int d = Mul(input[4], KC1) + Mul(input[12], KC2); - tmp[0] = a + d; - tmp[1] = b + c; - tmp[2] = b - c; - tmp[3] = a - d; - tmp = tmp.Slice(4); - input = input.Slice(1); - } - - tmp = C.AsSpan(); - for (i = 0; i < 4; ++i) - { - // horizontal pass. - int dc = tmp[0] + 4; - int a = dc + tmp[8]; - int b = dc - tmp[8]; - int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); - int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, a + d); - Store(dst, reference, 1, i, b + c); - Store(dst, reference, 2, i, b - c); - Store(dst, reference, 3, i, a - d); - tmp = tmp.Slice(1); - } - } - + /// + /// Converts the RGB values of the image to YUV. + /// + /// The pixel type of the image. + /// The image to convert. private void ConvertRgbToYuv(Image image) where TPixel : unmanaged, IPixel { int uvWidth = (image.Width + 1) >> 1; - bool hasAlpha = this.CheckNonOpaque(image); + bool hasAlpha = YuvConversion.CheckNonOpaque(image); // Temporary storage for accumulated R/G/B values during conversion to U/V. using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); @@ -1564,18 +1312,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); if (!hasAlpha) { - this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); } else { - this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); } - this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); + YuvConversion.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); } // Extra last row. @@ -1584,253 +1332,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span rowSpan = image.GetPixelRowSpan(rowIndex); if (!hasAlpha) { - this.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - else - { - this.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - } - } - - // Returns true if alpha has non-0xff values. - private bool CheckNonOpaque(Image image) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) - { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - for (int x = 0; x < image.Width; x++) - { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - if (rgba.A != 255) - { - return true; - } - } - } - - return false; - } - - private void ConvertRgbaToY(Span rowSpan, Span y, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - for (int x = 0; x < width; x++) - { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); - } - } - - private void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) - { - int rgbIdx = 0; - for (int i = 0; i < width; i += 1, rgbIdx += 4) - { - int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; - u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); - v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); - } - } - - private void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); - - dst[dstIdx] = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); - dst[dstIdx + 1] = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); - dst[dstIdx + 2] = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); - } - - if ((width & 1) != 0) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); - - dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); - } - } - - private void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); - uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); - int r, g, b; - if (a == 4 * 0xff || a == 0) - { - r = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); - g = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); - b = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); - } - else - { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; - } - - if ((width & 1) != 0) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); - uint a = (uint)(2u * (rgba0.A + rgba1.A)); - int r, g, b; - if (a == 4 * 0xff || a == 0) - { - r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); } else { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + YuvConversion.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); } - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; + YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); } } - private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) - { - uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); - return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); - } - - // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision - // U/V value, suitable for RGBToU/V calls. - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGamma(uint baseValue, int shift) - { - int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. - return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GammaToLinear(byte v) - { - return WebPLookupTables.GammaToLinearTab[v]; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Interpolate(int v) - { - int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. - int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. - int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; - int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; - int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate - - return y; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToY(byte r, byte g, byte b, int rounding) - { - int luma = (16839 * r) + (33059 * g) + (6420 * b); - return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToU(int r, int g, int b, int rounding) - { - int u = (-9719 * r) - (19081 * g) + (28800 * b); - return ClipUv(u, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToV(int r, int g, int b, int rounding) - { - int v = (+28800 * r) - (24116 * g) - (4684 * b); - return ClipUv(v, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipUv(int uv, int rounding) - { - uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); - return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int FinalAlphaValue(int alpha) { @@ -1933,24 +1445,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int QuantDiv(uint n, uint iQ, uint b) - { - return (int)(((n * iQ) + b) >> WebPConstants.QFix); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, int v) - { - dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul(int a, int b) - { - return (a * b) >> 16; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int GetProba(int a, int b) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs new file mode 100644 index 000000000..3f07abe31 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -0,0 +1,664 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Methods for encoding a VP8 frame. + /// + internal static class Vp8Encoding + { + private const int KC1 = 20091 + (1 << 16); + + private const int KC2 = 35468; + + private static readonly byte[] Clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] + + private const int I16DC16 = 0 * 16 * WebPConstants.Bps; + + private const int I16TM16 = I16DC16 + 16; + + private const int I16VE16 = 1 * 16 * WebPConstants.Bps; + + private const int I16HE16 = I16VE16 + 16; + + private const int C8DC8 = 2 * 16 * WebPConstants.Bps; + + private const int C8TM8 = C8DC8 + (1 * 16); + + private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); + + private const int C8HE8 = C8VE8 + (1 * 16); + + public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + + public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + + private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; + + private const int I4TM4 = I4DC4 + 4; + + private const int I4VE4 = I4DC4 + 8; + + private const int I4HE4 = I4DC4 + 12; + + private const int I4RD4 = I4DC4 + 16; + + private const int I4VR4 = I4DC4 + 20; + + private const int I4LD4 = I4DC4 + 24; + + private const int I4VL4 = I4DC4 + 28; + + private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); + + private const int I4HU4 = I4HD4 + 4; + + public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; + + static Vp8Encoding() + { + for (int i = -255; i <= 255 + 255; ++i) + { + Clip1[255 + i] = Clip8b(i); + } + } + + public static void ITransform(Span reference, Span input, Span dst, bool doTwo) + { + ITransformOne(reference, input, dst); + if (doTwo) + { + ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); + } + } + + public static void ITransformOne(Span reference, Span input, Span dst) + { + int i; +#pragma warning disable SA1312 // Variable names should begin with lower-case letter + var C = new int[4 * 4]; +#pragma warning restore SA1312 // Variable names should begin with lower-case letter + Span tmp = C.AsSpan(); + for (i = 0; i < 4; ++i) + { + // vertical pass. + int a = input[0] + input[8]; + int b = input[0] - input[8]; + int c = Mul(input[4], KC2) - Mul(input[12], KC1); + int d = Mul(input[4], KC1) + Mul(input[12], KC2); + tmp[0] = a + d; + tmp[1] = b + c; + tmp[2] = b - c; + tmp[3] = a - d; + tmp = tmp.Slice(4); + input = input.Slice(1); + } + + tmp = C.AsSpan(); + for (i = 0; i < 4; ++i) + { + // horizontal pass. + int dc = tmp[0] + 4; + int a = dc + tmp[8]; + int b = dc - tmp[8]; + int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); + int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); + Store(dst, reference, 0, i, a + d); + Store(dst, reference, 1, i, b + c); + Store(dst, reference, 2, i, b - c); + Store(dst, reference, 3, i, a - d); + tmp = tmp.Slice(1); + } + } + + public static void FTransform2(Span src, Span reference, Span output, Span output2) + { + FTransform(src, reference, output); + FTransform(src.Slice(4), reference.Slice(4), output2); + } + + public static void FTransform(Span src, Span reference, Span output) + { + int i; + var tmp = new int[16]; + int srcIdx = 0; + int refIdx = 0; + for (i = 0; i < 4; ++i) + { + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d3 = src[srcIdx + 3] - reference[refIdx + 3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + + srcIdx += WebPConstants.Bps; + refIdx += WebPConstants.Bps; + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } + } + + public static void FTransformWht(Span input, Span output) + { + var tmp = new int[16]; + int i; + int inputIdx = 0; + for (i = 0; i < 4; ++i) + { + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b + int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; + int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; + tmp[0 + (i * 4)] = a0 + a1; // 14b + tmp[1 + (i * 4)] = a3 + a2; + tmp[2 + (i * 4)] = a3 - a2; + tmp[3 + (i * 4)] = a0 - a1; + + inputIdx += 64; + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[8 + i]; // 15b + int a1 = tmp[4 + i] + tmp[12 + i]; + int a2 = tmp[4 + i] - tmp[12 + i]; + int a3 = tmp[0 + i] - tmp[8 + i]; + int b0 = a0 + a1; // 16b + int b1 = a3 + a2; + int b2 = a3 - a2; + int b3 = a0 - a1; + output[0 + i] = (short)(b0 >> 1); // 15b + output[4 + i] = (short)(b1 >> 1); + output[8 + i] = (short)(b2 >> 1); + output[12 + i] = (short)(b3 >> 1); + } + } + + // luma 16x16 prediction (paragraph 12.3). + public static void EncPredLuma16(Span dst, Span left, Span top) + { + DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); + VerticalPred(dst.Slice(I16VE16), top, 16); + HorizontalPred(dst.Slice(I16HE16), left, 16); + TrueMotion(dst.Slice(I16TM16), left, top, 16); + } + + // Chroma 8x8 prediction (paragraph 12.2). + public static void EncPredChroma8(Span dst, Span left, Span top) + { + // U block. + DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + VerticalPred(dst.Slice(C8VE8), top, 8); + HorizontalPred(dst.Slice(C8HE8), left, 8); + TrueMotion(dst.Slice(C8TM8), left, top, 8); + + // V block. + dst = dst.Slice(8); + if (top != null) + { + top = top.Slice(8); + } + + if (left != null) + { + left = left.Slice(16); + } + + DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + VerticalPred(dst.Slice(C8VE8), top, 8); + HorizontalPred(dst.Slice(C8HE8), left, 8); + TrueMotion(dst.Slice(C8TM8), left, top, 8); + } + + // Left samples are top[-5 .. -2], top_left is top[-1], top are + // located at top[0..3], and top right is top[4..7] + public static void EncPredLuma4(Span dst, Span top, int topOffset) + { + Dc4(dst.Slice(I4DC4), top, topOffset); + Tm4(dst.Slice(I4TM4), top, topOffset); + Ve4(dst.Slice(I4VE4), top, topOffset); + He4(dst.Slice(I4HE4), top, topOffset); + Rd4(dst.Slice(I4RD4), top, topOffset); + Vr4(dst.Slice(I4VR4), top, topOffset); + Ld4(dst.Slice(I4LD4), top, topOffset); + Vl4(dst.Slice(I4VL4), top, topOffset); + Hd4(dst.Slice(I4HD4), top, topOffset); + Hu4(dst.Slice(I4HU4), top, topOffset); + } + + private static void VerticalPred(Span dst, Span top, int size) + { + if (top != null) + { + for (int j = 0; j < size; ++j) + { + top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); + } + } + else + { + Fill(dst, 127, size); + } + } + + public static void HorizontalPred(Span dst, Span left, int size) + { + if (left != null) + { + left = left.Slice(1); // in the reference implementation, left starts at - 1. + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); + } + } + else + { + Fill(dst, 129, size); + } + } + + public static void TrueMotion(Span dst, Span left, Span top, int size) + { + if (left != null) + { + if (top != null) + { + Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 + for (int y = 0; y < size; ++y) + { + Span clipTable = clip.Slice(left[y + 1]); // left[y] + for (int x = 0; x < size; ++x) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + else + { + Vp8Encoding.HorizontalPred(dst, left, size); + } + } + else + { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != null) + { + Vp8Encoding.VerticalPred(dst, top, size); + } + else + { + Fill(dst, 129, size); + } + } + } + + private static void DcMode(Span dst, Span left, Span top, int size, int round, int shift) + { + int dc = 0; + int j; + if (top != null) + { + for (j = 0; j < size; ++j) + { + dc += top[j]; + } + + if (left != null) + { + // top and left present. + left = left.Slice(1); // in the reference implementation, left starts at -1. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + } + else + { + // top, but no left. + dc += dc; + } + + dc = (dc + round) >> shift; + } + else if (left != null) + { + // left but no top. + left = left.Slice(1); // in the reference implementation, left starts at -1. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + + dc += dc; + dc = (dc + round) >> shift; + } + else + { + // no top, no left, nothing. + dc = 0x80; + } + + Fill(dst, dc, size); + } + + private static void Dc4(Span dst, Span top, int topOffset) + { + uint dc = 4; + int i; + for (i = 0; i < 4; ++i) + { + dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); + } + + Fill(dst, (int)(dc >> 3), 4); + } + + private static void Tm4(Span dst, Span top, int topOffset) + { + Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); + for (int y = 0; y < 4; ++y) + { + Span clipTable = clip.Slice(top[topOffset - 2 - y]); + for (int x = 0; x < 4; ++x) + { + dst[x] = clipTable[top[topOffset + x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + + private static void Ve4(Span dst, Span top, int topOffset) + { + // vertical + byte[] vals = + { + LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), + LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), + LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), + LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) + }; + + for (int i = 0; i < 4; ++i) + { + vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); + } + } + + private static void He4(Span dst, Span top, int topOffset) + { + // horizontal + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * LossyUtils.Avg3(i, j, k); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(j, k, l); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(k, l, l); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); + } + + private static void Rd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); + var ijk = LossyUtils.Avg3(i, j, k); + LossyUtils.Dst(dst, 0, 2, ijk); + LossyUtils.Dst(dst, 1, 3, ijk); + var xij = LossyUtils.Avg3(x, i, j); + LossyUtils.Dst(dst, 0, 1, xij); + LossyUtils.Dst(dst, 1, 2, xij); + LossyUtils.Dst(dst, 2, 3, xij); + var axi = LossyUtils.Avg3(a, x, i); + LossyUtils.Dst(dst, 0, 0, axi); + LossyUtils.Dst(dst, 1, 1, axi); + LossyUtils.Dst(dst, 2, 2, axi); + LossyUtils.Dst(dst, 3, 3, axi); + var bax = LossyUtils.Avg3(b, a, x); + LossyUtils.Dst(dst, 1, 0, bax); + LossyUtils.Dst(dst, 2, 1, bax); + LossyUtils.Dst(dst, 3, 2, bax); + var cba = LossyUtils.Avg3(c, b, a); + LossyUtils.Dst(dst, 2, 0, cba); + LossyUtils.Dst(dst, 3, 1, cba); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); + } + + private static void Vr4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + var xa = LossyUtils.Avg2(x, a); + LossyUtils.Dst(dst, 0, 0, xa); + LossyUtils.Dst(dst, 1, 2, xa); + var ab = LossyUtils.Avg2(a, b); + LossyUtils.Dst(dst, 1, 0, ab); + LossyUtils.Dst(dst, 2, 2, ab); + var bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 2, 0, bc); + LossyUtils.Dst(dst, 3, 2, bc); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); + var ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 0, 1, ixa); + LossyUtils.Dst(dst, 1, 3, ixa); + var xab = LossyUtils.Avg3(x, a, b); + LossyUtils.Dst(dst, 1, 1, xab); + LossyUtils.Dst(dst, 2, 3, xab); + var abc = LossyUtils.Avg3(a, b, c); + LossyUtils.Dst(dst, 2, 1, abc); + LossyUtils.Dst(dst, 3, 3, abc); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); + } + + private static void Ld4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 0, bcd); + LossyUtils.Dst(dst, 0, 1, bcd); + var cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 0, cde); + LossyUtils.Dst(dst, 1, 1, cde); + LossyUtils.Dst(dst, 0, 2, cde); + var def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 0, def); + LossyUtils.Dst(dst, 2, 1, def); + LossyUtils.Dst(dst, 1, 2, def); + LossyUtils.Dst(dst, 0, 3, def); + var efg = LossyUtils.Avg3(e, f, g); + LossyUtils.Dst(dst, 3, 1, efg); + LossyUtils.Dst(dst, 2, 2, efg); + LossyUtils.Dst(dst, 1, 3, efg); + var fgh = LossyUtils.Avg3(f, g, h); + LossyUtils.Dst(dst, 3, 2, fgh); + LossyUtils.Dst(dst, 2, 3, fgh); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); + } + + private static void Vl4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); + var bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 1, 0, bc); + LossyUtils.Dst(dst, 0, 2, bc); + var cd = LossyUtils.Avg2(c, d); + LossyUtils.Dst(dst, 2, 0, cd); + LossyUtils.Dst(dst, 1, 2, cd); + var de = LossyUtils.Avg2(d, e); + LossyUtils.Dst(dst, 3, 0, de); + LossyUtils.Dst(dst, 2, 2, de); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 1, bcd); + LossyUtils.Dst(dst, 0, 3, bcd); + var cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 1, cde); + LossyUtils.Dst(dst, 1, 3, cde); + var def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 1, def); + LossyUtils.Dst(dst, 2, 3, def); + LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); + } + + private static void Hd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + + var ix = LossyUtils.Avg2(i, x); + LossyUtils.Dst(dst, 0, 0, ix); + LossyUtils.Dst(dst, 2, 1, ix); + var ji = LossyUtils.Avg2(j, i); + LossyUtils.Dst(dst, 0, 1, ji); + LossyUtils.Dst(dst, 2, 2, ji); + var kj = LossyUtils.Avg2(k, j); + LossyUtils.Dst(dst, 0, 2, kj); + LossyUtils.Dst(dst, 2, 3, kj); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); + var ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 1, 0, ixa); + LossyUtils.Dst(dst, 3, 1, ixa); + var jix = LossyUtils.Avg3(j, i, x); + LossyUtils.Dst(dst, 1, 1, jix); + LossyUtils.Dst(dst, 3, 2, jix); + var kji = LossyUtils.Avg3(k, j, i); + LossyUtils.Dst(dst, 1, 2, kji); + LossyUtils.Dst(dst, 3, 3, kji); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); + } + + private static void Hu4(Span dst, Span top, int topOffset) + { + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); + var jk = LossyUtils.Avg2(j, k); + LossyUtils.Dst(dst, 2, 0, jk); + LossyUtils.Dst(dst, 0, 1, jk); + var kl = LossyUtils.Avg2(k, l); + LossyUtils.Dst(dst, 2, 1, kl); + LossyUtils.Dst(dst, 0, 2, kl); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); + var jkl = LossyUtils.Avg3(j, k, l); + LossyUtils.Dst(dst, 3, 0, jkl); + LossyUtils.Dst(dst, 1, 1, jkl); + var kll = LossyUtils.Avg3(k, l, l); + LossyUtils.Dst(dst, 3, 1, kll); + LossyUtils.Dst(dst, 1, 2, kll); + LossyUtils.Dst(dst, 3, 2, l); + LossyUtils.Dst(dst, 2, 2, l); + LossyUtils.Dst(dst, 0, 3, l); + LossyUtils.Dst(dst, 1, 3, l); + LossyUtils.Dst(dst, 2, 3, l); + LossyUtils.Dst(dst, 3, 3, l); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Fill(Span dst, int value, int size) + { + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Clip8b(int v) + { + return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Store(Span dst, Span reference, int x, int y, int v) + { + dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mul(int a, int b) + { + return (a * b) >> 16; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs new file mode 100644 index 000000000..bd919e93d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal static class YuvConversion + { + /// + /// Fixed-point precision for RGB->YUV. + /// + private const int YuvFix = 16; + + private const int YuvHalf = 1 << (YuvFix - 1); + + /// + /// Checks if the image is not opaque. + /// + /// The pixel type of the image, + /// The image to check. + /// Returns true if alpha has non-0xff values. + public static bool CheckNonOpaque(Image image) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + for (int x = 0; x < image.Width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + if (rgba.A != 255) + { + return true; + } + } + } + + return false; + } + + public static void ConvertRgbaToY(Span rowSpan, Span y, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int x = 0; x < width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); + } + } + + public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) + { + int rgbIdx = 0; + for (int i = 0; i < width; i += 1, rgbIdx += 4) + { + int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; + u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); + v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); + } + } + + public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + + dst[dstIdx] = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + dst[dstIdx + 1] = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + dst[dstIdx + 2] = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + + dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + } + } + + public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + g = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + b = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); + } + else + { + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + uint a = (uint)(2u * (rgba0.A + rgba1.A)); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + } + else + { + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + } + + private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) + { + uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); + return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); + } + + // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision + // U/V value, suitable for RGBToU/V calls. + [MethodImpl(InliningOptions.ShortMethod)] + private static int LinearToGamma(uint baseValue, int shift) + { + int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. + return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GammaToLinear(byte v) + { + return WebPLookupTables.GammaToLinearTab[v]; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Interpolate(int v) + { + int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. + int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. + int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; + int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; + int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate + + return y; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToY(byte r, byte g, byte b, int rounding) + { + int luma = (16839 * r) + (33059 * g) + (6420 * b); + return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToU(int r, int g, int b, int rounding) + { + int u = (-9719 * r) - (19081 * g) + (28800 * b); + return ClipUv(u, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToV(int r, int g, int b, int rounding) + { + int v = (+28800 * r) - (24116 * g) - (4684 * b); + return ClipUv(v, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipUv(int uv, int rounding) + { + uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; + } + } +} From 0222a1f0d1f17d0330c19dbdc3bbe3df0d07d8fe Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Nov 2020 18:41:11 +0100 Subject: [PATCH 233/359] Add webp encoding benchmark --- .../ImageSharp.Benchmarks/Codecs/EncodeTga.cs | 29 ++--- .../Codecs/EncodeWebp.cs | 105 ++++++++++++++++++ 2 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs index 37cfa314c..a934153df 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public class EncodeTga : BenchmarkBase { private MagickImage tgaMagick; - private Image tgaCore; + private Image tga; private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); @@ -26,29 +26,32 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void ReadImages() { - if (this.tgaCore == null) + if (this.tga == null) { - this.tgaCore = Image.Load(this.TestImageFullPath); + this.tga = Image.Load(this.TestImageFullPath); this.tgaMagick = new MagickImage(this.TestImageFullPath); } } + [GlobalCleanup] + public void Cleanup() + { + this.tga.Dispose(); + this.tgaMagick.Dispose(); + } + [Benchmark(Baseline = true, Description = "Magick Tga")] - public void BmpSystemDrawing() + public void MagickTga() { - using (var memoryStream = new MemoryStream()) - { - this.tgaMagick.Write(memoryStream, MagickFormat.Tga); - } + using var memoryStream = new MemoryStream(); + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); } [Benchmark(Description = "ImageSharp Tga")] - public void BmpCore() + public void ImageSharpTga() { - using (var memoryStream = new MemoryStream()) - { - this.tgaCore.SaveAsBmp(memoryStream); - } + using var memoryStream = new MemoryStream(); + this.tga.SaveAsTga(memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs new file mode 100644 index 000000000..5c8658d15 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using BenchmarkDotNet.Attributes; +using ImageMagick; +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeWebp : BenchmarkBase + { + private MagickImage webpMagick; + private Image webp; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.WebP.Peak)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.webp == null) + { + this.webp = Image.Load(this.TestImageFullPath); + this.webpMagick = new MagickImage(this.TestImageFullPath); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.webp.Dispose(); + this.webpMagick.Dispose(); + } + + [Benchmark(Description = "Magick Webp Lossy")] + public void MagickWebpLossy() + { + using var memoryStream = new MemoryStream(); + this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", false); + this.webpMagick.Write(memoryStream, MagickFormat.WebP); + } + + [Benchmark(Description = "ImageSharp Webp Lossy")] + public void ImageSharpWebpLossy() + { + using var memoryStream = new MemoryStream(); + this.webp.Save(memoryStream, new WebPEncoder() + { + Lossy = true + }); + } + + [Benchmark(Baseline = true, Description = "Magick Webp Lossless")] + public void MagickWebpLossless() + { + using var memoryStream = new MemoryStream(); + this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", true); + this.webpMagick.Write(memoryStream, MagickFormat.WebP); + } + + [Benchmark(Description = "ImageSharp Webp Lossless")] + public void ImageSharpWebpLossless() + { + using var memoryStream = new MemoryStream(); + this.webp.Save(memoryStream, new WebPEncoder() + { + Lossy = false + }); + } + + /* Results 14.11.2020 + * Summary * + BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1) + Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores + .NET Core SDK=5.0.100 + [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + Job-OUUGWL : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT + Job-GAIITM : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT + Job-HWOBSO : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + + | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |-------------- |-------------- |----------:|----------:|----------:|------:|--------:|----------:|---------:|---------:|-----------:| + | 'Magick Webp Lossy' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 1.744 ms | 0.0399 ms | 0.0022 ms | 0.35 | 0.00 | 1.9531 | - | - | 13.58 KB | + | 'ImageSharp Webp Lossy' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 5.195 ms | 0.4241 ms | 0.0232 ms | 1.04 | 0.01 | 398.4375 | 93.7500 | - | 1661.83 KB | + | 'Magick Webp Lossless' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 4.993 ms | 0.5097 ms | 0.0279 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.7 KB | + | 'ImageSharp Webp Lossless' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 12.174 ms | 1.2476 ms | 0.0684 ms | 2.44 | 0.02 | 1000.0000 | 984.3750 | 984.3750 | 8197.11 KB | + | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 1.747 ms | 0.0581 ms | 0.0032 ms | 0.35 | 0.00 | 1.9531 | - | - | 13.34 KB | + | 'ImageSharp Webp Lossy' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 3.527 ms | 0.0972 ms | 0.0053 ms | 0.71 | 0.00 | 402.3438 | 97.6563 | - | 1656.92 KB | + | 'Magick Webp Lossless' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 5.001 ms | 0.4543 ms | 0.0249 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.39 KB | + | 'ImageSharp Webp Lossless' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 10.704 ms | 0.9844 ms | 0.0540 ms | 2.14 | 0.02 | 1000.0000 | 984.3750 | 984.3750 | 8182.6 KB | + | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 1.742 ms | 0.0279 ms | 0.0015 ms | 0.35 | 0.01 | 1.9531 | - | - | 13.31 KB | + | 'ImageSharp Webp Lossy' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 3.347 ms | 0.0638 ms | 0.0035 ms | 0.68 | 0.01 | 402.3438 | 97.6563 | - | 1656.93 KB | + | 'Magick Webp Lossless' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 4.954 ms | 1.4131 ms | 0.0775 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.35 KB | + | 'ImageSharp Webp Lossless' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 10.737 ms | 2.5604 ms | 0.1403 ms | 2.17 | 0.05 | 1000.0000 | 984.3750 | 984.3750 | 8182.49 KB | + */ + } +} From 75cdb2d1d52eaa98b5a67d1c431048b4cb3f4d07 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Nov 2020 21:03:47 +0100 Subject: [PATCH 234/359] Move hashchain fill to Vp8LHashChain, use memory allocator --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 276 +----------------- .../Formats/WebP/Lossless/CrunchConfig.cs | 14 + .../Formats/WebP/Lossless/CrunchSubConfig.cs | 12 + .../Formats/WebP/Lossless/LosslessUtils.cs | 38 +++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 23 +- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 236 +++++++++++++++ .../Formats/WebP/Lossy/LossyUtils.cs | 60 ++-- 7 files changed, 336 insertions(+), 323 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 0be198c15..f08b9f0b1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -13,28 +13,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public const int MaxLengthBits = 12; - private const int HashBits = 18; - - private const int HashSize = 1 << HashBits; - - private const uint HashMultiplierHi = 0xc6a4a793u; - - private const uint HashMultiplierLo = 0x5bd1e996u; - private const float MaxEntropy = 1e30f; private const int WindowOffsetsSizeMax = 32; - /// - /// The number of bits for the window size. - /// - private const int WindowSizeBits = 20; - - /// - /// 1M window (4M bytes) minus 120 special codes for short distances. - /// - private const int WindowSize = (1 << WindowSizeBits) - 120; - /// /// We want the max value to be attainable and stored in MaxLengthBits bits. /// @@ -46,183 +28,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private const int MinLength = 4; - // TODO: move to Hashchain? - public static void HashChainFill(Vp8LHashChain p, Span bgra, int quality, int xSize, int ySize) - { - int size = xSize * ySize; - int iterMax = GetMaxItersForQuality(quality); - int windowSize = GetWindowSizeForHashChain(quality, xSize); - int pos; - var hashToFirstIndex = new int[HashSize]; // TODO: use memory allocator - - // Initialize hashToFirstIndex array to -1. - hashToFirstIndex.AsSpan().Fill(-1); - - var chain = new int[size]; // TODO: use memory allocator. - - // Fill the chain linking pixels with the same hash. - var bgraComp = bgra[0] == bgra[1]; - for (pos = 0; pos < size - 2;) - { - uint hashCode; - bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; - if (bgraComp && bgraCompNext) - { - // Consecutive pixels with the same color will share the same hash. - // We therefore use a different hash: the color and its repetition length. - var tmp = new uint[2]; - uint len = 1; - tmp[0] = bgra[pos]; - - // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, - // as its next pixel does not have the same color, so we just need to get to - // the last pixel equal to its follower. - while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) - { - ++len; - } - - if (len > MaxLength) - { - // Skip the pixels that match for distance=1 and length>MaxLength - // because they are linked to their predecessor and we automatically - // check that in the main for loop below. Skipping means setting no - // predecessor in the chain, hence -1. - pos += (int)(len - MaxLength); - len = MaxLength; - } - - // Process the rest of the hash chain. - while (len > 0) - { - tmp[1] = len--; - hashCode = GetPixPairHash64(tmp); - chain[pos] = hashToFirstIndex[hashCode]; - hashToFirstIndex[hashCode] = pos++; - } - - bgraComp = false; - } - else - { - // Just move one pixel forward. - hashCode = GetPixPairHash64(bgra.Slice(pos)); - chain[pos] = hashToFirstIndex[hashCode]; - hashToFirstIndex[hashCode] = pos++; - bgraComp = bgraCompNext; - } - } - - // Process the penultimate pixel. - chain[pos] = hashToFirstIndex[GetPixPairHash64(bgra.Slice(pos))]; - - // Find the best match interval at each pixel, defined by an offset to the - // pixel and a length. The right-most pixel cannot match anything to the right - // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). - p.OffsetLength[0] = p.OffsetLength[size - 1] = 0; - for (int basePosition = size - 2; basePosition > 0;) - { - int maxLen = MaxFindCopyLength(size - 1 - basePosition); - int bgraStart = basePosition; - int iter = iterMax; - int bestLength = 0; - uint bestDistance = 0; - uint bestBgra; - int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; - int lengthMax = (maxLen < 256) ? maxLen : 256; - pos = chain[basePosition]; - int currLength; - - // Heuristic: use the comparison with the above line as an initialization. - if (basePosition >= (uint)xSize) - { - currLength = FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = (uint)xSize; - } - - iter--; - } - - // Heuristic: compare to the previous pixel. - currLength = FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = 1; - } - - iter--; - - if (bestLength == MaxLength) - { - pos = minPos - 1; - } - - bestBgra = bgra.Slice(bgraStart)[bestLength]; - - for (; pos >= minPos && (--iter > 0); pos = chain[pos]) - { - if (bgra[pos + bestLength] != bestBgra) - { - continue; - } - - currLength = VectorMismatch(bgra.Slice(pos), bgra.Slice(bgraStart), maxLen); - if (bestLength < currLength) - { - bestLength = currLength; - bestDistance = (uint)(basePosition - pos); - bestBgra = bgra.Slice(bgraStart)[bestLength]; - - // Stop if we have reached a good enough length. - if (bestLength >= lengthMax) - { - break; - } - } - } - - // We have the best match but in case the two intervals continue matching - // to the left, we have the best matches for the left-extended pixels. - var maxBasePosition = (uint)basePosition; - while (true) - { - p.OffsetLength[basePosition] = (bestDistance << MaxLengthBits) | (uint)bestLength; - --basePosition; - - // Stop if we don't have a match or if we are out of bounds. - if (bestDistance == 0 || basePosition == 0) - { - break; - } - - // Stop if we cannot extend the matching intervals to the left. - if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) - { - break; - } - - // Stop if we are matching at its limit because there could be a closer - // matching interval with the same maximum length. Then again, if the - // matching interval is as close as possible (best_distance == 1), we will - // never find anything better so let's continue. - if (bestLength == MaxLength && bestDistance != 1 && basePosition + MaxLength < maxBasePosition) - { - break; - } - - if (bestLength < MaxLength) - { - bestLength++; - maxBasePosition = (uint)basePosition; - } - } - } - } - /// /// Evaluates best possible backward references for specified quality. The input cacheBits to 'GetBackwardReferences' /// sets the maximum cache bits to use (passing 0 implies disabling the local color cache). @@ -901,9 +706,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int i = 1; while (i < pixelCount) { - int maxLen = MaxFindCopyLength(pixelCount - i); - int rleLen = FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen); - int prevRowLen = (i < xSize) ? 0 : FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); + int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); + int rleLen = LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen); + int prevRowLen = (i < xSize) ? 0 : LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); if (rleLen >= prevRowLen && rleLen >= MinLength) { refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); @@ -931,11 +736,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless i++; } } - - if (useColorCache) - { - // TODO: VP8LColorCacheClear()? - } } /// @@ -1033,75 +833,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return dist + 120; } - - /// - /// Returns the exact index where array1 and array2 are different. For an index - /// inferior or equal to bestLenMatch, the return value just has to be strictly - /// inferior to best_lenMatch. The current behavior is to return 0 if this index - /// is bestLenMatch, and the index itself otherwise. - /// If no two elements are the same, it returns maxLimit. - /// - private static int FindMatchLength(Span array1, Span array2, int bestLenMatch, int maxLimit) - { - // Before 'expensive' linear match, check if the two arrays match at the - // current best length index. - if (array1[bestLenMatch] != array2[bestLenMatch]) - { - return 0; - } - - return VectorMismatch(array1, array2, maxLimit); - } - - private static int VectorMismatch(Span array1, Span array2, int length) - { - int matchLen = 0; - - while (matchLen < length && array1[matchLen] == array2[matchLen]) - { - matchLen++; - } - - return matchLen; - } - - /// - /// Calculates the hash for a pixel pair. - /// - /// An Span with two pixels. - /// The hash. - private static uint GetPixPairHash64(Span bgra) - { - uint key = bgra[1] * HashMultiplierHi; - key += bgra[0] * HashMultiplierLo; - key = key >> (32 - HashBits); - return key; - } - - /// - /// Returns the maximum number of hash chain lookups to do for a - /// given compression quality. Return value in range [8, 86]. - /// - /// The quality. - /// Number of hash chain lookups. - private static int GetMaxItersForQuality(int quality) - { - return 8 + (quality * quality / 128); - } - - private static int MaxFindCopyLength(int len) - { - return (len < MaxLength) ? len : MaxLength; - } - - private static int GetWindowSizeForHashChain(int quality, int xSize) - { - int maxWindowSize = (quality > 75) ? WindowSize - : (quality > 50) ? (xSize << 8) - : (quality > 25) ? (xSize << 6) - : (xSize << 4); - - return (maxWindowSize > WindowSize) ? WindowSize : maxWindowSize; - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs new file mode 100644 index 000000000..02b0dcf71 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class CrunchConfig + { + public EntropyIx EntropyIdx { get; set; } + + public List SubConfigs { get; } = new List(); + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs new file mode 100644 index 000000000..04e2d511b --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class CrunchSubConfig + { + public int Lz77 { get; set; } + + public bool DoNotCache { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 16b5bce06..7c2cd4612 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -26,6 +26,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const double Log2Reciprocal = 1.44269504088896338700465094007086; + /// + /// Returns the exact index where array1 and array2 are different. For an index + /// inferior or equal to bestLenMatch, the return value just has to be strictly + /// inferior to best_lenMatch. The current behavior is to return 0 if this index + /// is bestLenMatch, and the index itself otherwise. + /// If no two elements are the same, it returns maxLimit. + /// + public static int FindMatchLength(Span array1, Span array2, int bestLenMatch, int maxLimit) + { + // Before 'expensive' linear match, check if the two arrays match at the + // current best length index. + if (array1[bestLenMatch] != array2[bestLenMatch]) + { + return 0; + } + + return VectorMismatch(array1, array2, maxLimit); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int VectorMismatch(Span array1, Span array2, int length) + { + int matchLen = 0; + + while (matchLen < length && array1[matchLen] == array2[matchLen]) + { + matchLen++; + } + + return matchLen; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int MaxFindCopyLength(int len) + { + return (len < BackwardReferenceEncoder.MaxLength) ? len : BackwardReferenceEncoder.MaxLength; + } + public static int PrefixEncodeBits(int distance, ref int extraBits) { if (distance < PrefixLookupIdxMax) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 078710486..a41d7295d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Encoder for lossless webp images. /// - internal class Vp8LEncoder : IDisposable + internal partial class Vp8LEncoder : IDisposable { /// /// Maximum number of reference blocks the image will be segmented into. @@ -420,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Calculate backward references from BGRA image. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, this.quality, width, height); + hashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; @@ -618,7 +617,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Calculate backward references from the image pixels. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + hashChain.Fill(this.memoryAllocator, bgra, quality, width, height); Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( width, @@ -1712,21 +1711,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Palette.Dispose(); this.TransformData.Dispose(); } - - // TODO : Not a fan of private classes - private class CrunchConfig - { - public EntropyIx EntropyIdx { get; set; } - - public List SubConfigs { get; } = new List(); - } - - // TODO : Not a fan of private classes - private class CrunchSubConfig - { - public int Lz77 { get; set; } - - public bool DoNotCache { get; set; } - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 0262ac332..54a711c38 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -2,11 +2,32 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LHashChain { + private const uint HashMultiplierHi = 0xc6a4a793u; + + private const uint HashMultiplierLo = 0x5bd1e996u; + + private const int HashBits = 18; + + private const int HashSize = 1 << HashBits; + + /// + /// The number of bits for the window size. + /// + private const int WindowSizeBits = 20; + + /// + /// 1M window (4M bytes) minus 120 special codes for short distances. + /// + private const int WindowSize = (1 << WindowSizeBits) - 120; + /// /// Initializes a new instance of the class. /// @@ -33,14 +54,229 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public int Size { get; } + public void Fill(MemoryAllocator memoryAllocator, Span bgra, int quality, int xSize, int ySize) + { + int size = xSize * ySize; + int iterMax = GetMaxItersForQuality(quality); + int windowSize = GetWindowSizeForHashChain(quality, xSize); + int pos; + using IMemoryOwner hashToFirstIndexBuffer = memoryAllocator.Allocate(HashSize); + Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); + + // Initialize hashToFirstIndex array to -1. + hashToFirstIndex.Fill(-1); + + var chain = new int[size]; + + // Fill the chain linking pixels with the same hash. + var bgraComp = bgra[0] == bgra[1]; + for (pos = 0; pos < size - 2;) + { + uint hashCode; + bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; + if (bgraComp && bgraCompNext) + { + // Consecutive pixels with the same color will share the same hash. + // We therefore use a different hash: the color and its repetition length. + var tmp = new uint[2]; + uint len = 1; + tmp[0] = bgra[pos]; + + // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, + // as its next pixel does not have the same color, so we just need to get to + // the last pixel equal to its follower. + while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) + { + ++len; + } + + if (len > BackwardReferenceEncoder.MaxLength) + { + // Skip the pixels that match for distance=1 and length>MaxLength + // because they are linked to their predecessor and we automatically + // check that in the main for loop below. Skipping means setting no + // predecessor in the chain, hence -1. + pos += (int)(len - BackwardReferenceEncoder.MaxLength); + len = BackwardReferenceEncoder.MaxLength; + } + + // Process the rest of the hash chain. + while (len > 0) + { + tmp[1] = len--; + hashCode = GetPixPairHash64(tmp); + chain[pos] = hashToFirstIndex[(int)hashCode]; + hashToFirstIndex[(int)hashCode] = pos++; + } + + bgraComp = false; + } + else + { + // Just move one pixel forward. + hashCode = GetPixPairHash64(bgra.Slice(pos)); + chain[pos] = hashToFirstIndex[(int)hashCode]; + hashToFirstIndex[(int)hashCode] = pos++; + bgraComp = bgraCompNext; + } + } + + // Process the penultimate pixel. + chain[pos] = hashToFirstIndex[(int)GetPixPairHash64(bgra.Slice(pos))]; + + // Find the best match interval at each pixel, defined by an offset to the + // pixel and a length. The right-most pixel cannot match anything to the right + // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). + this.OffsetLength[0] = this.OffsetLength[size - 1] = 0; + for (int basePosition = size - 2; basePosition > 0;) + { + int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); + int bgraStart = basePosition; + int iter = iterMax; + int bestLength = 0; + uint bestDistance = 0; + int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; + int lengthMax = (maxLen < 256) ? maxLen : 256; + pos = chain[basePosition]; + int currLength; + + // Heuristic: use the comparison with the above line as an initialization. + if (basePosition >= (uint)xSize) + { + currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = (uint)xSize; + } + + iter--; + } + + // Heuristic: compare to the previous pixel. + currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = 1; + } + + iter--; + + if (bestLength == BackwardReferenceEncoder.MaxLength) + { + pos = minPos - 1; + } + + var bestBgra = bgra.Slice(bgraStart)[bestLength]; + + for (; pos >= minPos && (--iter > 0); pos = chain[pos]) + { + if (bgra[pos + bestLength] != bestBgra) + { + continue; + } + + currLength = LosslessUtils.VectorMismatch(bgra.Slice(pos), bgra.Slice(bgraStart), maxLen); + if (bestLength < currLength) + { + bestLength = currLength; + bestDistance = (uint)(basePosition - pos); + bestBgra = bgra.Slice(bgraStart)[bestLength]; + + // Stop if we have reached a good enough length. + if (bestLength >= lengthMax) + { + break; + } + } + } + + // We have the best match but in case the two intervals continue matching + // to the left, we have the best matches for the left-extended pixels. + var maxBasePosition = (uint)basePosition; + while (true) + { + this.OffsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; + --basePosition; + + // Stop if we don't have a match or if we are out of bounds. + if (bestDistance == 0 || basePosition == 0) + { + break; + } + + // Stop if we cannot extend the matching intervals to the left. + if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) + { + break; + } + + // Stop if we are matching at its limit because there could be a closer + // matching interval with the same maximum length. Then again, if the + // matching interval is as close as possible (best_distance == 1), we will + // never find anything better so let's continue. + if (bestLength == BackwardReferenceEncoder.MaxLength && bestDistance != 1 && basePosition + BackwardReferenceEncoder.MaxLength < maxBasePosition) + { + break; + } + + if (bestLength < BackwardReferenceEncoder.MaxLength) + { + bestLength++; + maxBasePosition = (uint)basePosition; + } + } + } + } + + [MethodImpl(InliningOptions.ShortMethod)] public int FindLength(int basePosition) { return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); } + [MethodImpl(InliningOptions.ShortMethod)] public int FindOffset(int basePosition) { return (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); } + + /// + /// Calculates the hash for a pixel pair. + /// + /// An Span with two pixels. + /// The hash. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GetPixPairHash64(Span bgra) + { + uint key = bgra[1] * HashMultiplierHi; + key += bgra[0] * HashMultiplierLo; + key = key >> (32 - HashBits); + return key; + } + + /// + /// Returns the maximum number of hash chain lookups to do for a + /// given compression quality. Return value in range [8, 86]. + /// + /// The quality. + /// Number of hash chain lookups. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMaxItersForQuality(int quality) + { + return 8 + (quality * quality / 128); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetWindowSizeForHashChain(int quality, int xSize) + { + int maxWindowSize = (quality > 75) ? WindowSize + : (quality > 50) ? (xSize << 8) + : (quality > 25) ? (xSize << 6) + : (xSize << 4); + + return (maxWindowSize > WindowSize) ? WindowSize : maxWindowSize; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 53360a981..c06d93358 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal static class LossyUtils { - [MethodImpl(InliningOptions.ShortMethod)] - private static void Put16(int v, Span dst) - { - for (int j = 0; j < 16; ++j) - { - Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16); - } - } - public static void DC16(Span dst, Span yuv, int offset) { int offsetMinus1 = offset - 1; @@ -600,27 +591,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private static void TrueMotion(Span dst, Span yuv, int offset, int size) - { - // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. - int topOffset = offset - WebPConstants.Bps; - Span top = yuv.Slice(topOffset); - byte p = yuv[topOffset - 1]; - int leftOffset = offset - 1; - byte left = yuv[leftOffset]; - for (int y = 0; y < size; ++y) - { - for (int x = 0; x < size; ++x) - { - dst[x] = (byte)Clamp255(left + top[x] - p); - } - - leftOffset += WebPConstants.Bps; - left = yuv[leftOffset]; - dst = dst.Slice(WebPConstants.Bps); - } - } - // Simple In-loop filtering (Paragraph 15.2) public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) { @@ -790,6 +760,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; } + [MethodImpl(InliningOptions.ShortMethod)] + private static void Put16(int v, Span dst) + { + for (int j = 0; j < 16; ++j) + { + Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16); + } + } + + private static void TrueMotion(Span dst, Span yuv, int offset, int size) + { + // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. + int topOffset = offset - WebPConstants.Bps; + Span top = yuv.Slice(topOffset); + byte p = yuv[topOffset - 1]; + int leftOffset = offset - 1; + byte left = yuv[leftOffset]; + for (int y = 0; y < size; ++y) + { + for (int x = 0; x < size; ++x) + { + dst[x] = (byte)Clamp255(left + top[x] - p); + } + + leftOffset += WebPConstants.Bps; + left = yuv[leftOffset]; + dst = dst.Slice(WebPConstants.Bps); + } + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( Span p, From 314c30d631636d0efffd1e14915111862be405f0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Nov 2020 11:48:50 +0100 Subject: [PATCH 235/359] Change namespace to SixLabors.ImageSharp.Formats.Experimental.WebP --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- src/ImageSharp/Configuration.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 6 +++--- src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs | 2 +- src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs | 2 +- src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs | 4 ++-- src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs | 4 ++-- src/ImageSharp/Formats/WebP/EntropyIx.cs | 2 +- src/ImageSharp/Formats/WebP/HistoIx.cs | 2 +- src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/ImageExtensions.cs | 2 +- .../Formats/WebP/Lossless/BackwardReferenceEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostManager.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostModel.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs | 2 +- .../Formats/WebP/Lossless/WebPLosslessDecoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/PassStats.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs | 2 +- src/ImageSharp/Formats/WebP/MetadataExtensions.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs | 2 +- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPCommonUtils.cs | 2 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 +- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 8 ++++---- src/ImageSharp/Formats/WebP/WebPEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPEncoderCore.cs | 6 +++--- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 2 +- src/ImageSharp/Formats/WebP/WebPFormat.cs | 2 +- src/ImageSharp/Formats/WebP/WebPFormatType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 6 +++--- src/ImageSharp/Formats/WebP/WebPLookupTables.cs | 2 +- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/WebPThrowHelper.cs | 2 +- src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs | 2 +- tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs | 2 +- tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs | 2 +- .../TestUtilities/TestEnvironment.Formats.cs | 2 +- .../TestUtilities/Tests/TestEnvironmentTests.cs | 4 +--- 114 files changed, 131 insertions(+), 133 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 6ea9075eb..bdc60698c 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Advanced AotCodec(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder()); AotCodec(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder()); AotCodec(new Formats.Tga.TgaDecoder(), new Formats.Tga.TgaEncoder()); - AotCodec(new Formats.WebP.WebPDecoder(), new Formats.WebP.WebPEncoder()); + AotCodec(new Formats.Experimental.WebP.WebPDecoder(), new Formats.Experimental.WebP.WebPEncoder()); // TODO: Do the discovery work to figure out what works and what doesn't. } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index eea6996e1..0e66576d2 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,11 +6,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index 45f9b3441..cd66059a1 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { internal enum AlphaCompressionMethod { diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index fc071095b..a8fce6b0c 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.WebP.BitReader; -using SixLabors.ImageSharp.Formats.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Implements decoding for lossy alpha chunks which may be compressed. diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index ed937201c..365c6064b 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -7,7 +7,7 @@ using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader { /// /// Base class for VP8 and VP8L bitreader. diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 40f0ae5f7..711fe99db 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader { /// /// A bit reader for VP8 streams. diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index c2647f559..9d8ccc810 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader { /// /// A bit reader for reading lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 73e8e889c..12dca20ec 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; -namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter { internal abstract class BitWriterBase { diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 7eb5bb0d8..339e71894 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -4,9 +4,9 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; -namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter { /// /// A bit writer for writing lossy webp streams. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index ade6cb61a..2e23c530e 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -4,9 +4,9 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; -namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter { /// /// A bit writer for writing lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs index 0a8e9fb4c..759717092 100644 --- a/src/ImageSharp/Formats/WebP/EntropyIx.cs +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// These five modes are evaluated and their respective entropy is computed. diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs index 51a77ccd0..24845b5a7 100644 --- a/src/ImageSharp/Formats/WebP/HistoIx.cs +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { internal enum HistoIx { diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs index 7a50a6370..ac42e30de 100644 --- a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Image decoder options for generating an image out of a webp stream. diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index f08ef8a7f..2922de34b 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Configuration options for use during webp encoding. diff --git a/src/ImageSharp/Formats/WebP/ImageExtensions.cs b/src/ImageSharp/Formats/WebP/ImageExtensions.cs index 0ed072a88..2531fcee7 100644 --- a/src/ImageSharp/Formats/WebP/ImageExtensions.cs +++ b/src/ImageSharp/Formats/WebP/ImageExtensions.cs @@ -4,7 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index f08b9f0b1..b416a67a1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class BackwardReferenceEncoder { diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index 96f70641c..737901fb8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index 457e24cbe..dcd0fd203 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs index 75afac6f2..e7a16ffd7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// To perform backward reference every pixel at index index_ is considered and diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index bba82d10e..f3669f31f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// The CostManager is in charge of managing intervals and costs. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs index 3d050ab42..f0cb2e1b2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class CostModel { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs index 02b0dcf71..9a3ed42ab 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class CrunchConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs index 04e2d511b..a4b0ad884 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class CrunchSubConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs index 7ce42b5bb..239b99095 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Data container to keep track of cost range for the three dominant entropy symbols. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index 359fbc201..0e75b62ae 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Huffman table group. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs index e04557959..c1e0bf414 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal struct HistogramBinInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 68f2e5ff2..02e56451a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class HistogramEncoder { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs index 7458909e5..912e47e78 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index d1fc50741..9853935d0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Five Huffman codes are used at each meta code. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index 2830538c7..97014c3bc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index a4578912e..588260739 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Represents the Huffman tree. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index ef88aae84..cfd7a4920 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Represents the tree codes (depth and bits array). diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index bd791b70c..c889b5766 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Holds the tree header in coded form. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 2e0805c28..4625a625b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Utility functions related to creating the huffman tables. diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 7c2cd4612..7b6cf0f78 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Utility functions for the lossless decoder. diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 96f90c029..209a22015 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] internal class PixOrCopy diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs index ae0fbec84..f2a47794d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal enum PixOrCopyMode { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 156f82032..41a62befe 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Image transform methods for the lossless webp encoder. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index f80d26697..2c0166ae8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class Vp8LBackwardRefs { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index bdc8d853f..21707fbf0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Holds bit entropy results and entropy-related functions. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index 02777ec61..6d0330deb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Holds information for decoding a lossless webp image. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index a41d7295d..df89c5acf 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -8,11 +8,11 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.WebP.BitWriter; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Encoder for lossless webp images. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 54a711c38..994731b88 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class Vp8LHashChain { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index a97dfc3de..ebc1e1175 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class Vp8LHistogram : IDeepCloneable { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs index 734aa5dce..e582f4b03 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal enum Vp8LLz77Type { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index 29d41aa83..4aae397ee 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class Vp8LMetadata { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs index 8a9088b57..d89a2b7c6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal struct Vp8LMultipliers { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index e0e976068..ad94db49a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { internal class Vp8LStreaks { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index 0dc849362..9b2dcd153 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -4,7 +4,7 @@ using System.Buffers; using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Data associated with a VP8L transformation to reduce the entropy. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index 2aa2eb9d4..694c0072e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Enum for the different transform types. Transformations are reversible manipulations of the image data diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 943d18397..f3a095328 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -8,11 +8,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { /// /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index b135c3c5e..7ac72c98e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal enum IntraPredictionMode { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index c9a823ef8..48047d78a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Enum for the different loop filters used. VP8 supports two types of loop filters. diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index c06d93358..cc60be5c0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -6,7 +6,7 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal static class LossyUtils { diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs index 20f18648f..65107fa4d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Class for organizing convergence in either size or PSNR. diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index e8f782602..32141283e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Quantization methods. diff --git a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs index 50cb1ebfc..8e312a4b1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// All the probabilities associated to one band. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs index f0ff85989..527b0d26e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8CostArray { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index f7ef1c8db..c166632ad 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -3,10 +3,10 @@ using System; using System.Buffers; -using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Holds information for decoding a lossy webp image. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index a347e8263..8a5ee8d1a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Iterator structure to iterate through macroblocks, pointing to the diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index 4e54c410d..abc101c56 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8EncProba { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs index 72dd4e16f..0765efa78 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8EncSegmentHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index dbe9314e9..b1fc902a6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.WebP.BitWriter; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Encoder for lossy webp images. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs index 3f07abe31..e20683b12 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Methods for encoding a VP8 frame. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 4c314e2fc..0710b6fab 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8FilterHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index 64f4f1d3f..4ae39f710 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Filter information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index 73a9e1841..07541ca0d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Vp8 frame header information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index 6a4184450..65b823b31 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal ref struct Vp8Io { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index cc8c174e8..b6de2aa8a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Contextual macroblock information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index 1503a467a..c46f4f116 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Data needed to reconstruct a macroblock. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs index e8cee31bf..910ead4fd 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { [DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] internal class Vp8MacroBlockInfo diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs index 1178851ca..65306a0e1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal enum Vp8MacroBlockType { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 8095478df..a2bacefe8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8Matrix { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 816752085..1abf06855 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Class to accumulate score and info during RD-optimization and mode evaluation. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 8277dccaf..697016a87 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8PictureHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index 98d237d91..718ca7ded 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Data for all frame-persistent probabilities. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index 793905b72..73614da13 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Probabilities associated to one of the contexts. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index c8b0df12b..e3b3e9b0f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8QuantMatrix { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs index 763e29f5b..067a38379 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Rate-distortion optimization levels diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 7f96b4dba..efba2df7d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// On-the-fly info about the current set of residuals. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index 1eb144486..1ea12d51e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Segment features. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index 7d03f790a..2feb9384b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8SegmentInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs index 374d37960..e4a463751 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8Stats { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs index 69c0fd5bf..6681c23bb 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8StatsArray { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index 1f4c4de5a..1b696dd3a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal class Vp8TopSamples { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 53fe0b95d..742a6212f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { /// /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index bd919e93d..85c36be65 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { internal static class YuvConversion { diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs index a0a4674d1..81b0ef90d 100644 --- a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs index bb1baece7..1e194ce1f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs +++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Enum for the different VP8 chunk header types. diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs index d8a5b3deb..f4d6a4251 100644 --- a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Enum for the different alpha filter types. diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs index 7fd7da1b6..731516d2a 100644 --- a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs +++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Enumerates the available bits per pixel the webp image uses. diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index c448ff344..98a1c2f81 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Contains a list of different webp chunk types. diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs index 6098d1b69..f73bb1a83 100644 --- a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Utility methods for lossy and lossless webp format. diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 33145e85d..a0fb02e5b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Constants used for encoding and decoding VP8 and VP8L bitstreams. diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 33a85081f..4e1d24db4 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Image decoder for generating an image out of a webp stream. diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 4270e9efc..f2f654561 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -5,9 +5,9 @@ using System; using System.Buffers.Binary; using System.IO; using System.Threading; -using SixLabors.ImageSharp.Formats.WebP.BitReader; -using SixLabors.ImageSharp.Formats.WebP.Lossless; -using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -15,7 +15,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Performs the webp decoding operation. diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 0ee881880..120716f8f 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Image encoder for writing an image to a stream in the WebP format. diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 5fbae79e2..6b8885a1e 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -5,13 +5,13 @@ using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.WebP.Lossless; -using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Image encoder for writing an image to a stream in the WebP format. diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 53ff5f54d..38292e75e 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -4,7 +4,7 @@ using System; using System.Buffers; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Image features of a VP8X image. diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 2bb606f7f..782c26f5d 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Registers the image encoders, decoders and mime type detectors for the WebP format diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs index e4ed01e84..a85cac34e 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormatType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Info about the webp format used. diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 12b360560..6da95ef84 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Detects WebP file headers. diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 526521436..89c8170a3 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.WebP.BitReader; -using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { internal class WebPImageInfo : IDisposable { diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index ed84c377c..c3e0e89dd 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { internal static class WebPLookupTables { diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 0d8e8b323..7433072ac 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Provides WebP specific metadata information for the image. diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs index c2abd31a8..bbe399a36 100644 --- a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { internal static class WebPThrowHelper { diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index 2fa4c7e7b..393771535 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// /// Registers the image encoders, decoders and mime type detectors for the webp format. diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 5c8658d15..bc147f430 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -4,7 +4,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 684b75167..2757aaea7 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -7,11 +7,11 @@ using System.Linq; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 87af15ef3..43e21f6da 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -3,7 +3,7 @@ using System.IO; -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 51276962f..3e4b0799b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index 9a5bef8c1..4eff21572 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 1caa4443a..583fb9514 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,11 +5,11 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index a83d0f4a1..34be072df 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -3,15 +3,13 @@ using System; using System.IO; - using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - using Xunit; using Xunit.Abstractions; From 7505dfe90a50ef27867ff38d037e95822b8f8291 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Nov 2020 12:51:16 +0100 Subject: [PATCH 236/359] Mark webp as experimental, dont register it in the default config --- src/ImageSharp/Configuration.cs | 10 ++++------ src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 1 + src/ImageSharp/Formats/WebP/WebPEncoder.cs | 1 + src/ImageSharp/Formats/WebP/WebPFormat.cs | 1 + src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs | 1 + tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs | 7 ------- .../Formats/ImageFormatManagerTests.cs | 2 -- .../ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 7 +++++++ 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 0e66576d2..43c7d03f7 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,7 +6,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; @@ -78,7 +77,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the size of the buffer to use when working with streams. - /// Intitialized with by default. + /// Initialized with by default. /// public int StreamProcessingBufferSize { @@ -95,9 +94,9 @@ namespace SixLabors.ImageSharp } /// - /// Gets a set of properties for the Congiguration. + /// Gets a set of properties for the Configuration. /// - /// This can be used for storing global settings and defaults to be accessable to processors. + /// This can be used for storing global settings and defaults to be accessible to processors. public IDictionary Properties { get; } = new ConcurrentDictionary(); /// @@ -190,8 +189,7 @@ namespace SixLabors.ImageSharp new JpegConfigurationModule(), new GifConfigurationModule(), new BmpConfigurationModule(), - new TgaConfigurationModule(), - new WebPConfigurationModule()); + new TgaConfigurationModule()); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index b1fc902a6..d05077400 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -181,9 +181,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.filterHeader = new Vp8FilterHeader(); int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; - this.proba = new Vp8EncProba(); - this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; + this.proba = new Vp8EncProba(); + this.Preds = new byte[predSize + this.predsWidth + this.mbw]; // Initialize with default values, which the reference c implementation uses, // to be able to compare to the original and spot differences. diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 4e1d24db4..539ba0700 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// + /// EXPERIMENTAL: /// Image decoder for generating an image out of a webp stream. /// public sealed class WebPDecoder : IImageDecoder, IWebPDecoderOptions, IImageInfoDetector diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 120716f8f..cb9a4101d 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// + /// EXPERIMENTAL: /// Image encoder for writing an image to a stream in the WebP format. /// public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 782c26f5d..241ec879a 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// + /// EXPERIMENTAL: /// Registers the image encoders, decoders and mime type detectors for the WebP format /// public sealed class WebPFormat : IImageFormat diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index 393771535..ec2571807 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -4,6 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { /// + /// EXPERIMENTAL: /// Registers the image encoders, decoders and mime type detectors for the webp format. /// public sealed class WebPConfigurationModule : IConfigurationModule diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index f6111da5a..655e98c7f 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 6; + private readonly int expectedDefaultConfigurationCount = 5; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index d5b17ad50..7577093d9 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -127,11 +127,6 @@ namespace SixLabors.ImageSharp.Tests.Formats { image.SaveAsTga(output); } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.webp"))) - { - image.SaveAsWebp(output); - } } } } @@ -179,8 +174,6 @@ namespace SixLabors.ImageSharp.Tests.Formats [InlineData(100, 100, "tga")] [InlineData(100, 10, "tga")] [InlineData(10, 100, "tga")] - [InlineData(100, 10, "webp")] - [InlineData(10, 100, "webp")] public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 2757aaea7..d6ba59e4b 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -37,14 +37,12 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 43e21f6da..cb17184ad 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -20,6 +20,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + public WebPDecoderTests() + { + Configuration.Default.ImageFormatsManager.AddImageFormat(WebPFormat.Instance); + Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); + Configuration.Default.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); + } + [Theory] [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] From 3488203684455e1fbac67aff886f4cedcba4a4c3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 16 Nov 2020 22:20:12 +0000 Subject: [PATCH 237/359] Remove some low hanging allocations --- .../Formats/WebP/BitReader/Vp8BitReader.cs | 2 ++ .../WebP/Lossless/WebPLosslessDecoder.cs | 19 ++++++++++------- .../Formats/WebP/Lossy/LossyUtils.cs | 2 +- .../Formats/WebP/Lossy/WebPLossyDecoder.cs | 21 +++++++++++-------- .../Formats/WebP/WebPCommonUtils.cs | 3 ++- .../Formats/WebP/WebPLookupTables.cs | 3 ++- .../Codecs/DecodeWebp.cs | 11 +++++++--- .../Formats/WebP/WebPDecoderTests.cs | 8 +++++++ 8 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 711fe99db..80208918d 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -91,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader public uint Remaining { get; set; } + [MethodImpl(InliningOptions.ShortMethod)] public int GetBit(int prob) { uint range = this.range; @@ -184,6 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader this.LoadNewBytes(); } + [MethodImpl(InliningOptions.ColdPath)] private void LoadNewBytes() { if (this.pos < this.bufferMax) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index f3a095328..f115fd6b7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -381,9 +381,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); int huffmanPixels = huffmanXSize * huffmanYSize; + IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); Span huffmanImageSpan = huffmanImage.GetSpan(); decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; + + // TODO: Isn't huffmanPixels the length of the span? for (int i = 0; i < huffmanPixels; ++i) { // The huffman data is stored in red and green bytes. @@ -440,6 +443,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } + // TODO: Avoid allocation. hTreeGroup.HTrees.Add(huffmanTable.ToArray()); HuffmanCode huffTableZero = huffmanTable[0]; @@ -472,10 +476,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless hTreeGroup.IsTrivialCode = false; if (isTrivialLiteral) { - uint red = hTreeGroup.HTrees[HuffIndex.Red].First().Value; - uint blue = hTreeGroup.HTrees[HuffIndex.Blue].First().Value; - uint green = hTreeGroup.HTrees[HuffIndex.Green].First().Value; - uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha].First().Value; + uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; + uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; + uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; + uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) { @@ -542,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); } - this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); @@ -550,7 +554,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless return size; } - private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { int maxSymbol; int symbol = 0; @@ -580,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.bitReader.FillBitWindow(); ulong prefetchBits = this.bitReader.PrefetchBits(); - ulong idx = prefetchBits & 127; + int idx = (int)(prefetchBits & 127); HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); uint codeLen = huffmanCode.Value; @@ -625,6 +629,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless var transform = new Vp8LTransform(transformType, xSize, ySize); // Each transform is allowed to be used only once. + // TODO: No Linq, avoid 'transform' closure allocation. if (decoder.Transforms.Any(t => t.TransformType == transform.TransformType)) { WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index cc60be5c0..d9ff33ec0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -492,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void TransformOne(Span src, Span dst) { - var tmp = new int[4 * 4]; + Span tmp = stackalloc int[4 * 4]; int tmpOffset = 0; for (int srcOffset = 0; srcOffset < 4; srcOffset++) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 742a6212f..bc3e76231 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -55,14 +55,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1); var pictureHeader = new Vp8PictureHeader() - { - Width = (uint)width, - Height = (uint)height, - XScale = info.XScale, - YScale = info.YScale, - ColorSpace = colorSpace, - ClampType = clampType - }; + { + Width = (uint)width, + Height = (uint)height, + XScale = info.XScale, + YScale = info.YScale, + ColorSpace = colorSpace, + ClampType = clampType + }; // Paragraph 9.3: Parse the segment header. var proba = new Vp8Proba(); @@ -135,10 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Span alphaSpan = alpha.Memory.Span; for (int y = 0; y < height; y++) { + // TODO: Can we use span.Length here? int yMulWidth = y * width; Span pixelRow = pixels.GetRowSpan(y); for (int x = 0; x < width; x++) { + // TODO: Could use cast to Bgr24/Bgra32 then set alpha. int offset = yMulWidth + x; int idxBgr = offset * 3; byte b = pixelData[idxBgr]; @@ -165,9 +167,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.ParseIntraMode(dec, mbX); } - for (; dec.MbX < dec.MbWidth; ++dec.MbX) + while (dec.MbX < dec.MbWidth) { this.DecodeMacroBlock(dec, bitreader); + ++dec.MbX; } // Prepare for next scanline. diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs index f73bb1a83..024d81b0b 100644 --- a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP n >>= 8; } - return logValue + WebPLookupTables.LogTable8Bit[n]; + return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebPLookupTables.LogTable8Bit), (int)n); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index c3e0e89dd..d71949fa5 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { +#pragma warning disable SA1201 // Elements should appear in the correct order internal static class WebPLookupTables { public static readonly Dictionary Abs0; @@ -418,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP }; // 31 ^ clz(i) - public static readonly byte[] LogTable8Bit = + public static ReadOnlySpan LogTable8Bit => new byte[] { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 43dc8f9d5..84130cf40 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -5,7 +5,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; - +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Config(typeof(Config.ShortClr))] public class DecodeWebp : BenchmarkBase { + private Configuration configuration; + private byte[] webpLossyBytes; private byte[] webpLosslessBytes; @@ -31,6 +33,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void ReadImages() { + this.configuration = Configuration.CreateDefaultInstance(); + new WebPConfigurationModule().Configure(this.configuration); + this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); } @@ -47,7 +52,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossy() { using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = Image.Load(memoryStream); + using var image = Image.Load(this.configuration, memoryStream); return image.Height; } @@ -63,7 +68,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossless() { using var memoryStream = new MemoryStream(this.webpLosslessBytes); - using var image = Image.Load(memoryStream); + using var image = Image.Load(this.configuration, memoryStream); return image.Height; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index cb17184ad..0c39b8b4b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -332,5 +332,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } }); } + + [Theory] + [WithFile(Lossless.Earth, PixelTypes.Rgba32)] + public void ProfileTestLossless(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + } } } From 38a88b2adc8da47eac3d7db7e7b8b132a0d358d8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 18 Nov 2020 20:48:45 +0100 Subject: [PATCH 238/359] Fix issue in ExtractAlphaRows: Filter needs to be applied after extract green --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/sticker.webp | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/WebP/sticker.webp diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index a8fce6b0c..148a7ad94 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -261,8 +261,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP // Extract alpha (which is stored in the green plane). int pixelCount = width * numRowsToProcess; WebPLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); - this.AlphaApplyFilter(0, numRowsToProcess, output, width); ExtractGreen(input, output, pixelCount); + this.AlphaApplyFilter(0, numRowsToProcess, output, width); } private static void ColorIndexInverseTransformAlpha( diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index cb17184ad..226f76dbd 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -189,6 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaThinkingSmiley, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaSticker, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6be4d3dc7..477071797 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -641,6 +641,7 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp"; public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; public const string AlphaThinkingSmiley = "WebP/1602311202.webp"; + public const string AlphaSticker = "WebP/Sticker.webp"; } } } diff --git a/tests/Images/Input/WebP/sticker.webp b/tests/Images/Input/WebP/sticker.webp new file mode 100644 index 000000000..ae781c2d0 --- /dev/null +++ b/tests/Images/Input/WebP/sticker.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49795fc80522dae2ca687b345c21e9b0848f307d3cc3e39fbdcda730772d338c +size 27734 From d7b88c8b927c34f4470053c4c8e74d1534eabb00 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 18 Nov 2020 22:58:34 +0100 Subject: [PATCH 239/359] Fix test image name to lower case --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 477071797..7b830f851 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -641,7 +641,7 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp"; public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; public const string AlphaThinkingSmiley = "WebP/1602311202.webp"; - public const string AlphaSticker = "WebP/Sticker.webp"; + public const string AlphaSticker = "WebP/sticker.webp"; } } } From 7e4879198bef41cc4bd6b51be562f4bdd24befa9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 29 Nov 2020 01:33:37 +0000 Subject: [PATCH 240/359] Use Numerics.Clamp --- src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/PassStats.cs | 6 +++--- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index df89c5acf..3fac4eb45 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -68,8 +68,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless var pixelCount = width * height; int initialSize = pixelCount * 2; - this.quality = quality.Clamp(0, 100); - this.method = method.Clamp(0, 6); + this.quality = Numerics.Clamp(quality, 0, 100); + this.method = Numerics.Clamp(method, 0, 6); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs index 65107fa4d..e041aa26d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.Dq = 10.0f; this.Qmin = qMin; this.Qmax = qMax; - this.Q = quality.Clamp(qMin, qMax); + this.Q = Numerics.Clamp(quality, qMin, qMax); this.LastQ = this.Q; this.Target = doSizeSearch ? targetSize : (targetPsnr > 0.0f) ? targetPsnr @@ -65,10 +65,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } // Limit variable to avoid large swings. - this.Dq = dq.Clamp(-30.0f, 30.0f); + this.Dq = Numerics.Clamp(dq, -30.0f, 30.0f); this.LastQ = this.Q; this.LastValue = this.Value; - this.Q = (this.Q + this.Dq).Clamp(this.Qmin, this.Qmax); + this.Q = Numerics.Clamp(this.Q + this.Dq, this.Qmin, this.Qmax); return this.Q; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index d05077400..817b760d1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -140,9 +140,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.Width = width; this.Height = height; this.memoryAllocator = memoryAllocator; - this.quality = quality.Clamp(0, 100); - this.method = method.Clamp(0, 6); - this.entropyPasses = entropyPasses.Clamp(1, 10); + this.quality = Numerics.Clamp(quality, 0, 100); + this.method = Numerics.Clamp(method, 0, 6); + this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll : (method >= 5) ? Vp8RdLevel.RdOptTrellis : (method >= 3) ? Vp8RdLevel.RdOptBasic @@ -819,7 +819,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.segmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); if (!this.segmentHeader.UpdateMap) { - this.ResetSegments(); + this.ResetSegments(); } this.segmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + From f4ee04dda52b90c37f3b19583c4a8ba37d67abb8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Nov 2020 17:10:01 +0100 Subject: [PATCH 241/359] Avoid LINQ in ReadTransformation --- .../Formats/WebP/Lossless/WebPLosslessDecoder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index f115fd6b7..2c7248fe3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -629,10 +629,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless var transform = new Vp8LTransform(transformType, xSize, ySize); // Each transform is allowed to be used only once. - // TODO: No Linq, avoid 'transform' closure allocation. - if (decoder.Transforms.Any(t => t.TransformType == transform.TransformType)) + foreach (Vp8LTransform decoderTransform in decoder.Transforms) { - WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); + if (decoderTransform.TransformType == transform.TransformType) + { + WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); + } } switch (transformType) From aba62f950b1462b873f18f770c75cba3230dba1a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Nov 2020 18:02:57 +0100 Subject: [PATCH 242/359] Slice huffman table by size --- src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 2c7248fe3..0ff3cccbc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -444,7 +444,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } // TODO: Avoid allocation. - hTreeGroup.HTrees.Add(huffmanTable.ToArray()); + hTreeGroup.HTrees.Add(huffmanTable.Slice(0, size).ToArray()); HuffmanCode huffTableZero = huffmanTable[0]; if (isTrivialLiteral && LiteralMap[j] == 1) From 3d280757efdd85fb294a9f04ebb5f0da5f5ca606 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 6 Dec 2020 16:44:46 +0100 Subject: [PATCH 243/359] Add SaveAsWebP methods and tests --- .../Formats/ImageExtensions.Save.cs | 107 +++++++++++- .../Formats/ImageExtensions.Save.tt | 28 +-- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 6 +- .../WebP/Lossless/WebPLosslessDecoder.cs | 10 +- .../Formats/WebP/ImageExtensionsTest.cs | 164 ++++++++++++++++++ .../Formats/WebP/WebPDecoderTests.cs | 1 + .../Formats/WebP/WebPEncoderTests.cs | 1 + .../Formats/WebP/WebPMetaDataTests.cs | 1 + 8 files changed, 301 insertions(+), 17 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 075c708b6..f7900ec73 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -6,6 +6,8 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +// using SixLabors.ImageSharp.Formats.Experimental.Tiff; +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -535,5 +537,108 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), cancellationToken); + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsWebP(this Image source, string path) => SaveAsWebP(source, path, null); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebPAsync(this Image source, string path) => SaveAsWebPAsync(source, path, null); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebPAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsWebPAsync(source, path, null, cancellationToken); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsWebP(this Image source, string path, WebPEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebPAsync(this Image source, string path, WebPEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance), + cancellationToken); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsWebP(this Image source, Stream stream) + => SaveAsWebP(source, stream, null); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebPAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsWebPAsync(source, stream, null, cancellationToken); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsWebP(this Image source, Stream stream, WebPEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); + + /// + /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebPAsync(this Image source, Stream stream, WebPEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance), + cancellationToken); + } } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index 63b404cc4..c8979da0d 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -1,4 +1,4 @@ -<#@ template language="C#" #> +<#@ template language="C#" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // Copyright (c) Six Labors. @@ -9,6 +9,8 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +// using SixLabors.ImageSharp.Formats.Experimental.Tiff; +using SixLabors.ImageSharp.Formats.Experimental.WebP; <# var formats = new []{ @@ -17,10 +19,15 @@ using SixLabors.ImageSharp.Advanced; "Jpeg", "Png", "Tga", + "WebP" }; foreach (string fmt in formats) { + if (fmt == "Tiff" || fmt == "WebP") + { + continue; + } #> using SixLabors.ImageSharp.Formats.<#= fmt #>; <# @@ -38,9 +45,10 @@ namespace SixLabors.ImageSharp <# foreach (string fmt in formats) { + string experimentalString = fmt == "Tiff" || fmt == "WebP" ? @"EXPERIMENTAL! " : ""; #> /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The file path to save the image to. @@ -48,7 +56,7 @@ namespace SixLabors.ImageSharp public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The file path to save the image to. @@ -57,7 +65,7 @@ namespace SixLabors.ImageSharp public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The file path to save the image to. @@ -68,7 +76,7 @@ namespace SixLabors.ImageSharp => SaveAs<#= fmt #>Async(source, path, null, cancellationToken); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The file path to save the image to. @@ -80,7 +88,7 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The file path to save the image to. @@ -95,7 +103,7 @@ namespace SixLabors.ImageSharp cancellationToken); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The stream to save the image to. @@ -104,7 +112,7 @@ namespace SixLabors.ImageSharp => SaveAs<#= fmt #>(source, stream, null); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The stream to save the image to. @@ -115,7 +123,7 @@ namespace SixLabors.ImageSharp => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The stream to save the image to. @@ -128,7 +136,7 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); /// - /// Saves the image to the given stream with the <#= fmt #> format. + /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. /// /// The image this method extends. /// The stream to save the image to. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 3fac4eb45..9782d8ab7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -423,6 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; + bool isFirstIteration = true; foreach (CrunchSubConfig subConfig in config.SubConfigs) { Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( @@ -525,11 +526,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); // Keep track of the smallest image so far. - if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()) + if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) { // TODO: This was done in the reference by swapping references, this will be slower bitWriterBest = this.bitWriter.Clone(); } + + isFirstIteration = false; } this.bitWriter = bitWriterBest; @@ -1156,6 +1159,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless xBits = (paletteSize <= 16) ? 1 : 0; } + this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits); this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 0ff3cccbc..8a6c36595 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -196,15 +196,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - int widthMul4 = width * 4; + int bytesPerRow = width * 4; for (int y = 0; y < decoder.Height; y++) { - Span row = pixelDataAsBytes.Slice(y * widthMul4, widthMul4); - Span pixelSpan = pixels.GetRowSpan(y); + Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); + Span pixelRow = pixels.GetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes( this.configuration, - row, - pixelSpan, + rowAsBytes.Slice(0, bytesPerRow), + pixelRow.Slice(0, width), width); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs new file mode 100644 index 000000000..b20376b47 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class ImageExtensionsTest + { + public ImageExtensionsTest() + { + Configuration.Default.ImageFormatsManager.AddImageFormat(WebPFormat.Instance); + Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); + Configuration.Default.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); + Configuration.Default.ImageFormatsManager.SetEncoder(WebPFormat.Instance, new WebPEncoder()); + } + + [Fact] + public void SaveAsWebp_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); + + using (var image = new Image(10, 10)) + { + image.SaveAsWebP(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsWebpAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsWebPAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsWebp_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); + + using (var image = new Image(10, 10)) + { + image.SaveAsWebP(file, new WebPEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsWebpAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsWebPAsync(file, new WebPEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsWebp_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsWebP(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsWebpAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsWebPAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsWebp_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsWebp(memoryStream, new WebPEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsWebpAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsWebPAsync(memoryStream, new WebPEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 938ba2e7d..562befa21 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { using static SixLabors.ImageSharp.Tests.TestImages.WebP; + [Trait("Format", "Webp")] public class WebPDecoderTests { private static WebPDecoder WebpDecoder => new WebPDecoder(); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 3e4b0799b..8143503a1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { using static TestImages.WebP; + [Trait("Format", "Webp")] public class WebPEncoderTests { [Theory] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index 4eff21572..375770117 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -8,6 +8,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.WebP { + [Trait("Format", "Webp")] public class WebPMetadataTests { [Theory] From 71b5a1f31d1e742119801ceb1141e0c331a51371 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 6 Dec 2020 17:08:55 +0100 Subject: [PATCH 244/359] BitReader now uses stream extension to read the data from the stream --- .../Formats/WebP/BitReader/BitReaderBase.cs | 18 +----------------- .../WebP/Lossless/WebPLosslessDecoder.cs | 10 ++++++++-- .../Formats/WebP/WebPDecoderTests.cs | 13 ++++++++++++- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 365c6064b..60ed96ab3 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -29,23 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader { this.Data = memoryAllocator.Allocate(bytesToRead); Span dataSpan = this.Data.Memory.Span; - - 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) - { - buffer.Array.AsSpan(0, read).CopyTo(dataSpan); - bytesToRead -= read; - dataSpan = dataSpan.Slice(read); - } - - if (bytesToRead > 0) - { - WebPThrowHelper.ThrowImageFormatException("webp image file has insufficient data"); - } - } + input.Read(dataSpan.Slice(0, bytesToRead), 0, bytesToRead); } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 8a6c36595..d396d3746 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -945,17 +945,23 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// The number of pixels to copy. private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { + int start = decodedPixels - dist; + if (start < 0) + { + WebPThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); + } + if (dist >= length) { // no overlap. - Span src = pixelData.Slice(decodedPixels - dist, length); + Span src = pixelData.Slice(start, length); Span dest = pixelData.Slice(decodedPixels); src.CopyTo(dest); } else { // There is overlap between the backward reference distance and the pixels to copy. - Span src = pixelData.Slice(decodedPixels - dist); + Span src = pixelData.Slice(start); Span dest = pixelData.Slice(decodedPixels); for (int i = 0; i < length; i++) { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 562befa21..c58d4b4e4 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -321,8 +321,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] - [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecodeLosslessWithIssues(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Just make sure no exception is thrown. The reference decoder fails to load the image. + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + } + } + + [Theory] + [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { From d74463d5a616b864fe69be7dff659d969bc32762 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 6 Dec 2020 17:40:50 +0100 Subject: [PATCH 245/359] Rename WebP to Webp --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- .../Formats/ImageExtensions.Save.cs | 52 ++-- .../Formats/ImageExtensions.Save.tt | 8 +- .../Formats/WebP/AlphaCompressionMethod.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 38 +-- .../Formats/WebP/BitReader/BitReaderBase.cs | 2 +- .../Formats/WebP/BitReader/Vp8BitReader.cs | 4 +- .../Formats/WebP/BitReader/Vp8LBitReader.cs | 2 +- .../Formats/WebP/BitWriter/BitWriterBase.cs | 6 +- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 58 ++--- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 10 +- src/ImageSharp/Formats/WebP/EntropyIx.cs | 2 +- src/ImageSharp/Formats/WebP/HistoIx.cs | 2 +- .../Formats/WebP/IWebPDecoderOptions.cs | 4 +- .../Formats/WebP/IWebPEncoderOptions.cs | 2 +- .../Formats/WebP/ImageExtensions.cs | 36 --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 16 +- .../Formats/WebP/Lossless/ColorCache.cs | 2 +- .../WebP/Lossless/CostCacheInterval.cs | 2 +- .../Formats/WebP/Lossless/CostInterval.cs | 2 +- .../Formats/WebP/Lossless/CostManager.cs | 2 +- .../Formats/WebP/Lossless/CostModel.cs | 8 +- .../Formats/WebP/Lossless/CrunchConfig.cs | 2 +- .../Formats/WebP/Lossless/CrunchSubConfig.cs | 2 +- .../WebP/Lossless/DominantCostRange.cs | 2 +- .../Formats/WebP/Lossless/HTreeGroup.cs | 6 +- .../Formats/WebP/Lossless/HistogramBinInfo.cs | 2 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 2 +- .../Formats/WebP/Lossless/HistogramPair.cs | 2 +- .../Formats/WebP/Lossless/HuffIndex.cs | 2 +- .../Formats/WebP/Lossless/HuffmanCode.cs | 2 +- .../Formats/WebP/Lossless/HuffmanTree.cs | 2 +- .../Formats/WebP/Lossless/HuffmanTreeCode.cs | 2 +- .../Formats/WebP/Lossless/HuffmanTreeToken.cs | 2 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 26 +- .../Formats/WebP/Lossless/LosslessUtils.cs | 26 +- .../Formats/WebP/Lossless/PixOrCopy.cs | 2 +- .../Formats/WebP/Lossless/PixOrCopyMode.cs | 2 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 10 +- .../Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 2 +- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 2 +- .../Formats/WebP/Lossless/Vp8LDecoder.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 66 +++--- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 2 +- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 68 +++--- .../Formats/WebP/Lossless/Vp8LLz77Type.cs | 2 +- .../Formats/WebP/Lossless/Vp8LMetadata.cs | 2 +- .../Formats/WebP/Lossless/Vp8LMultipliers.cs | 2 +- .../Formats/WebP/Lossless/Vp8LStreaks.cs | 4 +- .../Formats/WebP/Lossless/Vp8LTransform.cs | 2 +- .../WebP/Lossless/Vp8LTransformType.cs | 2 +- .../WebP/Lossless/WebPLosslessDecoder.cs | 76 +++--- .../Formats/WebP/Lossy/IntraPredictionMode.cs | 2 +- .../Formats/WebP/Lossy/LoopFilter.cs | 2 +- .../Formats/WebP/Lossy/LossyUtils.cs | 222 +++++++++--------- .../Formats/WebP/Lossy/PassStats.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 4 +- .../Formats/WebP/Lossy/VP8BandProbas.cs | 6 +- .../Formats/WebP/Lossy/Vp8CostArray.cs | 4 +- .../Formats/WebP/Lossy/Vp8Decoder.cs | 18 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 40 ++-- .../Formats/WebP/Lossy/Vp8EncProba.cs | 60 ++--- .../Formats/WebP/Lossy/Vp8EncSegmentHeader.cs | 2 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 78 +++--- .../Formats/WebP/Lossy/Vp8Encoding.cs | 38 +-- .../Formats/WebP/Lossy/Vp8FilterHeader.cs | 2 +- .../Formats/WebP/Lossy/Vp8FilterInfo.cs | 2 +- .../Formats/WebP/Lossy/Vp8FrameHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs | 2 +- .../Formats/WebP/Lossy/Vp8MacroBlock.cs | 2 +- .../Formats/WebP/Lossy/Vp8MacroBlockData.cs | 2 +- .../Formats/WebP/Lossy/Vp8MacroBlockInfo.cs | 2 +- .../Formats/WebP/Lossy/Vp8MacroBlockType.cs | 2 +- .../Formats/WebP/Lossy/Vp8Matrix.cs | 8 +- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 2 +- .../Formats/WebP/Lossy/Vp8PictureHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs | 12 +- .../Formats/WebP/Lossy/Vp8ProbaArray.cs | 4 +- .../Formats/WebP/Lossy/Vp8QuantMatrix.cs | 2 +- .../Formats/WebP/Lossy/Vp8RDLevel.cs | 2 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 12 +- .../Formats/WebP/Lossy/Vp8SegmentHeader.cs | 2 +- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs | 6 +- .../Formats/WebP/Lossy/Vp8StatsArray.cs | 4 +- .../Formats/WebP/Lossy/Vp8TopSamples.cs | 2 +- .../Formats/WebP/Lossy/WebPLossyDecoder.cs | 118 +++++----- .../Formats/WebP/Lossy/YuvConversion.cs | 18 +- .../Formats/WebP/MetadataExtensions.cs | 6 +- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 2 +- .../Formats/WebP/WebPAlphaFilterType.cs | 4 +- .../Formats/WebP/WebPBitsPerPixel.cs | 4 +- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 4 +- .../Formats/WebP/WebPCommonUtils.cs | 6 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 4 +- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 12 +- .../Formats/WebP/WebPDecoderCore.cs | 128 +++++----- src/ImageSharp/Formats/WebP/WebPEncoder.cs | 8 +- .../Formats/WebP/WebPEncoderCore.cs | 12 +- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 4 +- src/ImageSharp/Formats/WebP/WebPFormat.cs | 14 +- src/ImageSharp/Formats/WebP/WebPFormatType.cs | 4 +- .../Formats/WebP/WebPImageFormatDetector.cs | 10 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 12 +- .../Formats/WebP/WebPLookupTables.cs | 40 ++-- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 18 +- .../Formats/WebP/WebPThrowHelper.cs | 4 +- .../Formats/WebP/WebpConfigurationModule.cs | 10 +- .../Codecs/DecodeWebp.cs | 4 +- .../Codecs/EncodeWebp.cs | 6 +- .../Formats/ImageFormatManagerTests.cs | 2 +- .../Formats/WebP/ImageExtensionsTest.cs | 26 +- .../Formats/WebP/WebPDecoderTests.cs | 16 +- .../Formats/WebP/WebPEncoderTests.cs | 14 +- .../Formats/WebP/WebPMetaDataTests.cs | 10 +- .../TestUtilities/TestEnvironment.Formats.cs | 4 +- .../Tests/TestEnvironmentTests.cs | 10 +- 117 files changed, 820 insertions(+), 856 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/ImageExtensions.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index bdc60698c..22f107c78 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Advanced AotCodec(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder()); AotCodec(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder()); AotCodec(new Formats.Tga.TgaDecoder(), new Formats.Tga.TgaEncoder()); - AotCodec(new Formats.Experimental.WebP.WebPDecoder(), new Formats.Experimental.WebP.WebPEncoder()); + AotCodec(new Formats.Experimental.Webp.WebpDecoder(), new Formats.Experimental.Webp.WebpEncoder()); // TODO: Do the discovery work to figure out what works and what doesn't. } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index f7900ec73..94f058524 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; // using SixLabors.ImageSharp.Formats.Experimental.Tiff; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -538,47 +538,47 @@ namespace SixLabors.ImageSharp cancellationToken); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The file path to save the image to. /// Thrown if the path is null. - public static void SaveAsWebP(this Image source, string path) => SaveAsWebP(source, path, null); + public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The file path to save the image to. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsWebPAsync(this Image source, string path) => SaveAsWebPAsync(source, path, null); + public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The file path to save the image to. /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsWebPAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsWebPAsync(source, path, null, cancellationToken); + public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsWebpAsync(source, path, null, cancellationToken); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The file path to save the image to. /// The encoder to save the image with. /// Thrown if the path is null. - public static void SaveAsWebP(this Image source, string path, WebPEncoder encoder) => + public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => source.Save( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The file path to save the image to. @@ -586,47 +586,47 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsWebPAsync(this Image source, string path, WebPEncoder encoder, CancellationToken cancellationToken = default) => + public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), cancellationToken); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The stream to save the image to. /// Thrown if the stream is null. - public static void SaveAsWebP(this Image source, Stream stream) - => SaveAsWebP(source, stream, null); + public static void SaveAsWebp(this Image source, Stream stream) + => SaveAsWebp(source, stream, null); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The stream to save the image to. /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsWebPAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsWebPAsync(source, stream, null, cancellationToken); + public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsWebpAsync(source, stream, null, cancellationToken); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The stream to save the image to. /// The encoder to save the image with. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static void SaveAsWebP(this Image source, Stream stream, WebPEncoder encoder) + public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) => source.Save( stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); /// - /// EXPERIMENTAL! Saves the image to the given stream with the WebP format. + /// EXPERIMENTAL! Saves the image to the given stream with the Webp format. /// /// The image this method extends. /// The stream to save the image to. @@ -634,10 +634,10 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsWebPAsync(this Image source, Stream stream, WebPEncoder encoder, CancellationToken cancellationToken = default) => + public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), cancellationToken); } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index c8979da0d..a9102cb29 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; // using SixLabors.ImageSharp.Formats.Experimental.Tiff; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; <# var formats = new []{ @@ -19,12 +19,12 @@ using SixLabors.ImageSharp.Formats.Experimental.WebP; "Jpeg", "Png", "Tga", - "WebP" + "Webp" }; foreach (string fmt in formats) { - if (fmt == "Tiff" || fmt == "WebP") + if (fmt == "Tiff" || fmt == "Webp") { continue; } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp <# foreach (string fmt in formats) { - string experimentalString = fmt == "Tiff" || fmt == "WebP" ? @"EXPERIMENTAL! " : ""; + string experimentalString = fmt == "Tiff" || fmt == "Webp" ? @"EXPERIMENTAL! " : ""; #> /// /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format. diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index cd66059a1..7b575adae 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { internal enum AlphaCompressionMethod { diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 148a7ad94..041072fa3 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Implements decoding for lossy alpha chunks which may be compressed. @@ -40,20 +40,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03); if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression) { - WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); + WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); } this.Compressed = compression == AlphaCompressionMethod.WebPLosslessCompression; // The filtering method used. Only values between 0 and 3 are valid. int filter = (alphaChunkHeader >> 2) & 0x03; - if (filter < (int)WebPAlphaFilterType.None || filter > (int)WebPAlphaFilterType.Gradient) + if (filter < (int)WebpAlphaFilterType.None || filter > (int)WebpAlphaFilterType.Gradient) { - WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); + WebpThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } this.Alpha = memoryAllocator.Allocate(totalPixels); - this.AlphaFilterType = (WebPAlphaFilterType)filter; + this.AlphaFilterType = (WebpAlphaFilterType)filter; this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); if (this.Compressed) @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Gets the used filter type. /// - public WebPAlphaFilterType AlphaFilterType { get; } + public WebpAlphaFilterType AlphaFilterType { get; } /// /// Gets or sets the last decoded row. @@ -133,11 +133,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP var pixelCount = this.Width * this.Height; if (dataSpan.Length < pixelCount) { - WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); + WebpThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } Span alphaSpan = this.Alpha.Memory.Span; - if (this.AlphaFilterType == WebPAlphaFilterType.None) + if (this.AlphaFilterType == WebpAlphaFilterType.None) { dataSpan.Slice(0, pixelCount).CopyTo(alphaSpan); return; @@ -150,13 +150,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { switch (this.AlphaFilterType) { - case WebPAlphaFilterType.Horizontal: + case WebpAlphaFilterType.Horizontal: HorizontalUnfilter(prev, deltas, dst, this.Width); break; - case WebPAlphaFilterType.Vertical: + case WebpAlphaFilterType.Vertical: VerticalUnfilter(prev, deltas, dst, this.Width); break; - case WebPAlphaFilterType.Gradient: + case WebpAlphaFilterType.Gradient: GradientUnfilter(prev, deltas, dst, this.Width); break; } @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// The stride to use. public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) { - if (this.AlphaFilterType == WebPAlphaFilterType.None) + if (this.AlphaFilterType == WebpAlphaFilterType.None) { return; } @@ -200,13 +200,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { switch (this.AlphaFilterType) { - case WebPAlphaFilterType.Horizontal: + case WebpAlphaFilterType.Horizontal: HorizontalUnfilter(prev, dst, dst, this.Width); break; - case WebPAlphaFilterType.Vertical: + case WebpAlphaFilterType.Vertical: VerticalUnfilter(prev, dst, dst, this.Width); break; - case WebPAlphaFilterType.Gradient: + case WebpAlphaFilterType.Gradient: GradientUnfilter(prev, dst, dst, this.Width); break; } @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. - int topRow = (this.AlphaFilterType == WebPAlphaFilterType.None || this.AlphaFilterType == WebPAlphaFilterType.Horizontal) ? 0 : this.LastRow; + int topRow = (this.AlphaFilterType == WebpAlphaFilterType.None || this.AlphaFilterType == WebpAlphaFilterType.Horizontal) ? 0 : this.LastRow; int firstRow = (this.LastRow < topRow) ? topRow : this.LastRow; if (lastRow > firstRow) { @@ -235,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP if (this.Vp8LDec.Transforms.Count == 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) { - WebPThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); + WebpThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); } Vp8LTransform transform = this.Vp8LDec.Transforms[0]; diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 60ed96ab3..2c1892532 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -7,7 +7,7 @@ using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader { /// /// Base class for VP8 and VP8L bitreader. diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 80208918d..5cc12259a 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader { /// /// A bit reader for VP8 streams. @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader range = split + 1; } - int shift = 7 ^ WebPCommonUtils.BitsLog2Floor(range); + int shift = 7 ^ WebpCommonUtils.BitsLog2Floor(range); range <<= shift; this.bits -= shift; diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index 9d8ccc810..61d00323e 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader { /// /// A bit reader for reading lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 12dca20ec..f12bacd87 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { internal abstract class BitWriterBase { @@ -98,10 +98,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter { Span buffer = stackalloc byte[4]; - stream.Write(WebPConstants.RiffFourCc); + stream.Write(WebpConstants.RiffFourCc); BinaryPrimitives.WriteUInt32LittleEndian(buffer, riffSize); stream.Write(buffer); - stream.Write(WebPConstants.WebPHeader); + stream.Write(WebpConstants.WebPHeader); } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 339e71894..e1483ce30 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -4,9 +4,9 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { /// /// A bit writer for writing lossy webp streams. @@ -100,13 +100,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter int v = sign ? -c : c; if (!this.PutBit(v != 0, p.Probabilities[1])) { - p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[0]; + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[0]; continue; } if (!this.PutBit(v > 1, p.Probabilities[2])) { - p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[1]; + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[1]; } else { @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter this.PutBit(0, p.Probabilities[9]); v -= 3 + (8 << 0); mask = 1 << 2; - tab = WebPConstants.Cat3; + tab = WebpConstants.Cat3; } else if (v < 3 + (8 << 2)) { @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter this.PutBit(1, p.Probabilities[9]); v -= 3 + (8 << 1); mask = 1 << 3; - tab = WebPConstants.Cat4; + tab = WebpConstants.Cat4; } else if (v < 3 + (8 << 3)) { @@ -158,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter this.PutBit(0, p.Probabilities[10]); v -= 3 + (8 << 2); mask = 1 << 4; - tab = WebPConstants.Cat5; + tab = WebpConstants.Cat5; } else { @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter this.PutBit(1, p.Probabilities[10]); v -= 3 + (8 << 3); mask = 1 << 10; - tab = WebPConstants.Cat6; + tab = WebpConstants.Cat6; } var tabIdx = 0; @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter } } - p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[2]; + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[2]; } this.PutBitUniform(sign ? 1 : 0); @@ -311,8 +311,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter if (this.range < 127) { // emit 'shift' bits out and renormalize. - int shift = WebPLookupTables.Norm[this.range]; - this.range = WebPLookupTables.NewRange[this.range]; + int shift = WebpLookupTables.Norm[this.range]; + this.range = WebpLookupTables.NewRange[this.range]; this.value <<= shift; this.nbBits += shift; if (this.nbBits > 0) @@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter if (this.range < 127) { - this.range = WebPLookupTables.NewRange[this.range]; + this.range = WebpLookupTables.NewRange[this.range]; this.value <<= 1; this.nbBits += 1; if (this.nbBits > 0) @@ -420,14 +420,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter // Partition #0 with header and partition sizes uint size0 = this.GeneratePartition0(bitWriterPartZero); - uint vp8Size = WebPConstants.Vp8FrameHeaderSize + size0; + uint vp8Size = WebpConstants.Vp8FrameHeaderSize + size0; vp8Size += numBytes; uint pad = vp8Size & 1; vp8Size += pad; // Compute RIFF size // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. - var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8Size; + var riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; // Emit headers and partition #0 this.WriteWebPHeaders(stream, size0, vp8Size, riffSize); @@ -474,12 +474,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter { // We always use absolute values, not relative ones. bitWriter.PutBitUniform(1); // (segment_feature_mode = 1. Paragraph 9.3.) - for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) { bitWriter.PutSignedBits(this.enc.SegmentInfos[s].Quant, 7); } - for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) { bitWriter.PutSignedBits(this.enc.SegmentInfos[s].FStrength, 6); } @@ -535,17 +535,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter private void WriteProbas(Vp8BitWriter bitWriter) { Vp8EncProba probas = this.enc.Proba; - for (int t = 0; t < WebPConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int b = 0; b < WebPConstants.NumBands; ++b) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int c = 0; c < WebPConstants.NumCtx; ++c) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int p = 0; p < WebPConstants.NumProbas; ++p) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { byte p0 = probas.Coeffs[t][b].Probabilities[c].Probabilities[p]; - bool update = p0 != WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; - if (bitWriter.PutBit(update, WebPLookupTables.CoeffsUpdateProba[t, b, c, p])) + bool update = p0 != WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; + if (bitWriter.PutBit(update, WebpLookupTables.CoeffsUpdateProba[t, b, c, p])) { bitWriter.PutBits(p0, 8); } @@ -594,7 +594,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter int left = it.Preds[predIdx - 1]; for (int x = 0; x < 4; ++x) { - byte[] probas = WebPLookupTables.ModesProba[topPred[x], left]; + byte[] probas = WebpLookupTables.ModesProba[topPred[x], left]; left = bitWriter.PutI4Mode(it.Preds[predIdx + x], probas); } @@ -617,9 +617,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter private void WriteVp8Header(Stream stream, uint size) { - Span vp8ChunkHeader = stackalloc byte[WebPConstants.ChunkHeaderSize]; + Span vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize]; - WebPConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader); + WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader); BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader.Slice(4), size); stream.Write(vp8ChunkHeader); @@ -630,7 +630,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter uint profile = 0; int width = this.enc.Width; int height = this.enc.Height; - var vp8FrameHeader = new byte[WebPConstants.Vp8FrameHeaderSize]; + var vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize]; // Paragraph 9.1. uint bits = 0 // keyframe (1b) @@ -643,9 +643,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter vp8FrameHeader[2] = (byte)((bits >> 16) & 0xff); // signature - vp8FrameHeader[3] = WebPConstants.Vp8HeaderMagicBytes[0]; - vp8FrameHeader[4] = WebPConstants.Vp8HeaderMagicBytes[1]; - vp8FrameHeader[5] = WebPConstants.Vp8HeaderMagicBytes[2]; + vp8FrameHeader[3] = WebpConstants.Vp8HeaderMagicBytes[0]; + vp8FrameHeader[4] = WebpConstants.Vp8HeaderMagicBytes[1]; + vp8FrameHeader[5] = WebpConstants.Vp8HeaderMagicBytes[2]; // dimensions vp8FrameHeader[6] = (byte)(width & 0xff); diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 2e23c530e..4bf3d5f05 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -4,9 +4,9 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { /// /// A bit writer for writing lossless webp streams. @@ -143,14 +143,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter // Write RIFF header. uint pad = size & 1; - uint riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + size + pad; + uint riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad; this.WriteRiffHeader(stream, riffSize); - stream.Write(WebPConstants.Vp8LMagicBytes); + stream.Write(WebpConstants.Vp8LMagicBytes); // Write Vp8 Header. BinaryPrimitives.WriteUInt32LittleEndian(buffer, size); stream.Write(buffer); - stream.WriteByte(WebPConstants.Vp8LHeaderMagicByte); + stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); this.WriteToStream(stream); if (pad == 1) diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs index 759717092..f9a70f919 100644 --- a/src/ImageSharp/Formats/WebP/EntropyIx.cs +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// These five modes are evaluated and their respective entropy is computed. diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs index 24845b5a7..d766a84bf 100644 --- a/src/ImageSharp/Formats/WebP/HistoIx.cs +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { internal enum HistoIx { diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs index ac42e30de..81c875761 100644 --- a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Image decoder options for generating an image out of a webp stream. /// - internal interface IWebPDecoderOptions + internal interface IWebpDecoderOptions { /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index 2922de34b..46f016a5f 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Configuration options for use during webp encoding. diff --git a/src/ImageSharp/Formats/WebP/ImageExtensions.cs b/src/ImageSharp/Formats/WebP/ImageExtensions.cs deleted file mode 100644 index 2531fcee7..000000000 --- a/src/ImageSharp/Formats/WebP/ImageExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Experimental.WebP; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream) => SaveAsWebp(source, stream, null); - - /// - /// Saves the image to the given stream with the webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The options for the encoder. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream, WebPEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); - } -} diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index b416a67a1..9b7e16389 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class BackwardReferenceEncoder { @@ -129,9 +129,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless double entropyMin = MaxEntropy; int pos = 0; - var colorCache = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; - var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; - for (int i = 0; i <= WebPConstants.MaxColorCacheBits; i++) + var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; + var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1]; + for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) { histos[i] = new Vp8LHistogram(paletteCodeBits: i); colorCache[i] = new ColorCache(); @@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { if (colorCache[i].Lookup(key) == pix) { - ++histos[i].Literal[WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + key]; + ++histos[i].Literal[WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + key]; } else { @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; - var literalArraySize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); + var literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); var costModel = new CostModel(literalArraySize); int offsetPrev = -1; int lenPrev = -1; @@ -824,11 +824,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int xOffset = dist - (yOffset * xSize); if (xOffset <= 8 && yOffset < 8) { - return (int)WebPLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; + return (int)WebpLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; } else if (xOffset > xSize - 8 && yOffset < 7) { - return (int)WebPLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; + return (int)WebpLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; } return dist + 120; diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index 737901fb8..7fd12236e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index dcd0fd203..c6febb82d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs index e7a16ffd7..2fce1651b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// To perform backward reference every pixel at index index_ is considered and diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index f3669f31f..0cf6df2a7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// The CostManager is in charge of managing intervals and costs. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs index f0cb2e1b2..8edbd0aca 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class CostModel { @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.Alpha = new double[ValuesInBytes]; this.Red = new double[ValuesInBytes]; this.Blue = new double[ValuesInBytes]; - this.Distance = new double[WebPConstants.NumDistanceCodes]; + this.Distance = new double[WebpConstants.NumDistanceCodes]; this.Literal = new double[literalArraySize]; } @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red); ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue); ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha); - ConvertPopulationCountTableToBitEstimates(WebPConstants.NumDistanceCodes, histogram.Distance, this.Distance); + ConvertPopulationCountTableToBitEstimates(WebpConstants.NumDistanceCodes, histogram.Distance, this.Distance); } public double GetLengthCost(int length) @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless public double GetCacheCost(uint idx) { - int literalIdx = (int)(ValuesInBytes + WebPConstants.NumLengthCodes + idx); + int literalIdx = (int)(ValuesInBytes + WebpConstants.NumLengthCodes + idx); return this.Literal[literalIdx]; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs index 9a3ed42ab..62ba42f9b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class CrunchConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs index a4b0ad884..4dc59c0c6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class CrunchSubConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs index 239b99095..1b4011108 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Data container to keep track of cost range for the three dominant entropy symbols. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index 0e75b62ae..a25dbffb4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Huffman table group. @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { public HTreeGroup(uint packedTableSize) { - this.HTrees = new List(WebPConstants.HuffmanCodesPerMetaCode); + this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); this.PackedTable = new HuffmanCode[packedTableSize]; for (int i = 0; i < packedTableSize; i++) { @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } /// - /// Gets the Huffman trees. This has a maximum of (5) entry's. + /// Gets the Huffman trees. This has a maximum of (5) entry's. /// public List HTrees { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs index c1e0bf414..5caee010e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal struct HistogramBinInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 02e56451a..6b1fee5a6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class HistogramEncoder { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs index 912e47e78..0b4c20926 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index 9853935d0..4f43725f4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Five Huffman codes are used at each meta code. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index 97014c3bc..a7bd5f919 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index 588260739..0a1f7d60f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Represents the Huffman tree. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index cfd7a4920..a3944a6d4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Represents the tree codes (depth and bits array). diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index c889b5766..c0472c651 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Holds the tree header in coded form. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 4625a625b..1329802eb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Utility functions related to creating the huffman tables. @@ -312,14 +312,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int totalSize = 1 << rootBits; // total size root table + 2nd level table. int len; // current code length. int symbol; // symbol index in original or sorted table. - var counts = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - var offsets = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. + var counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + var offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) { var codeLengthOfSymbol = codeLengths[symbol]; - if (codeLengthOfSymbol > WebPConstants.MaxAllowedCodeLength) + if (codeLengthOfSymbol > WebpConstants.MaxAllowedCodeLength) { return 0; } @@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // Generate offsets into sorted symbol table by code length. offsets[1] = 0; - for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) + for (len = 1; len < WebpConstants.MaxAllowedCodeLength; ++len) { int codesOfLength = counts[len]; if (codesOfLength > (1 << len)) @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } // Special case code with only one value. - if (offsets[WebPConstants.MaxAllowedCodeLength] == 1) + if (offsets[WebpConstants.MaxAllowedCodeLength] == 1) { var huffmanCode = new HuffmanCode() { @@ -407,7 +407,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // Fill in 2nd level tables and add pointers to root table. Span tableSpan = table; int tablePos = 0; - for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1) + for (len = rootBits + 1, step = 2; len <= WebpConstants.MaxAllowedCodeLength; ++len, step <<= 1) { numOpen <<= 1; numNodes += numOpen; @@ -542,8 +542,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) { // 0 bit-depth means that the symbol does not exist. - uint[] nextCode = new uint[WebPConstants.MaxAllowedCodeLength + 1]; - int[] depthCount = new int[WebPConstants.MaxAllowedCodeLength + 1]; + uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1]; + int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1]; int len = tree.NumSymbols; for (int i = 0; i < len; i++) @@ -556,7 +556,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless nextCode[0] = 0; uint code = 0; - for (int i = 1; i <= WebPConstants.MaxAllowedCodeLength; i++) + for (int i = 1; i <= WebpConstants.MaxAllowedCodeLength; i++) { code = (uint)((code + depthCount[i - 1]) << 1); nextCode[i] = code; @@ -589,11 +589,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless while (i < numBits) { i += 4; - retval |= (uint)(reversedBits[bits & 0xf] << (WebPConstants.MaxAllowedCodeLength + 1 - i)); + retval |= (uint)(reversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); bits >>= 4; } - retval >>= WebPConstants.MaxAllowedCodeLength + 1 - numBits; + retval >>= WebpConstants.MaxAllowedCodeLength + 1 - numBits; return retval; } @@ -604,7 +604,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private static int NextTableBitSize(int[] count, int len, int rootBits) { int left = 1 << (len - rootBits); - while (len < WebPConstants.MaxAllowedCodeLength) + while (len < WebpConstants.MaxAllowedCodeLength) { left -= count[len]; if (left <= 0) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 7b6cf0f78..9f370513b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -7,14 +7,14 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Utility functions for the lossless decoder. /// internal static unsafe class LosslessUtils { - private const uint Predictor0 = WebPConstants.ArgbBlack; + private const uint Predictor0 = WebpConstants.ArgbBlack; private const int PrefixLookupIdxMax = 512; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { if (distance < PrefixLookupIdxMax) { - (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance]; + (int code, int extraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; extraBits = prefixCode.extraBits; return prefixCode.code; } @@ -82,9 +82,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { if (distance < PrefixLookupIdxMax) { - (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance]; + (int code, int extraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; extraBits = prefixCode.extraBits; - extraBitsValue = WebPLookupTables.PrefixEncodeExtraBitsValue[distance]; + extraBitsValue = WebpLookupTables.PrefixEncodeExtraBitsValue[distance]; return prefixCode.code; } @@ -510,7 +510,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// public static float FastLog2(uint v) { - return (v < LogLookupIdxMax) ? WebPLookupTables.Log2Table[v] : FastLog2Slow(v); + return (v < LogLookupIdxMax) ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); } /// @@ -519,7 +519,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static float FastSLog2(uint v) { - return (v < LogLookupIdxMax) ? WebPLookupTables.SLog2Table[v] : FastSLog2Slow(v); + return (v < LogLookupIdxMax) ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); } [MethodImpl(InliningOptions.ShortMethod)] @@ -567,7 +567,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v // LOG_2_RECIPROCAL ~ 23/16 correction = (int)((23 * (origV & (y - 1))) >> 4); - return (vF * (WebPLookupTables.Log2Table[v] + logCnt)) + correction; + return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; } else { @@ -591,7 +591,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } while (v >= LogLookupIdxMax); - double log2 = WebPLookupTables.Log2Table[v] + logCnt; + double log2 = WebpLookupTables.Log2Table[v] + logCnt; if (origV >= ApproxLogMax) { // Since the division is still expensive, add this correction factor only @@ -615,7 +615,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) { - int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance); + int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; extraBits = highestBit - 1; var code = (2 * highestBit) + secondHighestBit; @@ -624,7 +624,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private static int PrefixEncodeNoLUT(int distance, ref int extraBits, ref int extraBitsValue) { - int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance); + int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; extraBits = highestBit - 1; extraBitsValue = distance & ((1 << extraBits) - 1); @@ -637,7 +637,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { for (int x = 0; x < numberOfPixels; ++x) { - output[x] = AddPixels(input[x], WebPConstants.ArgbBlack); + output[x] = AddPixels(input[x], WebpConstants.ArgbBlack); } } @@ -848,7 +848,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { for (int i = 0; i < numPixels; ++i) { - output[i] = SubPixels(input[i], WebPConstants.ArgbBlack); + output[i] = SubPixels(input[i], WebpConstants.ArgbBlack); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 209a22015..fea729048 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] internal class PixOrCopy diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs index f2a47794d..1a93ef6cc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal enum PixOrCopyMode { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 41a62befe..b41a372fc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Image transform methods for the lossless webp encoder. @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless usedSubtractGreen, image); - image[(tileY * tilesPerRow) + tileX] = (uint)(WebPConstants.ArgbBlack | (pred << 8)); + image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); } } @@ -199,7 +199,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless Span maxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); float bestDiff = MaxDiffCost; int bestMode = 0; - var residuals = new uint[1 << WebPConstants.MaxTransformBits]; + var residuals = new uint[1 << WebpConstants.MaxTransformBits]; var histoArgb = new int[4][]; var bestHisto = new int[4][]; for (int i = 0; i < 4; i++) @@ -329,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless uint residual; if (y == 0) { - predict = (x == 0) ? WebPConstants.ArgbBlack : currentRow[x - 1]; // Left. + predict = (x == 0) ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. } else if (x == 0) { @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless switch (mode) { case 0: - predict = WebPConstants.ArgbBlack; + predict = WebpConstants.ArgbBlack; break; case 1: predict = currentRow[x - 1]; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index 2c0166ae8..2182e6d81 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class Vp8LBackwardRefs { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index 21707fbf0..c11602c72 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Holds bit entropy results and entropy-related functions. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index 6d0330deb..da9cb9078 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Holds information for decoding a lossless webp image. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 9782d8ab7..5b7587471 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -8,11 +8,11 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Encoder for lossless webp images. @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.method = Numerics.Clamp(method, 0, 6); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); - this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); + this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; this.HashChain = new Vp8LHashChain(pixelCount); this.memoryAllocator = memoryAllocator; @@ -195,14 +195,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// The input image height. private void WriteImageSize(int inputImgWidth, int inputImgHeight) { - Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); - Guard.MustBeLessThan(inputImgHeight, WebPConstants.MaxDimension, nameof(inputImgHeight)); + Guard.MustBeLessThan(inputImgWidth, WebpConstants.MaxDimension, nameof(inputImgWidth)); + Guard.MustBeLessThan(inputImgHeight, WebpConstants.MaxDimension, nameof(inputImgHeight)); uint width = (uint)inputImgWidth - 1; uint height = (uint)inputImgHeight - 1; - this.bitWriter.PutBits(width, WebPConstants.Vp8LImageSizeBits); - this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); + this.bitWriter.PutBits(width, WebpConstants.Vp8LImageSizeBits); + this.bitWriter.PutBits(height, WebpConstants.Vp8LImageSizeBits); } /// @@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private void WriteAlphaAndVersion(bool hasAlpha) { this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); - this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); + this.bitWriter.PutBits(WebpConstants.Vp8LVersion, WebpConstants.Vp8LVersionBits); } /// @@ -270,9 +270,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.MapImageFromPalette(width, height); // If using a color cache, do not have it bigger than the number of colors. - if (useCache && this.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) + if (useCache && this.PaletteSize < (1 << WebpConstants.MaxColorCacheBits)) { - this.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; + this.CacheBits = WebpCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; } } @@ -399,7 +399,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); var histogramSymbols = new ushort[histogramImageXySize]; - var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; + var huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { huffTree[i] = default; @@ -410,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless if (cacheBits == 0) { // TODO: not sure if this should be 10 or 11. Original code comment says "The maximum allowed limit is 11.", but the value itself is 10. - cacheBits = WebPConstants.MaxColorCacheBits; + cacheBits = WebpConstants.MaxColorCacheBits; } } else @@ -543,10 +543,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// private void EncodePalette() { - Span tmpPalette = new uint[WebPConstants.MaxPaletteSize]; + Span tmpPalette = new uint[WebpConstants.MaxPaletteSize]; int paletteSize = this.PaletteSize; Span palette = this.Palette.Memory.Span; - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); this.bitWriter.PutBits((uint)paletteSize - 1, 8); for (int i = paletteSize - 1; i >= 1; i--) @@ -565,7 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless /// The height of the image. private void ApplySubtractGreen(int width, int height) { - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan(), width * height); } @@ -580,7 +580,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless PredictorEncoder.ResidualImage(width, height, predBits, this.Bgra.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); this.bitWriter.PutBits((uint)(predBits - 2), 3); @@ -595,7 +595,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); @@ -613,7 +613,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless huffmanCodes[i] = default; } - var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; + var huffTree = new HuffmanTree[3UL * WebpConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { huffTree[i] = default; @@ -732,19 +732,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) { int i; - var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes]; - var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes]; + var codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; + var codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; var huffmanCode = new HuffmanTreeCode { - NumSymbols = WebPConstants.CodeLengthCodes, + NumSymbols = WebpConstants.CodeLengthCodes, CodeLengths = codeLengthBitDepth, Codes = codeLengthBitDepthSymbols }; this.bitWriter.PutBits(0, 1); var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - var histogram = new uint[WebPConstants.CodeLengthCodes + 1]; - var bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; + var histogram = new uint[WebpConstants.CodeLengthCodes + 1]; + var bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; for (i = 0; i < numTokens; i++) { histogram[tokens[i].Code]++; @@ -790,7 +790,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else { - int nBits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nBits = WebpCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); int nBitPairs = (nBits / 2) + 1; this.bitWriter.PutBits((uint)nBitPairs - 1, 3); this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); @@ -830,7 +830,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; // Throw away trailing zeros: - int codesToStore = WebPConstants.CodeLengthCodes; + int codesToStore = WebpConstants.CodeLengthCodes; for (; codesToStore > 4; codesToStore--) { if (codeLengthBitDepth[storageOrder[codesToStore - 1]] != 0) @@ -882,7 +882,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless else if (v.IsCacheIdx()) { int code = (int)v.CacheIdx(); - int literalIx = 256 + WebPConstants.NumLengthCodes + code; + int literalIx = 256 + WebpConstants.NumLengthCodes + code; this.bitWriter.WriteHuffmanCode(codes[0], literalIx); } else @@ -1084,7 +1084,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { Span palette = this.Palette.Memory.Span; this.PaletteSize = this.GetColorPalette(image, palette); - if (this.PaletteSize > WebPConstants.MaxPaletteSize) + if (this.PaletteSize > WebpConstants.MaxPaletteSize) { this.PaletteSize = 0; return false; @@ -1119,10 +1119,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless for (int x = 0; x < rowSpan.Length; x++) { colors.Add(rowSpan[x]); - if (colors.Count > WebPConstants.MaxPaletteSize) + if (colors.Count > WebpConstants.MaxPaletteSize) { // Exact count is not needed, because a palette will not be used then anyway. - return WebPConstants.MaxPaletteSize + 1; + return WebpConstants.MaxPaletteSize + 1; } } } @@ -1464,7 +1464,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { int numSymbols = (k == 0) ? histo.NumCodes() : - (k == 4) ? WebPConstants.NumDistanceCodes : 256; + (k == 4) ? WebpConstants.NumDistanceCodes : 256; huffmanCodes[startIdx + k].NumSymbols = numSymbols; totalLengthSize += numSymbols; } @@ -1532,7 +1532,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless while (true) { int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); - if (huffImageSize <= WebPConstants.MaxHuffImageSize) + if (huffImageSize <= WebpConstants.MaxHuffImageSize) { break; } @@ -1540,8 +1540,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless histoBits++; } - return (histoBits < WebPConstants.MinHuffmanBits) ? WebPConstants.MinHuffmanBits : - (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; + return (histoBits < WebpConstants.MinHuffmanBits) ? WebpConstants.MinHuffmanBits : + (histoBits > WebpConstants.MaxHuffmanBits) ? WebpConstants.MaxHuffmanBits : histoBits; } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 994731b88..d99a26b01 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class Vp8LHashChain { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index ebc1e1175..31c293800 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class Vp8LHistogram : IDeepCloneable { @@ -66,12 +66,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless public Vp8LHistogram(int paletteCodeBits) { this.PaletteCodeBits = paletteCodeBits; - this.Red = new uint[WebPConstants.NumLiteralCodes + 1]; - this.Blue = new uint[WebPConstants.NumLiteralCodes + 1]; - this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1]; - this.Distance = new uint[WebPConstants.NumDistanceCodes]; + this.Red = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Blue = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Alpha = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Distance = new uint[WebpConstants.NumDistanceCodes]; - var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + (1 << WebPConstants.MaxColorCacheBits); + var literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); this.Literal = new uint[literalSize + 1]; // 5 for literal, red, blue, alpha, distance. @@ -150,14 +150,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else if (v.IsCacheIdx()) { - int literalIx = (int)(WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + v.CacheIdx()); + int literalIx = (int)(WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + v.CacheIdx()); this.Literal[literalIx]++; } else { int extraBits = 0; int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); - this.Literal[WebPConstants.NumLiteralCodes + code]++; + this.Literal[WebpConstants.NumLiteralCodes + code]++; if (!useDistanceModifier) { code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); @@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless public int NumCodes() { - return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + return WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); } /// @@ -185,24 +185,24 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless uint notUsed = 0; return PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0]) - + PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1]) - + PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2]) - + PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3]) - + PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) - + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes) - + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); + + PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1]) + + PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2]) + + PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3]) + + PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes) + + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); } public void UpdateHistogramCost() { uint alphaSym = 0, redSym = 0, blueSym = 0; uint notUsed = 0; - double alphaCost = PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]); - double distanceCost = PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); + double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]); + double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); int numCodes = this.NumCodes(); - this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); - this.RedCost = PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]); - this.BlueCost = PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]); + this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); + this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]); + this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]); this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; if ((alphaSym | redSym | blueSym) == NonTrivialSym) { @@ -247,10 +247,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int literalSize = this.NumCodes(); this.AddLiteral(b, output, literalSize); - this.AddRed(b, output, WebPConstants.NumLiteralCodes); - this.AddBlue(b, output, WebPConstants.NumLiteralCodes); - this.AddAlpha(b, output, WebPConstants.NumLiteralCodes); - this.AddDistance(b, output, WebPConstants.NumDistanceCodes); + this.AddRed(b, output, WebpConstants.NumLiteralCodes); + this.AddBlue(b, output, WebpConstants.NumLiteralCodes); + this.AddAlpha(b, output, WebpConstants.NumLiteralCodes); + this.AddDistance(b, output, WebpConstants.NumDistanceCodes); for (int i = 0; i < 5; i++) { @@ -269,7 +269,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false); - cost += ExtraCostCombined(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); + cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); if (cost > costThreshold) { @@ -290,31 +290,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } } - cost += GetCombinedEntropy(this.Red, b.Red, WebPConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd); + cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Blue, b.Blue, WebPConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd); + cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebPConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd); + cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Distance, b.Distance, WebPConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false); + cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false); if (cost > costThreshold) { return false; } - cost += ExtraCostCombined(this.Distance, b.Distance, WebPConstants.NumDistanceCodes); + cost += ExtraCostCombined(this.Distance, b.Distance, WebpConstants.NumDistanceCodes); if (cost > costThreshold) { return false; @@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { var output = new short[16]; - this.Vp8FTransform(reference.Slice(WebPLookupTables.Vp8DspScan[j]), pred.Slice(WebPLookupTables.Vp8DspScan[j]), output); + this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output); // Convert coefficients to bin. for (int k = 0; k < 16; ++k) @@ -352,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // for handling the useful small values which contribute most. int maxValue = this.maxValue; int lastNonZero = this.lastNonZero; - int alpha = (maxValue > 1) ? WebPConstants.AlphaScale * lastNonZero / maxValue : 0; + int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; return alpha; } @@ -400,8 +400,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // Do not change the span in the last iteration. if (i < 3) { - src = src.Slice(WebPConstants.Bps); - reference = reference.Slice(WebPConstants.Bps); + src = src.Slice(WebpConstants.Bps); + reference = reference.Slice(WebpConstants.Bps); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs index e582f4b03..34fca4018 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal enum Vp8LLz77Type { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index 4aae397ee..4c776c640 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class Vp8LMetadata { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs index d89a2b7c6..ff3d02705 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal struct Vp8LMultipliers { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index ad94db49a..9c3022e9f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { internal class Vp8LStreaks { @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private static double InitialHuffmanCost() { // Small bias because Huffman code length is typically not stored in full length. - int huffmanCodeOfHuffmanCodeSize = WebPConstants.CodeLengthCodes * 3; + int huffmanCodeOfHuffmanCodeSize = WebpConstants.CodeLengthCodes * 3; double smallBias = 9.1; return huffmanCodeOfHuffmanCodeSize - smallBias; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index 9b2dcd153..3bfa94525 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -4,7 +4,7 @@ using System.Buffers; using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Data associated with a VP8L transformation to reduce the entropy. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index 694c0072e..8dc2af6dd 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Enum for the different transform types. Transformations are reversible manipulations of the image data diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index d396d3746..79fbf4bbc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -8,11 +8,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless private const uint PackedNonLiteralCode = 0; - private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length; + private static readonly int CodeToPlaneCodes = WebpLookupTables.CodeToPlane.Length; // Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha and distance alphabets are constant (256 for red, blue and alpha, 40 for // distance) and lookup table sizes for them in worst case are 630 and 410 respectively. Size of green alphabet depends on color cache size and is equal @@ -114,14 +114,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int numberOfTransformsPresent = 0; if (isLevel0) { - decoder.Transforms = new List(WebPConstants.MaxNumberOfTransforms); + decoder.Transforms = new List(WebpConstants.MaxNumberOfTransforms); // Next bit indicates, if a transformation is present. while (this.bitReader.ReadBit()) { - if (numberOfTransformsPresent > WebPConstants.MaxNumberOfTransforms) + if (numberOfTransformsPresent > WebpConstants.MaxNumberOfTransforms) { - WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); + WebpThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebpConstants.MaxNumberOfTransforms} was exceeded"); } this.ReadTransformation(transformXSize, transformYSize, decoder); @@ -148,10 +148,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. // That is why 11 bits is also considered valid here. - bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= (WebPConstants.MaxColorCacheBits + 1); + bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= (WebpConstants.MaxColorCacheBits + 1); if (!colorCacheBitsIsValid) { - WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); + WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } } @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int height = decoder.Height; int row = lastPixel / width; int col = lastPixel % width; - const int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; int colorCacheSize = decoder.Metadata.ColorCacheSize; ColorCache colorCache = decoder.Metadata.ColorCache; int colorCacheLimit = lenCodeLimit + colorCacheSize; @@ -267,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } // Literal - if (code < WebPConstants.NumLiteralCodes) + if (code < WebpConstants.NumLiteralCodes) { if (hTreeGroup[0].IsTrivialLiteral) { @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless else if (code < lenCodeLimit) { // Backward reference is used. - int lengthSym = code - WebPConstants.NumLiteralCodes; + int lengthSym = code - WebpConstants.NumLiteralCodes; int length = this.GetCopyLength(lengthSym); uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else { - WebPThrowHelper.ThrowImageFormatException("Webp parsing error"); + WebpThrowHelper.ThrowImageFormatException("Webp parsing error"); } } } @@ -403,9 +403,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } // Find maximum alphabet size for the hTree group. - for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) + for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.AlphabetSize[j]; + int alphabetSize = WebpConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -429,9 +429,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless bool isTrivialLiteral = true; int maxBits = 0; var codeLengths = new int[maxAlphabetSize]; - for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) + for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.AlphabetSize[j]; + int alphabetSize = WebpConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -440,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); if (size == 0) { - WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + WebpThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } // TODO: Avoid allocation. @@ -481,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; - if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) + if (totalSize == 0 && green < WebpConstants.NumLiteralCodes) { hTreeGroup.IsTrivialCode = true; hTreeGroup.LiteralArb |= green << 8; @@ -538,7 +538,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless uint numCodes = this.bitReader.ReadValue(4) + 4; if (numCodes > NumCodeLengthCodes) { - WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); + WebpThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); } for (int i = 0; i < numCodes; i++) @@ -558,11 +558,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { int maxSymbol; int symbol = 0; - int prevCodeLen = WebPConstants.DefaultCodeLength; - int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); + int prevCodeLen = WebpConstants.DefaultCodeLength; + int size = HuffmanUtils.BuildHuffmanTable(table, WebpConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); if (size == 0) { - WebPThrowHelper.ThrowImageFormatException("Error building huffman table"); + WebpThrowHelper.ThrowImageFormatException("Error building huffman table"); } if (this.bitReader.ReadBit()) @@ -588,7 +588,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); uint codeLen = huffmanCode.Value; - if (codeLen < WebPConstants.CodeLengthLiterals) + if (codeLen < WebpConstants.CodeLengthLiterals) { codeLengths[symbol++] = (int)codeLen; if (codeLen != 0) @@ -598,10 +598,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else { - bool usePrev = codeLen == WebPConstants.CodeLengthRepeatCode; - uint slot = codeLen - WebPConstants.CodeLengthLiterals; - int extraBits = WebPConstants.CodeLengthExtraBits[slot]; - int repeatOffset = WebPConstants.CodeLengthRepeatOffsets[slot]; + bool usePrev = codeLen == WebpConstants.CodeLengthRepeatCode; + uint slot = codeLen - WebpConstants.CodeLengthLiterals; + int extraBits = WebpConstants.CodeLengthExtraBits[slot]; + int repeatOffset = WebpConstants.CodeLengthRepeatOffsets[slot]; int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { @@ -633,7 +633,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { if (decoderTransform.TransformType == transform.TransformType) { - WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); + WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); } } @@ -732,7 +732,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int end = width * height; // End of data. int last = end; // Last pixel to decode. int lastRow = height; - const int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; int mask = hdr.HuffmanMask; HTreeGroup[] htreeGroup = (pos < last) ? GetHTreeGroupForPos(hdr, col, row) : null; while (!this.bitReader.Eos && pos < last) @@ -745,7 +745,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.bitReader.FillBitWindow(); int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); - if (code < WebPConstants.NumLiteralCodes) + if (code < WebpConstants.NumLiteralCodes) { // Literal data[pos] = (byte)code; @@ -756,7 +756,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { col = 0; ++row; - if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows == 0)) + if (row <= lastRow && (row % WebpConstants.NumArgbCacheRows == 0)) { dec.ExtractPalettedAlphaRows(row); } @@ -765,7 +765,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless else if (code < lenCodeLimit) { // Backward reference - int lengthSym = code - WebPConstants.NumLiteralCodes; + int lengthSym = code - WebpConstants.NumLiteralCodes; int length = this.GetCopyLength(lengthSym); int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); @@ -777,7 +777,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else { - WebPThrowHelper.ThrowImageFormatException("error while decoding alpha data"); + WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); } pos += length; @@ -786,7 +786,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless { col -= width; ++row; - if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows == 0)) + if (row <= lastRow && (row % WebpConstants.NumArgbCacheRows == 0)) { dec.ExtractPalettedAlphaRows(row); } @@ -799,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless } else { - WebPThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); + WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); } this.bitReader.Eos = this.bitReader.IsEndOfStream(); @@ -841,7 +841,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless uint bits = code; HuffmanCode huff = hTreeGroup.PackedTable[bits]; HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; - if (hCode.Value >= WebPConstants.NumLiteralCodes) + if (hCode.Value >= WebpConstants.NumLiteralCodes) { huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; huff.Value = hCode.Value; @@ -926,7 +926,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless return planeCode - CodeToPlaneCodes; } - int distCode = WebPLookupTables.CodeToPlane[planeCode - 1]; + int distCode = WebpLookupTables.CodeToPlane[planeCode - 1]; int yOffset = distCode >> 4; int xOffset = 8 - (distCode & 0xf); int dist = (yOffset * xSize) + xOffset; @@ -948,7 +948,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int start = decodedPixels - dist; if (start < 0) { - WebPThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); + WebpThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); } if (dist >= length) diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index 7ac72c98e..231fc75f1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal enum IntraPredictionMode { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index 48047d78a..35231a913 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Enum for the different loop filters used. VP8 supports two types of loop filters. diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index d9ff33ec0..fcfce918a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -6,19 +6,19 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal static class LossyUtils { public static void DC16(Span dst, Span yuv, int offset) { int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebPConstants.Bps; + int offsetMinusBps = offset - WebpConstants.Bps; int dc = 16; for (int j = 0; j < 16; ++j) { // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[offsetMinus1 + (j * WebPConstants.Bps)] + yuv[offsetMinusBps + j]; + dc += yuv[offsetMinus1 + (j * WebpConstants.Bps)] + yuv[offsetMinusBps + j]; } Put16(dc >> 5, dst); @@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void VE16(Span dst, Span yuv, int offset) { // vertical - Span src = yuv.Slice(offset - WebPConstants.Bps, 16); + Span src = yuv.Slice(offset - WebpConstants.Bps, 16); for (int j = 0; j < 16; ++j) { // memcpy(dst + j * BPS, dst - BPS, 16); - src.CopyTo(dst.Slice(j * WebPConstants.Bps)); + src.CopyTo(dst.Slice(j * WebpConstants.Bps)); } } @@ -50,8 +50,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // memset(dst, dst[-1], 16); byte v = yuv[offset]; Memset(dst, v, 0, 16); - offset += WebPConstants.Bps; - dst = dst.Slice(WebPConstants.Bps); + offset += WebpConstants.Bps; + dst = dst.Slice(WebpConstants.Bps); } } @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (int j = 0; j < 16; ++j) { // DC += dst[-1 + j * BPS]; - dc += yuv[-1 + (j * WebPConstants.Bps) + offset]; + dc += yuv[-1 + (j * WebpConstants.Bps) + offset]; } Put16(dc >> 4, dst); @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (int i = 0; i < 16; ++i) { // DC += dst[i - BPS]; - dc += yuv[i - WebPConstants.Bps + offset]; + dc += yuv[i - WebpConstants.Bps + offset]; } Put16(dc >> 4, dst); @@ -92,11 +92,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { int dc0 = 8; int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebPConstants.Bps; + int offsetMinusBps = offset - WebpConstants.Bps; for (int i = 0; i < 8; ++i) { // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; - dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebPConstants.Bps)]; + dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebpConstants.Bps)]; } Put8x8uv((byte)(dc0 >> 4), dst); @@ -112,10 +112,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void VE8uv(Span dst, Span yuv, int offset) { // vertical - Span src = yuv.Slice(offset - WebPConstants.Bps, 8); + Span src = yuv.Slice(offset - WebpConstants.Bps, 8); - int endIdx = 8 * WebPConstants.Bps; - for (int j = 0; j < endIdx; j += WebPConstants.Bps) + int endIdx = 8 * WebpConstants.Bps; + for (int j = 0; j < endIdx; j += WebpConstants.Bps) { // memcpy(dst + j * BPS, dst - BPS, 8); src.CopyTo(dst.Slice(j)); @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // dst += BPS; byte v = yuv[offset]; Memset(dst, v, 0, 8); - dst = dst.Slice(WebPConstants.Bps); - offset += WebPConstants.Bps; + dst = dst.Slice(WebpConstants.Bps); + offset += WebpConstants.Bps; } } @@ -142,8 +142,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // DC with no top samples. int dc0 = 4; int offsetMinusOne = offset - 1; - int endIdx = 8 * WebPConstants.Bps; - for (int i = 0; i < endIdx; i += WebPConstants.Bps) + int endIdx = 8 * WebpConstants.Bps; + for (int i = 0; i < endIdx; i += WebpConstants.Bps) { // dc0 += dst[-1 + i * BPS]; dc0 += yuv[offsetMinusOne + i]; @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void DC8uvNoLeft(Span dst, Span yuv, int offset) { // DC with no left samples. - int offsetMinusBps = offset - WebPConstants.Bps; + int offsetMinusBps = offset - WebpConstants.Bps; int dc0 = 4; for (int i = 0; i < 8; ++i) { @@ -176,16 +176,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void DC4(Span dst, Span yuv, int offset) { int dc = 4; - int offsetMinusBps = offset - WebPConstants.Bps; + int offsetMinusBps = offset - WebpConstants.Bps; int offsetMinusOne = offset - 1; for (int i = 0; i < 4; ++i) { - dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebPConstants.Bps)]; + dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebpConstants.Bps)]; } dc >>= 3; - int endIndx = 4 * WebPConstants.Bps; - for (int i = 0; i < endIndx; i += WebPConstants.Bps) + int endIndx = 4 * WebpConstants.Bps; + for (int i = 0; i < endIndx; i += WebpConstants.Bps) { Memset(dst, (byte)dc, i, 4); } @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void VE4(Span dst, Span yuv, int offset) { // vertical - int topOffset = offset - WebPConstants.Bps; + int topOffset = offset - WebpConstants.Bps; byte[] vals = { Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]), @@ -209,8 +209,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]) }; - int endIdx = 4 * WebPConstants.Bps; - for (int i = 0; i < endIdx; i += WebPConstants.Bps) + int endIdx = 4 * WebpConstants.Bps; + for (int i = 0; i < endIdx; i += WebpConstants.Bps) { vals.CopyTo(dst.Slice(i)); } @@ -220,19 +220,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { // horizontal int offsetMinusOne = offset - 1; - byte a = yuv[offsetMinusOne - WebPConstants.Bps]; + byte a = yuv[offsetMinusOne - WebpConstants.Bps]; byte b = yuv[offsetMinusOne]; - byte c = yuv[offsetMinusOne + WebPConstants.Bps]; - byte d = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; - byte e = yuv[offsetMinusOne + (3 * WebPConstants.Bps)]; + byte c = yuv[offsetMinusOne + WebpConstants.Bps]; + byte d = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte e = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; uint val = 0x01010101U * Avg3(a, b, c); BinaryPrimitives.WriteUInt32BigEndian(dst, val); val = 0x01010101U * Avg3(b, c, d); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebpConstants.Bps), val); val = 0x01010101U * Avg3(c, d, e); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebpConstants.Bps), val); val = 0x01010101U * Avg3(d, e, e); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebpConstants.Bps), val); } public static void RD4(Span dst, Span yuv, int offset) @@ -240,14 +240,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Down-right int offsetMinusOne = offset - 1; byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebPConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; - byte l = yuv[offsetMinusOne + (3 * WebPConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebPConstants.Bps]; - byte a = yuv[offset - WebPConstants.Bps]; - byte b = yuv[offset + 1 - WebPConstants.Bps]; - byte c = yuv[offset + 2 - WebPConstants.Bps]; - byte d = yuv[offset + 3 - WebPConstants.Bps]; + byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte l = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; Dst(dst, 0, 3, Avg3(j, k, l)); byte ijk = Avg3(i, j, k); @@ -277,13 +277,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Vertical-Right int offsetMinusOne = offset - 1; byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebPConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebPConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebPConstants.Bps]; - byte a = yuv[offset - WebPConstants.Bps]; - byte b = yuv[offset + 1 - WebPConstants.Bps]; - byte c = yuv[offset + 2 - WebPConstants.Bps]; - byte d = yuv[offset + 3 - WebPConstants.Bps]; + byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; byte xa = Avg2(x, a); Dst(dst, 0, 0, xa); @@ -312,14 +312,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void LD4(Span dst, Span yuv, int offset) { // Down-Left - byte a = yuv[offset - WebPConstants.Bps]; - byte b = yuv[offset + 1 - WebPConstants.Bps]; - byte c = yuv[offset + 2 - WebPConstants.Bps]; - byte d = yuv[offset + 3 - WebPConstants.Bps]; - byte e = yuv[offset + 4 - WebPConstants.Bps]; - byte f = yuv[offset + 5 - WebPConstants.Bps]; - byte g = yuv[offset + 6 - WebPConstants.Bps]; - byte h = yuv[offset + 7 - WebPConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + byte e = yuv[offset + 4 - WebpConstants.Bps]; + byte f = yuv[offset + 5 - WebpConstants.Bps]; + byte g = yuv[offset + 6 - WebpConstants.Bps]; + byte h = yuv[offset + 7 - WebpConstants.Bps]; Dst(dst, 0, 0, Avg3(a, b, c)); byte bcd = Avg3(b, c, d); @@ -347,14 +347,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void VL4(Span dst, Span yuv, int offset) { // Vertical-Left - byte a = yuv[offset - WebPConstants.Bps]; - byte b = yuv[offset + 1 - WebPConstants.Bps]; - byte c = yuv[offset + 2 - WebPConstants.Bps]; - byte d = yuv[offset + 3 - WebPConstants.Bps]; - byte e = yuv[offset + 4 - WebPConstants.Bps]; - byte f = yuv[offset + 5 - WebPConstants.Bps]; - byte g = yuv[offset + 6 - WebPConstants.Bps]; - byte h = yuv[offset + 7 - WebPConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + byte e = yuv[offset + 4 - WebpConstants.Bps]; + byte f = yuv[offset + 5 - WebpConstants.Bps]; + byte g = yuv[offset + 6 - WebpConstants.Bps]; + byte h = yuv[offset + 7 - WebpConstants.Bps]; Dst(dst, 0, 0, Avg2(a, b)); byte bc = Avg2(b, c); @@ -384,13 +384,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { // Horizontal-Down byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; - byte x = yuv[offset - 1 - WebPConstants.Bps]; - byte a = yuv[offset - WebPConstants.Bps]; - byte b = yuv[offset + 1 - WebPConstants.Bps]; - byte c = yuv[offset + 2 - WebPConstants.Bps]; + byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; + byte x = yuv[offset - 1 - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; byte ix = Avg2(i, x); Dst(dst, 0, 0, ix); @@ -420,9 +420,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { // Horizontal-Up byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; + byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; Dst(dst, 0, 0, Avg2(i, j)); byte jk = Avg2(j, k); @@ -532,7 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Store(dst, 2, 0, b - c); Store(dst, 3, 0, a - d); tmpOffset++; - dst = dst.Slice(WebPConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); } } @@ -565,7 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void TransformUv(Span src, Span dst) { TransformTwo(src.Slice(0 * 16), dst); - TransformTwo(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); + TransformTwo(src.Slice(2 * 16), dst.Slice(4 * WebpConstants.Bps)); } public static void TransformDcuv(Span src, Span dst) @@ -582,12 +582,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy if (src[2 * 16] != 0) { - TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); + TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebpConstants.Bps)); } if (src[3 * 16] != 0) { - TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebPConstants.Bps) + 4)); + TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebpConstants.Bps) + 4)); } } @@ -745,7 +745,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void Dst(Span dst, int x, int y, byte v) { - dst[x + (y * WebPConstants.Bps)] = v; + dst[x + (y * WebpConstants.Bps)] = v; } [MethodImpl(InliningOptions.ShortMethod)] @@ -757,7 +757,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Cost of coding one event with probability 'proba'. public static int Vp8BitCost(int bit, byte proba) { - return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; + return bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; } [MethodImpl(InliningOptions.ShortMethod)] @@ -765,14 +765,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int j = 0; j < 16; ++j) { - Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16); + Memset(dst.Slice(j * WebpConstants.Bps), (byte)v, 0, 16); } } private static void TrueMotion(Span dst, Span yuv, int offset, int size) { // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. - int topOffset = offset - WebPConstants.Bps; + int topOffset = offset - WebpConstants.Bps; Span top = yuv.Slice(topOffset); byte p = yuv[topOffset - 1]; int leftOffset = offset - 1; @@ -784,9 +784,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy dst[x] = (byte)Clamp255(left + top[x] - p); } - leftOffset += WebPConstants.Bps; + leftOffset += WebpConstants.Bps; left = yuv[leftOffset]; - dst = dst.Slice(WebPConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); } } @@ -856,11 +856,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - int a = (3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]; - int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; - int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; - p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; - p[offset] = WebPLookupTables.Clip1[q0 - a1]; + int a = (3 * (q0 - p0)) + WebpLookupTables.Sclip1[p1 - q1]; + int a1 = WebpLookupTables.Sclip2[(a + 4) >> 3]; + int a2 = WebpLookupTables.Sclip2[(a + 3) >> 3]; + p[offset - step] = WebpLookupTables.Clip1[p0 + a2]; + p[offset] = WebpLookupTables.Clip1[q0 - a1]; } private static void DoFilter4(Span p, int offset, int step) @@ -872,13 +872,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int q0 = p[offset]; int q1 = p[offset + step]; int a = 3 * (q0 - p0); - int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; - int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; + int a1 = WebpLookupTables.Sclip2[(a + 4) >> 3]; + int a2 = WebpLookupTables.Sclip2[(a + 3) >> 3]; int a3 = (a1 + 1) >> 1; - p[offsetMinus2Step] = WebPLookupTables.Clip1[p1 + a3]; - p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; - p[offset] = WebPLookupTables.Clip1[q0 - a1]; - p[offset + step] = WebPLookupTables.Clip1[q1 - a3]; + p[offsetMinus2Step] = WebpLookupTables.Clip1[p1 + a3]; + p[offset - step] = WebpLookupTables.Clip1[p0 + a2]; + p[offset] = WebpLookupTables.Clip1[q0 - a1]; + p[offset + step] = WebpLookupTables.Clip1[q1 - a3]; } private static void DoFilter6(Span p, int offset, int step) @@ -893,18 +893,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int q0 = p[offset]; int q1 = p[offset + step]; int q2 = p[offset + step2]; - int a = WebPLookupTables.Sclip1[(3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]]; + int a = WebpLookupTables.Sclip1[(3 * (q0 - p0)) + WebpLookupTables.Sclip1[p1 - q1]]; // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - step3] = WebPLookupTables.Clip1[p2 + a3]; - p[offset - step2] = WebPLookupTables.Clip1[p1 + a2]; - p[offsetMinusStep] = WebPLookupTables.Clip1[p0 + a1]; - p[offset] = WebPLookupTables.Clip1[q0 - a1]; - p[offset + step] = WebPLookupTables.Clip1[q1 - a2]; - p[offset + step2] = WebPLookupTables.Clip1[q2 - a3]; + p[offset - step3] = WebpLookupTables.Clip1[p2 + a3]; + p[offset - step2] = WebpLookupTables.Clip1[p1 + a2]; + p[offsetMinusStep] = WebpLookupTables.Clip1[p0 + a1]; + p[offset] = WebpLookupTables.Clip1[q0 - a1]; + p[offset + step] = WebpLookupTables.Clip1[q1 - a2]; + p[offset + step2] = WebpLookupTables.Clip1[q2 - a3]; } [MethodImpl(InliningOptions.ShortMethod)] @@ -914,7 +914,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return ((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) <= t; + return ((4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1]) <= t; } private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) @@ -929,14 +929,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int q1 = p[offset + step]; int q2 = p[offset + step2]; int q3 = p[offset + step3]; - if (((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) > t) + if (((4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1]) > t) { return false; } - return WebPLookupTables.Abs0[p3 - p2] <= it && WebPLookupTables.Abs0[p2 - p1] <= it && - WebPLookupTables.Abs0[p1 - p0] <= it && WebPLookupTables.Abs0[q3 - q2] <= it && - WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it; + return WebpLookupTables.Abs0[p3 - p2] <= it && WebpLookupTables.Abs0[p2 - p1] <= it && + WebpLookupTables.Abs0[p1 - p0] <= it && WebpLookupTables.Abs0[q3 - q2] <= it && + WebpLookupTables.Abs0[q2 - q1] <= it && WebpLookupTables.Abs0[q1 - q0] <= it; } [MethodImpl(InliningOptions.ShortMethod)] @@ -946,7 +946,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (WebPLookupTables.Abs0[p1 - p0] > thresh) || (WebPLookupTables.Abs0[q1 - q0] > thresh); + return (WebpLookupTables.Abs0[p1 - p0] > thresh) || (WebpLookupTables.Abs0[q1 - q0] > thresh); } [MethodImpl(InliningOptions.ShortMethod)] @@ -958,7 +958,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, int x, int y, int v) { - var index = x + (y * WebPConstants.Bps); + var index = x + (y * WebpConstants.Bps); dst[index] = Clip8B(dst[index] + (v >> 3)); } @@ -993,8 +993,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static void Put8x8uv(byte value, Span dst) { - int end = 8 * WebPConstants.Bps; - for (int j = 0; j < end; j += WebPConstants.Bps) + int end = 8 * WebpConstants.Bps; + for (int j = 0; j < end; j += WebpConstants.Bps) { // memset(dst + j * BPS, value, 8); Memset(dst, value, j, 8); diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs index e041aa26d..115e87245 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Class for organizing convergence in either size or PSNR. diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index 32141283e..58d441e69 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Quantization methods. @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static int QuantDiv(uint n, uint iQ, uint b) { - return (int)(((n * iQ) + b) >> WebPConstants.QFix); + return (int)(((n * iQ) + b) >> WebpConstants.QFix); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs index 8e312a4b1..09424fb79 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// All the probabilities associated to one band. @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy /// public Vp8BandProbas() { - this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx]; - for (int i = 0; i < WebPConstants.NumCtx; i++) + this.Probabilities = new Vp8ProbaArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) { this.Probabilities[i] = new Vp8ProbaArray(); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs index 527b0d26e..c772b65c7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8CostArray { @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy /// public Vp8CostArray() { - this.Costs = new ushort[WebPConstants.NumCtx * (67 + 1)]; + this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)]; } public ushort[] Costs { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index c166632ad..070da84ae 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -3,10 +3,10 @@ using System; using System.Buffers; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Holds information for decoding a lossy webp image. @@ -49,9 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); - this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; - this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; - for (int i = 0; i < WebPConstants.NumMbSegments; i++) + this.DeQuantMatrices = new Vp8QuantMatrix[WebpConstants.NumMbSegments]; + this.FilterStrength = new Vp8FilterInfo[WebpConstants.NumMbSegments, 2]; + for (int i = 0; i < WebpConstants.NumMbSegments; i++) { this.DeQuantMatrices[i] = new Vp8QuantMatrix(); for (int j = 0; j < 2; j++) @@ -63,10 +63,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy uint width = pictureHeader.Width; uint height = pictureHeader.Height; - int extraRows = WebPConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter + int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = (extraRows / 2) * this.CacheUvStride; - this.YuvBuffer = memoryAllocator.Allocate((WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY); + this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; this.CacheU = memoryAllocator.Allocate(cacheUvSize); @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.CacheU.Memory.Span.Fill(205); this.CacheV.Memory.Span.Fill(205); - this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; + this.Vp8BitReaders = new Vp8BitReader[WebpConstants.MaxNumPartitions]; } /// @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } Vp8FilterHeader hdr = this.FilterHeader; - for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) { int baseLevel; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 8a5ee8d1a..8f8c7676d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Iterator structure to iterate through macroblocks, pointing to the @@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.uvTopIdx = 0; this.predsWidth = (4 * mbw) + 1; this.predIdx = this.predsWidth; - this.YuvIn = new byte[WebPConstants.Bps * 16]; - this.YuvOut = new byte[WebPConstants.Bps * 16]; - this.YuvOut2 = new byte[WebPConstants.Bps * 16]; - this.YuvP = new byte[(32 * WebPConstants.Bps) + (16 * WebPConstants.Bps) + (8 * WebPConstants.Bps)]; // I16+Chroma+I4 preds + this.YuvIn = new byte[WebpConstants.Bps * 16]; + this.YuvOut = new byte[WebpConstants.Bps * 16]; + this.YuvOut2 = new byte[WebpConstants.Bps * 16]; + this.YuvP = new byte[(32 * WebpConstants.Bps) + (16 * WebpConstants.Bps) + (8 * WebpConstants.Bps)]; // I16+Chroma+I4 preds this.YLeft = new byte[32]; this.UvLeft = new byte[32]; this.TopNz = new int[9]; @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy uint m2; for (k = 0; k < 16; k += 4) { - this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebPConstants.Bps)), dc.AsSpan(k)); + this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.AsSpan(k)); } for (m = 0, m2 = 0, k = 0; k < 16; ++k) @@ -466,7 +466,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int y = this.I4 >> 2; int left = (x == 0) ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; int top = (y == 0) ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; - return WebPLookupTables.Vp8FixedCostsI4[top, left]; + return WebpLookupTables.Vp8FixedCostsI4[top, left]; } public void SetIntraUvMode(int mode) @@ -526,13 +526,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // left for (int i = 0; i < 16; ++i) { - this.YLeft[i + 1] = ySrc[15 + (i * WebPConstants.Bps)]; + this.YLeft[i + 1] = ySrc[15 + (i * WebpConstants.Bps)]; } for (int i = 0; i < 8; ++i) { - this.UvLeft[i + 1] = uvSrc[7 + (i * WebPConstants.Bps)]; - this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebPConstants.Bps)]; + this.UvLeft[i + 1] = uvSrc[7 + (i * WebpConstants.Bps)]; + this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebpConstants.Bps)]; } // top-left (before 'top'!) @@ -544,14 +544,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy if (y < this.mbh - 1) { // top - ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); - uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); + ySrc.Slice(15 * WebpConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); + uvSrc.Slice(7 * WebpConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); } } public bool RotateI4(Span yuvOut) { - Span blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]); + Span blk = yuvOut.Slice(WebpLookupTables.Vp8Scan[this.I4]); Span top = this.I4Boundary.AsSpan(); int topOffset = this.I4BoundaryIdx; int i; @@ -559,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Update the cache with 7 fresh samples. for (i = 0; i <= 3; ++i) { - top[topOffset - 4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples. + top[topOffset - 4 + i] = blk[i + (3 * WebpConstants.Bps)]; // Store future top samples. } if ((this.I4 & 3) != 3) @@ -568,7 +568,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (i = 0; i <= 2; ++i) { // store future left samples - top[topOffset + i] = blk[3 + ((2 - i) * WebPConstants.Bps)]; + top[topOffset + i] = blk[3 + ((2 - i) * WebpConstants.Bps)]; } } else @@ -706,7 +706,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int x = 0; x < 4; ++x) { - avg += input[x + (y * WebPConstants.Bps)]; + avg += input[x + (y * WebpConstants.Bps)]; } } @@ -727,14 +727,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy dst.Slice(dstIdx, size - w).Fill(dst[dstIdx + w - 1]); } - dstIdx += WebPConstants.Bps; + dstIdx += WebpConstants.Bps; srcIdx += srcStride; } for (int i = h; i < size; ++i) { - dst.Slice(dstIdx - WebPConstants.Bps, size).CopyTo(dst); - dstIdx += WebPConstants.Bps; + dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst); + dstIdx += WebpConstants.Bps; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index abc101c56..a39919d7f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8EncProba { @@ -25,37 +25,37 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.Dirty = true; this.UseSkipProba = false; this.Segments = new byte[3]; - this.Coeffs = new Vp8BandProbas[WebPConstants.NumTypes][]; + this.Coeffs = new Vp8BandProbas[WebpConstants.NumTypes][]; for (int i = 0; i < this.Coeffs.Length; i++) { - this.Coeffs[i] = new Vp8BandProbas[WebPConstants.NumBands]; + this.Coeffs[i] = new Vp8BandProbas[WebpConstants.NumBands]; for (int j = 0; j < this.Coeffs[i].Length; j++) { this.Coeffs[i][j] = new Vp8BandProbas(); } } - this.Stats = new Vp8Stats[WebPConstants.NumTypes][]; + this.Stats = new Vp8Stats[WebpConstants.NumTypes][]; for (int i = 0; i < this.Coeffs.Length; i++) { - this.Stats[i] = new Vp8Stats[WebPConstants.NumBands]; + this.Stats[i] = new Vp8Stats[WebpConstants.NumBands]; for (int j = 0; j < this.Stats[i].Length; j++) { this.Stats[i][j] = new Vp8Stats(); } } - this.LevelCost = new Vp8CostArray[WebPConstants.NumTypes][]; + this.LevelCost = new Vp8CostArray[WebpConstants.NumTypes][]; for (int i = 0; i < this.LevelCost.Length; i++) { - this.LevelCost[i] = new Vp8CostArray[WebPConstants.NumBands]; + this.LevelCost[i] = new Vp8CostArray[WebpConstants.NumBands]; for (int j = 0; j < this.LevelCost[i].Length; j++) { this.LevelCost[i][j] = new Vp8CostArray(); } } - this.RemappedCosts = new Vp8CostArray[WebPConstants.NumTypes][]; + this.RemappedCosts = new Vp8CostArray[WebpConstants.NumTypes][]; for (int i = 0; i < this.RemappedCosts.Length; i++) { this.RemappedCosts[i] = new Vp8CostArray[16]; @@ -67,16 +67,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Initialize with default probabilities. this.Segments.AsSpan().Fill(255); - for (int t = 0; t < WebPConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int b = 0; b < WebPConstants.NumBands; ++b) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int c = 0; c < WebPConstants.NumCtx; ++c) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; - for (int p = 0; p < WebPConstants.NumProbas; ++p) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - dst.Probabilities[p] = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + dst.Probabilities[p] = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; } } } @@ -123,11 +123,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy return; // nothing to do. } - for (int ctype = 0; ctype < WebPConstants.NumTypes; ++ctype) + for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype) { - for (int band = 0; band < WebPConstants.NumBands; ++band) + for (int band = 0; band < WebpConstants.NumBands; ++band) { - for (int ctx = 0; ctx < WebPConstants.NumCtx; ++ctx) + for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; Span table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel); @@ -146,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (int n = 0; n < 16; ++n) { - for (int ctx = 0; ctx < WebPConstants.NumCtx; ++ctx) + for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { Span dst = this.RemappedCosts[ctype][n].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); - Span src = this.LevelCost[ctype][WebPConstants.Vp8EncBands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); + Span src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); src.CopyTo(dst); } } @@ -162,19 +162,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { bool hasChanged = false; int size = 0; - for (int t = 0; t < WebPConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int b = 0; b < WebPConstants.NumBands; ++b) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int c = 0; c < WebPConstants.NumCtx; ++c) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int p = 0; p < WebPConstants.NumProbas; ++p) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { var stats = this.Stats[t][b].Stats[c].Stats[p]; int nb = (int)((stats >> 0) & 0xffff); int total = (int)((stats >> 16) & 0xffff); - int updateProba = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; - int oldP = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; + int oldP = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; int newP = CalcTokenProba(nb, total); int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba); int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256); @@ -219,13 +219,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public void ResetTokenStats() { - for (int t = 0; t < WebPConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int b = 0; b < WebPConstants.NumBands; ++b) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int c = 0; c < WebPConstants.NumCtx; ++c) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int p = 0; p < WebPConstants.NumProbas; ++p) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { this.Stats[t][b].Stats[c].Stats[p] = 0; } @@ -241,8 +241,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private static int VariableLevelCost(int level, Span probas) { - int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0]; - int bits = WebPLookupTables.Vp8LevelCodes[level - 1][1]; + int pattern = WebpLookupTables.Vp8LevelCodes[level - 1][0]; + int bits = WebpLookupTables.Vp8LevelCodes[level - 1][1]; int cost = 0; for (int i = 2; pattern != 0; ++i) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs index 0765efa78..37acc565e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8EncSegmentHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 817b760d1..333b845bc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitWriter; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Encoder for lossy webp images. @@ -115,9 +115,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Convergence is considered reached if dq < DqLimit private const float DqLimit = 0.4f; - private const ulong Partition0SizeLimit = (WebPConstants.Vp8MaxPartition0Size - 2048UL) << 11; + private const ulong Partition0SizeLimit = (WebpConstants.Vp8MaxPartition0Size - 2048UL) << 11; - private const long HeaderSizeEstimate = WebPConstants.RiffHeaderSize + WebPConstants.ChunkHeaderSize + WebPConstants.Vp8FrameHeaderSize; + private const long HeaderSizeEstimate = WebpConstants.RiffHeaderSize + WebpConstants.ChunkHeaderSize + WebpConstants.Vp8FrameHeaderSize; private const int QMin = 0; @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int uvStride = (yStride + 1) >> 1; var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.mbw, this.mbh); - var alphas = new int[WebPConstants.MaxAlpha + 1]; + var alphas = new int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); int totalMb = this.mbw * this.mbw; this.alpha = this.alpha / totalMb; @@ -550,7 +550,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy if (FilterStrength > 0) { int maxLevel = 0; - for (int s = 0; s < WebPConstants.NumMbSegments; s++) + for (int s = 0; s < WebpConstants.NumMbSegments; s++) { Vp8SegmentInfo dqm = this.SegmentInfos[s]; @@ -600,18 +600,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int nb = (this.segmentHeader.NumSegments < NumMbSegments) ? this.segmentHeader.NumSegments : NumMbSegments; var centers = new int[NumMbSegments]; int weightedAverage = 0; - var map = new int[WebPConstants.MaxAlpha + 1]; + var map = new int[WebpConstants.MaxAlpha + 1]; int a, n, k; var accum = new int[NumMbSegments]; var distAccum = new int[NumMbSegments]; // Bracket the input. - for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) + for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) { } var minA = n; - for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) + for (n = WebpConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } @@ -730,7 +730,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int nb = this.segmentHeader.NumSegments; Vp8SegmentInfo[] dqm = this.SegmentInfos; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. - double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; + double amp = WebpConstants.SnsToDq * snsStrength / 100.0d / 128.0d; double cBase = QualityToCompression(quality / 100.0d); for (int i = 0; i < nb; ++i) { @@ -748,13 +748,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // uvAlpha is normally spread around ~60. The useful range is // typically ~30 (quite bad) to ~100 (ok to decimate UV more). // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. - this.dqUvAc = (this.uvAlpha - WebPConstants.QuantEncMidAlpha) * (WebPConstants.QuantEncMaxDqUv - WebPConstants.QuantEncMinDqUv) / (WebPConstants.QuantEncMaxAlpha - WebPConstants.QuantEncMinAlpha); + this.dqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); // We rescale by the user-defined strength of adaptation. this.dqUvAc = this.dqUvAc * snsStrength / 100; // and make it safe. - this.dqUvAc = Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); + this.dqUvAc = Clip(this.dqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); // We also boost the dc-uv-quant a little, based on sns-strength, since // U/V channels are quite more reactive to high quants (flat DC-blocks @@ -779,17 +779,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. int level0 = 5 * FilterStrength; - for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + for (int i = 0; i < WebpConstants.NumMbSegments; ++i) { Vp8SegmentInfo m = this.SegmentInfos[i]; // We focus on the quantization of AC coeffs. - int qstep = WebPLookupTables.AcTable[Clip(m.Quant, 0, 127)] >> 2; + int qstep = WebpLookupTables.AcTable[Clip(m.Quant, 0, 127)] >> 2; int baseStrength = this.FilterStrengthFromDelta(this.filterHeader.Sharpness, qstep); // Segments with lower complexity ('beta') will be less filtered. int f = baseStrength * level0 / (256 + m.Beta); - m.FStrength = (f < WebPConstants.FilterStrengthCutoff) ? 0 : (f > 63) ? 63 : f; + m.FStrength = (f < WebpConstants.FilterStrengthCutoff) ? 0 : (f > 63) ? 63 : f; } // We record the initial strength (mainly for the case of 1-segment only). @@ -861,14 +861,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy m.Y2 = new Vp8Matrix(); m.Uv = new Vp8Matrix(); - m.Y1.Q[0] = WebPLookupTables.DcTable[Clip(q, 0, 127)]; - m.Y1.Q[1] = WebPLookupTables.AcTable[Clip(q, 0, 127)]; + m.Y1.Q[0] = WebpLookupTables.DcTable[Clip(q, 0, 127)]; + m.Y1.Q[1] = WebpLookupTables.AcTable[Clip(q, 0, 127)]; - m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[Clip(q, 0, 127)] * 2); - m.Y2.Q[1] = WebPLookupTables.AcTable2[Clip(q, 0, 127)]; + m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Clip(q, 0, 127)] * 2); + m.Y2.Q[1] = WebpLookupTables.AcTable2[Clip(q, 0, 127)]; - m.Uv.Q[0] = WebPLookupTables.DcTable[Clip(q + this.dqUvDc, 0, 117)]; - m.Uv.Q[1] = WebPLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)]; + m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.dqUvDc, 0, 117)]; + m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)]; var qi4 = m.Y1.Expand(0); var qi16 = m.Y2.Expand(1); @@ -974,9 +974,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (mode = 0; mode < numPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - long score = (Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + long score = (Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); - if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) + if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) { continue; } @@ -1014,14 +1014,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { int bestI4Mode = -1; long bestI4Score = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); short[] modeCosts = it.GetCostModeI4(rd.ModesI4); it.MakeIntra4Preds(); for (mode = 0; mode < numBModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - long score = (Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); if (score < bestI4Score) { bestI4Mode = mode; @@ -1041,7 +1041,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy else { // Reconstruct partial block inside YuvOut2 buffer - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; } } @@ -1070,7 +1070,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (mode = 0; mode < numPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - long score = (Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + long score = (Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); if (score < bestUvScore) { bestMode = mode; @@ -1222,7 +1222,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (n = 0; n < 16; n += 2) { - Vp8Encoding.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); + Vp8Encoding.FTransform2(src.Slice(WebpLookupTables.Vp8Scan[n]), reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); } Vp8Encoding.FTransformWht(tmp, dcTmp); @@ -1240,7 +1240,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy LossyUtils.TransformWht(dcTmp, tmpSpan); for (n = 0; n < 16; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), true); } return nz; @@ -1268,8 +1268,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (n = 0; n < 8; n += 2) { Vp8Encoding.FTransform2( - src.Slice(WebPLookupTables.Vp8ScanUv[n]), - reference.Slice(WebPLookupTables.Vp8ScanUv[n]), + src.Slice(WebpLookupTables.Vp8ScanUv[n]), + reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 16), tmp.AsSpan((n + 1) * 16, 16)); } @@ -1283,7 +1283,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (n = 0; n < 8; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), true); } return nz << 16; @@ -1346,8 +1346,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static int FinalAlphaValue(int alpha) { - alpha = WebPConstants.MaxAlpha - alpha; - return Clip(alpha, 0, WebPConstants.MaxAlpha); + alpha = WebpConstants.MaxAlpha - alpha; + return Clip(alpha, 0, WebpConstants.MaxAlpha); } [MethodImpl(InliningOptions.ShortMethod)] @@ -1387,8 +1387,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy count += diff * diff; } - aOffset += WebPConstants.Bps; - bOffset += WebPConstants.Bps; + aOffset += WebpConstants.Bps; + bOffset += WebpConstants.Bps; } return count; @@ -1407,7 +1407,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy return false; } - src = src.Slice(WebPConstants.Bps); + src = src.Slice(WebpConstants.Bps); } return true; @@ -1435,8 +1435,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private int FilterStrengthFromDelta(int sharpness, int delta) { - int pos = (delta < WebPConstants.MaxDelzaSize) ? delta : WebPConstants.MaxDelzaSize - 1; - return WebPLookupTables.LevelsFromDelta[sharpness, pos]; + int pos = (delta < WebpConstants.MaxDelzaSize) ? delta : WebpConstants.MaxDelzaSize - 1; + return WebpLookupTables.LevelsFromDelta[sharpness, pos]; } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs index e20683b12..e2cad7faf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Methods for encoding a VP8 frame. @@ -18,19 +18,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private static readonly byte[] Clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] - private const int I16DC16 = 0 * 16 * WebPConstants.Bps; + private const int I16DC16 = 0 * 16 * WebpConstants.Bps; private const int I16TM16 = I16DC16 + 16; - private const int I16VE16 = 1 * 16 * WebPConstants.Bps; + private const int I16VE16 = 1 * 16 * WebpConstants.Bps; private const int I16HE16 = I16VE16 + 16; - private const int C8DC8 = 2 * 16 * WebPConstants.Bps; + private const int C8DC8 = 2 * 16 * WebpConstants.Bps; private const int C8TM8 = C8DC8 + (1 * 16); - private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); + private const int C8VE8 = (2 * 16 * WebpConstants.Bps) + (8 * WebpConstants.Bps); private const int C8HE8 = C8VE8 + (1 * 16); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; - private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; + private const int I4DC4 = (3 * 16 * WebpConstants.Bps) + 0; private const int I4TM4 = I4DC4 + 4; @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private const int I4VL4 = I4DC4 + 28; - private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); + private const int I4HD4 = (3 * 16 * WebpConstants.Bps) + (4 * WebpConstants.Bps); private const int I4HU4 = I4HD4 + 4; @@ -143,8 +143,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy tmp[2 + (i * 4)] = (a0 - a1) * 8; tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - srcIdx += WebPConstants.Bps; - refIdx += WebPConstants.Bps; + srcIdx += WebpConstants.Bps; + refIdx += WebpConstants.Bps; } for (i = 0; i < 4; ++i) @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int j = 0; j < size; ++j) { - top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); + top.Slice(0, size).CopyTo(dst.Slice(j * WebpConstants.Bps)); } } else @@ -270,7 +270,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy left = left.Slice(1); // in the reference implementation, left starts at - 1. for (int j = 0; j < size; ++j) { - dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); + dst.Slice(j * WebpConstants.Bps, size).Fill(left[j]); } } else @@ -294,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy dst[x] = clipTable[top[x]]; } - dst = dst.Slice(WebPConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); } } else @@ -391,7 +391,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy dst[x] = clipTable[top[topOffset + x]]; } - dst = dst.Slice(WebPConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); } } @@ -408,7 +408,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (int i = 0; i < 4; ++i) { - vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); + vals.AsSpan().CopyTo(dst.Slice(i * WebpConstants.Bps)); } } @@ -424,11 +424,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); BinaryPrimitives.WriteUInt32BigEndian(dst, val); val = 0x01010101U * LossyUtils.Avg3(i, j, k); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebpConstants.Bps), val); val = 0x01010101U * LossyUtils.Avg3(j, k, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebpConstants.Bps), val); val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebpConstants.Bps), val); } private static void Rd4(Span dst, Span top, int topOffset) @@ -639,7 +639,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int j = 0; j < size; ++j) { - dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); + dst.Slice(j * WebpConstants.Bps, size).Fill((byte)value); } } @@ -652,7 +652,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, Span reference, int x, int y, int v) { - dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); + dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 0710b6fab..67dbb0baf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8FilterHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index 4ae39f710..d7155d3e6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Filter information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index 07541ca0d..2cef291b7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Vp8 frame header information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index 65b823b31..ead8bd573 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal ref struct Vp8Io { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index b6de2aa8a..76a9b76f7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Contextual macroblock information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index c46f4f116..b955cac6a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Data needed to reconstruct a macroblock. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs index 910ead4fd..4e22b5f58 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { [DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] internal class Vp8MacroBlockInfo diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs index 65306a0e1..a9325693f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal enum Vp8MacroBlockType { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index a2bacefe8..7ceb2ed1d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8Matrix { @@ -71,13 +71,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { int isAcCoeff = (i > 0) ? 1 : 0; int bias = BiasMatrices[type][isAcCoeff]; - this.IQ[i] = (ushort)((1 << WebPConstants.QFix) / this.Q[i]); + this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); this.Bias[i] = (uint)this.BIAS(bias); // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: // * zero if coeff <= zthresh // * non-zero if coeff > zthresh - this.ZThresh[i] = ((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; + this.ZThresh[i] = ((1 << WebpConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; } for (i = 2; i < 16; ++i) @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private int BIAS(int b) { - return b << (WebPConstants.QFix - 8); + return b << (WebpConstants.QFix - 8); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 1abf06855..a5360707b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Class to accumulate score and info during RD-optimization and mode evaluation. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 697016a87..020ff07a3 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8PictureHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index 718ca7ded..3d37c2018 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Data for all frame-persistent probabilities. @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public Vp8Proba() { this.Segments = new uint[MbFeatureTreeProbs]; - this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; - this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes][]; + this.Bands = new Vp8BandProbas[WebpConstants.NumTypes, WebpConstants.NumBands]; + this.BandsPtr = new Vp8BandProbas[WebpConstants.NumTypes][]; - for (int i = 0; i < WebPConstants.NumTypes; i++) + for (int i = 0; i < WebpConstants.NumTypes; i++) { - for (int j = 0; j < WebPConstants.NumBands; j++) + for (int j = 0; j < WebpConstants.NumBands; j++) { this.Bands[i, j] = new Vp8BandProbas(); } } - for (int i = 0; i < WebPConstants.NumTypes; i++) + for (int i = 0; i < WebpConstants.NumTypes; i++) { this.BandsPtr[i] = new Vp8BandProbas[16 + 1]; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index 73614da13..30e1f79ee 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Probabilities associated to one of the contexts. @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy /// public Vp8ProbaArray() { - this.Probabilities = new byte[WebPConstants.NumProbas]; + this.Probabilities = new byte[WebpConstants.NumProbas]; } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index e3b3e9b0f..435631aae 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8QuantMatrix { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs index 067a38379..4b937b813 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Rate-distortion optimization levels diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index efba2df7d..f980bd3b9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// On-the-fly info about the current set of residuals. @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy while ((v = this.Coeffs[n++]) == 0) { this.RecordStats(0, s, 1); - s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[0]; + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[0]; } this.RecordStats(1, s, 1); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy if (this.RecordStats(bit ? 1 : 0, s, 2) == 0) { // v = -1 or 1 - s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1]; + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[1]; } else { @@ -88,8 +88,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy v = MaxVariableLevel; } - int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1]; - int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0]; + int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; + int pattern = WebpLookupTables.Vp8LevelCodes[v - 1][0]; int i; for (i = 0; (pattern >>= 1) != 0; ++i) { @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } } - s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2]; + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[2]; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index 1ea12d51e..96f1fbd60 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Segment features. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index 2feb9384b..cbf946b9f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8SegmentInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs index e4a463751..336124378 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8Stats { @@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy /// public Vp8Stats() { - this.Stats = new Vp8StatsArray[WebPConstants.NumCtx]; - for (int i = 0; i < WebPConstants.NumCtx; i++) + this.Stats = new Vp8StatsArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) { this.Stats[i] = new Vp8StatsArray(); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs index 6681c23bb..34e7d6733 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8StatsArray { @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy /// public Vp8StatsArray() { - this.Stats = new uint[WebPConstants.NumProbas]; + this.Stats = new uint[WebpConstants.NumProbas]; } public uint[] Stats { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index 1b696dd3a..a74231ff1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal class Vp8TopSamples { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index bc3e76231..cd501c45a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { /// /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.configuration = configuration; } - public void Decode(Buffer2D pixels, int width, int height, WebPImageInfo info) + public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info) where TPixel : unmanaged, IPixel { // Paragraph 9.2: color space and clamp type follow. @@ -227,11 +227,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int yMode = left[y]; for (int x = 0; x < 4; ++x) { - byte[] prob = WebPLookupTables.ModesProba[top[x], yMode]; - int i = WebPConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; + byte[] prob = WebpLookupTables.ModesProba[top[x], yMode]; + int i = WebpConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; while (i > 0) { - i = WebPConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; + i = WebpConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; } yMode = -i; @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private void ReconstructRow(Vp8Decoder dec) { int mby = dec.MbY; - const int yOff = (WebPConstants.Bps * 1) + 8; - const int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; + const int yOff = (WebpConstants.Bps * 1) + 8; + const int uOff = yOff + (WebpConstants.Bps * 16) + WebpConstants.Bps; const int vOff = uOff + 16; Span yuv = dec.YuvBuffer.Memory.Span; @@ -282,14 +282,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Span vDst = yuv.Slice(vOff); // Initialize left-most block. - var end = 16 * WebPConstants.Bps; - for (int i = 0; i < end; i += WebPConstants.Bps) + var end = 16 * WebpConstants.Bps; + for (int i = 0; i < end; i += WebpConstants.Bps) { yuv[i - 1 + yOff] = 129; } - end = 8 * WebPConstants.Bps; - for (int i = 0; i < end; i += WebPConstants.Bps) + end = 8 * WebpConstants.Bps; + for (int i = 0; i < end; i += WebpConstants.Bps) { yuv[i - 1 + uOff] = 129; yuv[i - 1 + vOff] = 129; @@ -298,25 +298,25 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Init top-left sample on left column too. if (mby > 0) { - yuv[yOff - 1 - WebPConstants.Bps] = yuv[uOff - 1 - WebPConstants.Bps] = yuv[vOff - 1 - WebPConstants.Bps] = 129; + yuv[yOff - 1 - WebpConstants.Bps] = yuv[uOff - 1 - WebpConstants.Bps] = yuv[vOff - 1 - WebpConstants.Bps] = 129; } else { // We only need to do this init once at block (0,0). // Afterward, it remains valid for the whole topmost row. - Span tmp = yuv.Slice(yOff - WebPConstants.Bps - 1, 16 + 4 + 1); + Span tmp = yuv.Slice(yOff - WebpConstants.Bps - 1, 16 + 4 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; } - tmp = yuv.Slice(uOff - WebPConstants.Bps - 1, 8 + 1); + tmp = yuv.Slice(uOff - WebpConstants.Bps - 1, 8 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; } - tmp = yuv.Slice(vOff - WebPConstants.Bps - 1, 8 + 1); + tmp = yuv.Slice(vOff - WebpConstants.Bps - 1, 8 + 1); for (int i = 0; i < tmp.Length; ++i) { tmp[i] = 127; @@ -334,18 +334,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int i = -1; i < 16; ++i) { - int srcIdx = (i * WebPConstants.Bps) + 12 + yOff; - int dstIdx = (i * WebPConstants.Bps) - 4 + yOff; + int srcIdx = (i * WebpConstants.Bps) + 12 + yOff; + int dstIdx = (i * WebpConstants.Bps) - 4 + yOff; yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); } for (int i = -1; i < 8; ++i) { - int srcIdx = (i * WebPConstants.Bps) + 4 + uOff; - int dstIdx = (i * WebPConstants.Bps) - 4 + uOff; + int srcIdx = (i * WebpConstants.Bps) + 4 + uOff; + int dstIdx = (i * WebpConstants.Bps) - 4 + uOff; yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); - srcIdx = (i * WebPConstants.Bps) + 4 + vOff; - dstIdx = (i * WebPConstants.Bps) - 4 + vOff; + srcIdx = (i * WebpConstants.Bps) + 4 + vOff; + dstIdx = (i * WebpConstants.Bps) - 4 + vOff; yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); } } @@ -356,15 +356,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy uint bits = block.NonZeroY; if (mby > 0) { - topYuv.Y.CopyTo(yuv.Slice(yOff - WebPConstants.Bps)); - topYuv.U.CopyTo(yuv.Slice(uOff - WebPConstants.Bps)); - topYuv.V.CopyTo(yuv.Slice(vOff - WebPConstants.Bps)); + topYuv.Y.CopyTo(yuv.Slice(yOff - WebpConstants.Bps)); + topYuv.U.CopyTo(yuv.Slice(uOff - WebpConstants.Bps)); + topYuv.V.CopyTo(yuv.Slice(vOff - WebpConstants.Bps)); } // Predict and add residuals. if (block.IsI4x4) { - Span topRight = yuv.Slice(yOff - WebPConstants.Bps + 16); + Span topRight = yuv.Slice(yOff - WebpConstants.Bps + 16); if (mby > 0) { if (mbx >= dec.MbWidth - 1) @@ -383,13 +383,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } // Replicate the top-right pixels below. - Span topRightUint = MemoryMarshal.Cast(yuv.Slice(yOff - WebPConstants.Bps + 16)); - topRightUint[WebPConstants.Bps] = topRightUint[2 * WebPConstants.Bps] = topRightUint[3 * WebPConstants.Bps] = topRightUint[0]; + Span topRightUint = MemoryMarshal.Cast(yuv.Slice(yOff - WebpConstants.Bps + 16)); + topRightUint[WebpConstants.Bps] = topRightUint[2 * WebpConstants.Bps] = topRightUint[3 * WebpConstants.Bps] = topRightUint[0]; // Predict and add residuals for all 4x4 blocks in turn. for (int n = 0; n < 16; ++n, bits <<= 2) { - int offset = yOff + WebPConstants.Scan[n]; + int offset = yOff + WebpConstants.Scan[n]; Span dst = yuv.Slice(offset); byte lumaMode = block.Modes[n]; switch (lumaMode) @@ -462,7 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { for (int n = 0; n < 16; ++n, bits <<= 2) { - this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebPConstants.Scan[n])); + this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebpConstants.Scan[n])); } } } @@ -508,9 +508,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy // Stash away top samples for next block. if (mby < dec.MbHeight - 1) { - yDst.Slice(15 * WebPConstants.Bps, 16).CopyTo(topYuv.Y); - uDst.Slice(7 * WebPConstants.Bps, 8).CopyTo(topYuv.U); - vDst.Slice(7 * WebPConstants.Bps, 8).CopyTo(topYuv.V); + yDst.Slice(15 * WebpConstants.Bps, 16).CopyTo(topYuv.Y); + uDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.U); + vDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.V); } // Transfer reconstructed samples from yuv_buffer cache to final destination. @@ -519,14 +519,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Span vOut = dec.CacheV.Memory.Span.Slice(dec.CacheUvOffset + (mbx * 8)); for (int j = 0; j < 16; ++j) { - yDst.Slice(j * WebPConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut.Slice(j * dec.CacheYStride)); + yDst.Slice(j * WebpConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut.Slice(j * dec.CacheYStride)); } for (int j = 0; j < 8; ++j) { int jUvStride = j * dec.CacheUvStride; - uDst.Slice(j * WebPConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(jUvStride)); - vDst.Slice(j * WebPConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut.Slice(jUvStride)); + uDst.Slice(j * WebpConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(jUvStride)); + vDst.Slice(j * WebpConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut.Slice(jUvStride)); } } } @@ -609,7 +609,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private void FinishRow(Vp8Decoder dec, Vp8Io io) { - int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; + int extraYRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; int ySize = extraYRows * dec.CacheYStride; int uvSize = (extraYRows / 2) * dec.CacheUvStride; Span yDst = dec.CacheY.Memory.Span; @@ -1008,7 +1008,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } int idx = n > 0 ? 1 : 0; - coeffs[WebPConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); + coeffs[WebpConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); } return 16; @@ -1053,19 +1053,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy switch (cat) { case 0: - tab = WebPConstants.Cat3; + tab = WebpConstants.Cat3; break; case 1: - tab = WebPConstants.Cat4; + tab = WebpConstants.Cat4; break; case 2: - tab = WebPConstants.Cat5; + tab = WebpConstants.Cat5; break; case 3: - tab = WebPConstants.Cat6; + tab = WebpConstants.Cat6; break; default: - WebPThrowHelper.ThrowImageFormatException("VP8 parsing error"); + WebpThrowHelper.ThrowImageFormatException("VP8 parsing error"); break; } @@ -1162,7 +1162,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } } - int extraRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; + int extraRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; int extraY = extraRows * dec.CacheYStride; int extraUv = (extraRows / 2) * dec.CacheUvStride; dec.CacheYOffset = extraY; @@ -1213,7 +1213,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; hasValue = this.bitReader.ReadBool(); int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + for (int i = 0; i < WebpConstants.NumMbSegments; ++i) { int q; if (vp8SegmentHeader.UseSegment) @@ -1238,20 +1238,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy } Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; - m.Y1Mat[0] = WebPLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebPLookupTables.AcTable[Clip(q + 0, 127)]; - m.Y2Mat[0] = WebPLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; + m.Y1Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebpLookupTables.AcTable[Clip(q + 0, 127)]; + m.Y2Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebPLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; + m.Y2Mat[1] = (WebpLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; if (m.Y2Mat[1] < 8) { m.Y2Mat[1] = 8; } - m.UvMat[0] = WebPLookupTables.DcTable[Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebPLookupTables.AcTable[Clip(q + dquvAc, 127)]; + m.UvMat[0] = WebpLookupTables.DcTable[Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebpLookupTables.AcTable[Clip(q + dquvAc, 127)]; // For dithering strength evaluation. m.UvQuant = q + dquvAc; @@ -1262,18 +1262,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy { Vp8Proba proba = dec.Probabilities; - for (int t = 0; t < WebPConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int b = 0; b < WebPConstants.NumBands; ++b) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int c = 0; c < WebPConstants.NumCtx; ++c) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int p = 0; p < WebPConstants.NumProbas; ++p) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - byte prob = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; + byte prob = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; byte v = (byte)(this.bitReader.GetBit(prob) != 0 ? this.bitReader.ReadValue(8) - : WebPLookupTables.DefaultCoeffsProba[t, b, c, p]); + : WebpLookupTables.DefaultCoeffsProba[t, b, c, p]); proba.Bands[t, b].Probabilities[c].Probabilities[p] = v; } } @@ -1281,7 +1281,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy for (int b = 0; b < 16 + 1; ++b) { - proba.BandsPtr[t][b] = proba.Bands[t, WebPConstants.Vp8EncBands[b]]; + proba.BandsPtr[t][b] = proba.Bands[t, WebpConstants.Vp8EncBands[b]]; } } @@ -1309,7 +1309,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy int intraPredModeSize = 4 * dec.MbWidth; dec.IntraT = new byte[intraPredModeSize]; - int extraPixels = WebPConstants.FilterExtraRows[(int)dec.Filter]; + int extraPixels = WebpConstants.FilterExtraRows[(int)dec.Filter]; if (dec.Filter == LoopFilter.Complex) { // For complex filter, we need to preserve the dependency chain. diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 85c36be65..00407608b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { internal static class YuvConversion { @@ -199,7 +199,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) { uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); - return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); + return LinearToGamma((sum * WebpLookupTables.InvAlpha[totalA]) >> (WebpConstants.AlphaFix - 2), 0); } // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision @@ -208,23 +208,23 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy private static int LinearToGamma(uint baseValue, int shift) { int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. - return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. + return (y + WebpConstants.GammaTabRounder) >> WebpConstants.GammaTabFix; // Descale. } [MethodImpl(InliningOptions.ShortMethod)] private static uint GammaToLinear(byte v) { - return WebPLookupTables.GammaToLinearTab[v]; + return WebpLookupTables.GammaToLinearTab[v]; } [MethodImpl(InliningOptions.ShortMethod)] private static int Interpolate(int v) { - int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. - int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. - int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; - int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; - int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate + int tabPos = v >> (WebpConstants.GammaTabFix + 2); // integer part. + int x = v & ((WebpConstants.GammaTabScale << 2) - 1); // fractional part. + int v0 = WebpLookupTables.LinearToGammaTab[tabPos]; + int v1 = WebpLookupTables.LinearToGammaTab[tabPos + 1]; + int y = (v1 * x) + (v0 * ((WebpConstants.GammaTabScale << 2) - x)); // interpolate return y; } diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs index 81b0ef90d..810eace48 100644 --- a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp /// Gets the webp format specific metadata for the image. /// /// The metadata this method extends. - /// The . - public static WebPMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebPFormat.Instance); + /// The . + public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); } } diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs index 1e194ce1f..313b8e074 100644 --- a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs +++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Enum for the different VP8 chunk header types. diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs index f4d6a4251..a52d18d15 100644 --- a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Enum for the different alpha filter types. /// - internal enum WebPAlphaFilterType : int + internal enum WebpAlphaFilterType : int { /// /// No filtering. diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs index 731516d2a..b6dc5c9fe 100644 --- a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs +++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Enumerates the available bits per pixel the webp image uses. /// - public enum WebPBitsPerPixel : short + public enum WebpBitsPerPixel : short { /// /// 24 bits per pixel. Each pixel consists of 3 bytes. diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index 98a1c2f81..afb1bf97d 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -1,13 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Contains a list of different webp chunk types. /// /// See WebP Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container - public enum WebPChunkType : uint + public enum WebpChunkType : uint { /// /// Header signaling the use of the VP8 format. diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs index 024d81b0b..7a93b0f10 100644 --- a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Utility methods for lossy and lossless webp format. /// - internal static class WebPCommonUtils + internal static class WebpCommonUtils { /// /// Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP n >>= 8; } - return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebPLookupTables.LogTable8Bit), (int)n); + return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebpLookupTables.LogTable8Bit), (int)n); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index a0fb02e5b..98c783a40 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Constants used for encoding and decoding VP8 and VP8L bitstreams. /// - internal static class WebPConstants + internal static class WebpConstants { /// /// The list of file extensions that equate to WebP. diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 539ba0700..cc2236a70 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -9,13 +9,13 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// EXPERIMENTAL: /// Image decoder for generating an image out of a webp stream. /// - public sealed class WebPDecoder : IImageDecoder, IWebPDecoderOptions, IImageInfoDetector + 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. @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { Guard.NotNull(stream, nameof(stream)); - var decoder = new WebPDecoderCore(configuration, this); + var decoder = new WebpDecoderCore(configuration, this); try { @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { Guard.NotNull(stream, nameof(stream)); - return new WebPDecoderCore(configuration, this).Identify(configuration, stream); + return new WebpDecoderCore(configuration, this).Identify(configuration, stream); } /// @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { Guard.NotNull(stream, nameof(stream)); - var decoder = new WebPDecoderCore(configuration, this); + var decoder = new WebpDecoderCore(configuration, this); try { @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP Guard.NotNull(stream, nameof(stream)); using var bufferedStream = new BufferedReadStream(configuration, stream); - return new WebPDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken); + return new WebpDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f2f654561..5f260deb7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -5,9 +5,9 @@ using System; using System.Buffers.Binary; using System.IO; using System.Threading; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -15,12 +15,12 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Performs the webp decoding operation. /// - internal sealed class WebPDecoderCore : IImageDecoderInternals + internal sealed class WebpDecoderCore : IImageDecoderInternals { /// /// Reusable buffer. @@ -40,19 +40,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// The webp specific metadata. /// - private WebPMetadata webpMetadata; + private WebpMetadata webpMetadata; /// /// Information about the webp image. /// - private WebPImageInfo webImageInfo; + private WebpImageInfo webImageInfo; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. /// The options. - public WebPDecoderCore(Configuration configuration, IWebPDecoderOptions options) + public WebpDecoderCore(Configuration configuration, IWebpDecoderOptions options) { this.Configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP { if (this.webImageInfo.Features != null && this.webImageInfo.Features.Animation) { - WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); + WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); } var image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); @@ -152,24 +152,24 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// Reads information present in the image header, about the image content and how to decode the image. /// /// Information about the webp image. - private WebPImageInfo ReadVp8Info() + private WebpImageInfo ReadVp8Info() { 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(); switch (chunkType) { - case WebPChunkType.Vp8: + case WebpChunkType.Vp8: return this.ReadVp8Header(); - case WebPChunkType.Vp8L: + case WebpChunkType.Vp8L: return this.ReadVp8LHeader(); - case WebPChunkType.Vp8X: + case WebpChunkType.Vp8X: return this.ReadVp8XHeader(); default: - WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); - return new WebPImageInfo(); // this return will never be reached, because throw helper will throw an exception. + WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + return new WebpImageInfo(); // this return will never be reached, because throw helper will throw an exception. } } @@ -182,9 +182,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. /// /// Information about this webp image. - private WebPImageInfo ReadVp8XHeader() + private WebpImageInfo ReadVp8XHeader() { - var features = new WebPFeatures(); + var features = new WebpFeatures(); uint chunkSize = this.ReadChunkSize(); // The first byte contains information about the image features used. @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP // The first two bit of it are reserved and should be 0. if (imageFeatures >> 6 != 0) { - WebPThrowHelper.ThrowImageFormatException( + WebpThrowHelper.ThrowImageFormatException( "first two bits of the VP8X header are expected to be zero"); } @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP this.currentStream.Read(this.buffer, 0, 3); if (this.buffer[0] != 0 || this.buffer[1] != 0 | this.buffer[2] != 0) { - WebPThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); + WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); } // 3 bytes for the width. @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // Optional chunks ICCP, ALPH and ANIM can follow here. - WebPChunkType chunkType = this.ReadChunkType(); + WebpChunkType chunkType = this.ReadChunkType(); while (IsOptionalVp8XChunk(chunkType)) { this.ParseOptionalExtendedChunks(chunkType, features); @@ -240,20 +240,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP if (features.Animation) { // TODO: Animations are not yet supported. - return new WebPImageInfo() { Width = width, Height = height, Features = features }; + return new WebpImageInfo() { Width = width, Height = height, Features = features }; } switch (chunkType) { - case WebPChunkType.Vp8: + case WebpChunkType.Vp8: return this.ReadVp8Header(features); - case WebPChunkType.Vp8L: + case WebpChunkType.Vp8L: return this.ReadVp8LHeader(features); } - WebPThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); - return new WebPImageInfo(); + return new WebpImageInfo(); } /// @@ -261,9 +261,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Webp features. /// Information about this webp image. - private WebPImageInfo ReadVp8Header(WebPFeatures features = null) + private WebpImageInfo ReadVp8Header(WebpFeatures features = null) { - this.webpMetadata.Format = WebPFormatType.Lossy; + this.webpMetadata.Format = WebpFormatType.Lossy; // VP8 data size (not including this 4 bytes). this.currentStream.Read(this.buffer, 0, 4); @@ -284,32 +284,32 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP bool isNoKeyFrame = (frameTag & 0x1) == 1; if (isNoKeyFrame) { - WebPThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); + WebpThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); } uint version = (frameTag >> 1) & 0x7; if (version > 3) { - WebPThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); + WebpThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); } bool invisibleFrame = ((frameTag >> 4) & 0x1) == 0; if (invisibleFrame) { - WebPThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); + WebpThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); } uint partitionLength = frameTag >> 5; if (partitionLength > dataSize) { - WebPThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); + WebpThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); } // Check for VP8 magic bytes. this.currentStream.Read(this.buffer, 0, 3); - if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8HeaderMagicBytes)) + if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) { - WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); + WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); } this.currentStream.Read(this.buffer, 0, 4); @@ -322,12 +322,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP remaining -= 7; if (width == 0 || height == 0) { - WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); + WebpThrowHelper.ThrowImageFormatException("width or height can not be zero"); } if (partitionLength > remaining) { - WebPThrowHelper.ThrowImageFormatException("bad partition length"); + WebpThrowHelper.ThrowImageFormatException("bad partition length"); } var vp8FrameHeader = new Vp8FrameHeader() @@ -346,13 +346,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP Remaining = remaining }; - return new WebPImageInfo() + return new WebpImageInfo() { Width = width, Height = height, XScale = xScale, YScale = yScale, - BitsPerPixel = features?.Alpha == true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, + BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Pixel32 : WebpBitsPerPixel.Pixel24, IsLossless = false, Features = features, Vp8Profile = (sbyte)version, @@ -366,9 +366,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Webp image features. /// Information about this image. - private WebPImageInfo ReadVp8LHeader(WebPFeatures features = null) + private WebpImageInfo ReadVp8LHeader(WebpFeatures features = null) { - this.webpMetadata.Format = WebPFormatType.Lossless; + this.webpMetadata.Format = WebpFormatType.Lossless; // VP8 data size. uint imageDataSize = this.ReadChunkSize(); @@ -377,17 +377,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP // One byte signature, should be 0x2f. uint signature = bitReader.ReadValue(8); - if (signature != WebPConstants.Vp8LHeaderMagicByte) + if (signature != WebpConstants.Vp8LHeaderMagicByte) { - WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); + WebpThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } // The first 28 bits of the bitstream specify the width and height of the image. - uint width = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; + uint width = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; if (width == 1 || height == 1) { - WebPThrowHelper.ThrowImageFormatException("invalid width or height read"); + WebpThrowHelper.ThrowImageFormatException("invalid width or height read"); } // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. @@ -396,17 +396,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. - uint version = bitReader.ReadValue(WebPConstants.Vp8LVersionBits); + uint version = bitReader.ReadValue(WebpConstants.Vp8LVersionBits); if (version != 0) { - WebPThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); + WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); } - return new WebPImageInfo() + return new WebpImageInfo() { Width = width, Height = height, - BitsPerPixel = WebPBitsPerPixel.Pixel32, + BitsPerPixel = WebpBitsPerPixel.Pixel32, IsLossless = true, Features = features, Vp8LBitReader = bitReader @@ -418,11 +418,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// The chunk type. /// The webp image features. - private void ParseOptionalExtendedChunks(WebPChunkType chunkType, WebPFeatures features) + private void ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features) { switch (chunkType) { - case WebPChunkType.Iccp: + case WebpChunkType.Iccp: uint iccpChunkSize = this.ReadChunkSize(); if (this.IgnoreMetadata) { @@ -441,11 +441,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP break; - case WebPChunkType.Animation: + case WebpChunkType.Animation: this.webpMetadata.Animated = true; break; - case WebPChunkType.Alpha: + case WebpChunkType.Alpha: uint alphaChunkSize = this.ReadChunkSize(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); var alphaDataSize = (int)(alphaChunkSize - 1); @@ -461,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. /// /// The webp features. - private void ParseOptionalChunks(WebPFeatures features) + private void ParseOptionalChunks(WebpFeatures features) { if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) { @@ -472,10 +472,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP while (this.currentStream.Position < streamLength) { // Read chunk header. - WebPChunkType chunkType = this.ReadChunkType(); + WebpChunkType chunkType = this.ReadChunkType(); uint chunkLength = this.ReadChunkSize(); - if (chunkType == WebPChunkType.Exif) + if (chunkType == WebpChunkType.Exif) { var exifData = new byte[chunkLength]; this.currentStream.Read(exifData, 0, (int)chunkLength); @@ -495,11 +495,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Thrown if the input stream is not valid. /// - private WebPChunkType ReadChunkType() + private WebpChunkType ReadChunkType() { if (this.currentStream.Read(this.buffer, 0, 4) == 4) { - var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); this.webpMetadata.ChunkTypes.Enqueue(chunkType); return chunkType; } @@ -528,13 +528,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// The chunk type. /// True, if its an optional chunk type. - private static bool IsOptionalVp8XChunk(WebPChunkType chunkType) + private static bool IsOptionalVp8XChunk(WebpChunkType chunkType) { return chunkType switch { - WebPChunkType.Alpha => true, - WebPChunkType.Animation => true, - WebPChunkType.Iccp => true, + WebpChunkType.Alpha => true, + WebpChunkType.Animation => true, + WebpChunkType.Iccp => true, _ => false }; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index cb9a4101d..88bceddb2 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -7,13 +7,13 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// EXPERIMENTAL: /// Image encoder for writing an image to a stream in the WebP format. /// - public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions + public sealed class WebpEncoder : IImageEncoder, IWebPEncoderOptions { /// public bool Lossy { get; set; } @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator()); + var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); } @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator()); + var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); return encoder.EncodeAsync(image, stream); } } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 6b8885a1e..73817e691 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -5,18 +5,18 @@ using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Image encoder for writing an image to a stream in the WebP format. /// - internal sealed class WebPEncoderCore + internal sealed class WebpEncoderCore { /// /// Used for allocating memory during processing operations. @@ -54,11 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP private readonly int entropyPasses; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The encoder options. /// The memory manager. - public WebPEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) + public WebpEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.alphaCompression = options.AlphaCompression; diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 38292e75e..4a0825c03 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -4,12 +4,12 @@ using System; using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Image features of a VP8X image. /// - internal class WebPFeatures : IDisposable + internal class WebpFeatures : IDisposable { /// /// Gets or sets a value indicating whether this image has an ICC Profile. diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 241ec879a..6aaa24728 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -3,22 +3,22 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// EXPERIMENTAL: /// Registers the image encoders, decoders and mime type detectors for the WebP format /// - public sealed class WebPFormat : IImageFormat + public sealed class WebpFormat : IImageFormat { - private WebPFormat() + private WebpFormat() { } /// /// Gets the current instance. /// - public static WebPFormat Instance { get; } = new WebPFormat(); + public static WebpFormat Instance { get; } = new WebpFormat(); /// public string Name => "WebP"; @@ -27,12 +27,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public string DefaultMimeType => "image/webp"; /// - public IEnumerable MimeTypes => WebPConstants.MimeTypes; + public IEnumerable MimeTypes => WebpConstants.MimeTypes; /// - public IEnumerable FileExtensions => WebPConstants.FileExtensions; + public IEnumerable FileExtensions => WebpConstants.FileExtensions; /// - public WebPMetadata CreateDefaultFormatMetadata() => new WebPMetadata(); + public WebpMetadata CreateDefaultFormatMetadata() => new WebpMetadata(); } } diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs index a85cac34e..3835f8804 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormatType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Info about the webp format used. /// - public enum WebPFormatType + public enum WebpFormatType { /// /// Unknown webp format. diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 6da95ef84..54fcbca46 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -3,12 +3,12 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Detects WebP file headers. /// - public sealed class WebPImageFormatDetector : IImageFormatDetector + public sealed class WebpImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 12; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// public IImageFormat DetectFormat(ReadOnlySpan header) { - return this.IsSupportedFileFormat(header) ? WebPFormat.Instance : null; + return this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; } private bool IsSupportedFileFormat(ReadOnlySpan header) @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// True, if its a valid RIFF FourCC. private bool IsRiffContainer(ReadOnlySpan header) { - return header.Slice(0, 4).SequenceEqual(WebPConstants.RiffFourCc); + return header.Slice(0, 4).SequenceEqual(WebpConstants.RiffFourCc); } /// @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// True, if its a webp file. private bool IsWebPFile(ReadOnlySpan header) { - return header.Slice(8, 4).SequenceEqual(WebPConstants.WebPHeader); + return header.Slice(8, 4).SequenceEqual(WebpConstants.WebPHeader); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 89c8170a3..3fa84e7a8 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { - internal class WebPImageInfo : IDisposable + internal class WebpImageInfo : IDisposable { /// /// Gets or sets the bitmap width in pixels. @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Gets or sets the bits per pixel. /// - public WebPBitsPerPixel BitsPerPixel { get; set; } + public WebpBitsPerPixel BitsPerPixel { get; set; } /// /// Gets or sets a value indicating whether this image uses lossless compression. @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Gets or sets additional features present in a VP8X image. /// - public WebPFeatures Features { get; set; } + public WebpFeatures Features { get; set; } /// /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index d71949fa5..14e5d333b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { #pragma warning disable SA1201 // Elements should appear in the correct order - internal static class WebPLookupTables + internal static class WebpLookupTables { public static readonly Dictionary Abs0; @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public static readonly ushort[] GammaToLinearTab = new ushort[256]; - public static readonly int[] LinearToGammaTab = new int[WebPConstants.GammaTabSize + 1]; + public static readonly int[] LinearToGammaTab = new int[WebpConstants.GammaTabSize + 1]; public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; @@ -30,28 +30,28 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public static readonly int[] Vp8DspScan = { // Luma - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), - 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), - 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), - 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), + 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), + 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), + 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U - 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U + 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V }; public static readonly short[] Vp8Scan = { // Luma - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), - 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), - 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), - 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), + 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), + 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), + 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), }; public static readonly short[] Vp8ScanUv = { - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U - 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U + 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V }; // This table gives, for a given sharpness, the filtering strength to be @@ -1092,18 +1092,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP 515, 515, 514, 514 }; - static WebPLookupTables() + static WebpLookupTables() { - double scale = (double)(1 << WebPConstants.GammaTabFix) / WebPConstants.GammaScale; + double scale = (double)(1 << WebpConstants.GammaTabFix) / WebpConstants.GammaScale; double norm = 1.0d / 255.0d; for (int v = 0; v < 256; ++v) { - GammaToLinearTab[v] = (ushort)((Math.Pow(norm * v, WebPConstants.Gamma) * WebPConstants.GammaScale) + .5); + GammaToLinearTab[v] = (ushort)((Math.Pow(norm * v, WebpConstants.Gamma) * WebpConstants.GammaScale) + .5); } - for (int v = 0; v <= WebPConstants.GammaTabSize; ++v) + for (int v = 0; v <= WebpConstants.GammaTabSize; ++v) { - LinearToGammaTab[v] = (int)((255.0d * Math.Pow(scale * v, 1.0d / WebPConstants.Gamma)) + .5); + LinearToGammaTab[v] = (int)((255.0d * Math.Pow(scale * v, 1.0d / WebpConstants.Gamma)) + .5); } Abs0 = new Dictionary(); diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 7433072ac..60fccc020 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -3,25 +3,25 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// Provides WebP specific metadata information for the image. /// - public class WebPMetadata : IDeepCloneable + public class WebpMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public WebPMetadata() + public WebpMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private WebPMetadata(WebPMetadata other) + private WebpMetadata(WebpMetadata other) { this.Animated = other.Animated; this.Format = other.Format; @@ -30,12 +30,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP /// /// Gets or sets the webp format used. Either lossless or lossy. /// - public WebPFormatType Format { get; set; } + public WebpFormatType Format { get; set; } /// /// Gets or sets all found chunk types ordered by appearance. /// - public Queue ChunkTypes { get; set; } = new Queue(); + public Queue ChunkTypes { get; set; } = new Queue(); /// /// Gets or sets a value indicating whether the webp file contains an animation. @@ -43,6 +43,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP public bool Animated { get; set; } = false; /// - public IDeepCloneable DeepClone() => new WebPMetadata(this); + public IDeepCloneable DeepClone() => new WebpMetadata(this); } } diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs index bbe399a36..4918e1ed3 100644 --- a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -4,9 +4,9 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { - internal static class WebPThrowHelper + internal static class WebpThrowHelper { /// /// Cold path optimization for throwing -s diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index ec2571807..d032578ca 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -1,20 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.WebP +namespace SixLabors.ImageSharp.Formats.Experimental.Webp { /// /// EXPERIMENTAL: /// Registers the image encoders, decoders and mime type detectors for the webp format. /// - public sealed class WebPConfigurationModule : IConfigurationModule + public sealed class WebpConfigurationModule : IConfigurationModule { /// public void Configure(Configuration configuration) { - configuration.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); - configuration.ImageFormatsManager.SetEncoder(WebPFormat.Instance, new WebPEncoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); + configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 84130cf40..9f6130bd5 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -5,7 +5,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void ReadImages() { this.configuration = Configuration.CreateDefaultInstance(); - new WebPConfigurationModule().Configure(this.configuration); + new WebpConfigurationModule().Configure(this.configuration); this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index bc147f430..3aa23c7ff 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -4,7 +4,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void ImageSharpWebpLossy() { using var memoryStream = new MemoryStream(); - this.webp.Save(memoryStream, new WebPEncoder() + this.webp.Save(memoryStream, new WebpEncoder() { Lossy = true }); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void ImageSharpWebpLossless() { using var memoryStream = new MemoryStream(); - this.webp.Save(memoryStream, new WebPEncoder() + this.webp.Save(memoryStream, new WebpEncoder() { Lossy = false }); diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index d6ba59e4b..babda726f 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -7,7 +7,7 @@ using System.Linq; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs index b20376b47..0fd9574af 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { public ImageExtensionsTest() { - Configuration.Default.ImageFormatsManager.AddImageFormat(WebPFormat.Instance); - Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); - Configuration.Default.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); - Configuration.Default.ImageFormatsManager.SetEncoder(WebPFormat.Instance, new WebPEncoder()); + Configuration.Default.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + Configuration.Default.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + Configuration.Default.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); } [Fact] @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - image.SaveAsWebP(file); + image.SaveAsWebp(file); } using (Image.Load(file, out IImageFormat mime)) @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - await image.SaveAsWebPAsync(file); + await image.SaveAsWebpAsync(file); } using (Image.Load(file, out IImageFormat mime)) @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - image.SaveAsWebP(file, new WebPEncoder()); + image.SaveAsWebp(file, new WebpEncoder()); } using (Image.Load(file, out IImageFormat mime)) @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - await image.SaveAsWebPAsync(file, new WebPEncoder()); + await image.SaveAsWebpAsync(file, new WebpEncoder()); } using (Image.Load(file, out IImageFormat mime)) @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - image.SaveAsWebP(memoryStream); + image.SaveAsWebp(memoryStream); } memoryStream.Position = 0; @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - await image.SaveAsWebPAsync(memoryStream); + await image.SaveAsWebpAsync(memoryStream); } memoryStream.Position = 0; @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - image.SaveAsWebp(memoryStream, new WebPEncoder()); + image.SaveAsWebp(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using (var image = new Image(10, 10)) { - await image.SaveAsWebPAsync(memoryStream, new WebPEncoder()); + await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index c58d4b4e4..35aca6999 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -3,29 +3,29 @@ using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { using static SixLabors.ImageSharp.Tests.TestImages.WebP; [Trait("Format", "Webp")] - public class WebPDecoderTests + public class WebpDecoderTests { - private static WebPDecoder WebpDecoder => new WebPDecoder(); + private static WebpDecoder WebpDecoder => new WebpDecoder(); private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); - public WebPDecoderTests() + public WebpDecoderTests() { - Configuration.Default.ImageFormatsManager.AddImageFormat(WebPFormat.Instance); - Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); - Configuration.Default.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); + Configuration.Default.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + Configuration.Default.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 8143503a1..3f6daa312 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -1,17 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { using static TestImages.WebP; [Trait("Format", "Webp")] - public class WebPEncoderTests + public class WebpEncoderTests { [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void Encode_Lossless_WithDifferentQuality_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { - var encoder = new WebPEncoder() + var encoder = new WebpEncoder() { Lossy = false, Quality = quality @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void Encode_Lossless_WithDifferentMethods_Works(TestImageProvider provider, int method) where TPixel : unmanaged, IPixel { - var encoder = new WebPEncoder() + var encoder = new WebpEncoder() { Lossy = false, Method = method, @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { - var encoder = new WebPEncoder() + var encoder = new WebpEncoder() { Lossy = true, Quality = quality @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP where TPixel : unmanaged, IPixel { int quality = 75; - var encoder = new WebPEncoder() + var encoder = new WebpEncoder() { Lossy = true, Method = method, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index 375770117..b73dcdfcf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -1,15 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] - public class WebPMetadataTests + public class WebpMetaDataTests { [Theory] [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, false)] @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void IgnoreMetadata_ControlsWhetherExifIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebPDecoder { IgnoreMetadata = ignoreMetadata }; + var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; using (Image image = provider.GetImage(decoder)) { @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebPDecoder { IgnoreMetadata = ignoreMetadata }; + var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; using (Image image = provider.GetImage(decoder)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 583fb9514..f6b5b4ca5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,7 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests new JpegConfigurationModule(), new GifConfigurationModule(), new TgaConfigurationModule(), - new WebPConfigurationModule()); + new WebpConfigurationModule()); // Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 7f5627a31..e8c4a1329 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -5,7 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.WebP; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebPEncoder))] + [InlineData("lol/foobar.webp", typeof(WebpEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) { if (!TestEnvironment.IsWindows) @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebPDecoder))] + [InlineData("lol/foobar.webp", typeof(WebpDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { if (!TestEnvironment.IsWindows) @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebPEncoder))] + [InlineData("lol/foobar.webp", typeof(WebpEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { if (!TestEnvironment.IsLinux) @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebPDecoder))] + [InlineData("lol/foobar.webp", typeof(WebpDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { if (!TestEnvironment.IsLinux) From 5330b5c1c6c70953479e7f7d1358a31ab29a2683 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 10 Dec 2020 18:16:35 +0100 Subject: [PATCH 246/359] Use configuration instance in webp tests instead of changing the default config --- .../Formats/WebP/ImageExtensionsTest.cs | 43 ++++++++++--------- .../Formats/WebP/WebPDecoderTests.cs | 15 ++++--- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs index 0fd9574af..003ca54bb 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs @@ -13,12 +13,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class ImageExtensionsTest { + private readonly Configuration configuration; + public ImageExtensionsTest() { - Configuration.Default.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - Configuration.Default.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); - Configuration.Default.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + this.configuration = new Configuration(); + this.configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + this.configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + this.configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + this.configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); } [Fact] @@ -27,12 +30,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { image.SaveAsWebp(file); } - using (Image.Load(file, out IImageFormat mime)) + using (Image.Load(this.configuration, file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -44,12 +47,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { await image.SaveAsWebpAsync(file); } - using (Image.Load(file, out IImageFormat mime)) + using (Image.Load(this.configuration, file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -61,12 +64,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { image.SaveAsWebp(file, new WebpEncoder()); } - using (Image.Load(file, out IImageFormat mime)) + using (Image.Load(this.configuration, file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -78,12 +81,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { await image.SaveAsWebpAsync(file, new WebpEncoder()); } - using (Image.Load(file, out IImageFormat mime)) + using (Image.Load(this.configuration, file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -94,14 +97,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { image.SaveAsWebp(memoryStream); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -112,14 +115,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { await image.SaveAsWebpAsync(memoryStream); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -130,14 +133,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { image.SaveAsWebp(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -148,14 +151,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) + using (var image = new Image(this.configuration, 10, 10)) { await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 35aca6999..ec25b2afb 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -9,23 +9,26 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.WebP; + // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Webp { - using static SixLabors.ImageSharp.Tests.TestImages.WebP; - [Trait("Format", "Webp")] public class WebpDecoderTests { + private readonly Configuration configuration; + private static WebpDecoder WebpDecoder => new WebpDecoder(); private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); public WebpDecoderTests() { - Configuration.Default.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - Configuration.Default.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + this.configuration = new Configuration(); + this.configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + this.configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + this.configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); } [Theory] @@ -44,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - IImageInfo imageInfo = Image.Identify(stream); + IImageInfo imageInfo = Image.Identify(this.configuration, stream); Assert.NotNull(imageInfo); Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedHeight, imageInfo.Height); From 1e0d1e5dd22ef3fe54f4b1a347bf85b613782016 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 11 Dec 2020 09:56:59 +0100 Subject: [PATCH 247/359] Add helper method to register the webp format --- src/ImageSharp/Configuration.cs | 12 ++++++++++++ .../Formats/WebP/ImageExtensionsTest.cs | 5 +---- .../Formats/WebP/WebPDecoderTests.cs | 4 +--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 43c7d03f7..4a4074a04 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; @@ -173,6 +174,17 @@ namespace SixLabors.ImageSharp }; } + /// + /// Registers the webp format detector, encoder and decoder. + /// + public void AddWebp() + { + this.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + this.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + this.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + this.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + } + /// /// Creates the default instance with the following s preregistered: /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs index 003ca54bb..ac60fc482 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs @@ -18,10 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public ImageExtensionsTest() { this.configuration = new Configuration(); - this.configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - this.configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - this.configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); - this.configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + this.configuration.AddWebp(); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index ec25b2afb..eb16a01b8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -26,9 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public WebpDecoderTests() { this.configuration = new Configuration(); - this.configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - this.configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - this.configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + this.configuration.AddWebp(); } [Theory] From 3ca3f5f8764dc45bf1ce2e9a651095bd43f7e769 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Dec 2020 17:40:52 +0100 Subject: [PATCH 248/359] Move AddWebp to a extension method --- src/ImageSharp/Configuration.cs | 12 ---------- .../Formats/WebP/ConfigurationExtensions.cs | 23 +++++++++++++++++++ .../Formats/WebP/WebPEncoderTests.cs | 3 +-- 3 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 4a4074a04..43c7d03f7 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,7 +6,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; @@ -174,17 +173,6 @@ namespace SixLabors.ImageSharp }; } - /// - /// Registers the webp format detector, encoder and decoder. - /// - public void AddWebp() - { - this.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - this.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - this.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); - this.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); - } - /// /// Creates the default instance with the following s preregistered: /// diff --git a/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs b/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs new file mode 100644 index 000000000..7077bf9a0 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Experimental.Webp +{ + /// + /// Helper methods for the Configuration. + /// + public static class ConfigurationExtensions + { + /// + /// Registers the webp format detector, encoder and decoder. + /// + /// The configuration. + public static void AddWebp(this Configuration configuration) + { + configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); + configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 3f6daa312..ac2fcaae7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -5,11 +5,10 @@ using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.WebP; namespace SixLabors.ImageSharp.Tests.Formats.Webp { - using static TestImages.WebP; - [Trait("Format", "Webp")] public class WebpEncoderTests { From bcd4c4c0cc33d7151d406b1be9f90714662177e9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Dec 2020 19:07:12 +0000 Subject: [PATCH 249/359] Rename WebP to Webp --- ...WebPDecoderOptions.cs => IWebpDecoderOptions.cs} | 0 ...WebPEncoderOptions.cs => IWebpEncoderOptions.cs} | 0 ...ebPLosslessDecoder.cs => WebpLosslessDecoder.cs} | 0 ...df => Webp_Lossless_Bitstream_Specification.pdf} | Bin .../Lossy/{VP8BandProbas.cs => Vp8BandProbas.cs} | 0 .../{WebPLossyDecoder.cs => WebpLossyDecoder.cs} | 0 ...ebPAlphaFilterType.cs => WebpAlphaFilterType.cs} | 0 .../{WebPBitsPerPixel.cs => WebpBitsPerPixel.cs} | 0 .../WebP/{WebPChunkType.cs => WebpChunkType.cs} | 0 .../WebP/{WebPCommonUtils.cs => WebpCommonUtils.cs} | 0 .../WebP/{WebPConstants.cs => WebpConstants.cs} | 0 .../Formats/WebP/{WebPDecoder.cs => WebpDecoder.cs} | 0 .../WebP/{WebPDecoderCore.cs => WebpDecoderCore.cs} | 0 .../Formats/WebP/{WebPEncoder.cs => WebpEncoder.cs} | 0 .../WebP/{WebPEncoderCore.cs => WebpEncoderCore.cs} | 0 .../WebP/{WebPFeatures.cs => WebpFeatures.cs} | 0 .../Formats/WebP/{WebPFormat.cs => WebpFormat.cs} | 0 .../WebP/{WebPFormatType.cs => WebpFormatType.cs} | 0 ...FormatDetector.cs => WebpImageFormatDetector.cs} | 0 .../WebP/{WebPImageInfo.cs => WebpImageInfo.cs} | 0 .../{WebPLookupTables.cs => WebpLookupTables.cs} | 0 .../WebP/{WebPMetadata.cs => WebpMetadata.cs} | 0 .../WebP/{WebPThrowHelper.cs => WebpThrowHelper.cs} | 0 ...ication.pdf => Webp_Container_Specification.pdf} | Bin .../{WebPDecoderTests.cs => WebpDecoderTests.cs} | 0 .../{WebPEncoderTests.cs => WebpEncoderTests.cs} | 0 .../{WebPMetaDataTests.cs => WebpMetaDataTests.cs} | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename src/ImageSharp/Formats/WebP/{IWebPDecoderOptions.cs => IWebpDecoderOptions.cs} (100%) rename src/ImageSharp/Formats/WebP/{IWebPEncoderOptions.cs => IWebpEncoderOptions.cs} (100%) rename src/ImageSharp/Formats/WebP/Lossless/{WebPLosslessDecoder.cs => WebpLosslessDecoder.cs} (100%) rename src/ImageSharp/Formats/WebP/Lossless/{WebP_Lossless_Bitstream_Specification.pdf => Webp_Lossless_Bitstream_Specification.pdf} (100%) rename src/ImageSharp/Formats/WebP/Lossy/{VP8BandProbas.cs => Vp8BandProbas.cs} (100%) rename src/ImageSharp/Formats/WebP/Lossy/{WebPLossyDecoder.cs => WebpLossyDecoder.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPAlphaFilterType.cs => WebpAlphaFilterType.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPBitsPerPixel.cs => WebpBitsPerPixel.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPChunkType.cs => WebpChunkType.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPCommonUtils.cs => WebpCommonUtils.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPConstants.cs => WebpConstants.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPDecoder.cs => WebpDecoder.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPDecoderCore.cs => WebpDecoderCore.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPEncoder.cs => WebpEncoder.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPEncoderCore.cs => WebpEncoderCore.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPFeatures.cs => WebpFeatures.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPFormat.cs => WebpFormat.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPFormatType.cs => WebpFormatType.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPImageFormatDetector.cs => WebpImageFormatDetector.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPImageInfo.cs => WebpImageInfo.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPLookupTables.cs => WebpLookupTables.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPMetadata.cs => WebpMetadata.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebPThrowHelper.cs => WebpThrowHelper.cs} (100%) rename src/ImageSharp/Formats/WebP/{WebP_Container_Specification.pdf => Webp_Container_Specification.pdf} (100%) rename tests/ImageSharp.Tests/Formats/WebP/{WebPDecoderTests.cs => WebpDecoderTests.cs} (100%) rename tests/ImageSharp.Tests/Formats/WebP/{WebPEncoderTests.cs => WebpEncoderTests.cs} (100%) rename tests/ImageSharp.Tests/Formats/WebP/{WebPMetaDataTests.cs => WebpMetaDataTests.cs} (100%) diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs rename to src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs rename to src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs rename to src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebP_Lossless_Bitstream_Specification.pdf b/src/ImageSharp/Formats/WebP/Lossless/Webp_Lossless_Bitstream_Specification.pdf similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/WebP_Lossless_Bitstream_Specification.pdf rename to src/ImageSharp/Formats/WebP/Lossless/Webp_Lossless_Bitstream_Specification.pdf diff --git a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs rename to src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs rename to src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs rename to src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebpChunkType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPChunkType.cs rename to src/ImageSharp/Formats/WebP/WebpChunkType.cs diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPCommonUtils.cs rename to src/ImageSharp/Formats/WebP/WebpCommonUtils.cs diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPConstants.cs rename to src/ImageSharp/Formats/WebP/WebpConstants.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebpDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPDecoder.cs rename to src/ImageSharp/Formats/WebP/WebpDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPDecoderCore.cs rename to src/ImageSharp/Formats/WebP/WebpDecoderCore.cs diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPEncoder.cs rename to src/ImageSharp/Formats/WebP/WebpEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPEncoderCore.cs rename to src/ImageSharp/Formats/WebP/WebpEncoderCore.cs diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebpFeatures.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPFeatures.cs rename to src/ImageSharp/Formats/WebP/WebpFeatures.cs diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebpFormat.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPFormat.cs rename to src/ImageSharp/Formats/WebP/WebpFormat.cs diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebpFormatType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPFormatType.cs rename to src/ImageSharp/Formats/WebP/WebpFormatType.cs diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs rename to src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebpImageInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPImageInfo.cs rename to src/ImageSharp/Formats/WebP/WebpImageInfo.cs diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPLookupTables.cs rename to src/ImageSharp/Formats/WebP/WebpLookupTables.cs diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebpMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPMetadata.cs rename to src/ImageSharp/Formats/WebP/WebpMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebPThrowHelper.cs rename to src/ImageSharp/Formats/WebP/WebpThrowHelper.cs diff --git a/src/ImageSharp/Formats/WebP/WebP_Container_Specification.pdf b/src/ImageSharp/Formats/WebP/Webp_Container_Specification.pdf similarity index 100% rename from src/ImageSharp/Formats/WebP/WebP_Container_Specification.pdf rename to src/ImageSharp/Formats/WebP/Webp_Container_Specification.pdf diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs rename to tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs rename to tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs rename to tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs From df8e5fa8bc66f8e75b5c3e3b5988c93442824afe Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 15 Dec 2020 15:10:19 +0100 Subject: [PATCH 250/359] Small code cleanup / improvements --- .../Formats/WebP/BitReader/Vp8BitReader.cs | 5 +-- .../Formats/WebP/BitWriter/BitWriterBase.cs | 7 ++--- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- .../WebP/Lossless/CostCacheInterval.cs | 2 +- .../Formats/WebP/Lossless/CostManager.cs | 7 ++--- .../Formats/WebP/Lossless/HistogramEncoder.cs | 9 ++---- .../Formats/WebP/Lossless/HuffmanUtils.cs | 5 ++- .../Formats/WebP/Lossless/LosslessUtils.cs | 25 +++++++-------- .../Formats/WebP/Lossless/PredictorEncoder.cs | 14 ++++----- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 4 +-- .../Formats/WebP/Lossless/Vp8LTransform.cs | 2 +- .../WebP/Lossless/WebpLosslessDecoder.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 12 +++---- .../Formats/WebP/Lossy/Vp8Decoder.cs | 16 ++-------- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 16 ++-------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 11 ++++--- .../Formats/WebP/Lossy/WebpLossyDecoder.cs | 2 -- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 31 ------------------- .../Formats/WebP/WebpAlphaFilterType.cs | 2 +- .../Formats/WebP/WebpDecoderCore.cs | 3 +- .../Formats/WebP/WebpEncoderCore.cs | 13 ++------ src/ImageSharp/Formats/WebP/WebpMetadata.cs | 2 +- 23 files changed, 52 insertions(+), 141 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/Vp8HeaderType.cs diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 5cc12259a..fde850025 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -80,10 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader this.InitBitreader(partitionLength, startPos); } - public int Pos - { - get { return (int)this.pos; } - } + public int Pos => (int)this.pos; public uint ImageDataSize { get; } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index f12bacd87..46f2dbdaf 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// The expected size in bytes. protected BitWriterBase(int expectedSize) { - // TODO: use memory allocator here. + // TODO: should we use memory allocator here? this.buffer = new byte[expectedSize]; } @@ -30,10 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// private protected BitWriterBase(byte[] buffer) => this.buffer = buffer; - public byte[] Buffer - { - get { return this.buffer; } - } + public byte[] Buffer => this.buffer; /// /// Writes the encoded bytes of the image to the stream. Call Finish() before this. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index e1483ce30..b9d326ada 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter private uint pos; - private int maxPos; + private readonly int maxPos; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 4bf3d5f05..1764a190c 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter // If needed, make some room by flushing some bits out. if (this.cur + WriterBytes > this.end) { - var extraSize = (this.end - this.cur) + MinExtraSize; + var extraSize = this.end - this.cur + MinExtraSize; this.BitWriterResize(extraSize); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index c6febb82d..2c09be2cb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. /// - [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}, Position: {Position}")] + [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] internal class CostCacheInterval { public double Cost { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index 0cf6df2a7..1eccc7af7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -255,17 +255,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// private void PositionOrphanInterval(CostInterval current, CostInterval previous) { - if (previous == null) - { - previous = this.head; - } + previous ??= this.head; while (previous != null && current.Start < previous.Start) { previous = previous.Previous; } - while (previous != null && previous.Next != null && previous.Next.Start < current.Start) + while (previous?.Next != null && previous.Next.Start < current.Start) { previous = previous.Next; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 6b1fee5a6..dd4a91961 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -259,8 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless doContinue = false; for (int i = 0; i < numClusters; i++) { - int k; - k = clusterMappings[i]; + int k = clusterMappings[i]; while (k != clusterMappings[k]) { clusterMappings[k] = clusterMappings[clusterMappings[k]]; @@ -336,8 +335,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) { double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; - int bestIdx1 = -1; - int bestIdx2 = 1; int numTries = numUsed / 2; uint randRange = (uint)((numUsed - 1) * numUsed); @@ -377,8 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } // Get the best histograms. - bestIdx1 = histoPriorityList[0].Idx1; - bestIdx2 = histoPriorityList[0].Idx2; + var bestIdx1 = histoPriorityList[0].Idx1; + var bestIdx2 = histoPriorityList[0].Idx2; var mappingIndex = Array.IndexOf(mappings, bestIdx2); Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 1329802eb..c467ff827 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; // Pre-reversed 4-bit values. - private static byte[] reversedBits = + private static readonly byte[] ReversedBits = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf @@ -427,7 +427,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless tableSize = 1 << tableBits; totalSize += tableSize; low = key & mask; - uint v = (uint)(tablePos - low); table[low] = new HuffmanCode { BitsUsed = tableBits + rootBits, @@ -589,7 +588,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless while (i < numBits) { i += 4; - retval |= (uint)(reversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); + retval |= (uint)(ReversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); bits >>= 4; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 9f370513b..b37a9dd3a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// internal static unsafe class LosslessUtils { - private const uint Predictor0 = WebpConstants.ArgbBlack; - private const int PrefixLookupIdxMax = 512; private const int LogLookupIdxMax = 256; @@ -298,8 +296,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless Span transformData = transform.Data.GetSpan(); // First Row follows the L (mode=1) mode. - PredictorAdd0(input, null, 1, output); - PredictorAdd1(input + 1, null, width - 1, output + 1); + PredictorAdd0(input, 1, output); + PredictorAdd1(input + 1, width - 1, output + 1); input += width; output += width; @@ -333,10 +331,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless switch (predictorMode) { case 0: - PredictorAdd0(input + x, output + x - width, xEnd - x, output + x); + PredictorAdd0(input + x, xEnd - x, output + x); break; case 1: - PredictorAdd1(input + x, output + x - width, xEnd - x, output + x); + PredictorAdd1(input + x, xEnd - x, output + x); break; case 2: PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); @@ -549,14 +547,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { int logCnt = 0; uint y = 1; - int correction = 0; - float vF = (float)v; + float vF = v; uint origV = v; do { ++logCnt; - v = v >> 1; - y = y << 1; + v >>= 1; + y <<= 1; } while (v >= LogLookupIdxMax); @@ -566,7 +563,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless // The correction factor: log(1 + d) ~ d; for very small d values, so // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v // LOG_2_RECIPROCAL ~ 23/16 - correction = (int)((23 * (origV & (y - 1))) >> 4); + var correction = (int)((23 * (origV & (y - 1))) >> 4); return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; } else @@ -633,7 +630,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd0(uint* input, uint* upper, int numberOfPixels, uint* output) + private static void PredictorAdd0(uint* input, int numberOfPixels, uint* output) { for (int x = 0; x < numberOfPixels; ++x) { @@ -642,7 +639,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd1(uint* input, uint* upper, int numberOfPixels, uint* output) + private static void PredictorAdd1(uint* input, int numberOfPixels, uint* output) { uint left = output[-1]; for (int x = 0; x < numberOfPixels; ++x) @@ -1092,7 +1089,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static int ColorTransformDelta(sbyte colorPred, sbyte color) { - return ((int)colorPred * color) >> 5; + return (colorPred * color) >> 5; } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index b41a372fc..c03000fd1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -210,7 +210,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless for (int mode = 0; mode < numPredModes; mode++) { - float curDiff; for (int i = 0; i < 4; i++) { histoArgb[i].AsSpan().Fill(0); @@ -256,7 +255,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } - curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); + var curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); // Favor keeping the areas locally similar. if (mode == leftMode) @@ -436,16 +435,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) { - int quantization; byte newGreen = 0; byte greenDiff = 0; - byte a, r, g, b; + byte a; if (maxDiff <= 2) { return LosslessUtils.SubPixels(value, predict); } - quantization = maxQuantization; + var quantization = maxQuantization; while (quantization >= maxDiff) { quantization >>= 1; @@ -461,7 +459,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); } - g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); + var g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); if (usedSubtractGreen) { @@ -475,8 +473,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); } - r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); - b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); + var r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); + var b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 5b7587471..f1952c77a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// Encoder for lossless webp images. /// - internal partial class Vp8LEncoder : IDisposable + internal class Vp8LEncoder : IDisposable { /// /// Maximum number of reference blocks the image will be segmented into. @@ -1452,7 +1452,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) { - long totalLengthSize = 0; int maxNumSymbols = 0; // Iterate over all histograms and get the aggregate number of codes used. @@ -1466,7 +1465,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless (k == 0) ? histo.NumCodes() : (k == 4) ? WebpConstants.NumDistanceCodes : 256; huffmanCodes[startIdx + k].NumSymbols = numSymbols; - totalLengthSize += numSymbols; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index 3bfa94525..de6b9d222 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// Data associated with a VP8L transformation to reduce the entropy. /// - [DebuggerDisplay("Transformtype: {TransformType}")] + [DebuggerDisplay("Transformtype: {" + nameof(TransformType) + "}")] internal class Vp8LTransform { public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index 79fbf4bbc..7a58ccba0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index 58d441e69..e45ba6b06 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -24,8 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy public static int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) { - int nz; - nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; + var nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; nz |= QuantEnc.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; return nz; } @@ -110,16 +109,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy { Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); Span left = it.LeftDerr.AsSpan(ch, 2); - int err0, err1, err2, err3; Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - err0 = QuantEnc.QuantizeSingle(c, mtx); + var err0 = QuantEnc.QuantizeSingle(c, mtx); c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); + var err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); + var err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); + var err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); // TODO: set errors in rd // rd->derr[ch][0] = (int8_t)err1; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 070da84ae..7a1b7ba3b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -239,13 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// public Vp8FilterInfo[] FilterInfo { get; set; } - public Vp8MacroBlock CurrentMacroBlock - { - get - { - return this.MacroBlockInfo[this.MbX]; - } - } + public Vp8MacroBlock CurrentMacroBlock => this.MacroBlockInfo[this.MbX]; public Vp8MacroBlock LeftMacroBlock { @@ -260,13 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } } - public Vp8MacroBlockData CurrentBlockData - { - get - { - return this.MacroBlockData[this.MbX]; - } - } + public Vp8MacroBlockData CurrentBlockData => this.MacroBlockData[this.MbX]; public void PrecomputeFilterStrengths() { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 8f8c7676d..38640e75f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -153,13 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// /// Gets the current start index of the intra mode predictors. /// - public int PredIdx - { - get - { - return this.predIdx; - } - } + public int PredIdx => this.predIdx; /// /// Gets the non-zero pattern. @@ -216,13 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// public int CountDown { get; set; } - public Vp8MacroBlockInfo CurrentMacroBlockInfo - { - get - { - return this.Mb[this.currentMbIdx]; - } - } + public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; private Vp8MacroBlockInfo[] Mb { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 333b845bc..eed716c1b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -358,8 +358,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy var alphas = new int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); int totalMb = this.mbw * this.mbw; - this.alpha = this.alpha / totalMb; - this.uvAlpha = this.uvAlpha / totalMb; + this.alpha /= totalMb; + this.uvAlpha /= totalMb; // Analysis is done, proceed to actual encoding. this.segmentHeader = new Vp8EncSegmentHeader(4); @@ -601,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy var centers = new int[NumMbSegments]; int weightedAverage = 0; var map = new int[WebpConstants.MaxAlpha + 1]; - int a, n, k; + int n, k; var accum = new int[NumMbSegments]; var distAccum = new int[NumMbSegments]; @@ -635,6 +635,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy // Assign nearest center for each 'a' n = 0; // track the nearest center for current 'a' + int a; for (a = minA; a <= maxA; ++a) { if (alphas[a] != 0) @@ -871,8 +872,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)]; var qi4 = m.Y1.Expand(0); - var qi16 = m.Y2.Expand(1); - var quv = m.Uv.Expand(2); + m.Y2.Expand(1); // qi16 + m.Uv.Expand(2); // quv m.I4Penalty = 1000 * qi4 * qi4; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index cd501c45a..058d4adb9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -807,8 +807,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy case 1: LossyUtils.TransformDc(src, dst); break; - default: - break; } } diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs deleted file mode 100644 index 313b8e074..000000000 --- a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Experimental.Webp -{ - /// - /// Enum for the different VP8 chunk header types. - /// - public enum Vp8HeaderType - { - /// - /// Invalid VP8 header. - /// - Invalid = 0, - - /// - /// A VP8 header. - /// - Vp8 = 1, - - /// - /// VP8 header, signaling the use of VP8L lossless format. - /// - Vp8L = 2, - - /// - /// Header for a extended-VP8 chunk. - /// - Vp8X = 3, - } -} diff --git a/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs index a52d18d15..f32d2cf68 100644 --- a/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// Enum for the different alpha filter types. /// - internal enum WebpAlphaFilterType : int + internal enum WebpAlphaFilterType { /// /// No filtering. diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index 5f260deb7..24967841d 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -193,8 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp // The first two bit of it are reserved and should be 0. if (imageFeatures >> 6 != 0) { - WebpThrowHelper.ThrowImageFormatException( - "first two bits of the VP8X header are expected to be zero"); + WebpThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); } // If bit 3 is set, a ICC Profile Chunk should be present. diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 73817e691..2ff6c6001 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -4,11 +4,9 @@ using System.IO; using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Experimental.Webp @@ -24,14 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp private readonly MemoryAllocator memoryAllocator; /// - /// The global configuration. - /// - private Configuration configuration; - - /// + /// TODO: not used at the moment. /// Indicating whether the alpha plane should be compressed with WebP lossless format. /// - private bool alphaCompression; + private readonly bool alphaCompression; /// /// Indicating whether lossy compression should be used. If false, lossless compression will be used. @@ -80,9 +74,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - if (this.lossy) { var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method, this.entropyPasses); diff --git a/src/ImageSharp/Formats/WebP/WebpMetadata.cs b/src/ImageSharp/Formats/WebP/WebpMetadata.cs index 60fccc020..dea2672f1 100644 --- a/src/ImageSharp/Formats/WebP/WebpMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebpMetadata.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// Gets or sets a value indicating whether the webp file contains an animation. /// - public bool Animated { get; set; } = false; + public bool Animated { get; set; } /// public IDeepCloneable DeepClone() => new WebpMetadata(this); From ea031c803d71b3a69101aa56aa5d7391b53cf605 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 18 Dec 2020 11:36:16 +0100 Subject: [PATCH 251/359] Attempt to use SSE in Subtract-Green Transform --- .../Formats/WebP/Lossless/LosslessUtils.cs | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index b37a9dd3a..7121a06cd 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -4,9 +4,13 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// @@ -98,7 +102,42 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// The pixel data to apply the transformation. public static void AddGreenToBlueAndRed(Span pixelData) { - for (int i = 0; i < pixelData.Length; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse.IsSupported) + { + var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i < numPixels; i += 4) + { + var idx = (ushort*)p + i; + Vector128 input = Sse2.LoadVector128(idx); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, mask); + Vector128 c = Sse2.ShuffleHigh(b, mask); // 0g0g + Vector128 output = Sse2.Add(input, c); + Sse2.Store(idx, output); + } + + if (i != numPixels) + { + AddGreenToBlueAndRedSequential(pixelData.Slice(i)); + } + } + } + else +#endif + { + AddGreenToBlueAndRedSequential(pixelData); + } + } + + private static void AddGreenToBlueAndRedSequential(Span pixelData) + { + int numPixels = pixelData.Length; + for (int i = 0; i < numPixels; i++) { uint argb = pixelData[i]; uint green = (argb >> 8) & 0xff; @@ -109,8 +148,43 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } - public static void SubtractGreenFromBlueAndRed(Span pixelData, int numPixels) + public static void SubtractGreenFromBlueAndRed(Span pixelData) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse.IsSupported) + { + var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i < numPixels; i += 4) + { + var idx = (ushort*)p + i; + Vector128 input = Sse2.LoadVector128(idx); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, mask); + Vector128 c = Sse2.ShuffleHigh(b, mask); // 0g0g + Vector128 output = Sse2.Subtract(input, c); + Sse2.Store(idx, output); + } + + if (i != numPixels) + { + SubtractGreenFromBlueAndRedSequential(pixelData.Slice(i)); + } + } + } + else +#endif + { + SubtractGreenFromBlueAndRedSequential(pixelData); + } + } + + private static void SubtractGreenFromBlueAndRedSequential(Span pixelData) { + int numPixels = pixelData.Length; for (int i = 0; i < numPixels; i++) { uint argb = pixelData[i]; From 469c5d62640641a7f2220aab24dcef36928bbd36 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 18 Dec 2020 12:38:34 +0100 Subject: [PATCH 252/359] Sse2.Subtract and Sse2.Add need to operate on bytes --- .../Formats/WebP/Lossless/LosslessUtils.cs | 20 +++++++++---------- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 7121a06cd..523b0d804 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -110,15 +110,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless int i; fixed (uint* p = pixelData) { - for (i = 0; i < numPixels; i += 4) + for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = (ushort*)p + i; - Vector128 input = Sse2.LoadVector128(idx); + var idx = p + i; + Vector128 input = Sse2.LoadVector128((ushort*)idx); Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g Vector128 b = Sse2.ShuffleLow(a, mask); Vector128 c = Sse2.ShuffleHigh(b, mask); // 0g0g - Vector128 output = Sse2.Add(input, c); - Sse2.Store(idx, output); + Vector128 output = Sse2.Add(input.AsByte(), c.AsByte()); + Sse2.Store((byte*)idx, output); } if (i != numPixels) @@ -158,15 +158,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless int i; fixed (uint* p = pixelData) { - for (i = 0; i < numPixels; i += 4) + for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = (ushort*)p + i; - Vector128 input = Sse2.LoadVector128(idx); + var idx = p + i; + Vector128 input = Sse2.LoadVector128((ushort*)idx); Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g Vector128 b = Sse2.ShuffleLow(a, mask); Vector128 c = Sse2.ShuffleHigh(b, mask); // 0g0g - Vector128 output = Sse2.Subtract(input, c); - Sse2.Store(idx, output); + Vector128 output = Sse2.Subtract(input.AsByte(), c.AsByte()); + Sse2.Store((byte*)idx, output); } if (i != numPixels) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index f1952c77a..e0d2cd093 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -567,7 +567,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); - LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan(), width * height); + LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan()); } private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) From d0550a4d1425c6f6ce6d1e5e4a27706af3b585d8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 19 Dec 2020 16:59:34 +0100 Subject: [PATCH 253/359] Add Ssse3 variant for substract green transform --- .../Formats/WebP/Lossless/LosslessUtils.cs | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 523b0d804..53decbab6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -103,7 +103,29 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public static void AddGreenToBlueAndRed(Span pixelData) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse.IsSupported) + if (Ssse3.IsSupported) + { + var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i + 4 <= numPixels; i += 4) + { + var idx = p + i; + Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, mask); + Vector128 output = Sse2.Add(input, in0g0g); + Sse2.Store((byte*)idx, output.AsByte()); + } + + if (i != numPixels) + { + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); + } + } + } + else if (Sse.IsSupported) { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; @@ -123,18 +145,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless if (i != numPixels) { - AddGreenToBlueAndRedSequential(pixelData.Slice(i)); + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); } } } else #endif { - AddGreenToBlueAndRedSequential(pixelData); + AddGreenToBlueAndRedNoneVectorized(pixelData); } } - private static void AddGreenToBlueAndRedSequential(Span pixelData) + private static void AddGreenToBlueAndRedNoneVectorized(Span pixelData) { int numPixels = pixelData.Length; for (int i = 0; i < numPixels; i++) @@ -151,7 +173,29 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public static void SubtractGreenFromBlueAndRed(Span pixelData) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse.IsSupported) + if (Ssse3.IsSupported) + { + var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i + 4 <= numPixels; i += 4) + { + var idx = p + i; + Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, mask); + Vector128 output = Sse2.Subtract(input, in0g0g); + Sse2.Store((byte*)idx, output.AsByte()); + } + + if (i != numPixels) + { + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); + } + } + } + else if (Sse.IsSupported) { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; @@ -171,18 +215,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless if (i != numPixels) { - SubtractGreenFromBlueAndRedSequential(pixelData.Slice(i)); + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); } } } else #endif { - SubtractGreenFromBlueAndRedSequential(pixelData); + SubtractGreenFromBlueAndRedNoneVectorized(pixelData); } } - private static void SubtractGreenFromBlueAndRedSequential(Span pixelData) + private static void SubtractGreenFromBlueAndRedNoneVectorized(Span pixelData) { int numPixels = pixelData.Length; for (int i = 0; i < numPixels; i++) From 81276e5f4a9d091df2d203931a8d6306478e49c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 19 Dec 2020 20:13:52 +0100 Subject: [PATCH 254/359] Add AVX version of substract green transform --- .../Formats/WebP/Lossless/LosslessUtils.cs | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 53decbab6..509349b49 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -103,7 +103,29 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public static void AddGreenToBlueAndRed(Span pixelData) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Ssse3.IsSupported) + if (Avx2.IsSupported) + { + var mask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i + 8 <= numPixels; i += 8) + { + var idx = p + i; + Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, mask); + Vector256 output = Avx2.Add(input, in0g0g); + Avx.Store((byte*)idx, output.AsByte()); + } + + if (i != numPixels) + { + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); + } + } + } + else if (Ssse3.IsSupported) { var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); int numPixels = pixelData.Length; @@ -173,7 +195,29 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public static void SubtractGreenFromBlueAndRed(Span pixelData) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Ssse3.IsSupported) + if (Avx2.IsSupported) + { + var mask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); + int numPixels = pixelData.Length; + int i; + fixed (uint* p = pixelData) + { + for (i = 0; i + 8 <= numPixels; i += 8) + { + var idx = p + i; + Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, mask); + Vector256 output = Avx2.Subtract(input, in0g0g); + Avx.Store((byte*)idx, output.AsByte()); + } + + if (i != numPixels) + { + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); + } + } + } + else if (Ssse3.IsSupported) { var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); int numPixels = pixelData.Length; From 6a381421c881bd7c98e43c3c2cf0edffee8a3b00 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Dec 2020 14:48:32 +0100 Subject: [PATCH 255/359] Add tests for substract green transform --- .../Formats/WebP/Lossless/LosslessUtils.cs | 4 +- .../Formats/WebP/LosslessUtilsTests.cs | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 509349b49..861af5a0e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); Vector256 in0g0g = Avx2.Shuffle(input, mask); Vector256 output = Avx2.Add(input, in0g0g); - Avx.Store((byte*)idx, output.AsByte()); + Avx.Store((byte*)idx, output); } if (i != numPixels) @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); Vector256 in0g0g = Avx2.Shuffle(input, mask); Vector256 output = Avx2.Subtract(input, in0g0g); - Avx.Store((byte*)idx, output.AsByte()); + Avx.Store((byte*)idx, output); } if (i != numPixels) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs new file mode 100644 index 000000000..ae1274c08 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + [Trait("Format", "Webp")] + public class LosslessUtilsTests + { + [Fact] + public void SubtractGreen_Works() + { + static void RunTest() + { + uint[] pixelData = + { + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + uint[] expectedOutput = + { + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + } + + [Fact] + public void AddGreenToBlueAndRed_Works() + { + static void RunTest() + { + uint[] pixelData = + { + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + uint[] expectedOutput = + { + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + LosslessUtils.AddGreenToBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + } + } +} From c563ea3401ff452a81bca682cfd1bc46aa87971e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Dec 2020 15:43:48 +0100 Subject: [PATCH 256/359] Split up intrinsics tests, add guard if hw intrinsics are present --- .../Formats/WebP/LosslessUtilsTests.cs | 164 +++++++++++------- 1 file changed, 98 insertions(+), 66 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index ae1274c08..f8b0f5a04 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -10,80 +10,112 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Trait("Format", "Webp")] public class LosslessUtilsTests { + private static void RunSubstractGreenTest() + { + uint[] pixelData = + { + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + uint[] expectedOutput = + { + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } + + private static void RunAddGreenToBlueAndRedTest() + { + uint[] pixelData = + { + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + uint[] expectedOutput = + { + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + LosslessUtils.AddGreenToBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } + [Fact] public void SubtractGreen_Works() { - static void RunTest() - { - uint[] pixelData = - { - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - }; - - uint[] expectedOutput = - { - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - }; - - LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + RunSubstractGreenTest(); } [Fact] public void AddGreenToBlueAndRed_Works() { - static void RunTest() - { - uint[] pixelData = - { - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - }; - - uint[] expectedOutput = - { - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - }; - - LosslessUtils.AddGreenToBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2); - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + RunAddGreenToBlueAndRedTest(); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void SubtractGreen_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.AllowAll); + } + + [Fact] + public void SubtractGreen_WithoutAvx_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX); + } + + [Fact] + public void SubtractGreen_WithoutAvxOrSSSE3_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); + } + + [Fact] + public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); + } + + [Fact] + public void AddGreenToBlueAndRed_WithoutAvx_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX); + } + + [Fact] + public void AddGreenToBlueAndRed_WithoutAvxOrSSSE3_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); } +#endif } } From 47946a283ae1cbd2e6783531fd9e9c347f33cbaa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Dec 2020 15:55:13 +0100 Subject: [PATCH 257/359] Fix check for Sse2.IsSupported instead of just Sse.IsSupported --- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 861af5a0e..94ba24660 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } } - else if (Sse.IsSupported) + else if (Sse2.IsSupported) { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } } - else if (Sse.IsSupported) + else if (Sse2.IsSupported) { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; From 36f7ea0035c8cf1b985215ba4921564f1969bddb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Dec 2020 11:31:02 +0100 Subject: [PATCH 258/359] Use MemoryMarshal.Cast to bgr in DecodePixelValues() --- .../Formats/WebP/Lossy/WebpLossyDecoder.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index 058d4adb9..b16792cef 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -112,43 +112,38 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels) where TPixel : unmanaged, IPixel { int widthMul3 = width * 3; for (int y = 0; y < height; y++) { Span row = pixelData.Slice(y * widthMul3, widthMul3); - Span pixelSpan = pixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.GetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row, - pixelSpan, + decodedPixelRow, width); } } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels, IMemoryOwner alpha) where TPixel : unmanaged, IPixel { TPixel color = default; Span alphaSpan = alpha.Memory.Span; + Span pixelsBgr = MemoryMarshal.Cast(pixelData); for (int y = 0; y < height; y++) { - // TODO: Can we use span.Length here? int yMulWidth = y * width; - Span pixelRow = pixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.GetRowSpan(y); for (int x = 0; x < width; x++) { - // TODO: Could use cast to Bgr24/Bgra32 then set alpha. int offset = yMulWidth + x; - int idxBgr = offset * 3; - byte b = pixelData[idxBgr]; - byte g = pixelData[idxBgr + 1]; - byte r = pixelData[idxBgr + 2]; - byte a = alphaSpan[offset]; - color.FromBgra32(new Bgra32(r, g, b, a)); - pixelRow[x] = color; + Bgr24 bgr = pixelsBgr[offset]; + color.FromBgra32(new Bgra32(bgr.R, bgr.G, bgr.B, alphaSpan[offset])); + decodedPixelRow[x] = color; } } } From 6678fba0c2abffd36c0b4514a968f4d696101922 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Dec 2020 11:43:05 +0100 Subject: [PATCH 259/359] Dont use enumerator --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 9b7e16389..f5f713fe1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -138,12 +138,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless colorCache[i].Init(i); } - // TODO: Don't use the enumerator here. // Find the cacheBits giving the lowest entropy. - using List.Enumerator c = refs.Refs.GetEnumerator(); - while (c.MoveNext()) + for (int idx = 0; idx < refs.Refs.Count; idx++) { - PixOrCopy v = c.Current; + PixOrCopy v = refs.Refs[idx]; if (v.IsLiteral()) { uint pix = bgra[pos++]; @@ -743,14 +741,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// private static void BackwardRefsWithLocalCache(Span bgra, int cacheBits, Vp8LBackwardRefs refs) { - // TODO: Don't use enumerator. int pixelIndex = 0; - using List.Enumerator c = refs.Refs.GetEnumerator(); var colorCache = new ColorCache(); colorCache.Init(cacheBits); - while (c.MoveNext()) + for (int idx = 0; idx < refs.Refs.Count; idx++) { - PixOrCopy v = c.Current; + PixOrCopy v = refs.Refs[idx]; if (v.IsLiteral()) { uint bgraLiteral = v.BgraOrDistance; From 92da90322e6e7095f70cb46c2f05447e99d5db4d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 22 Dec 2020 13:16:49 +0100 Subject: [PATCH 260/359] Add SSE4 version of CollectColorRedTransforms --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 90 ++++++++++--- .../Formats/WebP/PredictorEncoderTests.cs | 123 ++++++++++++++++++ 2 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index c03000fd1..d84670889 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -5,6 +5,11 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { /// @@ -84,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless usedSubtractGreen); } - public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span argb, Span image) + public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span bgra, Span image) { int maxTileSize = 1 << bits; int tileXSize = LosslessUtils.SubSampleSize(width, bits); @@ -118,10 +123,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless height, accumulatedRedHisto, accumulatedBlueHisto, - argb); + bgra); image[offset] = MultipliersToColorCode(prevX); - CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, argb); + CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra); // Gather accumulated histogram data. for (int y = tileYOffset; y < allYMax; y++) @@ -131,13 +136,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless for (; ix < ixEnd; ix++) { - uint pix = argb[ix]; - if (ix >= 2 && pix == argb[ix - 2] && pix == argb[ix - 1]) + uint pix = bgra[ix]; + if (ix >= 2 && pix == bgra[ix - 2] && pix == bgra[ix - 1]) { continue; // Repeated pixels are handled by backward references. } - if (ix >= width + 2 && argb[ix - 2] == argb[ix - width - 2] && argb[ix - 1] == argb[ix - width - 1] && pix == argb[ix - width]) + if (ix >= width + 2 && bgra[ix - 2] == bgra[ix - width - 2] && bgra[ix - 1] == bgra[ix - width - 1] && pix == bgra[ix - width]) { continue; // Repeated pixels are handled by backward references. } @@ -766,11 +771,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } - private static Vp8LMultipliers GetBestColorTransformForTile(int tile_x, int tile_y, int bits, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int xSize, int ySize, int[] accumulatedRedHisto, int[] accumulatedBlueHisto, Span argb) + private static Vp8LMultipliers GetBestColorTransformForTile(int tileX, int tileY, int bits, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int xSize, int ySize, int[] accumulatedRedHisto, int[] accumulatedBlueHisto, Span argb) { int maxTileSize = 1 << bits; - int tileYOffset = tile_y * maxTileSize; - int tileXOffset = tile_x * maxTileSize; + int tileYOffset = tileY * maxTileSize; + int tileXOffset = tileX * maxTileSize; int allXMax = GetMin(tileXOffset + maxTileSize, xSize); int allYMax = GetMin(tileYOffset + maxTileSize, ySize); int tileWidth = allXMax - tileXOffset; @@ -921,29 +926,84 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return curDiff; } - private static void CollectColorRedTransforms(Span argb, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) + private static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported) + { + var multsg = Vector128.Create((short)((greenToRed << 8) >> 5)); + var maskgreen = Vector128.Create(0x00ff00); + var mask = Vector128.Create((short)0xff); + + const int span = 8; + int y; + Span values = stackalloc ushort[span]; + for (y = 0; y < tileHeight; ++y) + { + Span srcSpan = bgra.Slice(y * stride); + fixed (uint* src = srcSpan) + fixed (ushort* dst = values) + { + for (int x = 0; x + span <= tileWidth; x += span) + { + uint* input0Idx = src + x; + uint* input1Idx = src + x + (span / 2); + Vector128 input0 = Sse2.LoadVector128((ushort*)input0Idx).AsByte(); + Vector128 input1 = Sse2.LoadVector128((ushort*)input1Idx).AsByte(); + Vector128 g0 = Sse2.And(input0, maskgreen.AsByte()); // 0 0 | g 0 + Vector128 g1 = Sse2.And(input1, maskgreen.AsByte()); + Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); + Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector128 d = Sse2.And(c, mask.AsByte()); // 0 r' + Sse2.Store(dst, d.AsUInt16()); + for (int i = 0; i < span; ++i) + { + ++histo[values[i]]; + } + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); + } + } + else +#endif + { + CollectColorRedTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToRed, histo); + } + } + + private static void CollectColorRedTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) { - int startIdx = 0; + int pos = 0; while (tileHeight-- > 0) { for (int x = 0; x < tileWidth; x++) { - int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, argb[startIdx + x]); + int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); ++histo[idx]; } - startIdx += stride; + pos += stride; } } - private static void CollectColorBlueTransforms(Span argb, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) + private static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) { int pos = 0; while (tileHeight-- > 0) { for (int x = 0; x < tileWidth; x++) { - int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, argb[pos + x]); + int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); ++histo[idx]; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs new file mode 100644 index 000000000..cfd7bc184 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + [Trait("Format", "Webp")] + public class PredictorEncoderTests + { + [Fact] + public void ColorSpaceTransform_ProducesExpectedData() + { + RunColorSpaceTransformTest(); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void ColorSpaceTransform_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_ProducesExpectedData, HwIntrinsics.AllowAll); + } + + [Fact] + public void ColorSpaceTransform_WithoutSSE41_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_ProducesExpectedData, HwIntrinsics.DisableSSE41); + } +#endif + + private static void RunColorSpaceTransformTest() + { + // arrange + uint[] expectedData = + { + 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, + 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, + 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, + 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, + 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, + 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, + 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, + 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, + 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, + 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, + 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, + 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, + 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, + 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, + 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, + 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, + 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, + 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, + 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, + 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, + 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, + 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, + 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, + 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, + 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, + 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 + }; + + // Convert image pixels to bgra array. + var imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Peak)); + using var image = Image.Load(imgBytes); + uint[] bgra = ToBgra(image); + + int colorTransformBits = 3; + int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); + var transformData = new uint[transformWidth * transformHeight]; + + // act + PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); + + // assert + Assert.Equal(expectedData, transformData); + } + + private static uint[] ToBgra(Image image) + where TPixel : unmanaged, IPixel + { + uint[] bgra = new uint[image.Width * image.Height]; + int idx = 0; + for (int y = 0; y < image.Height; y++) + { + Span rowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + } + } + + return bgra; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Bgra32 ToBgra32(TPixel color) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); + return bgra; + } + + private static string TestImageFullPath(string path) + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + } +} From e12fd7ba9e11a91cdebc45dee1e2beab53f087a8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 23 Dec 2020 14:37:25 +0100 Subject: [PATCH 261/359] Add SSE4 version of CollectColorBlueTransforms --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index d84670889..d2f810949 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -936,9 +936,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless var mask = Vector128.Create((short)0xff); const int span = 8; - int y; Span values = stackalloc ushort[span]; - for (y = 0; y < tileHeight; ++y) + for (int y = 0; y < tileHeight; ++y) { Span srcSpan = bgra.Slice(y * stride); fixed (uint* src = srcSpan) @@ -998,6 +997,67 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless private static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported) + { + const int span = 8; + Span values = stackalloc ushort[span]; + var multsr = Vector128.Create((short)((redToBlue << 8) >> 5)); + var multsg = Vector128.Create((short)((greenToBlue << 8) >> 5)); + var maskgreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var maskgreenblue = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + Vector128 maskblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + var shufflerLow = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); + var shufflerHigh = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); + + for (int y = 0; y < tileHeight; ++y) + { + Span srcSpan = bgra.Slice(y * stride); + fixed (uint* src = srcSpan) + fixed (ushort* dst = values) + { + for (int x = 0; x + span <= tileWidth; x += span) + { + uint* input0Idx = src + x; + uint* input1Idx = src + x + (span / 2); + Vector128 input0 = Sse2.LoadVector128((ushort*)input0Idx).AsByte(); + Vector128 input1 = Sse2.LoadVector128((ushort*)input1Idx).AsByte(); + Vector128 r0 = Ssse3.Shuffle(input0, shufflerLow); + Vector128 r1 = Ssse3.Shuffle(input1, shufflerHigh); + Vector128 r = Sse2.Or(r0, r1); + Vector128 gb0 = Sse2.And(input0, maskgreenblue); + Vector128 gb1 = Sse2.And(input1, maskgreenblue); + Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector128 g = Sse2.And(gb.AsByte(), maskgreen); + Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); + Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); + Vector128 d = Sse2.Subtract(c, a.AsByte()); + Vector128 e = Sse2.And(d, maskblue); + Sse2.Store(dst, e.AsUInt16()); + for (int i = 0; i < span; ++i) + { + ++histo[values[i]]; + } + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); + } + } + else +#endif + { + CollectColorBlueTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + } + } + + private static void CollectColorBlueTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) + { int pos = 0; while (tileHeight-- > 0) { From 0676e68bfddd5894613e587ad9cc491f5a487e47 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 25 Dec 2020 14:37:15 +0100 Subject: [PATCH 262/359] Add SSE2 version of TransformColor --- .../Formats/WebP/Lossless/LosslessUtils.cs | 98 +++++++++++++++---- .../Formats/WebP/Lossless/PredictorEncoder.cs | 13 ++- .../Codecs/EncodeWebp.cs | 36 +++---- .../Formats/WebP/LosslessUtilsTests.cs | 50 ++++++++++ 4 files changed, 155 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 94ba24660..5b4f3bf72 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -391,6 +391,49 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless public static void TransformColor(Vp8LMultipliers m, Span data, int numPixels) { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); + var maskalphagreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var maskredblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + var shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + int idx; + fixed (uint* src = data) + { + for (idx = 0; idx + 4 <= numPixels; idx += 4) + { + var pos = src + idx; + Vector128 input = Sse2.LoadVector128(pos); + Vector128 a = Sse2.And(input.AsByte(), maskalphagreen); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), shufflemask); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), shufflemask); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); + Vector128 f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector128 g = Sse2.ShiftRightLogical(f.AsInt32(), 16); + Vector128 h = Sse2.Add(g.AsByte(), d.AsByte()); + Vector128 i = Sse2.And(h, maskredblue); + Vector128 output = Sse2.Subtract(input.AsByte(), i); + Sse2.Store((byte*)pos, output); + } + + if (idx != numPixels) + { + TransformColorNoneVectorized(m, data.Slice(idx), numPixels - idx); + } + } + } + else +#endif + { + TransformColorNoneVectorized(m, data, numPixels); + } + } + + public static void TransformColorNoneVectorized(Vp8LMultipliers m, Span data, int numPixels) + { for (int i = 0; i < numPixels; i++) { uint argb = data[i]; @@ -1140,6 +1183,33 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } + /// + /// Computes sampled size of 'size' when sampling using 'sampling bits'. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int SubSampleSize(int size, int samplingBits) + { + return (size + (1 << samplingBits) - 1) >> samplingBits; + } + + /// + /// Sum of each component, mod 256. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint AddPixels(uint a, uint b) + { + uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); + uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } + + // For sign-extended multiplying constants, pre-shifted by 5: + [MethodImpl(InliningOptions.ShortMethod)] + public static short Cst5b(int x) + { + return (short)(((short)(x << 8)) >> 5); + } + private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { int a = AddSubtractComponentFull( @@ -1186,6 +1256,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return a < 256 ? a : ~a >> 24; } +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 MkCst16(int hi, int lo) + { + return Vector128.Create((hi << 16) | (lo & 0xffff)); + } +#endif + private static uint Select(uint a, uint b, uint c) { int paMinusPb = @@ -1222,26 +1300,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return Average2(Average2(a0, a1), Average2(a2, a3)); } - /// - /// Computes sampled size of 'size' when sampling using 'sampling bits'. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int SubSampleSize(int size, int samplingBits) - { - return (size + (1 << samplingBits) - 1) >> samplingBits; - } - - /// - /// Sum of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static uint AddPixels(uint a, uint b) - { - uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); - uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); - } - [MethodImpl(InliningOptions.ShortMethod)] private static uint GetArgbIndex(uint idx) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index d2f810949..83c4bda1f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -720,6 +720,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } + [MethodImpl(InliningOptions.ShortMethod)] private static int MaxDiffBetweenPixels(uint p1, uint p2) { int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); @@ -729,6 +730,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); } + [MethodImpl(InliningOptions.ShortMethod)] private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) { int diffUp = MaxDiffBetweenPixels(current, up); @@ -738,6 +740,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); } + [MethodImpl(InliningOptions.ShortMethod)] private static void UpdateHisto(int[][] histoArgb, uint argb) { ++histoArgb[0][argb >> 24]; @@ -931,7 +934,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless #if SUPPORTS_RUNTIME_INTRINSICS if (Sse41.IsSupported) { - var multsg = Vector128.Create((short)((greenToRed << 8) >> 5)); + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); var maskgreen = Vector128.Create(0x00ff00); var mask = Vector128.Create((short)0xff); @@ -1002,11 +1005,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { const int span = 8; Span values = stackalloc ushort[span]; - var multsr = Vector128.Create((short)((redToBlue << 8) >> 5)); - var multsg = Vector128.Create((short)((greenToBlue << 8) >> 5)); + var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); var maskgreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); var maskgreenblue = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - Vector128 maskblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + var maskblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); var shufflerLow = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); var shufflerHigh = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); @@ -1084,6 +1087,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return (float)retVal; } + [MethodImpl(InliningOptions.ShortMethod)] private static float PredictionCostCrossColor(int[] accumulated, int[] counts) { // Favor low entropy, locally and globally. @@ -1092,6 +1096,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); } + [MethodImpl(InliningOptions.ShortMethod)] private static float PredictionCostSpatial(int[] counts, int weight0, double expVal) { int significantSymbols = 256 >> 4; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 05b2e5fb5..ae2091957 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.WebP.Peak)] + [Params(TestImages.Png.Bike)] // The bike image will have all 3 transforms as lossless webp. public string TestImage { get; set; } [GlobalSetup] @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs }); } - /* Results 14.11.2020 + /* Results 25.12.2020 * Summary * BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores @@ -84,22 +84,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs Job-GAIITM : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT Job-HWOBSO : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |-------------- |-------------- |----------:|----------:|----------:|------:|--------:|----------:|---------:|---------:|-----------:| - | 'Magick Webp Lossy' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 1.744 ms | 0.0399 ms | 0.0022 ms | 0.35 | 0.00 | 1.9531 | - | - | 13.58 KB | - | 'ImageSharp Webp Lossy' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 5.195 ms | 0.4241 ms | 0.0232 ms | 1.04 | 0.01 | 398.4375 | 93.7500 | - | 1661.83 KB | - | 'Magick Webp Lossless' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 4.993 ms | 0.5097 ms | 0.0279 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.7 KB | - | 'ImageSharp Webp Lossless' | Job-MYNMXL | .NET 4.7.2 | WebP/Peak.png | 12.174 ms | 1.2476 ms | 0.0684 ms | 2.44 | 0.02 | 1000.0000 | 984.3750 | 984.3750 | 8197.11 KB | - | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 1.747 ms | 0.0581 ms | 0.0032 ms | 0.35 | 0.00 | 1.9531 | - | - | 13.34 KB | - | 'ImageSharp Webp Lossy' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 3.527 ms | 0.0972 ms | 0.0053 ms | 0.71 | 0.00 | 402.3438 | 97.6563 | - | 1656.92 KB | - | 'Magick Webp Lossless' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 5.001 ms | 0.4543 ms | 0.0249 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.39 KB | - | 'ImageSharp Webp Lossless' | Job-MPXHSM | .NET Core 2.1 | WebP/Peak.png | 10.704 ms | 0.9844 ms | 0.0540 ms | 2.14 | 0.02 | 1000.0000 | 984.3750 | 984.3750 | 8182.6 KB | - | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 1.742 ms | 0.0279 ms | 0.0015 ms | 0.35 | 0.01 | 1.9531 | - | - | 13.31 KB | - | 'ImageSharp Webp Lossy' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 3.347 ms | 0.0638 ms | 0.0035 ms | 0.68 | 0.01 | 402.3438 | 97.6563 | - | 1656.93 KB | - | 'Magick Webp Lossless' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 4.954 ms | 1.4131 ms | 0.0775 ms | 1.00 | 0.00 | 7.8125 | - | - | 35.35 KB | - | 'ImageSharp Webp Lossless' | Job-SYDSGM | .NET Core 3.1 | WebP/Peak.png | 10.737 ms | 2.5604 ms | 0.1403 ms | 2.17 | 0.05 | 1000.0000 | 984.3750 | 984.3750 | 8182.49 KB | + | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |-------------- |------------- |----------:|-----------:|----------:|------:|--------:|-----------:|----------:|----------:|-------------:| + | 'Magick Webp Lossy' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 23.89 ms | 3.742 ms | 0.205 ms | 0.14 | 0.00 | - | - | - | 68.19 KB | + | 'ImageSharp Webp Lossy' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 72.27 ms | 20.228 ms | 1.109 ms | 0.43 | 0.01 | 6142.8571 | 142.8571 | - | 26360.05 KB | + | 'Magick Webp Lossless' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 167.75 ms | 41.847 ms | 2.294 ms | 1.00 | 0.00 | - | - | - | 520.28 KB | + | 'ImageSharp Webp Lossless' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 388.12 ms | 84.867 ms | 4.652 ms | 2.31 | 0.03 | 34000.0000 | 5000.0000 | 2000.0000 | 163174.2 KB | + | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 24.00 ms | 7.621 ms | 0.418 ms | 0.14 | 0.00 | - | - | - | 67.67 KB | + | 'ImageSharp Webp Lossy' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 47.77 ms | 6.498 ms | 0.356 ms | 0.29 | 0.00 | 6272.7273 | 272.7273 | 90.9091 | 26284.65 KB | + | 'Magick Webp Lossless' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 166.07 ms | 25.133 ms | 1.378 ms | 1.00 | 0.00 | - | - | - | 519.06 KB | + | 'ImageSharp Webp Lossless' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 356.60 ms | 249.912 ms | 13.699 ms | 2.15 | 0.10 | 34000.0000 | 5000.0000 | 2000.0000 | 162719.59 KB | + | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 23.95 ms | 5.531 ms | 0.303 ms | 0.14 | 0.00 | - | - | - | 67.57 KB | + | 'ImageSharp Webp Lossy' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 44.12 ms | 4.250 ms | 0.233 ms | 0.27 | 0.01 | 6250.0000 | 250.0000 | 83.3333 | 26284.72 KB | + | 'Magick Webp Lossless' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 165.94 ms | 66.670 ms | 3.654 ms | 1.00 | 0.00 | - | - | - | 523.05 KB | + | 'ImageSharp Webp Lossless' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 342.97 ms | 92.856 ms | 5.090 ms | 2.07 | 0.05 | 34000.0000 | 5000.0000 | 2000.0000 | 162725.32 KB | */ } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index f8b0f5a04..afa6be0da 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -68,6 +68,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expectedOutput, pixelData); } + private static void RunTransformColorTest() + { + uint[] pixelData = + { + 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, + 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, + 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, + 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, + 392450, 196861, 16712192, 16711680, 130564, 16451071 + }; + + var m = new Vp8LMultipliers() + { + GreenToBlue = 240, + GreenToRed = 232, + RedToBlue = 0 + }; + + uint[] expectedOutput = + { + 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, + 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, + 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, + 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, + 16711680, 65027, 16712962 + }; + + LosslessUtils.TransformColor(m, pixelData, pixelData.Length); + + Assert.Equal(expectedOutput, pixelData); + } + [Fact] public void SubtractGreen_Works() { @@ -80,6 +112,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP RunAddGreenToBlueAndRedTest(); } + [Fact] + public void TrannsformColor_Works() + { + RunTransformColorTest(); + } + #if SUPPORTS_RUNTIME_INTRINSICS [Fact] public void SubtractGreen_WithHardwareIntrinsics_Works() @@ -116,6 +154,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); } + + [Fact] + public void TransformColor_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); + } + + [Fact] + public void TransformColor_WithoutSSE2_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); + } #endif } } From cf7a6986f4933d8dfe43132f2381baf7bad13881 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 26 Dec 2020 09:52:03 +0100 Subject: [PATCH 263/359] Add SSE2 version of TransformColorInverse --- .../Formats/WebP/Lossless/LosslessUtils.cs | 62 ++++++++++++++++--- .../Codecs/DecodeWebp.cs | 30 ++++----- .../Formats/WebP/LosslessUtilsTests.cs | 52 +++++++++++++++- .../Formats/WebP/PredictorEncoderTests.cs | 2 +- 4 files changed, 121 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 5b4f3bf72..086ad54b0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { uint colorCode = transformData[predRowIdx++]; ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData, pixelPos, tileWidth); + TransformColorInverse(m, pixelData.Slice(pixelPos, tileWidth)); pixelPos += tileWidth; } @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { uint colorCode = transformData[predRowIdx]; ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData, pixelPos, remainingWidth); + TransformColorInverse(m, pixelData.Slice(pixelPos, remainingWidth)); pixelPos += remainingWidth; } @@ -389,6 +389,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } + /// + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + /// The Vp8LMultipliers. + /// The pixel data to transform. + /// The number of pixels to process. public static void TransformColor(Vp8LMultipliers m, Span data, int numPixels) { #if SUPPORTS_RUNTIME_INTRINSICS @@ -432,7 +438,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } - public static void TransformColorNoneVectorized(Vp8LMultipliers m, Span data, int numPixels) + private static void TransformColorNoneVectorized(Vp8LMultipliers m, Span data, int numPixels) { for (int i = 0; i < numPixels; i++) { @@ -455,12 +461,52 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// The color transform element. /// The pixel data to apply the inverse transform on. - /// The start index of reverse transform. - /// The number of pixels to apply the transform. - public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels) + public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); + var maskalphagreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + fixed (uint* src = pixelData) + { + int idx; + for (idx = 0; idx + 4 <= pixelData.Length; idx += 4) + { + var pos = src + idx; + Vector128 input = Sse2.LoadVector128(pos); + Vector128 a = Sse2.And(input.AsByte(), maskalphagreen); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), shufflemask); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), shufflemask); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.Add(input.AsByte(), d.AsByte()); + Vector128 f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); + Vector128 g = Sse2.MultiplyHigh(f, multsb2.AsInt16()); + Vector128 h = Sse2.ShiftRightLogical(g.AsInt32(), 8); + Vector128 i = Sse2.Add(h.AsByte(), f.AsByte()); + Vector128 j = Sse2.ShiftRightLogical(i.AsInt16(), 8); + Vector128 output = Sse2.Or(j.AsByte(), a); + Sse2.Store((byte*)pos, output); + } + + if (idx != pixelData.Length) + { + TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); + } + } + } + else +#endif + { + TransformColorInverseNoneVectorized(m, pixelData); + } + } + + private static void TransformColorInverseNoneVectorized(Vp8LMultipliers m, Span pixelData) { - int end = start + numPixels; - for (int i = start; i < end; i++) + for (int i = 0; i < pixelData.Length; i++) { uint argb = pixelData[i]; sbyte green = (sbyte)(argb >> 8); diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 28d69f469..1ace21778 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs return image.Height; } - /* Results 15.05.2020 + /* Results 26.12.2020 * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=3.1.202 @@ -82,20 +82,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs Job-WMTYOZ : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT IterationCount=3 LaunchCount=1 WarmupCount=3 - | Method | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |-------------- |--------------------- |--------------------- |-----------:|----------:|--------:|----------:|----------:|------:|-------------:| - | 'Magick Lossy WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 125.2 ms | 7.93 ms | 0.43 ms | - | - | - | 18.05 KB | - | 'ImageSharp Lossy Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,102.1 ms | 67.88 ms | 3.72 ms | 2000.0000 | - | - | 11835.55 KB | - | 'Magick Lossless WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 183.6 ms | 7.11 ms | 0.39 ms | - | - | - | 18.71 KB | - | 'ImageSharp Lossless Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,820.1 ms | 68.66 ms | 3.76 ms | 4000.0000 | 1000.0000 | - | 223765.64 KB | - | 'Magick Lossy WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 124.7 ms | 1.92 ms | 0.11 ms | - | - | - | 15.97 KB | - | 'ImageSharp Lossy Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 739.0 ms | 39.51 ms | 2.17 ms | 2000.0000 | - | - | 11802.98 KB | - | 'Magick Lossless WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 184.0 ms | 21.65 ms | 1.19 ms | - | - | - | 17.96 KB | - | 'ImageSharp Lossless Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 618.3 ms | 16.33 ms | 0.90 ms | 4000.0000 | 1000.0000 | - | 223699.11 KB | - | 'Magick Lossy WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 125.6 ms | 17.51 ms | 0.96 ms | - | - | - | 16.1 KB | - | 'ImageSharp Lossy Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 768.4 ms | 114.73 ms | 6.29 ms | 2000.0000 | - | - | 11802.89 KB | - | 'Magick Lossless WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 183.6 ms | 3.32 ms | 0.18 ms | - | - | - | 17 KB | - | 'ImageSharp Lossless Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 621.3 ms | 12.12 ms | 0.66 ms | 4000.0000 | 1000.0000 | - | 223698.75 KB | + | Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |-------------- |--------------------- |--------------------- |-----------:|---------:|--------:|----------:|----------:|------:|------------:| + | 'Magick Lossy WebP' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 107.1 ms | 47.56 ms | 2.61 ms | - | - | - | 32.05 KB | + | 'ImageSharp Lossy Webp' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,108.4 ms | 25.90 ms | 1.42 ms | - | - | - | 2779.53 KB | + | 'Magick Lossless WebP' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 145.8 ms | 8.97 ms | 0.49 ms | - | - | - | 18.05 KB | + | 'ImageSharp Lossless Webp' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,662.9 ms | 9.34 ms | 0.51 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | + | 'Magick Lossy WebP' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 106.2 ms | 14.80 ms | 0.81 ms | - | - | - | 16 KB | + | 'ImageSharp Lossy Webp' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 743.1 ms | 7.53 ms | 0.41 ms | - | - | - | 2767.8 KB | + | 'Magick Lossless WebP' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 146.7 ms | 25.23 ms | 1.38 ms | - | - | - | 16.76 KB | + | 'ImageSharp Lossless Webp' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 529.2 ms | 64.09 ms | 3.51 ms | 4000.0000 | 1000.0000 | - | 22859.97 KB | + | 'Magick Lossy WebP' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 106.0 ms | 9.51 ms | 0.52 ms | - | - | - | 15.71 KB | + | 'ImageSharp Lossy Webp' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 765.8 ms | 34.82 ms | 1.91 ms | - | - | - | 2767.79 KB | + | 'Magick Lossless WebP' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 146.0 ms | 25.51 ms | 1.40 ms | - | - | - | 16.02 KB | + | 'ImageSharp Lossless Webp' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 478.3 ms | 89.70 ms | 4.92 ms | 4000.0000 | 1000.0000 | - | 22859.61 KB | */ } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index afa6be0da..8e0a14cce 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -100,6 +100,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expectedOutput, pixelData); } + private static void RunTransformColorInverseTest() + { + uint[] pixelData = + { + 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, + 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, + 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, + 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, + 16711680, 65027, 16712962 + }; + + var m = new Vp8LMultipliers() + { + GreenToBlue = 240, + GreenToRed = 232, + RedToBlue = 0 + }; + + uint[] expectedOutput = + { + 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, + 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, + 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, + 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, + 392450, 196861, 16712192, 16711680, 130564, 16451071 + }; + + LosslessUtils.TransformColorInverse(m, pixelData); + + Assert.Equal(expectedOutput, pixelData); + } + [Fact] public void SubtractGreen_Works() { @@ -113,11 +145,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Fact] - public void TrannsformColor_Works() + public void TransformColor_Works() { RunTransformColorTest(); } + [Fact] + public void TransformColorInverse_Works() + { + RunTransformColorInverseTest(); + } + #if SUPPORTS_RUNTIME_INTRINSICS [Fact] public void SubtractGreen_WithHardwareIntrinsics_Works() @@ -166,6 +204,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); } + + [Fact] + public void TransformColorInverse_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); + } + + [Fact] + public void TransformColorInverse_WithoutSSE2_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); + } #endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index cfd7bc184..b989259a3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public class PredictorEncoderTests { [Fact] - public void ColorSpaceTransform_ProducesExpectedData() + public static void ColorSpaceTransform_ProducesExpectedData() { RunColorSpaceTransformTest(); } From 48634cf2bf50252308e41e7ba42907666002d6ad Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 4 Jan 2021 20:24:26 +0100 Subject: [PATCH 264/359] Add another testcase for ColorSpaceTransform, disable ColorSpaceTransform test for net472 with peak image --- .../Formats/WebP/PredictorEncoderTests.cs | 67 ++++++++++++++++--- tests/ImageSharp.Tests/TestImages.cs | 2 + .../Input/WebP/bike_lossless_small.webp | 3 + 3 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 tests/Images/Input/WebP/bike_lossless_small.webp diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index b989259a3..fd4404d91 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -3,7 +3,7 @@ using System; using System.IO; -using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -15,26 +15,47 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public class PredictorEncoderTests { [Fact] - public static void ColorSpaceTransform_ProducesExpectedData() + public static void ColorSpaceTransform_WithBikeImage_ProducesExpectedData() { - RunColorSpaceTransformTest(); + RunColorSpaceTransformTestWithBikeImage(); } + // Note: only run with netcoreapp, because the test fails with net472 in Release mode (not in Debug mode) for unknown reason. +#if NETCOREAPP + [Fact] + public static void ColorSpaceTransform_WithPeakImage_ProducesExpectedData() + { + RunColorSpaceTransformTestWithPeakImage(); + } +#endif + #if SUPPORTS_RUNTIME_INTRINSICS [Fact] - public void ColorSpaceTransform_WithHardwareIntrinsics_Works() + public void ColorSpaceTransform_WithPeakImage_WithHardwareIntrinsics_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); + } + + [Fact] + public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_ProducesExpectedData, HwIntrinsics.AllowAll); + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); } [Fact] - public void ColorSpaceTransform_WithoutSSE41_Works() + public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_ProducesExpectedData, HwIntrinsics.DisableSSE41); + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); + } + + [Fact] + public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); } #endif - private static void RunColorSpaceTransformTest() + private static void RunColorSpaceTransformTestWithPeakImage() { // arrange uint[] expectedData = @@ -90,6 +111,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expectedData, transformData); } + private static void RunColorSpaceTransformTestWithBikeImage() + { + // arrange + uint[] expectedData = + { + 4278714368, 4278192876, 4278198304, 4278198304, 4278190304, 4278190080, 4278190080, 4278198272, + 4278197760, 4278198816, 4278197794, 4278197774, 4278190080, 4278190080, 4278198816, 4278197281, + 4278197280, 4278197792, 4278200353, 4278191343, 4278190304, 4294713873, 4278198784, 4294844416, + 4278201578, 4278200044, 4278191343, 4278190288, 4294705200, 4294717139, 4278203628, 4278201064, + 4278201586, 4278197792, 4279240909 + }; + + // Convert image pixels to bgra array. + var imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Lossless.BikeSmall)); + using var image = Image.Load(imgBytes, new WebpDecoder()); + uint[] bgra = ToBgra(image); + + int colorTransformBits = 4; + int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); + var transformData = new uint[transformWidth * transformHeight]; + + // act + PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); + + // assert + Assert.Equal(expectedData, transformData); + } + private static uint[] ToBgra(Image image) where TPixel : unmanaged, IPixel { @@ -107,7 +157,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP return bgra; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Bgra32 ToBgra32(TPixel color) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5f98875af..737c662d8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -551,6 +551,8 @@ namespace SixLabors.ImageSharp.Tests public const string ThreeTransforms7 = "WebP/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color public const string BikeThreeTransforms = "WebP/bike_lossless.webp"; // substract_green, predictor, cross_color + public const string BikeSmall = "WebP/bike_lossless_small.webp"; + // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." public const string LossLessCorruptImage1 = "WebP/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. diff --git a/tests/Images/Input/WebP/bike_lossless_small.webp b/tests/Images/Input/WebP/bike_lossless_small.webp new file mode 100644 index 000000000..661129451 --- /dev/null +++ b/tests/Images/Input/WebP/bike_lossless_small.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0cff46a5bbc4903e8e372ee79e988942c101a6cc6642658cb92a9f377443dca +size 2598 From 28872d09d3f7a08ba7cfd73ec4d5fe25a024a842 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 4 Jan 2021 21:22:20 +0100 Subject: [PATCH 265/359] Fix peak image file name --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 737c662d8..3f6095c11 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -498,7 +498,7 @@ namespace SixLabors.ImageSharp.Tests public static class WebP { // Reference image as png - public const string Peak = "WebP/Peak.png"; + public const string Peak = "WebP/peak.png"; public static class Animated { From dafafb8f2103572633737c7448232f63d59eb530 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 5 Jan 2021 01:28:39 +0000 Subject: [PATCH 266/359] Fix predictor encoder on NET472 32bit. --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 18 +++++++++--------- .../Formats/WebP/PredictorEncoderTests.cs | 13 ++++--------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 83c4bda1f..b1159fc6c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -798,7 +798,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6] int greenToRedBest = 0; - float bestDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); + double bestDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); for (int iter = 0; iter < maxIters; iter++) { // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to @@ -810,7 +810,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless for (int offset = -delta; offset <= delta; offset += 2 * delta) { int greenToRedCur = offset + greenToRedBest; - float curDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); + double curDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); if (curDiff < bestDiff) { bestDiff = curDiff; @@ -831,7 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless sbyte[] deltaLut = { 16, 16, 8, 4, 2, 2, 2 }; // Initial value at origin: - float bestDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); + double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); for (int iter = 0; iter < iters; iter++) { int delta = deltaLut[iter]; @@ -839,7 +839,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { int greenToBlueCur = (offset[axis][0] * delta) + greenToBlueBest; int redToBlueCur = (offset[axis][1] * delta) + redToBlueBest; - float curDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); + double curDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); if (curDiff < bestDiff) { bestDiff = curDiff; @@ -865,12 +865,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); } - private static float GetPredictionCostCrossColorRed(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToRed, int[] accumulatedRedHisto) + private static double GetPredictionCostCrossColorRed(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToRed, int[] accumulatedRedHisto) { int[] histo = new int[256]; CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); - float curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); + double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); if ((byte)greenToRed == prevX.GreenToRed) { @@ -890,12 +890,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return curDiff; } - private static float GetPredictionCostCrossColorBlue(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToBlue, int redToBlue, int[] accumulatedBlueHisto) + private static double GetPredictionCostCrossColorBlue(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToBlue, int redToBlue, int[] accumulatedBlueHisto) { int[] histo = new int[256]; CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); - float curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); + double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); if ((byte)greenToBlue == prevX.GreenToBlue) { curDiff -= 3; // Favor keeping the areas locally similar. @@ -1088,7 +1088,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static float PredictionCostCrossColor(int[] accumulated, int[] counts) + private static double PredictionCostCrossColor(int[] accumulated, int[] counts) { // Favor low entropy, locally and globally. // Favor small absolute values for PredictionCostSpatial. diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index fd4404d91..58d91951c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -6,7 +6,9 @@ using System.IO; using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; using SixLabors.ImageSharp.PixelFormats; +#if SUPPORTS_RUNTIME_INTRINSICS using SixLabors.ImageSharp.Tests.TestUtilities; +#endif using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP @@ -16,18 +18,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { [Fact] public static void ColorSpaceTransform_WithBikeImage_ProducesExpectedData() - { - RunColorSpaceTransformTestWithBikeImage(); - } + => RunColorSpaceTransformTestWithBikeImage(); - // Note: only run with netcoreapp, because the test fails with net472 in Release mode (not in Debug mode) for unknown reason. -#if NETCOREAPP [Fact] public static void ColorSpaceTransform_WithPeakImage_ProducesExpectedData() - { - RunColorSpaceTransformTestWithPeakImage(); - } -#endif + => RunColorSpaceTransformTestWithPeakImage(); #if SUPPORTS_RUNTIME_INTRINSICS [Fact] From 257449e85469e3774081a5f0a83f3b83893eb580 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 18 Jan 2021 15:27:44 +0100 Subject: [PATCH 267/359] Use auto properties in Vp8Encoder --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 20 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 45 +-- .../Formats/WebP/Lossy/Vp8Encoder.cs | 309 ++++++------------ 3 files changed, 122 insertions(+), 252 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index b1159fc6c..b2f5c88b7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -1112,27 +1112,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static byte NearLosslessDiff(byte a, byte b) - { - return (byte)((a - b) & 0xff); - } + private static byte NearLosslessDiff(byte a, byte b) => (byte)((a - b) & 0xff); [MethodImpl(InliningOptions.ShortMethod)] - private static uint MultipliersToColorCode(Vp8LMultipliers m) - { - return 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; - } + private static uint MultipliersToColorCode(Vp8LMultipliers m) => 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMin(int a, int b) - { - return (a > b) ? b : a; - } + private static int GetMin(int a, int b) => (a > b) ? b : a; [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMax(int a, int b) - { - return (a < b) ? b : a; - } + private static int GetMax(int a, int b) => (a < b) ? b : a; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index e0d2cd093..b2a254a61 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless // Apply transforms and write transform data. if (this.UseSubtractGreenTransform) { - this.ApplySubtractGreen(this.CurrentWidth, height); + this.ApplySubtractGreen(); } if (this.UsePredictorTransform) @@ -409,7 +409,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { if (cacheBits == 0) { - // TODO: not sure if this should be 10 or 11. Original code comment says "The maximum allowed limit is 11.", but the value itself is 10. cacheBits = WebpConstants.MaxColorCacheBits; } } @@ -441,7 +440,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless // two as a temporary for later usage. Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; - // TODO : Loop based on cache/no cache this.bitWriter.Reset(bwInit); var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); @@ -561,9 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// Applies the subtract green transformation to the pixel data of the image. /// - /// The width of the image. - /// The height of the image. - private void ApplySubtractGreen(int width, int height) + private void ApplySubtractGreen() { this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); @@ -1642,45 +1638,24 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash0(uint color) - { - // Focus on the green color. - return (color >> 8) & 0xff; - } + private static uint ApplyPaletteHash0(uint color) => (color >> 8) & 0xff; // Focus on the green color. [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash1(uint color) - { - // Forget about alpha. - return ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); - } + private static uint ApplyPaletteHash1(uint color) => ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); // Forget about alpha. [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash2(uint color) - { - // Forget about alpha. - return ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); - } + private static uint ApplyPaletteHash2(uint color) => ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); // Forget about alpha. + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. [MethodImpl(InliningOptions.ShortMethod)] - private static uint HashPix(uint pix) - { - // Note that masking with 0xffffffffu is for preventing an - // 'unsigned int overflow' warning. Doesn't impact the compiled code. - return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; - } + private static uint HashPix(uint pix) => (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; [MethodImpl(InliningOptions.ShortMethod)] - private static int PaletteCompareColorsForSort(uint p1, uint p2) - { - return (p1 < p2) ? -1 : 1; - } + private static int PaletteCompareColorsForSort(uint p1, uint p2) => (p1 < p2) ? -1 : 1; [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteComponentDistance(uint v) - { - return (v <= 128) ? v : (256 - v); - } + private static uint PaletteComponentDistance(uint v) => (v <= 128) ? v : (256 - v); public void AllocateTransformBuffer(int width, int height) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index eed716c1b..7cac484af 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -37,63 +37,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// private readonly int entropyPasses; - /// - /// Stride of the prediction plane (=4*mb_w + 1) - /// - private readonly int predsWidth; - - /// - /// Macroblock width. - /// - private readonly int mbw; - - /// - /// Macroblock height. - /// - private readonly int mbh; - /// /// A bit writer for writing lossy webp streams. /// private Vp8BitWriter bitWriter; - /// - /// The segment features. - /// - private Vp8EncSegmentHeader segmentHeader; - - /// - /// The filter header info's. - /// - private readonly Vp8FilterHeader filterHeader; - - /// - /// The segment infos. - /// - private readonly Vp8SegmentInfo[] segmentInfos; - - /// - /// Contextual macroblock infos. - /// - private readonly Vp8MacroBlockInfo[] mbInfo; - - /// - /// Probabilities. - /// - private readonly Vp8EncProba proba; - private readonly Vp8RdLevel rdOptLevel; - private int dqY1Dc; - - private int dqY2Dc; - - private int dqY2Ac; - - private int dqUvDc; - - private int dqUvAc; - private int maxI4HeaderBits; /// @@ -149,41 +99,40 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy : Vp8RdLevel.RdOptNone; var pixelCount = width * height; - this.mbw = (width + 15) >> 4; - this.mbh = (height + 15) >> 4; + this.Mbw = (width + 15) >> 4; + this.Mbh = (height + 15) >> 4; var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); - this.YTop = new byte[this.mbw * 16]; - this.UvTop = new byte[this.mbw * 16 * 2]; - this.Nz = new uint[this.mbw + 1]; - this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); - this.TopDerr = new sbyte[this.mbw * 4]; + this.YTop = new byte[this.Mbw * 16]; + this.UvTop = new byte[this.Mbw * 16 * 2]; + this.Nz = new uint[this.Mbw + 1]; + this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.Mbw * this.Mbh); + this.TopDerr = new sbyte[this.Mbw * 4]; // TODO: make partition_limit configurable? int limit = 100; // original code: limit = 100 - config->partition_limit; this.maxI4HeaderBits = - 256 * 16 * 16 * // upper bound: up to 16bit per 4x4 block - (limit * limit) / (100 * 100); // ... modulated with a quadratic curve. + 256 * 16 * 16 * limit * limit / (100 * 100); // ... modulated with a quadratic curve. - this.mbInfo = new Vp8MacroBlockInfo[this.mbw * this.mbh]; - for (int i = 0; i < this.mbInfo.Length; i++) + this.MbInfo = new Vp8MacroBlockInfo[this.Mbw * this.Mbh]; + for (int i = 0; i < this.MbInfo.Length; i++) { - this.mbInfo[i] = new Vp8MacroBlockInfo(); + this.MbInfo[i] = new Vp8MacroBlockInfo(); } - this.segmentInfos = new Vp8SegmentInfo[4]; + this.SegmentInfos = new Vp8SegmentInfo[4]; for (int i = 0; i < 4; i++) { - this.segmentInfos[i] = new Vp8SegmentInfo(); + this.SegmentInfos[i] = new Vp8SegmentInfo(); } - this.filterHeader = new Vp8FilterHeader(); - int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; - this.predsWidth = (4 * this.mbw) + 1; - this.proba = new Vp8EncProba(); - this.Preds = new byte[predSize + this.predsWidth + this.mbw]; + this.FilterHeader = new Vp8FilterHeader(); + int predSize = (((4 * this.Mbw) + 1) * ((4 * this.Mbh) + 1)) + this.PredsWidth + 1; + this.PredsWidth = (4 * this.Mbw) + 1; + this.Proba = new Vp8EncProba(); + this.Preds = new byte[predSize + this.PredsWidth + this.Mbw]; // Initialize with default values, which the reference c implementation uses, // to be able to compare to the original and spot differences. @@ -198,42 +147,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// /// Gets the probabilities. /// - public Vp8EncProba Proba - { - get => this.proba; - } + public Vp8EncProba Proba { get; private set; } /// /// Gets the segment features. /// - public Vp8EncSegmentHeader SegmentHeader - { - get => this.segmentHeader; - } + public Vp8EncSegmentHeader SegmentHeader { get; private set; } /// /// Gets the segment infos. /// - public Vp8SegmentInfo[] SegmentInfos - { - get => this.segmentInfos; - } + public Vp8SegmentInfo[] SegmentInfos { get; private set; } /// /// Gets the macro block info's. /// - public Vp8MacroBlockInfo[] MbInfo - { - get => this.mbInfo; - } + public Vp8MacroBlockInfo[] MbInfo { get; private set; } /// /// Gets the filter header. /// - public Vp8FilterHeader FilterHeader - { - get => this.filterHeader; - } + public Vp8FilterHeader FilterHeader { get; private set; } /// /// Gets or sets the global susceptibility. @@ -250,45 +184,30 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// public int Height { get; } - public int PredsWidth - { - get => this.predsWidth; - } + /// + /// Gets the stride of the prediction plane (=4*mb_w + 1) + /// + public int PredsWidth { get; } - public int Mbw - { - get => this.mbw; - } + /// + /// Gets the macroblock width. + /// + public int Mbw { get; } - public int Mbh - { - get => this.mbh; - } + /// + /// Gets the macroblock height. + /// + public int Mbh { get; } - public int DqY1Dc - { - get => this.dqY1Dc; - } + public int DqY1Dc { get; private set; } - public int DqY2Ac - { - get => this.dqY2Ac; - } + public int DqY2Ac { get; private set; } - public int DqY2Dc - { - get => this.dqY2Dc; - } + public int DqY2Dc { get; private set; } - public int DqUvAc - { - get => this.dqUvAc; - } + public int DqUvAc { get; private set; } - public int DqUvDc - { - get => this.dqUvDc; - } + public int DqUvDc { get; private set; } /// /// Gets the luma component. @@ -354,21 +273,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy int yStride = width; int uvStride = (yStride + 1) >> 1; - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.mbw, this.mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); var alphas = new int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); - int totalMb = this.mbw * this.mbw; + int totalMb = this.Mbw * this.Mbw; this.alpha /= totalMb; this.uvAlpha /= totalMb; // Analysis is done, proceed to actual encoding. - this.segmentHeader = new Vp8EncSegmentHeader(4); + this.SegmentHeader = new Vp8EncSegmentHeader(4); this.AssignSegments(alphas); this.SetLoopParams(this.quality); // Initialize the bitwriter. int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4]; - int expectedSize = this.mbw * this.mbh * averageBytesPerMacroBlock; + int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; this.bitWriter = new Vp8BitWriter(expectedSize, this); // TODO: EncodeAlpha(); @@ -426,10 +345,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; int numPassLeft = this.entropyPasses; Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; - int nbMbs = this.mbw * this.mbh; + int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); - this.proba.ResetTokenStats(); + this.Proba.ResetTokenStats(); // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) @@ -480,11 +399,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy if (!doSearch || !stats.DoSizeSearch) { // Need to finalize probas now, since it wasn't done during the search. - this.proba.FinalizeSkipProba(this.mbw, this.mbh); - this.proba.FinalizeTokenProbas(); + this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); + this.Proba.FinalizeTokenProbas(); } - this.proba.CalculateLevelCosts(); // Finalize costs. + this.Proba.CalculateLevelCosts(); // Finalize costs. } private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) @@ -492,7 +411,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); long size = 0; long sizeP0 = 0; long distortion = 0; @@ -506,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy if (this.Decimate(it, info, rdOpt)) { // Just record the number of skips and act like skipProba is not used. - ++this.proba.NbSkip; + ++this.Proba.NbSkip; } this.RecordResiduals(it, info); @@ -518,11 +437,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } while (it.Next()); - sizeP0 += this.segmentHeader.Size; + sizeP0 += this.SegmentHeader.Size; if (stats.DoSizeSearch) { - size += this.proba.FinalizeSkipProba(this.mbw, this.mbh); - size += this.proba.FinalizeTokenProbas(); + size += this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); + size += this.Proba.FinalizeTokenProbas(); size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; stats.Value = size; } @@ -556,7 +475,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy // this '>> 3' accounts for some inverse WHT scaling int delta = (dqm.MaxEdge * dqm.Y2.Q[1]) >> 3; - int level = this.FilterStrengthFromDelta(this.filterHeader.Sharpness, delta); + int level = this.FilterStrengthFromDelta(this.FilterHeader.Sharpness, delta); if (level > dqm.FStrength) { dqm.FStrength = level; @@ -568,28 +487,28 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } } - this.filterHeader.FilterLevel = maxLevel; + this.FilterHeader.FilterLevel = maxLevel; } } private void ResetBoundaryPredictions() { Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ - Span left = this.Preds.AsSpan(this.predsWidth - 1); - for (int i = 0; i < 4 * this.mbw; ++i) + Span left = this.Preds.AsSpan(this.PredsWidth - 1); + for (int i = 0; i < 4 * this.Mbw; ++i) { top[i] = (int)IntraPredictionMode.DcPrediction; } - for (int i = 0; i < 4 * this.mbh; ++i) + for (int i = 0; i < 4 * this.Mbh; ++i) { - left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction; + left[i * this.PredsWidth] = (int)IntraPredictionMode.DcPrediction; } - int predsW = (4 * this.mbw) + 1; - int predsH = (4 * this.mbh) + 1; + int predsW = (4 * this.Mbw) + 1; + int predsH = (4 * this.Mbh) + 1; int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.predsWidth - 4, 4).Fill(0); + this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Fill(0); this.Nz[0] = 0; // constant } @@ -597,7 +516,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy // Simplified k-Means, to assign Nb segments based on alpha-histogram. private void AssignSegments(int[] alphas) { - int nb = (this.segmentHeader.NumSegments < NumMbSegments) ? this.segmentHeader.NumSegments : NumMbSegments; + int nb = (this.SegmentHeader.NumSegments < NumMbSegments) ? this.SegmentHeader.NumSegments : NumMbSegments; var centers = new int[NumMbSegments]; int weightedAverage = 0; var map = new int[WebpConstants.MaxAlpha + 1]; @@ -677,9 +596,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } // Map each original value to the closest centroid - for (n = 0; n < this.mbw * this.mbh; ++n) + for (n = 0; n < this.Mbw * this.Mbh; ++n) { - Vp8MacroBlockInfo mb = this.mbInfo[n]; + Vp8MacroBlockInfo mb = this.MbInfo[n]; int alpha = mb.Alpha; mb.Segment = map[alpha]; mb.Alpha = centers[map[alpha]]; // for the record. @@ -691,8 +610,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy private void SetSegmentAlphas(int[] centers, int mid) { - int nb = this.segmentHeader.NumSegments; - Vp8SegmentInfo[] dqm = this.segmentInfos; + int nb = this.SegmentHeader.NumSegments; + Vp8SegmentInfo[] dqm = this.SegmentInfos; int min = centers[0], max = centers[0]; int n; @@ -728,7 +647,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy private void SetSegmentParams(float quality) { - int nb = this.segmentHeader.NumSegments; + int nb = this.SegmentHeader.NumSegments; Vp8SegmentInfo[] dqm = this.SegmentInfos; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebpConstants.SnsToDq * snsStrength / 100.0d / 128.0d; @@ -749,23 +668,23 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy // uvAlpha is normally spread around ~60. The useful range is // typically ~30 (quite bad) to ~100 (ok to decimate UV more). // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. - this.dqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); + this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); // We rescale by the user-defined strength of adaptation. - this.dqUvAc = this.dqUvAc * snsStrength / 100; + this.DqUvAc = this.DqUvAc * snsStrength / 100; // and make it safe. - this.dqUvAc = Clip(this.dqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); + this.DqUvAc = Clip(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); // We also boost the dc-uv-quant a little, based on sns-strength, since // U/V channels are quite more reactive to high quants (flat DC-blocks // tend to appear, and are unpleasant). - this.dqUvDc = -4 * snsStrength / 100; - this.dqUvDc = Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed + this.DqUvDc = -4 * snsStrength / 100; + this.DqUvDc = Clip(this.DqUvDc, -15, 15); // 4bit-signed max allowed - this.dqY1Dc = 0; - this.dqY2Dc = 0; - this.dqY2Ac = 0; + this.DqY1Dc = 0; + this.DqY2Dc = 0; + this.DqY2Ac = 0; // Initialize segments' filtering this.SetupFilterStrength(); @@ -786,7 +705,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy // We focus on the quantization of AC coeffs. int qstep = WebpLookupTables.AcTable[Clip(m.Quant, 0, 127)] >> 2; - int baseStrength = this.FilterStrengthFromDelta(this.filterHeader.Sharpness, qstep); + int baseStrength = this.FilterStrengthFromDelta(this.FilterHeader.Sharpness, qstep); // Segments with lower complexity ('beta') will be less filtered. int f = baseStrength * level0 / (256 + m.Beta); @@ -794,9 +713,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } // We record the initial strength (mainly for the case of 1-segment only). - this.filterHeader.FilterLevel = this.SegmentInfos[0].FStrength; - this.filterHeader.Simple = filterType == 0; - this.filterHeader.Sharpness = filterSharpness; + this.FilterHeader.FilterLevel = this.SegmentInfos[0].FStrength; + this.FilterHeader.Simple = filterType == 0; + this.FilterHeader.Sharpness = filterSharpness; } private void SetSegmentProbas() @@ -804,49 +723,49 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy var p = new int[NumMbSegments]; int n; - for (n = 0; n < this.mbw * this.mbh; ++n) + for (n = 0; n < this.Mbw * this.Mbh; ++n) { - Vp8MacroBlockInfo mb = this.mbInfo[n]; + Vp8MacroBlockInfo mb = this.MbInfo[n]; ++p[mb.Segment]; } - if (this.segmentHeader.NumSegments > 1) + if (this.SegmentHeader.NumSegments > 1) { - byte[] probas = this.proba.Segments; + byte[] probas = this.Proba.Segments; probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]); probas[1] = (byte)GetProba(p[0], p[1]); probas[2] = (byte)GetProba(p[2], p[3]); - this.segmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); - if (!this.segmentHeader.UpdateMap) + this.SegmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); + if (!this.SegmentHeader.UpdateMap) { this.ResetSegments(); } - this.segmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + + this.SegmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) + (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) + (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2]))); } else { - this.segmentHeader.UpdateMap = false; - this.segmentHeader.Size = 0; + this.SegmentHeader.UpdateMap = false; + this.SegmentHeader.Size = 0; } } private void ResetSegments() { int n; - for (n = 0; n < this.mbw * this.mbh; ++n) + for (n = 0; n < this.Mbw * this.Mbh; ++n) { - this.mbInfo[n].Segment = 0; + this.MbInfo[n].Segment = 0; } } private void ResetStats() { - Vp8EncProba proba = this.proba; + Vp8EncProba proba = this.Proba; proba.CalculateLevelCosts(); proba.NbSkip = 0; } @@ -868,8 +787,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Clip(q, 0, 127)] * 2); m.Y2.Q[1] = WebpLookupTables.AcTable2[Clip(q, 0, 127)]; - m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.dqUvDc, 0, 117)]; - m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)]; + m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.DqUvDc, 0, 117)]; + m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)]; var qi4 = m.Y1.Expand(0); m.Y2.Expand(1); // qi16 @@ -954,7 +873,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy int nz = 0; int mode; bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); - Vp8SegmentInfo dqm = this.segmentInfos[it.CurrentMacroBlockInfo.Segment]; + Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; // Some empiric constants, of approximate order of magnitude. int lambdaDi16 = 106; @@ -1100,15 +1019,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy int pos1 = this.bitWriter.NumBytes(); if (i16) { - residual.Init(0, 1, this.proba); + residual.Init(0, 1, this.Proba); residual.SetCoeffs(rd.YDcLevels); int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); it.TopNz[8] = it.LeftNz[8] = res; - residual.Init(1, 0, this.proba); + residual.Init(1, 0, this.Proba); } else { - residual.Init(0, 3, this.proba); + residual.Init(0, 3, this.Proba); } // luma-AC @@ -1127,7 +1046,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy int pos2 = this.bitWriter.NumBytes(); // U/V - residual.Init(0, 2, this.proba); + residual.Init(0, 2, this.Proba); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) @@ -1165,16 +1084,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy if (i16) { // i16x16 - residual.Init(0, 1, this.proba); + residual.Init(0, 1, this.Proba); residual.SetCoeffs(rd.YDcLevels); var res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); it.TopNz[8] = res; it.LeftNz[8] = res; - residual.Init(1, 0, this.proba); + residual.Init(1, 0, this.Proba); } else { - residual.Init(0, 3, this.proba); + residual.Init(0, 3, this.Proba); } // luma-AC @@ -1192,7 +1111,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } // U/V - residual.Init(0, 2, this.proba); + residual.Init(0, 2, this.Proba); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) @@ -1352,27 +1271,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Clip(int v, int min, int max) - { - return (v < min) ? min : (v > max) ? max : v; - } + private static int Clip(int v, int min, int max) => (v < min) ? min : (v > max) ? max : v; [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Sse16X16(Span a, Span b) - { - return GetSse(a, b, 16, 16); - } + private static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); - private static int Vp8Sse16X8(Span a, Span b) - { - return GetSse(a, b, 16, 8); - } + private static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Sse4X4(Span a, Span b) - { - return GetSse(a, b, 4, 4); - } + private static int Vp8Sse4X4(Span a, Span b) => GetSse(a, b, 4, 4); [MethodImpl(InliningOptions.ShortMethod)] private static int GetSse(Span a, Span b, int w, int h) From 4fa55b7a7bb34fa82952a8f8702c25f56b14abd9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Jan 2021 12:34:14 +0100 Subject: [PATCH 268/359] A little cleanup --- .../Formats/WebP/BitReader/Vp8BitReader.cs | 5 +- .../Formats/WebP/BitReader/Vp8LBitReader.cs | 20 +- .../Formats/WebP/BitWriter/BitWriterBase.cs | 13 +- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 10 +- .../Formats/WebP/Lossless/CostManager.cs | 10 +- .../Formats/WebP/Lossless/LosslessUtils.cs | 172 +++++------------- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 15 +- .../Formats/WebP/Lossy/Vp8Decoder.cs | 13 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 23 +-- .../Formats/WebP/WebpDecoderCore.cs | 17 +- .../Formats/WebP/LosslessUtilsTests.cs | 70 ++----- .../Formats/WebP/WebpDecoderTests.cs | 12 +- 12 files changed, 86 insertions(+), 294 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index fde850025..b796e6b1a 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -140,10 +140,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader } [MethodImpl(InliningOptions.ShortMethod)] - public bool ReadBool() - { - return this.ReadValue(1) is 1; - } + public bool ReadBool() => this.ReadValue(1) is 1; public uint ReadValue(int nBits) { diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index 61d00323e..5f9941929 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -157,20 +157,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader /// /// The number of bits to advance the position. [MethodImpl(InliningOptions.ShortMethod)] - public void AdvanceBitPosition(int numberOfBits) - { - this.bitPos += numberOfBits; - } + public void AdvanceBitPosition(int numberOfBits) => this.bitPos += numberOfBits; /// /// Return the pre-fetched bits, so they can be looked up. /// /// The pre-fetched bits. [MethodImpl(InliningOptions.ShortMethod)] - public ulong PrefetchBits() - { - return this.value >> (this.bitPos & (Lbits - 1)); - } + public ulong PrefetchBits() => this.value >> (this.bitPos & (Lbits - 1)); /// /// Advances the read buffer by 4 bytes to make room for reading next 32 bits. @@ -187,16 +181,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader /// Returns true if there was an attempt at reading bit past the end of the buffer. /// /// True, if end of buffer was reached. - public bool IsEndOfStream() - { - return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); - } + public bool IsEndOfStream() => this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); [MethodImpl(InliningOptions.ShortMethod)] - private void DoFillBitWindow() - { - this.ShiftBytes(); - } + private void DoFillBitWindow() => this.ShiftBytes(); /// /// If not at EOS, reload up to Vp8LLbits byte-by-byte. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 46f2dbdaf..5946aeb67 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -18,11 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// Initializes a new instance of the class. /// /// The expected size in bytes. - protected BitWriterBase(int expectedSize) - { - // TODO: should we use memory allocator here? - this.buffer = new byte[expectedSize]; - } + protected BitWriterBase(int expectedSize) => this.buffer = new byte[expectedSize]; /// /// Initializes a new instance of the class. @@ -36,10 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// Writes the encoded bytes of the image to the stream. Call Finish() before this. /// /// The stream to write to. - public void WriteToStream(Stream stream) - { - stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); - } + public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); /// /// Resizes the buffer to write to. @@ -79,8 +72,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter // Make new size multiple of 1k. newSize = ((newSize >> 10) + 1) << 10; - - // TODO: use memory allocator. Array.Resize(ref this.buffer, newSize); return false; diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 1764a190c..6fbb7e3b7 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -49,10 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// /// The expected size in bytes. public Vp8LBitWriter(int expectedSize) - : base(expectedSize) - { - this.end = this.Buffer.Length; - } + : base(expectedSize) => this.end = this.Buffer.Length; /// /// Initializes a new instance of the class. @@ -106,10 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter } /// - public override int NumBytes() - { - return this.cur + ((this.used + 7) >> 3); - } + public override int NumBytes() => this.cur + ((this.used + 7) >> 3); public Vp8LBitWriter Clone() { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index 1eccc7af7..b1aa98218 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -267,15 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless previous = previous.Next; } - if (previous != null) - { - this.ConnectIntervals(current, previous.Next); - } - else - { - this.ConnectIntervals(current, this.head); - } - + this.ConnectIntervals(current, previous != null ? previous.Next : this.head); this.ConnectIntervals(previous, current); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 086ad54b0..fd515c426 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -61,10 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public static int MaxFindCopyLength(int len) - { - return (len < BackwardReferenceEncoder.MaxLength) ? len : BackwardReferenceEncoder.MaxLength; - } + public static int MaxFindCopyLength(int len) => (len < BackwardReferenceEncoder.MaxLength) ? len : BackwardReferenceEncoder.MaxLength; public static int PrefixEncodeBits(int distance, ref int extraBits) { @@ -92,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } else { - return PrefixEncodeNoLUT(distance, ref extraBits, ref extraBitsValue); + return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); } } @@ -107,9 +104,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 8 <= numPixels; i += 8) { var idx = p + i; @@ -129,9 +126,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 4 <= numPixels; i += 4) { var idx = p + i; @@ -151,9 +148,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 4 <= numPixels; i += 4) { var idx = p + i; @@ -199,9 +196,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 8 <= numPixels; i += 8) { var idx = p + i; @@ -221,9 +218,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 4 <= numPixels; i += 4) { var idx = p + i; @@ -243,9 +240,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; - int i; fixed (uint* p = pixelData) { + int i; for (i = 0; i + 4 <= numPixels; i += 4) { var idx = p + i; @@ -757,19 +754,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// Fast calculation of log2(v) for integer input. /// - public static float FastLog2(uint v) - { - return (v < LogLookupIdxMax) ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); - } + public static float FastLog2(uint v) => (v < LogLookupIdxMax) ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); /// /// Fast calculation of v * log2(v) for integer input. /// [MethodImpl(InliningOptions.ShortMethod)] - public static float FastSLog2(uint v) - { - return (v < LogLookupIdxMax) ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); - } + public static float FastSLog2(uint v) => (v < LogLookupIdxMax) ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); [MethodImpl(InliningOptions.ShortMethod)] public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) @@ -779,17 +770,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless m.RedToBlue = (byte)((colorCode >> 16) & 0xff); } + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 [MethodImpl(InliningOptions.ShortMethod)] - public static int NearLosslessBits(int nearLosslessQuality) - { - // 100 -> 0 - // 80..99 -> 1 - // 60..79 -> 2 - // 40..59 -> 3 - // 20..39 -> 4 - // 0..19 -> 5 - return 5 - (nearLosslessQuality / 20); - } + public static int NearLosslessBits(int nearLosslessQuality) => 5 - (nearLosslessQuality / 20); private static float FastSLog2Slow(uint v) { @@ -834,8 +822,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless do { ++logCnt; - v = v >> 1; - y = y << 1; + v >>= 1; + y <<= 1; } while (v >= LogLookupIdxMax); @@ -870,7 +858,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return code; } - private static int PrefixEncodeNoLUT(int distance, ref int extraBits, ref int extraBitsValue) + private static int PrefixEncodeNoLut(int distance, ref int extraBits, ref int extraBitsValue) { int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; @@ -1020,76 +1008,40 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor2(uint left, uint* top) - { - return top[0]; - } + public static uint Predictor2(uint left, uint* top) => top[0]; [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor3(uint left, uint* top) - { - return top[1]; - } + public static uint Predictor3(uint left, uint* top) => top[1]; [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor4(uint left, uint* top) - { - return top[-1]; - } + public static uint Predictor4(uint left, uint* top) => top[-1]; [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor5(uint left, uint* top) - { - return Average3(left, top[0], top[1]); - } + public static uint Predictor5(uint left, uint* top) => Average3(left, top[0], top[1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor6(uint left, uint* top) - { - return Average2(left, top[-1]); - } + public static uint Predictor6(uint left, uint* top) => Average2(left, top[-1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor7(uint left, uint* top) - { - return Average2(left, top[0]); - } + public static uint Predictor7(uint left, uint* top) => Average2(left, top[0]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor8(uint left, uint* top) - { - return Average2(top[-1], top[0]); - } + public static uint Predictor8(uint left, uint* top) => Average2(top[-1], top[0]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor9(uint left, uint* top) - { - return Average2(top[0], top[1]); - } + public static uint Predictor9(uint left, uint* top) => Average2(top[0], top[1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor10(uint left, uint* top) - { - return Average4(left, top[-1], top[0], top[1]); - } + public static uint Predictor10(uint left, uint* top) => Average4(left, top[-1], top[0], top[1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor11(uint left, uint* top) - { - return Select(top[0], left, top[-1]); - } + public static uint Predictor11(uint left, uint* top) => Select(top[0], left, top[-1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor12(uint left, uint* top) - { - return ClampedAddSubtractFull(left, top[0], top[-1]); - } + public static uint Predictor12(uint left, uint* top) => ClampedAddSubtractFull(left, top[0], top[-1]); [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor13(uint left, uint* top) - { - return ClampedAddSubtractHalf(left, top[0], top[-1]); - } + public static uint Predictor13(uint left, uint* top) => ClampedAddSubtractHalf(left, top[0], top[-1]); [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub0(uint* input, int numPixels, uint* output) @@ -1233,10 +1185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// [MethodImpl(InliningOptions.ShortMethod)] - public static int SubSampleSize(int size, int samplingBits) - { - return (size + (1 << samplingBits) - 1) >> samplingBits; - } + public static int SubSampleSize(int size, int samplingBits) => (size + (1 << samplingBits) - 1) >> samplingBits; /// /// Sum of each component, mod 256. @@ -1251,10 +1200,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless // For sign-extended multiplying constants, pre-shifted by 5: [MethodImpl(InliningOptions.ShortMethod)] - public static short Cst5b(int x) - { - return (short)(((short)(x << 8)) >> 5); - } + public static short Cst5b(int x) => (short)(((short)(x << 8)) >> 5); private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { @@ -1285,29 +1231,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentHalf(int a, int b) - { - return (int)Clip255((uint)(a + ((a - b) / 2))); - } + private static int AddSubtractComponentHalf(int a, int b) => (int)Clip255((uint)(a + ((a - b) / 2))); [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentFull(int a, int b, int c) - { - return (int)Clip255((uint)(a + b - c)); - } + private static int AddSubtractComponentFull(int a, int b, int c) => (int)Clip255((uint)(a + b - c)); [MethodImpl(InliningOptions.ShortMethod)] - private static uint Clip255(uint a) - { - return a < 256 ? a : ~a >> 24; - } + private static uint Clip255(uint a) => a < 256 ? a : ~a >> 24; #if SUPPORTS_RUNTIME_INTRINSICS [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 MkCst16(int hi, int lo) - { - return Vector128.Create((hi << 16) | (lo & 0xffff)); - } + private static Vector128 MkCst16(int hi, int lo) => Vector128.Create((hi << 16) | (lo & 0xffff)); #endif private static uint Select(uint a, uint b, uint c) @@ -1329,39 +1263,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average2(uint a0, uint a1) - { - return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); - } + private static uint Average2(uint a0, uint a1) => (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average3(uint a0, uint a1, uint a2) - { - return Average2(Average2(a0, a2), a1); - } + private static uint Average3(uint a0, uint a1, uint a2) => Average2(Average2(a0, a2), a1); [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average4(uint a0, uint a1, uint a2, uint a3) - { - return Average2(Average2(a0, a1), Average2(a2, a3)); - } + private static uint Average4(uint a0, uint a1, uint a2, uint a3) => Average2(Average2(a0, a1), Average2(a2, a3)); [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetArgbIndex(uint idx) - { - return (idx >> 8) & 0xff; - } + private static uint GetArgbIndex(uint idx) => (idx >> 8) & 0xff; [MethodImpl(InliningOptions.ShortMethod)] - private static int ColorTransformDelta(sbyte colorPred, sbyte color) - { - return (colorPred * color) >> 5; - } + private static int ColorTransformDelta(sbyte colorPred, sbyte color) => (colorPred * color) >> 5; [MethodImpl(InliningOptions.ShortMethod)] - private static sbyte U32ToS8(uint v) - { - return (sbyte)(v & 0xff); - } + private static sbyte U32ToS8(uint v) => (sbyte)(v & 0xff); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 31c293800..673ea6d12 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -54,10 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// The backward references to initialize the histogram with. /// The palette code bits. public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) - : this(paletteCodeBits) - { - this.StoreRefs(refs); - } + : this(paletteCodeBits) => this.StoreRefs(refs); /// /// Initializes a new instance of the class. @@ -171,10 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } } - public int NumCodes() - { - return WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); - } + public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); /// /// Estimate how many bits the combined entropy of literals and distance approximately maps to. @@ -631,9 +625,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) - { - return (v > max) ? max : v; - } + private static int ClipMax(int v, int max) => (v > max) ? max : v; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 7a1b7ba3b..835150ea1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -241,18 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy public Vp8MacroBlock CurrentMacroBlock => this.MacroBlockInfo[this.MbX]; - public Vp8MacroBlock LeftMacroBlock - { - get - { - if (this.leftMacroBlock == null) - { - this.leftMacroBlock = new Vp8MacroBlock(); - } - - return this.leftMacroBlock; - } - } + public Vp8MacroBlock LeftMacroBlock => this.leftMacroBlock ??= new Vp8MacroBlock(); public Vp8MacroBlockData CurrentBlockData => this.MacroBlockData[this.MbX]; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 7cac484af..0a51c006a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// /// Gets the probabilities. /// - public Vp8EncProba Proba { get; private set; } + public Vp8EncProba Proba { get; } /// /// Gets the segment features. @@ -157,17 +157,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy /// /// Gets the segment infos. /// - public Vp8SegmentInfo[] SegmentInfos { get; private set; } + public Vp8SegmentInfo[] SegmentInfos { get; } /// /// Gets the macro block info's. /// - public Vp8MacroBlockInfo[] MbInfo { get; private set; } + public Vp8MacroBlockInfo[] MbInfo { get; } /// /// Gets the filter header. /// - public Vp8FilterHeader FilterHeader { get; private set; } + public Vp8FilterHeader FilterHeader { get; } /// /// Gets or sets the global susceptibility. @@ -825,15 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy it.SetSkip(false); // not skipped. it.SetSegment(0); // default segment, spec-wise. - int bestAlpha; - if (this.method <= 1) - { - bestAlpha = it.FastMbAnalyze(this.quality); - } - else - { - bestAlpha = it.MbAnalyzeBestIntra16Mode(); - } + int bestAlpha = this.method <= 1 ? it.FastMbAnalyze(this.quality) : it.MbAnalyzeBestIntra16Mode(); bestUvAlpha = it.MbAnalyzeBestUvMode(); @@ -1348,10 +1340,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static double GetPsnr(long mse, long size) - { - return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; - } + private static double GetPsnr(long mse, long size) => (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; [MethodImpl(InliningOptions.ShortMethod)] private static int GetProba(int a, int b) diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index 24967841d..879139a5d 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -213,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp // 3 reserved bytes should follow which are supposed to be zero. this.currentStream.Read(this.buffer, 0, 3); - if (this.buffer[0] != 0 || this.buffer[1] != 0 | this.buffer[2] != 0) + if (this.buffer[0] != 0 || this.buffer[1] != 0 || this.buffer[2] != 0) { WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); } @@ -527,15 +527,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// The chunk type. /// True, if its an optional chunk type. - private static bool IsOptionalVp8XChunk(WebpChunkType chunkType) + private static bool IsOptionalVp8XChunk(WebpChunkType chunkType) => chunkType switch { - return chunkType switch - { - WebpChunkType.Alpha => true, - WebpChunkType.Animation => true, - WebpChunkType.Iccp => true, - _ => false - }; - } + WebpChunkType.Alpha => true, + WebpChunkType.Animation => true, + WebpChunkType.Iccp => true, + _ => false + }; } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 8e0a14cce..723efdb7d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -133,89 +133,47 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Fact] - public void SubtractGreen_Works() - { - RunSubstractGreenTest(); - } + public void SubtractGreen_Works() => RunSubstractGreenTest(); [Fact] - public void AddGreenToBlueAndRed_Works() - { - RunAddGreenToBlueAndRedTest(); - } + public void AddGreenToBlueAndRed_Works() => RunAddGreenToBlueAndRedTest(); [Fact] - public void TransformColor_Works() - { - RunTransformColorTest(); - } + public void TransformColor_Works() => RunTransformColorTest(); [Fact] - public void TransformColorInverse_Works() - { - RunTransformColorInverseTest(); - } + public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS [Fact] - public void SubtractGreen_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.AllowAll); - } + public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.AllowAll); [Fact] - public void SubtractGreen_WithoutAvx_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX); - } + public void SubtractGreen_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX); [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); - } + public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); [Fact] - public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); - } + public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); [Fact] - public void AddGreenToBlueAndRed_WithoutAvx_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX); - } + public void AddGreenToBlueAndRed_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX); [Fact] - public void AddGreenToBlueAndRed_WithoutAvxOrSSSE3_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); - } + public void AddGreenToBlueAndRed_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); [Fact] - public void TransformColor_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); - } + public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); [Fact] - public void TransformColor_WithoutSSE2_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); - } + public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); [Fact] - public void TransformColorInverse_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); - } + public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); [Fact] - public void TransformColorInverse_WithoutSSE2_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); - } + public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); #endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index eb16a01b8..af46e3c58 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -336,8 +336,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Theory] [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { + where TPixel : unmanaged, IPixel => Assert.Throws( () => { @@ -345,14 +344,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { } }); - } - - [Theory] - [WithFile(Lossless.Earth, PixelTypes.Rgba32)] - public void ProfileTestLossless(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - } } } From 5db2d28e69bfca649dc1ca52079793d6d337f52d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Jan 2021 13:52:56 +0100 Subject: [PATCH 269/359] Write EXIF chunk, if Exif profile is present --- .../Formats/WebP/BitWriter/BitWriterBase.cs | 70 +++++++++++++++++-- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 47 ++++++++----- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 29 +++++++- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 2 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebpConstants.cs | 16 +++++ .../Formats/WebP/WebpDecoderCore.cs | 14 ++-- .../Formats/WebP/WebpThrowHelper.cs | 17 ++--- 8 files changed, 157 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 5946aeb67..60cca018c 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.IO; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { @@ -55,7 +56,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// Writes the encoded image to the stream. /// /// The stream to write to. - public abstract void WriteEncodedImageToStream(Stream stream); + /// The exif profile. + /// The width of the image. + /// The height of the image. + public abstract void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height); protected bool ResizeBuffer(int maxBytes, int sizeRequired) { @@ -84,12 +88,68 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// The block length. protected void WriteRiffHeader(Stream stream, uint riffSize) { - Span buffer = stackalloc byte[4]; - + Span buf = stackalloc byte[4]; stream.Write(WebpConstants.RiffFourCc); - BinaryPrimitives.WriteUInt32LittleEndian(buffer, riffSize); - stream.Write(buffer); + BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize); + stream.Write(buf); stream.Write(WebpConstants.WebPHeader); } + + /// + /// Writes the Exif profile to the stream. + /// + /// The stream to write to. + /// The exif profile bytes. + protected void WriteExifProfile(Stream stream, byte[] exifBytes) + { + DebugGuard.NotNull(exifBytes, nameof(exifBytes)); + + Span buf = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length); + stream.Write(buf); + stream.Write(exifBytes); + } + + /// + /// Writes a VP8X header to the stream. + /// + /// The stream to write to. + /// A exif profile or null, if it does not exist. + /// The width of the image. + /// The height of the image. + protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height) + { + int maxDimension = 16777215; + if (width > maxDimension || height > maxDimension) + { + WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}"); + } + + // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1. + if (width * height > 4294967295ul) + { + WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1"); + } + + uint flags = 0; + if (exifProfile != null) + { + // Set exif bit. + flags |= 8; + } + + Span buf = stackalloc byte[4]; + stream.Write(WebpConstants.Vp8XMagicBytes); + BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, flags); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1); + stream.Write(buf.Slice(0, 3)); + BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1); + stream.Write(buf.Slice(0, 3)); + } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index b9d326ada..97d5cd38c 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { @@ -73,16 +74,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter /// The expected size in bytes. /// The Vp8Encoder. public Vp8BitWriter(int expectedSize, Vp8Encoder enc) - : this(expectedSize) - { - this.enc = enc; - } + : this(expectedSize) => this.enc = enc; /// - public override int NumBytes() - { - return (int)this.pos; - } + public override int NumBytes() => (int)this.pos; public int PutCoeffs(int ctx, Vp8Residual residual) { @@ -290,10 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter } } - private bool PutBit(bool bit, int prob) - { - return this.PutBit(bit ? 1 : 0, prob); - } + private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob); private bool PutBit(int bit, int prob) { @@ -408,8 +400,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter } /// - public override void WriteEncodedImageToStream(Stream stream) + public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height) { + bool isVp8X = false; + byte[] exifBytes = null; + uint riffSize = 0; + if (exifProfile != null) + { + isVp8X = true; + riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; + exifBytes = exifProfile.ToByteArray(); + riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length; + } + this.Finish(); uint numBytes = (uint)this.NumBytes(); int mbSize = this.enc.Mbw * this.enc.Mbh; @@ -427,10 +430,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter // Compute RIFF size // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. - var riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; + riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; // Emit headers and partition #0 - this.WriteWebPHeaders(stream, size0, vp8Size, riffSize); + this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile); bitWriterPartZero.WriteToStream(stream); // Write the encoded image to the stream. @@ -439,6 +442,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { stream.WriteByte(0); } + + if (exifProfile != null) + { + this.WriteExifProfile(stream, exifBytes); + } } private uint GeneratePartition0(Vp8BitWriter bitWriter) @@ -608,9 +616,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter while (it.Next()); } - private void WriteWebPHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize) + private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile) { this.WriteRiffHeader(stream, riffSize); + + // Write VP8X, header if necessary. + if (isVp8X) + { + this.WriteVp8XHeader(stream, exifProfile, width, height); + } + this.WriteVp8Header(stream, vp8Size); this.WriteFrameHeader(stream, size0); } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 6fbb7e3b7..4a560940f 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter { @@ -127,9 +128,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter } /// - public override void WriteEncodedImageToStream(Stream stream) + public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height) { Span buffer = stackalloc byte[4]; + bool isVp8X = false; + byte[] exifBytes = null; + uint riffSize = 0; + if (exifProfile != null) + { + isVp8X = true; + riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; + exifBytes = exifProfile.ToByteArray(); + riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length; + } this.Finish(); uint size = (uint)this.NumBytes(); @@ -137,8 +148,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter // Write RIFF header. uint pad = size & 1; - uint riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad; + riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad; this.WriteRiffHeader(stream, riffSize); + + // Write VP8X, header if necessary. + if (isVp8X) + { + this.WriteVp8XHeader(stream, exifProfile, width, height); + } + + // Write magic bytes indicating its a lossless webp. stream.Write(WebpConstants.Vp8LMagicBytes); // Write Vp8 Header. @@ -146,11 +165,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter stream.Write(buffer); stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); + // Write the encoded bytes of the image to the stream. this.WriteToStream(stream); if (pad == 1) { stream.WriteByte(0); } + + if (exifProfile != null) + { + this.WriteExifProfile(stream, exifBytes); + } } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index b2a254a61..49591c04a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless this.EncodeStream(image); // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(stream); + this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height); } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 0a51c006a..f404fc610 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -321,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy this.AdjustFilterStrength(); // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(stream); + this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height); } /// diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index 98c783a40..239e02d75 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -57,6 +57,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp 0x4C // L }; + /// + /// Signature bytes identifying a VP8X header. + /// + public static readonly byte[] Vp8XMagicBytes = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x58 // X + }; + /// /// The header bytes identifying RIFF file. /// @@ -94,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// public const int Vp8FrameHeaderSize = 10; + /// + /// Size of a VP8X chunk in bytes. + /// + public const int Vp8XChunkSize = 10; + /// /// Size of a chunk header. /// diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index 879139a5d..06527aec0 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp this.Metadata = new ImageMetadata(); this.currentStream = stream; - this.ReadImageHeader(); + var fileSize = this.ReadImageHeader(); using (this.webImageInfo = this.ReadVp8Info()) { @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// Reads and skips over the image header. /// - /// The chunk size in bytes. + /// The file size in bytes. private uint ReadImageHeader() { // Skip FourCC header, we already know its a RIFF file at this point. @@ -140,12 +140,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp // Read file size. // The size of the file in bytes starting at offset 8. // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. - uint chunkSize = this.ReadChunkSize(); + uint fileSize = this.ReadChunkSize(); // Skip 'WEBP' from the header. this.currentStream.Skip(4); - return chunkSize; + return fileSize; } /// @@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp private WebpImageInfo ReadVp8XHeader() { var features = new WebpFeatures(); - uint chunkSize = this.ReadChunkSize(); + uint fileSize = this.ReadChunkSize(); // The first byte contains information about the image features used. byte imageFeatures = (byte)this.currentStream.ReadByte(); @@ -474,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp WebpChunkType chunkType = this.ReadChunkType(); uint chunkLength = this.ReadChunkSize(); - if (chunkType == WebpChunkType.Exif) + if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) { var exifData = new byte[chunkLength]; this.currentStream.Read(exifData, 0, (int)chunkLength); @@ -482,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp } else { - // Skip XMP chunk data for now. + // Skip XMP chunk data or any duplicate EXIF chunk. this.currentStream.Skip((int)chunkLength); } } diff --git a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs index 4918e1ed3..13a62454b 100644 --- a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs @@ -13,19 +13,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// The error message for the exception. [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowImageFormatException(string errorMessage) - { - throw new ImageFormatException(errorMessage); - } + public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); /// /// Cold path optimization for throwing -s /// /// The error message for the exception. [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowNotSupportedException(string errorMessage) - { - throw new NotSupportedException(errorMessage); - } + public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); + + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); } } From 67aace4daf7e6b4575b06667bfcef64ad936578f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Jan 2021 13:58:15 +0100 Subject: [PATCH 270/359] Add test for writing exif chunk --- .../Formats/WebP/WebpMetaDataTests.cs | 102 ++++++++++++++---- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index b73dcdfcf..9567684db 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -11,48 +13,104 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpMetaDataTests { + private readonly Configuration configuration; + + private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; + + public WebpMetaDataTests() + { + this.configuration = new Configuration(); + this.configuration.AddWebp(); + } + [Theory] - [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, false)] [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, true)] + [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, true)] public void IgnoreMetadata_ControlsWhetherExifIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - using (Image image = provider.GetImage(decoder)) + using Image image = provider.GetImage(decoder); + if (ignoreMetadata) { - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - } - else - { - Assert.NotNull(image.Metadata.ExifProfile); - Assert.NotEmpty(image.Metadata.ExifProfile.Values); - } + Assert.Null(image.Metadata.ExifProfile); + } + else + { + ExifProfile exifProfile = image.Metadata.ExifProfile; + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Make) && m.GetValue().Equals("Canon")); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Model) && m.GetValue().Equals("Canon PowerShot S40")); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); } } [Theory] - [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossy.WithIccp, PixelTypes.Rgba32, false)] [WithFile(TestImages.WebP.Lossy.WithIccp, PixelTypes.Rgba32, true)] + [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, true)] public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - using (Image image = provider.GetImage(decoder)) + using Image image = provider.GetImage(decoder); + if (ignoreMetadata) { - if (ignoreMetadata) - { - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.IccProfile); - Assert.NotEmpty(image.Metadata.IccProfile.Entries); - } + Assert.Null(image.Metadata.IccProfile); } + else + { + Assert.NotNull(image.Metadata.IccProfile); + Assert.NotEmpty(image.Metadata.IccProfile.Entries); + } + } + + [Theory] + [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32)] + public void EncodeLossyWebp_PreservesExif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(WebpDecoder); + using var memoryStream = new MemoryStream(); + ExifProfile expectedExif = input.Metadata.ExifProfile; + + // act + input.Save(memoryStream, new WebpEncoder() { Lossy = true }); + memoryStream.Position = 0; + + // assert + using Image image = WebpDecoder.Decode(this.configuration, memoryStream); + ExifProfile actualExif = image.Metadata.ExifProfile; + Assert.NotNull(actualExif); + Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + } + + [Theory] + [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32)] + public void EncodeLosslessWebp_PreservesExif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(WebpDecoder); + using var memoryStream = new MemoryStream(); + ExifProfile expectedExif = input.Metadata.ExifProfile; + + // act + input.Save(memoryStream, new WebpEncoder() { Lossy = false }); + memoryStream.Position = 0; + + // assert + using Image image = WebpDecoder.Decode(this.configuration, memoryStream); + ExifProfile actualExif = image.Metadata.ExifProfile; + Assert.NotNull(actualExif); + Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); } } } From be048ec0b4dabb3cfeb5e96b897d2d464a646152 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Mar 2021 14:05:15 +0100 Subject: [PATCH 271/359] Add Webp Encoder/Decoder AoT seeds --- src/ImageSharp/Advanced/AotCompilerTools.cs | 5 +++++ src/ImageSharp/Formats/WebP/WebpEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebpEncoderCore.cs | 13 ++++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index ea4cd1c8c..b61e63a53 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -194,6 +195,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileImageEncoderInternals() where TPixel : unmanaged, IPixel { + default(WebpEncoderCore).Encode(default, default, default); default(BmpEncoderCore).Encode(default, default, default); default(GifEncoderCore).Encode(default, default, default); default(JpegEncoderCore).Encode(default, default, default); @@ -209,6 +211,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileImageDecoderInternals() where TPixel : unmanaged, IPixel { + default(WebpDecoderCore).Decode(default, default, default); default(BmpDecoderCore).Decode(default, default, default); default(GifDecoderCore).Decode(default, default, default); default(JpegDecoderCore).Decode(default, default, default); @@ -224,6 +227,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileImageEncoders() where TPixel : unmanaged, IPixel { + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); @@ -239,6 +243,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileImageDecoders() where TPixel : unmanaged, IPixel { + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index 88bceddb2..5d86af7ee 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp where TPixel : unmanaged, IPixel { var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream); + return encoder.EncodeAsync(image, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 2ff6c6001..075f8f53e 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// /// Image encoder for writing an image to a stream in the WebP format. /// - internal sealed class WebpEncoderCore + internal sealed class WebpEncoderCore : IImageEncoderInternals { /// /// Used for allocating memory during processing operations. @@ -68,7 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(Image image, Stream stream) + /// The token to monitor for cancellation requests. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); @@ -92,12 +94,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// The pixel format. /// The to encode from. /// The to encode the image data to. - public async Task EncodeAsync(Image image, Stream stream) + /// The token to monitor for cancellation requests. + public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { if (stream.CanSeek) { - this.Encode(image, stream); + this.Encode(image, stream, cancellationToken); } else { @@ -105,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp { this.Encode(image, ms); ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); + await ms.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); } } } From ce5338bf92b1e763366527b48138e3b23b0bc90c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Mar 2021 14:38:23 +0100 Subject: [PATCH 272/359] Fix build errors --- src/ImageSharp/Formats/WebP/WebpEncoderCore.cs | 15 ++++++++++++--- .../Formats/ImageFormatManagerTests.cs | 1 - .../Formats/WebP/LosslessUtilsTests.cs | 10 +++++----- .../Formats/WebP/PredictorEncoderTests.cs | 18 +++++------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 075f8f53e..66d3c86e4 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -48,6 +48,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp /// private readonly int entropyPasses; + /// + /// The global configuration. + /// + private Configuration configuration; + /// /// Initializes a new instance of the class. /// @@ -76,6 +81,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + this.configuration = image.GetConfiguration(); + if (this.lossy) { var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method, this.entropyPasses); @@ -98,6 +105,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.configuration = image.GetConfiguration(); + if (stream.CanSeek) { this.Encode(image, stream, cancellationToken); @@ -106,9 +115,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp { using (var ms = new MemoryStream()) { - this.Encode(image, ms); + this.Encode(image, ms, cancellationToken); ms.Position = 0; - await ms.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); + await ms.CopyToAsync(stream, this.configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); } } } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index f4a704de6..dea8c62e1 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -7,7 +7,6 @@ using System.Linq; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 723efdb7d..93768f7db 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Trait("Format", "Webp")] public class LosslessUtilsTests { - private static void RunSubstractGreenTest() + private static void RunSubtractGreenTest() { uint[] pixelData = { @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Fact] - public void SubtractGreen_Works() => RunSubstractGreenTest(); + public void SubtractGreen_Works() => RunSubtractGreenTest(); [Fact] public void AddGreenToBlueAndRed_Works() => RunAddGreenToBlueAndRedTest(); @@ -146,13 +146,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP #if SUPPORTS_RUNTIME_INTRINSICS [Fact] - public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.AllowAll); + public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); [Fact] - public void SubtractGreen_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX); + public void SubtractGreen_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX); [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubstractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); + public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); [Fact] public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 58d91951c..a0bb82e80 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -27,27 +27,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP #if SUPPORTS_RUNTIME_INTRINSICS [Fact] public void ColorSpaceTransform_WithPeakImage_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); - } + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); [Fact] public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - } + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); [Fact] public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); - } + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); [Fact] public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - } + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); #endif private static void RunColorSpaceTransformTestWithPeakImage() @@ -91,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP // Convert image pixels to bgra array. var imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Peak)); - using var image = Image.Load(imgBytes); + using var image = Image.Load(imgBytes); uint[] bgra = ToBgra(image); int colorTransformBits = 3; From f99ed846ebd0429784422b5bbd960f1bdde380b5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Mar 2021 16:09:45 +0100 Subject: [PATCH 273/359] Remove not needed EncodeAsync from webp encoder --- .../Formats/WebP/WebpEncoderCore.cs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 66d3c86e4..527d151bb 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -94,32 +94,5 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp enc.Encode(image, stream); } } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - this.configuration = image.GetConfiguration(); - - if (stream.CanSeek) - { - this.Encode(image, stream, cancellationToken); - } - else - { - using (var ms = new MemoryStream()) - { - this.Encode(image, ms, cancellationToken); - ms.Position = 0; - await ms.CopyToAsync(stream, this.configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } } } From d6520d2574e4168a6978e771095c8c89a395b9ed Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 14 Apr 2021 12:48:19 +0200 Subject: [PATCH 274/359] Fix issue #1594 --- .../Formats/WebP/Lossless/WebpLosslessDecoder.cs | 9 ++++----- .../Formats/WebP/WebpDecoderTests.cs | 15 ++++++++++++++- tests/ImageSharp.Tests/TestImages.cs | 3 +++ tests/Images/Input/WebP/issues/Issue1594.webp | 3 +++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/WebP/issues/Issue1594.webp diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index 7a58ccba0..ee10bdb0e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless { this.DecodeImageStream(decoder, width, height, true); this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); - this.DecodePixelValues(decoder, pixels); + this.DecodePixelValues(decoder, pixels, width, height); } } @@ -185,18 +185,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless return pixelData; } - private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels) + private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels, int width, int height) where TPixel : unmanaged, IPixel { Span pixelData = decoder.Pixels.GetSpan(); - int width = decoder.Width; // Apply reverse transformations, if any are present. ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); int bytesPerRow = width * 4; - for (int y = 0; y < decoder.Height; y++) + for (int y = 0; y < height; y++) { Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); Span pixelRow = pixels.GetRowSpan(y); @@ -859,7 +858,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless /// /// Decodes the next Huffman code from the bit-stream. - /// FillBitWindow(br) needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. + /// FillBitWindow() needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. /// private uint ReadSymbol(Span table) { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index af46e3c58..19cb3f695 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -323,7 +323,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecodeLosslessWithIssues(TestImageProvider provider) + public void WebpDecoder_CanDecode_Lossless_WithIssues(TestImageProvider provider) where TPixel : unmanaged, IPixel { // Just make sure no exception is thrown. The reference decoder fails to load the image. @@ -333,6 +333,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + // https://github.com/SixLabors/ImageSharp/issues/1594 + [Theory] + [WithFile(Lossy.Issue1594, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue1594(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + [Theory] [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index df0a8e623..a33c502af 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -647,6 +647,9 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; public const string AlphaThinkingSmiley = "WebP/1602311202.webp"; public const string AlphaSticker = "WebP/sticker.webp"; + + // Issues + public const string Issue1594 = "Webp/issues/Issue1594.webp"; } } } diff --git a/tests/Images/Input/WebP/issues/Issue1594.webp b/tests/Images/Input/WebP/issues/Issue1594.webp new file mode 100644 index 000000000..664db4e2f --- /dev/null +++ b/tests/Images/Input/WebP/issues/Issue1594.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37413b1a89ba7d42cdfe98196775c2ddc2f8f4d143f6fc65218dc288423b7177 +size 62 From d014dc23c8ae958a0e2d2d53763bc3f78a098584 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 14 Apr 2021 13:18:20 +0200 Subject: [PATCH 275/359] Fix test file name --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a33c502af..319437a39 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -649,7 +649,7 @@ namespace SixLabors.ImageSharp.Tests public const string AlphaSticker = "WebP/sticker.webp"; // Issues - public const string Issue1594 = "Webp/issues/Issue1594.webp"; + public const string Issue1594 = "WebP/issues/Issue1594.webp"; } } } From e613094479abe91e18698edc6e800684b45e1db5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 25 May 2021 21:04:00 +0200 Subject: [PATCH 276/359] Rename namespace to SixLabors.ImageSharp.Formats.Webp --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- src/ImageSharp/Formats/ImageExtensions.Save.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 6 +++--- src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs | 2 +- src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs | 2 +- src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs | 4 ++-- src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs | 4 ++-- src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs | 2 +- src/ImageSharp/Formats/WebP/EntropyIx.cs | 2 +- src/ImageSharp/Formats/WebP/HistoIx.cs | 2 +- src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs | 2 +- .../Formats/WebP/Lossless/BackwardReferenceEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostManager.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CostModel.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs | 2 +- .../Formats/WebP/Lossless/WebpLosslessDecoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/PassStats.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs | 2 +- src/ImageSharp/Formats/WebP/MetadataExtensions.cs | 2 +- src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs | 2 +- src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs | 2 +- src/ImageSharp/Formats/WebP/WebpChunkType.cs | 2 +- src/ImageSharp/Formats/WebP/WebpCommonUtils.cs | 2 +- src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs | 3 +-- src/ImageSharp/Formats/WebP/WebpConstants.cs | 2 +- src/ImageSharp/Formats/WebP/WebpDecoder.cs | 3 +-- src/ImageSharp/Formats/WebP/WebpDecoderCore.cs | 8 ++++---- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 +-- src/ImageSharp/Formats/WebP/WebpEncoderCore.cs | 6 +++--- src/ImageSharp/Formats/WebP/WebpFeatures.cs | 2 +- src/ImageSharp/Formats/WebP/WebpFormat.cs | 5 ++--- src/ImageSharp/Formats/WebP/WebpFormatType.cs | 2 +- src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/WebP/WebpImageInfo.cs | 6 +++--- src/ImageSharp/Formats/WebP/WebpLookupTables.cs | 2 +- src/ImageSharp/Formats/WebP/WebpMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/WebpThrowHelper.cs | 2 +- tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs | 2 +- tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs | 2 +- .../ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs | 2 +- .../Formats/WebP/PredictorEncoderTests.cs | 4 ++-- tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs | 2 +- .../TestUtilities/TestEnvironment.Formats.cs | 2 +- .../TestUtilities/Tests/TestEnvironmentTests.cs | 2 +- 116 files changed, 135 insertions(+), 139 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 622af2047..bf04cb702 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,7 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 1110d3b2f..c5237f2bc 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -12,7 +12,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Tiff; namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index 7b575adae..d7bcb846d 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { internal enum AlphaCompressionMethod { diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 041072fa3..ff3a31636 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Implements decoding for lossy alpha chunks which may be compressed. diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 2c1892532..76a2f1128 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -7,7 +7,7 @@ using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader { /// /// Base class for VP8 and VP8L bitreader. diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index b796e6b1a..42168761a 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader { /// /// A bit reader for VP8 streams. diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index 5f9941929..fa59d82d9 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader { /// /// A bit reader for reading lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 60cca018c..5d9114001 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -6,7 +6,7 @@ using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { internal abstract class BitWriterBase { diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 97d5cd38c..106a3e48e 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -4,10 +4,10 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; +using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { /// /// A bit writer for writing lossy webp streams. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 4a560940f..46fb14d38 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -4,10 +4,10 @@ using System; using System.Buffers.Binary; using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { /// /// A bit writer for writing lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs b/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs index 7077bf9a0..5a8f178da 100644 --- a/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs +++ b/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Helper methods for the Configuration. diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs index f9a70f919..c72ddeb42 100644 --- a/src/ImageSharp/Formats/WebP/EntropyIx.cs +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// These five modes are evaluated and their respective entropy is computed. diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs index d766a84bf..3db720265 100644 --- a/src/ImageSharp/Formats/WebP/HistoIx.cs +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { internal enum HistoIx { diff --git a/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs index 81c875761..7bd78da3d 100644 --- a/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Image decoder options for generating an image out of a webp stream. diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 46f016a5f..2d8a7fdeb 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Configuration options for use during webp encoding. diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index f5f713fe1..f4806dd52 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class BackwardReferenceEncoder { diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index 7fd12236e..b629a6845 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index 2c09be2cb..b4038b141 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs index 2fce1651b..828487eb4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// To perform backward reference every pixel at index index_ is considered and diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index b1aa98218..4038555db 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// The CostManager is in charge of managing intervals and costs. diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs index 8edbd0aca..c210fa7d5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class CostModel { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs index 62ba42f9b..a36c70bca 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class CrunchConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs index 4dc59c0c6..22fbcdcf8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class CrunchSubConfig { diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs index 1b4011108..2c5850142 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Data container to keep track of cost range for the three dominant entropy symbols. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index a25dbffb4..a038248f1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Huffman table group. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs index 5caee010e..5f5f5d874 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal struct HistogramBinInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index dd4a91961..ece41e50e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class HistogramEncoder { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs index 0b4c20926..3cbc2062a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index 4f43725f4..f451df80b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Five Huffman codes are used at each meta code. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index a7bd5f919..f75c64de1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index 0a1f7d60f..927e0e170 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Represents the Huffman tree. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index a3944a6d4..1b5173c63 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Represents the tree codes (depth and bits array). diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index c0472c651..159e9cd9c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Holds the tree header in coded form. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index c467ff827..18817a33b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Utility functions related to creating the huffman tables. diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index fd515c426..c5ba2c46d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -11,7 +11,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Utility functions for the lossless decoder. diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index fea729048..9fcebfb5d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] internal class PixOrCopy diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs index 1a93ef6cc..0d7023ffc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal enum PixOrCopyMode { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index b2f5c88b7..687dc76ff 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -10,7 +10,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Image transform methods for the lossless webp encoder. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index 2182e6d81..b197a2c00 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LBackwardRefs { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index c11602c72..b6d06b676 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Holds bit entropy results and entropy-related functions. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index da9cb9078..a95ec0a49 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Holds information for decoding a lossless webp image. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 49591c04a..ead813751 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -8,11 +8,11 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter; +using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Encoder for lossless webp images. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index d99a26b01..0e6c4aece 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LHashChain { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 673ea6d12..3be3808b3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LHistogram : IDeepCloneable { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs index 34fca4018..bbc2c3479 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal enum Vp8LLz77Type { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index 4c776c640..773cf9331 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LMetadata { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs index ff3d02705..86454bd71 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal struct Vp8LMultipliers { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index 9c3022e9f..27ddcfd43 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LStreaks { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index de6b9d222..247512118 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -4,7 +4,7 @@ using System.Buffers; using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Data associated with a VP8L transformation to reduce the entropy. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index 8dc2af6dd..d6a069da2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Enum for the different transform types. Transformations are reversible manipulations of the image data diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index ee10bdb0e..0a450f11f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -7,11 +7,11 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index 231fc75f1..2f3ba3766 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal enum IntraPredictionMode { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index 35231a913..1a49b1427 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Enum for the different loop filters used. VP8 supports two types of loop filters. diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index fcfce918a..8c57be943 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -6,7 +6,7 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal static class LossyUtils { diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs index 115e87245..e3ac6562c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Class for organizing convergence in either size or PSNR. diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index e45ba6b06..5763e79a9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Quantization methods. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs index 09424fb79..92479a400 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// All the probabilities associated to one band. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs index c772b65c7..3d3a522ba 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8CostArray { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 835150ea1..6c83cce69 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -3,10 +3,10 @@ using System; using System.Buffers; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Holds information for decoding a lossy webp image. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 38640e75f..d9ca9d0cf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Iterator structure to iterate through macroblocks, pointing to the diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index a39919d7f..a2c3a001e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8EncProba { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs index 37acc565e..033bad02c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8EncSegmentHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index f404fc610..40c0cef3a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter; +using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Encoder for lossy webp images. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs index e2cad7faf..efdd9c605 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Methods for encoding a VP8 frame. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 67dbb0baf..b7d2a1a84 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8FilterHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index d7155d3e6..8ddc5f7de 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Filter information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index 2cef291b7..de6763b35 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Vp8 frame header information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index ead8bd573..aa4eb4208 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal ref struct Vp8Io { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index 76a9b76f7..a57590514 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Contextual macroblock information. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index b955cac6a..e1a8ad1a2 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Data needed to reconstruct a macroblock. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs index 4e22b5f58..a348d19a6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { [DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] internal class Vp8MacroBlockInfo diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs index a9325693f..b5f73e73e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal enum Vp8MacroBlockType { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 7ceb2ed1d..7b001b72a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8Matrix { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index a5360707b..049b7caed 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Class to accumulate score and info during RD-optimization and mode evaluation. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 020ff07a3..3449c5cd0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8PictureHeader { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index 3d37c2018..d21040b6c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Data for all frame-persistent probabilities. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index 30e1f79ee..fce157044 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Probabilities associated to one of the contexts. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index 435631aae..43aaf6633 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8QuantMatrix { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs index 4b937b813..1b077184b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Rate-distortion optimization levels diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index f980bd3b9..c50fede57 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// On-the-fly info about the current set of residuals. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index 96f1fbd60..231ccf0d9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Segment features. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index cbf946b9f..2051a2079 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8SegmentInfo { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs index 336124378..c9738cf0c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8Stats { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs index 34e7d6733..60e7e54ae 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8StatsArray { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index a74231ff1..ffae8abf3 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8TopSamples { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index b16792cef..836df6403 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -6,11 +6,11 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { /// /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 00407608b..afcd13ff9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal static class YuvConversion { diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs index 810eace48..63f8e3427 100644 --- a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs index f32d2cf68..a301239c0 100644 --- a/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Enum for the different alpha filter types. diff --git a/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs index b6dc5c9fe..fe2ad79fc 100644 --- a/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Enumerates the available bits per pixel the webp image uses. diff --git a/src/ImageSharp/Formats/WebP/WebpChunkType.cs b/src/ImageSharp/Formats/WebP/WebpChunkType.cs index afb1bf97d..1b2a422bc 100644 --- a/src/ImageSharp/Formats/WebP/WebpChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebpChunkType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Contains a list of different webp chunk types. diff --git a/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs index 7a93b0f10..82af88d67 100644 --- a/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Utility methods for lossy and lossless webp format. diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index d032578ca..b8e74a873 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -1,10 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// - /// EXPERIMENTAL: /// Registers the image encoders, decoders and mime type detectors for the webp format. /// public sealed class WebpConfigurationModule : IConfigurationModule diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index 239e02d75..f6e997fb4 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Constants used for encoding and decoding VP8 and VP8L bitstreams. diff --git a/src/ImageSharp/Formats/WebP/WebpDecoder.cs b/src/ImageSharp/Formats/WebP/WebpDecoder.cs index cc2236a70..84c758c78 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoder.cs @@ -9,10 +9,9 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// - /// EXPERIMENTAL: /// Image decoder for generating an image out of a webp stream. /// public sealed class WebpDecoder : IImageDecoder, IWebpDecoderOptions, IImageInfoDetector diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index 06527aec0..caa64ee61 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -5,9 +5,9 @@ using System; using System.Buffers.Binary; using System.IO; using System.Threading; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; +using SixLabors.ImageSharp.Formats.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -15,7 +15,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Performs the webp decoding operation. diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index 5d86af7ee..dc01840da 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -7,10 +7,9 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// - /// EXPERIMENTAL: /// Image encoder for writing an image to a stream in the WebP format. /// public sealed class WebpEncoder : IImageEncoder, IWebPEncoderOptions diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 527d151bb..b785345ee 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -5,12 +5,12 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; +using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Image encoder for writing an image to a stream in the WebP format. diff --git a/src/ImageSharp/Formats/WebP/WebpFeatures.cs b/src/ImageSharp/Formats/WebP/WebpFeatures.cs index 4a0825c03..385fe84d0 100644 --- a/src/ImageSharp/Formats/WebP/WebpFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebpFeatures.cs @@ -4,7 +4,7 @@ using System; using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Image features of a VP8X image. diff --git a/src/ImageSharp/Formats/WebP/WebpFormat.cs b/src/ImageSharp/Formats/WebP/WebpFormat.cs index 6aaa24728..6a23b9067 100644 --- a/src/ImageSharp/Formats/WebP/WebpFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebpFormat.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// - /// EXPERIMENTAL: - /// Registers the image encoders, decoders and mime type detectors for the WebP format + /// Registers the image encoders, decoders and mime type detectors for the Webp format /// public sealed class WebpFormat : IImageFormat { diff --git a/src/ImageSharp/Formats/WebP/WebpFormatType.cs b/src/ImageSharp/Formats/WebP/WebpFormatType.cs index 3835f8804..fdeb1447e 100644 --- a/src/ImageSharp/Formats/WebP/WebpFormatType.cs +++ b/src/ImageSharp/Formats/WebP/WebpFormatType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Info about the webp format used. diff --git a/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs index 54fcbca46..81655b249 100644 --- a/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Detects WebP file headers. diff --git a/src/ImageSharp/Formats/WebP/WebpImageInfo.cs b/src/ImageSharp/Formats/WebP/WebpImageInfo.cs index 3fa84e7a8..530f5c0a5 100644 --- a/src/ImageSharp/Formats/WebP/WebpImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebpImageInfo.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Experimental.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy; +using SixLabors.ImageSharp.Formats.Webp.BitReader; +using SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { internal class WebpImageInfo : IDisposable { diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs index 14e5d333b..afe9677c7 100644 --- a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { #pragma warning disable SA1201 // Elements should appear in the correct order internal static class WebpLookupTables diff --git a/src/ImageSharp/Formats/WebP/WebpMetadata.cs b/src/ImageSharp/Formats/WebP/WebpMetadata.cs index dea2672f1..0eb466239 100644 --- a/src/ImageSharp/Formats/WebP/WebpMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebpMetadata.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { /// /// Provides WebP specific metadata information for the image. diff --git a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs index 13a62454b..fffdd3410 100644 --- a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Experimental.Webp +namespace SixLabors.ImageSharp.Formats.Webp { internal static class WebpThrowHelper { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 1ace21778..b25da186b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -5,7 +5,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index ae2091957..b55e630c9 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -4,7 +4,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs index ac60fc482..7559bafc2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 93768f7db..9074c06ad 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index a0bb82e80..421015d1f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -3,8 +3,8 @@ using System; using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Webp; -using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless; +using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.PixelFormats; #if SUPPORTS_RUNTIME_INTRINSICS using SixLabors.ImageSharp.Tests.TestUtilities; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 19cb3f695..5384a02c9 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -3,7 +3,7 @@ using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index ac2fcaae7..11964f253 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 9567684db..34ff9a1f5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index d8d9d1c9d..2bcee62bd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,7 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index da2ed6375..4ad32a379 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -5,7 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Experimental.Webp; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; From c4a1b994e69536522c950f8b08aa5f85ebbebac9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 27 May 2021 21:02:45 +0200 Subject: [PATCH 277/359] Fix issue with encoding 1 by 1 pixel lossless image --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 48 +++++++++---------- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 34 +++++++------ .../Formats/WebP/WebpEncoderCore.cs | 1 - .../Codecs/EncodeTiff.cs | 8 +--- .../Formats/WebP/WebpEncoderTests.cs | 21 ++++++-- .../Metadata/ImageMetadataTests.cs | 4 +- .../TestUtilities/TestEnvironment.Formats.cs | 2 +- .../Tests/TestEnvironmentTests.cs | 2 +- 10 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index bf04cb702..3961cc6c5 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,13 +7,13 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index f4806dd52..ed4bfe908 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Keep the best backward references. var histo = new Vp8LHistogram(worst, cacheBitsTmp); - var bitCost = histo.EstimateBits(); + double bitCost = histo.EstimateBits(); if (lz77TypeBest == 0 || bitCost < bitCostBest) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index ead813751..f3c4ad1ca 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Quality/speed trade-off (0=fast, 6=slower-better). public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method) { - var pixelCount = width * height; + int pixelCount = width * height; int initialSize = pixelCount * 2; this.quality = Numerics.Clamp(quality, 0, 100); @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); - this.UseCrossColorTransform = redAndBlueAlwaysZero ? false : this.UsePredictorTransform; + this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; this.AllocateTransformBuffer(width, height); // Reset any parameter in the encoder that is set in the previous iteration. @@ -335,14 +335,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int height = image.Height; // Check if we only deal with a small number of colors and should use a palette. - var usePalette = this.AnalyzeAndCreatePalette(image); + bool usePalette = this.AnalyzeAndCreatePalette(image); // Empirical bit sizes. this.HistoBits = GetHistoBits(this.method, usePalette, width, height); this.TransformBits = GetTransformBits(this.method, this.HistoBits); // Try out multiple LZ77 on images with few colors. - var nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; + int nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); bool doNotCache = false; @@ -382,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Fill in the different LZ77s. foreach (CrunchConfig crunchConfig in crunchConfigs) { - for (var j = 0; j < nlz77s; ++j) + for (int j = 0; j < nlz77s; ++j) { crunchConfig.SubConfigs.Add(new CrunchSubConfig { @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); - var histogramSymbols = new ushort[histogramImageXySize]; + ushort[] histogramSymbols = new ushort[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { @@ -452,8 +452,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); // Create Huffman bit lengths and codes for each histogram image. - var histogramImageSize = histogramImage.Count; - var bitArraySize = 5 * histogramImageSize; + int histogramImageSize = histogramImage.Count; + int bitArraySize = 5 * histogramImageSize; var huffmanCodes = new HuffmanTreeCode[bitArraySize]; for (int i = 0; i < huffmanCodes.Length; i++) { @@ -601,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { int cacheBits = 0; - var histogramSymbols = new ushort[1]; // Only one tree, one symbol. + ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. var huffmanCodes = new HuffmanTreeCode[5]; for (int i = 0; i < huffmanCodes.Length; i++) @@ -728,8 +728,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) { int i; - var codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; - var codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; + byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; + short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; var huffmanCode = new HuffmanTreeCode { NumSymbols = WebpConstants.CodeLengthCodes, @@ -738,9 +738,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless }; this.bitWriter.PutBits(0, 1); - var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - var histogram = new uint[WebpConstants.CodeLengthCodes + 1]; - var bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; + int numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + uint[] histogram = new uint[WebpConstants.CodeLengthCodes + 1]; + bool[] bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; for (i = 0; i < numTokens; i++) { histogram[tokens[i].Code]++; @@ -758,7 +758,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int ix = tokens[i].Code; if (ix == 0 || ix == 17 || ix == 18) { - trimmedLength--; // discount trailing zeros. + trimmedLength--; // Discount trailing zeros. trailingZeroBits += codeLengthBitDepth[ix]; if (ix == 17) { @@ -775,8 +775,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - var writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; - var length = writeTrimmedLength ? trimmedLength : numTokens; + bool writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + int length = writeTrimmedLength ? trimmedLength : numTokens; this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); if (writeTrimmedLength) { @@ -976,8 +976,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless prevRow = currentRow; } - var entropyComp = new double[(int)HistoIx.HistoTotal]; - var entropy = new double[(int)EntropyIx.NumEntropyIx]; + double[] entropyComp = new double[(int)HistoIx.HistoTotal]; + double[] entropy = new double[(int)EntropyIx.NumEntropyIx]; int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; // Let's add one zero to the predicted histograms. The zeros are removed @@ -1195,7 +1195,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - var buffer = new uint[PaletteInvSize]; + uint[] buffer = new uint[PaletteInvSize]; // Try to find a perfect hash function able to go from a color to an index // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. @@ -1246,8 +1246,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - var idxMap = new uint[paletteSize]; - var paletteSorted = new uint[paletteSize]; + uint[] idxMap = new uint[paletteSize]; + uint[] paletteSorted = new uint[paletteSize]; PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); } @@ -1464,7 +1464,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - var end = 5 * histogramImage.Count; + int end = 5 * histogramImage.Count; for (int i = 0; i < end; i++) { int bitLength = huffmanCodes[i].NumSymbols; @@ -1477,7 +1477,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Create Huffman trees. - var bufRle = new bool[maxNumSymbols]; + bool[] bufRle = new bool[maxNumSymbols]; var huffTree = new HuffmanTree[3 * maxNumSymbols]; for (int i = 0; i < huffTree.Length; i++) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 0e6c4aece..f5762b6f8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -60,16 +60,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int iterMax = GetMaxItersForQuality(quality); int windowSize = GetWindowSizeForHashChain(quality, xSize); int pos; + + if (size <= 2) + { + this.OffsetLength[0] = 0; + return; + } + using IMemoryOwner hashToFirstIndexBuffer = memoryAllocator.Allocate(HashSize); Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); // Initialize hashToFirstIndex array to -1. hashToFirstIndex.Fill(-1); - var chain = new int[size]; + int[] chain = new int[size]; // Fill the chain linking pixels with the same hash. - var bgraComp = bgra[0] == bgra[1]; + bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; for (pos = 0; pos < size - 2;) { uint hashCode; @@ -78,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Consecutive pixels with the same color will share the same hash. // We therefore use a different hash: the color and its repetition length. - var tmp = new uint[2]; + uint[] tmp = new uint[2]; uint len = 1; tmp[0] = bgra[pos]; @@ -168,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless pos = minPos - 1; } - var bestBgra = bgra.Slice(bgraStart)[bestLength]; + uint bestBgra = bgra.Slice(bgraStart)[bestLength]; for (; pos >= minPos && (--iter > 0); pos = chain[pos]) { @@ -194,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // We have the best match but in case the two intervals continue matching // to the left, we have the best matches for the left-extended pixels. - var maxBasePosition = (uint)basePosition; + uint maxBasePosition = (uint)basePosition; while (true) { this.OffsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; @@ -231,16 +238,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public int FindLength(int basePosition) - { - return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); - } + public int FindLength(int basePosition) => (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); [MethodImpl(InliningOptions.ShortMethod)] - public int FindOffset(int basePosition) - { - return (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); - } + public int FindOffset(int basePosition) => (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); /// /// Calculates the hash for a pixel pair. @@ -252,7 +253,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { uint key = bgra[1] * HashMultiplierHi; key += bgra[0] * HashMultiplierLo; - key = key >> (32 - HashBits); + key >>= 32 - HashBits; return key; } @@ -263,10 +264,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The quality. /// Number of hash chain lookups. [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMaxItersForQuality(int quality) - { - return 8 + (quality * quality / 128); - } + private static int GetMaxItersForQuality(int quality) => 8 + (quality * quality / 128); [MethodImpl(InliningOptions.ShortMethod)] private static int GetWindowSizeForHashChain(int quality, int xSize) diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index b785345ee..ecc940782 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs index 39055faf5..0056a187b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs ImageCodecInfo codec = FindCodecForType("image/tiff"); using var parameters = new EncoderParameters(1) { - Param = {[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression))} + Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } }; using var memoryStream = new MemoryStream(); @@ -73,12 +73,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb; - // Workaround for 1-bit bug - if (this.Compression == TiffCompression.CcittGroup3Fax || this.Compression == TiffCompression.Ccitt1D) - { - photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; - } - var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; using var memoryStream = new MemoryStream(); this.core.SaveAsTiff(memoryStream, encoder); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 11964f253..828fc0dcd 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; using Image image = provider.GetImage(); - var testOutputDetails = string.Concat("lossless", "_q", quality); + string testOutputDetails = string.Concat("lossless", "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; using Image image = provider.GetImage(); - var testOutputDetails = string.Concat("lossless", "_m", method); + string testOutputDetails = string.Concat("lossless", "_m", method); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } @@ -67,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; using Image image = provider.GetImage(); - var testOutputDetails = string.Concat("lossy", "_q", quality); + string testOutputDetails = string.Concat("lossy", "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } @@ -91,10 +92,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; using Image image = provider.GetImage(); - var testOutputDetails = string.Concat("lossy", "_m", method); + string testOutputDetails = string.Concat("lossy", "_m", method); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } + [Fact] + public void Encode_Lossless_OneByOnePixel_Works() + { + // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. + using var image = new Image(1, 1); + var encoder = new WebpEncoder() { Lossy = false }; + using (var memStream = new MemoryStream()) + { + image.SaveAsWebp(memStream, encoder); + } + } + private static ImageComparer GetComparer(int quality) { float tolerance = 0.01f; // ~1.0% diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs index a82ea7017..2456246b6 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs @@ -98,8 +98,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata image.Metadata.SyncProfiles(); - Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 2bcee62bd..4b374b21f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,12 +5,12 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 4ad32a379..c836bda35 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -5,9 +5,9 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; From 1eb7307d82329b208b5f1edff6d13604c6f1d8ec Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 28 May 2021 04:53:35 +0200 Subject: [PATCH 278/359] Add webp EXIF tests --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 6 +-- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 4 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 10 ++-- .../Formats/WebP/Lossless/LosslessUtils.cs | 2 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 20 ++++---- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 2 + .../Formats/WebP/Lossy/Vp8Encoder.cs | 43 ++++++++--------- .../Formats/WebP/WebpDecoderCore.cs | 6 +-- .../Codecs/DecodeTiff.cs | 15 +----- .../Codecs/DecodeWebp.cs | 2 + .../Codecs/EncodeWebp.cs | 2 + .../Formats/WebP/PredictorEncoderTests.cs | 2 +- .../Profiles/Exif/ExifProfileTests.cs | 46 +++++++++++++++++-- 13 files changed, 96 insertions(+), 64 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 106a3e48e..f717bfe46 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter this.nbBits -= 8; if ((bits & 0xff) != 0xff) { - var pos = this.pos; + uint pos = this.pos; this.BitWriterResize(this.run + 1); if ((bits & 0x100) != 0) @@ -509,7 +509,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter private void WriteFilterHeader(Vp8BitWriter bitWriter) { Vp8FilterHeader hdr = this.enc.FilterHeader; - var useLfDelta = hdr.I4x4LfDelta != 0; + bool useLfDelta = hdr.I4x4LfDelta != 0; bitWriter.PutBitUniform(hdr.Simple ? 1 : 0); bitWriter.PutBits((uint)hdr.FilterLevel, 6); bitWriter.PutBits((uint)hdr.Sharpness, 3); @@ -645,7 +645,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter uint profile = 0; int width = this.enc.Width; int height = this.enc.Height; - var vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize]; + byte[] vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize]; // Paragraph 9.1. uint bits = 0 // keyframe (1b) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 46fb14d38..e3e1a9ddc 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter public Vp8LBitWriter Clone() { - var clonedBuffer = new byte[this.Buffer.Length]; + byte[] clonedBuffer = new byte[this.Buffer.Length]; System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur); return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter // If needed, make some room by flushing some bits out. if (this.cur + WriterBytes > this.end) { - var extraSize = this.end - this.cur + MinExtraSize; + int extraSize = this.end - this.cur + MinExtraSize; this.BitWriterResize(extraSize); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index ed4bfe908..e872eeb63 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void BackwardReferencesTraceBackwards(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refsSrc, Vp8LBackwardRefs refsDst) { int distArraySize = xSize * ySize; - var distArray = new ushort[distArraySize]; + ushort[] distArray = new ushort[distArraySize]; BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); int chosenPathSize = TraceBackwards(distArray, distArraySize); @@ -242,7 +242,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; - var literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); + int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); var costModel = new CostModel(literalArraySize); int offsetPrev = -1; int lenPrev = -1; @@ -511,11 +511,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void BackwardReferencesLz77Box(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int pixelCount = xSize * ySize; - var windowOffsets = new int[WindowOffsetsSizeMax]; - var windowOffsetsNew = new int[WindowOffsetsSizeMax]; + int[] windowOffsets = new int[WindowOffsetsSizeMax]; + int[] windowOffsetsNew = new int[WindowOffsetsSizeMax]; int windowOffsetsSize = 0; int windowOffsetsNewSize = 0; - var counts = new short[xSize * ySize]; + short[] counts = new short[xSize * ySize]; int bestOffsetPrev = -1; int bestLengthPrev = -1; diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index c5ba2c46d..e14a85ed9 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Returns the exact index where array1 and array2 are different. For an index /// inferior or equal to bestLenMatch, the return value just has to be strictly - /// inferior to best_lenMatch. The current behavior is to return 0 if this index + /// inferior to bestLenMatch match. The current behavior is to return 0 if this index /// is bestLenMatch, and the index itself otherwise. /// If no two elements are the same, it returns maxLimit. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 687dc76ff..75c182e4d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -94,8 +94,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxTileSize = 1 << bits; int tileXSize = LosslessUtils.SubSampleSize(width, bits); int tileYSize = LosslessUtils.SubSampleSize(height, bits); - var accumulatedRedHisto = new int[256]; - var accumulatedBlueHisto = new int[256]; + int[] accumulatedRedHisto = new int[256]; + int[] accumulatedBlueHisto = new int[256]; var prevX = default(Vp8LMultipliers); var prevY = default(Vp8LMultipliers); for (int tileY = 0; tileY < tileYSize; tileY++) @@ -204,9 +204,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span maxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); float bestDiff = MaxDiffCost; int bestMode = 0; - var residuals = new uint[1 << WebpConstants.MaxTransformBits]; - var histoArgb = new int[4][]; - var bestHisto = new int[4][]; + uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits]; + int[][] histoArgb = new int[4][]; + int[][] bestHisto = new int[4][]; for (int i = 0; i < 4; i++) { histoArgb[i] = new int[256]; @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - var curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); + float curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); // Favor keeping the areas locally similar. if (mode == leftMode) @@ -448,7 +448,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return LosslessUtils.SubPixels(value, predict); } - var quantization = maxQuantization; + int quantization = maxQuantization; while (quantization >= maxDiff) { quantization >>= 1; @@ -464,7 +464,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); } - var g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); + byte g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); if (usedSubtractGreen) { @@ -478,8 +478,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); } - var r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); - var b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); + byte r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); + byte b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index f3c4ad1ca..3b6ad45eb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -172,6 +172,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { + image.Metadata.SyncProfiles(); + // Write the image size. int width = image.Width; int height = image.Height; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 40c0cef3a..c22cd92a5 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -98,10 +98,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy : (method >= 3) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; - var pixelCount = width * height; + int pixelCount = width * height; this.Mbw = (width + 15) >> 4; this.Mbh = (height + 15) >> 4; - var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); + int uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); @@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int uvStride = (yStride + 1) >> 1; var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); - var alphas = new int[WebpConstants.MaxAlpha + 1]; + int[] alphas = new int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); int totalMb = this.Mbw * this.Mbw; this.alpha /= totalMb; @@ -321,6 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.AdjustFilterStrength(); // Write bytes from the bitwriter buffer to the stream. + image.Metadata.SyncProfiles(); this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height); } @@ -367,7 +368,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy while (numPassLeft-- > 0) { bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); - var sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); + long sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); if (sizeP0 == 0) { return; @@ -517,25 +518,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void AssignSegments(int[] alphas) { int nb = (this.SegmentHeader.NumSegments < NumMbSegments) ? this.SegmentHeader.NumSegments : NumMbSegments; - var centers = new int[NumMbSegments]; + int[] centers = new int[NumMbSegments]; int weightedAverage = 0; - var map = new int[WebpConstants.MaxAlpha + 1]; + int[] map = new int[WebpConstants.MaxAlpha + 1]; int n, k; - var accum = new int[NumMbSegments]; - var distAccum = new int[NumMbSegments]; + int[] accum = new int[NumMbSegments]; + int[] distAccum = new int[NumMbSegments]; // Bracket the input. for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) { } - var minA = n; + int minA = n; for (n = WebpConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } - var maxA = n; - var rangeA = maxA - minA; + int maxA = n; + int rangeA = maxA - minA; // Spread initial centers evenly. for (k = 0, n = 1; k < nb; ++k, n += 2) @@ -573,9 +574,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // All point are classified. Move the centroids to the center of their respective cloud. - var displaced = 0; + int displaced = 0; weightedAverage = 0; - var totalWeight = 0; + int totalWeight = 0; for (n = 0; n < nb; ++n) { if (accum[n] != 0) @@ -694,7 +695,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetupFilterStrength() { - var filterSharpness = 0; // TODO: filterSharpness is hardcoded + int filterSharpness = 0; // TODO: filterSharpness is hardcoded var filterType = 1; // TODO: filterType is hardcoded // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. @@ -720,7 +721,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetSegmentProbas() { - var p = new int[NumMbSegments]; + int[] p = new int[NumMbSegments]; int n; for (n = 0; n < this.Mbw * this.Mbh; ++n) @@ -1078,7 +1079,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // i16x16 residual.Init(0, 1, this.Proba); residual.SetCoeffs(rd.YDcLevels); - var res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); + int res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); it.TopNz[8] = res; it.LeftNz[8] = res; residual.Init(1, 0, this.Proba); @@ -1128,8 +1129,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; - var dcTmp = new short[16]; - var tmp = new short[16 * 16]; + short[] dcTmp = new short[16]; + short[] tmp = new short[16 * 16]; Span tmpSpan = tmp.AsSpan(); for (n = 0; n < 16; n += 2) @@ -1161,9 +1162,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - var tmp = new short[16]; + short[] tmp = new short[16]; Vp8Encoding.FTransform(src, reference, tmp); - var nz = QuantEnc.QuantizeBlock(tmp, levels, dqm.Y1); + int nz = QuantEnc.QuantizeBlock(tmp, levels, dqm.Y1); Vp8Encoding.ITransform(reference, tmp, yuvOut, false); return nz; @@ -1175,7 +1176,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); int nz = 0; int n; - var tmp = new short[8 * 16]; + short[] tmp = new short[8 * 16]; for (n = 0; n < 8; n += 2) { diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index caa64ee61..50d3b48a8 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -84,11 +84,11 @@ namespace SixLabors.ImageSharp.Formats.Webp this.Metadata = new ImageMetadata(); this.currentStream = stream; - var fileSize = this.ReadImageHeader(); + uint fileSize = this.ReadImageHeader(); using (this.webImageInfo = this.ReadVp8Info()) { - if (this.webImageInfo.Features != null && this.webImageInfo.Features.Animation) + if (this.webImageInfo.Features is { Animation: true }) { WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); } @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Formats.Webp // The first 28 bits of the bitstream specify the width and height of the image. uint width = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; - if (width == 1 || height == 1) + if (width == 0 || height == 0) { WebpThrowHelper.ThrowImageFormatException("invalid width or height read"); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs index e77bb8b3e..88b6ec26d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs @@ -8,7 +8,6 @@ using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -26,8 +25,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private byte[] data; - private Configuration configuration; - #if BIG_TESTS private static readonly int BufferSize = 1024 * 68; @@ -61,16 +58,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public string TestImage { get; set; } #endif - [GlobalSetup] - public void Config() - { - if (this.configuration == null) - { - this.configuration = new Configuration(); - this.configuration.StreamProcessingBufferSize = BufferSize; - } - } - [IterationSetup] public void ReadImages() { @@ -95,7 +82,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public Size TiffCore() { using (var ms = new MemoryStream(this.data)) - using (var image = Image.Load(this.configuration, ms)) + using (var image = Image.Load(ms)) { return image.Size(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index b25da186b..17cc1865f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -11,6 +11,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { + [MarkdownExporter] + [HtmlExporter] [Config(typeof(Config.ShortMultiFramework))] public class DecodeWebp { diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index b55e630c9..49e1678a2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -10,6 +10,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { + [MarkdownExporter] + [HtmlExporter] [Config(typeof(Config.ShortMultiFramework))] public class EncodeWebp { diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 421015d1f..b5a5df4e8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -13,7 +13,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP { - [Trait("Format", "Webp")] + [Trait("Format", "WebpLossless")] public class PredictorEncoderTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 1f23838ab..b14938ca8 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -27,7 +28,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif /// /// Writes a png file. /// - Png + Png, + + /// + /// Writes a lossless webp file. + /// + WebpLossless, + + /// + /// Writes a lossy webp file. + /// + WebpLossy } private static readonly Dictionary TestProfileValues = new Dictionary @@ -44,6 +55,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] public void Constructor(TestImageWriteFormat imageFormat) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); @@ -92,6 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] public void WriteFraction(TestImageWriteFormat imageFormat) { using (var memStream = new MemoryStream()) @@ -135,6 +150,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] public void ReadWriteInfinity(TestImageWriteFormat imageFormat) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); @@ -170,6 +187,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif https://exiftool.org/TagNames/EXIF.html */ [InlineData(TestImageWriteFormat.Jpeg, 16)] [InlineData(TestImageWriteFormat.Png, 16)] + [InlineData(TestImageWriteFormat.WebpLossless, 16)] public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); @@ -241,6 +259,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat) { // Arrange @@ -327,7 +347,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Fact] public void ReadWriteLargeProfileJpg() { - ExifTag[] tags = new[] { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; + ExifTag[] tags = { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; foreach (ExifTag tag in tags) { // Arrange @@ -344,7 +364,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif image.Metadata.ExifProfile = expectedProfile; // Act - using Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); + Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); // Assert ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; @@ -366,7 +386,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Fact] public void ExifTypeUndefined() { - // This image contains an 802 byte EXIF profile + // This image contains an 802 byte EXIF profile. // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) using Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); Assert.NotNull(image); @@ -409,6 +429,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) { // Arrange @@ -483,6 +505,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif return WriteAndReadJpeg(image); case TestImageWriteFormat.Png: return WriteAndReadPng(image); + case TestImageWriteFormat.WebpLossless: + return WriteAndReadWebp(image, lossy: false); + case TestImageWriteFormat.WebpLossy: + return WriteAndReadWebp(image, lossy: true); default: throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); } @@ -512,6 +538,18 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif } } + private static Image WriteAndReadWebp(Image image, bool lossy) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsWebp(memStream, new WebpEncoder() { Lossy = lossy }); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream, new WebpDecoder()); + } + } + private static void TestProfile(ExifProfile profile) { Assert.NotNull(profile); From a534513ee93a524f36bacbb597fe1b96c49e493a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 16 Jun 2021 18:32:22 +0200 Subject: [PATCH 279/359] Fix issue with AccumulateRgba in the last row --- .../Formats/WebP/Lossy/Vp8Encoder.cs | 15 ++++++--- .../Formats/WebP/Lossy/YuvConversion.cs | 32 +++++++++++++------ .../Formats/WebP/PredictorEncoderTests.cs | 2 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index c22cd92a5..20b0b1d1d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -791,7 +791,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.DqUvDc, 0, 117)]; m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)]; - var qi4 = m.Y1.Expand(0); + int qi4 = m.Y1.Expand(0); m.Y2.Expand(1); // qi16 m.Uv.Expand(2); // quv @@ -808,7 +808,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy do { it.Import(y, u, v, yStride, uvStride, width, height, true); - int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha); + int bestAlpha = this.MbAnalyze(it, alphas, out int bestUvAlpha); // Accumulate for later complexity analysis. alpha += bestAlpha; @@ -1211,19 +1211,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy where TPixel : unmanaged, IPixel { int uvWidth = (image.Width + 1) >> 1; - bool hasAlpha = YuvConversion.CheckNonOpaque(image); // Temporary storage for accumulated R/G/B values during conversion to U/V. using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); Span tmpRgbSpan = tmpRgb.GetSpan(); int uvRowIndex = 0; int rowIndex; + bool rowsHaveAlpha = false; for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) { + rowsHaveAlpha = YuvConversion.CheckNonOpaque(image, rowIndex, rowIndex + 1); + // Downsample U/V planes, two rows at a time. Span rowSpan = image.GetPixelRowSpan(rowIndex); Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); - if (!hasAlpha) + if (!rowsHaveAlpha) { YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); } @@ -1243,7 +1245,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((image.Height & 1) != 0) { Span rowSpan = image.GetPixelRowSpan(rowIndex); - if (!hasAlpha) + if (!rowsHaveAlpha) { YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); } @@ -1269,6 +1271,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); + [MethodImpl(InliningOptions.ShortMethod)] private static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); [MethodImpl(InliningOptions.ShortMethod)] @@ -1319,6 +1322,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// is around q=75. Internally, our "good" middle is around c=50. So we /// map accordingly using linear piece-wise function /// + [MethodImpl(InliningOptions.ShortMethod)] private static double QualityToCompression(double c) { double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; @@ -1334,6 +1338,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return v; } + [MethodImpl(InliningOptions.ShortMethod)] private int FilterStrengthFromDelta(int sharpness, int delta) { int pos = (delta < WebpConstants.MaxDelzaSize) ? delta : WebpConstants.MaxDelzaSize - 1; diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index afcd13ff9..7676555a6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -21,12 +21,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// The pixel type of the image, /// The image to check. + /// The row to start with. + /// The row to end with. /// Returns true if alpha has non-0xff values. - public static bool CheckNonOpaque(Image image) + public static bool CheckNonOpaque(Image image, int rowIdxStart, int rowIdxEnd) where TPixel : unmanaged, IPixel { Rgba32 rgba = default; - for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) + for (int rowIndex = rowIdxStart; rowIndex <= rowIdxEnd; rowIndex++) { Span rowSpan = image.GetPixelRowSpan(rowIndex); for (int x = 0; x < image.Width; x++) @@ -43,6 +45,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return false; } + /// + /// Converts a rgba pixel row to Y. + /// + /// The type of the pixel. + /// The row span to convert. + /// The destination span for y. + /// The width. public static void ConvertRgbaToY(Span rowSpan, Span y, int width) where TPixel : unmanaged, IPixel { @@ -55,6 +64,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + /// + /// Converts a rgb row of pixels to UV. + /// + /// The RGB pixel row. + /// The destination span for u. + /// The destination span for v. + /// The width. public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) { int rgbIdx = 0; @@ -184,9 +200,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } else { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba0.R, rgba1.R, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba0.G, rgba1.G, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba0.B, rgba1.B, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); } dst[dstIdx] = (ushort)r; @@ -196,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + [MethodImpl(InliningOptions.ShortMethod)] private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) { uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); @@ -212,10 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static uint GammaToLinear(byte v) - { - return WebpLookupTables.GammaToLinearTab[v]; - } + private static uint GammaToLinear(byte v) => WebpLookupTables.GammaToLinearTab[v]; [MethodImpl(InliningOptions.ShortMethod)] private static int Interpolate(int v) diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index b5a5df4e8..421015d1f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -13,7 +13,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.WebP { - [Trait("Format", "WebpLossless")] + [Trait("Format", "Webp")] public class PredictorEncoderTests { [Fact] From 92f9bbaac6d2ffb3fa85ccf250874a08d01804c6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 16 Jun 2021 20:41:27 +0200 Subject: [PATCH 280/359] Bulk convert rows to rgba --- .../Formats/WebP/Lossy/Vp8Encoder.cs | 47 ++++++---- .../Formats/WebP/Lossy/YuvConversion.cs | 88 ++++++------------- .../Formats/WebP/WebpEncoderCore.cs | 2 +- 3 files changed, 60 insertions(+), 77 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 20b0b1d1d..0b3b209b1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private readonly MemoryAllocator memoryAllocator; + /// + /// The global configuration. + /// + private Configuration configuration; + /// /// The quality, that will be used to encode the image. /// @@ -80,16 +85,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Initializes a new instance of the class. /// /// The memory allocator. + /// The global configuration. /// The width of the input image. /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). /// Number of entropy-analysis passes (in [1..10]). - public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method, int entropyPasses) + public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses) { + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; this.Width = width; this.Height = height; - this.memoryAllocator = memoryAllocator; this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); @@ -1210,51 +1217,59 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void ConvertRgbToYuv(Image image) where TPixel : unmanaged, IPixel { - int uvWidth = (image.Width + 1) >> 1; + int width = image.Width; + int height = image.Height; + int uvWidth = (width + 1) >> 1; // Temporary storage for accumulated R/G/B values during conversion to U/V. using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); + using IMemoryOwner rgbaRow0Buffer = this.memoryAllocator.Allocate(width); + using IMemoryOwner rgbaRow1Buffer = this.memoryAllocator.Allocate(width); Span tmpRgbSpan = tmpRgb.GetSpan(); + Span rgbaRow0 = rgbaRow0Buffer.GetSpan(); + Span rgbaRow1 = rgbaRow1Buffer.GetSpan(); int uvRowIndex = 0; int rowIndex; bool rowsHaveAlpha = false; - for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) + for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - rowsHaveAlpha = YuvConversion.CheckNonOpaque(image, rowIndex, rowIndex + 1); - - // Downsample U/V planes, two rows at a time. Span rowSpan = image.GetPixelRowSpan(rowIndex); Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + PixelOperations.Instance.ToRgba32(this.configuration, rowSpan, rgbaRow0); + PixelOperations.Instance.ToRgba32(this.configuration, nextRowSpan, rgbaRow1); + + rowsHaveAlpha = YuvConversion.CheckNonOpaque(rgbaRow0) && YuvConversion.CheckNonOpaque(rgbaRow1); + + // Downsample U/V planes, two rows at a time. if (!rowsHaveAlpha) { - YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); } else { - YuvConversion.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); } YuvConversion.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; - YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - YuvConversion.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width); + YuvConversion.ConvertRgbaToY(rgbaRow1, this.Y.Slice((rowIndex + 1) * width), width); } // Extra last row. - if ((image.Height & 1) != 0) + if ((height & 1) != 0) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); if (!rowsHaveAlpha) { - YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } else { - YuvConversion.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } - YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 7676555a6..fbc2996fc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -17,28 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int YuvHalf = 1 << (YuvFix - 1); /// - /// Checks if the image is not opaque. + /// Checks if the pixel row is not opaque. /// - /// The pixel type of the image, - /// The image to check. - /// The row to start with. - /// The row to end with. + /// The row to check. /// Returns true if alpha has non-0xff values. - public static bool CheckNonOpaque(Image image, int rowIdxStart, int rowIdxEnd) - where TPixel : unmanaged, IPixel + [MethodImpl(InliningOptions.ShortMethod)] + public static bool CheckNonOpaque(Span row) { - Rgba32 rgba = default; - for (int rowIndex = rowIdxStart; rowIndex <= rowIdxEnd; rowIndex++) + for (int x = 0; x < row.Length; x++) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - for (int x = 0; x < image.Width; x++) + if (row[x].A != 255) { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - if (rgba.A != 255) - { - return true; - } + return true; } } @@ -48,19 +38,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Converts a rgba pixel row to Y. /// - /// The type of the pixel. /// The row span to convert. /// The destination span for y. /// The width. - public static void ConvertRgbaToY(Span rowSpan, Span y, int width) - where TPixel : unmanaged, IPixel + [MethodImpl(InliningOptions.ShortMethod)] + public static void ConvertRgbaToY(Span rowSpan, Span y, int width) { - Rgba32 rgba = default; for (int x = 0; x < width; x++) { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); + y[x] = (byte)RgbToY(rowSpan[x].R, rowSpan[x].G, rowSpan[x].B, YuvHalf); } } @@ -82,25 +68,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel + public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; + Rgba32 rgba0; + Rgba32 rgba1; int i, j; int dstIdx = 0; for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); + rgba0 = rowSpan[j]; + rgba1 = rowSpan[j + 1]; + Rgba32 rgba2 = nextRowSpan[j]; + Rgba32 rgba3 = nextRowSpan[j + 1]; dst[dstIdx] = (ushort)LinearToGamma( GammaToLinear(rgba0.R) + @@ -121,10 +100,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((width & 1) != 0) { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); + rgba0 = rowSpan[j]; + rgba1 = nextRowSpan[j]; dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); @@ -132,25 +109,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel + public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; + Rgba32 rgba0; + Rgba32 rgba1; int i, j; int dstIdx = 0; for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); + rgba0 = rowSpan[j]; + rgba1 = rowSpan[j + 1]; + Rgba32 rgba2 = nextRowSpan[j]; + Rgba32 rgba3 = nextRowSpan[j + 1]; uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); int r, g, b; if (a == 4 * 0xff || a == 0) @@ -186,10 +156,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((width & 1) != 0) { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); + rgba0 = rowSpan[j]; + rgba1 = nextRowSpan[j]; uint a = (uint)(2u * (rgba0.A + rgba1.A)); int r, g, b; if (a == 4 * 0xff || a == 0) diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index ecc940782..985300a56 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.lossy) { - var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method, this.entropyPasses); + var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses); enc.Encode(image, stream); } else From 316d4cc5549b4fc53df1e9f1ef616178a4e9c751 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Jun 2021 10:54:50 +0200 Subject: [PATCH 281/359] Use bulk conversion to bgra --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 71 +++++++++++-------- .../Formats/WebP/WebpEncoderCore.cs | 2 +- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 3b6ad45eb..1b0022748 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -19,6 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// internal class Vp8LEncoder : IDisposable { + /// + /// The to use for buffer allocations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + /// /// Maximum number of reference blocks the image will be segmented into. /// @@ -29,11 +39,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private const int MinBlockSize = 256; - /// - /// The to use for buffer allocations. - /// - private readonly MemoryAllocator memoryAllocator; - /// /// A bit writer for writing lossless webp streams. /// @@ -59,15 +64,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Initializes a new instance of the class. /// /// The memory allocator. + /// The global configuration. /// The width of the input image. /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). - public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method) + public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method) { int pixelCount = width * height; int initialSize = pixelCount * 2; + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); this.bitWriter = new Vp8LBitWriter(initialSize); @@ -75,7 +83,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; this.HashChain = new Vp8LHashChain(pixelCount); - this.memoryAllocator = memoryAllocator; // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; @@ -230,13 +237,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Convert image pixels to bgra array. Span bgra = this.Bgra.GetSpan(); + using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); + Span bgraRow = bgraRowBuffer.GetSpan(); int idx = 0; for (int y = 0; y < height; y++) { Span rowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) + PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); + for (int x = 0; x < width; x++) { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + bgra[idx++] = bgraRow[x].PackedValue; } } @@ -933,18 +943,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + using IMemoryOwner bgraBuffer = this.memoryAllocator.Allocate(width); + Span currentRow = bgraBuffer.GetSpan(); Span histo = histoBuffer.Memory.Span; - Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. - Span prevRow = null; + TPixel firstPixel = image.GetPixelRowSpan(0)[0]; + Bgra32 bgra = default; + Rgba32 rgba = default; + firstPixel.ToRgba32(ref rgba); + bgra.FromRgba32(rgba); + Bgra32 pixPrev = bgra; // Skip the first pixel. + Span prevRow = null; for (int y = 0; y < height; y++) { - Span currentRow = image.GetPixelRowSpan(y); + Span pixelRow = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToBgra32(this.configuration, pixelRow, currentRow); for (int x = 0; x < width; x++) { - Bgra32 pix = ToBgra32(currentRow[x]); + Bgra32 pix = currentRow[x]; uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); pixPrev = pix; - if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) + if ((pixDiff == 0) || (prevRow != null && pix == prevRow[x])) { continue; } @@ -1110,13 +1128,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private int GetColorPalette(Image image, Span palette) where TPixel : unmanaged, IPixel { - var colors = new HashSet(); + int width = image.Width; + var colors = new HashSet(); + using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); + Span bgraRow = bgraRowBuffer.GetSpan(); for (int y = 0; y < image.Height; y++) { Span rowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) + PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); + for (int x = 0; x < width; x++) { - colors.Add(rowSpan[x]); + colors.Add(bgraRow[x]); if (colors.Count > WebpConstants.MaxPaletteSize) { // Exact count is not needed, because a palette will not be used then anyway. @@ -1126,12 +1148,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Fill the colors into the palette. - using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); + using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); int idx = 0; while (colorEnumerator.MoveNext()) { - Bgra32 bgra = ToBgra32(colorEnumerator.Current); - palette[idx++] = bgra.PackedValue; + palette[idx++] = colorEnumerator.Current.PackedValue; } return colors.Count; @@ -1591,16 +1612,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return res; } - [MethodImpl(InliningOptions.ShortMethod)] - private static Bgra32 ToBgra32(TPixel color) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - color.ToRgba32(ref rgba); - var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); - return bgra; - } - [MethodImpl(InliningOptions.ShortMethod)] private static void AddSingle(uint p, Span a, Span r, Span g, Span b) { diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 985300a56..4c405b262 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method); + var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method); enc.Encode(image, stream); } } From e7858532530d094198a43fcf3f6c2a6cb973abbd Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Jun 2021 11:32:50 +0200 Subject: [PATCH 282/359] Avoid converting to bgra multiple times --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 81 ++++++++----------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 1b0022748..a486e9557 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Analyze image (entropy, numPalettes etc). - CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero); + CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); int bestSize = 0; Vp8LBitWriter bitWriterInit = this.bitWriter; @@ -340,14 +340,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Analyzes the image and decides which transforms should be used. /// - private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) - where TPixel : unmanaged, IPixel + /// The image as packed bgra values. + /// The image width. + /// The image height. + /// Indicates if red and blue are always zero. + private CrunchConfig[] EncoderAnalyze(Span bgra, int width, int height, out bool redAndBlueAlwaysZero) { - int width = image.Width; - int height = image.Height; - // Check if we only deal with a small number of colors and should use a palette. - bool usePalette = this.AnalyzeAndCreatePalette(image); + bool usePalette = this.AnalyzeAndCreatePalette(bgra, width, height); // Empirical bit sizes. this.HistoBits = GetHistoBits(this.method, usePalette, width, height); @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Try out multiple LZ77 on images with few colors. int nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); + EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); bool doNotCache = false; var crunchConfigs = new List(); @@ -921,19 +921,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. /// - /// The pixel type of the image. - /// The image to analyze. + /// The image to analyze as a bgra span. + /// The image width. + /// The image height. /// Indicates whether a palette should be used. /// The palette size. /// The transformation bits. /// Indicates if red and blue are always zero. /// The entropy mode to use. - private EntropyIx AnalyzeEntropy(Image image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) - where TPixel : unmanaged, IPixel + private EntropyIx AnalyzeEntropy(Span bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) { - int width = image.Width; - int height = image.Height; - if (usePalette && paletteSize <= 16) { // In the case of small palettes, we pack 2, 4 or 8 pixels together. In @@ -943,24 +940,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); - using IMemoryOwner bgraBuffer = this.memoryAllocator.Allocate(width); - Span currentRow = bgraBuffer.GetSpan(); Span histo = histoBuffer.Memory.Span; - TPixel firstPixel = image.GetPixelRowSpan(0)[0]; - Bgra32 bgra = default; - Rgba32 rgba = default; - firstPixel.ToRgba32(ref rgba); - bgra.FromRgba32(rgba); - Bgra32 pixPrev = bgra; // Skip the first pixel. - Span prevRow = null; + uint pixPrev = bgra[0]; // Skip the first pixel. + Span prevRow = null; for (int y = 0; y < height; y++) { - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToBgra32(this.configuration, pixelRow, currentRow); + Span currentRow = bgra.Slice(y * width, width); for (int x = 0; x < width; x++) { - Bgra32 pix = currentRow[x]; - uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); + uint pix = currentRow[x]; + uint pixDiff = LosslessUtils.SubPixels(pix, pixPrev); pixPrev = pix; if ((pixDiff == 0) || (prevRow != null && pix == prevRow[x])) { @@ -968,7 +957,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } AddSingle( - pix.PackedValue, + pix, histo.Slice((int)HistoIx.HistoAlpha * 256), histo.Slice((int)HistoIx.HistoRed * 256), histo.Slice((int)HistoIx.HistoGreen * 256), @@ -980,7 +969,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless histo.Slice((int)HistoIx.HistoGreenPred * 256), histo.Slice((int)HistoIx.HistoBluePred * 256)); AddSingleSubGreen( - pix.PackedValue, + pix, histo.Slice((int)HistoIx.HistoRedSubGreen * 256), histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); AddSingleSubGreen( @@ -989,7 +978,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless histo.Slice((int)HistoIx.HistoBluePredSubGreen * 256)); // Approximate the palette by the entropy of the multiplicative hash. - uint hash = HashPix(pix.PackedValue); + uint hash = HashPix(pix); histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; } @@ -1094,12 +1083,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// If number of colors in the image is less than or equal to MaxPaletteSize, /// creates a palette and returns true, else returns false. /// + /// The image as packed bgra values. + /// The image width. + /// The image height. /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(Image image) - where TPixel : unmanaged, IPixel + private bool AnalyzeAndCreatePalette(Span bgra, int width, int height) { Span palette = this.Palette.Memory.Span; - this.PaletteSize = this.GetColorPalette(image, palette); + this.PaletteSize = this.GetColorPalette(bgra, width, height, palette); if (this.PaletteSize > WebpConstants.MaxPaletteSize) { this.PaletteSize = 0; @@ -1121,21 +1112,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Gets the color palette. /// - /// The pixel type of the image. - /// The image to get the palette from. + /// The image to get the palette from as packed bgra values. + /// The image width. + /// The image height. /// The span to store the palette into. /// The number of palette entries. - private int GetColorPalette(Image image, Span palette) - where TPixel : unmanaged, IPixel + private int GetColorPalette(Span bgra, int width, int height, Span palette) { - int width = image.Width; - var colors = new HashSet(); - using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); - Span bgraRow = bgraRowBuffer.GetSpan(); - for (int y = 0; y < image.Height; y++) + var colors = new HashSet(); + for (int y = 0; y < height; y++) { - Span rowSpan = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); + Span bgraRow = bgra.Slice(y * width, width); for (int x = 0; x < width; x++) { colors.Add(bgraRow[x]); @@ -1148,11 +1135,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Fill the colors into the palette. - using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); + using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); int idx = 0; while (colorEnumerator.MoveNext()) { - palette[idx++] = colorEnumerator.Current.PackedValue; + palette[idx++] = colorEnumerator.Current; } return colors.Count; From 810bebf972929568492bfdbbe1ab3c40ac7a5fb1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Jun 2021 11:39:33 +0200 Subject: [PATCH 283/359] Add CoreRuntime.Core50 --- tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs | 4 ++-- tests/ImageSharp.Benchmarks/Config.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 17cc1865f..a05da5edf 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); } - [Benchmark(Description = "Magick Lossy WebP")] + [Benchmark(Description = "Magick Lossy Webp")] public int WebpLossyMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs return image.Height; } - [Benchmark(Description = "Magick Lossless WebP")] + [Benchmark(Description = "Magick Lossless Webp")] public int WebpLosslessMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 63064eec5..c905718e2 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -34,7 +34,8 @@ namespace SixLabors.ImageSharp.Benchmarks public MultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472), Job.Default.WithRuntime(CoreRuntime.Core21), - Job.Default.WithRuntime(CoreRuntime.Core31)); + Job.Default.WithRuntime(CoreRuntime.Core31), + Job.Default.WithRuntime(CoreRuntime.Core50)); } public class ShortMultiFramework : Config @@ -42,7 +43,8 @@ namespace SixLabors.ImageSharp.Benchmarks public ShortMultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.WithRuntime(CoreRuntime.Core50).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); } public class ShortCore31 : Config From f4180d16c487167cb2a207906704ba127d360f47 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Jun 2021 12:01:47 +0200 Subject: [PATCH 284/359] Update benchmark results --- .../Codecs/DecodeWebp.cs | 34 +++++++++++-------- .../Codecs/EncodeWebp.cs | 31 ++++++++++------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index a05da5edf..fedc22eb1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs return image.Height; } - /* Results 26.12.2020 + /* Results 17.06.2021 * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=3.1.202 @@ -84,20 +84,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs Job-WMTYOZ : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT IterationCount=3 LaunchCount=1 WarmupCount=3 - | Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |-------------- |--------------------- |--------------------- |-----------:|---------:|--------:|----------:|----------:|------:|------------:| - | 'Magick Lossy WebP' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 107.1 ms | 47.56 ms | 2.61 ms | - | - | - | 32.05 KB | - | 'ImageSharp Lossy Webp' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,108.4 ms | 25.90 ms | 1.42 ms | - | - | - | 2779.53 KB | - | 'Magick Lossless WebP' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 145.8 ms | 8.97 ms | 0.49 ms | - | - | - | 18.05 KB | - | 'ImageSharp Lossless Webp' | Job-TNALDZ | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,662.9 ms | 9.34 ms | 0.51 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | - | 'Magick Lossy WebP' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 106.2 ms | 14.80 ms | 0.81 ms | - | - | - | 16 KB | - | 'ImageSharp Lossy Webp' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 743.1 ms | 7.53 ms | 0.41 ms | - | - | - | 2767.8 KB | - | 'Magick Lossless WebP' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 146.7 ms | 25.23 ms | 1.38 ms | - | - | - | 16.76 KB | - | 'ImageSharp Lossless Webp' | Job-ATRTFL | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 529.2 ms | 64.09 ms | 3.51 ms | 4000.0000 | 1000.0000 | - | 22859.97 KB | - | 'Magick Lossy WebP' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 106.0 ms | 9.51 ms | 0.52 ms | - | - | - | 15.71 KB | - | 'ImageSharp Lossy Webp' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 765.8 ms | 34.82 ms | 1.91 ms | - | - | - | 2767.79 KB | - | 'Magick Lossless WebP' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 146.0 ms | 25.51 ms | 1.40 ms | - | - | - | 16.02 KB | - | 'ImageSharp Lossless Webp' | Job-TMFWEM | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 478.3 ms | 89.70 ms | 4.92 ms | 4000.0000 | 1000.0000 | - | 22859.61 KB | + | Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |-------------- |---------------------- |------------------------- |-----------:|----------:|---------:|----------:|----------:|------:|------------:| + | 'Magick Lossy Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.8 ms | 6.28 ms | 0.34 ms | - | - | - | 17.65 KB | + | 'ImageSharp Lossy Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 1,145.0 ms | 110.82 ms | 6.07 ms | - | - | - | 2779.53 KB | + | 'Magick Lossless Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 145.9 ms | 8.55 ms | 0.47 ms | - | - | - | 18.05 KB | + | 'ImageSharp Lossless Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 1,694.1 ms | 55.09 ms | 3.02 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | + | 'Magick Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.7 ms | 1.89 ms | 0.10 ms | - | - | - | 15.75 KB | + | 'ImageSharp Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 741.6 ms | 21.45 ms | 1.18 ms | - | - | - | 2767.85 KB | + | 'Magick Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.1 ms | 9.52 ms | 0.52 ms | - | - | - | 16.54 KB | + | 'ImageSharp Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 522.5 ms | 21.15 ms | 1.16 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | + | 'Magick Lossy Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.9 ms | 5.34 ms | 0.29 ms | - | - | - | 15.45 KB | + | 'ImageSharp Lossy Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 748.8 ms | 290.47 ms | 15.92 ms | - | - | - | 2767.84 KB | + | 'Magick Lossless Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.1 ms | 1.14 ms | 0.06 ms | - | - | - | 15.9 KB | + | 'ImageSharp Lossless Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 480.7 ms | 25.25 ms | 1.38 ms | 4000.0000 | 1000.0000 | - | 22859.7 KB | + | 'Magick Lossy Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.7 ms | 4.71 ms | 0.26 ms | - | - | - | 15.48 KB | + | 'ImageSharp Lossy Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 645.7 ms | 61.00 ms | 3.34 ms | - | - | - | 2768.13 KB | + | 'Magick Lossless Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.5 ms | 18.63 ms | 1.02 ms | - | - | - | 15.8 KB | + | 'ImageSharp Lossless Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 306.7 ms | 32.31 ms | 1.77 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | */ } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 49e1678a2..8f3869a52 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs }); } - /* Results 25.12.2020 + /* Results 17.06.2021 * Summary * BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores @@ -88,20 +88,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------------------- |----------- |-------------- |------------- |----------:|-----------:|----------:|------:|--------:|-----------:|----------:|----------:|-------------:| - | 'Magick Webp Lossy' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 23.89 ms | 3.742 ms | 0.205 ms | 0.14 | 0.00 | - | - | - | 68.19 KB | - | 'ImageSharp Webp Lossy' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 72.27 ms | 20.228 ms | 1.109 ms | 0.43 | 0.01 | 6142.8571 | 142.8571 | - | 26360.05 KB | - | 'Magick Webp Lossless' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 167.75 ms | 41.847 ms | 2.294 ms | 1.00 | 0.00 | - | - | - | 520.28 KB | - | 'ImageSharp Webp Lossless' | Job-NTTOHF | .NET 4.7.2 | Png/Bike.png | 388.12 ms | 84.867 ms | 4.652 ms | 2.31 | 0.03 | 34000.0000 | 5000.0000 | 2000.0000 | 163174.2 KB | + | 'Magick Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 23.30 ms | 0.869 ms | 0.048 ms | 0.14 | 0.00 | - | - | - | 68.19 KB | + | 'ImageSharp Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 68.22 ms | 16.454 ms | 0.902 ms | 0.42 | 0.01 | 6125.0000 | 125.0000 | - | 26359.49 KB | + | 'Magick Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 161.96 ms | 9.879 ms | 0.541 ms | 1.00 | 0.00 | - | - | - | 520.28 KB | + | 'ImageSharp Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 370.88 ms | 58.875 ms | 3.227 ms | 2.29 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 163177.15 KB | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 24.00 ms | 7.621 ms | 0.418 ms | 0.14 | 0.00 | - | - | - | 67.67 KB | - | 'ImageSharp Webp Lossy' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 47.77 ms | 6.498 ms | 0.356 ms | 0.29 | 0.00 | 6272.7273 | 272.7273 | 90.9091 | 26284.65 KB | - | 'Magick Webp Lossless' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 166.07 ms | 25.133 ms | 1.378 ms | 1.00 | 0.00 | - | - | - | 519.06 KB | - | 'ImageSharp Webp Lossless' | Job-RXOYDK | .NET Core 2.1 | Png/Bike.png | 356.60 ms | 249.912 ms | 13.699 ms | 2.15 | 0.10 | 34000.0000 | 5000.0000 | 2000.0000 | 162719.59 KB | + | 'Magick Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 23.35 ms | 0.428 ms | 0.023 ms | 0.14 | 0.00 | - | - | - | 67.76 KB | + | 'ImageSharp Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 43.95 ms | 2.850 ms | 0.156 ms | 0.27 | 0.00 | 6250.0000 | 250.0000 | 83.3333 | 26284.72 KB | + | 'Magick Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 161.44 ms | 3.749 ms | 0.206 ms | 1.00 | 0.00 | - | - | - | 519.26 KB | + | 'ImageSharp Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 335.78 ms | 78.666 ms | 4.312 ms | 2.08 | 0.03 | 34000.0000 | 5000.0000 | 2000.0000 | 162727.56 KB | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 23.95 ms | 5.531 ms | 0.303 ms | 0.14 | 0.00 | - | - | - | 67.57 KB | - | 'ImageSharp Webp Lossy' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 44.12 ms | 4.250 ms | 0.233 ms | 0.27 | 0.01 | 6250.0000 | 250.0000 | 83.3333 | 26284.72 KB | - | 'Magick Webp Lossless' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 165.94 ms | 66.670 ms | 3.654 ms | 1.00 | 0.00 | - | - | - | 523.05 KB | - | 'ImageSharp Webp Lossless' | Job-UDPFDM | .NET Core 3.1 | Png/Bike.png | 342.97 ms | 92.856 ms | 5.090 ms | 2.07 | 0.05 | 34000.0000 | 5000.0000 | 2000.0000 | 162725.32 KB | + | 'Magick Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 23.48 ms | 4.325 ms | 0.237 ms | 0.15 | 0.00 | - | - | - | 67.66 KB | + | 'ImageSharp Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 43.29 ms | 16.503 ms | 0.905 ms | 0.27 | 0.01 | 6272.7273 | 272.7273 | 90.9091 | 26284.86 KB | + | 'Magick Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 161.81 ms | 10.693 ms | 0.586 ms | 1.00 | 0.00 | - | - | - | 523.25 KB | + | 'ImageSharp Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 323.97 ms | 235.468 ms | 12.907 ms | 2.00 | 0.08 | 34000.0000 | 5000.0000 | 2000.0000 | 162724.84 KB | + | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 23.36 ms | 0.448 ms | 0.025 ms | 0.14 | 0.00 | - | - | - | 67.66 KB | + | 'ImageSharp Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 40.11 ms | 2.465 ms | 0.135 ms | 0.25 | 0.00 | 6307.6923 | 230.7692 | 76.9231 | 26284.71 KB | + | 'Magick Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 161.55 ms | 6.662 ms | 0.365 ms | 1.00 | 0.00 | - | - | - | 518.84 KB | + | 'ImageSharp Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 298.73 ms | 17.953 ms | 0.984 ms | 1.85 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 162725.13 KB | */ } } From f120728730daa7c87bb738cb79e95437dc9550fb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Jun 2021 10:35:18 +0200 Subject: [PATCH 285/359] Implement PickBestIntra16 --- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- .../Formats/WebP/Lossy/LossyUtils.cs | 156 ++++++------- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 5 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 211 ++++++++++++++++-- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 7 + .../Formats/WebP/Lossy/Vp8Residual.cs | 54 ++++- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 28 +++ src/ImageSharp/Formats/WebP/WebpConstants.cs | 2 + .../Formats/WebP/WebpLookupTables.cs | 127 +++++++++++ 9 files changed, 485 insertions(+), 107 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 3be3808b3..c5db7a568 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { double sumCost = this.BitCost + b.BitCost; costThreshold += sumCost; - if (this.GetCombinedHistogramEntropy(b, costThreshold, costInitial: 0, out var cost)) + if (this.GetCombinedHistogramEntropy(b, costThreshold, costInitial: 0, out double cost)) { this.Add(b, output); output.BitCost = cost; diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 8c57be943..c28ae6cfa 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -25,10 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static void TM16(Span dst, Span yuv, int offset) - { - TrueMotion(dst, yuv, offset, 16); - } + public static void TM16(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 16); public static void VE16(Span dst, Span yuv, int offset) { @@ -82,11 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static void DC16NoTopLeft(Span dst) - { - // DC with no top and left samples. - Put16(0x80, dst); - } + public static void DC16NoTopLeft(Span dst) => + Put16(0x80, dst); // DC with no top and left samples. public static void DC8uv(Span dst, Span yuv, int offset) { @@ -103,11 +97,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static void TM8uv(Span dst, Span yuv, int offset) - { - // TrueMotion - TrueMotion(dst, yuv, offset, 8); - } + public static void TM8uv(Span dst, Span yuv, int offset) => + TrueMotion(dst, yuv, offset, 8); // TrueMotion public static void VE8uv(Span dst, Span yuv, int offset) { @@ -167,11 +158,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static void DC8uvNoTopLeft(Span dst) - { - // DC with nothing. - Put8x8uv(0x80, dst); - } + public static void DC8uvNoTopLeft(Span dst) => + Put8x8uv(0x80, dst); // DC with nothing. public static void DC4(Span dst, Span yuv, int offset) { @@ -192,10 +180,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static void TM4(Span dst, Span yuv, int offset) - { - TrueMotion(dst, yuv, offset, 4); - } + public static void TM4(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 4); public static void VE4(Span dst, Span yuv, int offset) { @@ -484,6 +469,54 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + /// + /// Hadamard transform + /// Returns the weighted sum of the absolute value of transformed coefficients. + /// w[] contains a row-major 4 by 4 symmetric matrix. + /// + public static int TTransform(Span input, Span w) + { + int sum = 0; + int[] tmp = new int[16]; + + // horizontal pass. + for (int i = 0; i < 4; ++i) + { + int a0 = input[0] + input[2]; + int a1 = input[1] + input[3]; + int a2 = input[1] - input[3]; + int a3 = input[0] - input[2]; + tmp[0 + (i * 4)] = a0 + a1; + tmp[1 + (i * 4)] = a3 + a2; + tmp[2 + (i * 4)] = a3 - a2; + tmp[3 + (i * 4)] = a0 - a1; + + input = input.Slice(WebpConstants.Bps); + } + + // vertical pass + for (int i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[8 + i]; + int a1 = tmp[4 + i] + tmp[12 + i]; + int a2 = tmp[4 + i] - tmp[12 + i]; + int a3 = tmp[0 + i] - tmp[8 + i]; + int b0 = a0 + a1; + int b1 = a3 + a2; + int b2 = a3 - a2; + int b3 = a0 - a1; + + sum += w[0] * Math.Abs(b0); + sum += w[4] * Math.Abs(b1); + sum += w[8] * Math.Abs(b2); + sum += w[12] * Math.Abs(b3); + + w = w.Slice(1); + } + + return sum; + } + public static void TransformTwo(Span src, Span dst) { TransformOne(src, dst); @@ -638,15 +671,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); - } + => FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); - } + => FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { @@ -698,11 +727,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static uint LoadUv(byte u, byte v) - { - // We process u and v together stashed into 32bit(16bit each). - return (uint)(u | (v << 16)); - } + public static uint LoadUv(byte u, byte v) => + (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). [MethodImpl(InliningOptions.ShortMethod)] public static void YuvToBgr(int y, int u, int v, Span bgr) @@ -713,52 +739,28 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToB(int y, int u) - { - return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); - } + public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToG(int y, int u, int v) - { - return Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); - } + public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToR(int y, int v) - { - return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); - } + public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg2(byte a, byte b) - { - return (byte)((a + b + 1) >> 1); - } + public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg3(byte a, byte b, byte c) - { - return (byte)((a + (2 * b) + c + 2) >> 2); - } + public static byte Avg3(byte a, byte b, byte c) => (byte)((a + (2 * b) + c + 2) >> 2); [MethodImpl(InliningOptions.ShortMethod)] - public static void Dst(Span dst, int x, int y, byte v) - { - dst[x + (y * WebpConstants.Bps)] = v; - } + public static void Dst(Span dst, int x, int y, byte v) => dst[x + (y * WebpConstants.Bps)] = v; [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip8B(int v) - { - return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); - } + public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); // Cost of coding one event with probability 'proba'. - public static int Vp8BitCost(int bit, byte proba) - { - return bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; - } + public static int Vp8BitCost(int bit, byte proba) => bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; [MethodImpl(InliningOptions.ShortMethod)] private static void Put16(int v, Span dst) @@ -950,15 +952,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int MultHi(int v, int coeff) - { - return (v * coeff) >> 8; - } + private static int MultHi(int v, int coeff) => (v * coeff) >> 8; [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, int x, int y, int v) { - var index = x + (y * WebpConstants.Bps); + int index = x + (y * WebpConstants.Bps); dst[index] = Clip8B(dst[index] + (v >> 3)); } @@ -972,16 +971,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul1(int a) - { - return ((a * 20091) >> 16) + a; - } + private static int Mul1(int a) => ((a * 20091) >> 16) + a; [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul2(int a) - { - return (a * 35468) >> 16; - } + private static int Mul2(int a) => (a * 35468) >> 16; [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8(int v) @@ -1012,9 +1005,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Clamp255(int x) - { - return x < 0 ? 0 : (x > 255 ? 255 : x); - } + private static int Clamp255(int x) => x < 0 ? 0 : (x > 255 ? 255 : x); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index d9ca9d0cf..2484ba877 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -214,10 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private Vp8MacroBlockInfo[] Mb { get; } - public void Init() - { - this.Reset(); - } + public void Init() => this.Reset(); public void InitFilter() { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 0b3b209b1..4b8c476f3 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// The global configuration. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// The quality, that will be used to encode the image. @@ -81,6 +81,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // TODO: filterStrength is hardcoded, should be configurable. private const int FilterStrength = 60; + /// + /// I16 mode (special case). + /// + private const int FlatenessLimitI16 = 0; + /// /// Initializes a new instance of the class. /// @@ -261,6 +266,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private int MbHeaderLimit { get; } + /// + /// The number of prediction modes. + /// + private const int NumPredModes = 4; + + private readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + /// /// Encodes the image to the specified stream from the . /// @@ -850,15 +862,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { rd.InitScore(); + // We can perform predictions for Luma16x16 and Chroma8x8 already. + // Luma4x4 predictions needs to be done as-we-go. it.MakeLuma16Preds(); it.MakeChroma8Preds(); - // TODO: add support for Rate-distortion optimization levels - // At this point we have heuristically decided intra16 / intra4. - // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). - // For method <= 1, we don't re-examine the decision but just go ahead with - // quantization/reconstruction. - this.RefineUsingDistortion(it, rd, this.method >= 2, this.method >= 1); + if (rdOpt > Vp8RdLevel.RdOptNone) + { + this.PickBestIntra16(it, rd); + if (this.method >= 2) + { + this.PickBestIntra4(it, rd); + } + + this.PickBestUv(it, rd); + } + else + { + // At this point we have heuristically decided intra16 / intra4. + // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). + // For method <= 1, we don't re-examine the decision but just go ahead with + // quantization/reconstruction. + this.RefineUsingDistortion(it, rd, this.method >= 2, this.method >= 1); + } bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); @@ -866,6 +892,115 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return isSkipped; } + private void PickBestIntra16(Vp8EncIterator it, Vp8ModeScore rd) + { + const int numBlocks = 16; + Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI16; + int tlambda = dqm.TLambda; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + var rdTmp = new Vp8ModeScore(); + Vp8ModeScore rdCur = rdTmp; + Vp8ModeScore rdBest = rd; + int mode; + bool isFlat = IsFlatSource16(src); + rd.ModeI16 = -1; + for (mode = 0; mode < NumPredModes; ++mode) + { + // scratch buffer. + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + rdCur.ModeI16 = mode; + + // Reconstruct. + rdCur.Nz = (uint)this.ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); + + // Measure RD-score. + rdCur.D = Vp8Sse16X16(src, tmpDst); + rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.WeightY)) : 0; + rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; + rdCur.R = this.GetCostLuma16(it, rdCur); + + if (isFlat) + { + // Refine the first impression (which was in pixel space). + isFlat = IsFlat(rdCur.YAcLevels, numBlocks, FlatenessLimitI16); + if (isFlat) + { + // Block is very flat. We put emphasis on the distortion being very low! + rdCur.D *= 2; + rdCur.SD *= 2; + } + } + + // Since we always examine Intra16 first, we can overwrite *rd directly. + rdCur.SetRdScore(lambda); + + if (mode == 0 || rdCur.Score < rdBest.Score) + { + Vp8ModeScore tmp = rdCur; + rdCur = rdBest; + rdBest = tmp; + it.SwapOut(); + } + } + + if (rdBest != rd) + { + rd = rdBest; + } + + // Finalize score for mode decision. + rd.SetRdScore(dqm.LambdaMode); + it.SetIntra16Mode(rd.ModeI16); + + // We have a blocky macroblock (only DCs are non-zero) with fairly high + // distortion, record max delta so we can later adjust the minimal filtering + // strength needed to smooth these blocks out. + if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) + { + dqm.StoreMaxDelta(rd.YDcLevels); + } + } + + private void PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd) + { + + } + + private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd) + { + + } + + private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd) + { + var res = new Vp8Residual(); + int r = 0; + + // re-import the non-zero context. + it.NzToBytes(); + + // DC + res.Init(0, 1, this.Proba); + res.SetCoeffs(rd.YDcLevels); + r += res.GetResidualCost(it.TopNz[8] + it.LeftNz[8]); + + // AC + res.Init(1, 0, this.Proba); + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + int ctx = it.TopNz[x] + it.LeftNz[y]; + res.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4))); + r += res.GetResidualCost(ctx); + it.TopNz[x] = it.LeftNz[y] = (res.Last >= 0) ? 1 : 0; + } + } + + return r; + } + // Refine intra16/intra4 sub-modes based on distortion only (not rate). private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) { @@ -876,22 +1011,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; // Some empiric constants, of approximate order of magnitude. - int lambdaDi16 = 106; - int lambdaDi4 = 11; - int lambdaDuv = 120; + const int lambdaDi16 = 106; + const int lambdaDi4 = 11; + const int lambdaDuv = 120; long scoreI4 = dqm.I4Penalty; long i4BitSum = 0; long bitLimit = tryBothModes ? this.MbHeaderLimit : Vp8ModeScore.MaxCost; // no early-out allowed. - int numPredModes = 4; - int numBModes = 10; + const int numBModes = 10; if (isI16) { int bestMode = -1; Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - for (mode = 0; mode < numPredModes; ++mode) + for (mode = 0; mode < NumPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); long score = (Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); @@ -987,7 +1121,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int bestMode = -1; long bestUvScore = Vp8ModeScore.MaxCost; Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - for (mode = 0; mode < numPredModes; ++mode) + for (mode = 0; mode < NumPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); long score = (Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); @@ -1313,6 +1447,52 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return count; } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8Disto16x16(Span a, Span b, Span w) + { + int D = 0; + int x, y; + for (y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) + { + for (x = 0; x < 16; x += 4) + { + D += Disto4x4(a.Slice(x + y), b.Slice(x + y), w); + } + } + + return D; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Disto4x4(Span a, Span b, Span w) + { + int sum1 = LossyUtils.TTransform(a, w); + int sum2 = LossyUtils.TTransform(b, w); + return Math.Abs(sum2 - sum1) >> 5; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsFlat(Span levels, int numBlocks, int thresh) + { + int score = 0; + while (numBlocks-- > 0) + { + for (int i = 1; i < 16; ++i) + { + // omit DC, we're only interested in AC + score += (levels[i] != 0) ? 1 : 0; + if (score > thresh) + { + return false; + } + } + + levels = levels.Slice(16, 16); + } + + return true; + } + [MethodImpl(InliningOptions.ShortMethod)] private static bool IsFlatSource16(Span src) { @@ -1360,6 +1540,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return WebpLookupTables.LevelsFromDelta[sharpness, pos]; } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; + [MethodImpl(InliningOptions.ShortMethod)] private static double GetPsnr(long mse, long size) => (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 049b7caed..90a8997cb 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -10,6 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { public const long MaxCost = 0x7fffffffffffffL; + /// + /// Distortion multiplier (equivalent of lambda). + /// + private const int RdDistoMult = 256; + /// /// Initializes a new instance of the class. /// @@ -91,5 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Nz = 0; this.Score = MaxCost; } + + public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index c50fede57..76cea1a03 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -10,8 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// internal class Vp8Residual { - private const int MaxVariableLevel = 67; - public int First { get; set; } public int Last { get; set; } @@ -24,12 +22,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public Vp8Stats[] Stats { get; set; } + public ushort[] Costs { get; set; } + public void Init(int first, int coeffType, Vp8EncProba prob) { this.First = first; this.CoeffType = coeffType; this.Prob = prob.Coeffs[this.CoeffType]; this.Stats = prob.Stats[this.CoeffType]; + this.Costs = new ushort[WebpConstants.NumCtx * (WebpConstants.MaxVariableLevel + 1)]; // TODO: // res->costs = enc->proba_.remapped_costs_[coeff_type]; @@ -83,9 +84,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else { v = Math.Abs(v); - if (v > MaxVariableLevel) + if (v > WebpConstants.MaxVariableLevel) { - v = MaxVariableLevel; + v = WebpConstants.MaxVariableLevel; } int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; @@ -106,12 +107,55 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (n < 16) { - this.RecordStats(0, s, 0); + this.RecordStats(0, s, 0); } return 1; } + public int GetResidualCost(int ctx0) + { + int n = this.First; + int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0]; + ushort[] costs = this.Costs; + Span t = costs.AsSpan(n * ctx0); + + // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; + + if (this.Last < 0) + { + return LossyUtils.Vp8BitCost(0, (byte)p0); + } + + int v; + for (; n < this.Last; ++n) + { + v = Math.Abs(this.Coeffs[n]); + int ctx = (v >= 2) ? 2 : v; + cost += LevelCost(t, v); + t[0] = costs[(n + 1) * ctx]; + } + + // Last coefficient is always non-zero + v = Math.Abs(this.Coeffs[n]); + cost += LevelCost(t, v); + if (n < 15) + { + int b = WebpConstants.Vp8EncBands[n + 1]; + int ctx = (v == 1) ? 1 : 2; + int last_p0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)last_p0); + } + + return cost; + } + + private static int LevelCost(Span table, int level) + => WebpLookupTables.Vp8LevelFixedCosts[level] + table[(level > WebpConstants.MaxVariableLevel) ? WebpConstants.MaxVariableLevel : level]; + private int RecordStats(int bit, Vp8StatsArray statsArr, int idx) { // An overflow is inbound. Note we handle this at 0xfffe0000u instead of diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index 2051a2079..bb04eaa11 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8SegmentInfo @@ -49,5 +51,31 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Gets or sets the penalty for using Intra4. /// public long I4Penalty { get; set; } + + /// + /// Gets or sets the minimum distortion required to trigger filtering record. + /// + public int MinDisto { get; set; } + + public int LambdaI16 { get; set; } + + public int TLambda { get; set; } + + public int LambdaMode { get; set; } + + public void StoreMaxDelta(Span dcs) + { + // We look at the first three AC coefficients to determine what is the average + // delta between each sub-4x4 block. + int v0 = Math.Abs(dcs[1]); + int v1 = Math.Abs(dcs[2]); + int v2 = Math.Abs(dcs[4]); + int maxV = (v1 > v0) ? v1 : v0; + maxV = (v2 > maxV) ? v2 : maxV; + if (maxV > this.MaxEdge) + { + this.MaxEdge = maxV; + } + } } } diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index f6e997fb4..81434d875 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -222,6 +222,8 @@ namespace SixLabors.ImageSharp.Formats.Webp public const int NumCtx = 3; + public const int MaxVariableLevel = 67; + // This is the common stride for enc/dec. public const int Bps = 32; diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs index afe9677c7..4808ee3ce 100644 --- a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs @@ -54,6 +54,133 @@ namespace SixLabors.ImageSharp.Formats.Webp 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V }; + // fixed costs for coding levels, deduce from the coding tree. + // This is only the part that doesn't depend on the probability state. + public static readonly short[] Vp8LevelFixedCosts = + { + 0, 256, 256, 256, 256, 432, 618, 630, 731, 640, 640, 828, 901, 948, 1021, 1101, 1174, 1221, 1294, 1042, + 1085, 1115, 1158, 1202, 1245, 1275, 1318, 1337, 1380, 1410, 1453, 1497, 1540, 1570, 1613, 1280, 1295, + 1317, 1332, 1358, 1373, 1395, 1410, 1454, 1469, 1491, 1506, 1532, 1547, 1569, 1584, 1601, 1616, 1638, + 1653, 1679, 1694, 1716, 1731, 1775, 1790, 1812, 1827, 1853, 1868, 1890, 1905, 1727, 1733, 1742, 1748, + 1759, 1765, 1774, 1780, 1800, 1806, 1815, 1821, 1832, 1838, 1847, 1853, 1878, 1884, 1893, 1899, 1910, + 1916, 1925, 1931, 1951, 1957, 1966, 1972, 1983, 1989, 1998, 2004, 2027, 2033, 2042, 2048, 2059, 2065, + 2074, 2080, 2100, 2106, 2115, 2121, 2132, 2138, 2147, 2153, 2178, 2184, 2193, 2199, 2210, 2216, 2225, + 2231, 2251, 2257, 2266, 2272, 2283, 2289, 2298, 2304, 2168, 2174, 2183, 2189, 2200, 2206, 2215, 2221, + 2241, 2247, 2256, 2262, 2273, 2279, 2288, 2294, 2319, 2325, 2334, 2340, 2351, 2357, 2366, 2372, 2392, + 2398, 2407, 2413, 2424, 2430, 2439, 2445, 2468, 2474, 2483, 2489, 2500, 2506, 2515, 2521, 2541, 2547, + 2556, 2562, 2573, 2579, 2588, 2594, 2619, 2625, 2634, 2640, 2651, 2657, 2666, 2672, 2692, 2698, 2707, + 2713, 2724, 2730, 2739, 2745, 2540, 2546, 2555, 2561, 2572, 2578, 2587, 2593, 2613, 2619, 2628, 2634, + 2645, 2651, 2660, 2666, 2691, 2697, 2706, 2712, 2723, 2729, 2738, 2744, 2764, 2770, 2779, 2785, 2796, + 2802, 2811, 2817, 2840, 2846, 2855, 2861, 2872, 2878, 2887, 2893, 2913, 2919, 2928, 2934, 2945, 2951, + 2960, 2966, 2991, 2997, 3006, 3012, 3023, 3029, 3038, 3044, 3064, 3070, 3079, 3085, 3096, 3102, 3111, + 3117, 2981, 2987, 2996, 3002, 3013, 3019, 3028, 3034, 3054, 3060, 3069, 3075, 3086, 3092, 3101, 3107, + 3132, 3138, 3147, 3153, 3164, 3170, 3179, 3185, 3205, 3211, 3220, 3226, 3237, 3243, 3252, 3258, 3281, + 3287, 3296, 3302, 3313, 3319, 3328, 3334, 3354, 3360, 3369, 3375, 3386, 3392, 3401, 3407, 3432, 3438, + 3447, 3453, 3464, 3470, 3479, 3485, 3505, 3511, 3520, 3526, 3537, 3543, 3552, 3558, 2816, 2822, 2831, + 2837, 2848, 2854, 2863, 2869, 2889, 2895, 2904, 2910, 2921, 2927, 2936, 2942, 2967, 2973, 2982, 2988, + 2999, 3005, 3014, 3020, 3040, 3046, 3055, 3061, 3072, 3078, 3087, 3093, 3116, 3122, 3131, 3137, 3148, + 3154, 3163, 3169, 3189, 3195, 3204, 3210, 3221, 3227, 3236, 3242, 3267, 3273, 3282, 3288, 3299, 3305, + 3314, 3320, 3340, 3346, 3355, 3361, 3372, 3378, 3387, 3393, 3257, 3263, 3272, 3278, 3289, 3295, 3304, + 3310, 3330, 3336, 3345, 3351, 3362, 3368, 3377, 3383, 3408, 3414, 3423, 3429, 3440, 3446, 3455, 3461, + 3481, 3487, 3496, 3502, 3513, 3519, 3528, 3534, 3557, 3563, 3572, 3578, 3589, 3595, 3604, 3610, 3630, + 3636, 3645, 3651, 3662, 3668, 3677, 3683, 3708, 3714, 3723, 3729, 3740, 3746, 3755, 3761, 3781, 3787, + 3796, 3802, 3813, 3819, 3828, 3834, 3629, 3635, 3644, 3650, 3661, 3667, 3676, 3682, 3702, 3708, 3717, + 3723, 3734, 3740, 3749, 3755, 3780, 3786, 3795, 3801, 3812, 3818, 3827, 3833, 3853, 3859, 3868, 3874, + 3885, 3891, 3900, 3906, 3929, 3935, 3944, 3950, 3961, 3967, 3976, 3982, 4002, 4008, 4017, 4023, 4034, + 4040, 4049, 4055, 4080, 4086, 4095, 4101, 4112, 4118, 4127, 4133, 4153, 4159, 4168, 4174, 4185, 4191, + 4200, 4206, 4070, 4076, 4085, 4091, 4102, 4108, 4117, 4123, 4143, 4149, 4158, 4164, 4175, 4181, 4190, + 4196, 4221, 4227, 4236, 4242, 4253, 4259, 4268, 4274, 4294, 4300, 4309, 4315, 4326, 4332, 4341, 4347, + 4370, 4376, 4385, 4391, 4402, 4408, 4417, 4423, 4443, 4449, 4458, 4464, 4475, 4481, 4490, 4496, 4521, + 4527, 4536, 4542, 4553, 4559, 4568, 4574, 4594, 4600, 4609, 4615, 4626, 4632, 4641, 4647, 3515, 3521, + 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, 3672, 3681, + 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, 3830, 3836, + 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, + 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, 3994, + 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, 4145, 4154, + 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, 4303, 4309, + 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, 4460, 4480, + 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, 4401, 4407, + 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, 4558, 4567, + 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, 4716, 4722, + 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, + 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, 4880, + 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, 5031, 5040, + 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, 5189, 5195, + 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, 5346, 4604, + 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, 4755, 4761, + 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, 4910, 4919, + 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, 5070, 5076, + 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, + 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, 5234, + 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, 5383, 5392, + 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, 5543, 5549, + 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, 5470, 5490, + 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, 5641, 5647, + 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, 5796, 5805, + 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, 5956, 5962, + 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, + 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, 6120, + 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, 6269, 6278, + 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, 6429, 6435, + 3515, 3521, 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, + 3672, 3681, 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, + 3830, 3836, 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, + 3987, 3998, 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, + 3988, 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, + 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, + 4303, 4309, 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, + 4460, 4480, 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, + 4401, 4407, 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, + 4558, 4567, 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, + 4716, 4722, 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, + 4873, 4884, 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, + 4874, 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, + 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, + 5189, 5195, 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, + 5346, 4604, 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, + 4755, 4761, 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, + 4910, 4919, 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, + 5070, 5076, 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, + 5066, 5077, 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, + 5228, 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, + 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, + 5543, 5549, 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, + 5470, 5490, 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, + 5641, 5647, 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, + 5796, 5805, 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, + 5956, 5962, 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, + 5952, 5963, 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, + 6114, 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, + 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, + 6429, 6435, 5303, 5309, 5318, 5324, 5335, 5341, 5350, 5356, 5376, 5382, 5391, 5397, 5408, 5414, 5423, + 5429, 5454, 5460, 5469, 5475, 5486, 5492, 5501, 5507, 5527, 5533, 5542, 5548, 5559, 5565, 5574, 5580, + 5603, 5609, 5618, 5624, 5635, 5641, 5650, 5656, 5676, 5682, 5691, 5697, 5708, 5714, 5723, 5729, 5754, + 5760, 5769, 5775, 5786, 5792, 5801, 5807, 5827, 5833, 5842, 5848, 5859, 5865, 5874, 5880, 5744, 5750, + 5759, 5765, 5776, 5782, 5791, 5797, 5817, 5823, 5832, 5838, 5849, 5855, 5864, 5870, 5895, 5901, 5910, + 5916, 5927, 5933, 5942, 5948, 5968, 5974, 5983, 5989, 6000, 6006, 6015, 6021, 6044, 6050, 6059, 6065, + 6076, 6082, 6091, 6097, 6117, 6123, 6132, 6138, 6149, 6155, 6164, 6170, 6195, 6201, 6210, 6216, 6227, + 6233, 6242, 6248, 6268, 6274, 6283, 6289, 6300, 6306, 6315, 6321, 6116, 6122, 6131, 6137, 6148, 6154, + 6163, 6169, 6189, 6195, 6204, 6210, 6221, 6227, 6236, 6242, 6267, 6273, 6282, 6288, 6299, 6305, 6314, + 6320, 6340, 6346, 6355, 6361, 6372, 6378, 6387, 6393, 6416, 6422, 6431, 6437, 6448, 6454, 6463, 6469, + 6489, 6495, 6504, 6510, 6521, 6527, 6536, 6542, 6567, 6573, 6582, 6588, 6599, 6605, 6614, 6620, 6640, + 6646, 6655, 6661, 6672, 6678, 6687, 6693, 6557, 6563, 6572, 6578, 6589, 6595, 6604, 6610, 6630, 6636, + 6645, 6651, 6662, 6668, 6677, 6683, 6708, 6714, 6723, 6729, 6740, 6746, 6755, 6761, 6781, 6787, 6796, + 6802, 6813, 6819, 6828, 6834, 6857, 6863, 6872, 6878, 6889, 6895, 6904, 6910, 6930, 6936, 6945, 6951, + 6962, 6968, 6977, 6983, 7008, 7014, 7023, 7029, 7040, 7046, 7055, 7061, 7081, 7087, 7096, 7102, 7113, + 7119, 7128, 7134, 6392, 6398, 6407, 6413, 6424, 6430, 6439, 6445, 6465, 6471, 6480, 6486, 6497, 6503, + 6512, 6518, 6543, 6549, 6558, 6564, 6575, 6581, 6590, 6596, 6616, 6622, 6631, 6637, 6648, 6654, 6663, + 6669, 6692, 6698, 6707, 6713, 6724, 6730, 6739, 6745, 6765, 6771, 6780, 6786, 6797, 6803, 6812, 6818, + 6843, 6849, 6858, 6864, 6875, 6881, 6890, 6896, 6916, 6922, 6931, 6937, 6948, 6954, 6963, 6969, 6833, + 6839, 6848, 6854, 6865, 6871, 6880, 6886, 6906, 6912, 6921, 6927, 6938, 6944, 6953, 6959, 6984, 6990, + 6999, 7005, 7016, 7022, 7031, 7037, 7057, 7063, 7072, 7078, 7089, 7095, 7104, 7110, 7133, 7139, 7148, + 7154, 7165, 7171, 7180, 7186, 7206, 7212, 7221, 7227, 7238, 7244, 7253, 7259, 7284, 7290, 7299, 7305, + 7316, 7322, 7331, 7337, 7357, 7363, 7372, 7378, 7389, 7395, 7404, 7410, 7205, 7211, 7220, 7226, 7237, + 7243, 7252, 7258, 7278, 7284, 7293, 7299, 7310, 7316, 7325, 7331, 7356, 7362, 7371, 7377, 7388, 7394, + 7403, 7409, 7429, 7435, 7444, 7450, 7461, 7467, 7476, 7482, 7505, 7511, 7520, 7526, 7537, 7543, 7552, + 7558, 7578, 7584, 7593, 7599, 7610, 7616, 7625, 7631, 7656, 7662, 7671, 7677, 7688, 7694, 7703, 7709, + 7729, 7735, 7744, 7750, 7761 + }; + // This table gives, for a given sharpness, the filtering strength to be // used (at least) in order to filter a given edge step delta. public static readonly byte[,] LevelsFromDelta = From caed173b389e15647eca99bc29c6962c596abf93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Jun 2021 20:19:58 +0200 Subject: [PATCH 286/359] Add PickBestIntra4 --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 49 +++--- .../Formats/WebP/Lossy/Vp8Encoder.cs | 154 ++++++++++++++++-- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 20 +++ src/ImageSharp/Formats/WebP/WebpConstants.cs | 8 + 4 files changed, 186 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 2484ba877..d4ef150f6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -454,29 +454,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return WebpLookupTables.Vp8FixedCostsI4[top, left]; } - public void SetIntraUvMode(int mode) + public int GetCostLuma4(short[] levels, Vp8EncProba proba) { - this.CurrentMacroBlockInfo.UvMode = mode; - } + int x = this.I4 & 3; + int y = this.I4 >> 2; + var res = new Vp8Residual(); + int R = 0; + int ctx; - public void SetSkip(bool skip) - { - this.CurrentMacroBlockInfo.Skip = skip; + res.Init(0, 3, proba); + ctx = this.TopNz[x] + this.LeftNz[y]; + res.SetCoeffs(levels); + R += res.GetResidualCost(ctx); + return R; } - public void SetSegment(int segment) - { - this.CurrentMacroBlockInfo.Segment = segment; - } + public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode; + + public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip; + + public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment; /// /// Returns true if iteration is finished. /// /// True if iterator is finished. - public bool IsDone() - { - return this.CountDown <= 0; - } + public bool IsDone() => this.CountDown <= 0; /// /// Go to next macroblock. @@ -588,7 +591,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } else { - this.Nz[this.nzIdx] &= 1 << 24; // Preserve the dc_nz bit. + // Preserve the dc_nz bit. + this.Nz[this.nzIdx] &= 1 << 24; } } @@ -606,10 +610,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8Encoding.EncPredChroma8(this.YuvP, left, top); } - public void MakeIntra4Preds() - { - Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); - } + public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); public void SwapOut() { @@ -802,18 +803,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.TopDerr.AsSpan().Fill(0); } - private int Bit(uint nz, int n) - { - return (nz & (1 << n)) != 0 ? 1 : 0; - } + private int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0; /// /// Set count down. /// /// Number of iterations to go. - private void SetCountDown(int countDown) - { - this.CountDown = countDown; - } + private void SetCountDown(int countDown) => this.CountDown = countDown; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 4b8c476f3..68cf2773b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -65,6 +65,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int NumMbSegments = 4; + private const int NumBModes = 10; + + /// + /// The number of prediction modes. + /// + private const int NumPredModes = 4; + private const int MaxItersKMeans = 6; // Convergence is considered reached if dq < DqLimit @@ -81,11 +88,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // TODO: filterStrength is hardcoded, should be configurable. private const int FilterStrength = 60; - /// - /// I16 mode (special case). - /// - private const int FlatenessLimitI16 = 0; - /// /// Initializes a new instance of the class. /// @@ -266,12 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private int MbHeaderLimit { get; } - /// - /// The number of prediction modes. - /// - private const int NumPredModes = 4; - - private readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; /// /// Encodes the image to the specified stream from the . @@ -916,14 +913,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Measure RD-score. rdCur.D = Vp8Sse16X16(src, tmpDst); - rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.WeightY)) : 0; + rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.weightY)) : 0; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.R = this.GetCostLuma16(it, rdCur); if (isFlat) { // Refine the first impression (which was in pixel space). - isFlat = IsFlat(rdCur.YAcLevels, numBlocks, FlatenessLimitI16); + isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); if (isFlat) { // Block is very flat. We put emphasis on the distortion being very low! @@ -962,9 +959,116 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - private void PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd) + private bool PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd) { + Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI16; + int tlambda = dqm.TLambda; + Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + int totalHeaderBits = 0; + var rdBest = new Vp8ModeScore(); + IMemoryOwner scratchBuffer = this.memoryAllocator.Allocate(512); + + if (this.maxI4HeaderBits == 0) + { + return false; + } + + rdBest.InitScore(); + rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) + rdBest.SetRdScore(dqm.LambdaMode); + it.StartI4(); + do + { + int numBlocks = 1; + var rdi4 = new Vp8ModeScore(); + int mode; + int bestMode = -1; + Span src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + Span bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]); + Span tmpDst = scratchBuffer.GetSpan(); + + rdi4.InitScore(); + it.MakeIntra4Preds(); + for (mode = 0; mode < NumBModes; ++mode) + { + var rdTmp = new Vp8ModeScore(); + short[] tmpLevels = new short[16]; + + // Reconstruct. + rdTmp.Nz = (uint)this.ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); + + // Compute RD-score. + rdTmp.D = Vp8Sse4X4(src, tmpDst); + rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4x4(src, tmpDst, this.weightY)) : 0; + rdTmp.H = modeCosts[mode]; + + // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. + if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) + { + rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; + } + else + { + rdTmp.R = 0; + } + + // early-out check. + rdTmp.SetRdScore(lambda); + if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) + { + continue; + } + + // finish computing score. + rdTmp.R += it.GetCostLuma4(tmpLevels, this.Proba); + rdTmp.SetRdScore(lambda); + + if (bestMode < 0 || rdTmp.Score < rdi4.Score) + { + rdi4.CopyScore(rdTmp); + bestMode = mode; + Span tmp = tmpDst; + tmpDst = bestBlock; + bestBlock = tmp; + tmpLevels.AsSpan(0, rdBest.YAcLevels[it.I4]).CopyTo(rdBest.YAcLevels); + } + } + + rdi4.SetRdScore(lambda); + rdBest.AddScore(rdi4); + if (rdBest.Score >= rd.Score) + { + return false; + } + + totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; + if (totalHeaderBits > this.maxI4HeaderBits) + { + return false; + } + // Copy selected samples if not in the right place already. + if (bestBlock != bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])) + { + Vp8Copy4x4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])); + } + + rd.ModesI4[it.I4] = (byte)bestMode; + it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; + } + while (it.RotateI4(bestBlocks)); + + // Finalize state. + rd.CopyScore(rdBest); + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); + + // Select intra4x4 over intra16x16. + return true; } private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd) @@ -972,6 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } + // TODO: move to Vp8EncIterator private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd) { var res = new Vp8Residual(); @@ -1019,7 +1124,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy long bitLimit = tryBothModes ? this.MbHeaderLimit : Vp8ModeScore.MaxCost; // no early-out allowed. - const int numBModes = 10; if (isI16) { @@ -1072,7 +1176,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy short[] modeCosts = it.GetCostModeI4(rd.ModesI4); it.MakeIntra4Preds(); - for (mode = 0; mode < numBModes; ++mode) + for (mode = 0; mode < NumBModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); @@ -1447,6 +1551,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return count; } + [MethodImpl(InliningOptions.ShortMethod)] + private static void Vp8Copy4x4(Span src, Span dst) => Copy(src, dst, 4, 4); + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Copy(Span src, Span dst, int w, int h) + { + for (int y = 0; y < h; ++y) + { + src.Slice(0, w).CopyTo(dst); + src = src.Slice(WebpConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); + } + } + [MethodImpl(InliningOptions.ShortMethod)] private static int Vp8Disto16x16(Span a, Span b, Span w) { @@ -1456,7 +1574,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { for (x = 0; x < 16; x += 4) { - D += Disto4x4(a.Slice(x + y), b.Slice(x + y), w); + D += Vp8Disto4x4(a.Slice(x + y), b.Slice(x + y), w); } } @@ -1464,7 +1582,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Disto4x4(Span a, Span b, Span w) + private static int Vp8Disto4x4(Span a, Span b, Span w) { int sum1 = LossyUtils.TTransform(a, w); int sum2 = LossyUtils.TTransform(b, w); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 90a8997cb..e47fa7160 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -97,6 +97,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Score = MaxCost; } + public void CopyScore(Vp8ModeScore other) + { + this.D = other.D; + this.SD = other.SD; + this.R = other.R; + this.H = other.H; + this.Nz = other.Nz; // note that nz is not accumulated, but just copied. + this.Score = other.Score; + } + + public void AddScore(Vp8ModeScore other) + { + this.D += other.D; + this.SD += other.SD; + this.R += other.R; + this.H += other.H; + this.Nz |= other.Nz; // here, new nz bits are accumulated. + this.Score += other.Score; + } + public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); } } diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index 81434d875..88aa7728a 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -224,6 +224,14 @@ namespace SixLabors.ImageSharp.Formats.Webp public const int MaxVariableLevel = 67; + public const int FlatnessLimitI16 = 0; + + public const int FlatnessLimitIUv = 2; + + public const int FlatnessLimitI4 = 3; + + public const int FlatnessPenality = 140; + // This is the common stride for enc/dec. public const int Bps = 32; From 96bb6665229cfdc4946b1d96958a9e8052f805c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 22 Jun 2021 12:03:26 +0200 Subject: [PATCH 287/359] Add PickBestUv --- .../Formats/WebP/Lossy/LossyUtils.cs | 26 ++-- .../Formats/WebP/Lossy/Vp8CostArray.cs | 5 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs | 28 ++++ .../Formats/WebP/Lossy/Vp8EncIterator.cs | 42 ++++- .../Formats/WebP/Lossy/Vp8EncProba.cs | 50 +++--- .../Formats/WebP/Lossy/Vp8Encoder.cs | 143 +++++++++++++----- .../Formats/WebP/Lossy/Vp8ProbaArray.cs | 5 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 22 ++- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 4 + 9 files changed, 223 insertions(+), 102 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index c28ae6cfa..350f6d00d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -480,18 +480,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int[] tmp = new int[16]; // horizontal pass. + int inputOffset = 0; for (int i = 0; i < 4; ++i) { - int a0 = input[0] + input[2]; - int a1 = input[1] + input[3]; - int a2 = input[1] - input[3]; - int a3 = input[0] - input[2]; + int inputOffsetPlusOne = inputOffset + 1; + int inputOffsetPlusTwo = inputOffset + 2; + int inputOffsetPlusThree = inputOffset + 3; + int a0 = input[inputOffset] + input[inputOffsetPlusTwo]; + int a1 = input[inputOffsetPlusOne] + input[inputOffsetPlusThree]; + int a2 = input[inputOffsetPlusOne] - input[inputOffsetPlusThree]; + int a3 = input[inputOffset] - input[inputOffsetPlusTwo]; tmp[0 + (i * 4)] = a0 + a1; tmp[1 + (i * 4)] = a3 + a2; tmp[2 + (i * 4)] = a3 - a2; tmp[3 + (i * 4)] = a0 - a1; - input = input.Slice(WebpConstants.Bps); + inputOffset += WebpConstants.Bps; } // vertical pass @@ -549,6 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. tmpOffset = 0; + int dstOffset = 0; for (int i = 0; i < 4; ++i) { // horizontal pass @@ -560,12 +565,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int b = dc - tmp[tmpOffsetPlus8]; int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); - Store(dst, 0, 0, a + d); - Store(dst, 1, 0, b + c); - Store(dst, 2, 0, b - c); - Store(dst, 3, 0, a - d); + Store(dst.Slice(dstOffset), 0, 0, a + d); + Store(dst.Slice(dstOffset), 1, 0, b + c); + Store(dst.Slice(dstOffset), 2, 0, b - c); + Store(dst.Slice(dstOffset), 3, 0, a - d); tmpOffset++; - dst = dst.Slice(WebpConstants.Bps); + + dstOffset += WebpConstants.Bps; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs index 3d3a522ba..4015a18a9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -8,10 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Initializes a new instance of the class. /// - public Vp8CostArray() - { - this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)]; - } + public Vp8CostArray() => this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)]; public ushort[] Costs { get; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs new file mode 100644 index 000000000..763c89c57 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Formats.Webp.Lossy; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8Costs + { + /// + /// Initializes a new instance of the class. + /// + public Vp8Costs() + { + this.Costs = new Vp8CostArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) + { + this.Costs[i] = new Vp8CostArray(); + } + } + + /// + /// Gets the Costs. + /// + public Vp8CostArray[] Costs { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index d4ef150f6..ff8f192e1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.LeftNz = new int[9]; this.I4Boundary = new byte[37]; this.BitCount = new long[4, 3]; + this.Scratch = new byte[WebpConstants.Bps * 16]; // To match the C initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; @@ -86,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.YuvP.AsSpan().Fill(defaultInitVal); this.YLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal); + this.Scratch.AsSpan().Fill(defaultInitVal); this.Reset(); } @@ -210,6 +212,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// public int CountDown { get; set; } + /// + /// Gets the scratch buffer. + /// + public byte[] Scratch { get; } + public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; private Vp8MacroBlockInfo[] Mb { get; } @@ -459,14 +466,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int x = this.I4 & 3; int y = this.I4 >> 2; var res = new Vp8Residual(); - int R = 0; - int ctx; + int r = 0; res.Init(0, 3, proba); - ctx = this.TopNz[x] + this.LeftNz[y]; + int ctx = this.TopNz[x] + this.LeftNz[y]; res.SetCoeffs(levels); - R += res.GetResidualCost(ctx); - return R; + r += res.GetResidualCost(ctx); + return r; + } + + public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba) + { + var res = new Vp8Residual(); + int r = 0; + + // re-import the non-zero context. + this.NzToBytes(); + + res.Init(0, 2, proba); + for (int ch = 0; ch <= 2; ch += 2) + { + for (int y = 0; y < 2; ++y) + { + for (int x = 0; x < 2; ++x) + { + int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; + res.SetCoeffs(rd.UvLevels.AsSpan((ch * 2) + x + (y * 2))); + r += res.GetResidualCost(ctx); + this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = (res.Last >= 0) ? 1 : 0; + } + } + } + + return r; } public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index a2c3a001e..3481b26c1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -45,23 +46,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - this.LevelCost = new Vp8CostArray[WebpConstants.NumTypes][]; + this.LevelCost = new Vp8Costs[WebpConstants.NumTypes][]; for (int i = 0; i < this.LevelCost.Length; i++) { - this.LevelCost[i] = new Vp8CostArray[WebpConstants.NumBands]; + this.LevelCost[i] = new Vp8Costs[WebpConstants.NumBands]; for (int j = 0; j < this.LevelCost[i].Length; j++) { - this.LevelCost[i][j] = new Vp8CostArray(); + this.LevelCost[i][j] = new Vp8Costs(); } } - this.RemappedCosts = new Vp8CostArray[WebpConstants.NumTypes][]; + this.RemappedCosts = new Vp8Costs[WebpConstants.NumTypes][]; for (int i = 0; i < this.RemappedCosts.Length; i++) { - this.RemappedCosts[i] = new Vp8CostArray[16]; + this.RemappedCosts[i] = new Vp8Costs[16]; for (int j = 0; j < this.RemappedCosts[i].Length; j++) { - this.RemappedCosts[i][j] = new Vp8CostArray(); + this.RemappedCosts[i][j] = new Vp8Costs(); } } @@ -102,9 +103,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public Vp8Stats[][] Stats { get; } - public Vp8CostArray[][] LevelCost { get; } + public Vp8Costs[][] LevelCost { get; } - public Vp8CostArray[][] RemappedCosts { get; } + public Vp8Costs[][] RemappedCosts { get; } /// /// Gets or sets the number of skipped blocks. @@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { if (!this.Dirty) { - return; // nothing to do. + return; // Nothing to do. } for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype) @@ -130,17 +131,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; - Span table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel); + Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx]; int cost0 = (ctx > 0) ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; int v; - table[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); + table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); for (v = 1; v <= MaxVariableLevel; ++v) { - table[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); + table.Costs[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); } - // Starting at level 67 and up, the variable part of the cost is actually constant. + // Starting at level 67 and up, the variable part of the cost is actually constant } } @@ -148,9 +149,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { - Span dst = this.RemappedCosts[ctype][n].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); - Span src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); - src.CopyTo(dst); + Vp8CostArray dst = this.RemappedCosts[ctype][n].Costs[ctx]; + Vp8CostArray src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs[ctx]; + src.Costs.CopyTo(dst.Costs.AsSpan()); } } } @@ -170,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { for (int p = 0; p < WebpConstants.NumProbas; ++p) { - var stats = this.Stats[t][b].Stats[c].Stats[p]; + uint stats = this.Stats[t][b].Stats[c].Stats[p]; int nb = (int)((stats >> 0) & 0xffff); int total = (int)((stats >> 16) & 0xffff); int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; @@ -234,10 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - private static int CalcSkipProba(long nb, long total) - { - return (int)(total != 0 ? (total - nb) * 255 / total : 255); - } + private static int CalcSkipProba(long nb, long total) => (int)(total != 0 ? (total - nb) * 255 / total : 255); private static int VariableLevelCost(int level, Span probas) { @@ -260,15 +258,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Collect statistics and deduce probabilities for next coding pass. // Return the total bit-cost for coding the probability updates. - private static int CalcTokenProba(int nb, int total) - { - return nb != 0 ? (255 - (nb * 255 / total)) : 255; - } + private static int CalcTokenProba(int nb, int total) => nb != 0 ? (255 - (nb * 255 / total)) : 255; // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. - private static int BranchCost(int nb, int total, int proba) - { - return (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); - } + private static int BranchCost(int nb, int total, int proba) => (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 68cf2773b..757babdc0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -63,6 +63,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; + private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + private const int NumMbSegments = 4; private const int NumBModes = 10; @@ -268,8 +270,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private int MbHeaderLimit { get; } - private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; - /// /// Encodes the image to the specified stream from the . /// @@ -452,7 +452,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.SaveBoundary(); } - while (it.Next()); + while (it.Next() && --nbMbs > 0); sizeP0 += this.SegmentHeader.Size; if (stats.DoSizeSearch) @@ -706,13 +706,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Initialize segments' filtering this.SetupFilterStrength(); - this.SetupMatrices(dqm); + this.SetupMatrices(dqm, snsStrength); } private void SetupFilterStrength() { int filterSharpness = 0; // TODO: filterSharpness is hardcoded - var filterType = 1; // TODO: filterType is hardcoded + int filterType = 1; // TODO: filterType is hardcoded // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. int level0 = 5 * FilterStrength; @@ -787,8 +787,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy proba.NbSkip = 0; } - private void SetupMatrices(Vp8SegmentInfo[] dqm) + private void SetupMatrices(Vp8SegmentInfo[] dqm, int snsStrength) { + int tlambdaScale = (this.method >= 4) ? snsStrength : 0; for (int i = 0; i < dqm.Length; ++i) { Vp8SegmentInfo m = dqm[i]; @@ -808,8 +809,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)]; int qi4 = m.Y1.Expand(0); - m.Y2.Expand(1); // qi16 - m.Uv.Expand(2); // quv + int qi16 = m.Y2.Expand(1); + int quv = m.Uv.Expand(2); + + m.I4Penalty = 1000 * qi4 * qi4; + + m.LambdaI16 = 3 * qi16 * qi16; + m.LambdaI4 = (3 * qi4 * qi4) >> 7; + m.LambdaUv = (3 * quv * quv) >> 6; + m.LambdaMode = (1 * qi4 * qi4) >> 7; + m.TLambda = (tlambdaScale * qi4) >> 5; + + // none of these constants should be < 1. + m.LambdaI16 = m.LambdaI16 < 1 ? 1 : m.LambdaI16; + m.LambdaI4 = m.LambdaI4 < 1 ? 1 : m.LambdaI4; + m.LambdaUv = m.LambdaUv < 1 ? 1 : m.LambdaUv; + m.LambdaMode = m.LambdaMode < 1 ? 1 : m.LambdaMode; + m.TLambda = m.TLambda < 1 ? 1 : m.TLambda; + + m.MinDisto = 20 * m.Y1.Q[0]; + m.MaxEdge = 0; m.I4Penalty = 1000 * qi4 * qi4; } @@ -864,15 +883,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.MakeLuma16Preds(); it.MakeChroma8Preds(); - if (rdOpt > Vp8RdLevel.RdOptNone) + // TODO: disabled picking best mode because its still bugged. + // if (rdOpt > Vp8RdLevel.RdOptNone) + if (false) { - this.PickBestIntra16(it, rd); + this.PickBestIntra16(it, ref rd); if (this.method >= 2) { - this.PickBestIntra4(it, rd); + this.PickBestIntra4(it, ref rd); } - this.PickBestUv(it, rd); + this.PickBestUv(it, ref rd); } else { @@ -889,7 +910,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return isSkipped; } - private void PickBestIntra16(Vp8EncIterator it, Vp8ModeScore rd) + private void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd) { const int numBlocks = 16; Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; @@ -913,7 +934,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Measure RD-score. rdCur.D = Vp8Sse16X16(src, tmpDst); - rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.weightY)) : 0; + rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16X16(src, tmpDst, this.weightY)) : 0; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.R = this.GetCostLuma16(it, rdCur); @@ -959,16 +980,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - private bool PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd) + private bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd) { Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI16; + int lambda = dqm.LambdaI4; int tlambda = dqm.TLambda; Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); int totalHeaderBits = 0; var rdBest = new Vp8ModeScore(); - IMemoryOwner scratchBuffer = this.memoryAllocator.Allocate(512); if (this.maxI4HeaderBits == 0) { @@ -988,7 +1008,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]); short[] modeCosts = it.GetCostModeI4(rd.ModesI4); Span bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]); - Span tmpDst = scratchBuffer.GetSpan(); + Span tmpDst = it.Scratch.AsSpan(); + tmpDst.Fill(0); rdi4.InitScore(); it.MakeIntra4Preds(); @@ -1002,7 +1023,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Compute RD-score. rdTmp.D = Vp8Sse4X4(src, tmpDst); - rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4x4(src, tmpDst, this.weightY)) : 0; + rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4X4(src, tmpDst, this.weightY)) : 0; rdTmp.H = modeCosts[mode]; // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. @@ -1033,11 +1054,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span tmp = tmpDst; tmpDst = bestBlock; bestBlock = tmp; - tmpLevels.AsSpan(0, rdBest.YAcLevels[it.I4]).CopyTo(rdBest.YAcLevels); + tmpLevels.AsSpan().CopyTo(rdBest.YAcLevels); } } - rdi4.SetRdScore(lambda); + rdi4.SetRdScore(dqm.LambdaMode); rdBest.AddScore(rdi4); if (rdBest.Score >= rd.Score) { @@ -1050,11 +1071,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return false; } - // Copy selected samples if not in the right place already. - if (bestBlock != bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])) - { - Vp8Copy4x4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])); - } + // Copy selected samples to the right place. + Vp8Copy4X4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])); rd.ModesI4[it.I4] = (byte)bestMode; it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; @@ -1071,9 +1089,56 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return true; } - private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd) + private void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd) { + const int numBlocks = 8; + Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaUv; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); + Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); + Span dst = dst0; + var rdBest = new Vp8ModeScore(); + int mode; + rd.ModeUv = -1; + rdBest.InitScore(); + for (mode = 0; mode < NumPredModes; ++mode) + { + var rdUv = new Vp8ModeScore(); + + // Reconstruct + rdUv.Nz = (uint)this.ReconstructUv(it, dqm, rdUv, tmpDst, mode); + + // Compute RD-score + rdUv.D = Vp8Sse16X8(src, tmpDst); + rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. + rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; + rdUv.R = it.GetCostUv(rdUv, this.Proba); + if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) + { + rdUv.R += WebpConstants.FlatnessPenality * numBlocks; + } + + rdUv.SetRdScore(lambda); + if (mode == 0 || rdUv.Score < rdBest.Score) + { + rdBest.CopyScore(rdUv); + rd.ModeUv = mode; + rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); + Span tmp = dst; + dst = tmpDst; + tmpDst = tmp; + } + } + + it.SetIntraUvMode(rd.ModeUv); + rd.AddScore(rdBest); + if (dst != dst0) + { + // copy 16x8 block if needed. + Vp8Copy16X8(dst, dst0); + } } // TODO: move to Vp8EncIterator @@ -1358,7 +1423,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - var res = residual.RecordCoeffs(ctx); + int res = residual.RecordCoeffs(ctx); it.TopNz[4 + ch + x] = res; it.LeftNz[4 + ch + y] = res; } @@ -1552,7 +1617,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static void Vp8Copy4x4(Span src, Span dst) => Copy(src, dst, 4, 4); + private static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); [MethodImpl(InliningOptions.ShortMethod)] private static void Copy(Span src, Span dst, int w, int h) @@ -1566,23 +1634,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Disto16x16(Span a, Span b, Span w) + private static int Vp8Disto16X16(Span a, Span b, Span w) { - int D = 0; - int x, y; - for (y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) + int d = 0; + for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) { - for (x = 0; x < 16; x += 4) + for (int x = 0; x < 16; x += 4) { - D += Vp8Disto4x4(a.Slice(x + y), b.Slice(x + y), w); + d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w); } } - return D; + return d; } [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Disto4x4(Span a, Span b, Span w) + private static int Vp8Disto4X4(Span a, Span b, Span w) { int sum1 = LossyUtils.TTransform(a, w); int sum2 = LossyUtils.TTransform(b, w); @@ -1605,7 +1672,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - levels = levels.Slice(16, 16); + levels = levels.Slice(16); } return true; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index fce157044..7bb917a6d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -11,10 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Initializes a new instance of the class. /// - public Vp8ProbaArray() - { - this.Probabilities = new byte[WebpConstants.NumProbas]; - } + public Vp8ProbaArray() => this.Probabilities = new byte[WebpConstants.NumProbas]; /// /// Gets the probabilities. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 76cea1a03..4a48a94ca 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public Vp8Stats[] Stats { get; set; } - public ushort[] Costs { get; set; } + public Vp8Costs[] Costs { get; set; } public void Init(int first, int coeffType, Vp8EncProba prob) { @@ -30,10 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.CoeffType = coeffType; this.Prob = prob.Coeffs[this.CoeffType]; this.Stats = prob.Stats[this.CoeffType]; - this.Costs = new ushort[WebpConstants.NumCtx * (WebpConstants.MaxVariableLevel + 1)]; - - // TODO: - // res->costs = enc->proba_.remapped_costs_[coeff_type]; + this.Costs = prob.RemappedCosts[this.CoeffType]; } public void SetCoeffs(Span coeffs) @@ -117,8 +115,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int n = this.First; int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0]; - ushort[] costs = this.Costs; - Span t = costs.AsSpan(n * ctx0); + Vp8Costs[] costs = this.Costs; + Vp8CostArray t = costs[n].Costs[ctx0]; // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll @@ -135,19 +133,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { v = Math.Abs(this.Coeffs[n]); int ctx = (v >= 2) ? 2 : v; - cost += LevelCost(t, v); - t[0] = costs[(n + 1) * ctx]; + cost += LevelCost(t.Costs, v); + t = costs[n + 1].Costs[ctx]; } // Last coefficient is always non-zero v = Math.Abs(this.Coeffs[n]); - cost += LevelCost(t, v); + cost += LevelCost(t.Costs, v); if (n < 15) { int b = WebpConstants.Vp8EncBands[n + 1]; int ctx = (v == 1) ? 1 : 2; - int last_p0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)last_p0); + int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); } return cost; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index bb04eaa11..a9d2464ae 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -59,8 +59,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public int LambdaI16 { get; set; } + public int LambdaI4 { get; set; } + public int TLambda { get; set; } + public int LambdaUv { get; set; } + public int LambdaMode { get; set; } public void StoreMaxDelta(Span dcs) From 2348ab2465e496e8ee91811768d8d348bec053ff Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 24 Jun 2021 12:27:29 +0200 Subject: [PATCH 288/359] Refactor Vp8Encoder --- .../Formats/WebP/Lossy/LossyUtils.cs | 70 ++ src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 509 ++++++++++++- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 29 + .../Formats/WebP/Lossy/Vp8Encoder.cs | 711 +----------------- .../Formats/WebP/Lossy/YuvConversion.cs | 71 ++ src/ImageSharp/Formats/WebP/WebpConstants.cs | 4 + 6 files changed, 697 insertions(+), 697 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 350f6d00d..a03d2c5c5 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -10,6 +10,76 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal static class LossyUtils { + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Sse4X4(Span a, Span b) => GetSse(a, b, 4, 4); + + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetSse(Span a, Span b, int w, int h) + { + int count = 0; + int aOffset = 0; + int bOffset = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + int diff = a[aOffset + x] - b[bOffset + x]; + count += diff * diff; + } + + aOffset += WebpConstants.Bps; + bOffset += WebpConstants.Bps; + } + + return count; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Copy(Span src, Span dst, int w, int h) + { + for (int y = 0; y < h; ++y) + { + src.Slice(0, w).CopyTo(dst); + src = src.Slice(WebpConstants.Bps); + dst = dst.Slice(WebpConstants.Bps); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Disto16X16(Span a, Span b, Span w) + { + int d = 0; + for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) + { + for (int x = 0; x < 16; x += 4) + { + d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w); + } + } + + return d; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Disto4X4(Span a, Span b, Span w) + { + int sum1 = TTransform(a, w); + int sum2 = TTransform(b, w); + return Math.Abs(sum2 - sum1) >> 5; + } + public static void DC16(Span dst, Span yuv, int offset) { int offsetMinus1 = offset - 1; diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index 5763e79a9..fda44f7e0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + private static readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + private const int MaxLevel = 2047; // Diffusion weights. We under-correct a bit (15/16th of the error is actually @@ -22,10 +24,460 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int DSHIFT = 4; private const int DSCALE = 1; // storage descaling, needed to make the error fit byte + public static void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + { + const int numBlocks = 16; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI16; + int tlambda = dqm.TLambda; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + var rdTmp = new Vp8ModeScore(); + Vp8ModeScore rdCur = rdTmp; + Vp8ModeScore rdBest = rd; + int mode; + bool isFlat = IsFlatSource16(src); + rd.ModeI16 = -1; + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) + { + // scratch buffer. + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + rdCur.ModeI16 = mode; + + // Reconstruct. + rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); + + // Measure RD-score. + rdCur.D = LossyUtils.Vp8Sse16X16(src, tmpDst); + rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY)) : 0; + rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; + rdCur.R = it.GetCostLuma16(rdCur, proba); + + if (isFlat) + { + // Refine the first impression (which was in pixel space). + isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); + if (isFlat) + { + // Block is very flat. We put emphasis on the distortion being very low! + rdCur.D *= 2; + rdCur.SD *= 2; + } + } + + // Since we always examine Intra16 first, we can overwrite *rd directly. + rdCur.SetRdScore(lambda); + + if (mode == 0 || rdCur.Score < rdBest.Score) + { + Vp8ModeScore tmp = rdCur; + rdCur = rdBest; + rdBest = tmp; + it.SwapOut(); + } + } + + if (rdBest != rd) + { + rd = rdBest; + } + + // Finalize score for mode decision. + rd.SetRdScore(dqm.LambdaMode); + it.SetIntra16Mode(rd.ModeI16); + + // We have a blocky macroblock (only DCs are non-zero) with fairly high + // distortion, record max delta so we can later adjust the minimal filtering + // strength needed to smooth these blocks out. + if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) + { + dqm.StoreMaxDelta(rd.YDcLevels); + } + } + + public static bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba, int maxI4HeaderBits) + { + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI4; + int tlambda = dqm.TLambda; + Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + int totalHeaderBits = 0; + var rdBest = new Vp8ModeScore(); + + if (maxI4HeaderBits == 0) + { + return false; + } + + rdBest.InitScore(); + rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) + rdBest.SetRdScore(dqm.LambdaMode); + it.StartI4(); + do + { + int numBlocks = 1; + var rdi4 = new Vp8ModeScore(); + int mode; + int bestMode = -1; + Span src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + Span bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]); + Span tmpDst = it.Scratch.AsSpan(); + tmpDst.Fill(0); + + rdi4.InitScore(); + it.MakeIntra4Preds(); + for (mode = 0; mode < WebpConstants.NumBModes; ++mode) + { + var rdTmp = new Vp8ModeScore(); + short[] tmpLevels = new short[16]; + + // Reconstruct. + rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); + + // Compute RD-score. + rdTmp.D = LossyUtils.Vp8Sse4X4(src, tmpDst); + rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY)) : 0; + rdTmp.H = modeCosts[mode]; + + // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. + if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) + { + rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; + } + else + { + rdTmp.R = 0; + } + + // early-out check. + rdTmp.SetRdScore(lambda); + if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) + { + continue; + } + + // finish computing score. + rdTmp.R += it.GetCostLuma4(tmpLevels, proba); + rdTmp.SetRdScore(lambda); + + if (bestMode < 0 || rdTmp.Score < rdi4.Score) + { + rdi4.CopyScore(rdTmp); + bestMode = mode; + Span tmp = tmpDst; + tmpDst = bestBlock; + bestBlock = tmp; + tmpLevels.AsSpan().CopyTo(rdBest.YAcLevels); + } + } + + rdi4.SetRdScore(dqm.LambdaMode); + rdBest.AddScore(rdi4); + if (rdBest.Score >= rd.Score) + { + return false; + } + + totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; + if (totalHeaderBits > maxI4HeaderBits) + { + return false; + } + + // Copy selected samples to the right place. + LossyUtils.Vp8Copy4X4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])); + + rd.ModesI4[it.I4] = (byte)bestMode; + it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; + } + while (it.RotateI4(bestBlocks)); + + // Finalize state. + rd.CopyScore(rdBest); + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); + + // Select intra4x4 over intra16x16. + return true; + } + + public static void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + { + const int numBlocks = 8; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaUv; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); + Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); + Span dst = dst0; + var rdBest = new Vp8ModeScore(); + int mode; + + rd.ModeUv = -1; + rdBest.InitScore(); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) + { + var rdUv = new Vp8ModeScore(); + + // Reconstruct + rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); + + // Compute RD-score + rdUv.D = LossyUtils.Vp8Sse16X8(src, tmpDst); + rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. + rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; + rdUv.R = it.GetCostUv(rdUv, proba); + if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) + { + rdUv.R += WebpConstants.FlatnessPenality * numBlocks; + } + + rdUv.SetRdScore(lambda); + if (mode == 0 || rdUv.Score < rdBest.Score) + { + rdBest.CopyScore(rdUv); + rd.ModeUv = mode; + rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); + Span tmp = dst; + dst = tmpDst; + tmpDst = tmp; + } + } + + it.SetIntraUvMode(rd.ModeUv); + rd.AddScore(rdBest); + if (dst != dst0) + { + // copy 16x8 block if needed. + LossyUtils.Vp8Copy16X8(dst, dst0); + } + } + + public static int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + int nz = 0; + int n; + short[] dcTmp = new short[16]; + short[] tmp = new short[16 * 16]; + Span tmpSpan = tmp.AsSpan(); + + for (n = 0; n < 16; n += 2) + { + Vp8Encoding.FTransform2(src.Slice(WebpLookupTables.Vp8Scan[n]), reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); + } + + Vp8Encoding.FTransformWht(tmp, dcTmp); + nz |= QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; + + for (n = 0; n < 16; n += 2) + { + // Zero-out the first coeff, so that: a) nz is correct below, and + // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. + tmp[n * 16] = tmp[(n + 1) * 16] = 0; + nz |= Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; + } + + // Transform back. + LossyUtils.TransformWht(dcTmp, tmpSpan); + for (n = 0; n < 16; n += 2) + { + Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), true); + } + + return nz; + } + + public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); + short[] tmp = new short[16]; + Vp8Encoding.FTransform(src, reference, tmp); + int nz = QuantizeBlock(tmp, levels, dqm.Y1); + Vp8Encoding.ITransform(reference, tmp, yuvOut, false); + + return nz; + } + + public static int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + int nz = 0; + int n; + short[] tmp = new short[8 * 16]; + + for (n = 0; n < 8; n += 2) + { + Vp8Encoding.FTransform2( + src.Slice(WebpLookupTables.Vp8ScanUv[n]), + reference.Slice(WebpLookupTables.Vp8ScanUv[n]), + tmp.AsSpan(n * 16, 16), + tmp.AsSpan((n + 1) * 16, 16)); + } + + CorrectDcValues(it, dqm.Uv, tmp, rd); + + for (n = 0; n < 8; n += 2) + { + nz |= Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; + } + + for (n = 0; n < 8; n += 2) + { + Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), true); + } + + return nz << 16; + } + + // Refine intra16/intra4 sub-modes based on distortion only (not rate). + public static void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode, int mbHeaderLimit) + { + long bestScore = Vp8ModeScore.MaxCost; + int nz = 0; + int mode; + bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + + // Some empiric constants, of approximate order of magnitude. + const int lambdaDi16 = 106; + const int lambdaDi4 = 11; + const int lambdaDuv = 120; + long scoreI4 = dqm.I4Penalty; + long i4BitSum = 0; + long bitLimit = tryBothModes + ? mbHeaderLimit + : Vp8ModeScore.MaxCost; // no early-out allowed. + + if (isI16) + { + int bestMode = -1; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); + long score = (LossyUtils.Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + + if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) + { + continue; + } + + if (score < bestScore) + { + bestMode = mode; + bestScore = score; + } + } + + if (it.X == 0 || it.Y == 0) + { + // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. + if (IsFlatSource16(src)) + { + bestMode = (it.X == 0) ? 0 : 2; + tryBothModes = false; // Stick to i16. + } + } + + it.SetIntra16Mode(bestMode); + + // We'll reconstruct later, if i16 mode actually gets selected. + } + + // Next, evaluate Intra4. + if (tryBothModes || !isI16) + { + // We don't evaluate the rate here, but just account for it through a + // constant penalty (i4 mode usually needs more bits compared to i16). + isI16 = false; + it.StartI4(); + do + { + int bestI4Mode = -1; + long bestI4Score = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + + it.MakeIntra4Preds(); + for (mode = 0; mode < WebpConstants.NumBModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); + long score = (LossyUtils.Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + if (score < bestI4Score) + { + bestI4Mode = mode; + bestI4Score = score; + } + } + + i4BitSum += modeCosts[bestI4Mode]; + rd.ModesI4[it.I4] = (byte)bestI4Mode; + scoreI4 += bestI4Score; + if (scoreI4 >= bestScore || i4BitSum > bitLimit) + { + // Intra4 won't be better than Intra16. Bail out and pick Intra16. + isI16 = true; + break; + } + else + { + // Reconstruct partial block inside YuvOut2 buffer + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); + nz |= ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; + } + } + while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); + } + + // Final reconstruction, depending on which mode is selected. + if (!isI16) + { + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + bestScore = scoreI4; + } + else + { + int intra16Mode = it.Preds[it.PredIdx]; + nz = ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); + } + + // ... and UV! + if (refineUvMode) + { + int bestMode = -1; + long bestUvScore = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); + long score = (LossyUtils.Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + if (score < bestUvScore) + { + bestMode = mode; + bestUvScore = score; + } + } + + it.SetIntraUvMode(bestMode); + } + + nz |= ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); + + rd.Nz = (uint)nz; + rd.Score = bestScore; + } + + [MethodImpl(InliningOptions.ShortMethod)] public static int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) { - var nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; - nz |= QuantEnc.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; + int nz = QuantizeBlock(input, output, mtx) << 0; + nz |= QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; return nz; } @@ -111,13 +563,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span left = it.LeftDerr.AsSpan(ch, 2); Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - var err0 = QuantEnc.QuantizeSingle(c, mtx); + int err0 = QuantizeSingle(c, mtx); c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - var err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); + int err1 = QuantizeSingle(c.Slice(1 * 16), mtx); c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - var err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); + int err2 = QuantizeSingle(c.Slice(2 * 16), mtx); c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - var err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); + int err3 = QuantizeSingle(c.Slice(3 * 16), mtx); // TODO: set errors in rd // rd->derr[ch][0] = (int8_t)err1; @@ -127,9 +579,50 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int QuantDiv(uint n, uint iQ, uint b) + private static bool IsFlatSource16(Span src) { - return (int)(((n * iQ) + b) >> WebpConstants.QFix); + uint v = src[0] * 0x01010101u; + Span vSpan = BitConverter.GetBytes(v).AsSpan(); + for (int i = 0; i < 16; ++i) + { + if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || + !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) + { + return false; + } + + src = src.Slice(WebpConstants.Bps); + } + + return true; } + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsFlat(Span levels, int numBlocks, int thresh) + { + int score = 0; + while (numBlocks-- > 0) + { + for (int i = 1; i < 16; ++i) + { + // omit DC, we're only interested in AC + score += (levels[i] != 0) ? 1 : 0; + if (score > thresh) + { + return false; + } + } + + levels = levels.Slice(16); + } + + return true; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int QuantDiv(uint n, uint iQ, uint b) => (int)(((n * iQ) + b) >> WebpConstants.QFix); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index ff8f192e1..f1fb6c13a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -450,6 +450,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; } + public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba) + { + var res = new Vp8Residual(); + int r = 0; + + // re-import the non-zero context. + this.NzToBytes(); + + // DC + res.Init(0, 1, proba); + res.SetCoeffs(rd.YDcLevels); + r += res.GetResidualCost(this.TopNz[8] + this.LeftNz[8]); + + // AC + res.Init(1, 0, proba); + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + int ctx = this.TopNz[x] + this.LeftNz[y]; + res.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4))); + r += res.GetResidualCost(ctx); + this.TopNz[x] = this.LeftNz[y] = (res.Last >= 0) ? 1 : 0; + } + } + + return r; + } + public short[] GetCostModeI4(byte[] modes) { int predsWidth = this.predsWidth; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 757babdc0..86f014424 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -63,17 +63,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; - private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; - private const int NumMbSegments = 4; - private const int NumBModes = 10; - - /// - /// The number of prediction modes. - /// - private const int NumPredModes = 4; - private const int MaxItersKMeans = 6; // Convergence is considered reached if dq < DqLimit @@ -281,10 +272,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int width = image.Width; int height = image.Height; - this.ConvertRgbToYuv(image); Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); + YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v); int yStride = width; int uvStride = (yStride + 1) >> 1; @@ -657,8 +648,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int alpha = 255 * (centers[n] - mid) / (max - min); int beta = 255 * (centers[n] - min) / (max - min); - dqm[n].Alpha = Clip(alpha, -127, 127); - dqm[n].Beta = Clip(beta, 0, 255); + dqm[n].Alpha = Numerics.Clamp(alpha, -127, 127); + dqm[n].Beta = Numerics.Clamp(beta, 0, 255); } } @@ -676,7 +667,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy double expn = 1.0d - (amp * dqm[i].Alpha); double c = Math.Pow(cBase, expn); int q = (int)(127.0d * (1.0d - c)); - dqm[i].Quant = Clip(q, 0, 127); + dqm[i].Quant = Numerics.Clamp(q, 0, 127); } // Purely indicative in the bitstream (except for the 1-segment case). @@ -691,13 +682,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.DqUvAc = this.DqUvAc * snsStrength / 100; // and make it safe. - this.DqUvAc = Clip(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); + this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); // We also boost the dc-uv-quant a little, based on sns-strength, since // U/V channels are quite more reactive to high quants (flat DC-blocks // tend to appear, and are unpleasant). this.DqUvDc = -4 * snsStrength / 100; - this.DqUvDc = Clip(this.DqUvDc, -15, 15); // 4bit-signed max allowed + this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed this.DqY1Dc = 0; this.DqY2Dc = 0; @@ -721,7 +712,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8SegmentInfo m = this.SegmentInfos[i]; // We focus on the quantization of AC coeffs. - int qstep = WebpLookupTables.AcTable[Clip(m.Quant, 0, 127)] >> 2; + int qstep = WebpLookupTables.AcTable[Numerics.Clamp(m.Quant, 0, 127)] >> 2; int baseStrength = this.FilterStrengthFromDelta(this.FilterHeader.Sharpness, qstep); // Segments with lower complexity ('beta') will be less filtered. @@ -799,14 +790,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy m.Y2 = new Vp8Matrix(); m.Uv = new Vp8Matrix(); - m.Y1.Q[0] = WebpLookupTables.DcTable[Clip(q, 0, 127)]; - m.Y1.Q[1] = WebpLookupTables.AcTable[Clip(q, 0, 127)]; + m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q, 0, 127)]; + m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; - m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Clip(q, 0, 127)] * 2); - m.Y2.Q[1] = WebpLookupTables.AcTable2[Clip(q, 0, 127)]; + m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q, 0, 127)] * 2); + m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q, 0, 127)]; - m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.DqUvDc, 0, 117)]; - m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)]; + m.Uv.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqUvDc, 0, 117)]; + m.Uv.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q + this.DqUvAc, 0, 127)]; int qi4 = m.Y1.Expand(0); int qi16 = m.Y2.Expand(1); @@ -884,16 +875,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.MakeChroma8Preds(); // TODO: disabled picking best mode because its still bugged. - // if (rdOpt > Vp8RdLevel.RdOptNone) - if (false) + /* if (rdOpt > Vp8RdLevel.RdOptNone) { - this.PickBestIntra16(it, ref rd); + QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); if (this.method >= 2) { - this.PickBestIntra4(it, ref rd); + QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } - this.PickBestUv(it, ref rd); + QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); } else { @@ -901,8 +891,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - this.RefineUsingDistortion(it, rd, this.method >= 2, this.method >= 1); + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); } + */ + + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); @@ -910,406 +903,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return isSkipped; } - private void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd) - { - const int numBlocks = 16; - Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI16; - int tlambda = dqm.TLambda; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - var rdTmp = new Vp8ModeScore(); - Vp8ModeScore rdCur = rdTmp; - Vp8ModeScore rdBest = rd; - int mode; - bool isFlat = IsFlatSource16(src); - rd.ModeI16 = -1; - for (mode = 0; mode < NumPredModes; ++mode) - { - // scratch buffer. - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - rdCur.ModeI16 = mode; - - // Reconstruct. - rdCur.Nz = (uint)this.ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); - - // Measure RD-score. - rdCur.D = Vp8Sse16X16(src, tmpDst); - rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16X16(src, tmpDst, this.weightY)) : 0; - rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; - rdCur.R = this.GetCostLuma16(it, rdCur); - - if (isFlat) - { - // Refine the first impression (which was in pixel space). - isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); - if (isFlat) - { - // Block is very flat. We put emphasis on the distortion being very low! - rdCur.D *= 2; - rdCur.SD *= 2; - } - } - - // Since we always examine Intra16 first, we can overwrite *rd directly. - rdCur.SetRdScore(lambda); - - if (mode == 0 || rdCur.Score < rdBest.Score) - { - Vp8ModeScore tmp = rdCur; - rdCur = rdBest; - rdBest = tmp; - it.SwapOut(); - } - } - - if (rdBest != rd) - { - rd = rdBest; - } - - // Finalize score for mode decision. - rd.SetRdScore(dqm.LambdaMode); - it.SetIntra16Mode(rd.ModeI16); - - // We have a blocky macroblock (only DCs are non-zero) with fairly high - // distortion, record max delta so we can later adjust the minimal filtering - // strength needed to smooth these blocks out. - if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) - { - dqm.StoreMaxDelta(rd.YDcLevels); - } - } - - private bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd) - { - Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI4; - int tlambda = dqm.TLambda; - Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - int totalHeaderBits = 0; - var rdBest = new Vp8ModeScore(); - - if (this.maxI4HeaderBits == 0) - { - return false; - } - - rdBest.InitScore(); - rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) - rdBest.SetRdScore(dqm.LambdaMode); - it.StartI4(); - do - { - int numBlocks = 1; - var rdi4 = new Vp8ModeScore(); - int mode; - int bestMode = -1; - Span src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]); - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - Span bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]); - Span tmpDst = it.Scratch.AsSpan(); - tmpDst.Fill(0); - - rdi4.InitScore(); - it.MakeIntra4Preds(); - for (mode = 0; mode < NumBModes; ++mode) - { - var rdTmp = new Vp8ModeScore(); - short[] tmpLevels = new short[16]; - - // Reconstruct. - rdTmp.Nz = (uint)this.ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); - - // Compute RD-score. - rdTmp.D = Vp8Sse4X4(src, tmpDst); - rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4X4(src, tmpDst, this.weightY)) : 0; - rdTmp.H = modeCosts[mode]; - - // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. - if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) - { - rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; - } - else - { - rdTmp.R = 0; - } - - // early-out check. - rdTmp.SetRdScore(lambda); - if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) - { - continue; - } - - // finish computing score. - rdTmp.R += it.GetCostLuma4(tmpLevels, this.Proba); - rdTmp.SetRdScore(lambda); - - if (bestMode < 0 || rdTmp.Score < rdi4.Score) - { - rdi4.CopyScore(rdTmp); - bestMode = mode; - Span tmp = tmpDst; - tmpDst = bestBlock; - bestBlock = tmp; - tmpLevels.AsSpan().CopyTo(rdBest.YAcLevels); - } - } - - rdi4.SetRdScore(dqm.LambdaMode); - rdBest.AddScore(rdi4); - if (rdBest.Score >= rd.Score) - { - return false; - } - - totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; - if (totalHeaderBits > this.maxI4HeaderBits) - { - return false; - } - - // Copy selected samples to the right place. - Vp8Copy4X4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4])); - - rd.ModesI4[it.I4] = (byte)bestMode; - it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; - } - while (it.RotateI4(bestBlocks)); - - // Finalize state. - rd.CopyScore(rdBest); - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); - - // Select intra4x4 over intra16x16. - return true; - } - - private void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd) - { - const int numBlocks = 8; - Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaUv; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); - Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); - Span dst = dst0; - var rdBest = new Vp8ModeScore(); - int mode; - - rd.ModeUv = -1; - rdBest.InitScore(); - for (mode = 0; mode < NumPredModes; ++mode) - { - var rdUv = new Vp8ModeScore(); - - // Reconstruct - rdUv.Nz = (uint)this.ReconstructUv(it, dqm, rdUv, tmpDst, mode); - - // Compute RD-score - rdUv.D = Vp8Sse16X8(src, tmpDst); - rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. - rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; - rdUv.R = it.GetCostUv(rdUv, this.Proba); - if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) - { - rdUv.R += WebpConstants.FlatnessPenality * numBlocks; - } - - rdUv.SetRdScore(lambda); - if (mode == 0 || rdUv.Score < rdBest.Score) - { - rdBest.CopyScore(rdUv); - rd.ModeUv = mode; - rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); - Span tmp = dst; - dst = tmpDst; - tmpDst = tmp; - } - } - - it.SetIntraUvMode(rd.ModeUv); - rd.AddScore(rdBest); - if (dst != dst0) - { - // copy 16x8 block if needed. - Vp8Copy16X8(dst, dst0); - } - } - - // TODO: move to Vp8EncIterator - private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd) - { - var res = new Vp8Residual(); - int r = 0; - - // re-import the non-zero context. - it.NzToBytes(); - - // DC - res.Init(0, 1, this.Proba); - res.SetCoeffs(rd.YDcLevels); - r += res.GetResidualCost(it.TopNz[8] + it.LeftNz[8]); - - // AC - res.Init(1, 0, this.Proba); - for (int y = 0; y < 4; ++y) - { - for (int x = 0; x < 4; ++x) - { - int ctx = it.TopNz[x] + it.LeftNz[y]; - res.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4))); - r += res.GetResidualCost(ctx); - it.TopNz[x] = it.LeftNz[y] = (res.Last >= 0) ? 1 : 0; - } - } - - return r; - } - - // Refine intra16/intra4 sub-modes based on distortion only (not rate). - private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) - { - long bestScore = Vp8ModeScore.MaxCost; - int nz = 0; - int mode; - bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); - Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment]; - - // Some empiric constants, of approximate order of magnitude. - const int lambdaDi16 = 106; - const int lambdaDi4 = 11; - const int lambdaDuv = 120; - long scoreI4 = dqm.I4Penalty; - long i4BitSum = 0; - long bitLimit = tryBothModes - ? this.MbHeaderLimit - : Vp8ModeScore.MaxCost; // no early-out allowed. - - if (isI16) - { - int bestMode = -1; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - for (mode = 0; mode < NumPredModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - long score = (Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); - - if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) - { - continue; - } - - if (score < bestScore) - { - bestMode = mode; - bestScore = score; - } - } - - if (it.X == 0 || it.Y == 0) - { - // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. - if (IsFlatSource16(src)) - { - bestMode = (it.X == 0) ? 0 : 2; - tryBothModes = false; // Stick to i16. - } - } - - it.SetIntra16Mode(bestMode); - - // We'll reconstruct later, if i16 mode actually gets selected. - } - - // Next, evaluate Intra4. - if (tryBothModes || !isI16) - { - // We don't evaluate the rate here, but just account for it through a - // constant penalty (i4 mode usually needs more bits compared to i16). - isI16 = false; - it.StartI4(); - do - { - int bestI4Mode = -1; - long bestI4Score = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - - it.MakeIntra4Preds(); - for (mode = 0; mode < NumBModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); - if (score < bestI4Score) - { - bestI4Mode = mode; - bestI4Score = score; - } - } - - i4BitSum += modeCosts[bestI4Mode]; - rd.ModesI4[it.I4] = (byte)bestI4Mode; - scoreI4 += bestI4Score; - if (scoreI4 >= bestScore || i4BitSum > bitLimit) - { - // Intra4 won't be better than Intra16. Bail out and pick Intra16. - isI16 = true; - break; - } - else - { - // Reconstruct partial block inside YuvOut2 buffer - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; - } - } - while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); - } - - // Final reconstruction, depending on which mode is selected. - if (!isI16) - { - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - bestScore = scoreI4; - } - else - { - int intra16Mode = it.Preds[it.PredIdx]; - nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); - } - - // ... and UV! - if (refineUvMode) - { - int bestMode = -1; - long bestUvScore = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - for (mode = 0; mode < NumPredModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - long score = (Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); - if (score < bestUvScore) - { - bestMode = mode; - bestUvScore = score; - } - } - - it.SetIntraUvMode(bestMode); - } - - nz |= this.ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); - - rd.Nz = (uint)nz; - rd.Score = bestScore; - } - private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd) { int x, y, ch; @@ -1433,268 +1026,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.BytesToNz(); } - private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - int nz = 0; - int n; - short[] dcTmp = new short[16]; - short[] tmp = new short[16 * 16]; - Span tmpSpan = tmp.AsSpan(); - - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.FTransform2(src.Slice(WebpLookupTables.Vp8Scan[n]), reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); - } - - Vp8Encoding.FTransformWht(tmp, dcTmp); - nz |= QuantEnc.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; - - for (n = 0; n < 16; n += 2) - { - // Zero-out the first coeff, so that: a) nz is correct below, and - // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. - tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= QuantEnc.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; - } - - // Transform back. - LossyUtils.TransformWht(dcTmp, tmpSpan); - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), true); - } - - return nz; - } - - private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - short[] tmp = new short[16]; - Vp8Encoding.FTransform(src, reference, tmp); - int nz = QuantEnc.QuantizeBlock(tmp, levels, dqm.Y1); - Vp8Encoding.ITransform(reference, tmp, yuvOut, false); - - return nz; - } - - private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - int nz = 0; - int n; - short[] tmp = new short[8 * 16]; - - for (n = 0; n < 8; n += 2) - { - Vp8Encoding.FTransform2( - src.Slice(WebpLookupTables.Vp8ScanUv[n]), - reference.Slice(WebpLookupTables.Vp8ScanUv[n]), - tmp.AsSpan(n * 16, 16), - tmp.AsSpan((n + 1) * 16, 16)); - } - - QuantEnc.CorrectDcValues(it, dqm.Uv, tmp, rd); - - for (n = 0; n < 8; n += 2) - { - nz |= QuantEnc.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; - } - - for (n = 0; n < 8; n += 2) - { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), true); - } - - return nz << 16; - } - - /// - /// Converts the RGB values of the image to YUV. - /// - /// The pixel type of the image. - /// The image to convert. - private void ConvertRgbToYuv(Image image) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - int uvWidth = (width + 1) >> 1; - - // Temporary storage for accumulated R/G/B values during conversion to U/V. - using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); - using IMemoryOwner rgbaRow0Buffer = this.memoryAllocator.Allocate(width); - using IMemoryOwner rgbaRow1Buffer = this.memoryAllocator.Allocate(width); - Span tmpRgbSpan = tmpRgb.GetSpan(); - Span rgbaRow0 = rgbaRow0Buffer.GetSpan(); - Span rgbaRow1 = rgbaRow1Buffer.GetSpan(); - int uvRowIndex = 0; - int rowIndex; - bool rowsHaveAlpha = false; - for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) - { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); - PixelOperations.Instance.ToRgba32(this.configuration, rowSpan, rgbaRow0); - PixelOperations.Instance.ToRgba32(this.configuration, nextRowSpan, rgbaRow1); - - rowsHaveAlpha = YuvConversion.CheckNonOpaque(rgbaRow0) && YuvConversion.CheckNonOpaque(rgbaRow1); - - // Downsample U/V planes, two rows at a time. - if (!rowsHaveAlpha) - { - YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); - } - else - { - YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); - } - - YuvConversion.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); - uvRowIndex++; - - YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width); - YuvConversion.ConvertRgbaToY(rgbaRow1, this.Y.Slice((rowIndex + 1) * width), width); - } - - // Extra last row. - if ((height & 1) != 0) - { - if (!rowsHaveAlpha) - { - YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); - } - else - { - YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); - } - - YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width); - } - } - [MethodImpl(InliningOptions.ShortMethod)] private static int FinalAlphaValue(int alpha) { alpha = WebpConstants.MaxAlpha - alpha; - return Clip(alpha, 0, WebpConstants.MaxAlpha); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Clip(int v, int min, int max) => (v < min) ? min : (v > max) ? max : v; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Sse4X4(Span a, Span b) => GetSse(a, b, 4, 4); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetSse(Span a, Span b, int w, int h) - { - int count = 0; - int aOffset = 0; - int bOffset = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - int diff = a[aOffset + x] - b[bOffset + x]; - count += diff * diff; - } - - aOffset += WebpConstants.Bps; - bOffset += WebpConstants.Bps; - } - - return count; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Copy(Span src, Span dst, int w, int h) - { - for (int y = 0; y < h; ++y) - { - src.Slice(0, w).CopyTo(dst); - src = src.Slice(WebpConstants.Bps); - dst = dst.Slice(WebpConstants.Bps); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Disto16X16(Span a, Span b, Span w) - { - int d = 0; - for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) - { - for (int x = 0; x < 16; x += 4) - { - d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w); - } - } - - return d; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8Disto4X4(Span a, Span b, Span w) - { - int sum1 = LossyUtils.TTransform(a, w); - int sum2 = LossyUtils.TTransform(b, w); - return Math.Abs(sum2 - sum1) >> 5; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlat(Span levels, int numBlocks, int thresh) - { - int score = 0; - while (numBlocks-- > 0) - { - for (int i = 1; i < 16; ++i) - { - // omit DC, we're only interested in AC - score += (levels[i] != 0) ? 1 : 0; - if (score > thresh) - { - return false; - } - } - - levels = levels.Slice(16); - } - - return true; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlatSource16(Span src) - { - uint v = src[0] * 0x01010101u; - Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (int i = 0; i < 16; ++i) - { - if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || - !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) - { - return false; - } - - src = src.Slice(WebpConstants.Bps); - } - - return true; + return Numerics.Clamp(alpha, 0, WebpConstants.MaxAlpha); } /// @@ -1725,9 +1061,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return WebpLookupTables.LevelsFromDelta[sharpness, pos]; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; - [MethodImpl(InliningOptions.ShortMethod)] private static double GetPsnr(long mse, long size) => (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index fbc2996fc..68ef9416b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp.Lossy @@ -16,6 +18,75 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int YuvHalf = 1 << (YuvFix - 1); + /// + /// Converts the RGB values of the image to YUV. + /// + /// The pixel type of the image. + /// The image to convert. + /// The global configuration. + /// The memory allocator. + /// Span to store the luma component of the image. + /// Span to store the u component of the image. + /// Span to store the v component of the image. + public static void ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + int uvWidth = (width + 1) >> 1; + + // Temporary storage for accumulated R/G/B values during conversion to U/V. + using IMemoryOwner tmpRgb = memoryAllocator.Allocate(4 * uvWidth); + using IMemoryOwner rgbaRow0Buffer = memoryAllocator.Allocate(width); + using IMemoryOwner rgbaRow1Buffer = memoryAllocator.Allocate(width); + Span tmpRgbSpan = tmpRgb.GetSpan(); + Span rgbaRow0 = rgbaRow0Buffer.GetSpan(); + Span rgbaRow1 = rgbaRow1Buffer.GetSpan(); + int uvRowIndex = 0; + int rowIndex; + bool rowsHaveAlpha = false; + for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); + PixelOperations.Instance.ToRgba32(configuration, nextRowSpan, rgbaRow1); + + rowsHaveAlpha = YuvConversion.CheckNonOpaque(rgbaRow0) && YuvConversion.CheckNonOpaque(rgbaRow1); + + // Downsample U/V planes, two rows at a time. + if (!rowsHaveAlpha) + { + YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + } + else + { + YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + } + + YuvConversion.ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); + uvRowIndex++; + + YuvConversion.ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + YuvConversion.ConvertRgbaToY(rgbaRow1, y.Slice((rowIndex + 1) * width), width); + } + + // Extra last row. + if ((height & 1) != 0) + { + if (!rowsHaveAlpha) + { + YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + } + else + { + YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + } + + YuvConversion.ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + } + } + /// /// Checks if the pixel row is not opaque. /// diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index 88aa7728a..85b65e0a1 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -220,6 +220,10 @@ namespace SixLabors.ImageSharp.Formats.Webp public const int NumProbas = 11; + public const int NumPredModes = 4; + + public const int NumBModes = 10; + public const int NumCtx = 3; public const int MaxVariableLevel = 67; From 5ad068833fbe70ccb825ca4cc602eff7094eba1f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 27 Jun 2021 14:36:18 +0200 Subject: [PATCH 289/359] Fix issue in ImportBlock --- src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs | 7 +++++-- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 10 ++++------ src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs | 5 +---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index f1fb6c13a..4bdae0265 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -768,10 +768,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int srcIdx = 0; for (int i = 0; i < h; ++i) { + // memcpy(dst, src, w); src.Slice(srcIdx, w).CopyTo(dst.Slice(dstIdx)); if (w < size) { - dst.Slice(dstIdx, size - w).Fill(dst[dstIdx + w - 1]); + // memset(dst + w, dst[w - 1], size - w); + dst.Slice(dstIdx + w, size - w).Fill(dst[dstIdx + w - 1]); } dstIdx += WebpConstants.Bps; @@ -780,7 +782,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int i = h; i < size; ++i) { - dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst); + // memcpy(dst, dst - BPS, size); + dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst.Slice(dstIdx)); dstIdx += WebpConstants.Bps; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 86f014424..4544292f6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -609,7 +609,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8MacroBlockInfo mb = this.MbInfo[n]; int alpha = mb.Alpha; mb.Segment = map[alpha]; - mb.Alpha = centers[map[alpha]]; // for the record. + mb.Alpha = centers[map[alpha]]; } // TODO: add possibility for SmoothSegmentMap @@ -790,11 +790,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy m.Y2 = new Vp8Matrix(); m.Uv = new Vp8Matrix(); - m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q, 0, 127)]; + m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)]; m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; - m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q, 0, 127)] * 2); - m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q, 0, 127)]; + m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY2Dc, 0, 127)] * 2); + m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q + this.DqY2Ac, 0, 127)]; m.Uv.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqUvDc, 0, 117)]; m.Uv.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q + this.DqUvAc, 0, 127)]; @@ -803,8 +803,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int qi16 = m.Y2.Expand(1); int quv = m.Uv.Expand(2); - m.I4Penalty = 1000 * qi4 * qi4; - m.LambdaI16 = 3 * qi16 * qi16; m.LambdaI4 = (3 * qi4 * qi4) >> 7; m.LambdaUv = (3 * quv * quv) >> 6; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 7b001b72a..f9fd6602a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -106,9 +106,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return (sum + 8) >> 4; } - private int BIAS(int b) - { - return b << (WebpConstants.QFix - 8); - } + private int BIAS(int b) => b << (WebpConstants.QFix - 8); } } From 24fec9b4bc0c991e46e1dc50a0aef401079dc06a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 27 Jun 2021 16:43:30 +0200 Subject: [PATCH 290/359] Add encoding test pattern tests --- .../Formats/WebP/WebpEncoderTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 828fc0dcd..1741ea2c6 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.WebP; @@ -96,6 +97,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } + [Theory] + [WithTestPatternImages(187, 221, PixelTypes.Rgba32)] + [WithTestPatternImages(100, 118, PixelTypes.Rgba32)] + public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // Encoding lossy images with transparency is not yet supported, therefor the test image will be made opaque. + image.Mutate(img => img.MakeOpaque()); + var encoder = new WebpEncoder() { Lossy = true }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } + [Fact] public void Encode_Lossless_OneByOnePixel_Works() { From 5b6d4d82fbaa07bd4fb908810162d8710f829bd7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 28 Jun 2021 12:04:05 +0200 Subject: [PATCH 291/359] Fix convert to yuv: missed last uv row when height is uneven --- .../Formats/WebP/Lossy/YuvConversion.cs | 19 ++++++++++--------- .../Formats/WebP/WebpEncoderTests.cs | 6 ++---- tests/ImageSharp.Tests/TestImages.cs | 4 ++++ .../Images/Input/WebP/testpattern_opaque.png | 3 +++ .../Input/WebP/testpattern_opaque_small.png | 3 +++ 5 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 tests/Images/Input/WebP/testpattern_opaque.png create mode 100644 tests/Images/Input/WebP/testpattern_opaque_small.png diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 68ef9416b..f6bc14570 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -52,23 +52,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); PixelOperations.Instance.ToRgba32(configuration, nextRowSpan, rgbaRow1); - rowsHaveAlpha = YuvConversion.CheckNonOpaque(rgbaRow0) && YuvConversion.CheckNonOpaque(rgbaRow1); + rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); // Downsample U/V planes, two rows at a time. if (!rowsHaveAlpha) { - YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); } else { - YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); } - YuvConversion.ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); + ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; - YuvConversion.ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); - YuvConversion.ConvertRgbaToY(rgbaRow1, y.Slice((rowIndex + 1) * width), width); + ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + ConvertRgbaToY(rgbaRow1, y.Slice((rowIndex + 1) * width), width); } // Extra last row. @@ -76,14 +76,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { if (!rowsHaveAlpha) { - YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } else { - YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } - YuvConversion.ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 1741ea2c6..f7aaad700 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -98,15 +98,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithTestPatternImages(187, 221, PixelTypes.Rgba32)] - [WithTestPatternImages(100, 118, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - // Encoding lossy images with transparency is not yet supported, therefor the test image will be made opaque. - image.Mutate(img => img.MakeOpaque()); var encoder = new WebpEncoder() { Lossy = true }; image.VerifyEncoder(provider, "webp", string.Empty, encoder); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fb032ea86..072eeec0f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -504,6 +504,10 @@ namespace SixLabors.ImageSharp.Tests // Reference image as png public const string Peak = "WebP/peak.png"; + // Test pattern images for testing the encoder. + public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; + public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; + public static class Animated { public const string Animated1 = "WebP/animated-webp.webp"; diff --git a/tests/Images/Input/WebP/testpattern_opaque.png b/tests/Images/Input/WebP/testpattern_opaque.png new file mode 100644 index 000000000..4f1f3ea09 --- /dev/null +++ b/tests/Images/Input/WebP/testpattern_opaque.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b89449ae398c5b54a120b6b1e6b394e6d5cd58f0a55e5fb86f759fa12dcd325f +size 1983 diff --git a/tests/Images/Input/WebP/testpattern_opaque_small.png b/tests/Images/Input/WebP/testpattern_opaque_small.png new file mode 100644 index 000000000..62cdcf141 --- /dev/null +++ b/tests/Images/Input/WebP/testpattern_opaque_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81ef8da0aae89095da92ac82830e0f3de935d62248954e577bf4d573158ffd5f +size 35660 From 103b4402132d27cc981ce7cdfa469ba02be21ed0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 28 Jun 2021 14:02:31 +0200 Subject: [PATCH 292/359] Fix another issue with converting the last row to yuv --- src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs | 10 ++++++---- .../ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index f6bc14570..cf3f32e8e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -44,7 +44,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span rgbaRow1 = rgbaRow1Buffer.GetSpan(); int uvRowIndex = 0; int rowIndex; - bool rowsHaveAlpha = false; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { Span rowSpan = image.GetPixelRowSpan(rowIndex); @@ -52,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); PixelOperations.Instance.ToRgba32(configuration, nextRowSpan, rgbaRow1); - rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); + var rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); // Downsample U/V planes, two rows at a time. if (!rowsHaveAlpha) @@ -74,7 +73,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Extra last row. if ((height & 1) != 0) { - if (!rowsHaveAlpha) + Span rowSpan = image.GetPixelRowSpan(rowIndex); + PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); + ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + + if (!CheckNonOpaque(rgbaRow0)) { AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } @@ -83,7 +86,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); } - ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index f7aaad700..4c054d7e7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -4,7 +4,6 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.WebP; From e41a9252ebc74950d948de81346263b3a1b84426 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 28 Jun 2021 16:37:33 +0200 Subject: [PATCH 293/359] Use tolerant comparer for Encode_Lossy_WorksWithTestPattern --- tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 4c054d7e7..11ba41d01 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using Image image = provider.GetImage(); var encoder = new WebpEncoder() { Lossy = true }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder); + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } [Fact] From 8d7d68f62656e01a0e36b3277650425a511a832c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 29 Jun 2021 12:24:05 +0200 Subject: [PATCH 294/359] AnalyzeBestIntra4Mode for method >= 5 --- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 17 +++++- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 58 ++++++++++++++++++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 16 ++++- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index c5db7a568..d49653a05 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.Alpha = new uint[WebpConstants.NumLiteralCodes + 1]; this.Distance = new uint[WebpConstants.NumDistanceCodes]; - var literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); + int literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); this.Literal = new uint[literalSize + 1]; // 5 for literal, red, blue, alpha, distance. @@ -232,7 +232,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public double AddThresh(Vp8LHistogram b, double costThreshold) { double costInitial = -this.BitCost; - this.GetCombinedHistogramEntropy(b, costThreshold, costInitial, out var cost); + this.GetCombinedHistogramEntropy(b, costThreshold, costInitial, out double cost); return cost; } @@ -350,6 +350,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return alpha; } + public void Merge(Vp8LHistogram other) + { + if (this.maxValue > other.maxValue) + { + other.maxValue = this.maxValue; + } + + if (this.lastNonZero > other.lastNonZero) + { + other.lastNonZero = this.lastNonZero; + } + } + private void SetHistogramData(int[] distribution) { int maxValue = 0; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 4bdae0265..340263f7e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -18,9 +18,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public const int VOffEnc = 16 + 8; + private const int MaxIntra16Mode = 2; + + private const int MaxIntra4Mode = 2; + private const int MaxUvMode = 2; - private const int MaxIntra16Mode = 2; + private const int DefaultAlpha = -1; private readonly int mbw; @@ -373,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int maxMode = MaxIntra16Mode; int mode; - int bestAlpha = -1; + int bestAlpha = DefaultAlpha; int bestMode = 0; this.MakeLuma16Preds(); @@ -393,9 +397,57 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return bestAlpha; } + public int MbAnalyzeBestIntra4Mode(int bestAlpha) + { + byte[] modes = new byte[16]; + int maxMode = MaxIntra4Mode; + int i4Alpha; + var totalHisto = new Vp8LHistogram(); + int curHisto = 0; + this.StartI4(); + do + { + int mode; + int bestModeAlpha = DefaultAlpha; + var histos = new Vp8LHistogram[2]; + Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); + + this.MakeIntra4Preds(); + for (mode = 0; mode < maxMode; ++mode) + { + int alpha; + histos[curHisto] = new Vp8LHistogram(); + histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); + + alpha = histos[curHisto].GetAlpha(); + if (alpha > bestModeAlpha) + { + bestModeAlpha = alpha; + modes[this.I4] = (byte)mode; + + // Keep track of best histo so far. + curHisto ^= 1; + } + } + + // Accumulate best histogram. + histos[curHisto ^ 1].Merge(totalHisto); + } + while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. + + i4Alpha = totalHisto.GetAlpha(); + if (i4Alpha > bestAlpha) + { + this.SetIntra4Mode(modes); + bestAlpha = i4Alpha; + } + + return bestAlpha; + } + public int MbAnalyzeBestUvMode() { - int bestAlpha = -1; + int bestAlpha = DefaultAlpha; int smallestAlpha = 0; int bestMode = 0; int maxMode = MaxUvMode; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 4544292f6..9d1baafb9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -850,7 +850,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.SetSkip(false); // not skipped. it.SetSegment(0); // default segment, spec-wise. - int bestAlpha = this.method <= 1 ? it.FastMbAnalyze(this.quality) : it.MbAnalyzeBestIntra16Mode(); + int bestAlpha; + if (this.method <= 1) + { + bestAlpha = it.FastMbAnalyze(this.quality); + } + else + { + bestAlpha = it.MbAnalyzeBestIntra16Mode(); + if (this.method >= 5) + { + // We go and make a fast decision for intra4/intra16. + // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. + bestAlpha = it.MbAnalyzeBestIntra4Mode(bestAlpha); + } + } bestUvAlpha = it.MbAnalyzeBestUvMode(); From f873859a75d28afb72e36f2530d88e4757086656 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 29 Jun 2021 12:49:08 +0200 Subject: [PATCH 295/359] Split up Vp8 and Vp8L Histogram --- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 128 ----------------- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 12 +- .../Formats/WebP/Lossy/Vp8Histogram.cs | 132 ++++++++++++++++++ 3 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index d49653a05..ffd9d6cf3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -11,22 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { private const uint NonTrivialSym = 0xffffffff; - /// - /// Size of histogram used by CollectHistogram. - /// - private const int MaxCoeffThresh = 31; - - private int maxValue; - - private int lastNonZero; - - /// - /// Initializes a new instance of the class. - /// - public Vp8LHistogram() - { - } - /// /// Initializes a new instance of the class. /// @@ -317,114 +300,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return true; } - public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) - { - int j; - var distribution = new int[MaxCoeffThresh + 1]; - for (j = startBlock; j < endBlock; ++j) - { - var output = new short[16]; - - this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output); - - // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) - { - int v = Math.Abs(output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++distribution[clippedValue]; - } - } - - this.SetHistogramData(distribution); - } - - public int GetAlpha() - { - // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer - // values which happen to be mostly noise. This leaves the maximum precision - // for handling the useful small values which contribute most. - int maxValue = this.maxValue; - int lastNonZero = this.lastNonZero; - int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; - return alpha; - } - - public void Merge(Vp8LHistogram other) - { - if (this.maxValue > other.maxValue) - { - other.maxValue = this.maxValue; - } - - if (this.lastNonZero > other.lastNonZero) - { - other.lastNonZero = this.lastNonZero; - } - } - - private void SetHistogramData(int[] distribution) - { - int maxValue = 0; - int lastNonZero = 1; - for (int k = 0; k <= MaxCoeffThresh; ++k) - { - int value = distribution[k]; - if (value > 0) - { - if (value > maxValue) - { - maxValue = value; - } - - lastNonZero = k; - } - } - - this.maxValue = maxValue; - this.lastNonZero = lastNonZero; - } - - private void Vp8FTransform(Span src, Span reference, Span output) - { - int i; - var tmp = new int[16]; - for (i = 0; i < 4; ++i) - { - int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) - int d1 = src[1] - reference[1]; - int d2 = src[2] - reference[2]; - int d3 = src[3] - reference[3]; - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - - // Do not change the span in the last iteration. - if (i < 3) - { - src = src.Slice(WebpConstants.Bps); - reference = reference.Slice(WebpConstants.Bps); - } - } - - for (i = 0; i < 4; ++i) - { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); - } - } - private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) { if (this.IsUsed[0]) @@ -636,8 +511,5 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless output[i] = a[i] + b[i]; } } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) => (v > max) ? max : v; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 340263f7e..ac582fd42 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.MakeLuma16Preds(); for (mode = 0; mode < maxMode; ++mode) { - var histo = new Vp8LHistogram(); + var histo = new Vp8Histogram(); histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) @@ -402,21 +402,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte[] modes = new byte[16]; int maxMode = MaxIntra4Mode; int i4Alpha; - var totalHisto = new Vp8LHistogram(); + var totalHisto = new Vp8Histogram(); int curHisto = 0; this.StartI4(); do { int mode; int bestModeAlpha = DefaultAlpha; - var histos = new Vp8LHistogram[2]; + var histos = new Vp8Histogram[2]; Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); this.MakeIntra4Preds(); for (mode = 0; mode < maxMode; ++mode) { int alpha; - histos[curHisto] = new Vp8LHistogram(); + histos[curHisto] = new Vp8Histogram(); histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); alpha = histos[curHisto].GetAlpha(); @@ -456,7 +456,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.MakeChroma8Preds(); for (mode = 0; mode < maxMode; ++mode) { - var histo = new Vp8LHistogram(); + var histo = new Vp8Histogram(); histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs new file mode 100644 index 000000000..e569ab0d9 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -0,0 +1,132 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Webp; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8Histogram + { + /// + /// Size of histogram used by CollectHistogram. + /// + private const int MaxCoeffThresh = 31; + + private int maxValue; + + private int lastNonZero; + + public int GetAlpha() + { + // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer + // values which happen to be mostly noise. This leaves the maximum precision + // for handling the useful small values which contribute most. + int maxValue = this.maxValue; + int lastNonZero = this.lastNonZero; + int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; + return alpha; + } + + public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) + { + int j; + int[] distribution = new int[MaxCoeffThresh + 1]; + for (j = startBlock; j < endBlock; ++j) + { + short[] output = new short[16]; + + this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output); + + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + int v = Math.Abs(output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++distribution[clippedValue]; + } + } + + this.SetHistogramData(distribution); + } + + public void Merge(Vp8Histogram other) + { + if (this.maxValue > other.maxValue) + { + other.maxValue = this.maxValue; + } + + if (this.lastNonZero > other.lastNonZero) + { + other.lastNonZero = this.lastNonZero; + } + } + + private void SetHistogramData(int[] distribution) + { + int maxValue = 0; + int lastNonZero = 1; + for (int k = 0; k <= MaxCoeffThresh; ++k) + { + int value = distribution[k]; + if (value > 0) + { + if (value > maxValue) + { + maxValue = value; + } + + lastNonZero = k; + } + } + + this.maxValue = maxValue; + this.lastNonZero = lastNonZero; + } + + private void Vp8FTransform(Span src, Span reference, Span output) + { + int i; + int[] tmp = new int[16]; + for (i = 0; i < 4; ++i) + { + int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) + int d1 = src[1] - reference[1]; + int d2 = src[2] - reference[2]; + int d3 = src[3] - reference[3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + + // Do not change the span in the last iteration. + if (i < 3) + { + src = src.Slice(WebpConstants.Bps); + reference = reference.Slice(WebpConstants.Bps); + } + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipMax(int v, int max) => (v > max) ? max : v; + } +} From ea911998ef0ceebb944f2583ece4f5f1a659916b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Jun 2021 13:29:21 +0200 Subject: [PATCH 296/359] Use Readonly Span for bgra data --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 42 ++++++++++---- .../Formats/WebP/Lossless/LosslessUtils.cs | 4 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 14 ++--- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 56 ++++++++++++------- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 4 +- 5 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index e872eeb63..99dbf2e44 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static Vp8LBackwardRefs GetBackwardReferences( int width, int height, - Span bgra, + ReadOnlySpan bgra, int quality, int lz77TypesToTry, ref int cacheBits, @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The local color cache is also disabled for the lower (smaller then 25) quality. /// /// Best cache size. - private static int CalculateBestCacheSize(Span bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) + private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits; if (cacheBitsMax == 0) @@ -227,7 +227,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return bestCacheBits; } - private static void BackwardReferencesTraceBackwards(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refsSrc, Vp8LBackwardRefs refsDst) + private static void BackwardReferencesTraceBackwards( + int xSize, + int ySize, + ReadOnlySpan bgra, + int cacheBits, + Vp8LHashChain hashChain, + Vp8LBackwardRefs refsSrc, + Vp8LBackwardRefs refsDst) { int distArraySize = xSize * ySize; ushort[] distArray = new ushort[distArraySize]; @@ -238,7 +245,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); } - private static void BackwardReferencesHashChainDistanceOnly(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, ushort[] distArray) + private static void BackwardReferencesHashChainDistanceOnly( + int xSize, + int ySize, + ReadOnlySpan bgra, + int cacheBits, + Vp8LHashChain hashChain, + Vp8LBackwardRefs refs, + ushort[] distArray) { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; @@ -346,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return chosenPathSize; } - private static void BackwardReferencesHashChainFollowChosenPath(Span bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) + private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) { bool useColorCache = cacheBits > 0; var colorCache = new ColorCache(); @@ -402,7 +416,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - private static void AddSingleLiteralWithCostModel(Span bgra, ColorCache colorCache, CostModel costModel, int idx, bool useColorCache, float prevCost, float[] cost, ushort[] distArray) + private static void AddSingleLiteralWithCostModel( + ReadOnlySpan bgra, + ColorCache colorCache, + CostModel costModel, + int idx, + bool useColorCache, + float prevCost, + float[] cost, + ushort[] distArray) { double costVal = prevCost; uint color = bgra[idx]; @@ -430,7 +452,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - private static void BackwardReferencesLz77(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int iLastCheck = -1; bool useColorCache = cacheBits > 0; @@ -508,7 +530,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Compute an LZ77 by forcing matches to happen within a given distance cost. /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. /// - private static void BackwardReferencesLz77Box(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int pixelCount = xSize * ySize; int[] windowOffsets = new int[WindowOffsetsSizeMax]; @@ -686,7 +708,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); } - private static void BackwardReferencesRle(int xSize, int ySize, Span bgra, int cacheBits, Vp8LBackwardRefs refs) + private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) { int pixelCount = xSize * ySize; bool useColorCache = cacheBits > 0; @@ -739,7 +761,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Update (in-place) backward references for the specified cacheBits. /// - private static void BackwardRefsWithLocalCache(Span bgra, int cacheBits, Vp8LBackwardRefs refs) + private static void BackwardRefsWithLocalCache(ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) { int pixelIndex = 0; var colorCache = new ColorCache(); diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index e14a85ed9..d6ba6e481 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// is bestLenMatch, and the index itself otherwise. /// If no two elements are the same, it returns maxLimit. /// - public static int FindMatchLength(Span array1, Span array2, int bestLenMatch, int maxLimit) + public static int FindMatchLength(ReadOnlySpan array1, ReadOnlySpan array2, int bestLenMatch, int maxLimit) { // Before 'expensive' linear match, check if the two arrays match at the // current best length index. @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public static int VectorMismatch(Span array1, Span array2, int length) + public static int VectorMismatch(ReadOnlySpan array1, ReadOnlySpan array2, int length) { int matchLen = 0; diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 75c182e4d..97d96e713 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -36,8 +36,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int width, int height, int bits, - Span argb, - Span argbScratch, + Span bgra, + Span bgraScratch, Span image, int nearLosslessQuality, bool exact, @@ -66,8 +66,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless tileY, bits, histo, - argbScratch, - argb, + bgraScratch, + bgra, maxQuantization, exact, usedSubtractGreen, @@ -82,8 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless height, bits, image, - argbScratch, - argb, + bgraScratch, + bgra, maxQuantization, exact, usedSubtractGreen); @@ -559,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (maxQuantization > 1) { // Compute max_diffs for the lower row now, because that needs the - // contents of argb for the current row, which we will overwrite with + // contents of bgra for the current row, which we will overwrite with // residuals before proceeding with the next row. Span tmp8 = currentMaxDiffs; currentMaxDiffs = lowerMaxDiffs; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index a486e9557..04e4546d5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } /// - /// Gets memory for the transformed image data. + /// Gets the memory for the encoded output image data. /// public IMemoryOwner Bgra { get; } @@ -236,19 +236,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int height = image.Height; // Convert image pixels to bgra array. + this.ConvertPixelsToBgra(image, width, height); Span bgra = this.Bgra.GetSpan(); - using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); - Span bgraRow = bgraRowBuffer.GetSpan(); - int idx = 0; - for (int y = 0; y < height; y++) - { - Span rowSpan = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); - for (int x = 0; x < width; x++) - { - bgra[idx++] = bgraRow[x].PackedValue; - } - } // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); @@ -337,6 +326,31 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.BitWriterSwap(ref bitWriterBest, ref this.bitWriter); } + /// + /// Converts the pixels of the image to bgra. + /// + /// The type of the pixels. + /// The image to convert. + /// The width of the image. + /// The height of the image. + private void ConvertPixelsToBgra(Image image, int width, int height) + where TPixel : unmanaged, IPixel + { + Span bgra = this.Bgra.GetSpan(); + using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); + Span bgraRow = bgraRowBuffer.GetSpan(); + int idx = 0; + for (int y = 0; y < height; y++) + { + Span rowSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); + for (int x = 0; x < width; x++) + { + bgra[idx++] = bgraRow[x].PackedValue; + } + } + } + /// /// Analyzes the image and decides which transforms should be used. /// @@ -344,7 +358,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The image width. /// The image height. /// Indicates if red and blue are always zero. - private CrunchConfig[] EncoderAnalyze(Span bgra, int width, int height, out bool redAndBlueAlwaysZero) + private CrunchConfig[] EncoderAnalyze(ReadOnlySpan bgra, int width, int height, out bool redAndBlueAlwaysZero) { // Check if we only deal with a small number of colors and should use a palette. bool usePalette = this.AnalyzeAndCreatePalette(bgra, width, height); @@ -407,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) + private void EncodeImage(ReadOnlySpan bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); ushort[] histogramSymbols = new ushort[histogramImageXySize]; @@ -929,7 +943,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The transformation bits. /// Indicates if red and blue are always zero. /// The entropy mode to use. - private EntropyIx AnalyzeEntropy(Span bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) + private EntropyIx AnalyzeEntropy(ReadOnlySpan bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) { if (usePalette && paletteSize <= 16) { @@ -942,10 +956,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); Span histo = histoBuffer.Memory.Span; uint pixPrev = bgra[0]; // Skip the first pixel. - Span prevRow = null; + ReadOnlySpan prevRow = null; for (int y = 0; y < height; y++) { - Span currentRow = bgra.Slice(y * width, width); + ReadOnlySpan currentRow = bgra.Slice(y * width, width); for (int x = 0; x < width; x++) { uint pix = currentRow[x]; @@ -1087,7 +1101,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The image width. /// The image height. /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(Span bgra, int width, int height) + private bool AnalyzeAndCreatePalette(ReadOnlySpan bgra, int width, int height) { Span palette = this.Palette.Memory.Span; this.PaletteSize = this.GetColorPalette(bgra, width, height, palette); @@ -1117,12 +1131,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The image height. /// The span to store the palette into. /// The number of palette entries. - private int GetColorPalette(Span bgra, int width, int height, Span palette) + private int GetColorPalette(ReadOnlySpan bgra, int width, int height, Span palette) { var colors = new HashSet(); for (int y = 0; y < height; y++) { - Span bgraRow = bgra.Slice(y * width, width); + ReadOnlySpan bgraRow = bgra.Slice(y * width, width); for (int x = 0; x < width; x++) { colors.Add(bgraRow[x]); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index f5762b6f8..0dafe8e9e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// public int Size { get; } - public void Fill(MemoryAllocator memoryAllocator, Span bgra, int quality, int xSize, int ySize) + public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int quality, int xSize, int ySize) { int size = xSize * ySize; int iterMax = GetMaxItersForQuality(quality); @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// An Span with two pixels. /// The hash. [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetPixPairHash64(Span bgra) + private static uint GetPixPairHash64(ReadOnlySpan bgra) { uint key = bgra[1] * HashMultiplierHi; key += bgra[0] * HashMultiplierLo; From 1fe943a9fa53c39520beed2c60dd61bba20d6c9f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Jun 2021 14:08:30 +0200 Subject: [PATCH 297/359] Use separate buffer for encoded image data and input bgra data --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 04e4546d5..739529831 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -80,6 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.method = Numerics.Clamp(method, 0, 6); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); + this.EncodedData = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; this.HashChain = new Vp8LHashChain(pixelCount); @@ -96,10 +97,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } /// - /// Gets the memory for the encoded output image data. + /// Gets the memory for the image data as packed bgra values. /// public IMemoryOwner Bgra { get; } + /// + /// Gets the memory for the encoded output image data. + /// + public IMemoryOwner EncodedData { get; } + /// /// Gets or sets the scratch memory for bgra rows used for predictions. /// @@ -237,7 +243,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Convert image pixels to bgra array. this.ConvertPixelsToBgra(image, width, height); - Span bgra = this.Bgra.GetSpan(); + ReadOnlySpan bgra = this.Bgra.GetSpan(); + Span encodedData = this.EncodedData.GetSpan(); // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); @@ -248,6 +255,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bool isFirstConfig = true; foreach (CrunchConfig crunchConfig in crunchConfigs) { + bgra.CopyTo(encodedData); bool useCache = true; this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; @@ -297,15 +305,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Encode and write the transformed image. this.EncodeImage( - bgra, - this.HashChain, - this.Refs, this.CurrentWidth, height, useCache, crunchConfig, - this.CacheBits, - this.HistoBits); + this.CacheBits); // If we are better than what we already have. if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) @@ -421,9 +425,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(ReadOnlySpan bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) + private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits) { - int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); + // bgra data with transformations applied. + Span bgra = this.EncodedData.GetSpan(); + int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits); ushort[] histogramSymbols = new ushort[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) @@ -444,7 +450,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Calculate backward references from BGRA image. - hashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); + this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; @@ -458,13 +464,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.quality, subConfig.Lz77, ref cacheBits, - hashChain, - refsArray[0], - refsArray[1]); // TODO : Pass do not cache + this.HashChain, + this.Refs[0], + this.Refs[1]); // Keep the best references aside and use the other element from the first // two as a temporary for later usage. - Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; + Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; this.bitWriter.Reset(bwInit); var tmpHisto = new Vp8LHistogram(cacheBits); @@ -475,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, this.HistoBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); // Create Huffman bit lengths and codes for each histogram image. int histogramImageSize = histogramImage.Count; @@ -517,8 +523,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - this.bitWriter.PutBits((uint)(histogramBits - 2), 3); - this.EncodeImageNoHuffman(histogramBgra, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), this.quality); + this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3); + this.EncodeImageNoHuffman( + histogramBgra, + this.HashChain, + refsTmp, + this.Refs[2], + LosslessUtils.SubSampleSize(width, this.HistoBits), + LosslessUtils.SubSampleSize(height, this.HistoBits), + this.quality); } // Store Huffman codes. @@ -547,12 +560,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Store actual literals. - this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); + this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes); // Keep track of the smallest image so far. if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) { - // TODO: This was done in the reference by swapping references, this will be slower + // TODO: This was done in the reference by swapping references, this will be slower. bitWriterBest = this.bitWriter.Clone(); } @@ -589,7 +602,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); - LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan()); + LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); } private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) @@ -600,7 +613,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - PredictorEncoder.ResidualImage(width, height, predBits, this.Bgra.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); + PredictorEncoder.ResidualImage(width, height, predBits, this.EncodedData.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); @@ -615,7 +628,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan()); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); @@ -1161,9 +1174,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void MapImageFromPalette(int width, int height) { - Span src = this.Bgra.GetSpan(); + Span src = this.EncodedData.GetSpan(); int srcStride = this.CurrentWidth; - Span dst = this.Bgra.GetSpan(); // Applying the palette will be done in place. + Span dst = this.EncodedData.GetSpan(); // Applying the palette will be done in place. Span palette = this.Palette.GetSpan(); int paletteSize = this.PaletteSize; int xBits; @@ -1698,6 +1711,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public void Dispose() { this.Bgra.Dispose(); + this.EncodedData.Dispose(); this.BgraScratch.Dispose(); this.Palette.Dispose(); this.TransformData.Dispose(); From dd0242fe49c85db407a122bca6b0d654a983c975 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Jun 2021 15:53:48 +0200 Subject: [PATCH 298/359] Additional tests --- .../Formats/WebP/Lossy/Vp8Histogram.cs | 9 ++ .../Formats/WebP/Lossy/YuvConversion.cs | 2 +- .../Formats/WebP/WebpEncoderCore.cs | 4 +- .../Formats/WebP/DominantCostRangeTests.cs | 79 +++++++++++ ...ensionsTest.cs => ImageExtensionsTests.cs} | 8 +- .../Formats/WebP/LosslessUtilsTests.cs | 2 +- .../Formats/WebP/PredictorEncoderTests.cs | 10 +- .../Formats/WebP/Vp8HistogramTests.cs | 115 +++++++++++++++ .../Formats/WebP/Vp8ModeScoreTests.cs | 94 +++++++++++++ .../Formats/WebP/WebpEncoderTests.cs | 83 ++++++++--- .../Formats/WebP/YuvConversionTests.cs | 131 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 6 + tests/Images/Input/WebP/flag_of_germany.png | 3 + tests/Images/Input/WebP/yuv_test.png | 3 + 14 files changed, 516 insertions(+), 33 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs rename tests/ImageSharp.Tests/Formats/WebP/{ImageExtensionsTest.cs => ImageExtensionsTests.cs} (97%) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs create mode 100644 tests/Images/Input/WebP/flag_of_germany.png create mode 100644 tests/Images/Input/WebP/yuv_test.png diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs index e569ab0d9..cadd8a2c7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -18,6 +18,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int lastNonZero; + /// + /// Initializes a new instance of the class. + /// + public Vp8Histogram() + { + this.maxValue = 0; + this.lastNonZero = 1; + } + public int GetAlpha() { // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index cf3f32e8e..c49d2048c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); PixelOperations.Instance.ToRgba32(configuration, nextRowSpan, rgbaRow1); - var rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); + bool rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); // Downsample U/V planes, two rows at a time. if (!rowsHaveAlpha) diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 4c405b262..9fe477d0c 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -84,12 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.lossy) { - var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses); + using var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses); enc.Encode(image, stream); } else { - var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method); + using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method); enc.Encode(image, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs new file mode 100644 index 000000000..417b9fed5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Webp.Lossless; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class DominantCostRangeTests + { + [Fact] + public void DominantCost_Constructor() + { + var dominantCostRange = new DominantCostRange(); + Assert.Equal(0, dominantCostRange.LiteralMax); + Assert.Equal(double.MaxValue, dominantCostRange.LiteralMin); + Assert.Equal(0, dominantCostRange.RedMax); + Assert.Equal(double.MaxValue, dominantCostRange.RedMin); + Assert.Equal(0, dominantCostRange.BlueMax); + Assert.Equal(double.MaxValue, dominantCostRange.BlueMin); + } + + [Fact] + public void UpdateDominantCostRange_Works() + { + // arrange + var dominantCostRange = new DominantCostRange(); + var histogram = new Vp8LHistogram(10) + { + LiteralCost = 1.0d, + RedCost = 2.0d, + BlueCost = 3.0d + }; + + // act + dominantCostRange.UpdateDominantCostRange(histogram); + + // assert + Assert.Equal(1.0d, dominantCostRange.LiteralMax); + Assert.Equal(1.0d, dominantCostRange.LiteralMin); + Assert.Equal(2.0d, dominantCostRange.RedMax); + Assert.Equal(2.0d, dominantCostRange.RedMin); + Assert.Equal(3.0d, dominantCostRange.BlueMax); + Assert.Equal(3.0d, dominantCostRange.BlueMin); + } + + [Theory] + [InlineData(3, 19)] + [InlineData(4, 34)] + public void GetHistoBinIndex_Works(int partitions, int expectedIndex) + { + // arrange + var dominantCostRange = new DominantCostRange() + { + BlueMax = 253.4625, + BlueMin = 109.0, + LiteralMax = 285.0, + LiteralMin = 133.0, + RedMax = 191.0, + RedMin = 109.0 + }; + var histogram = new Vp8LHistogram(6) + { + LiteralCost = 247.0d, + RedCost = 112.0d, + BlueCost = 202.0d, + BitCost = 733.0d + }; + dominantCostRange.UpdateDominantCostRange(histogram); + + // act + int binIndex = dominantCostRange.GetHistoBinIndex(histogram, partitions); + + // assert + Assert.Equal(expectedIndex, binIndex); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs similarity index 97% rename from tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs rename to tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs index 7559bafc2..31fc1919c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs @@ -11,11 +11,11 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] - public class ImageExtensionsTest + public class ImageExtensionsTests { private readonly Configuration configuration; - public ImageExtensionsTest() + public ImageExtensionsTests() { this.configuration = new Configuration(); this.configuration.AddWebp(); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void SaveAsWebp_Path() { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); using (var image = new Image(this.configuration, 10, 10)) @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public async Task SaveAsWebpAsync_Path() { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); using (var image = new Image(this.configuration, 10, 10)) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 9074c06ad..be7bc27d3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class LosslessUtilsTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 421015d1f..e63ca2fec 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities; #endif using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class PredictorEncoderTests @@ -82,14 +82,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP }; // Convert image pixels to bgra array. - var imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Peak)); + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Peak)); using var image = Image.Load(imgBytes); uint[] bgra = ToBgra(image); int colorTransformBits = 3; int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - var transformData = new uint[transformWidth * transformHeight]; + uint[] transformData = new uint[transformWidth * transformHeight]; // act PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); @@ -111,14 +111,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP }; // Convert image pixels to bgra array. - var imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Lossless.BikeSmall)); + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Lossless.BikeSmall)); using var image = Image.Load(imgBytes, new WebpDecoder()); uint[] bgra = ToBgra(image); int colorTransformBits = 4; int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - var transformData = new uint[transformWidth * transformHeight]; + uint[] transformData = new uint[transformWidth * transformHeight]; // act PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs new file mode 100644 index 000000000..d4e1f69e0 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.WebP.Lossy; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class Vp8HistogramTests + { + public static IEnumerable Data + { + get + { + var result = new List(); + result.Add(new object[] + { + new byte[] + { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204 + }, + new byte[] + { + 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, + 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, 127, 128, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 128, 127, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, + 129, 128, 129, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, + 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 129, 129, + 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + } + }); + return result; + } + } + + [Fact] + public void GetAlpha_WithEmptyHistogram_Works() + { + // arrange + var histogram = new Vp8Histogram(); + + // act + int alpha = histogram.GetAlpha(); + + // assert + Assert.Equal(0, alpha); + } + + [Theory] + [MemberData(nameof(Data))] + public void GetAlpha_Works(byte[] reference, byte[] pred) + { + // arrange + var histogram = new Vp8Histogram(); + histogram.CollectHistogram(reference, pred, 0, 1); + + // act + int alpha = histogram.GetAlpha(); + + // assert + Assert.Equal(1054, alpha); + } + + [Theory] + [MemberData(nameof(Data))] + public void Merge_Works(byte[] reference, byte[] pred) + { + // arrange + var histogram1 = new Vp8Histogram(); + histogram1.CollectHistogram(reference, pred, 0, 1); + var histogram2 = new Vp8Histogram(); + histogram1.Merge(histogram2); + + // act + int alpha = histogram2.GetAlpha(); + + // assert + Assert.Equal(1054, alpha); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs new file mode 100644 index 000000000..d3b11bdb5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Webp.Lossy; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class Vp8ModeScoreTests + { + [Fact] + public void InitScore_Works() + { + var score = new Vp8ModeScore(); + score.InitScore(); + Assert.Equal(0, score.D); + Assert.Equal(0, score.SD); + Assert.Equal(0, score.R); + Assert.Equal(0, score.H); + Assert.Equal(0u, score.Nz); + Assert.Equal(Vp8ModeScore.MaxCost, score.Score); + } + + [Fact] + public void CopyScore_Works() + { + // arrange + var score1 = new Vp8ModeScore + { + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; + var score2 = new Vp8ModeScore(); + score2.InitScore(); + + // act + score2.CopyScore(score1); + + // assert + Assert.Equal(score1.D, score2.D); + Assert.Equal(score1.SD, score2.SD); + Assert.Equal(score1.R, score2.R); + Assert.Equal(score1.H, score2.H); + Assert.Equal(score1.Nz, score2.Nz); + Assert.Equal(score1.Score, score2.Score); + } + + [Fact] + public void AddScore_Works() + { + // arrange + var score1 = new Vp8ModeScore + { + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; + var score2 = new Vp8ModeScore + { + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; + + // act + score2.AddScore(score1); + + // assert + Assert.Equal(4, score2.D); + Assert.Equal(14, score2.SD); + Assert.Equal(12, score2.R); + Assert.Equal(6, score2.H); + Assert.Equal(1u, score2.Nz); + Assert.Equal(246, score2.Score); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 11ba41d01..d0b2c73a1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -13,6 +13,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpEncoderTests { + [Theory] + [WithFile(Flag, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] + public void Encode_Lossless_WithPalette_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = false, + Quality = 100, + Method = 6 + }; + + using Image image = provider.GetImage(); + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } + [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] @@ -32,25 +50,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] - public void Encode_Lossless_WithDifferentMethods_Works(TestImageProvider provider, int method) + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] + public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() { Lossy = false, Method = method, - Quality = 75 + Quality = quality }; using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossless", "_m", method); + string testOutputDetails = string.Concat("lossless", "_m", method, "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } @@ -73,17 +98,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] - public void Encode_Lossy_WithDifferentMethods_Works(TestImageProvider provider, int method) + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] + public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, int method, int quality) where TPixel : unmanaged, IPixel { - int quality = 75; var encoder = new WebpEncoder() { Lossy = true, @@ -92,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossy", "_m", method); + string testOutputDetails = string.Concat("lossy", "_m", method, "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } @@ -108,6 +139,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] + public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + var encoder = new WebpEncoder() { Lossy = false }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } + [Fact] public void Encode_Lossless_OneByOnePixel_Works() { diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs new file mode 100644 index 000000000..211db14a9 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Webp.Lossy; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class YuvConversionTests + { + [Theory] + [WithFile(TestImages.WebP.Yuv, PixelTypes.Rgba32)] + public void ConvertRgbToYuv_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + Configuration config = image.GetConfiguration(); + MemoryAllocator memoryAllocator = config.MemoryAllocator; + int pixels = image.Width * image.Height; + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(pixels / 2); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(pixels / 2); + Span y = yBuffer.GetSpan(); + Span u = uBuffer.GetSpan(); + Span v = vBuffer.GetSpan(); + byte[] expectedY = + { + 82, 82, 82, 82, 128, 135, 134, 129, 167, 179, 176, 172, 192, 201, 200, 204, 188, 172, 175, 177, 168, + 151, 154, 154, 153, 152, 151, 151, 152, 160, 160, 160, 160, 82, 82, 82, 82, 140, 137, 135, 116, 174, + 183, 176, 162, 196, 199, 199, 210, 188, 166, 176, 181, 170, 145, 155, 154, 153, 154, 151, 151, 150, + 162, 159, 160, 160, 82, 82, 83, 82, 142, 139, 137, 117, 176, 184, 177, 164, 195, 199, 198, 210, 188, + 165, 175, 180, 169, 145, 155, 154, 153, 154, 152, 151, 150, 163, 160, 160, 160, 82, 82, 82, 82, 124, + 122, 120, 101, 161, 171, 165, 151, 197, 209, 208, 210, 197, 174, 183, 189, 175, 148, 158, 158, 155, + 151, 148, 148, 147, 159, 156, 156, 159, 128, 140, 142, 124, 189, 185, 183, 167, 201, 199, 198, 209, + 179, 165, 171, 179, 160, 145, 151, 152, 151, 154, 152, 151, 153, 164, 160, 160, 160, 170, 170, 170, + 169, 135, 137, 139, 122, 185, 182, 180, 165, 201, 200, 199, 210, 180, 166, 173, 180, 162, 145, 153, + 153, 151, 154, 151, 150, 152, 164, 160, 159, 159, 170, 170, 170, 170, 134, 135, 137, 120, 184, 180, + 177, 164, 200, 198, 196, 210, 181, 167, 174, 181, 163, 146, 155, 155, 153, 154, 152, 150, 152, 163, + 160, 159, 159, 167, 167, 167, 168, 129, 116, 117, 101, 167, 166, 164, 149, 205, 210, 209, 210, 191, + 177, 184, 191, 170, 149, 158, 159, 153, 151, 148, 146, 148, 159, 155, 155, 155, 170, 169, 170, 170, + 167, 174, 175, 161, 201, 201, 200, 204, 178, 173, 174, 185, 159, 148, 155, 158, 152, 152, 151, 150, + 153, 162, 159, 158, 160, 170, 169, 169, 168, 109, 122, 120, 129, 179, 183, 184, 171, 199, 200, 198, + 210, 172, 166, 170, 179, 155, 145, 150, 152, 149, 155, 152, 150, 155, 164, 161, 159, 162, 170, 170, + 170, 170, 92, 111, 109, 115, 176, 176, 177, 165, 198, 198, 196, 209, 174, 170, 173, 183, 159, 148, + 155, 156, 152, 154, 152, 150, 154, 163, 160, 158, 159, 166, 166, 168, 169, 98, 117, 116, 117, 172, + 162, 164, 152, 209, 210, 210, 210, 184, 179, 183, 192, 164, 151, 157, 159, 150, 150, 148, 146, 150, + 159, 155, 154, 157, 170, 169, 170, 170, 117, 136, 134, 123, 192, 196, 196, 197, 179, 180, 180, 191, + 159, 155, 159, 164, 153, 151, 152, 150, 154, 160, 157, 155, 160, 170, 166, 167, 165, 120, 134, 135, + 139, 69, 87, 86, 90, 201, 199, 199, 208, 165, 166, 167, 177, 148, 145, 148, 151, 150, 155, 153, 150, + 157, 165, 162, 159, 165, 170, 169, 170, 166, 84, 107, 108, 111, 49, 66, 64, 71, 200, 199, 198, 208, + 171, 173, 174, 184, 155, 150, 155, 157, 152, 153, 153, 149, 156, 163, 160, 157, 162, 167, 165, 169, + 167, 97, 121, 121, 125, 60, 77, 75, 76, 204, 210, 210, 210, 179, 180, 181, 191, 158, 152, 156, 159, + 150, 150, 149, 146, 152, 159, 156, 153, 160, 170, 169, 170, 170, 112, 135, 136, 138, 71, 88, 86, 79, + 188, 188, 188, 197, 160, 162, 163, 170, 152, 150, 152, 151, 154, 157, 156, 152, 160, 167, 164, 164, + 161, 135, 146, 150, 143, 77, 98, 99, 103, 51, 62, 60, 62, 172, 166, 165, 174, 145, 145, 145, 150, + 152, 155, 154, 150, 160, 165, 163, 159, 168, 170, 168, 170, 151, 80, 104, 109, 101, 44, 63, 63, 66, + 55, 52, 53, 51, 175, 176, 175, 183, 151, 153, 155, 158, 151, 152, 152, 148, 157, 161, 160, 156, 164, + 168, 165, 169, 156, 100, 122, 126, 118, 60, 79, 79, 81, 54, 52, 52, 51, 177, 181, 180, 188, 153, + 153, 155, 159, 149, 150, 150, 146, 155, 159, 157, 153, 164, 170, 169, 170, 170, 109, 131, 136, 127, + 66, 86, 86, 87, 46, 43, 43, 47, 168, 170, 169, 175, 151, 151, 152, 153, 153, 155, 154, 150, 160, + 164, 162, 160, 161, 151, 157, 165, 144, 88, 109, 114, 105, 55, 69, 68, 67, 62, 56, 56, 59, 151, 145, + 145, 148, 154, 154, 154, 150, 162, 164, 163, 159, 170, 170, 167, 170, 135, 80, 100, 110, 89, 41, 61, + 64, 59, 56, 53, 50, 50, 94, 85, 86, 79, 154, 155, 155, 158, 152, 152, 152, 148, 159, 161, 160, 155, + 166, 169, 165, 169, 146, 104, 122, 131, 110, 61, 80, 83, 75, 53, 53, 48, 47, 84, 74, 75, 75, 154, + 154, 154, 158, 151, 150, 150, 146, 158, 159, 158, 154, 167, 170, 169, 170, 153, 108, 127, 136, 113, + 63, 83, 87, 78, 48, 46, 43, 41, 81, 71, 72, 74, 153, 153, 153, 155, 153, 152, 152, 148, 160, 161, + 159, 157, 165, 165, 166, 170, 143, 101, 118, 127, 104, 60, 75, 78, 70, 56, 51, 48, 46, 85, 76, 77, + 81, 152, 154, 154, 151, 164, 164, 163, 159, 170, 170, 167, 170, 121, 84, 98, 114, 78, 44, 60, 68, + 52, 56, 53, 48, 56, 96, 85, 85, 83, 107, 105, 106, 100, 151, 151, 152, 148, 160, 160, 160, 155, 169, + 170, 166, 169, 134, 108, 121, 135, 98, 63, 79, 87, 69, 53, 53, 46, 50, 85, 73, 73, 71, 104, 95, 96, + 97, 151, 151, 151, 148, 160, 159, 159, 155, 169, 170, 170, 170, 137, 108, 121, 136, 99, 63, 78, 87, + 67, 51, 48, 43, 48, 85, 73, 72, 71, 105, 96, 97, 98, 152, 150, 150, 147, 160, 159, 159, 155, 169, + 170, 169, 170, 140, 111, 125, 139, 102, 67, 81, 87, 67, 50, 47, 41, 46, 83, 71, 71, 70, 103, 96, 96, + 98, 160, 162, 163, 159, 170, 170, 167, 170, 109, 91, 98, 117, 70, 49, 60, 72, 49, 55, 54, 46, 62, + 95, 84, 81, 85, 107, 104, 105, 103, 96, 98, 97, 100, 160, 159, 160, 156, 170, 170, 167, 169, 122, + 111, 118, 136, 87, 66, 77, 88, 62, 52, 53, 43, 56, 85, 74, 71, 76, 105, 95, 96, 96, 98, 100, 100, + 100, 160, 160, 160, 156, 170, 170, 167, 170, 120, 109, 116, 134, 86, 64, 75, 86, 60, 53, 51, 43, 56, + 86, 75, 72, 77, 106, 96, 97, 96, 97, 100, 100, 100, 160, 160, 160, 159, 169, 170, 168, 170, 129, + 115, 117, 123, 90, 71, 76, 79, 62, 51, 51, 47, 59, 79, 75, 74, 81, 100, 97, 98, 98, 100, 100, 100, + 100 + }; + byte[] expectedU = + { + 90, 90, 59, 63, 36, 38, 23, 20, 34, 35, 47, 48, 70, 82, 104, 121, 121, 90, 90, 61, 69, 37, 42, 22, + 18, 33, 32, 47, 47, 67, 75, 97, 113, 120, 59, 61, 30, 37, 22, 20, 38, 36, 50, 50, 78, 83, 113, 122, + 142, 166, 164, 63, 69, 37, 43, 20, 18, 34, 32, 48, 47, 70, 73, 102, 110, 136, 166, 166, 36, 37, 22, + 20, 38, 35, 50, 49, 80, 80, 116, 119, 145, 165, 185, 197, 193, 38, 42, 20, 18, 35, 32, 48, 47, 72, + 72, 106, 108, 142, 165, 184, 191, 194, 23, 22, 38, 34, 50, 48, 81, 77, 117, 115, 150, 160, 184, 194, + 212, 220, 217, 20, 18, 36, 32, 49, 47, 76, 71, 111, 108, 148, 164, 185, 190, 208, 217, 219, 34, 33, + 50, 48, 80, 73, 116, 111, 150, 154, 184, 190, 213, 217, 226, 232, 232, 35, 32, 49, 47, 80, 72, 115, + 107, 154, 164, 187, 189, 211, 216, 228, 237, 235, 47, 46, 77, 70, 115, 106, 149, 148, 184, 187, 213, + 214, 226, 230, 227, 223, 224, 48, 47, 83, 73, 119, 108, 159, 164, 190, 189, 214, 216, 229, 236, 229, + 222, 220, 70, 67, 113, 101, 145, 142, 184, 185, 213, 211, 226, 229, 226, 226, 218, 211, 212, 82, 75, + 122, 110, 165, 165, 193, 190, 217, 216, 231, 236, 226, 222, 214, 208, 207, 104, 97, 142, 136, 186, + 184, 212, 208, 227, 228, 227, 229, 218, 214, 196, 185, 188, 121, 113, 166, 166, 197, 191, 220, 217, + 232, 237, 223, 222, 211, 208, 185, 173, 172, 121, 120, 164, 166, 193, 194, 217, 219, 232, 235, 224, + 220, 212, 207, 188, 172, 172 + }; + byte[] expectedV = + { + 240, 240, 201, 206, 172, 174, 136, 136, 92, 90, 55, 50, 37, 30, 26, 23, 23, 240, 240, 204, 213, 173, + 179, 141, 141, 96, 98, 56, 54, 38, 31, 27, 25, 23, 201, 204, 164, 172, 129, 135, 82, 87, 46, 47, 33, + 29, 25, 23, 20, 16, 16, 206, 213, 172, 180, 137, 141, 93, 99, 54, 54, 36, 31, 26, 25, 21, 17, 16, + 172, 173, 129, 138, 81, 89, 45, 49, 32, 30, 24, 24, 19, 16, 42, 55, 51, 174, 179, 136, 141, 89, 99, + 51, 55, 35, 31, 26, 25, 21, 17, 39, 48, 52, 136, 141, 82, 92, 45, 51, 31, 32, 24, 24, 19, 17, 43, + 51, 74, 85, 81, 136, 141, 87, 99, 49, 55, 32, 32, 25, 25, 20, 17, 41, 46, 69, 81, 83, 92, 96, 46, + 53, 32, 34, 24, 25, 18, 18, 44, 47, 76, 81, 103, 117, 116, 90, 97, 48, 54, 30, 31, 24, 25, 18, 17, + 43, 46, 74, 80, 103, 118, 122, 55, 57, 33, 36, 24, 26, 19, 20, 44, 43, 76, 77, 102, 111, 138, 159, + 157, 50, 54, 30, 31, 24, 25, 17, 17, 47, 46, 77, 79, 106, 118, 143, 164, 168, 37, 38, 25, 26, 19, + 21, 43, 41, 75, 73, 103, 106, 138, 152, 174, 195, 194, 30, 31, 23, 25, 16, 17, 51, 46, 81, 79, 111, + 118, 151, 164, 188, 205, 206, 26, 27, 20, 21, 43, 39, 74, 69, 103, 102, 138, 143, 174, 188, 204, + 216, 218, 23, 25, 16, 17, 55, 48, 85, 81, 117, 118, 159, 164, 195, 205, 216, 227, 227, 23, 23, 16, + 16, 51, 52, 81, 83, 116, 122, 157, 168, 194, 206, 218, 227, 227 + }; + + // act + YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); + + // assert + Assert.True(expectedY.AsSpan().SequenceEqual(y)); + Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); + Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 072eeec0f..a223ce0fe 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -508,6 +508,12 @@ namespace SixLabors.ImageSharp.Tests public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; + // Test image for encoding image with a palette. + public const string Flag = "WebP/flag_of_germany.png"; + + // Test images for converting rgb data to yuv. + public const string Yuv = "WebP/yuv_test.png"; + public static class Animated { public const string Animated1 = "WebP/animated-webp.webp"; diff --git a/tests/Images/Input/WebP/flag_of_germany.png b/tests/Images/Input/WebP/flag_of_germany.png new file mode 100644 index 000000000..f6a4438fb --- /dev/null +++ b/tests/Images/Input/WebP/flag_of_germany.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26bf39cea75210c9132eec4567f1f63c870b1eec3b541cfc25da7b5095902f41 +size 72315 diff --git a/tests/Images/Input/WebP/yuv_test.png b/tests/Images/Input/WebP/yuv_test.png new file mode 100644 index 000000000..5606b783e --- /dev/null +++ b/tests/Images/Input/WebP/yuv_test.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96b86c39cad831c97c6ef9633d4d2d04ea0382547514dda5b1f639e10d7207fa +size 3389 From 925c2ffe204bc55154e1b8da6f544ca340e3acf9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Jun 2021 18:17:27 +0200 Subject: [PATCH 299/359] Add yuv conversion test with argb --- .../Formats/WebP/YuvConversionTests.cs | 110 +++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 211db14a9..19820c2ef 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -23,9 +23,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Configuration config = image.GetConfiguration(); MemoryAllocator memoryAllocator = config.MemoryAllocator; int pixels = image.Width * image.Height; + int uvWidth = (image.Width + 1) >> 1; using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(pixels / 2); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(pixels / 2); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); Span y = yBuffer.GetSpan(); Span u = uBuffer.GetSpan(); Span v = vBuffer.GetSpan(); @@ -127,5 +128,110 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); } + + [Theory] + [WithTestPatternImages(31, 31, PixelTypes.Rgba32)] + public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + Configuration config = image.GetConfiguration(); + MemoryAllocator memoryAllocator = config.MemoryAllocator; + int pixels = image.Width * image.Height; + int uvWidth = (image.Width + 1) >> 1; + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + Span y = yBuffer.GetSpan(); + Span u = uBuffer.GetSpan(); + Span v = vBuffer.GetSpan(); + byte[] expectedY = + { + 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, + 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, + 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, + 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, + 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 81, 158, 170, 118, 130, 182, 65, 142, 220, 103, 155, + 167, 115, 127, 204, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 145, 157, 106, + 118, 170, 118, 130, 207, 90, 142, 154, 103, 114, 192, 115, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 145, 93, 105, 157, 105, 117, 195, 78, 130, 142, 90, 102, 179, 102, 114, 192, + 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 92, 170, 93, 105, 182, 65, 142, 129, + 77, 155, 167, 90, 102, 179, 62, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 157, + 80, 92, 170, 52, 130, 117, 65, 142, 154, 102, 89, 166, 49, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 145, 197, 80, 157, 169, 117, 104, 181, 130, 142, 90, 77, 154, 37, 114, 191, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 209, 67, 144, 156, 105, 117, 169, 117, + 129, 206, 64, 141, 153, 102, 179, 166, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 55, 132, 144, 92, 169, 156, 104, 116, 194, 77, 129, 141, 89, 166, 178, 101, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 80, 157, 144, 92, 104, 181, 64, 116, 193, 76, 154, + 166, 89, 101, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 67, 144, 156, 79, 91, + 169, 52, 104, 181, 64, 141, 153, 76, 88, 165, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 183, 132, 144, 196, 79, 156, 39, 116, 168, 51, 129, 141, 89, 76, 153, 101, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 183, 66, 143, 155, 104, 156, 168, 116, 128, + 205, 63, 140, 218, 101, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 119, 196, 54, + 131, 208, 91, 143, 155, 103, 115, 193, 51, 128, 205, 88, 165, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 183, 41, 118, 196, 79, 156, 143, 91, 103, 180, 63, 115, 193, 75, 153, 165, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 29, 106, 183, 66, 143, 130, 78, 90, 168, + 116, 103, 180, 63, 140, 152, 75, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 93, + 171, 53, 131, 118, 66, 78, 155, 103, 90, 167, 50, 128, 140, 63, 75 + }; + byte[] expectedU = + { + 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, + 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, 240, 139, 240, 139, + 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, 240, 139, 240, 139, 240, 139, 240, 139, + 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 115, + 174, 157, 172, 129, 191, 129, 204, 112, 90, 90, 90, 90, 90, 90, 90, 90, 161, 92, 116, 141, 99, 155, + 113, 97, 90, 90, 90, 90, 90, 90, 90, 91, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, + 96, 97, 96, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 92, 134, 130, 112, 149, + 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 90, 164, 117, 149, 127, 128, 166, 107, 129, 159, + 159, 159, 159, 159, 159, 159, 160, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, 240, + 240, 240, 235, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 232, 150, + 108, 140, 161, 80, 157, 162, 128 + }; + byte[] expectedV = + { + 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 157, + 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 160, 110, 189, 110, 189, + 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 160, 110, 189, 110, 189, 110, 189, 110, 189, + 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 173, + 110, 151, 106, 153, 122, 146, 92, 195, 240, 240, 240, 240, 240, 240, 240, 239, 121, 131, 142, 135, + 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 238, 136, 148, 137, 113, 157, 155, 121, 130, + 155, 155, 155, 155, 155, 155, 155, 154, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, + 81, 81, 82, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 80, 147, 133, 119, + 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 101, 109, 129, 122, 124, 107, 108, 128, + 138, 110, 110, 110, 110, 110, 110, 110, 110, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, + 110, 110, 110, 110, 112, 156, 119, 137, 167, 141, 151, 66, 85 + }; + + // act + YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); + + // assert + Assert.True(expectedY.AsSpan().SequenceEqual(y)); + Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); + Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); + } } } From b0c6d73978e8f3f9a394d30764f481d8993b0b35 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Jun 2021 20:09:53 +0200 Subject: [PATCH 300/359] Convert pixels to bgra with ToBgra32Bytes --- src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 739529831..dab106524 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -341,17 +341,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless where TPixel : unmanaged, IPixel { Span bgra = this.Bgra.GetSpan(); - using IMemoryOwner bgraRowBuffer = this.memoryAllocator.Allocate(width); - Span bgraRow = bgraRowBuffer.GetSpan(); - int idx = 0; + Span bgraBytes = MemoryMarshal.Cast(bgra); + int widthBytes = width * 4; for (int y = 0; y < height; y++) { Span rowSpan = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToBgra32(this.configuration, rowSpan, bgraRow); - for (int x = 0; x < width; x++) - { - bgra[idx++] = bgraRow[x].PackedValue; - } + PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, bgraBytes.Slice(y * widthBytes, widthBytes), width); } } From 3b603be32e40097c46aa333c34d2aed65a98887c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 1 Jul 2021 13:32:14 +0200 Subject: [PATCH 301/359] Fix issue with resizing the bitwriter buffer --- .../Formats/WebP/BitWriter/BitWriterBase.cs | 9 +-------- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 20 +++++++------------ .../Formats/WebP/WebpEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 5d9114001..10ec6201d 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -61,13 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// The height of the image. public abstract void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height); - protected bool ResizeBuffer(int maxBytes, int sizeRequired) + protected void ResizeBuffer(int maxBytes, int sizeRequired) { - if (maxBytes > 0 && sizeRequired < maxBytes) - { - return true; - } - int newSize = (3 * maxBytes) >> 1; if (newSize < sizeRequired) { @@ -77,8 +72,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter // Make new size multiple of 1k. newSize = ((newSize >> 10) + 1) << 10; Array.Resize(ref this.buffer, newSize); - - return false; } /// diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index e3e1a9ddc..2f942231f 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -43,14 +43,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// private int cur; - private int end; - /// /// Initializes a new instance of the class. /// /// The expected size in bytes. public Vp8LBitWriter(int expectedSize) - : base(expectedSize) => this.end = this.Buffer.Length; + : base(expectedSize) + { + } /// /// Initializes a new instance of the class. @@ -184,9 +184,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter private void PutBitsFlushBits() { // If needed, make some room by flushing some bits out. - if (this.cur + WriterBytes > this.end) + if (this.cur + WriterBytes > this.Buffer.Length) { - int extraSize = this.end - this.cur + MinExtraSize; + int extraSize = this.Buffer.Length - this.cur + MinExtraSize; this.BitWriterResize(extraSize); } @@ -204,15 +204,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - int maxBytes = this.end + this.Buffer.Length; + int maxBytes = this.Buffer.Length + this.Buffer.Length; int sizeRequired = this.cur + extraSize; - - if (this.ResizeBuffer(maxBytes, sizeRequired)) - { - return; - } - - this.end = this.Buffer.Length; + this.ResizeBuffer(maxBytes, sizeRequired); } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index d0b2c73a1..95ea65d2b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] + [WithFile(TestImages.Png.BikeSmall, PixelTypes.Rgba32, 6, 100)] public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a223ce0fe..292545747 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -44,6 +44,7 @@ namespace SixLabors.ImageSharp.Tests public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; public const string Bike = "Png/Bike.png"; + public const string BikeSmall = "Png/bike-small.png"; public const string BikeGrayscale = "Png/BikeGrayscale.png"; public const string SnakeGame = "Png/SnakeGame.png"; public const string Icon = "Png/icon.png"; From 798220a0f537836c808ac84d15927237b30837a9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 1 Jul 2021 19:24:42 +0200 Subject: [PATCH 302/359] Add SSE2 version of CheckNonOpaque --- .../Formats/WebP/Lossy/YuvConversion.cs | 75 +++++++++++- .../Formats/WebP/YuvConversionTests.cs | 112 ++++++++++++++++++ 2 files changed, 183 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index c49d2048c..4bd70add6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -4,9 +4,15 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal static class YuvConversion @@ -96,13 +102,74 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// The row to check. /// Returns true if alpha has non-0xff values. [MethodImpl(InliningOptions.ShortMethod)] - public static bool CheckNonOpaque(Span row) + public static unsafe bool CheckNonOpaque(Span row) { - for (int x = 0; x < row.Length; x++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); + + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) + { + for (; i + 64 <= length; i += 64) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); + Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); + Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); + Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + } + + for (; i + 32 <= length; i += 32) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + } + + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) + { + return true; + } + } + } + } + else +#endif { - if (row[x].A != 255) + for (int x = 0; x < row.Length; x++) { - return true; + if (row[x].A != 0xFF) + { + return true; + } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 19820c2ef..79b2315a2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -2,17 +2,45 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit; +#if SUPPORTS_RUNTIME_INTRINSICS +using SixLabors.ImageSharp.Tests.TestUtilities; +#endif namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class YuvConversionTests { + [Fact] + public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); +#endif + [Theory] [WithFile(TestImages.WebP.Yuv, PixelTypes.Rgba32)] public void ConvertRgbToYuv_Works(TestImageProvider provider) @@ -233,5 +261,89 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); } + + private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() + { + // arrange + byte[] rowBytes = + { + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 100, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + // act + bool noneOpaque = YuvConversion.CheckNonOpaque(row); + + // assert + Assert.True(noneOpaque); + } + + private static void RunCheckNoneOpaqueWithOpaquePixelsTest() + { + // arrange + byte[] rowBytes = + { + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + // act + bool noneOpaque = YuvConversion.CheckNonOpaque(row); + + // assert + Assert.False(noneOpaque); + } } } From 415836fdcca9edbe244ceedcbf897a23504c31e2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 1 Jul 2021 20:52:52 +0200 Subject: [PATCH 303/359] Add Avx2 version of CheckNonOpaque --- .../Formats/WebP/Lossy/YuvConversion.cs | 129 ++++++++++++++---- .../Formats/WebP/YuvConversionTests.cs | 66 ++++++++- 2 files changed, 169 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 4bd70add6..3c67bfb57 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -101,13 +101,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// The row to check. /// Returns true if alpha has non-0xff values. - [MethodImpl(InliningOptions.ShortMethod)] public static unsafe bool CheckNonOpaque(Span row) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Avx2.IsSupported) { ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); @@ -115,22 +116,30 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int length = (row.Length * 4) - 3; fixed (byte* src = rowBytes) { + for (; i + 128 <= length; i += 128) + { + Vector256 a0 = Avx.LoadVector256(src + i).AsByte(); + Vector256 a1 = Avx.LoadVector256(src + i + 32).AsByte(); + Vector256 a2 = Avx.LoadVector256(src + i + 64).AsByte(); + Vector256 a3 = Avx.LoadVector256(src + i + 96).AsByte(); + Vector256 b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); + Vector256 b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); + Vector256 b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); + Vector256 b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); + Vector256 c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); + Vector256 c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); + Vector256 d = Avx2.PackSignedSaturate(c0, c1).AsByte(); + Vector256 bits = Avx2.CompareEqual(d, all0x80Vector256); + int mask = Avx2.MoveMask(bits); + if (mask != -1) + { + return true; + } + } + for (; i + 64 <= length; i += 64) { - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); - Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); - Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); - Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, all0x80); - int mask = Sse2.MoveMask(bits); - if (mask != 0xFFFF) + if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) { return true; } @@ -138,15 +147,42 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (; i + 32 <= length; i += 32) { - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, all0x80); - int mask = Sse2.MoveMask(bits); - if (mask != 0xFFFF) + if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) + { + return true; + } + } + } + } + else if (Sse2.IsSupported) + { + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); + + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) + { + for (; i + 64 <= length; i += 64) + { + if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i + 32 <= length; i += 32) + { + if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) { return true; } @@ -176,6 +212,49 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return false; } +#if SUPPORTS_RUNTIME_INTRINSICS + private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); + Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); + Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); + Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + + return false; + } + + private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + + return false; + } +#endif + /// /// Converts a rgba pixel row to Y. /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 79b2315a2..59ef221bf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -287,13 +287,45 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 148, 158, 158, 255, 171, 165, 151, 255, 209, 208, 210, 255, - 174, 183, 189, 100, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, 148, 158, 158, 255, 171, 165, 151, 255, 209, 208, 210, 255, 174, 183, 189, 255, 148, 158, 158, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 100, + 171, 165, 151, 0, + 209, 208, 210, 100, + 174, 183, 189, 255, + 148, 158, 158, 255, }; Span row = MemoryMarshal.Cast(rowBytes); @@ -334,6 +366,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 148, 158, 158, 255, 171, 165, 151, 255, 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, 174, 183, 189, 255, 148, 158, 158, 255, }; From 397588ff1892287b67cbccbbc5a8682564f49854 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 2 Jul 2021 12:24:58 +0200 Subject: [PATCH 304/359] Add CheckNonOpaque tests disabling avx --- .../Formats/WebP/YuvConversionTests.cs | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 59ef221bf..439295406 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -32,6 +32,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); + [Fact] public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); @@ -39,6 +43,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); #endif [Theory] @@ -289,7 +297,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 209, 208, 210, 255, 174, 183, 189, 255, 148, 158, 158, 255, - 148, 158, 158, 255, + 148, 158, 158, 10, 171, 165, 151, 255, 209, 208, 210, 255, 171, 165, 151, 255, @@ -299,7 +307,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 171, 165, 151, 255, 209, 208, 210, 255, 174, 183, 189, 255, - 148, 158, 158, 255, + 148, 158, 158, 10, 171, 165, 151, 255, 209, 208, 210, 255, 174, 183, 189, 255, @@ -308,13 +316,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 209, 208, 210, 255, 174, 183, 189, 255, 148, 158, 158, 255, - 209, 208, 210, 255, + 209, 208, 210, 0, 174, 183, 189, 255, 148, 158, 158, 255, 148, 158, 158, 255, 171, 165, 151, 255, 209, 208, 210, 255, - 174, 183, 189, 255, + 174, 183, 189, 0, 148, 158, 158, 255, 148, 158, 158, 255, 171, 165, 151, 255, @@ -329,10 +337,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; Span row = MemoryMarshal.Cast(rowBytes); - // act - bool noneOpaque = YuvConversion.CheckNonOpaque(row); + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act + noneOpaque = YuvConversion.CheckNonOpaque(row); - // assert + // assert + Assert.True(noneOpaque); + } + + // One last test with the complete row. + noneOpaque = YuvConversion.CheckNonOpaque(row); Assert.True(noneOpaque); } @@ -403,10 +419,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; Span row = MemoryMarshal.Cast(rowBytes); - // act - bool noneOpaque = YuvConversion.CheckNonOpaque(row); + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act + noneOpaque = YuvConversion.CheckNonOpaque(row.Slice(0, length)); - // assert + // assert + Assert.False(noneOpaque); + } + + // One last test with the complete row. + noneOpaque = YuvConversion.CheckNonOpaque(row); Assert.False(noneOpaque); } } From a368f4158626f57a14be4c039681be43e9cb70e0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 3 Jul 2021 20:15:46 +0200 Subject: [PATCH 305/359] Enable PickBestIntra16 and PickBestUv --- .../Formats/WebP/Lossy/Vp8CostArray.cs | 2 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 7 +++---- .../Formats/WebP/Lossy/Vp8Encoder.cs | 19 +++++++++---------- .../Formats/WebP/Lossy/Vp8Residual.cs | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs index 4015a18a9..f448de6dc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Initializes a new instance of the class. /// - public Vp8CostArray() => this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)]; + public Vp8CostArray() => this.Costs = new ushort[67 + 1]; public ushort[] Costs { get; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index ac582fd42..16dba6516 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -415,11 +415,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.MakeIntra4Preds(); for (mode = 0; mode < maxMode; ++mode) { - int alpha; histos[curHisto] = new Vp8Histogram(); histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); - alpha = histos[curHisto].GetAlpha(); + var alpha = histos[curHisto].GetAlpha(); if (alpha > bestModeAlpha) { bestModeAlpha = alpha; @@ -522,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int x = 0; x < 4; ++x) { int ctx = this.TopNz[x] + this.LeftNz[y]; - res.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4))); + res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); r += res.GetResidualCost(ctx); this.TopNz[x] = this.LeftNz[y] = (res.Last >= 0) ? 1 : 0; } @@ -572,7 +571,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int x = 0; x < 2; ++x) { int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; - res.SetCoeffs(rd.UvLevels.AsSpan((ch * 2) + x + (y * 2))); + res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); r += res.GetResidualCost(ctx); this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = (res.Last >= 0) ? 1 : 0; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 9d1baafb9..a0dfc143c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -311,7 +311,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Warning! order is important: first call VP8Decimate() and // *then* decide how to code the skip decision if there's one. - if (!this.Decimate(it, info, this.rdOptLevel) || dontUseSkip) + if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip) { this.CodeResiduals(it, info); } @@ -411,7 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Proba.FinalizeTokenProbas(); } - this.Proba.CalculateLevelCosts(); // Finalize costs. + // Finalize costs. + this.Proba.CalculateLevelCosts(); } private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) @@ -425,12 +426,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy long distortion = 0; long pixelCount = nbMbs * 384; + it.Init(); this.SetLoopParams(stats.Q); do { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height, false); - if (this.Decimate(it, info, rdOpt)) + if (this.Decimate(it, ref info, rdOpt)) { // Just record the number of skips and act like skipProba is not used. ++this.Proba.NbSkip; @@ -877,7 +879,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } - private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, Vp8RdLevel rdOpt) + private bool Decimate(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8RdLevel rdOpt) { rd.InitScore(); @@ -886,13 +888,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.MakeLuma16Preds(); it.MakeChroma8Preds(); - // TODO: disabled picking best mode because its still bugged. - /* if (rdOpt > Vp8RdLevel.RdOptNone) + if (rdOpt > Vp8RdLevel.RdOptNone) { QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); if (this.method >= 2) { - QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); + // TODO: there is still a bug in PickBestIntra4, therefore disabled. + // QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); @@ -905,9 +907,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // quantization/reconstruction. QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); } - */ - - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 4a48a94ca..20c5cafa8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - this.Coeffs = coeffs.ToArray(); + this.Coeffs = coeffs.Slice(0, 16).ToArray(); } // Simulate block coding, but only record statistics. From 9cb828ec2d2b81559f872191325ab9603645846c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 4 Jul 2021 13:44:44 +0200 Subject: [PATCH 306/359] Store diffusion errors --- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 17 ++++++++++---- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 23 ++++++++++++++++++- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 6 +++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index fda44f7e0..0cc28b423 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -240,6 +240,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy rdBest.CopyScore(rdUv); rd.ModeUv = mode; rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); + for (int i = 0; i < 2; i++) + { + rd.Derr[i, 0] = rdUv.Derr[i, 0]; + rd.Derr[i, 1] = rdUv.Derr[i, 1]; + rd.Derr[i, 2] = rdUv.Derr[i, 2]; + } + Span tmp = dst; dst = tmpDst; tmpDst = tmp; @@ -253,6 +260,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // copy 16x8 block if needed. LossyUtils.Vp8Copy16X8(dst, dst0); } + + // Store diffusion errors for next block. + it.StoreDiffusionErrors(rd); } public static int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) @@ -571,10 +581,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); int err3 = QuantizeSingle(c.Slice(3 * 16), mtx); - // TODO: set errors in rd - // rd->derr[ch][0] = (int8_t)err1; - // rd->derr[ch][1] = (int8_t)err2; - // rd->derr[ch][2] = (int8_t)err3; + rd.Derr[ch, 0] = err1; + rd.Derr[ch, 1] = err2; + rd.Derr[ch, 2] = err3; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 16dba6516..0e1d84243 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public uint[] Nz { get; } /// - /// Gets the diffusion error. + /// Gets the top diffusion error. /// public sbyte[] TopDerr { get; } @@ -587,6 +587,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment; + public void StoreDiffusionErrors(Vp8ModeScore rd) + { + for (int ch = 0; ch <= 1; ++ch) + { + Span top = this.TopDerr.AsSpan((this.X * 4) + ch, 2); + Span left = this.LeftDerr.AsSpan(ch, 2); + + // restore err1 + left[0] = (sbyte)rd.Derr[ch, 0]; + + // 3/4th of err3 + left[1] = (sbyte)((3 * rd.Derr[ch, 2]) >> 2); + + // err2 + top[0] = (sbyte)rd.Derr[ch, 1]; + + // 1/4th of err3. + top[1] = (sbyte)(rd.Derr[ch, 2] - left[1]); + } + } + /// /// Returns true if iteration is finished. /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index e47fa7160..7182f6021 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -25,6 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.UvLevels = new short[(4 + 4) * 16]; this.ModesI4 = new byte[16]; + this.Derr = new int[2, 3]; } /// @@ -87,6 +88,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// public uint Nz { get; set; } + /// + /// Gets the diffusion errors. + /// + public int[,] Derr { get; } + public void InitScore() { this.D = 0; From 422337ccf598dce7619f15a3b42682ae918862bb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 4 Jul 2021 14:39:50 +0200 Subject: [PATCH 307/359] Enable PickBestIntra4 --- src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs | 6 +++--- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index a03d2c5c5..c7c5119d8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -49,11 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void Copy(Span src, Span dst, int w, int h) { + int offset = 0; for (int y = 0; y < h; ++y) { - src.Slice(0, w).CopyTo(dst); - src = src.Slice(WebpConstants.Bps); - dst = dst.Slice(WebpConstants.Bps); + src.Slice(offset, w).CopyTo(dst.Slice(offset, w)); + offset += WebpConstants.Bps; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index 0cc28b423..f62161dbb 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span tmp = tmpDst; tmpDst = bestBlock; bestBlock = tmp; - tmpLevels.AsSpan().CopyTo(rdBest.YAcLevels); + tmpLevels.CopyTo(rdBest.YAcLevels.AsSpan(it.I4 * 16, 16)); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index a0dfc143c..83f44e1ee 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -893,8 +893,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); if (this.method >= 2) { - // TODO: there is still a bug in PickBestIntra4, therefore disabled. - // QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); + QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); From 217c3e6dc75c2163d64bb516168a3414d218da7d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 4 Jul 2021 18:50:04 +0200 Subject: [PATCH 308/359] Add exact flag as a encoder parameter --- .../Formats/WebP/IWebpEncoderOptions.cs | 7 ++++++ .../Formats/WebP/Lossless/PredictorEncoder.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 22 +++++++++++++--- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 +++ .../Formats/WebP/WebpEncoderCore.cs | 9 ++++++- .../Formats/WebP/WebpEncoderTests.cs | 25 ++++++++++++++++++- 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 2d8a7fdeb..2dbd54478 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -38,5 +38,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Gets the number of entropy-analysis passes (in [1..10]). /// int EntropyPasses { get; } + + /// + /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// The default value is false. + /// + bool Exact { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 97d96e713..9f666ff6a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -641,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless switch (mode) { case 0: - LosslessUtils.PredictorSub0(current, numPixels, output); + LosslessUtils.PredictorSub0(current + xStart, numPixels, output); break; case 1: LosslessUtils.PredictorSub1(current + xStart, numPixels, output); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index dab106524..d6a5de07a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -54,6 +54,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private readonly int method; + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// + private readonly bool exact; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -69,7 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). - public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method) + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. + public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact) { int pixelCount = width * height; int initialSize = pixelCount * 2; @@ -78,6 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); + this.exact = exact; this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.EncodedData = memoryAllocator.Allocate(pixelCount); @@ -603,12 +611,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) { int nearLosslessStrength = 100; // TODO: for now always 100 - bool exact = false; // TODO: always false for now. int predBits = this.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - PredictorEncoder.ResidualImage(width, height, predBits, this.EncodedData.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); + PredictorEncoder.ResidualImage( + width, + height, + predBits, + this.EncodedData.GetSpan(), + this.BgraScratch.GetSpan(), + this.TransformData.GetSpan(), + nearLosslessStrength, + this.exact, + usedSubtractGreen); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index dc01840da..225938d2b 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public int EntropyPasses { get; set; } + /// + public bool Exact { get; set; } + /// public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 9fe477d0c..8d7da5a17 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -47,6 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly int entropyPasses; + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// + private readonly bool exact; + /// /// The global configuration. /// @@ -65,6 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; + this.exact = options.Exact; } /// @@ -89,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method); + using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.exact); enc.Encode(image, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 95ea65d2b..44ae674f3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -64,7 +64,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - [WithFile(TestImages.Png.BikeSmall, PixelTypes.Rgba32, 6, 100)] public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) where TPixel : unmanaged, IPixel { @@ -128,6 +127,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] + public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, int method) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = false, + Method = method, + Exact = true + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossless", "_m", method); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } + [Theory] [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] From 388eb5267b5ae1533610e46cbf8d670b04974d73 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 12:44:50 +0200 Subject: [PATCH 309/359] Add RunSerial attribute to the encoder and decoder tests --- tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 5384a02c9..0bf4b5dcf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -14,6 +14,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.WebP; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Webp { + [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 44ae674f3..8de790b98 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -10,6 +10,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.WebP; namespace SixLabors.ImageSharp.Tests.Formats.Webp { + [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpEncoderTests { From 4fefcf1461c94c65865cd15365810b65b18bd8d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 14:47:14 +0200 Subject: [PATCH 310/359] Add filter strength as encoder parameter --- .../Formats/WebP/IWebpEncoderOptions.cs | 9 +++++++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 18 ++++++++++------- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 +++ .../Formats/WebP/WebpEncoderCore.cs | 8 +++++++- .../Formats/WebP/WebpEncoderTests.cs | 20 +++++++++++++++++++ 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 2dbd54478..dad67f804 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -39,6 +39,15 @@ namespace SixLabors.ImageSharp.Formats.Webp /// int EntropyPasses { get; } + /// + /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture. + /// The higher the value the smoother the picture will appear. + /// Typical values are usually in the range of 20 to 50. + /// Defaults to 60. + /// + int FilterStrength { get; } + /// /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 83f44e1ee..ee3f1a8a5 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private readonly int entropyPasses; + /// + /// Specify the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). A value of 0 will turn off any filtering. + /// + private readonly int filterStrength; + /// /// A bit writer for writing lossy webp streams. /// @@ -78,11 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int QMax = 100; - // TODO: filterStrength is hardcoded, should be configurable. - private const int FilterStrength = 60; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The memory allocator. /// The global configuration. @@ -91,7 +93,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). /// Number of entropy-analysis passes (in [1..10]). - public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses) + /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; @@ -100,6 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); + this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll : (method >= 5) ? Vp8RdLevel.RdOptTrellis : (method >= 3) ? Vp8RdLevel.RdOptBasic @@ -476,7 +480,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void AdjustFilterStrength() { - if (FilterStrength > 0) + if (this.filterStrength > 0) { int maxLevel = 0; for (int s = 0; s < WebpConstants.NumMbSegments; s++) @@ -708,7 +712,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int filterType = 1; // TODO: filterType is hardcoded // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. - int level0 = 5 * FilterStrength; + int level0 = 5 * this.filterStrength; for (int i = 0; i < WebpConstants.NumMbSegments; ++i) { Vp8SegmentInfo m = this.SegmentInfos[i]; diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index 225938d2b..ae0fb5122 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public int EntropyPasses { get; set; } + /// + public int FilterStrength { get; set; } = 60; + /// public bool Exact { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 8d7da5a17..09a319de8 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly int entropyPasses; + /// + /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// + private readonly int filterStrength; + /// /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. @@ -71,6 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; + this.filterStrength = options.FilterStrength; this.exact = options.Exact; } @@ -91,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.lossy) { - using var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses); + using var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses, this.filterStrength); enc.Encode(image, stream); } else diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 8de790b98..4d9ff2cfb 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -98,6 +98,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] + public void Encode_Lossy_WithDifferentFilterStrength_Works(TestImageProvider provider, int filterStrength) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = true, + FilterStrength = filterStrength + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_f", filterStrength); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); + } + [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] From b440d51331e5c8c7f88ac6b50f498f4fce77747d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 20:27:20 +0200 Subject: [PATCH 311/359] Write hasAlpha flag when encoding lossless webp --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 24 +- .../Formats/WebP/Lossy/YuvConversion.cs | 311 +++--------- .../Formats/WebP/WebpCommonUtils.cs | 165 ++++++ .../Formats/WebP/WebpCommonUtilsTests.cs | 214 ++++++++ .../Formats/WebP/WebpDecoderTests.cs | 12 + .../Formats/WebP/YuvConversionTests.cs | 480 +++++++++++------- tests/ImageSharp.Tests/TestImages.cs | 38 +- .../Input/WebP/lossless_alpha_small.webp | 3 + 8 files changed, 793 insertions(+), 454 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs create mode 100644 tests/Images/Input/WebP/lossless_alpha_small.webp diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index d6a5de07a..5b86cd4c2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -194,14 +194,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless where TPixel : unmanaged, IPixel { image.Metadata.SyncProfiles(); - - // Write the image size. int width = image.Width; int height = image.Height; + + // Convert image pixels to bgra array. + bool hasAlpha = this.ConvertPixelsToBgra(image, width, height); + + // Write the image size. this.WriteImageSize(width, height); // Write the non-trivial Alpha flag and lossless version. - bool hasAlpha = false; // TODO: for the start, this will be always false. this.WriteAlphaAndVersion(hasAlpha); // Encode the main image stream. @@ -249,8 +251,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int width = image.Width; int height = image.Height; - // Convert image pixels to bgra array. - this.ConvertPixelsToBgra(image, width, height); ReadOnlySpan bgra = this.Bgra.GetSpan(); Span encodedData = this.EncodedData.GetSpan(); @@ -345,17 +345,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The image to convert. /// The width of the image. /// The height of the image. - private void ConvertPixelsToBgra(Image image, int width, int height) + /// true, if the image is non opaque. + private bool ConvertPixelsToBgra(Image image, int width, int height) where TPixel : unmanaged, IPixel { + bool nonOpaque = false; Span bgra = this.Bgra.GetSpan(); Span bgraBytes = MemoryMarshal.Cast(bgra); int widthBytes = width * 4; for (int y = 0; y < height; y++) { Span rowSpan = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, bgraBytes.Slice(y * widthBytes, widthBytes), width); + Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); + PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); + if (!nonOpaque) + { + Span rowBgra = MemoryMarshal.Cast(rowBytes); + nonOpaque = WebpCommonUtils.CheckNonOpaque(rowBgra); + } } + + return nonOpaque; } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 3c67bfb57..1b970eb45 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -4,15 +4,9 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif - namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal static class YuvConversion @@ -43,218 +37,59 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Temporary storage for accumulated R/G/B values during conversion to U/V. using IMemoryOwner tmpRgb = memoryAllocator.Allocate(4 * uvWidth); - using IMemoryOwner rgbaRow0Buffer = memoryAllocator.Allocate(width); - using IMemoryOwner rgbaRow1Buffer = memoryAllocator.Allocate(width); + using IMemoryOwner bgraRow0Buffer = memoryAllocator.Allocate(width); + using IMemoryOwner bgraRow1Buffer = memoryAllocator.Allocate(width); Span tmpRgbSpan = tmpRgb.GetSpan(); - Span rgbaRow0 = rgbaRow0Buffer.GetSpan(); - Span rgbaRow1 = rgbaRow1Buffer.GetSpan(); + Span bgraRow0 = bgraRow0Buffer.GetSpan(); + Span bgraRow1 = bgraRow1Buffer.GetSpan(); int uvRowIndex = 0; int rowIndex; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { Span rowSpan = image.GetPixelRowSpan(rowIndex); Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); - PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); - PixelOperations.Instance.ToRgba32(configuration, nextRowSpan, rgbaRow1); + PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); + PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); - bool rowsHaveAlpha = CheckNonOpaque(rgbaRow0) && CheckNonOpaque(rgbaRow1); + bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1); // Downsample U/V planes, two rows at a time. if (!rowsHaveAlpha) { - AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + AccumulateRgb(bgraRow0, bgraRow1, tmpRgbSpan, width); } else { - AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width); + AccumulateRgba(bgraRow0, bgraRow1, tmpRgbSpan, width); } ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; - ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); - ConvertRgbaToY(rgbaRow1, y.Slice((rowIndex + 1) * width), width); + ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); + ConvertRgbaToY(bgraRow1, y.Slice((rowIndex + 1) * width), width); } // Extra last row. if ((height & 1) != 0) { Span rowSpan = image.GetPixelRowSpan(rowIndex); - PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow0); - ConvertRgbaToY(rgbaRow0, y.Slice(rowIndex * width), width); + PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); + ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); - if (!CheckNonOpaque(rgbaRow0)) + if (!WebpCommonUtils.CheckNonOpaque(bgraRow0)) { - AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + AccumulateRgb(bgraRow0, bgraRow0, tmpRgbSpan, width); } else { - AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width); + AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width); } ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); } } - /// - /// Checks if the pixel row is not opaque. - /// - /// The row to check. - /// Returns true if alpha has non-0xff values. - public static unsafe bool CheckNonOpaque(Span row) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported) - { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); - - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) - { - for (; i + 128 <= length; i += 128) - { - Vector256 a0 = Avx.LoadVector256(src + i).AsByte(); - Vector256 a1 = Avx.LoadVector256(src + i + 32).AsByte(); - Vector256 a2 = Avx.LoadVector256(src + i + 64).AsByte(); - Vector256 a3 = Avx.LoadVector256(src + i + 96).AsByte(); - Vector256 b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); - Vector256 b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); - Vector256 b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); - Vector256 b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); - Vector256 c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); - Vector256 c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); - Vector256 d = Avx2.PackSignedSaturate(c0, c1).AsByte(); - Vector256 bits = Avx2.CompareEqual(d, all0x80Vector256); - int mask = Avx2.MoveMask(bits); - if (mask != -1) - { - return true; - } - } - - for (; i + 64 <= length; i += 64) - { - if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) - { - return true; - } - } - - for (; i + 32 <= length; i += 32) - { - if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) - { - return true; - } - } - - for (; i <= length; i += 4) - { - if (src[i + 3] != 0xFF) - { - return true; - } - } - } - } - else if (Sse2.IsSupported) - { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); - - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) - { - for (; i + 64 <= length; i += 64) - { - if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) - { - return true; - } - } - - for (; i + 32 <= length; i += 32) - { - if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) - { - return true; - } - } - - for (; i <= length; i += 4) - { - if (src[i + 3] != 0xFF) - { - return true; - } - } - } - } - else -#endif - { - for (int x = 0; x < row.Length; x++) - { - if (row[x].A != 0xFF) - { - return true; - } - } - } - - return false; - } - -#if SUPPORTS_RUNTIME_INTRINSICS - private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) - { - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); - Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); - Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); - Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, all0x80); - int mask = Sse2.MoveMask(bits); - if (mask != 0xFFFF) - { - return true; - } - - return false; - } - - private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) - { - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, all0x80); - int mask = Sse2.MoveMask(bits); - if (mask != 0xFFFF) - { - return true; - } - - return false; - } -#endif - /// /// Converts a rgba pixel row to Y. /// @@ -262,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// The destination span for y. /// The width. [MethodImpl(InliningOptions.ShortMethod)] - public static void ConvertRgbaToY(Span rowSpan, Span y, int width) + public static void ConvertRgbaToY(Span rowSpan, Span y, int width) { for (int x = 0; x < width; x++) { @@ -288,84 +123,84 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) { - Rgba32 rgba0; - Rgba32 rgba1; + Bgra32 bgra0; + Bgra32 bgra1; int i, j; int dstIdx = 0; for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) { - rgba0 = rowSpan[j]; - rgba1 = rowSpan[j + 1]; - Rgba32 rgba2 = nextRowSpan[j]; - Rgba32 rgba3 = nextRowSpan[j + 1]; + bgra0 = rowSpan[j]; + bgra1 = rowSpan[j + 1]; + Bgra32 bgra2 = nextRowSpan[j]; + Bgra32 bgra3 = nextRowSpan[j + 1]; dst[dstIdx] = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); + GammaToLinear(bgra0.R) + + GammaToLinear(bgra1.R) + + GammaToLinear(bgra2.R) + + GammaToLinear(bgra3.R), 0); dst[dstIdx + 1] = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); + GammaToLinear(bgra0.G) + + GammaToLinear(bgra1.G) + + GammaToLinear(bgra2.G) + + GammaToLinear(bgra3.G), 0); dst[dstIdx + 2] = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); + GammaToLinear(bgra0.B) + + GammaToLinear(bgra1.B) + + GammaToLinear(bgra2.B) + + GammaToLinear(bgra3.B), 0); } if ((width & 1) != 0) { - rgba0 = rowSpan[j]; - rgba1 = nextRowSpan[j]; + bgra0 = rowSpan[j]; + bgra1 = nextRowSpan[j]; - dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); + dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); + dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); } } - public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) { - Rgba32 rgba0; - Rgba32 rgba1; + Bgra32 bgra0; + Bgra32 bgra1; int i, j; int dstIdx = 0; for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) { - rgba0 = rowSpan[j]; - rgba1 = rowSpan[j + 1]; - Rgba32 rgba2 = nextRowSpan[j]; - Rgba32 rgba3 = nextRowSpan[j + 1]; - uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); + bgra0 = rowSpan[j]; + bgra1 = rowSpan[j + 1]; + Bgra32 bgra2 = nextRowSpan[j]; + Bgra32 bgra3 = nextRowSpan[j + 1]; + uint a = (uint)(bgra0.A + bgra1.A + bgra2.A + bgra3.A); int r, g, b; if (a == 4 * 0xff || a == 0) { r = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); + GammaToLinear(bgra0.R) + + GammaToLinear(bgra1.R) + + GammaToLinear(bgra2.R) + + GammaToLinear(bgra3.R), 0); g = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); + GammaToLinear(bgra0.G) + + GammaToLinear(bgra1.G) + + GammaToLinear(bgra2.G) + + GammaToLinear(bgra3.G), 0); b = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); + GammaToLinear(bgra0.B) + + GammaToLinear(bgra1.B) + + GammaToLinear(bgra2.B) + + GammaToLinear(bgra3.B), 0); } else { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra2.R, bgra3.R, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); + g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra2.G, bgra3.G, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); + b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra2.B, bgra3.B, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); } dst[dstIdx] = (ushort)r; @@ -376,21 +211,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((width & 1) != 0) { - rgba0 = rowSpan[j]; - rgba1 = nextRowSpan[j]; - uint a = (uint)(2u * (rgba0.A + rgba1.A)); + bgra0 = rowSpan[j]; + bgra1 = nextRowSpan[j]; + uint a = (uint)(2u * (bgra0.A + bgra1.A)); int r, g, b; if (a == 4 * 0xff || a == 0) { - r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + r = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); + g = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); + b = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); } else { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba0.R, rgba1.R, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba0.G, rgba1.G, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba0.B, rgba1.B, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a); + r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra0.R, bgra1.R, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); + g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra0.G, bgra1.G, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); + b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra0.B, bgra1.B, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); } dst[dstIdx] = (ushort)r; diff --git a/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs index 82af88d67..cdd324b07 100644 --- a/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs @@ -1,8 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Webp { @@ -26,5 +32,164 @@ namespace SixLabors.ImageSharp.Formats.Webp return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebpLookupTables.LogTable8Bit), (int)n); } + + /// + /// Checks if the pixel row is not opaque. + /// + /// The row to check. + /// Returns true if alpha has non-0xff values. + public static unsafe bool CheckNonOpaque(Span row) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); + + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) + { + for (; i + 128 <= length; i += 128) + { + Vector256 a0 = Avx.LoadVector256(src + i).AsByte(); + Vector256 a1 = Avx.LoadVector256(src + i + 32).AsByte(); + Vector256 a2 = Avx.LoadVector256(src + i + 64).AsByte(); + Vector256 a3 = Avx.LoadVector256(src + i + 96).AsByte(); + Vector256 b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); + Vector256 b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); + Vector256 b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); + Vector256 b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); + Vector256 c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); + Vector256 c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); + Vector256 d = Avx2.PackSignedSaturate(c0, c1).AsByte(); + Vector256 bits = Avx2.CompareEqual(d, all0x80Vector256); + int mask = Avx2.MoveMask(bits); + if (mask != -1) + { + return true; + } + } + + for (; i + 64 <= length; i += 64) + { + if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i + 32 <= length; i += 32) + { + if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) + { + return true; + } + } + } + } + else if (Sse2.IsSupported) + { + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 all0x80 = Vector128.Create((byte)0x80).AsByte(); + + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) + { + for (; i + 64 <= length; i += 64) + { + if (IsNoneOpaque64Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i + 32 <= length; i += 32) + { + if (IsNoneOpaque32Bytes(src, i, alphaMask, all0x80)) + { + return true; + } + } + + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) + { + return true; + } + } + } + } + else +#endif + { + for (int x = 0; x < row.Length; x++) + { + if (row[x].A != 0xFF) + { + return true; + } + } + } + + return false; + } + +#if SUPPORTS_RUNTIME_INTRINSICS + private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); + Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); + Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); + Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + + return false; + } + + private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i, Vector128 alphaMask, Vector128 all0x80) + { + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, all0x80); + int mask = Sse2.MoveMask(bits); + if (mask != 0xFFFF) + { + return true; + } + + return false; + } +#endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs new file mode 100644 index 000000000..71bd5bf8d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs @@ -0,0 +1,214 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +#if SUPPORTS_RUNTIME_INTRINSICS +using SixLabors.ImageSharp.Tests.TestUtilities; +#endif + +namespace SixLabors.ImageSharp.Tests.Formats.Webp +{ + [Trait("Format", "Webp")] + public class WebpCommonUtilsTests + { + [Fact] + public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); +#endif + + private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() + { + // arrange + byte[] rowBytes = + { + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 10, + 171, 165, 151, 255, + 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 10, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 209, 208, 210, 0, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 0, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 100, + 171, 165, 151, 0, + 209, 208, 210, 100, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act + noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + + // assert + Assert.True(noneOpaque); + } + + // One last test with the complete row. + noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + Assert.True(noneOpaque); + } + + private static void RunCheckNoneOpaqueWithOpaquePixelsTest() + { + // arrange + byte[] rowBytes = + { + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act + noneOpaque = WebpCommonUtils.CheckNonOpaque(row.Slice(0, length)); + + // assert + Assert.False(noneOpaque); + } + + // One last test with the complete row. + noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + Assert.False(noneOpaque); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 0bf4b5dcf..33f7930d1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -203,6 +203,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + [Theory] + [WithFile(Lossless.Alpha, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + [Theory] [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] [WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 439295406..0e2a5c13a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -2,53 +2,17 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit; -#if SUPPORTS_RUNTIME_INTRINSICS -using SixLabors.ImageSharp.Tests.TestUtilities; -#endif namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class YuvConversionTests { - [Fact] - public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); - -#if SUPPORTS_RUNTIME_INTRINSICS - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); -#endif - [Theory] [WithFile(TestImages.WebP.Yuv, PixelTypes.Rgba32)] public void ConvertRgbToYuv_Works(TestImageProvider provider) @@ -167,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Theory] [WithTestPatternImages(31, 31, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) + public void ConvertRgbToYuv_WithTestPattern_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { // arrange @@ -270,168 +234,304 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); } - private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() + [Theory] + [WithFile(TestImages.WebP.Lossless.Alpha, PixelTypes.Rgba32)] + public void ConvertRgbToYuv_WithAlphaImage_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel { // arrange - byte[] rowBytes = + using Image image = provider.GetImage(); + Configuration config = image.GetConfiguration(); + MemoryAllocator memoryAllocator = config.MemoryAllocator; + int pixels = image.Width * image.Height; + int uvWidth = (image.Width + 1) >> 1; + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + Span y = yBuffer.GetSpan(); + Span u = uBuffer.GetSpan(); + Span v = vBuffer.GetSpan(); + byte[] expectedY = { - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 0, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 0, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 100, - 171, 165, 151, 0, - 209, 208, 210, 100, - 174, 183, 189, 255, - 148, 158, 158, 255, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 108, 106, 113, 110, 111, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 105, 100, 95, 105, 92, 126, + 130, 130, 130, 130, 130, 126, 108, 112, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 155, 123, 107, + 140, 126, 122, 123, 123, 123, 123, 123, 119, 57, 107, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, + 127, 116, 142, 159, 156, 154, 155, 155, 154, 154, 154, 152, 132, 79, 33, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 17, 157, 132, 158, 158, 159, 157, 148, 146, 154, 159, 159, 160, 149, 109, 57, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 17, 160, 133, 158, 159, 156, 133, 101, 88, 122, 150, 159, 159, 150, 110, 57, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 160, 133, 158, 159, 145, 83, 115, 108, 164, 132, 158, 159, + 150, 110, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 161, 134, 158, 159, 140, 210, 96, 109, 134, + 127, 156, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 161, 134, 158, 159, 146, 96, + 123, 117, 211, 135, 158, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 163, 134, 158, + 158, 158, 139, 116, 110, 129, 153, 159, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, + 142, 126, 154, 160, 159, 159, 153, 151, 158, 160, 159, 160, 143, 101, 48, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 101, 27, 130, 144, 145, 145, 145, 145, 145, 145, 145, 140, 116, 129, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 112, 103, 41, 83, 84, 84, 84, 84, 84, 84, 85, 73, 129, 41, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 104, 107, 107, 109, 109, 109, 109, 109, 109, 109, 109, 108, 100, + 106, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; - Span row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) - { - // act - noneOpaque = YuvConversion.CheckNonOpaque(row); - - // assert - Assert.True(noneOpaque); - } - - // One last test with the complete row. - noneOpaque = YuvConversion.CheckNonOpaque(row); - Assert.True(noneOpaque); - } - - private static void RunCheckNoneOpaqueWithOpaquePixelsTest() - { - // arrange - byte[] rowBytes = + byte[] expectedU = { - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 120, 112, 106, 112, 112, 116, + 124, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 108, 90, 78, 79, 79, 79, 84, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 103, 77, 70, 79, 81, 70, 72, 105, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 102, 77, 75, 125, 91, 78, 72, 105, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 102, 77, 72, 92, 94, 74, 72, 105, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 115, + 84, 74, 75, 76, 74, 79, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 120, 240, 124, 123, 123, 123, 132, 148, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 110, 226, 185, 225, 56, + 48, 209, 111, 254, 56, 74, 116, 56, 222, 107, 255, 163, 237, 83, 1, 99, 241, 23, 1, 128, 186, 211, + 52, 84, 206, 135, 202, 159, 178, 4, 118, 130, 203, 127, 164, 82, 203, 119, 36, 123, 187, 82, 188, + 237, 245, 42, 15, 46, 92, 94, 191, 244, 175, 146, 126, 163, 13, 56, 67, 113, 84, 4, 48, 70, 9, 5, + 207, 208, 212, 229, 220, 34, 75, 138, 148, 253, 173, 238, 137, 121, 176, 79, 36, 185, 51, 17, 150, + 194, 218, 92, 83, 147, 117, 107, 59, 202, 241, 94, 251, 31, 109, 159, 10, 24, 139, 127, 208, 0, 95, + 128, 236, 27, 97, 149, 231, 77, 79, 253, 9, 154, 137, 50, 187, 183, 43, 234, 147, 85, 145, 3, 54, + 146, 151, 7, 255, 131, 208, 141, 202, 155, 31, 254, 95, 68, 71, 9, 5, 255, 83, 83, 151, 119, 75, 44, + 41, 226, 40, 81, 71, 117, 56, 92, 255, 7, 151, 187, 35, 145, 214, 74, 125, 222, 81, 146, 136, 131, + 119, 202, 241, 126, 251, 31, 109, 175, 5, 2, 174, 0, 233, 55, 60, 171, 54, 22, 21, 27, 132, 254, + 150, 200, 193, 119, 87, 47, 207, 78, 57, 129, 190, 13, 255, 69, 131, 40, 113, 71, 241, 47, 178, 195, + 184, 27, 45, 55, 208, 156, 22, 1, 140, 210, 148, 67, 237, 238, 195, 245, 118, 157, 149, 23, 14, 164, + 122, 19, 235, 95, 111, 52, 22, 198, 61, 182, 20, 113, 148, 72, 8, 224, 83, 113, 35, 180, 242, 178, + 33, 247, 179, 52, 208, 169, 136, 108, 90, 203, 250, 235, 222, 75, 22, 176, 13, 237, 215, 21, 205, + 61, 209, 139, 53, 112, 39, 244, 158, 138, 0, 250, 40, 161, 88, 157, 107, 234, 242, 110, 245, 226, + 130, 37, 141, 218, 42, 195, 81, 178, 39, 55, 68, 219, 75, 89, 69, 202, 79, 207, 140, 172, 56, 74, + 116, 56, 209, 111, 21, 227, 168, 69, 38, 75, 127, 208, 8, 160, 234, 70, 93, 245, 230, 54, 109, 245, + 166, 225, 114, 6, 99, 99, 93, 254, 106, 175, 136, 93, 32, 216, 168, 20, 220, 97, 120, 95, 32, 228, + 27, 31, 120, 78, 220, 43, 2, 40, 161, 132, 162, 187, 222, 83, 20, 79, 67, 179, 99, 43, 158, 214, 82, + 42, 239, 167, 6, 141, 142, 247, 148, 92, 45, 234, 105, 244, 175, 190, 207, 235, 76, 163, 133, 47, 4, + 0, 38, 79, 8, 220, 85, 58, 182, 49, 234, 168, 102, 27, 198, 183, 186, 218, 251, 215, 101, 194, 43, + 191, 48, 157, 112, 185, 40, 42, 55, 25, 135, 114, 1, 253, 82, 1, 206, 0, 86, 169, 239, 211, 26, 152, + 254, 205, 149, 233, 48, 223, 215, 234, 95, 251, 91, 253, 125, 127, 171, 187, 31, 104, 119, 15, 87, + 31, 255, 253, 173, 238, 177, 163, 58, 92, 38, 123, 114, 85, 69, 202, 35, 21, 240, 69, 252, 32, 165, + 138, 233, 54, 196, 190, 29, 152, 108, 155, 217, 131, 164, 29, 145, 184, 173, 172, 128, 182, 129, + 127, 221, 239, 10, 239, 93, 102, 254, 137, 244, 69, 250, 95, 86, 1, 250, 1, 72, 227, 28, 19, 1, 47, + 107, 192, 211, 192, 60, 218, 135, 205, 143, 118, 33, 129, 22, 199, 187, 176, 233, 250, 217, 91, 145, + 114, 128, 39, 130, 200, 143, 44, 224, 66, 64, 33, 181, 202, 14, 244, 109, 83, 119, 241, 23, 245, 69, + 196, 225, 173, 211, 104, 148, 207, 252, 209, 40, 230, 103, 235, 85, 138, 182, 205, 104, 229, 176, + 242, 243, 223, 93, 77, 1, 172, 82, 169, 255, 219, 141, 80, 203, 76, 232, 199, 154, 8, 219, 68, 132, + 173, 32, 56, 220, 90, 241, 122, 184, 16, 252, 190, 70, 192, 222, 118, 61, 111, 239, 223, 93, 169, + 239, 74, 254, 162, 99, 87, 28, 23, 243, 96, 110, 246, 191, 23, 123, 160, 226, 247, 47, 186, 42, 38, + 34, 187, 181, 246, 158, 84, 130, 52, 192, 1, 160, 209, 83, 183, 124, 118, 86, 89, 239, 226, 59, 76, + 102, 249, 79, 122, 15, 68, 39, 235, 203, 195, 187, 250, 41, 2, 71, 250, 229, 8, 241, 152, 189, 116, + 241, 32, 25, 246, 36, 117, 34, 197, 111, 249, 240, 2, 213, 221, 130, 39, 255, 143, 223, 118, 254, + 153, 212, 63, 64 }; - Span row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) + byte[] expectedV = { - // act - noneOpaque = YuvConversion.CheckNonOpaque(row.Slice(0, length)); + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 127, 126, 126, 126, 127, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 126, 123, 122, 122, 123, 122, 123, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 122, 121, 122, 122, 121, 122, + 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 125, 122, 122, 128, 124, 122, 121, 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 122, 121, 124, 124, 122, 122, + 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 126, 123, 122, 122, 122, 122, 123, 127, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 110, 129, 129, 129, 129, 130, + 124, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; - // assert - Assert.False(noneOpaque); - } + // act + YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); - // One last test with the complete row. - noneOpaque = YuvConversion.CheckNonOpaque(row); - Assert.False(noneOpaque); + // assert + Assert.True(expectedY.AsSpan().SequenceEqual(y)); + Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); + Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 292545747..5c86ffcd3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -526,6 +526,7 @@ namespace SixLabors.ImageSharp.Tests public static class Lossless { public const string Earth = "WebP/earth_lossless.webp"; + public const string Alpha = "WebP/lossless_alpha_small.webp"; public const string WithExif = "WebP/exif_lossless.webp"; public const string WithIccp = "WebP/lossless_with_iccp.webp"; public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; @@ -558,29 +559,29 @@ namespace SixLabors.ImageSharp.Tests public const string TwoTransforms12 = "WebP/lossless_vec_2_6.webp"; // substract_green, predictor public const string TwoTransforms13 = "WebP/lossless_vec_2_9.webp"; // color_indexing, predictor - public const string - ThreeTransforms1 = "WebP/color_cache_bits_11.webp"; // substract_green, predictor, cross_color + // substract_green, predictor, cross_color + public const string ThreeTransforms1 = "WebP/color_cache_bits_11.webp"; - public const string - ThreeTransforms2 = "WebP/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color + // color_indexing, predictor, cross_color + public const string ThreeTransforms2 = "WebP/lossless_vec_1_11.webp"; - public const string - ThreeTransforms3 = "WebP/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color + // substract_green, predictor, cross_color + public const string ThreeTransforms3 = "WebP/lossless_vec_1_14.webp"; - public const string - ThreeTransforms4 = "WebP/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color + // color_indexing, predictor, cross_color + public const string ThreeTransforms4 = "WebP/lossless_vec_1_15.webp"; - public const string - ThreeTransforms5 = "WebP/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color + // color_indexing, predictor, cross_color + public const string ThreeTransforms5 = "WebP/lossless_vec_2_11.webp"; - public const string - ThreeTransforms6 = "WebP/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color + // substract_green, predictor, cross_color + public const string ThreeTransforms6 = "WebP/lossless_vec_2_14.webp"; - public const string - ThreeTransforms7 = "WebP/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color + // color_indexing, predictor, cross_color + public const string ThreeTransforms7 = "WebP/lossless_vec_2_15.webp"; - public const string - BikeThreeTransforms = "WebP/bike_lossless.webp"; // substract_green, predictor, cross_color + // substract_green, predictor, cross_color + public const string BikeThreeTransforms = "WebP/bike_lossless.webp"; public const string BikeSmall = "WebP/bike_lossless_small.webp"; @@ -592,8 +593,7 @@ namespace SixLabors.ImageSharp.Tests public const string LossLessCorruptImage2 = "WebP/lossless_vec_2_7.webp"; // color_indexing, predictor. - public const string - LossLessCorruptImage3 = "WebP/lossless_color_transform.webp"; // cross_color, predictor + public const string LossLessCorruptImage3 = "WebP/lossless_color_transform.webp"; // cross_color, predictor public const string LossLessCorruptImage4 = "WebP/near_lossless_75.webp"; // predictor, cross_color. } @@ -666,7 +666,7 @@ namespace SixLabors.ImageSharp.Tests public const string Small03 = "WebP/small_1x13.webp"; public const string Small04 = "WebP/small_31x13.webp"; - // Lossy images with an alpha channel. + // Lossy images with a alpha channel. public const string Alpha1 = "WebP/lossy_alpha1.webp"; public const string Alpha2 = "WebP/lossy_alpha2.webp"; public const string Alpha3 = "WebP/alpha_color_cache.webp"; diff --git a/tests/Images/Input/WebP/lossless_alpha_small.webp b/tests/Images/Input/WebP/lossless_alpha_small.webp new file mode 100644 index 000000000..304080f93 --- /dev/null +++ b/tests/Images/Input/WebP/lossless_alpha_small.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d078eb784835863f12ef25d9c1c135e79c2495532cec08da6f19c2e27c0cacee +size 1638 From ea66e97909bd8c24ec89d80c213ef4ea66fd151c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 7 Jul 2021 20:26:57 +0200 Subject: [PATCH 312/359] Remove flaky test --- .../Formats/WebP/YuvConversionTests.cs | 344 ++---------------- 1 file changed, 35 insertions(+), 309 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 0e2a5c13a..db2b73878 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp @@ -131,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Theory] [WithTestPatternImages(31, 31, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_WithTestPattern_Works(TestImageProvider provider) + public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { // arrange @@ -143,6 +145,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + + // Make the image completely transparent. + image.Mutate(c => c.ProcessPixelRowsAsVector4(row => + { + for (int x = 0; x < row.Length; x++) + { + row[x] = new Vector4(row[x].X, row[x].Y, row[x].Z, 0.0f); + } + })); + Span y = yBuffer.GetSpan(); Span u = uBuffer.GetSpan(); Span v = vBuffer.GetSpan(); @@ -200,13 +212,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 115, - 174, 157, 172, 129, 191, 129, 204, 112, 90, 90, 90, 90, 90, 90, 90, 90, 161, 92, 116, 141, 99, 155, - 113, 97, 90, 90, 90, 90, 90, 90, 90, 91, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, - 96, 97, 96, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 92, 134, 130, 112, 149, - 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 90, 164, 117, 149, 127, 128, 166, 107, 129, 159, - 159, 159, 159, 159, 159, 159, 160, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, 240, - 240, 240, 235, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 232, 150, - 108, 140, 161, 80, 157, 162, 128 + 174, 157, 172, 129, 191, 129, 204, 112, 90, 90, 90, 90, 90, 90, 90, 95, 155, 95, 116, 142, 98, 155, + 105, 97, 90, 90, 90, 90, 90, 90, 90, 127, 151, 114, 172, 123, 133, 129, 115, 171, 96, 96, 96, 96, + 96, 96, 96, 116, 93, 131, 127, 112, 139, 93, 160, 87, 91, 91, 91, 91, 91, 91, 91, 100, 129, 135, + 110, 148, 107, 139, 136, 110, 91, 91, 91, 91, 91, 91, 91, 96, 164, 116, 147, 131, 127, 165, 107, + 128, 159, 159, 159, 159, 159, 159, 159, 162, 120, 114, 140, 86, 143, 112, 105, 161, 240, 240, 240, + 240, 240, 240, 240, 191, 112, 160, 110, 140, 158, 103, 159, 138, 240, 240, 240, 240, 240, 240, 240, + 175, 141, 114, 140, 159, 81, 158, 136, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; byte[] expectedV = { @@ -216,313 +235,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 160, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 173, - 110, 151, 106, 153, 122, 146, 92, 195, 240, 240, 240, 240, 240, 240, 240, 239, 121, 131, 142, 135, - 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 238, 136, 148, 137, 113, 157, 155, 121, 130, - 155, 155, 155, 155, 155, 155, 155, 154, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, - 81, 81, 82, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 80, 147, 133, 119, - 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 101, 109, 129, 122, 124, 107, 108, 128, - 138, 110, 110, 110, 110, 110, 110, 110, 110, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, - 110, 110, 110, 110, 112, 156, 119, 137, 167, 141, 151, 66, 85 - }; - - // act - YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); - - // assert - Assert.True(expectedY.AsSpan().SequenceEqual(y)); - Assert.True(expectedU.AsSpan().SequenceEqual(u.Slice(0, expectedU.Length))); - Assert.True(expectedV.AsSpan().SequenceEqual(v.Slice(0, expectedV.Length))); - } - - [Theory] - [WithFile(TestImages.WebP.Lossless.Alpha, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_WithAlphaImage_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image image = provider.GetImage(); - Configuration config = image.GetConfiguration(); - MemoryAllocator memoryAllocator = config.MemoryAllocator; - int pixels = image.Width * image.Height; - int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - Span y = yBuffer.GetSpan(); - Span u = uBuffer.GetSpan(); - Span v = vBuffer.GetSpan(); - byte[] expectedY = - { - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 108, 106, 113, 110, 111, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 105, 100, 95, 105, 92, 126, - 130, 130, 130, 130, 130, 126, 108, 112, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 155, 123, 107, - 140, 126, 122, 123, 123, 123, 123, 123, 119, 57, 107, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, - 127, 116, 142, 159, 156, 154, 155, 155, 154, 154, 154, 152, 132, 79, 33, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 17, 157, 132, 158, 158, 159, 157, 148, 146, 154, 159, 159, 160, 149, 109, 57, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 17, 160, 133, 158, 159, 156, 133, 101, 88, 122, 150, 159, 159, 150, 110, 57, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 160, 133, 158, 159, 145, 83, 115, 108, 164, 132, 158, 159, - 150, 110, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 161, 134, 158, 159, 140, 210, 96, 109, 134, - 127, 156, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 161, 134, 158, 159, 146, 96, - 123, 117, 211, 135, 158, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 163, 134, 158, - 158, 158, 139, 116, 110, 129, 153, 159, 159, 150, 111, 58, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, - 142, 126, 154, 160, 159, 159, 153, 151, 158, 160, 159, 160, 143, 101, 48, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 101, 27, 130, 144, 145, 145, 145, 145, 145, 145, 145, 140, 116, 129, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 112, 103, 41, 83, 84, 84, 84, 84, 84, 84, 85, 73, 129, 41, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 104, 107, 107, 109, 109, 109, 109, 109, 109, 109, 109, 108, 100, - 106, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 - }; - byte[] expectedU = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 120, 112, 106, 112, 112, 116, - 124, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 108, 90, 78, 79, 79, 79, 84, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 103, 77, 70, 79, 81, 70, 72, 105, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 102, 77, 75, 125, 91, 78, 72, 105, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 102, 77, 72, 92, 94, 74, 72, 105, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 115, - 84, 74, 75, 76, 74, 79, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 120, 240, 124, 123, 123, 123, 132, 148, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 110, 226, 185, 225, 56, - 48, 209, 111, 254, 56, 74, 116, 56, 222, 107, 255, 163, 237, 83, 1, 99, 241, 23, 1, 128, 186, 211, - 52, 84, 206, 135, 202, 159, 178, 4, 118, 130, 203, 127, 164, 82, 203, 119, 36, 123, 187, 82, 188, - 237, 245, 42, 15, 46, 92, 94, 191, 244, 175, 146, 126, 163, 13, 56, 67, 113, 84, 4, 48, 70, 9, 5, - 207, 208, 212, 229, 220, 34, 75, 138, 148, 253, 173, 238, 137, 121, 176, 79, 36, 185, 51, 17, 150, - 194, 218, 92, 83, 147, 117, 107, 59, 202, 241, 94, 251, 31, 109, 159, 10, 24, 139, 127, 208, 0, 95, - 128, 236, 27, 97, 149, 231, 77, 79, 253, 9, 154, 137, 50, 187, 183, 43, 234, 147, 85, 145, 3, 54, - 146, 151, 7, 255, 131, 208, 141, 202, 155, 31, 254, 95, 68, 71, 9, 5, 255, 83, 83, 151, 119, 75, 44, - 41, 226, 40, 81, 71, 117, 56, 92, 255, 7, 151, 187, 35, 145, 214, 74, 125, 222, 81, 146, 136, 131, - 119, 202, 241, 126, 251, 31, 109, 175, 5, 2, 174, 0, 233, 55, 60, 171, 54, 22, 21, 27, 132, 254, - 150, 200, 193, 119, 87, 47, 207, 78, 57, 129, 190, 13, 255, 69, 131, 40, 113, 71, 241, 47, 178, 195, - 184, 27, 45, 55, 208, 156, 22, 1, 140, 210, 148, 67, 237, 238, 195, 245, 118, 157, 149, 23, 14, 164, - 122, 19, 235, 95, 111, 52, 22, 198, 61, 182, 20, 113, 148, 72, 8, 224, 83, 113, 35, 180, 242, 178, - 33, 247, 179, 52, 208, 169, 136, 108, 90, 203, 250, 235, 222, 75, 22, 176, 13, 237, 215, 21, 205, - 61, 209, 139, 53, 112, 39, 244, 158, 138, 0, 250, 40, 161, 88, 157, 107, 234, 242, 110, 245, 226, - 130, 37, 141, 218, 42, 195, 81, 178, 39, 55, 68, 219, 75, 89, 69, 202, 79, 207, 140, 172, 56, 74, - 116, 56, 209, 111, 21, 227, 168, 69, 38, 75, 127, 208, 8, 160, 234, 70, 93, 245, 230, 54, 109, 245, - 166, 225, 114, 6, 99, 99, 93, 254, 106, 175, 136, 93, 32, 216, 168, 20, 220, 97, 120, 95, 32, 228, - 27, 31, 120, 78, 220, 43, 2, 40, 161, 132, 162, 187, 222, 83, 20, 79, 67, 179, 99, 43, 158, 214, 82, - 42, 239, 167, 6, 141, 142, 247, 148, 92, 45, 234, 105, 244, 175, 190, 207, 235, 76, 163, 133, 47, 4, - 0, 38, 79, 8, 220, 85, 58, 182, 49, 234, 168, 102, 27, 198, 183, 186, 218, 251, 215, 101, 194, 43, - 191, 48, 157, 112, 185, 40, 42, 55, 25, 135, 114, 1, 253, 82, 1, 206, 0, 86, 169, 239, 211, 26, 152, - 254, 205, 149, 233, 48, 223, 215, 234, 95, 251, 91, 253, 125, 127, 171, 187, 31, 104, 119, 15, 87, - 31, 255, 253, 173, 238, 177, 163, 58, 92, 38, 123, 114, 85, 69, 202, 35, 21, 240, 69, 252, 32, 165, - 138, 233, 54, 196, 190, 29, 152, 108, 155, 217, 131, 164, 29, 145, 184, 173, 172, 128, 182, 129, - 127, 221, 239, 10, 239, 93, 102, 254, 137, 244, 69, 250, 95, 86, 1, 250, 1, 72, 227, 28, 19, 1, 47, - 107, 192, 211, 192, 60, 218, 135, 205, 143, 118, 33, 129, 22, 199, 187, 176, 233, 250, 217, 91, 145, - 114, 128, 39, 130, 200, 143, 44, 224, 66, 64, 33, 181, 202, 14, 244, 109, 83, 119, 241, 23, 245, 69, - 196, 225, 173, 211, 104, 148, 207, 252, 209, 40, 230, 103, 235, 85, 138, 182, 205, 104, 229, 176, - 242, 243, 223, 93, 77, 1, 172, 82, 169, 255, 219, 141, 80, 203, 76, 232, 199, 154, 8, 219, 68, 132, - 173, 32, 56, 220, 90, 241, 122, 184, 16, 252, 190, 70, 192, 222, 118, 61, 111, 239, 223, 93, 169, - 239, 74, 254, 162, 99, 87, 28, 23, 243, 96, 110, 246, 191, 23, 123, 160, 226, 247, 47, 186, 42, 38, - 34, 187, 181, 246, 158, 84, 130, 52, 192, 1, 160, 209, 83, 183, 124, 118, 86, 89, 239, 226, 59, 76, - 102, 249, 79, 122, 15, 68, 39, 235, 203, 195, 187, 250, 41, 2, 71, 250, 229, 8, 241, 152, 189, 116, - 241, 32, 25, 246, 36, 117, 34, 197, 111, 249, 240, 2, 213, 221, 130, 39, 255, 143, 223, 118, 254, - 153, 212, 63, 64 - }; - byte[] expectedV = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 127, 126, 126, 126, 127, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 126, 123, 122, 122, 123, 122, 123, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 122, 121, 122, 122, 121, 122, - 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 125, 122, 122, 128, 124, 122, 121, 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 125, 122, 121, 124, 124, 122, 122, - 125, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 126, 123, 122, 122, 122, 122, 123, 127, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 110, 129, 129, 129, 129, 130, - 124, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 110, 151, 106, 153, 122, 146, 92, 195, 240, 240, 240, 240, 240, 240, 240, 181, 122, 125, 144, 134, + 109, 92, 151, 114, 240, 240, 240, 240, 240, 240, 240, 184, 124, 151, 137, 112, 159, 155, 124, 130, + 155, 155, 155, 155, 155, 155, 155, 135, 134, 96, 89, 142, 135, 106, 144, 118, 81, 81, 81, 81, 81, + 81, 81, 109, 103, 150, 150, 111, 138, 127, 124, 142, 81, 81, 81, 81, 81, 81, 81, 71, 153, 129, 119, + 140, 165, 126, 129, 172, 101, 101, 101, 101, 101, 101, 101, 114, 107, 128, 118, 124, 108, 107, 131, + 137, 110, 110, 110, 110, 110, 110, 110, 113, 135, 151, 127, 115, 133, 139, 123, 118, 110, 110, 110, + 110, 110, 110, 110, 125, 156, 124, 141, 164, 141, 150, 123, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // act From 84572d887fc20f59ce5df516fa69a063ddebe699 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 7 Jul 2021 21:47:20 +0200 Subject: [PATCH 313/359] Remove bitmap with negative height from test, because reference decoder seems to have a problem with it --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index e64d8452f..7eb0febfc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -55,7 +55,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] + [WithFile(Car, PixelTypes.Rgba32)] + [WithFile(F, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_MiscellaneousBitmaps_WithLimitedAllocatorBufferCapacity( TestImageProvider provider) { From cd0148c6a99837f09391b4923db066dd7b47a4de Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 7 Jul 2021 22:02:10 +0200 Subject: [PATCH 314/359] Run gif metadata tests serial with decoder and encoder tests --- tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index e209586f5..d3aa4b913 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -13,6 +13,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Gif { + [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifMetadataTests { From 51bf965f8964f0ac6caa0f2fc401adb3a2d7a141 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 8 Jul 2021 18:22:26 +0200 Subject: [PATCH 315/359] Fix solution file, test images got messed up --- ImageSharp.sln | 50 ++++++---------- .../Formats/WebP/YuvConversionTests.cs | 59 ++++++++++--------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index aac624bde..0ead02ceb 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28902.138 @@ -379,19 +379,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913 tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml - EndProjectSection -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}" ProjectSection(SolutionItems) = preProject tests\Images\Input\WebP\alpha_color_cache.webp = tests\Images\Input\WebP\alpha_color_cache.webp @@ -416,13 +403,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp tests\Images\Input\WebP\bug3.webp = tests\Images\Input\WebP\bug3.webp tests\Images\Input\WebP\color_cache_bits_11.webp = tests\Images\Input\WebP\color_cache_bits_11.webp - tests\Images\Input\WebP\grid.bmp = tests\Images\Input\WebP\grid.bmp - tests\Images\Input\WebP\grid.pam = tests\Images\Input\WebP\grid.pam - tests\Images\Input\WebP\grid.pgm = tests\Images\Input\WebP\grid.pgm - tests\Images\Input\WebP\grid.png = tests\Images\Input\WebP\grid.png - tests\Images\Input\WebP\grid.ppm = tests\Images\Input\WebP\grid.ppm - tests\Images\Input\WebP\grid.tiff = tests\Images\Input\WebP\grid.tiff - tests\Images\Input\WebP\libwebp_tests.md5 = tests\Images\Input\WebP\libwebp_tests.md5 tests\Images\Input\WebP\lossless1.webp = tests\Images\Input\WebP\lossless1.webp tests\Images\Input\WebP\lossless2.webp = tests\Images\Input\WebP\lossless2.webp tests\Images\Input\WebP\lossless3.webp = tests\Images\Input\WebP\lossless3.webp @@ -474,12 +454,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\lossy_extreme_probabilities.webp = tests\Images\Input\WebP\lossy_extreme_probabilities.webp tests\Images\Input\WebP\lossy_q0_f100.webp = tests\Images\Input\WebP\lossy_q0_f100.webp tests\Images\Input\WebP\near_lossless_75.webp = tests\Images\Input\WebP\near_lossless_75.webp - tests\Images\Input\WebP\peak.bmp = tests\Images\Input\WebP\peak.bmp - tests\Images\Input\WebP\peak.pam = tests\Images\Input\WebP\peak.pam - tests\Images\Input\WebP\peak.pgm = tests\Images\Input\WebP\peak.pgm tests\Images\Input\WebP\peak.png = tests\Images\Input\WebP\peak.png - tests\Images\Input\WebP\peak.ppm = tests\Images\Input\WebP\peak.ppm - tests\Images\Input\WebP\peak.tiff = tests\Images\Input\WebP\peak.tiff tests\Images\Input\WebP\segment01.webp = tests\Images\Input\WebP\segment01.webp tests\Images\Input\WebP\segment02.webp = tests\Images\Input\WebP\segment02.webp tests\Images\Input\WebP\segment03.webp = tests\Images\Input\WebP\segment03.webp @@ -489,9 +464,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\small_31x13.webp = tests\Images\Input\WebP\small_31x13.webp tests\Images\Input\WebP\test-nostrong.webp = tests\Images\Input\WebP\test-nostrong.webp tests\Images\Input\WebP\test.webp = tests\Images\Input\WebP\test.webp - tests\Images\Input\WebP\test_cwebp.sh = tests\Images\Input\WebP\test_cwebp.sh - tests\Images\Input\WebP\test_dwebp.sh = tests\Images\Input\WebP\test_dwebp.sh - tests\Images\Input\WebP\test_lossless.sh = tests\Images\Input\WebP\test_lossless.sh tests\Images\Input\WebP\very_short.webp = tests\Images\Input\WebP\very_short.webp tests\Images\Input\WebP\vp80-00-comprehensive-001.webp = tests\Images\Input\WebP\vp80-00-comprehensive-001.webp tests\Images\Input\WebP\vp80-00-comprehensive-002.webp = tests\Images\Input\WebP\vp80-00-comprehensive-002.webp @@ -549,7 +521,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\vp80-05-sharpness-1439.webp = tests\Images\Input\WebP\vp80-05-sharpness-1439.webp tests\Images\Input\WebP\vp80-05-sharpness-1440.webp = tests\Images\Input\WebP\vp80-05-sharpness-1440.webp tests\Images\Input\WebP\vp80-05-sharpness-1443.webp = tests\Images\Input\WebP\vp80-05-sharpness-1443.webp -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tga", "Tga", "{5DFC394F-136F-4B76-9BCA-3BA786515EFC}" + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml + EndProjectSection +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tga", "Tga", "{FCB65777-1D97-42DD-9ECC-96732D495D5C}" ProjectSection(SolutionItems) = preProject tests\Images\Input\Tga\16bit_noalphabits.tga = tests\Images\Input\Tga\16bit_noalphabits.tga tests\Images\Input\Tga\16bit_rle_noalphabits.tga = tests\Images\Input\Tga\16bit_rle_noalphabits.tga @@ -680,12 +667,13 @@ Global {6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} {39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0} {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {FCB65777-1D97-42DD-9ECC-96732D495D5C} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index db2b73878..271a48c90 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -146,10 +146,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - // Make the image completely transparent. + // Make half of the the image transparent. image.Mutate(c => c.ProcessPixelRowsAsVector4(row => { - for (int x = 0; x < row.Length; x++) + int halfWidth = row.Length / 2; + for (int x = 0; x < halfWidth; x++) { row[x] = new Vector4(row[x].X, row[x].Y, row[x].Z, 0.0f); } @@ -206,42 +207,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; byte[] expectedU = { - 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, - 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, 240, 139, 240, 139, - 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 134, 240, 139, 240, 139, 240, 139, 240, 139, - 128, 128, 128, 128, 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 133, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 115, - 174, 157, 172, 129, 191, 129, 204, 112, 90, 90, 90, 90, 90, 90, 90, 95, 155, 95, 116, 142, 98, 155, - 105, 97, 90, 90, 90, 90, 90, 90, 90, 127, 151, 114, 172, 123, 133, 129, 115, 171, 96, 96, 96, 96, - 96, 96, 96, 116, 93, 131, 127, 112, 139, 93, 160, 87, 91, 91, 91, 91, 91, 91, 91, 100, 129, 135, - 110, 148, 107, 139, 136, 110, 91, 91, 91, 91, 91, 91, 91, 96, 164, 116, 147, 131, 127, 165, 107, - 128, 159, 159, 159, 159, 159, 159, 159, 162, 120, 114, 140, 86, 143, 112, 105, 161, 240, 240, 240, - 240, 240, 240, 240, 191, 112, 160, 110, 140, 158, 103, 159, 138, 240, 240, 240, 240, 240, 240, 240, - 175, 141, 114, 140, 159, 81, 158, 136, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, + 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, + 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, + 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 139, + 229, 146, 204, 132, 199, 131, 204, 135, 90, 90, 90, 90, 90, 90, 90, 100, 161, 92, 116, 141, 99, 155, + 113, 97, 90, 90, 90, 90, 90, 90, 90, 173, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, + 96, 96, 148, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 108, 134, 130, 112, + 149, 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 107, 164, 117, 149, 127, 128, 166, 107, 129, + 159, 159, 159, 159, 159, 159, 159, 161, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, + 240, 240, 240, 137, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 109, + 150, 108, 140, 161, 80, 157, 162, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; byte[] expectedV = { - 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 157, - 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 160, 110, 189, 110, 189, - 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 160, 110, 189, 110, 189, 110, 189, 110, 189, - 128, 128, 128, 128, 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 153, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 173, - 110, 151, 106, 153, 122, 146, 92, 195, 240, 240, 240, 240, 240, 240, 240, 181, 122, 125, 144, 134, - 109, 92, 151, 114, 240, 240, 240, 240, 240, 240, 240, 184, 124, 151, 137, 112, 159, 155, 124, 130, - 155, 155, 155, 155, 155, 155, 155, 135, 134, 96, 89, 142, 135, 106, 144, 118, 81, 81, 81, 81, 81, - 81, 81, 109, 103, 150, 150, 111, 138, 127, 124, 142, 81, 81, 81, 81, 81, 81, 81, 71, 153, 129, 119, - 140, 165, 126, 129, 172, 101, 101, 101, 101, 101, 101, 101, 114, 107, 128, 118, 124, 108, 107, 131, - 137, 110, 110, 110, 110, 110, 110, 110, 113, 135, 151, 127, 115, 133, 139, 123, 118, 110, 110, 110, - 110, 110, 110, 110, 125, 156, 124, 141, 164, 141, 150, 123, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, + 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, + 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, + 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 188, + 109, 172, 108, 164, 119, 152, 92, 189, 240, 240, 240, 240, 240, 240, 240, 104, 121, 131, 142, 135, + 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 122, 136, 148, 137, 113, 157, 155, 121, 130, + 155, 155, 155, 155, 155, 155, 155, 109, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, + 81, 81, 143, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 63, 147, 133, 119, + 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 135, 109, 129, 122, 124, 107, 108, 128, + 138, 110, 110, 110, 110, 110, 110, 110, 117, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, + 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, From be36ff691155c5b6119f8d8255b85decd19493c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 9 Jul 2021 20:58:02 +0200 Subject: [PATCH 316/359] Add near lossless encoding mode --- .../Formats/WebP/IWebpEncoderOptions.cs | 12 ++ .../Formats/WebP/Lossless/LosslessUtils.cs | 1 + .../Formats/WebP/Lossless/NearLosslessEnc.cs | 129 ++++++++++++++++++ .../Formats/WebP/Lossless/PredictorEncoder.cs | 89 ++++++++---- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 44 ++++-- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 6 + .../Formats/WebP/WebpEncoderCore.cs | 33 ++++- .../Formats/GeneralFormatTests.cs | 3 +- .../Formats/WebP/WebpEncoderTests.cs | 117 +++++++++------- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/rgb_pattern.png | 3 + 11 files changed, 349 insertions(+), 89 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs create mode 100644 tests/Images/Input/WebP/rgb_pattern.png diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index dad67f804..6c8449772 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -54,5 +54,17 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The default value is false. /// bool Exact { get; } + + /// + /// Gets a value indicating whether near lossless mode should be used. + /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. + /// + bool NearLossless { get; } + + /// + /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results. + /// + int NearLosslessQuality { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index d6ba6e481..4b3cce9af 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -770,6 +770,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless m.RedToBlue = (byte)((colorCode >> 16) & 0xff); } + // Converts near lossless quality into max number of bits shaved off. // 100 -> 0 // 80..99 -> 1 // 60..79 -> 2 diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs new file mode 100644 index 000000000..4c035a647 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -0,0 +1,129 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Webp.Lossless; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee + /// of maximum deviation between original and resulting pixel values. + /// + internal static class NearLosslessEnc + { + private const int MinDimForNearLossless = 64; + + public static void ApplyNearLossless(int xSize, int ySize, int quality, Span argbSrc, Span argbDst, int stride) + { + uint[] copyBuffer = new uint[xSize * 3]; + int limitBits = LosslessUtils.NearLosslessBits(quality); + + // For small icon images, don't attempt to apply near-lossless compression. + if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) + { + for (int i = 0; i < ySize; ++i) + { + argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); + } + + return; + } + + NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); + for (int i = limitBits - 1; i != 0; --i) + { + NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); + } + } + + // Adjusts pixel values of image with given maximum error. + private static void NearLossless(int xSize, int ySize, Span argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst) + { + int x, y; + int limit = 1 << limitBits; + Span prevRow = copyBuffer; + Span currRow = copyBuffer.Slice(xSize, xSize); + Span nextRow = copyBuffer.Slice(xSize * 2, xSize); + argbSrc.Slice(0, xSize).CopyTo(currRow); + argbSrc.Slice(xSize, xSize).CopyTo(nextRow); + + int srcOffset = 0; + int dstOffset = 0; + for (y = 0; y < ySize; ++y) + { + if (y == 0 || y == ySize - 1) + { + argbSrc.Slice(srcOffset, xSize).CopyTo(argbDst.Slice(dstOffset, xSize)); + } + else + { + argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); + argbDst[dstOffset] = argbSrc[srcOffset]; + argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; + for (x = 1; x < xSize - 1; ++x) + { + if (IsSmooth(prevRow, currRow, nextRow, x, limit)) + { + argbDst[dstOffset + x] = currRow[x]; + } + else + { + argbDst[dstOffset + x] = ClosestDiscretizedArgb(currRow[x], limitBits); + } + } + } + + Span temp = prevRow; + prevRow = currRow; + currRow = nextRow; + nextRow = temp; + srcOffset += stride; + dstOffset += xSize; + } + } + + // Applies FindClosestDiscretized to all channels of pixel. + private static uint ClosestDiscretizedArgb(uint a, int bits) => + (FindClosestDiscretized(a >> 24, bits) << 24) | + (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | + (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | + FindClosestDiscretized(a & 0xff, bits); + + private static uint FindClosestDiscretized(uint a, int bits) + { + uint mask = (1u << bits) - 1; + uint biased = a + (mask >> 1) + ((a >> bits) & 1); + if (biased > 0xff) + { + return 0xff; + } + + return biased & ~mask; + } + + private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) + { + // Check that all pixels in 4-connected neighborhood are smooth. + return IsNear(currRow[ix], currRow[ix - 1], limit) && + IsNear(currRow[ix], currRow[ix + 1], limit) && + IsNear(currRow[ix], prevRow[ix], limit) && + IsNear(currRow[ix], nextRow[ix], limit); + } + + // Checks if distance between corresponding channel values of pixels a and b is within the given limit. + private static bool IsNear(uint a, uint b, int limit) + { + for (int k = 0; k < 4; ++k) + { + int delta = (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff); + if (delta >= limit || delta <= -limit) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 9f666ff6a..e060bbc10 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span bgra, Span bgraScratch, Span image, + bool nearLossless, int nearLosslessQuality, bool exact, bool usedSubtractGreen) @@ -71,6 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless maxQuantization, exact, usedSubtractGreen, + nearLossless, image); image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); @@ -86,7 +88,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bgra, maxQuantization, exact, - usedSubtractGreen); + usedSubtractGreen, + nearLossless); } public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span bgra, Span image) @@ -175,6 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxQuantization, bool exact, bool usedSubtractGreen, + bool nearLossless, Span modes) { const int numPredModes = 14; @@ -242,18 +246,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // pixel to the right in all cases except at the bottom right corner of // the image (wrapping to the leftmost pixel of the next row if it does // not exist in the currentRow). - Span src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); + int offset = (y * width) + contextStartX; + Span src = argb.Slice(offset, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); Span dst = currentRow.Slice(contextStartX); src.CopyTo(dst); - // TODO: Source wraps this in conditional - // WEBP_NEAR_LOSSLESS == 1 - if (maxQuantization > 1 && y >= 1 && y + 1 < height) + if (nearLossless) { - MaxDiffsForRow(contextWidth, width, argb.Slice((y * width) + contextStartX), maxDiffs.Slice(contextStartX), usedSubtractGreen); + if (maxQuantization > 1 && y >= 1 && y + 1 < height) + { + MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs.Slice(contextStartX), usedSubtractGreen); + } } - GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, residuals); + GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, nearLossless, residuals); for (int relativeX = 0; relativeX < maxX; ++relativeX) { UpdateHisto(histoArgb, residuals[relativeX]); @@ -316,6 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxQuantization, bool exact, bool usedSubtractGreen, + bool nearLossless, Span output) { if (exact) @@ -388,18 +395,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) + if (nearLossless) { - residual = LosslessUtils.SubPixels(currentRow[x], predict); + if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) + { + residual = LosslessUtils.SubPixels(currentRow[x], predict); + } + else + { + residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); + + // Update the source image. + currentRow[x] = LosslessUtils.AddPixels(predict, residual); + + // x is never 0 here so we do not need to update upperRow like below. + } } else { - residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); - - // Update the source image. - currentRow[x] = LosslessUtils.AddPixels(predict, residual); - - // x is never 0 here so we do not need to update upperRow like below. + residual = LosslessUtils.SubPixels(currentRow[x], predict); } if ((currentRow[x] & MaskAlpha) == 0) @@ -534,7 +548,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// residuals to multiples of quantization levels up to max_quantization /// (the actual quantization level depends on smoothness near the given pixel). /// - private static void CopyImageWithPrediction(int width, int height, int bits, Span modes, Span argbScratch, Span argb, int maxQuantization, bool exact, bool usedSubtractGreen) + private static void CopyImageWithPrediction( + int width, + int height, + int bits, + Span modes, + Span argbScratch, + Span argb, + int maxQuantization, + bool exact, + bool usedSubtractGreen, + bool nearLossless) { int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); @@ -566,7 +590,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless lowerMaxDiffs = tmp8; if (y + 2 < height) { - MaxDiffsForRow(width, width, argb.Slice((y + 1) * width), lowerMaxDiffs, usedSubtractGreen); + MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); } } @@ -592,6 +616,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless maxQuantization, exact, usedSubtractGreen, + nearLossless, argb.Slice((y * width) + x)); x = xEnd; @@ -687,15 +712,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - private static void MaxDiffsForRow(int width, int stride, Span argb, Span maxDiffs, bool usedSubtractGreen) + private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span maxDiffs, bool usedSubtractGreen) { if (width <= 2) { return; } - uint current = argb[0]; - uint right = argb[1]; + uint current = argb[offset]; + uint right = argb[offset + 1]; if (usedSubtractGreen) { current = AddGreenToBlueAndRed(current); @@ -704,11 +729,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int x = 1; x < width - 1; ++x) { - uint up = argb[-stride + x]; - uint down = argb[stride + x]; + uint up = argb[offset - stride + x]; + uint down = argb[offset + stride + x]; uint left = current; current = right; - right = argb[x + 1]; + right = argb[offset + x + 1]; if (usedSubtractGreen) { up = AddGreenToBlueAndRed(up); @@ -874,12 +899,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if ((byte)greenToRed == prevX.GreenToRed) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if ((byte)greenToRed == prevY.GreenToRed) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if (greenToRed == 0) @@ -898,22 +925,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); if ((byte)greenToBlue == prevX.GreenToBlue) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if ((byte)greenToBlue == prevY.GreenToBlue) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if ((byte)redToBlue == prevX.RedToBlue) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if ((byte)redToBlue == prevY.RedToBlue) { - curDiff -= 3; // Favor keeping the areas locally similar. + // Favor keeping the areas locally similar. + curDiff -= 3; } if (greenToBlue == 0) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 5b86cd4c2..678eb696a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; +using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -60,6 +61,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private readonly bool exact; + /// + /// Indicating whether near lossless mode should be used. + /// + private readonly bool nearLossless; + + /// + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// + private readonly int nearLosslessQuality; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -76,7 +87,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. - public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact) + /// Indicating whether near lossless mode should be used. + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact, bool nearLossless, int nearLosslessQuality) { int pixelCount = width * height; int initialSize = pixelCount * 2; @@ -86,6 +99,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); this.exact = exact; + this.nearLossless = nearLossless; + this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.EncodedData = memoryAllocator.Allocate(pixelCount); @@ -251,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int width = image.Width; int height = image.Height; - ReadOnlySpan bgra = this.Bgra.GetSpan(); + Span bgra = this.Bgra.GetSpan(); Span encodedData = this.EncodedData.GetSpan(); // Analyze image (entropy, numPalettes etc). @@ -278,7 +293,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.CacheBits = 0; this.ClearRefs(); - // TODO: Apply near-lossless preprocessing. + if (this.nearLossless) + { + // Apply near-lossless preprocessing. + bool useNearLossless = (this.nearLosslessQuality < 100) && !this.UsePalette && !this.UsePredictorTransform; + if (useNearLossless) + { + this.AllocateTransformBuffer(width, height); + NearLosslessEnc.ApplyNearLossless(width, height, this.nearLosslessQuality, bgra, bgra, width); + } + } // Encode palette. if (this.UsePalette) @@ -301,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (this.UsePredictorTransform) { - this.ApplyPredictFilter(this.CurrentWidth, height, this.UseSubtractGreenTransform); + this.ApplyPredictFilter(this.CurrentWidth, height); } if (this.UseCrossColorTransform) @@ -618,9 +642,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); } - private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) + private void ApplyPredictFilter(int width, int height) { - int nearLosslessStrength = 100; // TODO: for now always 100 + // We disable near-lossless quantization if palette is used. + int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; int predBits = this.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits); @@ -632,9 +657,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.EncodedData.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), + this.nearLossless, nearLosslessStrength, this.exact, - usedSubtractGreen); + this.UseSubtractGreenTransform); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); @@ -1709,10 +1735,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra // pixel in each, plus 2 regular scanlines of bytes. - int argbScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; + int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; int transformDataSize = (this.UsePredictorTransform || this.UseCrossColorTransform) ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; - this.BgraScratch = this.memoryAllocator.Allocate(argbScratchSize); + this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize); this.TransformData = this.memoryAllocator.Allocate(transformDataSize); this.CurrentWidth = width; } diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index ae0fb5122..eb7148386 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -35,6 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public bool Exact { get; set; } + /// + public bool NearLossless { get; set; } + + /// + public int NearLosslessQuality { get; set; } = 100; + /// public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 09a319de8..b515bd48b 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -58,6 +58,16 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly bool exact; + /// + /// Indicating whether near lossless mode should be used. + /// + private readonly bool nearLossless; + + /// + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// + private readonly int nearLosslessQuality; + /// /// The global configuration. /// @@ -78,6 +88,8 @@ namespace SixLabors.ImageSharp.Formats.Webp this.entropyPasses = options.EntropyPasses; this.filterStrength = options.FilterStrength; this.exact = options.Exact; + this.nearLossless = options.NearLossless; + this.nearLosslessQuality = options.NearLosslessQuality; } /// @@ -97,12 +109,29 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.lossy) { - using var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses, this.filterStrength); + using var enc = new Vp8Encoder( + this.memoryAllocator, + this.configuration, + image.Width, + image.Height, + this.quality, + this.method, + this.entropyPasses, + this.filterStrength); enc.Encode(image, stream); } else { - using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.exact); + using var enc = new Vp8LEncoder( + this.memoryAllocator, + this.configuration, + image.Width, + image.Height, + this.quality, + this.method, + this.exact, + this.nearLossless, + this.nearLosslessQuality); enc.Encode(image, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index c0843a51b..bf13a9097 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -16,6 +16,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats { + [Collection("RunSerial")] public class GeneralFormatTests { /// @@ -152,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Formats using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) { - image.SaveAsTga(output); + image.SaveAsTiff(output); } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 4d9ff2cfb..6173ebfc3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -80,6 +80,75 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } + [Theory] + [WithFile(RgbTestPattern, PixelTypes.Rgba32, 85)] + [WithFile(RgbTestPattern, PixelTypes.Rgba32, 60)] + [WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)] + [WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)] + [WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)] + public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = false, + NearLossless = true, + NearLosslessQuality = nearLosslessQuality + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality)); + } + + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] + public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, int method) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = false, + Method = method, + Exact = true + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossless", "_m", method); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } + + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] + public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + var encoder = new WebpEncoder() { Lossy = false }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } + + [Fact] + public void Encode_Lossless_OneByOnePixel_Works() + { + // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. + using var image = new Image(1, 1); + var encoder = new WebpEncoder() { Lossy = false }; + using (var memStream = new MemoryStream()) + { + image.SaveAsWebp(memStream, encoder); + } + } + [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] @@ -148,30 +217,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] - public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, int method) - where TPixel : unmanaged, IPixel - { - var encoder = new WebpEncoder() - { - Lossy = false, - Method = method, - Exact = true - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossless", "_m", method); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } - [Theory] [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] @@ -184,30 +229,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] - public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - var encoder = new WebpEncoder() { Lossy = false }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } - - [Fact] - public void Encode_Lossless_OneByOnePixel_Works() - { - // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. - using var image = new Image(1, 1); - var encoder = new WebpEncoder() { Lossy = false }; - using (var memStream = new MemoryStream()) - { - image.SaveAsWebp(memStream, encoder); - } - } - private static ImageComparer GetComparer(int quality) { float tolerance = 0.01f; // ~1.0% diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5c86ffcd3..524ea9849 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -508,6 +508,7 @@ namespace SixLabors.ImageSharp.Tests // Test pattern images for testing the encoder. public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; + public const string RgbTestPattern = "WebP/rgb_pattern.png"; // Test image for encoding image with a palette. public const string Flag = "WebP/flag_of_germany.png"; diff --git a/tests/Images/Input/WebP/rgb_pattern.png b/tests/Images/Input/WebP/rgb_pattern.png new file mode 100644 index 000000000..d3c59cf88 --- /dev/null +++ b/tests/Images/Input/WebP/rgb_pattern.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1ffed99a3cc701fff7f63cdca32c437a3e03d2a8a178380744190636decb0f8 +size 12453 From 1c3110699e1de1bf12a38c20900edd910be25dec Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 11 Jul 2021 12:37:24 +0200 Subject: [PATCH 317/359] Add low effort path for method == 0 --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 137 ++++++++++-------- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 46 +++--- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 42 +++--- .../Formats/WebP/WebpEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/rgb_pattern.png | 4 +- tests/Images/Input/WebP/rgb_pattern_63x63.png | 3 + 7 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 tests/Images/Input/WebP/rgb_pattern_63x63.png diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index e060bbc10..c705a6b2d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private const float SpatialPredictorBias = 15.0f; + private const int PredLowEffort = 11; + /// /// Finds the best predictor for each tile, and converts the image to residuals /// with respect to predictions. If nearLosslessQuality < 100, applies @@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bool nearLossless, int nearLosslessQuality, bool exact, - bool usedSubtractGreen) + bool usedSubtractGreen, + bool lowEffort) { int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); @@ -55,27 +58,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless histo[i] = new int[256]; } - // TODO: Low Effort - for (int tileY = 0; tileY < tilesPerCol; ++tileY) + if (lowEffort) { - for (int tileX = 0; tileX < tilesPerRow; ++tileX) + for (int i = 0; i < tilesPerRow * tilesPerCol; ++i) { - int pred = GetBestPredictorForTile( - width, - height, - tileX, - tileY, - bits, - histo, - bgraScratch, - bgra, - maxQuantization, - exact, - usedSubtractGreen, - nearLossless, - image); - - image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); + image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); + } + } + else + { + for (int tileY = 0; tileY < tilesPerCol; ++tileY) + { + for (int tileX = 0; tileX < tilesPerRow; ++tileX) + { + int pred = GetBestPredictorForTile( + width, + height, + tileX, + tileY, + bits, + histo, + bgraScratch, + bgra, + maxQuantization, + exact, + usedSubtractGreen, + nearLossless, + image); + + image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); + } } } @@ -89,7 +101,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless maxQuantization, exact, usedSubtractGreen, - nearLossless); + nearLossless, + lowEffort); } public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span bgra, Span image) @@ -558,18 +571,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxQuantization, bool exact, bool usedSubtractGreen, - bool nearLossless) + bool nearLossless, + bool lowEffort) { int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - // The width of upper_row and current_row is one pixel larger than image width + // The width of upperRow and currentRow is one pixel larger than image width // to allow the top right pixel to point to the leftmost pixel of the next row // when at the right edge. Span upperRow = argbScratch; Span currentRow = upperRow.Slice(width + 1); Span currentMaxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); - // TODO: This should be wrapped in a condition? Span lowerMaxDiffs = currentMaxDiffs.Slice(width); for (int y = 0; y < height; ++y) { @@ -579,47 +592,53 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); src.CopyTo(currentRow); - // TODO: Near lossless conditional? - if (maxQuantization > 1) + if (lowEffort) { - // Compute max_diffs for the lower row now, because that needs the - // contents of bgra for the current row, which we will overwrite with - // residuals before proceeding with the next row. - Span tmp8 = currentMaxDiffs; - currentMaxDiffs = lowerMaxDiffs; - lowerMaxDiffs = tmp8; - if (y + 2 < height) - { - MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); - } + PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb.Slice(y * width)); } - - for (int x = 0; x < width;) + else { - int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); - int xEnd = x + (1 << bits); - if (xEnd > width) + if (nearLossless && maxQuantization > 1) { - xEnd = width; + // Compute maxDiffs for the lower row now, because that needs the + // contents of bgra for the current row, which we will overwrite with + // residuals before proceeding with the next row. + Span tmp8 = currentMaxDiffs; + currentMaxDiffs = lowerMaxDiffs; + lowerMaxDiffs = tmp8; + if (y + 2 < height) + { + MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); + } } - GetResidual( - width, - height, - upperRow, - currentRow, - currentMaxDiffs, - mode, - x, - xEnd, - y, - maxQuantization, - exact, - usedSubtractGreen, - nearLossless, - argb.Slice((y * width) + x)); - - x = xEnd; + for (int x = 0; x < width;) + { + int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); + int xEnd = x + (1 << bits); + if (xEnd > width) + { + xEnd = width; + } + + GetResidual( + width, + height, + upperRow, + currentRow, + currentMaxDiffs, + mode, + x, + xEnd, + y, + maxQuantization, + exact, + usedSubtractGreen, + nearLossless, + argb.Slice((y * width) + x)); + + x = xEnd; + } } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 678eb696a..e337881cf 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -268,6 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span bgra = this.Bgra.GetSpan(); Span encodedData = this.EncodedData.GetSpan(); + bool lowEffort = this.method == 0; // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); @@ -286,7 +287,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); - this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; + if (lowEffort) + { + this.UseCrossColorTransform = false; + } + else + { + this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; + } + this.AllocateTransformBuffer(width, height); // Reset any parameter in the encoder that is set in the previous iteration. @@ -307,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Encode palette. if (this.UsePalette) { - this.EncodePalette(); + this.EncodePalette(lowEffort); this.MapImageFromPalette(width, height); // If using a color cache, do not have it bigger than the number of colors. @@ -325,12 +334,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (this.UsePredictorTransform) { - this.ApplyPredictFilter(this.CurrentWidth, height); + this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort); } if (this.UseCrossColorTransform) { - this.ApplyCrossColorFilter(this.CurrentWidth, height); + this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort); } this.bitWriter.PutBits(0, 1); // No more transforms. @@ -341,7 +350,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless height, useCache, crunchConfig, - this.CacheBits); + this.CacheBits, + lowEffort); // If we are better than what we already have. if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) @@ -462,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits) + private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort) { // bgra data with transformations applied. Span bgra = this.EncodedData.GetSpan(); @@ -487,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Calculate backward references from BGRA image. - this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); + this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height, lowEffort); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; @@ -568,7 +578,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.Refs[2], LosslessUtils.SubSampleSize(width, this.HistoBits), LosslessUtils.SubSampleSize(height, this.HistoBits), - this.quality); + this.quality, + lowEffort); } // Store Huffman codes. @@ -615,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Save the palette to the bitstream. /// - private void EncodePalette() + private void EncodePalette(bool lowEffort) { Span tmpPalette = new uint[WebpConstants.MaxPaletteSize]; int paletteSize = this.PaletteSize; @@ -629,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } tmpPalette[0] = palette[0]; - this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20); + this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20, lowEffort); } /// @@ -642,7 +653,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); } - private void ApplyPredictFilter(int width, int height) + private void ApplyPredictFilter(int width, int height, bool lowEffort) { // We disable near-lossless quantization if palette is used. int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; @@ -660,16 +671,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.nearLossless, nearLosslessStrength, this.exact, - this.UseSubtractGreenTransform); + this.UseSubtractGreenTransform, + lowEffort); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); this.bitWriter.PutBits((uint)(predBits - 2), 3); - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); } - private void ApplyCrossColorFilter(int width, int height) + private void ApplyCrossColorFilter(int width, int height, bool lowEffort) { int colorTransformBits = this.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); @@ -681,10 +693,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); } - private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) + private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality, bool lowEffort) { int cacheBits = 0; ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. @@ -702,7 +714,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Calculate backward references from the image pixels. - hashChain.Fill(this.memoryAllocator, bgra, quality, width, height); + hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort); Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( width, diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 0dafe8e9e..14b62b11a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// public int Size { get; } - public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int quality, int xSize, int ySize) + public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) { int size = xSize * ySize; int iterMax = GetMaxItersForQuality(quality); @@ -147,32 +147,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless pos = chain[basePosition]; int currLength; - // Heuristic: use the comparison with the above line as an initialization. - if (basePosition >= (uint)xSize) + if (!lowEffort) { - currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); + // Heuristic: use the comparison with the above line as an initialization. + if (basePosition >= (uint)xSize) + { + currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = (uint)xSize; + } + + iter--; + } + + // Heuristic: compare to the previous pixel. + currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); if (currLength > bestLength) { bestLength = currLength; - bestDistance = (uint)xSize; + bestDistance = 1; } iter--; - } - // Heuristic: compare to the previous pixel. - currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = 1; - } - - iter--; - - if (bestLength == BackwardReferenceEncoder.MaxLength) - { - pos = minPos - 1; + // Skip the for loop if we already have the maximum. + if (bestLength == BackwardReferenceEncoder.MaxLength) + { + pos = minPos - 1; + } } uint bestBgra = bgra.Slice(bgraStart)[bestLength]; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 6173ebfc3..2dee02a18 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -86,6 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)] [WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)] [WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)] + [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 524ea9849..406f65289 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -509,6 +509,7 @@ namespace SixLabors.ImageSharp.Tests public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; public const string RgbTestPattern = "WebP/rgb_pattern.png"; + public const string RgbTestPattern63x63 = "WebP/rgb_pattern_63x63.png"; // Test image for encoding image with a palette. public const string Flag = "WebP/flag_of_germany.png"; diff --git a/tests/Images/Input/WebP/rgb_pattern.png b/tests/Images/Input/WebP/rgb_pattern.png index d3c59cf88..554afd50c 100644 --- a/tests/Images/Input/WebP/rgb_pattern.png +++ b/tests/Images/Input/WebP/rgb_pattern.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1ffed99a3cc701fff7f63cdca32c437a3e03d2a8a178380744190636decb0f8 -size 12453 +oid sha256:5150fccc821b2196678771d46567e01af4702c53e4031aee24e2611cecfdf48e +size 12841 diff --git a/tests/Images/Input/WebP/rgb_pattern_63x63.png b/tests/Images/Input/WebP/rgb_pattern_63x63.png new file mode 100644 index 000000000..37a6e8812 --- /dev/null +++ b/tests/Images/Input/WebP/rgb_pattern_63x63.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a7826312b4dabc2d8a89bf84e501ddb0bcc09932c54d2dedb0c96909da94da8 +size 12071 From ee90c31c20b22564ac96298b1d9d1a361b3cf3b5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 11 Jul 2021 13:53:54 +0200 Subject: [PATCH 318/359] Better test image for near lossless --- .../ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs | 1 + .../ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 10 +++++----- tests/ImageSharp.Tests/TestImages.cs | 2 +- tests/Images/Input/WebP/rgb_pattern.png | 3 --- tests/Images/Input/WebP/rgb_pattern_100x100.png | 3 +++ 5 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 tests/Images/Input/WebP/rgb_pattern.png create mode 100644 tests/Images/Input/WebP/rgb_pattern_100x100.png diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index c80d9fc16..d1a71a9bd 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -17,6 +17,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { + [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffMetadataTests { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 2dee02a18..223f98629 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -81,11 +81,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(RgbTestPattern, PixelTypes.Rgba32, 85)] - [WithFile(RgbTestPattern, PixelTypes.Rgba32, 60)] - [WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)] - [WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)] - [WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 40)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 20)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 10)] [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 406f65289..37ee5c1c2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -508,7 +508,7 @@ namespace SixLabors.ImageSharp.Tests // Test pattern images for testing the encoder. public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; - public const string RgbTestPattern = "WebP/rgb_pattern.png"; + public const string RgbTestPattern100x100 = "WebP/rgb_pattern_100x100.png"; public const string RgbTestPattern63x63 = "WebP/rgb_pattern_63x63.png"; // Test image for encoding image with a palette. diff --git a/tests/Images/Input/WebP/rgb_pattern.png b/tests/Images/Input/WebP/rgb_pattern.png deleted file mode 100644 index 554afd50c..000000000 --- a/tests/Images/Input/WebP/rgb_pattern.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5150fccc821b2196678771d46567e01af4702c53e4031aee24e2611cecfdf48e -size 12841 diff --git a/tests/Images/Input/WebP/rgb_pattern_100x100.png b/tests/Images/Input/WebP/rgb_pattern_100x100.png new file mode 100644 index 000000000..789424dcb --- /dev/null +++ b/tests/Images/Input/WebP/rgb_pattern_100x100.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12f39b990367eb09ffbe69eb11bf970b5386e75a02a820e4740e66a079dda527 +size 30225 From 42656680723ebad6bb3331ac32d0141868d4825d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 11 Jul 2021 18:25:53 +0200 Subject: [PATCH 319/359] Swap best bitwriter and histo reference instead of cloning --- src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs | 9 +++------ src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 5 +++-- src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 6 +++--- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/rgb_pattern_80x80.png | 3 +++ 6 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 tests/Images/Input/WebP/rgb_pattern_80x80.png diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index c705a6b2d..0e36a6c45 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -294,12 +294,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (curDiff < bestDiff) { - // TODO: Consider swapping references - for (int i = 0; i < 4; i++) - { - histoArgb[i].AsSpan().CopyTo(bestHisto[i]); - } - + int[][] tmp = histoArgb; + histoArgb = bestHisto; + bestHisto = tmp; bestDiff = curDiff; bestMode = mode; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index e337881cf..497a09148 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -613,8 +613,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Keep track of the smallest image so far. if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) { - // TODO: This was done in the reference by swapping references, this will be slower. - bitWriterBest = this.bitWriter.Clone(); + Vp8LBitWriter tmp = this.bitWriter; + this.bitWriter = bitWriterBest; + bitWriterBest = tmp; } isFirstIteration = false; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 0e1d84243..63ed1f399 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -885,7 +885,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.SetCountDown(this.mbw * this.mbh); this.InitTop(); - // TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_)); + Array.Clear(this.BitCount, 0, this.BitCount.Length); } /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 223f98629..8831e6054 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -83,9 +83,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Theory] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 40)] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 20)] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 10)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 40)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 20)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 10)] [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 37ee5c1c2..1b624ae65 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -509,6 +509,7 @@ namespace SixLabors.ImageSharp.Tests public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; public const string RgbTestPattern100x100 = "WebP/rgb_pattern_100x100.png"; + public const string RgbTestPattern80x80 = "WebP/rgb_pattern_80x80.png"; public const string RgbTestPattern63x63 = "WebP/rgb_pattern_63x63.png"; // Test image for encoding image with a palette. diff --git a/tests/Images/Input/WebP/rgb_pattern_80x80.png b/tests/Images/Input/WebP/rgb_pattern_80x80.png new file mode 100644 index 000000000..d4722cfc1 --- /dev/null +++ b/tests/Images/Input/WebP/rgb_pattern_80x80.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4e27705d23ff33dbac5bdfe8e7e75a6eeda359ff343594fb07feb29abbc2fb5 +size 19393 From 8c3b021a98298be399e8886832811d40ec209c8a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Jul 2021 14:45:55 +0200 Subject: [PATCH 320/359] Add Spatial noise shaping option for the lossy encoder --- .../Formats/WebP/IWebpEncoderOptions.cs | 8 +++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 30 +++++++++++-------- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 ++ .../Formats/WebP/WebpEncoderCore.cs | 9 +++++- .../Formats/WebP/WebpEncoderTests.cs | 20 +++++++++++++ 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 6c8449772..2cf2cd311 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -39,6 +39,14 @@ namespace SixLabors.ImageSharp.Formats.Webp /// int EntropyPasses { get; } + /// + /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms + /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits. + /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect). + /// Defaults to 50. + /// + int SpatialNoiseShaping { get; } + /// /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index ee3f1a8a5..9da7217b8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private readonly int filterStrength; + /// + /// The spatial noise shaping. 0=off, 100=maximum. + /// + private readonly int spatialNoiseShaping; + /// /// A bit writer for writing lossy webp streams. /// @@ -94,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Quality/speed trade-off (0=fast, 6=slower-better). /// Number of entropy-analysis passes (in [1..10]). /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength) + public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength, int spatialNoiseShaping) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; @@ -104,6 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.method = Numerics.Clamp(method, 0, 6); this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); + this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll : (method >= 5) ? Vp8RdLevel.RdOptTrellis : (method >= 3) ? Vp8RdLevel.RdOptBasic @@ -353,7 +359,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. - bool doSearch = false; // TODO: doSearch hardcoded for now. + bool doSearch = targetSize > 0 || targetPsnr > 0; bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; int numPassLeft = this.entropyPasses; Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; @@ -663,8 +669,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int nb = this.SegmentHeader.NumSegments; Vp8SegmentInfo[] dqm = this.SegmentInfos; - int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. - double amp = WebpConstants.SnsToDq * snsStrength / 100.0d / 128.0d; + double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d; double cBase = QualityToCompression(quality / 100.0d); for (int i = 0; i < nb; ++i) { @@ -685,25 +690,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); // We rescale by the user-defined strength of adaptation. - this.DqUvAc = this.DqUvAc * snsStrength / 100; + this.DqUvAc = this.DqUvAc * this.spatialNoiseShaping / 100; // and make it safe. this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); // We also boost the dc-uv-quant a little, based on sns-strength, since - // U/V channels are quite more reactive to high quants (flat DC-blocks - // tend to appear, and are unpleasant). - this.DqUvDc = -4 * snsStrength / 100; - this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed + // U/V channels are quite more reactive to high quants (flat DC-blocks tend to appear, and are unpleasant). + this.DqUvDc = -4 * this.spatialNoiseShaping / 100; + this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed. this.DqY1Dc = 0; this.DqY2Dc = 0; this.DqY2Ac = 0; - // Initialize segments' filtering + // Initialize segments' filtering. this.SetupFilterStrength(); - this.SetupMatrices(dqm, snsStrength); + this.SetupMatrices(dqm); } private void SetupFilterStrength() @@ -784,9 +788,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy proba.NbSkip = 0; } - private void SetupMatrices(Vp8SegmentInfo[] dqm, int snsStrength) + private void SetupMatrices(Vp8SegmentInfo[] dqm) { - int tlambdaScale = (this.method >= 4) ? snsStrength : 0; + int tlambdaScale = (this.method >= 4) ? this.spatialNoiseShaping : 0; for (int i = 0; i < dqm.Length; ++i) { Vp8SegmentInfo m = dqm[i]; diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index eb7148386..e9b9fbbe6 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public int EntropyPasses { get; set; } + /// + public int SpatialNoiseShaping { get; set; } = 50; + /// public int FilterStrength { get; set; } = 60; diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index b515bd48b..ae7bce72f 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly int entropyPasses; + /// + /// Spatial Noise Shaping. 0=off, 100=maximum. + /// + private readonly int spatialNoiseShaping; + /// /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). /// @@ -86,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; + this.spatialNoiseShaping = options.SpatialNoiseShaping; this.filterStrength = options.FilterStrength; this.exact = options.Exact; this.nearLossless = options.NearLossless; @@ -117,7 +123,8 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality, this.method, this.entropyPasses, - this.filterStrength); + this.filterStrength, + this.spatialNoiseShaping); enc.Encode(image, stream); } else diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 8831e6054..8374342e8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -188,6 +188,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); } + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] + public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works(TestImageProvider provider, int snsStrength) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = true, + SpatialNoiseShaping = snsStrength + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_sns", snsStrength); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); + } + [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] From 70b616a5667958d51ac64c534a052dd310590d81 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Jul 2021 16:16:30 +0200 Subject: [PATCH 321/359] Cleanup --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 13 ++- .../Formats/WebP/BitReader/Vp8BitReader.cs | 2 +- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 18 ++-- src/ImageSharp/Formats/WebP/HistoIx.cs | 2 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 21 +++-- .../Formats/WebP/Lossless/ColorCache.cs | 20 +---- .../Formats/WebP/Lossless/CostManager.cs | 2 +- .../Formats/WebP/Lossless/CostModel.cs | 5 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 34 +++---- .../Formats/WebP/Lossless/HuffmanTree.cs | 2 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 31 +++---- .../Formats/WebP/Lossless/LosslessUtils.cs | 38 ++++---- .../Formats/WebP/Lossless/NearLosslessEnc.cs | 7 +- .../Formats/WebP/Lossless/PixOrCopy.cs | 60 ++++--------- .../Formats/WebP/Lossless/PredictorEncoder.cs | 18 ++-- .../Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 10 +-- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 54 +++++------ .../Formats/WebP/Lossless/Vp8LHashChain.cs | 14 +-- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 4 +- .../WebP/Lossless/WebpLosslessDecoder.cs | 51 +++++------ .../Formats/WebP/Lossy/LossyUtils.cs | 18 ++-- .../Formats/WebP/Lossy/PassStats.cs | 4 +- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 8 +- .../Formats/WebP/Lossy/Vp8Decoder.cs | 8 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 16 ++-- .../Formats/WebP/Lossy/Vp8EncProba.cs | 2 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 31 +++---- .../Formats/WebP/Lossy/Vp8Encoding.cs | 89 +++++++++---------- .../Formats/WebP/Lossy/Vp8Histogram.cs | 6 +- .../Formats/WebP/Lossy/Vp8Matrix.cs | 2 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 12 +-- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 4 +- .../Formats/WebP/Lossy/Vp8StatsArray.cs | 5 +- .../Formats/WebP/Lossy/WebpLossyDecoder.cs | 42 ++++----- .../Formats/WebP/Lossy/YuvConversion.cs | 4 +- src/ImageSharp/Formats/WebP/WebpConstants.cs | 2 +- .../Formats/WebP/WebpDecoderCore.cs | 8 +- src/ImageSharp/Formats/WebP/WebpFeatures.cs | 5 +- .../Formats/WebP/WebpImageFormatDetector.cs | 22 +---- .../Formats/WebP/WebpLookupTables.cs | 6 +- 41 files changed, 304 insertions(+), 398 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index ff3a31636..86380a070 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.Compressed == false) { Span dataSpan = this.Data.Memory.Span; - var pixelCount = this.Width * this.Height; + int pixelCount = this.Width * this.Height; if (dataSpan.Length < pixelCount) { WebpThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); @@ -222,8 +222,8 @@ namespace SixLabors.ImageSharp.Formats.Webp { // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. - int topRow = (this.AlphaFilterType == WebpAlphaFilterType.None || this.AlphaFilterType == WebpAlphaFilterType.Horizontal) ? 0 : this.LastRow; - int firstRow = (this.LastRow < topRow) ? topRow : this.LastRow; + int topRow = this.AlphaFilterType == WebpAlphaFilterType.None || this.AlphaFilterType == WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; + int firstRow = this.LastRow < topRow ? topRow : this.LastRow; if (lastRow > firstRow) { // Special method for paletted alpha data. @@ -402,16 +402,13 @@ namespace SixLabors.ImageSharp.Formats.Webp } [MethodImpl(InliningOptions.ShortMethod)] - private static byte GetAlphaValue(int val) - { - return (byte)((val >> 8) & 0xff); - } + private static byte GetAlphaValue(int val) => (byte)((val >> 8) & 0xff); [MethodImpl(InliningOptions.ShortMethod)] private static int GradientPredictor(byte a, byte b, byte c) { int g = a + b - c; - return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit + return (g & ~0xff) == 0 ? g : g < 0 ? 0 : 255; // clip to 8bit. } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 42168761a..5fc68e095 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader private void InitBitreader(uint size, int pos = 0) { - var posPlusSize = pos + size; + long posPlusSize = pos + size; this.range = 255 - 1; this.value = 0; this.bits = -8; // to load the very first 8 bits. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index f717bfe46..959fbc284 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter tab = WebpConstants.Cat6; } - var tabIdx = 0; + int tabIdx = 0; while (mask != 0) { this.PutBit(v & mask, tab[tabIdx++]); @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - var neededSize = this.pos + extraSize; + long neededSize = this.pos + extraSize; if (neededSize <= this.maxPos) { return; @@ -204,9 +204,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// public override void Finish() { - this.PutBits(0, 9 - this.nbBits); - this.nbBits = 0; // pad with zeroes. - this.Flush(); + this.PutBits(0, 9 - this.nbBits); + this.nbBits = 0; // pad with zeroes. + this.Flush(); } public void PutSegment(int s, Span p) @@ -352,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter if (value < 0) { - var valueToWrite = ((-value) << 1) | 1; + int valueToWrite = (-value << 1) | 1; this.PutBits((uint)valueToWrite, nbBits + 1); } else @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter // overflow -> propagate carry over pending 0xff's if (pos > 0) { - this.Buffer[pos - 1]++; + this.Buffer[pos - 1]++; } } @@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { for (int s = 0; s < 3; ++s) { - if (bitWriter.PutBitUniform((proba.Segments[s] != 255) ? 1 : 0) != 0) + if (bitWriter.PutBitUniform(proba.Segments[s] != 255 ? 1 : 0) != 0) { bitWriter.PutBits(proba.Segments[s], 8); } @@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter if (bitWriter.PutBitUniform(probas.UseSkipProba ? 1 : 0) != 0) { - bitWriter.PutBits(probas.SkipProba, 8); + bitWriter.PutBits(probas.SkipProba, 8); } } diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs index 3db720265..68b00394b 100644 --- a/src/ImageSharp/Formats/WebP/HistoIx.cs +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -31,6 +31,6 @@ namespace SixLabors.ImageSharp.Formats.Webp HistoPalette, - HistoTotal, // Must be last. + HistoTotal } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 99dbf2e44..ea9752db8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) { - Vp8LHashChain hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox; + Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); var histo = new Vp8LHistogram(worst, cacheBits); double bitCostTrace = histo.EstimateBits(); @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Best cache size. private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { - int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits; + int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; if (cacheBitsMax == 0) { // Local color cache is disabled. @@ -256,12 +256,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; - int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); + int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0); var costModel = new CostModel(literalArraySize); int offsetPrev = -1; int lenPrev = -1; double offsetCost = -1; - int firstOffsetIsConstant = -1; // initialized with 'impossible' value + int firstOffsetIsConstant = -1; // initialized with 'impossible' value. int reach = 0; var colorCache = new ColorCache(); @@ -273,8 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless costModel.Build(xSize, cacheBits, refs); var costManager = new CostManager(distArray, pixCount, costModel); - // We loop one pixel at a time, but store all currently best points to - // non-processed locations from this point. + // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. distArray[0] = 0; // Add first pixel as literal. @@ -474,10 +473,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int lenIni = len; int maxReach = 0; - int jMax = (i + lenIni >= pixCount) ? pixCount - 1 : i + lenIni; + int jMax = i + lenIni >= pixCount ? pixCount - 1 : i + lenIni; // Only start from what we have not checked already. - iLastCheck = (i > iLastCheck) ? i : iLastCheck; + iLastCheck = i > iLastCheck ? i : iLastCheck; // We know the best match for the current pixel but we try to find the // best matches for the current pixel AND the next one combined. @@ -640,7 +639,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Figure out if we should use the offset/length from the previous pixel // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. - bool usePrev = (bestLengthPrev > 1) && (bestLengthPrev < MaxLength); + bool usePrev = bestLengthPrev > 1 && bestLengthPrev < MaxLength; int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; bestLength = usePrev ? bestLengthPrev - 1 : 0; bestOffset = usePrev ? bestOffsetPrev : 0; @@ -663,7 +662,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int countsJ = counts[j]; if (countsJOffset != countsJ) { - currLength += (countsJOffset < countsJ) ? countsJOffset : countsJ; + currLength += countsJOffset < countsJ ? countsJOffset : countsJ; break; } @@ -728,7 +727,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); int rleLen = LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen); - int prevRowLen = (i < xSize) ? 0 : LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); + int prevRowLen = i < xSize ? 0 : LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); if (rleLen >= prevRowLen && rleLen >= MinLength) { refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index b629a6845..8596d8555 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -52,10 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The key to lookup. /// The color for the key. - public uint Lookup(int key) - { - return this.Colors[key]; - } + public uint Lookup(int key) => this.Colors[key]; /// /// Returns the index of the given color. @@ -73,24 +70,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The color. /// The index for the color. - public int GetIndex(uint bgra) - { - return HashPix(bgra, this.HashShift); - } + public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift); /// /// Adds a new color to the cache. /// /// The key. /// The color to add. - public void Set(uint key, uint bgra) - { - this.Colors[key] = bgra; - } + public void Set(uint key, uint bgra) => this.Colors[key] = bgra; - public static int HashPix(uint argb, int shift) - { - return (int)((argb * HashMul) >> shift); - } + public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index 4038555db..e93c1d2de 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public CostManager(ushort[] distArray, int pixCount, CostModel costModel) { - int costCacheSize = (pixCount > BackwardReferenceEncoder.MaxLength) ? BackwardReferenceEncoder.MaxLength : pixCount; + int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; this.CacheIntervals = new List(); this.CostCache = new List(); diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs index c210fa7d5..7f4d0307b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -70,10 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return this.Literal[literalIdx]; } - public double GetLiteralCost(uint v) - { - return this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; - } + public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index ece41e50e..6021579bb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -35,8 +35,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; int imageHistoRawSize = histoXSize * histoYSize; int entropyCombineNumBins = BinSize; - var mapTmp = new ushort[imageHistoRawSize]; - var clusterMappings = new ushort[imageHistoRawSize]; + ushort[] mapTmp = new ushort[imageHistoRawSize]; + ushort[] clusterMappings = new ushort[imageHistoRawSize]; var origHisto = new List(imageHistoRawSize); for (int i = 0; i < imageHistoRawSize; i++) { @@ -49,11 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Copies the histograms and computes its bitCost. histogramSymbols is optimized. int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); - var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); + bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; if (entropyCombine) { ushort[] binMap = mapTmp; - var numClusters = numUsed; + int numClusters = numUsed; double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); HistogramAnalyzeEntropyBin(imageHisto, binMap); @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // For some images, 'tryCombine' turns out to be false for a lot of // histogram pairs. In that case, we fallback to combining // histograms as usual to avoid increasing the header size. - bool tryCombine = (curCombo.TrivialSymbol != NonTrivialSym) || ((histograms[idx].TrivialSymbol == NonTrivialSym) && (histograms[first].TrivialSymbol == NonTrivialSym)); + bool tryCombine = curCombo.TrivialSymbol != NonTrivialSym || (histograms[idx].TrivialSymbol == NonTrivialSym && histograms[first].TrivialSymbol == NonTrivialSym); int maxCombineFailures = 32; if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) { @@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Create a mapping from a cluster id to its minimal version. - var clusterMax = 0; + int clusterMax = 0; clusterMappingsTmp.AsSpan().Fill(0); // Re-map the ids. @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { uint seed = 1; int triesWithNoSuccess = 0; - var numUsed = histograms.Count(h => h != null); + int numUsed = histograms.Count(h => h != null); int outerIters = numUsed; int numTriesNoSuccess = outerIters / 2; @@ -320,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxSize = 9; // Fill the initial mapping. - var mappings = new int[histograms.Count]; + int[] mappings = new int[histograms.Count]; for (int j = 0, iter = 0; iter < histograms.Count; iter++) { if (histograms[iter] == null) @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Collapse similar histograms. for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) { - double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; + double bestCost = histoPriorityList.Count == 0 ? 0.0d : histoPriorityList[0].CostDiff; int numTries = numUsed / 2; uint randRange = (uint)((numUsed - 1) * numUsed); @@ -354,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless idx2 = mappings[idx2]; // Calculate cost reduction on combination. - var currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost); + double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost); // Found a better pair? if (currCost < 0) @@ -374,10 +374,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Get the best histograms. - var bestIdx1 = histoPriorityList[0].Idx1; - var bestIdx2 = histoPriorityList[0].Idx2; + int bestIdx1 = histoPriorityList[0].Idx1; + int bestIdx2 = histoPriorityList[0].Idx2; - var mappingIndex = Array.IndexOf(mappings, bestIdx2); + int mappingIndex = Array.IndexOf(mappings, bestIdx2); Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); Span dst = mappings.AsSpan(mappingIndex); src.CopyTo(dst); @@ -554,7 +554,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Recompute each output. - var paletteCodeBits = output.First().PaletteCodeBits; + int paletteCodeBits = output.First().PaletteCodeBits; output.Clear(); for (int i = 0; i < outSize; i++) { @@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { double sumCost = h1.BitCost + h2.BitCost; pair.CostCombo = 0.0d; - h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out var cost); + h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out double cost); pair.CostCombo = cost; pair.CostDiff = pair.CostCombo - sumCost; } @@ -633,7 +633,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (pair.CostDiff < histoList[0].CostDiff) { // Replace the best pair. - var oldIdx = histoList.IndexOf(pair); + int oldIdx = histoList.IndexOf(pair); histoList[oldIdx] = histoList[0]; histoList[0] = pair; } @@ -642,7 +642,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) { a.Add(b, output); - output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol) ? a.TrivialSymbol : NonTrivialSym; + output.TrivialSymbol = a.TrivialSymbol == b.TrivialSymbol ? a.TrivialSymbol : NonTrivialSym; } private static double GetCombineCostFactor(int histoSize, int quality) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index 927e0e170..cd8be9aac 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - return (t1.Value < t2.Value) ? -1 : 1; + return t1.Value < t2.Value ? -1 : 1; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 18817a33b..2d94c9280 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint sum = 0; for (int i = 0; i < length + 1; ++i) { - var valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); + bool valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage) { if (stride >= 4 || (stride >= 3 && sum == 0)) @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { if (histogram[j] != 0) { - uint count = (histogram[j] < countMin) ? countMin : histogram[j]; + uint count = histogram[j] < countMin ? countMin : histogram[j]; tree[idx].TotalCount = (int)count; tree[idx].Value = j; tree[idx].PoolIndexLeft = -1; @@ -229,9 +229,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - var endIdx = k + 1; - var num = treeSize - k; - var startIdx = endIdx + num - 1; + int endIdx = k + 1; + int num = treeSize - k; + int startIdx = endIdx + num - 1; for (int i = startIdx; i >= endIdx; i--) { tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless tree[k].Value = -1; tree[k].PoolIndexLeft = treePoolSize - 1; tree[k].PoolIndexRight = treePoolSize - 2; - treeSize = treeSize + 1; + treeSize++; } SetBitDepths(tree, treePool, bitDepths, 0); @@ -284,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless k++; } - var runs = k - i; + int runs = k - i; if (value == 0) { tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); @@ -308,17 +308,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. - var sorted = new int[codeLengthsSize]; + int[] sorted = new int[codeLengthsSize]; int totalSize = 1 << rootBits; // total size root table + 2nd level table. int len; // current code length. int symbol; // symbol index in original or sorted table. - var counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - var offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. + int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) { - var codeLengthOfSymbol = codeLengths[symbol]; + int codeLengthOfSymbol = codeLengths[symbol]; if (codeLengthOfSymbol > WebpConstants.MaxAllowedCodeLength) { return 0; @@ -338,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (len = 1; len < WebpConstants.MaxAllowedCodeLength; ++len) { int codesOfLength = counts[len]; - if (codesOfLength > (1 << len)) + if (codesOfLength > 1 << len) { return 0; } @@ -381,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Fill in root table. for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) { - var countsLen = counts[len]; + int countsLen = counts[len]; numOpen <<= 1; numNodes += numOpen; numOpen -= counts[len]; @@ -652,9 +652,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Heuristics for selecting the stride ranges to collapse. /// - private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) - { - return Math.Abs(a - b) < 4; - } + private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 4b3cce9af..dc6f4843b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public static int MaxFindCopyLength(int len) => (len < BackwardReferenceEncoder.MaxLength) ? len : BackwardReferenceEncoder.MaxLength; + public static int MaxFindCopyLength(int len) => len < BackwardReferenceEncoder.MaxLength ? len : BackwardReferenceEncoder.MaxLength; public static int PrefixEncodeBits(int distance, ref int extraBits) { @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int i; for (i = 0; i + 8 <= numPixels; i += 8) { - var idx = p + i; + uint* idx = p + i; Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); Vector256 in0g0g = Avx2.Shuffle(input, mask); Vector256 output = Avx2.Add(input, in0g0g); @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int i; for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = p + i; + uint* idx = p + i; Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); Vector128 in0g0g = Ssse3.Shuffle(input, mask); Vector128 output = Sse2.Add(input, in0g0g); @@ -146,14 +146,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else if (Sse2.IsSupported) { - var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + byte mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; fixed (uint* p = pixelData) { int i; for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = p + i; + uint* idx = p + i; Vector128 input = Sse2.LoadVector128((ushort*)idx); Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g Vector128 b = Sse2.ShuffleLow(a, mask); @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int i; for (i = 0; i + 8 <= numPixels; i += 8) { - var idx = p + i; + uint* idx = p + i; Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); Vector256 in0g0g = Avx2.Shuffle(input, mask); Vector256 output = Avx2.Subtract(input, in0g0g); @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int i; for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = p + i; + uint* idx = p + i; Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); Vector128 in0g0g = Ssse3.Shuffle(input, mask); Vector128 output = Sse2.Subtract(input, in0g0g); @@ -238,14 +238,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else if (Sse2.IsSupported) { - var mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + byte mask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int numPixels = pixelData.Length; fixed (uint* p = pixelData) { int i; for (i = 0; i + 4 <= numPixels; i += 4) { - var idx = p + i; + uint* idx = p + i; Vector128 input = Sse2.LoadVector128((ushort*)idx); Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g Vector128 b = Sse2.ShuffleLow(a, mask); @@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; - var decodedPixelData = new uint[width * height]; + uint[] decodedPixelData = new uint[width * height]; int pixelDataPos = 0; for (int y = 0; y < height; ++y) { @@ -401,13 +401,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); var maskalphagreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); var maskredblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - var shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + byte shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); int idx; fixed (uint* src = data) { for (idx = 0; idx + 4 <= numPixels; idx += 4) { - var pos = src + idx; + uint* pos = src + idx; Vector128 input = Sse2.LoadVector128(pos); Vector128 a = Sse2.And(input.AsByte(), maskalphagreen); Vector128 b = Sse2.ShuffleLow(a.AsInt16(), shufflemask); @@ -466,13 +466,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); var maskalphagreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - var shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); + byte shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); fixed (uint* src = pixelData) { int idx; for (idx = 0; idx + 4 <= pixelData.Length; idx += 4) { - var pos = src + idx; + uint* pos = src + idx; Vector128 input = Sse2.LoadVector128(pos); Vector128 a = Sse2.And(input.AsByte(), maskalphagreen); Vector128 b = Sse2.ShuffleLow(a.AsInt16(), shufflemask); @@ -754,13 +754,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Fast calculation of log2(v) for integer input. /// - public static float FastLog2(uint v) => (v < LogLookupIdxMax) ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); + public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); /// /// Fast calculation of v * log2(v) for integer input. /// [MethodImpl(InliningOptions.ShortMethod)] - public static float FastSLog2(uint v) => (v < LogLookupIdxMax) ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); + public static float FastSLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); [MethodImpl(InliningOptions.ShortMethod)] public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) @@ -803,7 +803,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // The correction factor: log(1 + d) ~ d; for very small d values, so // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v // LOG_2_RECIPROCAL ~ 23/16 - var correction = (int)((23 * (origV & (y - 1))) >> 4); + int correction = (int)((23 * (origV & (y - 1))) >> 4); return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; } else @@ -855,7 +855,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; extraBits = highestBit - 1; - var code = (2 * highestBit) + secondHighestBit; + int code = (2 * highestBit) + secondHighestBit; return code; } @@ -1252,7 +1252,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); - return (paMinusPb <= 0) ? a : b; + return paMinusPb <= 0 ? a : b; } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs index 4c035a647..1539440f8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -102,14 +102,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return biased & ~mask; } - private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) - { - // Check that all pixels in 4-connected neighborhood are smooth. - return IsNear(currRow[ix], currRow[ix - 1], limit) && + private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) => + IsNear(currRow[ix], currRow[ix - 1], limit) && // Check that all pixels in 4-connected neighborhood are smooth. IsNear(currRow[ix], currRow[ix + 1], limit) && IsNear(currRow[ix], prevRow[ix], limit) && IsNear(currRow[ix], nextRow[ix], limit); - } // Checks if distance between corresponding channel values of pixels a and b is within the given limit. private static bool IsNear(uint a, uint b, int limit) diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 9fcebfb5d..2d71a7af6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -14,69 +14,41 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public uint BgraOrDistance { get; set; } - public static PixOrCopy CreateCacheIdx(int idx) - { - return new PixOrCopy() + public static PixOrCopy CreateCacheIdx(int idx) => + new PixOrCopy() { Mode = PixOrCopyMode.CacheIdx, BgraOrDistance = (uint)idx, Len = 1 }; - } - public static PixOrCopy CreateLiteral(uint bgra) - { - return new PixOrCopy() + public static PixOrCopy CreateLiteral(uint bgra) => + new PixOrCopy() { Mode = PixOrCopyMode.Literal, BgraOrDistance = bgra, Len = 1 }; - } - public static PixOrCopy CreateCopy(uint distance, ushort len) + public static PixOrCopy CreateCopy(uint distance, ushort len) => new PixOrCopy() { - return new PixOrCopy() - { - Mode = PixOrCopyMode.Copy, - BgraOrDistance = distance, - Len = len - }; - } + Mode = PixOrCopyMode.Copy, + BgraOrDistance = distance, + Len = len + }; - public uint Literal(int component) - { - return (this.BgraOrDistance >> (component * 8)) & 0xff; - } + public uint Literal(int component) => (this.BgraOrDistance >> (component * 8)) & 0xff; - public uint CacheIdx() - { - return this.BgraOrDistance; - } + public uint CacheIdx() => this.BgraOrDistance; - public ushort Length() - { - return this.Len; - } + public ushort Length() => this.Len; - public uint Distance() - { - return this.BgraOrDistance; - } + public uint Distance() => this.BgraOrDistance; - public bool IsLiteral() - { - return this.Mode == PixOrCopyMode.Literal; - } + public bool IsLiteral() => this.Mode == PixOrCopyMode.Literal; - public bool IsCacheIdx() - { - return this.Mode == PixOrCopyMode.CacheIdx; - } + public bool IsCacheIdx() => this.Mode == PixOrCopyMode.CacheIdx; - public bool IsCopy() - { - return this.Mode == PixOrCopyMode.Copy; - } + public bool IsCopy() => this.Mode == PixOrCopyMode.Copy; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 0e36a6c45..159c0c2f8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); // TODO: Can we optimize this? - var histo = new int[4][]; + int[][] histo = new int[4][]; for (int i = 0; i < 4; i++) { histo[i] = new int[256]; @@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxX = GetMin(tileSize, width - startX); // Whether there exist columns just outside the tile. - int haveLeft = (startX > 0) ? 1 : 0; + int haveLeft = startX > 0 ? 1 : 0; // Position and size of the strip covering the tile and adjacent columns if they exist. int contextStartX = startX - haveLeft; @@ -210,8 +210,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); // Prediction modes of the left and above neighbor tiles. - int leftMode = (int)((tileX > 0) ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); - int aboveMode = (int)((tileY > 0) ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); + int leftMode = (int)(tileX > 0 ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); + int aboveMode = (int)(tileY > 0 ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); // The width of upper_row and current_row is one pixel larger than image width // to allow the top right pixel to point to the leftmost pixel of the next row @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // the image (wrapping to the leftmost pixel of the next row if it does // not exist in the currentRow). int offset = (y * width) + contextStartX; - Span src = argb.Slice(offset, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); + Span src = argb.Slice(offset, maxX + haveLeft + (y + 1 < height ? 1 : 0)); Span dst = currentRow.Slice(contextStartX); src.CopyTo(dst); @@ -350,7 +350,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint residual; if (y == 0) { - predict = (x == 0) ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. + predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. } else if (x == 0) { @@ -478,7 +478,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless quantization >>= 1; } - if ((value >> 24) == 0 || (value >> 24) == 0xff) + if (value >> 24 == 0 || value >> 24 == 0xff) { // Preserve transparency of fully transparent or fully opaque pixels. a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); @@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span tmp32 = upperRow; upperRow = currentRow; currentRow = tmp32; - Span src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); + Span src = argb.Slice(y * width, width + (y + 1 < height ? 1 : 0)); src.CopyTo(currentRow); if (lowEffort) @@ -1165,7 +1165,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static uint MultipliersToColorCode(Vp8LMultipliers m) => 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMin(int a, int b) => (a > b) ? b : a; + private static int GetMin(int a, int b) => a > b ? b : a; [MethodImpl(InliningOptions.ShortMethod)] private static int GetMax(int a, int b) => (a < b) ? b : a; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index b197a2c00..502728b15 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -7,10 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LBackwardRefs { - public Vp8LBackwardRefs() - { - this.Refs = new List(); - } + public Vp8LBackwardRefs() => this.Refs = new List(); /// /// Gets or sets the common block-size. @@ -22,9 +19,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// public List Refs { get; } - public void Add(PixOrCopy pixOrCopy) - { - this.Refs.Add(pixOrCopy); - } + public void Add(PixOrCopy pixOrCopy) => this.Refs.Add(pixOrCopy); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index b6d06b676..e3d7fd28a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless double minLimit = (2 * this.Sum) - this.MaxVal; minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); - return (this.Entropy < minLimit) ? minLimit : this.Entropy; + return this.Entropy < minLimit ? minLimit : this.Entropy; } public void BitsEntropyUnrefined(Span array, int n) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 497a09148..d1ba52a6b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { this.Refs[i] = new Vp8LBackwardRefs { - BlockSize = (refsBlockSize < MinBlockSize) ? MinBlockSize : refsBlockSize + BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize }; } } @@ -283,10 +283,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bool useCache = true; this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; - this.UseSubtractGreenTransform = (crunchConfig.EntropyIdx == EntropyIx.SubGreen) || - (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); - this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || - (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); + this.UseSubtractGreenTransform = crunchConfig.EntropyIdx == EntropyIx.SubGreen || + crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen; + this.UsePredictorTransform = crunchConfig.EntropyIdx == EntropyIx.Spatial || + crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen; if (lowEffort) { this.UseCrossColorTransform = false; @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (this.nearLossless) { // Apply near-lossless preprocessing. - bool useNearLossless = (this.nearLosslessQuality < 100) && !this.UsePalette && !this.UsePredictorTransform; + bool useNearLossless = this.nearLosslessQuality < 100 && !this.UsePalette && !this.UsePredictorTransform; if (useNearLossless) { this.AllocateTransformBuffer(width, height); @@ -320,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.MapImageFromPalette(width, height); // If using a color cache, do not have it bigger than the number of colors. - if (useCache && this.PaletteSize < (1 << WebpConstants.MaxColorCacheBits)) + if (useCache && this.PaletteSize < 1 << WebpConstants.MaxColorCacheBits) { this.CacheBits = WebpCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; } @@ -419,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.TransformBits = GetTransformBits(this.method, this.HistoBits); // Try out multiple LZ77 on images with few colors. - int nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; + int nlz77s = this.PaletteSize > 0 && this.PaletteSize <= 16 ? 2 : 1; EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); bool doNotCache = false; @@ -463,7 +463,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { crunchConfig.SubConfigs.Add(new CrunchSubConfig { - Lz77 = (j == 0) ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, + Lz77 = j == 0 ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, DoNotCache = doNotCache }); } @@ -944,7 +944,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; - int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); + int tileMask = histoBits == 0 ? 0 : -(1 << histoBits); // x and y trace the position in the image. int x = 0; @@ -957,7 +957,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless while (c.MoveNext()) { PixOrCopy v = c.Current; - if ((tileX != (x & tileMask)) || (tileY != (y & tileMask))) + if (tileX != (x & tileMask) || tileY != (y & tileMask)) { tileX = x & tileMask; tileY = y & tileMask; @@ -1038,7 +1038,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint pix = currentRow[x]; uint pixDiff = LosslessUtils.SubPixels(pix, pixPrev); pixPrev = pix; - if ((pixDiff == 0) || (prevRow != null && pix == prevRow[x])) + if (pixDiff == 0 || (prevRow != null && pix == prevRow[x])) { continue; } @@ -1245,11 +1245,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // This is done line by line. if (paletteSize <= 4) { - xBits = (paletteSize <= 2) ? 3 : 2; + xBits = paletteSize <= 2 ? 3 : 2; } else { - xBits = (paletteSize <= 16) ? 1 : 0; + xBits = paletteSize <= 16 ? 1 : 0; } this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits); @@ -1495,17 +1495,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless byte bd = (byte)((diff >> 0) & 0xff); if (rd != 0x00) { - signFound |= (byte)((rd < 0x80) ? 1 : 2); + signFound |= (byte)(rd < 0x80 ? 1 : 2); } if (gd != 0x00) { - signFound |= (byte)((gd < 0x80) ? 8 : 16); + signFound |= (byte)(gd < 0x80 ? 8 : 16); } if (bd != 0x00) { - signFound |= (byte)((bd < 0x80) ? 64 : 128); + signFound |= (byte)(bd < 0x80 ? 64 : 128); } } @@ -1555,8 +1555,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int k = 0; k < 5; k++) { int numSymbols = - (k == 0) ? histo.NumCodes() : - (k == 4) ? WebpConstants.NumDistanceCodes : 256; + k == 0 ? histo.NumCodes() : + k == 4 ? WebpConstants.NumDistanceCodes : 256; huffmanCodes[startIdx + k].NumSymbols = numSymbols; } } @@ -1631,8 +1631,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless histoBits++; } - return (histoBits < WebpConstants.MinHuffmanBits) ? WebpConstants.MinHuffmanBits : - (histoBits > WebpConstants.MaxHuffmanBits) ? WebpConstants.MaxHuffmanBits : histoBits; + return histoBits < WebpConstants.MinHuffmanBits ? WebpConstants.MinHuffmanBits : + histoBits > WebpConstants.MaxHuffmanBits ? WebpConstants.MaxHuffmanBits : histoBits; } /// @@ -1681,8 +1681,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static int GetTransformBits(int method, int histoBits) { - int maxTransformBits = (method < 4) ? 6 : (method > 4) ? 4 : 5; - int res = (histoBits > maxTransformBits) ? maxTransformBits : histoBits; + int maxTransformBits = method < 4 ? 6 : method > 4 ? 4 : 5; + int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; return res; } @@ -1728,10 +1728,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static uint ApplyPaletteHash0(uint color) => (color >> 8) & 0xff; // Focus on the green color. [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash1(uint color) => ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); // Forget about alpha. + private static uint ApplyPaletteHash1(uint color) => (uint)((color & 0x00ffffffu) * 4222244071ul) >> (32 - PaletteInvSizeBits); // Forget about alpha. [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash2(uint color) => ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); // Forget about alpha. + private static uint ApplyPaletteHash2(uint color) => (uint)((color & 0x00ffffffu) * ((1ul << 31) - 1)) >> (32 - PaletteInvSizeBits); // Forget about alpha. // Note that masking with 0xffffffffu is for preventing an // 'unsigned int overflow' warning. Doesn't impact the compiled code. @@ -1739,7 +1739,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static uint HashPix(uint pix) => (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; [MethodImpl(InliningOptions.ShortMethod)] - private static int PaletteCompareColorsForSort(uint p1, uint p2) => (p1 < p2) ? -1 : 1; + private static int PaletteCompareColorsForSort(uint p1, uint p2) => p1 < p2 ? -1 : 1; [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteComponentDistance(uint v) => (v <= 128) ? v : (256 - v); @@ -1749,7 +1749,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra // pixel in each, plus 2 regular scanlines of bytes. int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; - int transformDataSize = (this.UsePredictorTransform || this.UseCrossColorTransform) ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; + int transformDataSize = this.UsePredictorTransform || this.UseCrossColorTransform ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize); this.TransformData = this.memoryAllocator.Allocate(transformDataSize); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 14b62b11a..977a094bd 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -142,8 +142,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int iter = iterMax; int bestLength = 0; uint bestDistance = 0; - int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; - int lengthMax = (maxLen < 256) ? maxLen : 256; + int minPos = basePosition > windowSize ? basePosition - windowSize : 0; + int lengthMax = maxLen < 256 ? maxLen : 256; pos = chain[basePosition]; int currLength; @@ -273,12 +273,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static int GetWindowSizeForHashChain(int quality, int xSize) { - int maxWindowSize = (quality > 75) ? WindowSize - : (quality > 50) ? (xSize << 8) - : (quality > 25) ? (xSize << 6) - : (xSize << 4); + int maxWindowSize = quality > 75 ? WindowSize + : quality > 50 ? xSize << 8 + : quality > 25 ? xSize << 6 + : xSize << 4; - return (maxWindowSize > WindowSize) ? WindowSize : maxWindowSize; + return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index ffd9d6cf3..20997c3db 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (this.PaletteCodeBits > 0 ? 1 << this.PaletteCodeBits : 0); /// /// Estimate how many bits the combined entropy of literals and distance approximately maps to. @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i]; } - output.TrivialSymbol = (this.TrivialSymbol == b.TrivialSymbol) + output.TrivialSymbol = this.TrivialSymbol == b.TrivialSymbol ? this.TrivialSymbol : NonTrivialSym; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index 0a450f11f..b206c9aa7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. // That is why 11 bits is also considered valid here. - bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= (WebpConstants.MaxColorCacheBits + 1); + bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebpConstants.MaxColorCacheBits + 1; if (!colorCacheBitsIsValid) { WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); @@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int totalSize = 0; bool isTrivialLiteral = true; int maxBits = 0; - var codeLengths = new int[maxAlphabetSize]; + int[] codeLengths = new int[maxAlphabetSize]; for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { int alphabetSize = WebpConstants.AlphabetSize[j]; @@ -459,7 +459,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int k; for (k = 1; k < alphabetSize; ++k) { - var codeLengthK = codeLengths[k]; + int codeLengthK = codeLengths[k]; if (codeLengthK > localMaxBits) { localMaxBits = codeLengthK; @@ -517,7 +517,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint firstSymbolLenCode = this.bitReader.ReadValue(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadValue((firstSymbolLenCode == 0) ? 1 : 8); + uint symbol = this.bitReader.ReadValue(firstSymbolLenCode == 0 ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. @@ -532,7 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // (ii) Normal Code Length Code: // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - var codeLengthCodeLengths = new int[NumCodeLengthCodes]; + int[] codeLengthCodeLengths = new int[NumCodeLengthCodes]; uint numCodes = this.bitReader.ReadValue(4) + 4; if (numCodes > NumCodeLengthCodes) { @@ -644,9 +644,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. uint numColors = this.bitReader.ReadValue(8) + 1; - int bits = (numColors > 16) ? 0 - : (numColors > 4) ? 1 - : (numColors > 2) ? 2 + int bits = numColors > 16 ? 0 + : numColors > 4 ? 1 + : numColors > 2 ? 2 : 3; transform.Bits = bits; using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) @@ -661,15 +661,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: - { - // The first 3 bits of prediction data define the block width and height in number of bits. - transform.Bits = (int)this.bitReader.ReadValue(3) + 2; - int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); - int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); - IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); - transform.Data = transformData; - break; - } + { + // The first 3 bits of prediction data define the block width and height in number of bits. + transform.Bits = (int)this.bitReader.ReadValue(3) + 2; + int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); + int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); + IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); + transform.Data = transformData; + break; + } } decoder.Transforms.Add(transform); @@ -732,7 +732,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int lastRow = height; const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; int mask = hdr.HuffmanMask; - HTreeGroup[] htreeGroup = (pos < last) ? GetHTreeGroupForPos(hdr, col, row) : null; + HTreeGroup[] htreeGroup = pos < last ? GetHTreeGroupForPos(hdr, col, row) : null; while (!this.bitReader.Eos && pos < last) { // Only update when changing tile. @@ -754,7 +754,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { col = 0; ++row; - if (row <= lastRow && (row % WebpConstants.NumArgbCacheRows == 0)) + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) { dec.ExtractPalettedAlphaRows(row); } @@ -784,7 +784,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { col -= width; ++row; - if (row <= lastRow && (row % WebpConstants.NumArgbCacheRows == 0)) + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) { dec.ExtractPalettedAlphaRows(row); } @@ -813,7 +813,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless decoder.Width = width; decoder.Height = height; decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); - decoder.Metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; + decoder.Metadata.HuffmanMask = numBits == 0 ? ~0 : (1 << numBits) - 1; } private uint ReadPackedSymbols(HTreeGroup[] group, Span pixelData, int decodedPixels) @@ -879,11 +879,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyLength(int lengthSymbol) - { - // Length and distance prefixes are encoded the same way. - return this.GetCopyDistance(lengthSymbol); - } + private int GetCopyLength(int lengthSymbol) => + this.GetCopyDistance(lengthSymbol); // Length and distance prefixes are encoded the same way. private int GetCopyDistance(int distanceSymbol) { @@ -930,7 +927,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int dist = (yOffset * xSize) + xOffset; // dist < 1 can happen if xSize is very small. - return (dist >= 1) ? dist : 1; + return dist >= 1 ? dist : 1; } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index c7c5119d8..aebd22577 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -506,7 +506,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// public static void TransformWht(Span input, Span output) { - var tmp = new int[16]; + int[] tmp = new int[16]; for (int i = 0; i < 4; ++i) { int iPlus4 = 4 + i; @@ -732,7 +732,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int k = 3; k > 0; --k) { offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); + SimpleVFilter16(p, offset, stride, thresh); } } @@ -833,7 +833,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void Dst(Span dst, int x, int y, byte v) => dst[x + (y * WebpConstants.Bps)] = v; [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); + public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : v < 0 ? 0 : 255); // Cost of coding one event with probability 'proba'. public static int Vp8BitCost(int bit, byte proba) => bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; @@ -882,7 +882,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int thresh2 = (2 * thresh) + 1; while (size-- > 0) { - if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) + if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) { if (Hev(p, offset, hStride, hevThresh)) { @@ -992,7 +992,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return ((4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1]) <= t; + return (4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1] <= t; } private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) @@ -1007,7 +1007,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int q1 = p[offset + step]; int q2 = p[offset + step2]; int q3 = p[offset + step3]; - if (((4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1]) > t) + if ((4 * WebpLookupTables.Abs0[p0 - q0]) + WebpLookupTables.Abs0[p1 - q1] > t) { return false; } @@ -1024,7 +1024,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int p0 = p[offset - step]; int q0 = p[offset]; int q1 = p[offset + step]; - return (WebpLookupTables.Abs0[p1 - p0] > thresh) || (WebpLookupTables.Abs0[q1 - q0] > thresh); + return WebpLookupTables.Abs0[p1 - p0] > thresh || WebpLookupTables.Abs0[q1 - q0] > thresh; } [MethodImpl(InliningOptions.ShortMethod)] @@ -1056,7 +1056,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static byte Clip8(int v) { int yuvMask = (256 << 6) - 1; - return (byte)(((v & ~yuvMask) == 0) ? (v >> 6) : (v < 0) ? 0 : 255); + return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); } [MethodImpl(InliningOptions.ShortMethod)] @@ -1081,6 +1081,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Clamp255(int x) => x < 0 ? 0 : (x > 255 ? 255 : x); + private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs index e3ac6562c..64a122afb 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Q = Numerics.Clamp(quality, qMin, qMax); this.LastQ = this.Q; this.Target = doSizeSearch ? targetSize - : (targetPsnr > 0.0f) ? targetPsnr + : targetPsnr > 0.0f ? targetPsnr : 40.0f; // default, just in case this.Value = 0.0f; this.LastValue = 0.0f; @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy float dq; if (this.IsFirst) { - dq = (this.Value > this.Target) ? -this.Dq : this.Dq; + dq = this.Value > this.Target ? -this.Dq : this.Dq; this.IsFirst = false; } else if (this.Value != this.LastValue) diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index f62161dbb..8abeab6bf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -350,7 +350,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy long bestScore = Vp8ModeScore.MaxCost; int nz = 0; int mode; - bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); + bool isI16 = tryBothModes || it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; // Some empiric constants, of approximate order of magnitude. @@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. if (IsFlatSource16(src)) { - bestMode = (it.X == 0) ? 0 : 2; + bestMode = it.X == 0 ? 0 : 2; tryBothModes = false; // Stick to i16. } } @@ -530,7 +530,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - return (last >= 0) ? 1 : 0; + return last >= 0 ? 1 : 0; } // Quantize as usual, but also compute and return the quantization error. @@ -615,7 +615,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int i = 1; i < 16; ++i) { // omit DC, we're only interested in AC - score += (levels[i] != 0) ? 1 : 0; + score += levels[i] != 0 ? 1 : 0; if (score > thresh) { return false; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 6c83cce69..499b5c66d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; - int extraUv = (extraRows / 2) * this.CacheUvStride; + int extraUv = extraRows / 2 * this.CacheUvStride; this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Gets the contextual macroblock info. /// - public Vp8MacroBlock[] MacroBlockInfo { get; } + public Vp8MacroBlock[] MacroBlockInfo { get; } /// /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce) @@ -284,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - level = (level < 0) ? 0 : (level > 63) ? 63 : level; + level = level < 0 ? 0 : level > 63 ? 63 : level; if (level > 0) { int iLevel = level; @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy info.InnerLevel = (byte)iLevel; info.Limit = (byte)((2 * level) + iLevel); - info.HighEdgeVarianceThreshold = (byte)((level >= 40) ? 2 : (level >= 15) ? 1 : 0); + info.HighEdgeVarianceThreshold = (byte)(level >= 40 ? 2 : level >= 15 ? 1 : 0); } else { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 63ed1f399..5a9875633 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -346,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int q = quality; int kThreshold = 8 + ((17 - 8) * q / 100); int k; - var dc = new uint[16]; + uint[] dc = new uint[16]; uint m; uint m2; for (k = 0; k < 16; k += 4) @@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } else { - var modes = new byte[16]; // DC4 + byte[] modes = new byte[16]; // DC4 this.SetIntra4Mode(modes); } @@ -418,7 +418,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy histos[curHisto] = new Vp8Histogram(); histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); - var alpha = histos[curHisto].GetAlpha(); + int alpha = histos[curHisto].GetAlpha(); if (alpha > bestModeAlpha) { bestModeAlpha = alpha; @@ -523,7 +523,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int ctx = this.TopNz[x] + this.LeftNz[y]; res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); r += res.GetResidualCost(ctx); - this.TopNz[x] = this.LeftNz[y] = (res.Last >= 0) ? 1 : 0; + this.TopNz[x] = this.LeftNz[y] = res.Last >= 0 ? 1 : 0; } } @@ -536,8 +536,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int predIdx = this.predIdx; int x = this.I4 & 3; int y = this.I4 >> 2; - int left = (x == 0) ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; - int top = (y == 0) ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; + int left = x == 0 ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; + int top = y == 0 ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; return WebpLookupTables.Vp8FixedCostsI4[top, left]; } @@ -573,7 +573,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); r += res.GetResidualCost(ctx); - this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = (res.Last >= 0) ? 1 : 0; + this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = res.Last >= 0 ? 1 : 0; } } } @@ -910,7 +910,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span yLeft = this.YLeft.AsSpan(); Span uLeft = this.UvLeft.AsSpan(0, 16); Span vLeft = this.UvLeft.AsSpan(16, 16); - byte val = (byte)((this.Y > 0) ? 129 : 127); + byte val = (byte)(this.Y > 0 ? 129 : 127); yLeft[0] = val; uLeft[0] = val; vLeft[0] = val; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index 3481b26c1..c9976f768 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx]; - int cost0 = (ctx > 0) ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; + int cost0 = ctx > 0 ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; int v; table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 9da7217b8..61c4f4d1d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -99,6 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Quality/speed trade-off (0=fast, 6=slower-better). /// Number of entropy-analysis passes (in [1..10]). /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// The spatial noise shaping. 0=off, 100=maximum. public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength, int spatialNoiseShaping) { this.memoryAllocator = memoryAllocator; @@ -110,9 +111,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); - this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll - : (method >= 5) ? Vp8RdLevel.RdOptTrellis - : (method >= 3) ? Vp8RdLevel.RdOptBasic + this.rdOptLevel = method >= 6 ? Vp8RdLevel.RdOptTrellisAll + : method >= 5 ? Vp8RdLevel.RdOptTrellis + : method >= 3 ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int pixelCount = width * height; @@ -362,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy bool doSearch = targetSize > 0 || targetPsnr > 0; bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -374,11 +375,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (this.method == 3) { // We need more stats for method 3 to be reliable. - nbMbs = (nbMbs > 200) ? nbMbs >> 1 : 100; + nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; } else { - nbMbs = (nbMbs > 200) ? nbMbs >> 2 : 50; + nbMbs = nbMbs > 200 ? nbMbs >> 2 : 50; } } @@ -536,7 +537,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Simplified k-Means, to assign Nb segments based on alpha-histogram. private void AssignSegments(int[] alphas) { - int nb = (this.SegmentHeader.NumSegments < NumMbSegments) ? this.SegmentHeader.NumSegments : NumMbSegments; + int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments; int[] centers = new int[NumMbSegments]; int weightedAverage = 0; int[] map = new int[WebpConstants.MaxAlpha + 1]; @@ -727,7 +728,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Segments with lower complexity ('beta') will be less filtered. int f = baseStrength * level0 / (256 + m.Beta); - m.FStrength = (f < WebpConstants.FilterStrengthCutoff) ? 0 : (f > 63) ? 63 : f; + m.FStrength = f < WebpConstants.FilterStrengthCutoff ? 0 : f > 63 ? 63 : f; } // We record the initial strength (mainly for the case of 1-segment only). @@ -754,7 +755,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy probas[1] = (byte)GetProba(p[0], p[1]); probas[2] = (byte)GetProba(p[2], p[3]); - this.SegmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); + this.SegmentHeader.UpdateMap = probas[0] != 255 || probas[1] != 255 || probas[2] != 255; if (!this.SegmentHeader.UpdateMap) { this.ResetSegments(); @@ -969,7 +970,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - var res = this.bitWriter.PutCoeffs(ctx, residual); + int res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; } } @@ -1018,7 +1019,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int ctx = it.TopNz[x] + it.LeftNz[y]; Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); residual.SetCoeffs(coeffs); - var res = residual.RecordCoeffs(ctx); + int res = residual.RecordCoeffs(ctx); it.TopNz[x] = res; it.LeftNz[y] = res; } @@ -1059,7 +1060,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static double QualityToCompression(double c) { - double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; + double linearC = c < 0.75 ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; // The file size roughly scales as pow(quantizer, 3.). Actually, the // exponent is somewhere between 2.8 and 3.2, but we're mostly interested @@ -1075,18 +1076,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private int FilterStrengthFromDelta(int sharpness, int delta) { - int pos = (delta < WebpConstants.MaxDelzaSize) ? delta : WebpConstants.MaxDelzaSize - 1; + int pos = delta < WebpConstants.MaxDelzaSize ? delta : WebpConstants.MaxDelzaSize - 1; return WebpLookupTables.LevelsFromDelta[sharpness, pos]; } [MethodImpl(InliningOptions.ShortMethod)] - private static double GetPsnr(long mse, long size) => (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; + private static double GetPsnr(long mse, long size) => mse > 0 && size > 0 ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; [MethodImpl(InliningOptions.ShortMethod)] private static int GetProba(int a, int b) { int total = a + b; - return (total == 0) ? 255 // that's the default probability. + return total == 0 ? 255 // that's the default probability. : ((255 * a) + (total / 2)) / total; // rounded proba } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs index efdd9c605..fe7edd011 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int i; #pragma warning disable SA1312 // Variable names should begin with lower-case letter - var C = new int[4 * 4]; + int[] C = new int[4 * 4]; #pragma warning restore SA1312 // Variable names should begin with lower-case letter Span tmp = C.AsSpan(); for (i = 0; i < 4; ++i) @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransform(Span src, Span reference, Span output) { int i; - var tmp = new int[16]; + int[] tmp = new int[16]; int srcIdx = 0; int refIdx = 0; for (i = 0; i < 4; ++i) @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransformWht(Span input, Span output) { - var tmp = new int[16]; + int[] tmp = new int[16]; int i; int inputIdx = 0; for (i = 0; i < 4; ++i) @@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } else { - Vp8Encoding.HorizontalPred(dst, left, size); + HorizontalPred(dst, left, size); } } else @@ -310,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // then 129, and not 127 as in the VerticalPred case. if (top != null) { - Vp8Encoding.VerticalPred(dst, top, size); + VerticalPred(dst, top, size); } else { @@ -444,23 +444,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte d = top[topOffset + 3]; LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); - var ijk = LossyUtils.Avg3(i, j, k); + byte ijk = LossyUtils.Avg3(i, j, k); LossyUtils.Dst(dst, 0, 2, ijk); LossyUtils.Dst(dst, 1, 3, ijk); - var xij = LossyUtils.Avg3(x, i, j); + byte xij = LossyUtils.Avg3(x, i, j); LossyUtils.Dst(dst, 0, 1, xij); LossyUtils.Dst(dst, 1, 2, xij); LossyUtils.Dst(dst, 2, 3, xij); - var axi = LossyUtils.Avg3(a, x, i); + byte axi = LossyUtils.Avg3(a, x, i); LossyUtils.Dst(dst, 0, 0, axi); LossyUtils.Dst(dst, 1, 1, axi); LossyUtils.Dst(dst, 2, 2, axi); LossyUtils.Dst(dst, 3, 3, axi); - var bax = LossyUtils.Avg3(b, a, x); + byte bax = LossyUtils.Avg3(b, a, x); LossyUtils.Dst(dst, 1, 0, bax); LossyUtils.Dst(dst, 2, 1, bax); LossyUtils.Dst(dst, 3, 2, bax); - var cba = LossyUtils.Avg3(c, b, a); + byte cba = LossyUtils.Avg3(c, b, a); LossyUtils.Dst(dst, 2, 0, cba); LossyUtils.Dst(dst, 3, 1, cba); LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); @@ -477,25 +477,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte c = top[topOffset + 2]; byte d = top[topOffset + 3]; - var xa = LossyUtils.Avg2(x, a); + byte xa = LossyUtils.Avg2(x, a); LossyUtils.Dst(dst, 0, 0, xa); LossyUtils.Dst(dst, 1, 2, xa); - var ab = LossyUtils.Avg2(a, b); + byte ab = LossyUtils.Avg2(a, b); LossyUtils.Dst(dst, 1, 0, ab); LossyUtils.Dst(dst, 2, 2, ab); - var bc = LossyUtils.Avg2(b, c); + byte bc = LossyUtils.Avg2(b, c); LossyUtils.Dst(dst, 2, 0, bc); LossyUtils.Dst(dst, 3, 2, bc); LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); - var ixa = LossyUtils.Avg3(i, x, a); + byte ixa = LossyUtils.Avg3(i, x, a); LossyUtils.Dst(dst, 0, 1, ixa); LossyUtils.Dst(dst, 1, 3, ixa); - var xab = LossyUtils.Avg3(x, a, b); + byte xab = LossyUtils.Avg3(x, a, b); LossyUtils.Dst(dst, 1, 1, xab); LossyUtils.Dst(dst, 2, 3, xab); - var abc = LossyUtils.Avg3(a, b, c); + byte abc = LossyUtils.Avg3(a, b, c); LossyUtils.Dst(dst, 2, 1, abc); LossyUtils.Dst(dst, 3, 3, abc); LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); @@ -513,23 +513,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte h = top[topOffset + 7]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); + byte bcd = LossyUtils.Avg3(b, c, d); LossyUtils.Dst(dst, 1, 0, bcd); LossyUtils.Dst(dst, 0, 1, bcd); - var cde = LossyUtils.Avg3(c, d, e); + byte cde = LossyUtils.Avg3(c, d, e); LossyUtils.Dst(dst, 2, 0, cde); LossyUtils.Dst(dst, 1, 1, cde); LossyUtils.Dst(dst, 0, 2, cde); - var def = LossyUtils.Avg3(d, e, f); + byte def = LossyUtils.Avg3(d, e, f); LossyUtils.Dst(dst, 3, 0, def); LossyUtils.Dst(dst, 2, 1, def); LossyUtils.Dst(dst, 1, 2, def); LossyUtils.Dst(dst, 0, 3, def); - var efg = LossyUtils.Avg3(e, f, g); + byte efg = LossyUtils.Avg3(e, f, g); LossyUtils.Dst(dst, 3, 1, efg); LossyUtils.Dst(dst, 2, 2, efg); LossyUtils.Dst(dst, 1, 3, efg); - var fgh = LossyUtils.Avg3(f, g, h); + byte fgh = LossyUtils.Avg3(f, g, h); LossyUtils.Dst(dst, 3, 2, fgh); LossyUtils.Dst(dst, 2, 3, fgh); LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); @@ -547,23 +547,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte h = top[topOffset + 7]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); - var bc = LossyUtils.Avg2(b, c); + byte bc = LossyUtils.Avg2(b, c); LossyUtils.Dst(dst, 1, 0, bc); LossyUtils.Dst(dst, 0, 2, bc); - var cd = LossyUtils.Avg2(c, d); + byte cd = LossyUtils.Avg2(c, d); LossyUtils.Dst(dst, 2, 0, cd); LossyUtils.Dst(dst, 1, 2, cd); - var de = LossyUtils.Avg2(d, e); + byte de = LossyUtils.Avg2(d, e); LossyUtils.Dst(dst, 3, 0, de); LossyUtils.Dst(dst, 2, 2, de); LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); + byte bcd = LossyUtils.Avg3(b, c, d); LossyUtils.Dst(dst, 1, 1, bcd); LossyUtils.Dst(dst, 0, 3, bcd); - var cde = LossyUtils.Avg3(c, d, e); + byte cde = LossyUtils.Avg3(c, d, e); LossyUtils.Dst(dst, 2, 1, cde); LossyUtils.Dst(dst, 1, 3, cde); - var def = LossyUtils.Avg3(d, e, f); + byte def = LossyUtils.Avg3(d, e, f); LossyUtils.Dst(dst, 3, 1, def); LossyUtils.Dst(dst, 2, 3, def); LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); @@ -581,25 +581,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte b = top[topOffset + 1]; byte c = top[topOffset + 2]; - var ix = LossyUtils.Avg2(i, x); + byte ix = LossyUtils.Avg2(i, x); LossyUtils.Dst(dst, 0, 0, ix); LossyUtils.Dst(dst, 2, 1, ix); - var ji = LossyUtils.Avg2(j, i); + byte ji = LossyUtils.Avg2(j, i); LossyUtils.Dst(dst, 0, 1, ji); LossyUtils.Dst(dst, 2, 2, ji); - var kj = LossyUtils.Avg2(k, j); + byte kj = LossyUtils.Avg2(k, j); LossyUtils.Dst(dst, 0, 2, kj); LossyUtils.Dst(dst, 2, 3, kj); LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); - var ixa = LossyUtils.Avg3(i, x, a); + byte ixa = LossyUtils.Avg3(i, x, a); LossyUtils.Dst(dst, 1, 0, ixa); LossyUtils.Dst(dst, 3, 1, ixa); - var jix = LossyUtils.Avg3(j, i, x); + byte jix = LossyUtils.Avg3(j, i, x); LossyUtils.Dst(dst, 1, 1, jix); LossyUtils.Dst(dst, 3, 2, jix); - var kji = LossyUtils.Avg3(k, j, i); + byte kji = LossyUtils.Avg3(k, j, i); LossyUtils.Dst(dst, 1, 2, kji); LossyUtils.Dst(dst, 3, 3, kji); LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); @@ -613,17 +613,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte l = top[topOffset - 5]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); - var jk = LossyUtils.Avg2(j, k); + byte jk = LossyUtils.Avg2(j, k); LossyUtils.Dst(dst, 2, 0, jk); LossyUtils.Dst(dst, 0, 1, jk); - var kl = LossyUtils.Avg2(k, l); + byte kl = LossyUtils.Avg2(k, l); LossyUtils.Dst(dst, 2, 1, kl); LossyUtils.Dst(dst, 0, 2, kl); LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); - var jkl = LossyUtils.Avg3(j, k, l); + byte jkl = LossyUtils.Avg3(j, k, l); LossyUtils.Dst(dst, 3, 0, jkl); LossyUtils.Dst(dst, 1, 1, jkl); - var kll = LossyUtils.Avg3(k, l, l); + byte kll = LossyUtils.Avg3(k, l, l); LossyUtils.Dst(dst, 3, 1, kll); LossyUtils.Dst(dst, 1, 2, kll); LossyUtils.Dst(dst, 3, 2, l); @@ -644,21 +644,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8b(int v) - { - return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; - } + private static byte Clip8b(int v) => (v & ~0xff) == 0 ? (byte)v : v < 0 ? (byte)0 : (byte)255; [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, int v) - { - dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); - } + private static void Store(Span dst, Span reference, int x, int y, int v) => dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul(int a, int b) - { - return (a * b) >> 16; - } + private static int Mul(int a, int b) => (a * b) >> 16; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs index cadd8a2c7..220fd589d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // for handling the useful small values which contribute most. int maxValue = this.maxValue; int lastNonZero = this.lastNonZero; - int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; + int alpha = maxValue > 1 ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; return alpha; } @@ -129,13 +129,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int a2 = tmp[4 + i] - tmp[8 + i]; int a3 = tmp[0 + i] - tmp[12 + i]; output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); output[8 + i] = (short)((a0 - a1 + 7) >> 4); output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); } } [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) => (v > max) ? max : v; + private static int ClipMax(int v, int max) => v > max ? max : v; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index f9fd6602a..9990f9ef9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int i; for (i = 0; i < 2; ++i) { - int isAcCoeff = (i > 0) ? 1 : 0; + int isAcCoeff = i > 0 ? 1 : 0; int bias = BiasMatrices[type][isAcCoeff]; this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); this.Bias[i] = (uint)this.BIAS(bias); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 20c5cafa8..94c3dab02 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8StatsArray s = this.Stats[n].Stats[ctx]; if (this.Last < 0) { - this.RecordStats(0, s, 0); + this.RecordStats(0, s, 0); return 0; } @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } this.RecordStats(1, s, 1); - var bit = (uint)(v + 1) > 2u; + bool bit = (uint)(v + 1) > 2u; if (this.RecordStats(bit ? 1 : 0, s, 2) == 0) { // v = -1 or 1 @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll // be missing during the loop. - int cost = (ctx0 == 0) ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; + int cost = ctx0 == 0 ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; if (this.Last < 0) { @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (; n < this.Last; ++n) { v = Math.Abs(this.Coeffs[n]); - int ctx = (v >= 2) ? 2 : v; + int ctx = v >= 2 ? 2 : v; cost += LevelCost(t.Costs, v); t = costs[n + 1].Costs[ctx]; } @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (n < 15) { int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = (v == 1) ? 1 : 2; + int ctx = v == 1 ? 1 : 2; int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); } @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } private static int LevelCost(Span table, int level) - => WebpLookupTables.Vp8LevelFixedCosts[level] + table[(level > WebpConstants.MaxVariableLevel) ? WebpConstants.MaxVariableLevel : level]; + => WebpLookupTables.Vp8LevelFixedCosts[level] + table[level > WebpConstants.MaxVariableLevel ? WebpConstants.MaxVariableLevel : level]; private int RecordStats(int bit, Vp8StatsArray statsArr, int idx) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index a9d2464ae..cf2a5c177 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -74,8 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int v0 = Math.Abs(dcs[1]); int v1 = Math.Abs(dcs[2]); int v2 = Math.Abs(dcs[4]); - int maxV = (v1 > v0) ? v1 : v0; - maxV = (v2 > maxV) ? v2 : maxV; + int maxV = v1 > v0 ? v1 : v0; + maxV = v2 > maxV ? v2 : maxV; if (maxV > this.MaxEdge) { this.MaxEdge = maxV; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs index 60e7e54ae..88cc24728 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs @@ -8,10 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Initializes a new instance of the class. /// - public Vp8StatsArray() - { - this.Stats = new uint[WebpConstants.NumProbas]; - } + public Vp8StatsArray() => this.Stats = new uint[WebpConstants.NumProbas]; public uint[] Stats { get; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index 836df6403..92a3a65e6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -277,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span vDst = yuv.Slice(vOff); // Initialize left-most block. - var end = 16 * WebpConstants.Bps; + int end = 16 * WebpConstants.Bps; for (int i = 0; i < end; i += WebpConstants.Bps) { yuv[i - 1 + yOff] = 129; @@ -365,7 +365,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (mbx >= dec.MbWidth - 1) { // On rightmost border. - var topYuv15 = topYuv.Y[15]; + byte topYuv15 = topYuv.Y[15]; topRight[0] = topYuv15; topRight[1] = topYuv15; topRight[2] = topYuv15; @@ -421,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy break; } - this.DoTransform(bits, coeffs.AsSpan(n * 16), dst); + this.DoTransform(bits, coeffs.AsSpan(n * 16), dst); } } else @@ -606,15 +606,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int extraYRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; int ySize = extraYRows * dec.CacheYStride; - int uvSize = (extraYRows / 2) * dec.CacheUvStride; + int uvSize = extraYRows / 2 * dec.CacheUvStride; Span yDst = dec.CacheY.Memory.Span; Span uDst = dec.CacheU.Memory.Span; Span vDst = dec.CacheV.Memory.Span; int mby = dec.MbY; bool isFirstRow = mby == 0; bool isLastRow = mby >= dec.BottomRightMbY - 1; - bool filterRow = (dec.Filter != LoopFilter.None) && - (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); + bool filterRow = dec.Filter != LoopFilter.None && dec.MbY >= dec.TopLeftMbY && dec.MbY <= dec.BottomRightMbY; if (filterRow) { @@ -698,8 +697,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Loop over each output pairs of row. - var bufferStride2 = 2 * bufferStride; - var ioStride2 = 2 * io.YStride; + int bufferStride2 = 2 * bufferStride; + int ioStride2 = 2 * io.YStride; for (; y + 2 < yEnd; y += 2) { topU = curU; @@ -879,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else { // Parse DC - var dc = new short[16]; + short[] dc = new short[16]; int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); int nz = this.GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); @@ -913,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int ctx = l + (tnz & 1); int nz = this.GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); - l = (nz > first) ? 1 : 0; + l = nz > first ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 7)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); dstOffset += 16; @@ -930,7 +929,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int ch = 0; ch < 4; ch += 2) { uint nzCoeffs = 0; - var chPlus4 = 4 + ch; + int chPlus4 = 4 + ch; tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); for (int y = 0; y < 2; ++y) @@ -940,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int ctx = l + (tnz & 1); int nz = this.GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); - l = (nz > 0) ? 1 : 0; + l = nz > 0 ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 3)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); dstOffset += 16; @@ -952,7 +951,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Note: we don't really need the per-4x4 details for U/V blocks. nonZeroUv |= nzCoeffs << (4 * ch); - outTnz |= (uint)((tnz << 4) << ch); + outTnz |= (uint)(tnz << 4 << ch); outLnz |= (uint)((lnz & 0xf0) << ch); } @@ -1128,7 +1127,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); - dec.Filter = (vp8FilterHeader.FilterLevel == 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; + dec.Filter = vp8FilterHeader.FilterLevel == 0 ? LoopFilter.None : vp8FilterHeader.LoopFilter; if (vp8FilterHeader.UseLfDelta) { // Update lf-delta? @@ -1157,7 +1156,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int extraRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; int extraY = extraRows * dec.CacheYStride; - int extraUv = (extraRows / 2) * dec.CacheUvStride; + int extraUv = extraRows / 2 * dec.CacheUvStride; dec.CacheYOffset = extraY; dec.CacheUvOffset = extraUv; } @@ -1313,7 +1312,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // For simple filter, we include 'extraPixels' on the other side of the boundary, // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. - var extraShift4 = (-extraPixels) >> 4; + int extraShift4 = -extraPixels >> 4; dec.TopLeftMbX = extraShift4; dec.TopLeftMbY = extraShift4; if (dec.TopLeftMbX < 0) @@ -1347,7 +1346,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) { nzCoeffs <<= 2; - nzCoeffs |= (uint)((nz > 3) ? 3 : (nz > 1) ? 2 : dcNz); + nzCoeffs |= (uint)(nz > 3 ? 3 : nz > 1 ? 2 : dcNz); return nzCoeffs; } @@ -1359,12 +1358,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { if (mbx == 0) { - return (mby == 0) + return mby == 0 ? 6 // B_DC_PRED_NOTOPLEFT : 5; // B_DC_PRED_NOLEFT } - return (mby == 0) + return mby == 0 ? 4 // B_DC_PRED_NOTOP : 0; // B_DC_PRED } @@ -1373,9 +1372,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static int Clip(int value, int max) - { - return value < 0 ? 0 : value > max ? max : value; - } + private static int Clip(int value, int max) => value < 0 ? 0 : value > max ? max : value; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 1b970eb45..797fbe3b6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -170,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Bgra32 bgra1; int i, j; int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + for (i = 0, j = 0; i < width >> 1; i += 1, j += 2, dstIdx += 4) { bgra0 = rowSpan[j]; bgra1 = rowSpan[j + 1]; @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static int ClipUv(int uv, int rounding) { uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); - return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; + return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; } } } diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs index 85b65e0a1..169bc5b7d 100644 --- a/src/ImageSharp/Formats/WebP/WebpConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 }; public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; - public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; public static readonly sbyte[] YModesIntra4 = { diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index 50d3b48a8..e9c61ee08 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -429,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - var iccpData = new byte[iccpChunkSize]; + byte[] iccpData = new byte[iccpChunkSize]; this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); var profile = new IccProfile(iccpData); if (profile.CheckIsValid()) @@ -447,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Webp case WebpChunkType.Alpha: uint alphaChunkSize = this.ReadChunkSize(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); - var alphaDataSize = (int)(alphaChunkSize - 1); + int alphaDataSize = (int)(alphaChunkSize - 1); features.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); break; @@ -467,7 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Webp return; } - var streamLength = this.currentStream.Length; + long streamLength = this.currentStream.Length; while (this.currentStream.Position < streamLength) { // Read chunk header. @@ -476,7 +476,7 @@ namespace SixLabors.ImageSharp.Formats.Webp if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) { - var exifData = new byte[chunkLength]; + byte[] exifData = new byte[chunkLength]; this.currentStream.Read(exifData, 0, (int)chunkLength); this.Metadata.ExifProfile = new ExifProfile(exifData); } diff --git a/src/ImageSharp/Formats/WebP/WebpFeatures.cs b/src/ImageSharp/Formats/WebP/WebpFeatures.cs index 385fe84d0..b26e4101e 100644 --- a/src/ImageSharp/Formats/WebP/WebpFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebpFeatures.cs @@ -47,9 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Webp public bool Animation { get; set; } /// - public void Dispose() - { - this.AlphaData?.Dispose(); - } + public void Dispose() => this.AlphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs index 81655b249..a5d7a8201 100644 --- a/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs @@ -14,36 +14,22 @@ namespace SixLabors.ImageSharp.Formats.Webp public int HeaderSize => 12; /// - public IImageFormat DetectFormat(ReadOnlySpan header) - { - return this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; - } + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - return header.Length >= this.HeaderSize && - this.IsRiffContainer(header) && - this.IsWebPFile(header); - } + private bool IsSupportedFileFormat(ReadOnlySpan header) => header.Length >= this.HeaderSize && this.IsRiffContainer(header) && 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.Slice(0, 4).SequenceEqual(WebpConstants.RiffFourCc); - } + private bool IsRiffContainer(ReadOnlySpan header) => header.Slice(0, 4).SequenceEqual(WebpConstants.RiffFourCc); /// /// Checks if 'WEBP' is present in the header. /// /// The header bytes. /// True, if its a webp file. - private bool IsWebPFile(ReadOnlySpan header) - { - return header.Slice(8, 4).SequenceEqual(WebpConstants.WebPHeader); - } + private bool IsWebPFile(ReadOnlySpan header) => header.Slice(8, 4).SequenceEqual(WebpConstants.WebPHeader); } } diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs index 4808ee3ce..462b65aa6 100644 --- a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs @@ -1242,19 +1242,19 @@ namespace SixLabors.ImageSharp.Formats.Webp Clip1 = new Dictionary(); for (int i = -255; i <= 255 + 255; ++i) { - Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); + Clip1[i] = (byte)(i < 0 ? 0 : i > 255 ? 255 : i); } Sclip1 = new Dictionary(); for (int i = -1020; i <= 1020; ++i) { - Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); + Sclip1[i] = (sbyte)(i < -128 ? -128 : i > 127 ? 127 : i); } Sclip2 = new Dictionary(); for (int i = -112; i <= 112; ++i) { - Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); + Sclip2[i] = (sbyte)(i < -16 ? -16 : i > 15 ? 15 : i); } InitializeModesProbabilities(); From 8a3f40728c0452744e5ee751cf484b2820160262 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Jul 2021 16:26:27 +0200 Subject: [PATCH 322/359] Remove setter from ChunkTypes property --- src/ImageSharp/Formats/WebP/WebpMetadata.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebpMetadata.cs b/src/ImageSharp/Formats/WebP/WebpMetadata.cs index 0eb466239..7020a386a 100644 --- a/src/ImageSharp/Formats/WebP/WebpMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebpMetadata.cs @@ -33,9 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Webp public WebpFormatType Format { get; set; } /// - /// Gets or sets all found chunk types ordered by appearance. + /// Gets all found chunk types ordered by appearance. /// - public Queue ChunkTypes { get; set; } = new Queue(); + public Queue ChunkTypes { get; } = new Queue(); /// /// Gets or sets a value indicating whether the webp file contains an animation. From 795111be30a8266a7ff36b8da886cda01766ac55 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Aug 2021 09:22:20 +0200 Subject: [PATCH 323/359] Add webp to the default configuration --- src/ImageSharp/Configuration.cs | 33 +++++++-------- .../Formats/WebP/ConfigurationExtensions.cs | 23 ----------- .../Codecs/DecodeWebp.cs | 6 ++- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- .../Formats/ImageFormatManagerTests.cs | 3 ++ .../Formats/WebP/ImageExtensionsTests.cs | 40 ++++++++----------- .../Formats/WebP/WebpDecoderTests.cs | 10 +---- .../Formats/WebP/WebpMetaDataTests.cs | 12 +----- 8 files changed, 42 insertions(+), 87 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 49b7aa79b..ea9524827 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; @@ -159,20 +160,17 @@ namespace SixLabors.ImageSharp /// Creates a shallow copy of the . /// /// A new configuration instance. - public Configuration Clone() + public Configuration Clone() => new Configuration { - return new Configuration - { - MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, - StreamProcessingBufferSize = this.StreamProcessingBufferSize, - ImageFormatsManager = this.ImageFormatsManager, - MemoryAllocator = this.MemoryAllocator, - ImageOperationsProvider = this.ImageOperationsProvider, - ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem, - WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, - }; - } + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + StreamProcessingBufferSize = this.StreamProcessingBufferSize, + ImageFormatsManager = this.ImageFormatsManager, + MemoryAllocator = this.MemoryAllocator, + ImageOperationsProvider = this.ImageOperationsProvider, + ReadOrigin = this.ReadOrigin, + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, + }; /// /// Creates the default instance with the following s preregistered: @@ -182,17 +180,16 @@ namespace SixLabors.ImageSharp /// . /// . /// . + /// . /// /// The default configuration of . - internal static Configuration CreateDefaultInstance() - { - return new Configuration( + internal static Configuration CreateDefaultInstance() => new Configuration( new PngConfigurationModule(), new JpegConfigurationModule(), new GifConfigurationModule(), new BmpConfigurationModule(), new TgaConfigurationModule(), - new TiffConfigurationModule()); - } + new TiffConfigurationModule(), + new WebpConfigurationModule()); } } diff --git a/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs b/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs deleted file mode 100644 index 5a8f178da..000000000 --- a/src/ImageSharp/Formats/WebP/ConfigurationExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Webp -{ - /// - /// Helper methods for the Configuration. - /// - public static class ConfigurationExtensions - { - /// - /// Registers the webp format detector, encoder and decoder. - /// - /// The configuration. - public static void AddWebp(this Configuration configuration) - { - configuration.ImageFormatsManager.AddImageFormat(WebpFormat.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); - configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index fedc22eb1..98e1f8689 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -46,7 +46,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossyMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using var image = new MagickImage(new MemoryStream(this.webpLossyBytes), settings); + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = new MagickImage(memoryStream, settings); return image.Width; } @@ -62,7 +63,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLosslessMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using var image = new MagickImage(new MemoryStream(this.webpLosslessBytes), settings); + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = new MagickImage(memoryStream, settings); return image.Width; } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 3ad8ef2f8..803babdfa 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 6; + private readonly int expectedDefaultConfigurationCount = 7; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 1e00bfff8..5cd70b100 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -38,6 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); @@ -45,6 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs index 31fc1919c..a17248612 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs @@ -13,26 +13,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class ImageExtensionsTests { - private readonly Configuration configuration; - - public ImageExtensionsTests() - { - this.configuration = new Configuration(); - this.configuration.AddWebp(); - } - [Fact] public void SaveAsWebp_Path() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { image.SaveAsWebp(file); } - using (Image.Load(this.configuration, file, out IImageFormat mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -44,12 +36,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { await image.SaveAsWebpAsync(file); } - using (Image.Load(this.configuration, file, out IImageFormat mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -61,12 +53,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { image.SaveAsWebp(file, new WebpEncoder()); } - using (Image.Load(this.configuration, file, out IImageFormat mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -78,12 +70,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { await image.SaveAsWebpAsync(file, new WebpEncoder()); } - using (Image.Load(this.configuration, file, out IImageFormat mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -94,14 +86,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { image.SaveAsWebp(memoryStream); } memoryStream.Position = 0; - using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) + using (Image.Load(memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -112,14 +104,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { await image.SaveAsWebpAsync(memoryStream); } memoryStream.Position = 0; - using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) + using (Image.Load(memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -130,14 +122,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { image.SaveAsWebp(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; - using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) + using (Image.Load(memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } @@ -148,14 +140,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using var memoryStream = new MemoryStream(); - using (var image = new Image(this.configuration, 10, 10)) + using (var image = new Image(10, 10)) { await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); } memoryStream.Position = 0; - using (Image.Load(this.configuration, memoryStream, out IImageFormat mime)) + using (Image.Load(memoryStream, out IImageFormat mime)) { Assert.Equal("image/webp", mime.DefaultMimeType); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 33f7930d1..262e1724b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -18,18 +18,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpDecoderTests { - private readonly Configuration configuration; - private static WebpDecoder WebpDecoder => new WebpDecoder(); private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); - public WebpDecoderTests() - { - this.configuration = new Configuration(); - this.configuration.AddWebp(); - } - [Theory] [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] @@ -46,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - IImageInfo imageInfo = Image.Identify(this.configuration, stream); + IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedHeight, imageInfo.Height); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 34ff9a1f5..901da3dbd 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -13,16 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpMetaDataTests { - private readonly Configuration configuration; - private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; - public WebpMetaDataTests() - { - this.configuration = new Configuration(); - this.configuration.AddWebp(); - } - [Theory] [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, false)] [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, true)] @@ -86,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp memoryStream.Position = 0; // assert - using Image image = WebpDecoder.Decode(this.configuration, memoryStream); + using var image = Image.Load(memoryStream); ExifProfile actualExif = image.Metadata.ExifProfile; Assert.NotNull(actualExif); Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); @@ -107,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp memoryStream.Position = 0; // assert - using Image image = WebpDecoder.Decode(this.configuration, memoryStream); + using var image = Image.Load(memoryStream); ExifProfile actualExif = image.Metadata.ExifProfile; Assert.NotNull(actualExif); Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); From b9440d18bc76817fd50102df56daf0f90bffd14f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 2 Oct 2021 17:08:02 +0200 Subject: [PATCH 324/359] Fix namespace in some places from SixLabors.ImageSharp.Formats.WebP to SixLabors.ImageSharp.Formats.Webp --- src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs | 5 +---- src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs | 3 +-- src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs | 1 - tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs | 2 +- 8 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs index 1539440f8..84097524b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -4,7 +4,7 @@ using System; using SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.WebP.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// /// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index d1ba52a6b..b5277d3be 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; -using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs index 763c89c57..0058684f5 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Formats.Webp.Lossy; - -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8Costs { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 5a9875633..b70c733c5 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index c9976f768..4207e5de9 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs index 220fd589d..f4aacf712 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -3,9 +3,8 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Webp; -namespace SixLabors.ImageSharp.Formats.WebP.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy { internal class Vp8Histogram { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 94c3dab02..919cc1753 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs index d4e1f69e0..4ff42f4ee 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.Formats.Webp.Lossy; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp From 3d36c5f77662dc152af19525237e442b06df4b8b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 2 Oct 2021 17:40:57 +0200 Subject: [PATCH 325/359] Change test images path from WebP to Webp --- ImageSharp.sln | 283 +++++++++--------- .../Formats/WebP/Lossless/NearLosslessEnc.cs | 1 - .../Program.cs | 3 + tests/ImageSharp.Tests/TestImages.cs | 248 ++++++++------- 4 files changed, 267 insertions(+), 268 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index e1074ced9..7c747651f 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -381,146 +381,146 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}" ProjectSection(SolutionItems) = preProject - tests\Images\Input\WebP\alpha_color_cache.webp = tests\Images\Input\WebP\alpha_color_cache.webp - tests\Images\Input\WebP\alpha_filter_0_method_0.webp = tests\Images\Input\WebP\alpha_filter_0_method_0.webp - tests\Images\Input\WebP\alpha_filter_0_method_1.webp = tests\Images\Input\WebP\alpha_filter_0_method_1.webp - tests\Images\Input\WebP\alpha_filter_1.webp = tests\Images\Input\WebP\alpha_filter_1.webp - tests\Images\Input\WebP\alpha_filter_1_method_0.webp = tests\Images\Input\WebP\alpha_filter_1_method_0.webp - tests\Images\Input\WebP\alpha_filter_1_method_1.webp = tests\Images\Input\WebP\alpha_filter_1_method_1.webp - tests\Images\Input\WebP\alpha_filter_2.webp = tests\Images\Input\WebP\alpha_filter_2.webp - tests\Images\Input\WebP\alpha_filter_2_method_0.webp = tests\Images\Input\WebP\alpha_filter_2_method_0.webp - tests\Images\Input\WebP\alpha_filter_2_method_1.webp = tests\Images\Input\WebP\alpha_filter_2_method_1.webp - tests\Images\Input\WebP\alpha_filter_3.webp = tests\Images\Input\WebP\alpha_filter_3.webp - tests\Images\Input\WebP\alpha_filter_3_method_0.webp = tests\Images\Input\WebP\alpha_filter_3_method_0.webp - tests\Images\Input\WebP\alpha_filter_3_method_1.webp = tests\Images\Input\WebP\alpha_filter_3_method_1.webp - tests\Images\Input\WebP\alpha_no_compression.webp = tests\Images\Input\WebP\alpha_no_compression.webp - tests\Images\Input\WebP\animated-webp.webp = tests\Images\Input\WebP\animated-webp.webp - tests\Images\Input\WebP\animated2.webp = tests\Images\Input\WebP\animated2.webp - tests\Images\Input\WebP\animated3.webp = tests\Images\Input\WebP\animated3.webp - tests\Images\Input\WebP\animated_lossy.webp = tests\Images\Input\WebP\animated_lossy.webp - tests\Images\Input\WebP\bad_palette_index.webp = tests\Images\Input\WebP\bad_palette_index.webp - tests\Images\Input\WebP\big_endian_bug_393.webp = tests\Images\Input\WebP\big_endian_bug_393.webp - tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp - tests\Images\Input\WebP\bug3.webp = tests\Images\Input\WebP\bug3.webp - tests\Images\Input\WebP\color_cache_bits_11.webp = tests\Images\Input\WebP\color_cache_bits_11.webp - tests\Images\Input\WebP\lossless1.webp = tests\Images\Input\WebP\lossless1.webp - tests\Images\Input\WebP\lossless2.webp = tests\Images\Input\WebP\lossless2.webp - tests\Images\Input\WebP\lossless3.webp = tests\Images\Input\WebP\lossless3.webp - tests\Images\Input\WebP\lossless4.webp = tests\Images\Input\WebP\lossless4.webp - tests\Images\Input\WebP\lossless_big_random_alpha.webp = tests\Images\Input\WebP\lossless_big_random_alpha.webp - tests\Images\Input\WebP\lossless_color_transform.bmp = tests\Images\Input\WebP\lossless_color_transform.bmp - tests\Images\Input\WebP\lossless_color_transform.pam = tests\Images\Input\WebP\lossless_color_transform.pam - tests\Images\Input\WebP\lossless_color_transform.pgm = tests\Images\Input\WebP\lossless_color_transform.pgm - tests\Images\Input\WebP\lossless_color_transform.ppm = tests\Images\Input\WebP\lossless_color_transform.ppm - tests\Images\Input\WebP\lossless_color_transform.tiff = tests\Images\Input\WebP\lossless_color_transform.tiff - tests\Images\Input\WebP\lossless_color_transform.webp = tests\Images\Input\WebP\lossless_color_transform.webp - tests\Images\Input\WebP\lossless_vec_1_0.webp = tests\Images\Input\WebP\lossless_vec_1_0.webp - tests\Images\Input\WebP\lossless_vec_1_1.webp = tests\Images\Input\WebP\lossless_vec_1_1.webp - tests\Images\Input\WebP\lossless_vec_1_10.webp = tests\Images\Input\WebP\lossless_vec_1_10.webp - tests\Images\Input\WebP\lossless_vec_1_11.webp = tests\Images\Input\WebP\lossless_vec_1_11.webp - tests\Images\Input\WebP\lossless_vec_1_12.webp = tests\Images\Input\WebP\lossless_vec_1_12.webp - tests\Images\Input\WebP\lossless_vec_1_13.webp = tests\Images\Input\WebP\lossless_vec_1_13.webp - tests\Images\Input\WebP\lossless_vec_1_14.webp = tests\Images\Input\WebP\lossless_vec_1_14.webp - tests\Images\Input\WebP\lossless_vec_1_15.webp = tests\Images\Input\WebP\lossless_vec_1_15.webp - tests\Images\Input\WebP\lossless_vec_1_2.webp = tests\Images\Input\WebP\lossless_vec_1_2.webp - tests\Images\Input\WebP\lossless_vec_1_3.webp = tests\Images\Input\WebP\lossless_vec_1_3.webp - tests\Images\Input\WebP\lossless_vec_1_4.webp = tests\Images\Input\WebP\lossless_vec_1_4.webp - tests\Images\Input\WebP\lossless_vec_1_5.webp = tests\Images\Input\WebP\lossless_vec_1_5.webp - tests\Images\Input\WebP\lossless_vec_1_6.webp = tests\Images\Input\WebP\lossless_vec_1_6.webp - tests\Images\Input\WebP\lossless_vec_1_7.webp = tests\Images\Input\WebP\lossless_vec_1_7.webp - tests\Images\Input\WebP\lossless_vec_1_8.webp = tests\Images\Input\WebP\lossless_vec_1_8.webp - tests\Images\Input\WebP\lossless_vec_1_9.webp = tests\Images\Input\WebP\lossless_vec_1_9.webp - tests\Images\Input\WebP\lossless_vec_2_0.webp = tests\Images\Input\WebP\lossless_vec_2_0.webp - tests\Images\Input\WebP\lossless_vec_2_1.webp = tests\Images\Input\WebP\lossless_vec_2_1.webp - tests\Images\Input\WebP\lossless_vec_2_10.webp = tests\Images\Input\WebP\lossless_vec_2_10.webp - tests\Images\Input\WebP\lossless_vec_2_11.webp = tests\Images\Input\WebP\lossless_vec_2_11.webp - tests\Images\Input\WebP\lossless_vec_2_12.webp = tests\Images\Input\WebP\lossless_vec_2_12.webp - tests\Images\Input\WebP\lossless_vec_2_13.webp = tests\Images\Input\WebP\lossless_vec_2_13.webp - tests\Images\Input\WebP\lossless_vec_2_14.webp = tests\Images\Input\WebP\lossless_vec_2_14.webp - tests\Images\Input\WebP\lossless_vec_2_15.webp = tests\Images\Input\WebP\lossless_vec_2_15.webp - tests\Images\Input\WebP\lossless_vec_2_2.webp = tests\Images\Input\WebP\lossless_vec_2_2.webp - tests\Images\Input\WebP\lossless_vec_2_3.webp = tests\Images\Input\WebP\lossless_vec_2_3.webp - tests\Images\Input\WebP\lossless_vec_2_4.webp = tests\Images\Input\WebP\lossless_vec_2_4.webp - tests\Images\Input\WebP\lossless_vec_2_5.webp = tests\Images\Input\WebP\lossless_vec_2_5.webp - tests\Images\Input\WebP\lossless_vec_2_6.webp = tests\Images\Input\WebP\lossless_vec_2_6.webp - tests\Images\Input\WebP\lossless_vec_2_7.webp = tests\Images\Input\WebP\lossless_vec_2_7.webp - tests\Images\Input\WebP\lossless_vec_2_8.webp = tests\Images\Input\WebP\lossless_vec_2_8.webp - tests\Images\Input\WebP\lossless_vec_2_9.webp = tests\Images\Input\WebP\lossless_vec_2_9.webp - tests\Images\Input\WebP\lossless_vec_list.txt = tests\Images\Input\WebP\lossless_vec_list.txt - tests\Images\Input\WebP\lossy_alpha1.webp = tests\Images\Input\WebP\lossy_alpha1.webp - tests\Images\Input\WebP\lossy_alpha2.webp = tests\Images\Input\WebP\lossy_alpha2.webp - tests\Images\Input\WebP\lossy_alpha3.webp = tests\Images\Input\WebP\lossy_alpha3.webp - tests\Images\Input\WebP\lossy_alpha4.webp = tests\Images\Input\WebP\lossy_alpha4.webp - tests\Images\Input\WebP\lossy_extreme_probabilities.webp = tests\Images\Input\WebP\lossy_extreme_probabilities.webp - tests\Images\Input\WebP\lossy_q0_f100.webp = tests\Images\Input\WebP\lossy_q0_f100.webp - tests\Images\Input\WebP\near_lossless_75.webp = tests\Images\Input\WebP\near_lossless_75.webp - tests\Images\Input\WebP\peak.png = tests\Images\Input\WebP\peak.png - tests\Images\Input\WebP\segment01.webp = tests\Images\Input\WebP\segment01.webp - tests\Images\Input\WebP\segment02.webp = tests\Images\Input\WebP\segment02.webp - tests\Images\Input\WebP\segment03.webp = tests\Images\Input\WebP\segment03.webp - tests\Images\Input\WebP\small_13x1.webp = tests\Images\Input\WebP\small_13x1.webp - tests\Images\Input\WebP\small_1x1.webp = tests\Images\Input\WebP\small_1x1.webp - tests\Images\Input\WebP\small_1x13.webp = tests\Images\Input\WebP\small_1x13.webp - tests\Images\Input\WebP\small_31x13.webp = tests\Images\Input\WebP\small_31x13.webp - tests\Images\Input\WebP\test-nostrong.webp = tests\Images\Input\WebP\test-nostrong.webp - tests\Images\Input\WebP\test.webp = tests\Images\Input\WebP\test.webp - tests\Images\Input\WebP\very_short.webp = tests\Images\Input\WebP\very_short.webp - tests\Images\Input\WebP\vp80-00-comprehensive-001.webp = tests\Images\Input\WebP\vp80-00-comprehensive-001.webp - tests\Images\Input\WebP\vp80-00-comprehensive-002.webp = tests\Images\Input\WebP\vp80-00-comprehensive-002.webp - tests\Images\Input\WebP\vp80-00-comprehensive-003.webp = tests\Images\Input\WebP\vp80-00-comprehensive-003.webp - tests\Images\Input\WebP\vp80-00-comprehensive-004.webp = tests\Images\Input\WebP\vp80-00-comprehensive-004.webp - tests\Images\Input\WebP\vp80-00-comprehensive-005.webp = tests\Images\Input\WebP\vp80-00-comprehensive-005.webp - tests\Images\Input\WebP\vp80-00-comprehensive-006.webp = tests\Images\Input\WebP\vp80-00-comprehensive-006.webp - tests\Images\Input\WebP\vp80-00-comprehensive-007.webp = tests\Images\Input\WebP\vp80-00-comprehensive-007.webp - tests\Images\Input\WebP\vp80-00-comprehensive-008.webp = tests\Images\Input\WebP\vp80-00-comprehensive-008.webp - tests\Images\Input\WebP\vp80-00-comprehensive-009.webp = tests\Images\Input\WebP\vp80-00-comprehensive-009.webp - tests\Images\Input\WebP\vp80-00-comprehensive-010.webp = tests\Images\Input\WebP\vp80-00-comprehensive-010.webp - tests\Images\Input\WebP\vp80-00-comprehensive-011.webp = tests\Images\Input\WebP\vp80-00-comprehensive-011.webp - tests\Images\Input\WebP\vp80-00-comprehensive-012.webp = tests\Images\Input\WebP\vp80-00-comprehensive-012.webp - tests\Images\Input\WebP\vp80-00-comprehensive-013.webp = tests\Images\Input\WebP\vp80-00-comprehensive-013.webp - tests\Images\Input\WebP\vp80-00-comprehensive-014.webp = tests\Images\Input\WebP\vp80-00-comprehensive-014.webp - tests\Images\Input\WebP\vp80-00-comprehensive-015.webp = tests\Images\Input\WebP\vp80-00-comprehensive-015.webp - tests\Images\Input\WebP\vp80-00-comprehensive-016.webp = tests\Images\Input\WebP\vp80-00-comprehensive-016.webp - tests\Images\Input\WebP\vp80-00-comprehensive-017.webp = tests\Images\Input\WebP\vp80-00-comprehensive-017.webp - tests\Images\Input\WebP\vp80-01-intra-1400.webp = tests\Images\Input\WebP\vp80-01-intra-1400.webp - tests\Images\Input\WebP\vp80-01-intra-1411.webp = tests\Images\Input\WebP\vp80-01-intra-1411.webp - tests\Images\Input\WebP\vp80-01-intra-1416.webp = tests\Images\Input\WebP\vp80-01-intra-1416.webp - tests\Images\Input\WebP\vp80-01-intra-1417.webp = tests\Images\Input\WebP\vp80-01-intra-1417.webp - tests\Images\Input\WebP\vp80-02-inter-1402.webp = tests\Images\Input\WebP\vp80-02-inter-1402.webp - tests\Images\Input\WebP\vp80-02-inter-1412.webp = tests\Images\Input\WebP\vp80-02-inter-1412.webp - tests\Images\Input\WebP\vp80-02-inter-1418.webp = tests\Images\Input\WebP\vp80-02-inter-1418.webp - tests\Images\Input\WebP\vp80-02-inter-1424.webp = tests\Images\Input\WebP\vp80-02-inter-1424.webp - tests\Images\Input\WebP\vp80-03-segmentation-1401.webp = tests\Images\Input\WebP\vp80-03-segmentation-1401.webp - tests\Images\Input\WebP\vp80-03-segmentation-1403.webp = tests\Images\Input\WebP\vp80-03-segmentation-1403.webp - tests\Images\Input\WebP\vp80-03-segmentation-1407.webp = tests\Images\Input\WebP\vp80-03-segmentation-1407.webp - tests\Images\Input\WebP\vp80-03-segmentation-1408.webp = tests\Images\Input\WebP\vp80-03-segmentation-1408.webp - tests\Images\Input\WebP\vp80-03-segmentation-1409.webp = tests\Images\Input\WebP\vp80-03-segmentation-1409.webp - tests\Images\Input\WebP\vp80-03-segmentation-1410.webp = tests\Images\Input\WebP\vp80-03-segmentation-1410.webp - tests\Images\Input\WebP\vp80-03-segmentation-1413.webp = tests\Images\Input\WebP\vp80-03-segmentation-1413.webp - tests\Images\Input\WebP\vp80-03-segmentation-1414.webp = tests\Images\Input\WebP\vp80-03-segmentation-1414.webp - tests\Images\Input\WebP\vp80-03-segmentation-1415.webp = tests\Images\Input\WebP\vp80-03-segmentation-1415.webp - tests\Images\Input\WebP\vp80-03-segmentation-1425.webp = tests\Images\Input\WebP\vp80-03-segmentation-1425.webp - tests\Images\Input\WebP\vp80-03-segmentation-1426.webp = tests\Images\Input\WebP\vp80-03-segmentation-1426.webp - tests\Images\Input\WebP\vp80-03-segmentation-1427.webp = tests\Images\Input\WebP\vp80-03-segmentation-1427.webp - tests\Images\Input\WebP\vp80-03-segmentation-1432.webp = tests\Images\Input\WebP\vp80-03-segmentation-1432.webp - tests\Images\Input\WebP\vp80-03-segmentation-1435.webp = tests\Images\Input\WebP\vp80-03-segmentation-1435.webp - tests\Images\Input\WebP\vp80-03-segmentation-1436.webp = tests\Images\Input\WebP\vp80-03-segmentation-1436.webp - tests\Images\Input\WebP\vp80-03-segmentation-1437.webp = tests\Images\Input\WebP\vp80-03-segmentation-1437.webp - tests\Images\Input\WebP\vp80-03-segmentation-1441.webp = tests\Images\Input\WebP\vp80-03-segmentation-1441.webp - tests\Images\Input\WebP\vp80-03-segmentation-1442.webp = tests\Images\Input\WebP\vp80-03-segmentation-1442.webp - tests\Images\Input\WebP\vp80-04-partitions-1404.webp = tests\Images\Input\WebP\vp80-04-partitions-1404.webp - tests\Images\Input\WebP\vp80-04-partitions-1405.webp = tests\Images\Input\WebP\vp80-04-partitions-1405.webp - tests\Images\Input\WebP\vp80-04-partitions-1406.webp = tests\Images\Input\WebP\vp80-04-partitions-1406.webp - tests\Images\Input\WebP\vp80-05-sharpness-1428.webp = tests\Images\Input\WebP\vp80-05-sharpness-1428.webp - tests\Images\Input\WebP\vp80-05-sharpness-1429.webp = tests\Images\Input\WebP\vp80-05-sharpness-1429.webp - tests\Images\Input\WebP\vp80-05-sharpness-1430.webp = tests\Images\Input\WebP\vp80-05-sharpness-1430.webp - tests\Images\Input\WebP\vp80-05-sharpness-1431.webp = tests\Images\Input\WebP\vp80-05-sharpness-1431.webp - tests\Images\Input\WebP\vp80-05-sharpness-1433.webp = tests\Images\Input\WebP\vp80-05-sharpness-1433.webp - tests\Images\Input\WebP\vp80-05-sharpness-1434.webp = tests\Images\Input\WebP\vp80-05-sharpness-1434.webp - tests\Images\Input\WebP\vp80-05-sharpness-1438.webp = tests\Images\Input\WebP\vp80-05-sharpness-1438.webp - tests\Images\Input\WebP\vp80-05-sharpness-1439.webp = tests\Images\Input\WebP\vp80-05-sharpness-1439.webp - tests\Images\Input\WebP\vp80-05-sharpness-1440.webp = tests\Images\Input\WebP\vp80-05-sharpness-1440.webp - tests\Images\Input\WebP\vp80-05-sharpness-1443.webp = tests\Images\Input\WebP\vp80-05-sharpness-1443.webp + tests\Images\Input\Webp\alpha_color_cache.webp = tests\Images\Input\Webp\alpha_color_cache.webp + tests\Images\Input\Webp\alpha_filter_0_method_0.webp = tests\Images\Input\Webp\alpha_filter_0_method_0.webp + tests\Images\Input\Webp\alpha_filter_0_method_1.webp = tests\Images\Input\Webp\alpha_filter_0_method_1.webp + tests\Images\Input\Webp\alpha_filter_1.webp = tests\Images\Input\Webp\alpha_filter_1.webp + tests\Images\Input\Webp\alpha_filter_1_method_0.webp = tests\Images\Input\Webp\alpha_filter_1_method_0.webp + tests\Images\Input\Webp\alpha_filter_1_method_1.webp = tests\Images\Input\Webp\alpha_filter_1_method_1.webp + tests\Images\Input\Webp\alpha_filter_2.webp = tests\Images\Input\Webp\alpha_filter_2.webp + tests\Images\Input\Webp\alpha_filter_2_method_0.webp = tests\Images\Input\Webp\alpha_filter_2_method_0.webp + tests\Images\Input\Webp\alpha_filter_2_method_1.webp = tests\Images\Input\Webp\alpha_filter_2_method_1.webp + tests\Images\Input\Webp\alpha_filter_3.webp = tests\Images\Input\Webp\alpha_filter_3.webp + tests\Images\Input\Webp\alpha_filter_3_method_0.webp = tests\Images\Input\Webp\alpha_filter_3_method_0.webp + tests\Images\Input\Webp\alpha_filter_3_method_1.webp = tests\Images\Input\Webp\alpha_filter_3_method_1.webp + tests\Images\Input\Webp\alpha_no_compression.webp = tests\Images\Input\Webp\alpha_no_compression.webp + tests\Images\Input\Webp\animated-webp.webp = tests\Images\Input\Webp\animated-webp.webp + tests\Images\Input\Webp\animated2.webp = tests\Images\Input\Webp\animated2.webp + tests\Images\Input\Webp\animated3.webp = tests\Images\Input\Webp\animated3.webp + tests\Images\Input\Webp\animated_lossy.webp = tests\Images\Input\Webp\animated_lossy.webp + tests\Images\Input\Webp\bad_palette_index.webp = tests\Images\Input\Webp\bad_palette_index.webp + tests\Images\Input\Webp\big_endian_bug_393.webp = tests\Images\Input\Webp\big_endian_bug_393.webp + tests\Images\Input\Webp\bryce.webp = tests\Images\Input\Webp\bryce.webp + tests\Images\Input\Webp\bug3.webp = tests\Images\Input\Webp\bug3.webp + tests\Images\Input\Webp\color_cache_bits_11.webp = tests\Images\Input\Webp\color_cache_bits_11.webp + tests\Images\Input\Webp\lossless1.webp = tests\Images\Input\Webp\lossless1.webp + tests\Images\Input\Webp\lossless2.webp = tests\Images\Input\Webp\lossless2.webp + tests\Images\Input\Webp\lossless3.webp = tests\Images\Input\Webp\lossless3.webp + tests\Images\Input\Webp\lossless4.webp = tests\Images\Input\Webp\lossless4.webp + tests\Images\Input\Webp\lossless_big_random_alpha.webp = tests\Images\Input\Webp\lossless_big_random_alpha.webp + tests\Images\Input\Webp\lossless_color_transform.bmp = tests\Images\Input\Webp\lossless_color_transform.bmp + tests\Images\Input\Webp\lossless_color_transform.pam = tests\Images\Input\Webp\lossless_color_transform.pam + tests\Images\Input\Webp\lossless_color_transform.pgm = tests\Images\Input\Webp\lossless_color_transform.pgm + tests\Images\Input\Webp\lossless_color_transform.ppm = tests\Images\Input\Webp\lossless_color_transform.ppm + tests\Images\Input\Webp\lossless_color_transform.tiff = tests\Images\Input\Webp\lossless_color_transform.tiff + tests\Images\Input\Webp\lossless_color_transform.webp = tests\Images\Input\Webp\lossless_color_transform.webp + tests\Images\Input\Webp\lossless_vec_1_0.webp = tests\Images\Input\Webp\lossless_vec_1_0.webp + tests\Images\Input\Webp\lossless_vec_1_1.webp = tests\Images\Input\Webp\lossless_vec_1_1.webp + tests\Images\Input\Webp\lossless_vec_1_10.webp = tests\Images\Input\Webp\lossless_vec_1_10.webp + tests\Images\Input\Webp\lossless_vec_1_11.webp = tests\Images\Input\Webp\lossless_vec_1_11.webp + tests\Images\Input\Webp\lossless_vec_1_12.webp = tests\Images\Input\Webp\lossless_vec_1_12.webp + tests\Images\Input\Webp\lossless_vec_1_13.webp = tests\Images\Input\Webp\lossless_vec_1_13.webp + tests\Images\Input\Webp\lossless_vec_1_14.webp = tests\Images\Input\Webp\lossless_vec_1_14.webp + tests\Images\Input\Webp\lossless_vec_1_15.webp = tests\Images\Input\Webp\lossless_vec_1_15.webp + tests\Images\Input\Webp\lossless_vec_1_2.webp = tests\Images\Input\Webp\lossless_vec_1_2.webp + tests\Images\Input\Webp\lossless_vec_1_3.webp = tests\Images\Input\Webp\lossless_vec_1_3.webp + tests\Images\Input\Webp\lossless_vec_1_4.webp = tests\Images\Input\Webp\lossless_vec_1_4.webp + tests\Images\Input\Webp\lossless_vec_1_5.webp = tests\Images\Input\Webp\lossless_vec_1_5.webp + tests\Images\Input\Webp\lossless_vec_1_6.webp = tests\Images\Input\Webp\lossless_vec_1_6.webp + tests\Images\Input\Webp\lossless_vec_1_7.webp = tests\Images\Input\Webp\lossless_vec_1_7.webp + tests\Images\Input\Webp\lossless_vec_1_8.webp = tests\Images\Input\Webp\lossless_vec_1_8.webp + tests\Images\Input\Webp\lossless_vec_1_9.webp = tests\Images\Input\Webp\lossless_vec_1_9.webp + tests\Images\Input\Webp\lossless_vec_2_0.webp = tests\Images\Input\Webp\lossless_vec_2_0.webp + tests\Images\Input\Webp\lossless_vec_2_1.webp = tests\Images\Input\Webp\lossless_vec_2_1.webp + tests\Images\Input\Webp\lossless_vec_2_10.webp = tests\Images\Input\Webp\lossless_vec_2_10.webp + tests\Images\Input\Webp\lossless_vec_2_11.webp = tests\Images\Input\Webp\lossless_vec_2_11.webp + tests\Images\Input\Webp\lossless_vec_2_12.webp = tests\Images\Input\Webp\lossless_vec_2_12.webp + tests\Images\Input\Webp\lossless_vec_2_13.webp = tests\Images\Input\Webp\lossless_vec_2_13.webp + tests\Images\Input\Webp\lossless_vec_2_14.webp = tests\Images\Input\Webp\lossless_vec_2_14.webp + tests\Images\Input\Webp\lossless_vec_2_15.webp = tests\Images\Input\Webp\lossless_vec_2_15.webp + tests\Images\Input\Webp\lossless_vec_2_2.webp = tests\Images\Input\Webp\lossless_vec_2_2.webp + tests\Images\Input\Webp\lossless_vec_2_3.webp = tests\Images\Input\Webp\lossless_vec_2_3.webp + tests\Images\Input\Webp\lossless_vec_2_4.webp = tests\Images\Input\Webp\lossless_vec_2_4.webp + tests\Images\Input\Webp\lossless_vec_2_5.webp = tests\Images\Input\Webp\lossless_vec_2_5.webp + tests\Images\Input\Webp\lossless_vec_2_6.webp = tests\Images\Input\Webp\lossless_vec_2_6.webp + tests\Images\Input\Webp\lossless_vec_2_7.webp = tests\Images\Input\Webp\lossless_vec_2_7.webp + tests\Images\Input\Webp\lossless_vec_2_8.webp = tests\Images\Input\Webp\lossless_vec_2_8.webp + tests\Images\Input\Webp\lossless_vec_2_9.webp = tests\Images\Input\Webp\lossless_vec_2_9.webp + tests\Images\Input\Webp\lossless_vec_list.txt = tests\Images\Input\Webp\lossless_vec_list.txt + tests\Images\Input\Webp\lossy_alpha1.webp = tests\Images\Input\Webp\lossy_alpha1.webp + tests\Images\Input\Webp\lossy_alpha2.webp = tests\Images\Input\Webp\lossy_alpha2.webp + tests\Images\Input\Webp\lossy_alpha3.webp = tests\Images\Input\Webp\lossy_alpha3.webp + tests\Images\Input\Webp\lossy_alpha4.webp = tests\Images\Input\Webp\lossy_alpha4.webp + tests\Images\Input\Webp\lossy_extreme_probabilities.webp = tests\Images\Input\Webp\lossy_extreme_probabilities.webp + tests\Images\Input\Webp\lossy_q0_f100.webp = tests\Images\Input\Webp\lossy_q0_f100.webp + tests\Images\Input\Webp\near_lossless_75.webp = tests\Images\Input\Webp\near_lossless_75.webp + tests\Images\Input\Webp\peak.png = tests\Images\Input\Webp\peak.png + tests\Images\Input\Webp\segment01.webp = tests\Images\Input\Webp\segment01.webp + tests\Images\Input\Webp\segment02.webp = tests\Images\Input\Webp\segment02.webp + tests\Images\Input\Webp\segment03.webp = tests\Images\Input\Webp\segment03.webp + tests\Images\Input\Webp\small_13x1.webp = tests\Images\Input\Webp\small_13x1.webp + tests\Images\Input\Webp\small_1x1.webp = tests\Images\Input\Webp\small_1x1.webp + tests\Images\Input\Webp\small_1x13.webp = tests\Images\Input\Webp\small_1x13.webp + tests\Images\Input\Webp\small_31x13.webp = tests\Images\Input\Webp\small_31x13.webp + tests\Images\Input\Webp\test-nostrong.webp = tests\Images\Input\Webp\test-nostrong.webp + tests\Images\Input\Webp\test.webp = tests\Images\Input\Webp\test.webp + tests\Images\Input\Webp\very_short.webp = tests\Images\Input\Webp\very_short.webp + tests\Images\Input\Webp\vp80-00-comprehensive-001.webp = tests\Images\Input\Webp\vp80-00-comprehensive-001.webp + tests\Images\Input\Webp\vp80-00-comprehensive-002.webp = tests\Images\Input\Webp\vp80-00-comprehensive-002.webp + tests\Images\Input\Webp\vp80-00-comprehensive-003.webp = tests\Images\Input\Webp\vp80-00-comprehensive-003.webp + tests\Images\Input\Webp\vp80-00-comprehensive-004.webp = tests\Images\Input\Webp\vp80-00-comprehensive-004.webp + tests\Images\Input\Webp\vp80-00-comprehensive-005.webp = tests\Images\Input\Webp\vp80-00-comprehensive-005.webp + tests\Images\Input\Webp\vp80-00-comprehensive-006.webp = tests\Images\Input\Webp\vp80-00-comprehensive-006.webp + tests\Images\Input\Webp\vp80-00-comprehensive-007.webp = tests\Images\Input\Webp\vp80-00-comprehensive-007.webp + tests\Images\Input\Webp\vp80-00-comprehensive-008.webp = tests\Images\Input\Webp\vp80-00-comprehensive-008.webp + tests\Images\Input\Webp\vp80-00-comprehensive-009.webp = tests\Images\Input\Webp\vp80-00-comprehensive-009.webp + tests\Images\Input\Webp\vp80-00-comprehensive-010.webp = tests\Images\Input\Webp\vp80-00-comprehensive-010.webp + tests\Images\Input\Webp\vp80-00-comprehensive-011.webp = tests\Images\Input\Webp\vp80-00-comprehensive-011.webp + tests\Images\Input\Webp\vp80-00-comprehensive-012.webp = tests\Images\Input\Webp\vp80-00-comprehensive-012.webp + tests\Images\Input\Webp\vp80-00-comprehensive-013.webp = tests\Images\Input\Webp\vp80-00-comprehensive-013.webp + tests\Images\Input\Webp\vp80-00-comprehensive-014.webp = tests\Images\Input\Webp\vp80-00-comprehensive-014.webp + tests\Images\Input\Webp\vp80-00-comprehensive-015.webp = tests\Images\Input\Webp\vp80-00-comprehensive-015.webp + tests\Images\Input\Webp\vp80-00-comprehensive-016.webp = tests\Images\Input\Webp\vp80-00-comprehensive-016.webp + tests\Images\Input\Webp\vp80-00-comprehensive-017.webp = tests\Images\Input\Webp\vp80-00-comprehensive-017.webp + tests\Images\Input\Webp\vp80-01-intra-1400.webp = tests\Images\Input\Webp\vp80-01-intra-1400.webp + tests\Images\Input\Webp\vp80-01-intra-1411.webp = tests\Images\Input\Webp\vp80-01-intra-1411.webp + tests\Images\Input\Webp\vp80-01-intra-1416.webp = tests\Images\Input\Webp\vp80-01-intra-1416.webp + tests\Images\Input\Webp\vp80-01-intra-1417.webp = tests\Images\Input\Webp\vp80-01-intra-1417.webp + tests\Images\Input\Webp\vp80-02-inter-1402.webp = tests\Images\Input\Webp\vp80-02-inter-1402.webp + tests\Images\Input\Webp\vp80-02-inter-1412.webp = tests\Images\Input\Webp\vp80-02-inter-1412.webp + tests\Images\Input\Webp\vp80-02-inter-1418.webp = tests\Images\Input\Webp\vp80-02-inter-1418.webp + tests\Images\Input\Webp\vp80-02-inter-1424.webp = tests\Images\Input\Webp\vp80-02-inter-1424.webp + tests\Images\Input\Webp\vp80-03-segmentation-1401.webp = tests\Images\Input\Webp\vp80-03-segmentation-1401.webp + tests\Images\Input\Webp\vp80-03-segmentation-1403.webp = tests\Images\Input\Webp\vp80-03-segmentation-1403.webp + tests\Images\Input\Webp\vp80-03-segmentation-1407.webp = tests\Images\Input\Webp\vp80-03-segmentation-1407.webp + tests\Images\Input\Webp\vp80-03-segmentation-1408.webp = tests\Images\Input\Webp\vp80-03-segmentation-1408.webp + tests\Images\Input\Webp\vp80-03-segmentation-1409.webp = tests\Images\Input\Webp\vp80-03-segmentation-1409.webp + tests\Images\Input\Webp\vp80-03-segmentation-1410.webp = tests\Images\Input\Webp\vp80-03-segmentation-1410.webp + tests\Images\Input\Webp\vp80-03-segmentation-1413.webp = tests\Images\Input\Webp\vp80-03-segmentation-1413.webp + tests\Images\Input\Webp\vp80-03-segmentation-1414.webp = tests\Images\Input\Webp\vp80-03-segmentation-1414.webp + tests\Images\Input\Webp\vp80-03-segmentation-1415.webp = tests\Images\Input\Webp\vp80-03-segmentation-1415.webp + tests\Images\Input\Webp\vp80-03-segmentation-1425.webp = tests\Images\Input\Webp\vp80-03-segmentation-1425.webp + tests\Images\Input\Webp\vp80-03-segmentation-1426.webp = tests\Images\Input\Webp\vp80-03-segmentation-1426.webp + tests\Images\Input\Webp\vp80-03-segmentation-1427.webp = tests\Images\Input\Webp\vp80-03-segmentation-1427.webp + tests\Images\Input\Webp\vp80-03-segmentation-1432.webp = tests\Images\Input\Webp\vp80-03-segmentation-1432.webp + tests\Images\Input\Webp\vp80-03-segmentation-1435.webp = tests\Images\Input\Webp\vp80-03-segmentation-1435.webp + tests\Images\Input\Webp\vp80-03-segmentation-1436.webp = tests\Images\Input\Webp\vp80-03-segmentation-1436.webp + tests\Images\Input\Webp\vp80-03-segmentation-1437.webp = tests\Images\Input\Webp\vp80-03-segmentation-1437.webp + tests\Images\Input\Webp\vp80-03-segmentation-1441.webp = tests\Images\Input\Webp\vp80-03-segmentation-1441.webp + tests\Images\Input\Webp\vp80-03-segmentation-1442.webp = tests\Images\Input\Webp\vp80-03-segmentation-1442.webp + tests\Images\Input\Webp\vp80-04-partitions-1404.webp = tests\Images\Input\Webp\vp80-04-partitions-1404.webp + tests\Images\Input\Webp\vp80-04-partitions-1405.webp = tests\Images\Input\Webp\vp80-04-partitions-1405.webp + tests\Images\Input\Webp\vp80-04-partitions-1406.webp = tests\Images\Input\Webp\vp80-04-partitions-1406.webp + tests\Images\Input\Webp\vp80-05-sharpness-1428.webp = tests\Images\Input\Webp\vp80-05-sharpness-1428.webp + tests\Images\Input\Webp\vp80-05-sharpness-1429.webp = tests\Images\Input\Webp\vp80-05-sharpness-1429.webp + tests\Images\Input\Webp\vp80-05-sharpness-1430.webp = tests\Images\Input\Webp\vp80-05-sharpness-1430.webp + tests\Images\Input\Webp\vp80-05-sharpness-1431.webp = tests\Images\Input\Webp\vp80-05-sharpness-1431.webp + tests\Images\Input\Webp\vp80-05-sharpness-1433.webp = tests\Images\Input\Webp\vp80-05-sharpness-1433.webp + tests\Images\Input\Webp\vp80-05-sharpness-1434.webp = tests\Images\Input\Webp\vp80-05-sharpness-1434.webp + tests\Images\Input\Webp\vp80-05-sharpness-1438.webp = tests\Images\Input\Webp\vp80-05-sharpness-1438.webp + tests\Images\Input\Webp\vp80-05-sharpness-1439.webp = tests\Images\Input\Webp\vp80-05-sharpness-1439.webp + tests\Images\Input\Webp\vp80-05-sharpness-1440.webp = tests\Images\Input\Webp\vp80-05-sharpness-1440.webp + tests\Images\Input\Webp\vp80-05-sharpness-1443.webp = tests\Images\Input\Webp\vp80-05-sharpness-1443.webp EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" @@ -536,7 +536,6 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "sha EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tga", "Tga", "{FCB65777-1D97-42DD-9ECC-96732D495D5C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{670DD46C-82E9-499A-B2D2-00A802ED0141}" ProjectSection(SolutionItems) = preProject tests\Images\Input\Png\issues\Issue_1014_1.png = tests\Images\Input\Png\issues\Issue_1014_1.png @@ -691,7 +690,7 @@ Global {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {FCB65777-1D97-42DD-9ECC-96732D495D5C} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs index 84097524b..46345e728 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Webp.Lossless; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 51d616fc7..e6e82b981 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e867404f6..75a7f63f9 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -518,190 +518,188 @@ namespace SixLabors.ImageSharp.Tests public static class WebP { // Reference image as png - public const string Peak = "WebP/peak.png"; + public const string Peak = "Webp/peak.png"; // Test pattern images for testing the encoder. - public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; - public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; - public const string RgbTestPattern100x100 = "WebP/rgb_pattern_100x100.png"; - public const string RgbTestPattern80x80 = "WebP/rgb_pattern_80x80.png"; - public const string RgbTestPattern63x63 = "WebP/rgb_pattern_63x63.png"; + public const string TestPatternOpaque = "Webp/testpattern_opaque.png"; + public const string TestPatternOpaqueSmall = "Webp/testpattern_opaque_small.png"; + public const string RgbTestPattern100x100 = "Webp/rgb_pattern_100x100.png"; + public const string RgbTestPattern80x80 = "Webp/rgb_pattern_80x80.png"; + public const string RgbTestPattern63x63 = "Webp/rgb_pattern_63x63.png"; // Test image for encoding image with a palette. - public const string Flag = "WebP/flag_of_germany.png"; + public const string Flag = "Webp/flag_of_germany.png"; // Test images for converting rgb data to yuv. - public const string Yuv = "WebP/yuv_test.png"; + public const string Yuv = "Webp/yuv_test.png"; public static class Animated { - public const string Animated1 = "WebP/animated-webp.webp"; - public const string Animated2 = "WebP/animated2.webp"; - public const string Animated3 = "WebP/animated3.webp"; - public const string Animated4 = "WebP/animated_lossy.webp"; + public const string Animated1 = "Webp/animated-webp.webp"; + public const string Animated2 = "Webp/animated2.webp"; + public const string Animated3 = "Webp/animated3.webp"; + public const string Animated4 = "Webp/animated_lossy.webp"; } public static class Lossless { - public const string Earth = "WebP/earth_lossless.webp"; - public const string Alpha = "WebP/lossless_alpha_small.webp"; - public const string WithExif = "WebP/exif_lossless.webp"; - public const string WithIccp = "WebP/lossless_with_iccp.webp"; - public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; - public const string NoTransform2 = "WebP/lossless_vec_2_0.webp"; - public const string GreenTransform1 = "WebP/lossless1.webp"; - public const string GreenTransform2 = "WebP/lossless2.webp"; - public const string GreenTransform3 = "WebP/lossless3.webp"; - public const string GreenTransform4 = "WebP/lossless_vec_1_4.webp"; - public const string GreenTransform5 = "WebP/lossless_vec_2_4.webp"; - public const string CrossColorTransform1 = "WebP/lossless_vec_1_8.webp"; - public const string CrossColorTransform2 = "WebP/lossless_vec_2_8.webp"; - public const string PredictorTransform1 = "WebP/lossless_vec_1_2.webp"; - public const string PredictorTransform2 = "WebP/lossless_vec_2_2.webp"; - public const string ColorIndexTransform1 = "WebP/lossless4.webp"; - public const string ColorIndexTransform2 = "WebP/lossless_vec_1_1.webp"; - public const string ColorIndexTransform3 = "WebP/lossless_vec_1_5.webp"; - public const string ColorIndexTransform4 = "WebP/lossless_vec_2_1.webp"; - public const string ColorIndexTransform5 = "WebP/lossless_vec_2_5.webp"; - public const string TwoTransforms1 = "WebP/lossless_vec_1_10.webp"; // cross_color, predictor - public const string TwoTransforms2 = "WebP/lossless_vec_1_12.webp"; // cross_color, substract_green - public const string TwoTransforms3 = "WebP/lossless_vec_1_13.webp"; // color_indexing, cross_color - public const string TwoTransforms4 = "WebP/lossless_vec_1_3.webp"; // color_indexing, predictor - public const string TwoTransforms5 = "WebP/lossless_vec_1_6.webp"; // substract_green, predictor - public const string TwoTransforms6 = "WebP/lossless_vec_1_7.webp"; // color_indexing, predictor - public const string TwoTransforms7 = "WebP/lossless_vec_1_9.webp"; // color_indexing, cross_color - public const string TwoTransforms8 = "WebP/lossless_vec_2_10.webp"; // predictor, cross_color - public const string TwoTransforms9 = "WebP/lossless_vec_2_12.webp"; // substract_green, cross_color - public const string TwoTransforms10 = "WebP/lossless_vec_2_13.webp"; // color_indexing, cross_color - public const string TwoTransforms11 = "WebP/lossless_vec_2_3.webp"; // color_indexing, predictor - public const string TwoTransforms12 = "WebP/lossless_vec_2_6.webp"; // substract_green, predictor - public const string TwoTransforms13 = "WebP/lossless_vec_2_9.webp"; // color_indexing, predictor + public const string Earth = "Webp/earth_lossless.webp"; + public const string Alpha = "Webp/lossless_alpha_small.webp"; + public const string WithExif = "Webp/exif_lossless.webp"; + public const string WithIccp = "Webp/lossless_with_iccp.webp"; + public const string NoTransform1 = "Webp/lossless_vec_1_0.webp"; + public const string NoTransform2 = "Webp/lossless_vec_2_0.webp"; + public const string GreenTransform1 = "Webp/lossless1.webp"; + public const string GreenTransform2 = "Webp/lossless2.webp"; + public const string GreenTransform3 = "Webp/lossless3.webp"; + public const string GreenTransform4 = "Webp/lossless_vec_1_4.webp"; + public const string GreenTransform5 = "Webp/lossless_vec_2_4.webp"; + public const string CrossColorTransform1 = "Webp/lossless_vec_1_8.webp"; + public const string CrossColorTransform2 = "Webp/lossless_vec_2_8.webp"; + public const string PredictorTransform1 = "Webp/lossless_vec_1_2.webp"; + public const string PredictorTransform2 = "Webp/lossless_vec_2_2.webp"; + public const string ColorIndexTransform1 = "Webp/lossless4.webp"; + public const string ColorIndexTransform2 = "Webp/lossless_vec_1_1.webp"; + public const string ColorIndexTransform3 = "Webp/lossless_vec_1_5.webp"; + public const string ColorIndexTransform4 = "Webp/lossless_vec_2_1.webp"; + public const string ColorIndexTransform5 = "Webp/lossless_vec_2_5.webp"; + public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor + public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green + public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color + public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor + public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor + public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor + public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color + public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color + public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color + public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color + public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor + public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor + public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor // substract_green, predictor, cross_color - public const string ThreeTransforms1 = "WebP/color_cache_bits_11.webp"; + public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms2 = "WebP/lossless_vec_1_11.webp"; + public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms3 = "WebP/lossless_vec_1_14.webp"; + public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms4 = "WebP/lossless_vec_1_15.webp"; + public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms5 = "WebP/lossless_vec_2_11.webp"; + public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms6 = "WebP/lossless_vec_2_14.webp"; + public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms7 = "WebP/lossless_vec_2_15.webp"; + public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; // substract_green, predictor, cross_color - public const string BikeThreeTransforms = "WebP/bike_lossless.webp"; + public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; - public const string BikeSmall = "WebP/bike_lossless_small.webp"; + public const string BikeSmall = "Webp/bike_lossless_small.webp"; // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." - public const string - LossLessCorruptImage1 = - "WebP/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. + public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. - public const string LossLessCorruptImage2 = "WebP/lossless_vec_2_7.webp"; // color_indexing, predictor. + public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. - public const string LossLessCorruptImage3 = "WebP/lossless_color_transform.webp"; // cross_color, predictor + public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor - public const string LossLessCorruptImage4 = "WebP/near_lossless_75.webp"; // predictor, cross_color. + public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. } public static class Lossy { - public const string Earth = "WebP/earth_lossy.webp"; - public const string WithExif = "WebP/exif_lossy.webp"; - public const string WithIccp = "WebP/lossy_with_iccp.webp"; + public const string Earth = "Webp/earth_lossy.webp"; + public const string WithExif = "Webp/exif_lossy.webp"; + public const string WithIccp = "Webp/lossy_with_iccp.webp"; // Lossy images without macroblock filtering. - public const string Bike = "WebP/bike_lossy.webp"; - public const string NoFilter01 = "WebP/vp80-01-intra-1400.webp"; - public const string NoFilter02 = "WebP/vp80-00-comprehensive-010.webp"; - public const string NoFilter03 = "WebP/vp80-00-comprehensive-005.webp"; - public const string NoFilter04 = "WebP/vp80-01-intra-1417.webp"; - public const string NoFilter05 = "WebP/vp80-02-inter-1402.webp"; - public const string NoFilter06 = "WebP/test.webp"; + public const string Bike = "Webp/bike_lossy.webp"; + public const string NoFilter01 = "Webp/vp80-01-intra-1400.webp"; + public const string NoFilter02 = "Webp/vp80-00-comprehensive-010.webp"; + public const string NoFilter03 = "Webp/vp80-00-comprehensive-005.webp"; + public const string NoFilter04 = "Webp/vp80-01-intra-1417.webp"; + public const string NoFilter05 = "Webp/vp80-02-inter-1402.webp"; + public const string NoFilter06 = "Webp/test.webp"; // Lossy images with a simple filter. - public const string SimpleFilter01 = "WebP/segment01.webp"; - public const string SimpleFilter02 = "WebP/segment02.webp"; - public const string SimpleFilter03 = "WebP/vp80-00-comprehensive-003.webp"; - public const string SimpleFilter04 = "WebP/vp80-00-comprehensive-007.webp"; - public const string SimpleFilter05 = "WebP/test-nostrong.webp"; + public const string SimpleFilter01 = "Webp/segment01.webp"; + public const string SimpleFilter02 = "Webp/segment02.webp"; + public const string SimpleFilter03 = "Webp/vp80-00-comprehensive-003.webp"; + public const string SimpleFilter04 = "Webp/vp80-00-comprehensive-007.webp"; + public const string SimpleFilter05 = "Webp/test-nostrong.webp"; // Lossy images with a complex filter. public const string IccpComplexFilter = WithIccp; - public const string VeryShort = "WebP/very_short.webp"; - public const string BikeComplexFilter = "WebP/bike_lossy_complex_filter.webp"; - public const string ComplexFilter01 = "WebP/vp80-02-inter-1418.webp"; - public const string ComplexFilter02 = "WebP/vp80-02-inter-1418.webp"; - public const string ComplexFilter03 = "WebP/vp80-00-comprehensive-002.webp"; - public const string ComplexFilter04 = "WebP/vp80-00-comprehensive-006.webp"; - public const string ComplexFilter05 = "WebP/vp80-00-comprehensive-009.webp"; - public const string ComplexFilter06 = "WebP/vp80-00-comprehensive-012.webp"; - public const string ComplexFilter07 = "WebP/vp80-00-comprehensive-015.webp"; - public const string ComplexFilter08 = "WebP/vp80-00-comprehensive-016.webp"; - public const string ComplexFilter09 = "WebP/vp80-00-comprehensive-017.webp"; + public const string VeryShort = "Webp/very_short.webp"; + public const string BikeComplexFilter = "Webp/bike_lossy_complex_filter.webp"; + public const string ComplexFilter01 = "Webp/vp80-02-inter-1418.webp"; + public const string ComplexFilter02 = "Webp/vp80-02-inter-1418.webp"; + public const string ComplexFilter03 = "Webp/vp80-00-comprehensive-002.webp"; + public const string ComplexFilter04 = "Webp/vp80-00-comprehensive-006.webp"; + public const string ComplexFilter05 = "Webp/vp80-00-comprehensive-009.webp"; + public const string ComplexFilter06 = "Webp/vp80-00-comprehensive-012.webp"; + public const string ComplexFilter07 = "Webp/vp80-00-comprehensive-015.webp"; + public const string ComplexFilter08 = "Webp/vp80-00-comprehensive-016.webp"; + public const string ComplexFilter09 = "Webp/vp80-00-comprehensive-017.webp"; // Lossy with partitions. - public const string Partitions01 = "WebP/vp80-04-partitions-1404.webp"; - public const string Partitions02 = "WebP/vp80-04-partitions-1405.webp"; - public const string Partitions03 = "WebP/vp80-04-partitions-1406.webp"; + public const string Partitions01 = "Webp/vp80-04-partitions-1404.webp"; + public const string Partitions02 = "Webp/vp80-04-partitions-1405.webp"; + public const string Partitions03 = "Webp/vp80-04-partitions-1406.webp"; // Lossy with segmentation. - public const string SegmentationNoFilter01 = "WebP/vp80-03-segmentation-1401.webp"; - public const string SegmentationNoFilter02 = "WebP/vp80-03-segmentation-1403.webp"; - public const string SegmentationNoFilter03 = "WebP/vp80-03-segmentation-1407.webp"; - public const string SegmentationNoFilter04 = "WebP/vp80-03-segmentation-1408.webp"; - public const string SegmentationNoFilter05 = "WebP/vp80-03-segmentation-1409.webp"; - public const string SegmentationNoFilter06 = "WebP/vp80-03-segmentation-1410.webp"; - public const string SegmentationComplexFilter01 = "WebP/vp80-03-segmentation-1413.webp"; - public const string SegmentationComplexFilter02 = "WebP/vp80-03-segmentation-1425.webp"; - public const string SegmentationComplexFilter03 = "WebP/vp80-03-segmentation-1426.webp"; - public const string SegmentationComplexFilter04 = "WebP/vp80-03-segmentation-1427.webp"; - public const string SegmentationComplexFilter05 = "WebP/vp80-03-segmentation-1432.webp"; + public const string SegmentationNoFilter01 = "Webp/vp80-03-segmentation-1401.webp"; + public const string SegmentationNoFilter02 = "Webp/vp80-03-segmentation-1403.webp"; + public const string SegmentationNoFilter03 = "Webp/vp80-03-segmentation-1407.webp"; + public const string SegmentationNoFilter04 = "Webp/vp80-03-segmentation-1408.webp"; + public const string SegmentationNoFilter05 = "Webp/vp80-03-segmentation-1409.webp"; + public const string SegmentationNoFilter06 = "Webp/vp80-03-segmentation-1410.webp"; + public const string SegmentationComplexFilter01 = "Webp/vp80-03-segmentation-1413.webp"; + public const string SegmentationComplexFilter02 = "Webp/vp80-03-segmentation-1425.webp"; + public const string SegmentationComplexFilter03 = "Webp/vp80-03-segmentation-1426.webp"; + public const string SegmentationComplexFilter04 = "Webp/vp80-03-segmentation-1427.webp"; + public const string SegmentationComplexFilter05 = "Webp/vp80-03-segmentation-1432.webp"; // Lossy with sharpness level. - public const string Sharpness01 = "WebP/vp80-05-sharpness-1428.webp"; - public const string Sharpness02 = "WebP/vp80-05-sharpness-1429.webp"; - public const string Sharpness03 = "WebP/vp80-05-sharpness-1430.webp"; - public const string Sharpness04 = "WebP/vp80-05-sharpness-1431.webp"; - public const string Sharpness05 = "WebP/vp80-05-sharpness-1433.webp"; - public const string Sharpness06 = "WebP/vp80-05-sharpness-1434.webp"; + public const string Sharpness01 = "Webp/vp80-05-sharpness-1428.webp"; + public const string Sharpness02 = "Webp/vp80-05-sharpness-1429.webp"; + public const string Sharpness03 = "Webp/vp80-05-sharpness-1430.webp"; + public const string Sharpness04 = "Webp/vp80-05-sharpness-1431.webp"; + public const string Sharpness05 = "Webp/vp80-05-sharpness-1433.webp"; + public const string Sharpness06 = "Webp/vp80-05-sharpness-1434.webp"; // Very small images (all with complex filter). - public const string Small01 = "WebP/small_13x1.webp"; - public const string Small02 = "WebP/small_1x1.webp"; - public const string Small03 = "WebP/small_1x13.webp"; - public const string Small04 = "WebP/small_31x13.webp"; + public const string Small01 = "Webp/small_13x1.webp"; + public const string Small02 = "Webp/small_1x1.webp"; + public const string Small03 = "Webp/small_1x13.webp"; + public const string Small04 = "Webp/small_31x13.webp"; // Lossy images with a alpha channel. - public const string Alpha1 = "WebP/lossy_alpha1.webp"; - public const string Alpha2 = "WebP/lossy_alpha2.webp"; - public const string Alpha3 = "WebP/alpha_color_cache.webp"; - public const string AlphaNoCompression = "WebP/alpha_no_compression.webp"; - public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp"; - public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp"; - public const string AlphaNoCompressionHorizontalFilter = "WebP/alpha_filter_1_method_0.webp"; - public const string AlphaCompressedHorizontalFilter = "WebP/alpha_filter_1_method_1.webp"; - public const string AlphaNoCompressionVerticalFilter = "WebP/alpha_filter_2_method_0.webp"; - public const string AlphaCompressedVerticalFilter = "WebP/alpha_filter_2_method_1.webp"; - public const string AlphaNoCompressionGradientFilter = "WebP/alpha_filter_3_method_0.webp"; - public const string AlphaCompressedGradientFilter = "WebP/alpha_filter_3_method_1.webp"; - public const string AlphaThinkingSmiley = "WebP/1602311202.webp"; - public const string AlphaSticker = "WebP/sticker.webp"; + public const string Alpha1 = "Webp/lossy_alpha1.webp"; + public const string Alpha2 = "Webp/lossy_alpha2.webp"; + public const string Alpha3 = "Webp/alpha_color_cache.webp"; + public const string AlphaNoCompression = "Webp/alpha_no_compression.webp"; + public const string AlphaNoCompressionNoFilter = "Webp/alpha_filter_0_method_0.webp"; + public const string AlphaCompressedNoFilter = "Webp/alpha_filter_0_method_1.webp"; + public const string AlphaNoCompressionHorizontalFilter = "Webp/alpha_filter_1_method_0.webp"; + public const string AlphaCompressedHorizontalFilter = "Webp/alpha_filter_1_method_1.webp"; + public const string AlphaNoCompressionVerticalFilter = "Webp/alpha_filter_2_method_0.webp"; + public const string AlphaCompressedVerticalFilter = "Webp/alpha_filter_2_method_1.webp"; + public const string AlphaNoCompressionGradientFilter = "Webp/alpha_filter_3_method_0.webp"; + public const string AlphaCompressedGradientFilter = "Webp/alpha_filter_3_method_1.webp"; + public const string AlphaThinkingSmiley = "Webp/1602311202.webp"; + public const string AlphaSticker = "Webp/sticker.webp"; // Issues - public const string Issue1594 = "WebP/issues/Issue1594.webp"; + public const string Issue1594 = "Webp/issues/Issue1594.webp"; } } From 18c6ed7b10cea12aaca91aa543f2979397ce2e09 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 2 Oct 2021 19:17:38 +0200 Subject: [PATCH 326/359] Add missing webp test files to the solution --- ImageSharp.sln | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ImageSharp.sln b/ImageSharp.sln index 7c747651f..e5c6ca2a0 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -381,6 +381,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}" ProjectSection(SolutionItems) = preProject + tests\Images\Input\Webp\1602311202.webp = tests\Images\Input\Webp\1602311202.webp tests\Images\Input\Webp\alpha_color_cache.webp = tests\Images\Input\Webp\alpha_color_cache.webp tests\Images\Input\Webp\alpha_filter_0_method_0.webp = tests\Images\Input\Webp\alpha_filter_0_method_0.webp tests\Images\Input\Webp\alpha_filter_0_method_1.webp = tests\Images\Input\Webp\alpha_filter_0_method_1.webp @@ -400,13 +401,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\Webp\animated_lossy.webp = tests\Images\Input\Webp\animated_lossy.webp tests\Images\Input\Webp\bad_palette_index.webp = tests\Images\Input\Webp\bad_palette_index.webp tests\Images\Input\Webp\big_endian_bug_393.webp = tests\Images\Input\Webp\big_endian_bug_393.webp + tests\Images\Input\Webp\bike_lossless.webp = tests\Images\Input\Webp\bike_lossless.webp + tests\Images\Input\Webp\bike_lossless_small.webp = tests\Images\Input\Webp\bike_lossless_small.webp + tests\Images\Input\Webp\bike_lossy.webp = tests\Images\Input\Webp\bike_lossy.webp + tests\Images\Input\Webp\bike_lossy_complex_filter.webp = tests\Images\Input\Webp\bike_lossy_complex_filter.webp tests\Images\Input\Webp\bryce.webp = tests\Images\Input\Webp\bryce.webp tests\Images\Input\Webp\bug3.webp = tests\Images\Input\Webp\bug3.webp tests\Images\Input\Webp\color_cache_bits_11.webp = tests\Images\Input\Webp\color_cache_bits_11.webp + tests\Images\Input\Webp\earth_lossless.webp = tests\Images\Input\Webp\earth_lossless.webp + tests\Images\Input\Webp\earth_lossy.webp = tests\Images\Input\Webp\earth_lossy.webp + tests\Images\Input\Webp\exif_lossless.webp = tests\Images\Input\Webp\exif_lossless.webp + tests\Images\Input\Webp\exif_lossy.webp = tests\Images\Input\Webp\exif_lossy.webp + tests\Images\Input\Webp\flag_of_germany.png = tests\Images\Input\Webp\flag_of_germany.png tests\Images\Input\Webp\lossless1.webp = tests\Images\Input\Webp\lossless1.webp tests\Images\Input\Webp\lossless2.webp = tests\Images\Input\Webp\lossless2.webp tests\Images\Input\Webp\lossless3.webp = tests\Images\Input\Webp\lossless3.webp tests\Images\Input\Webp\lossless4.webp = tests\Images\Input\Webp\lossless4.webp + tests\Images\Input\Webp\lossless_alpha_small.webp = tests\Images\Input\Webp\lossless_alpha_small.webp tests\Images\Input\Webp\lossless_big_random_alpha.webp = tests\Images\Input\Webp\lossless_big_random_alpha.webp tests\Images\Input\Webp\lossless_color_transform.bmp = tests\Images\Input\Webp\lossless_color_transform.bmp tests\Images\Input\Webp\lossless_color_transform.pam = tests\Images\Input\Webp\lossless_color_transform.pam @@ -447,14 +458,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\Webp\lossless_vec_2_8.webp = tests\Images\Input\Webp\lossless_vec_2_8.webp tests\Images\Input\Webp\lossless_vec_2_9.webp = tests\Images\Input\Webp\lossless_vec_2_9.webp tests\Images\Input\Webp\lossless_vec_list.txt = tests\Images\Input\Webp\lossless_vec_list.txt + tests\Images\Input\Webp\lossless_with_iccp.webp = tests\Images\Input\Webp\lossless_with_iccp.webp tests\Images\Input\Webp\lossy_alpha1.webp = tests\Images\Input\Webp\lossy_alpha1.webp tests\Images\Input\Webp\lossy_alpha2.webp = tests\Images\Input\Webp\lossy_alpha2.webp tests\Images\Input\Webp\lossy_alpha3.webp = tests\Images\Input\Webp\lossy_alpha3.webp tests\Images\Input\Webp\lossy_alpha4.webp = tests\Images\Input\Webp\lossy_alpha4.webp tests\Images\Input\Webp\lossy_extreme_probabilities.webp = tests\Images\Input\Webp\lossy_extreme_probabilities.webp tests\Images\Input\Webp\lossy_q0_f100.webp = tests\Images\Input\Webp\lossy_q0_f100.webp + tests\Images\Input\Webp\lossy_with_iccp.webp = tests\Images\Input\Webp\lossy_with_iccp.webp tests\Images\Input\Webp\near_lossless_75.webp = tests\Images\Input\Webp\near_lossless_75.webp tests\Images\Input\Webp\peak.png = tests\Images\Input\Webp\peak.png + tests\Images\Input\Webp\rgb_pattern_100x100.png = tests\Images\Input\Webp\rgb_pattern_100x100.png + tests\Images\Input\Webp\rgb_pattern_63x63.png = tests\Images\Input\Webp\rgb_pattern_63x63.png + tests\Images\Input\Webp\rgb_pattern_80x80.png = tests\Images\Input\Webp\rgb_pattern_80x80.png tests\Images\Input\Webp\segment01.webp = tests\Images\Input\Webp\segment01.webp tests\Images\Input\Webp\segment02.webp = tests\Images\Input\Webp\segment02.webp tests\Images\Input\Webp\segment03.webp = tests\Images\Input\Webp\segment03.webp @@ -462,8 +478,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\Webp\small_1x1.webp = tests\Images\Input\Webp\small_1x1.webp tests\Images\Input\Webp\small_1x13.webp = tests\Images\Input\Webp\small_1x13.webp tests\Images\Input\Webp\small_31x13.webp = tests\Images\Input\Webp\small_31x13.webp + tests\Images\Input\Webp\sticker.webp = tests\Images\Input\Webp\sticker.webp tests\Images\Input\Webp\test-nostrong.webp = tests\Images\Input\Webp\test-nostrong.webp tests\Images\Input\Webp\test.webp = tests\Images\Input\Webp\test.webp + tests\Images\Input\Webp\testpattern_opaque.png = tests\Images\Input\Webp\testpattern_opaque.png + tests\Images\Input\Webp\testpattern_opaque_small.png = tests\Images\Input\Webp\testpattern_opaque_small.png tests\Images\Input\Webp\very_short.webp = tests\Images\Input\Webp\very_short.webp tests\Images\Input\Webp\vp80-00-comprehensive-001.webp = tests\Images\Input\Webp\vp80-00-comprehensive-001.webp tests\Images\Input\Webp\vp80-00-comprehensive-002.webp = tests\Images\Input\Webp\vp80-00-comprehensive-002.webp @@ -521,6 +540,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\Webp\vp80-05-sharpness-1439.webp = tests\Images\Input\Webp\vp80-05-sharpness-1439.webp tests\Images\Input\Webp\vp80-05-sharpness-1440.webp = tests\Images\Input\Webp\vp80-05-sharpness-1440.webp tests\Images\Input\Webp\vp80-05-sharpness-1443.webp = tests\Images\Input\Webp\vp80-05-sharpness-1443.webp + tests\Images\Input\Webp\yuv_test.png = tests\Images\Input\Webp\yuv_test.png EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" From 3f021650aa04b77509900dd2b6b2fd658f25f68f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 3 Oct 2021 10:56:26 +0200 Subject: [PATCH 327/359] Change case of webp test images directory from WebP to Webp --- tests/Images/Input/{WebP => Webp}/1602311202.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_color_cache.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_0_method_0.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_0_method_1.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_1.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_1_method_0.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_1_method_1.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_2.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_2_method_0.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_2_method_1.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_3.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_3_method_0.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_filter_3_method_1.webp | 0 tests/Images/Input/{WebP => Webp}/alpha_no_compression.webp | 0 tests/Images/Input/{WebP => Webp}/animated-webp.webp | 0 tests/Images/Input/{WebP => Webp}/animated2.webp | 0 tests/Images/Input/{WebP => Webp}/animated3.webp | 0 tests/Images/Input/{WebP => Webp}/animated_lossy.webp | 0 tests/Images/Input/{WebP => Webp}/bad_palette_index.webp | 0 tests/Images/Input/{WebP => Webp}/big_endian_bug_393.webp | 0 tests/Images/Input/{WebP => Webp}/bike_lossless.webp | 0 tests/Images/Input/{WebP => Webp}/bike_lossless_small.webp | 0 tests/Images/Input/{WebP => Webp}/bike_lossy.webp | 0 tests/Images/Input/{WebP => Webp}/bike_lossy_complex_filter.webp | 0 tests/Images/Input/{WebP => Webp}/bryce.webp | 0 tests/Images/Input/{WebP => Webp}/bug3.webp | 0 tests/Images/Input/{WebP => Webp}/color_cache_bits_11.webp | 0 tests/Images/Input/{WebP => Webp}/earth_lossless.webp | 0 tests/Images/Input/{WebP => Webp}/earth_lossy.webp | 0 tests/Images/Input/{WebP => Webp}/exif_lossless.webp | 0 tests/Images/Input/{WebP => Webp}/exif_lossy.webp | 0 tests/Images/Input/{WebP => Webp}/flag_of_germany.png | 0 tests/Images/Input/{WebP => Webp}/issues/Issue1594.webp | 0 tests/Images/Input/{WebP => Webp}/lossless1.webp | 0 tests/Images/Input/{WebP => Webp}/lossless2.webp | 0 tests/Images/Input/{WebP => Webp}/lossless3.webp | 0 tests/Images/Input/{WebP => Webp}/lossless4.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_alpha_small.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_big_random_alpha.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_color_transform.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_0.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_1.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_10.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_11.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_12.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_13.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_14.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_15.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_2.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_3.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_4.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_5.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_6.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_7.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_8.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_1_9.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_0.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_1.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_10.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_11.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_12.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_13.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_14.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_15.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_2.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_3.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_4.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_5.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_6.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_7.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_8.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_vec_2_9.webp | 0 tests/Images/Input/{WebP => Webp}/lossless_with_iccp.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_alpha1.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_alpha2.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_alpha3.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_alpha4.webp | 0 .../Images/Input/{WebP => Webp}/lossy_extreme_probabilities.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_q0_f100.webp | 0 tests/Images/Input/{WebP => Webp}/lossy_with_iccp.webp | 0 tests/Images/Input/{WebP => Webp}/near_lossless_75.webp | 0 tests/Images/Input/{WebP => Webp}/peak.png | 0 tests/Images/Input/{WebP => Webp}/rgb_pattern_100x100.png | 0 tests/Images/Input/{WebP => Webp}/rgb_pattern_63x63.png | 0 tests/Images/Input/{WebP => Webp}/rgb_pattern_80x80.png | 0 tests/Images/Input/{WebP => Webp}/segment01.webp | 0 tests/Images/Input/{WebP => Webp}/segment02.webp | 0 tests/Images/Input/{WebP => Webp}/segment03.webp | 0 tests/Images/Input/{WebP => Webp}/small_13x1.webp | 0 tests/Images/Input/{WebP => Webp}/small_1x1.webp | 0 tests/Images/Input/{WebP => Webp}/small_1x13.webp | 0 tests/Images/Input/{WebP => Webp}/small_31x13.webp | 0 tests/Images/Input/{WebP => Webp}/sticker.webp | 0 tests/Images/Input/{WebP => Webp}/test-nostrong.webp | 0 tests/Images/Input/{WebP => Webp}/test.webp | 0 tests/Images/Input/{WebP => Webp}/testpattern_opaque.png | 0 tests/Images/Input/{WebP => Webp}/testpattern_opaque_small.png | 0 tests/Images/Input/{WebP => Webp}/very_short.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-001.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-002.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-003.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-004.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-005.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-006.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-007.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-008.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-009.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-010.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-011.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-012.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-013.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-014.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-015.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-016.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-017.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-01-intra-1400.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-01-intra-1411.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-01-intra-1416.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-01-intra-1417.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-02-inter-1402.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-02-inter-1412.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-02-inter-1418.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-02-inter-1424.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1401.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1403.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1407.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1408.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1409.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1410.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1413.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1414.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1415.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1425.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1426.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1427.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1432.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1435.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1436.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1437.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1441.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1442.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1404.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1405.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1406.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1428.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1429.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1430.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1431.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1433.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1434.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1438.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1439.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1440.webp | 0 tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1443.webp | 0 tests/Images/Input/{WebP => Webp}/yuv_test.png | 0 155 files changed, 0 insertions(+), 0 deletions(-) rename tests/Images/Input/{WebP => Webp}/1602311202.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_color_cache.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_0_method_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_0_method_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_1_method_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_1_method_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_2.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_2_method_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_2_method_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_3.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_3_method_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_filter_3_method_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/alpha_no_compression.webp (100%) rename tests/Images/Input/{WebP => Webp}/animated-webp.webp (100%) rename tests/Images/Input/{WebP => Webp}/animated2.webp (100%) rename tests/Images/Input/{WebP => Webp}/animated3.webp (100%) rename tests/Images/Input/{WebP => Webp}/animated_lossy.webp (100%) rename tests/Images/Input/{WebP => Webp}/bad_palette_index.webp (100%) rename tests/Images/Input/{WebP => Webp}/big_endian_bug_393.webp (100%) rename tests/Images/Input/{WebP => Webp}/bike_lossless.webp (100%) rename tests/Images/Input/{WebP => Webp}/bike_lossless_small.webp (100%) rename tests/Images/Input/{WebP => Webp}/bike_lossy.webp (100%) rename tests/Images/Input/{WebP => Webp}/bike_lossy_complex_filter.webp (100%) rename tests/Images/Input/{WebP => Webp}/bryce.webp (100%) rename tests/Images/Input/{WebP => Webp}/bug3.webp (100%) rename tests/Images/Input/{WebP => Webp}/color_cache_bits_11.webp (100%) rename tests/Images/Input/{WebP => Webp}/earth_lossless.webp (100%) rename tests/Images/Input/{WebP => Webp}/earth_lossy.webp (100%) rename tests/Images/Input/{WebP => Webp}/exif_lossless.webp (100%) rename tests/Images/Input/{WebP => Webp}/exif_lossy.webp (100%) rename tests/Images/Input/{WebP => Webp}/flag_of_germany.png (100%) rename tests/Images/Input/{WebP => Webp}/issues/Issue1594.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless1.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless2.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless3.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless4.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_alpha_small.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_big_random_alpha.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_color_transform.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_10.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_11.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_12.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_13.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_14.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_15.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_2.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_3.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_4.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_5.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_6.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_7.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_8.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_1_9.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_0.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_1.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_10.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_11.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_12.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_13.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_14.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_15.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_2.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_3.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_4.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_5.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_6.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_7.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_8.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_vec_2_9.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossless_with_iccp.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_alpha1.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_alpha2.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_alpha3.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_alpha4.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_extreme_probabilities.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_q0_f100.webp (100%) rename tests/Images/Input/{WebP => Webp}/lossy_with_iccp.webp (100%) rename tests/Images/Input/{WebP => Webp}/near_lossless_75.webp (100%) rename tests/Images/Input/{WebP => Webp}/peak.png (100%) rename tests/Images/Input/{WebP => Webp}/rgb_pattern_100x100.png (100%) rename tests/Images/Input/{WebP => Webp}/rgb_pattern_63x63.png (100%) rename tests/Images/Input/{WebP => Webp}/rgb_pattern_80x80.png (100%) rename tests/Images/Input/{WebP => Webp}/segment01.webp (100%) rename tests/Images/Input/{WebP => Webp}/segment02.webp (100%) rename tests/Images/Input/{WebP => Webp}/segment03.webp (100%) rename tests/Images/Input/{WebP => Webp}/small_13x1.webp (100%) rename tests/Images/Input/{WebP => Webp}/small_1x1.webp (100%) rename tests/Images/Input/{WebP => Webp}/small_1x13.webp (100%) rename tests/Images/Input/{WebP => Webp}/small_31x13.webp (100%) rename tests/Images/Input/{WebP => Webp}/sticker.webp (100%) rename tests/Images/Input/{WebP => Webp}/test-nostrong.webp (100%) rename tests/Images/Input/{WebP => Webp}/test.webp (100%) rename tests/Images/Input/{WebP => Webp}/testpattern_opaque.png (100%) rename tests/Images/Input/{WebP => Webp}/testpattern_opaque_small.png (100%) rename tests/Images/Input/{WebP => Webp}/very_short.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-001.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-002.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-003.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-004.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-005.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-006.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-007.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-008.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-009.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-010.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-011.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-012.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-013.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-014.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-015.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-016.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-00-comprehensive-017.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-01-intra-1400.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-01-intra-1411.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-01-intra-1416.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-01-intra-1417.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-02-inter-1402.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-02-inter-1412.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-02-inter-1418.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-02-inter-1424.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1401.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1403.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1407.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1408.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1409.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1410.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1413.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1414.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1415.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1425.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1426.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1427.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1432.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1435.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1436.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1437.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1441.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-03-segmentation-1442.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1404.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1405.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-04-partitions-1406.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1428.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1429.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1430.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1431.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1433.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1434.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1438.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1439.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1440.webp (100%) rename tests/Images/Input/{WebP => Webp}/vp80-05-sharpness-1443.webp (100%) rename tests/Images/Input/{WebP => Webp}/yuv_test.png (100%) diff --git a/tests/Images/Input/WebP/1602311202.webp b/tests/Images/Input/Webp/1602311202.webp similarity index 100% rename from tests/Images/Input/WebP/1602311202.webp rename to tests/Images/Input/Webp/1602311202.webp diff --git a/tests/Images/Input/WebP/alpha_color_cache.webp b/tests/Images/Input/Webp/alpha_color_cache.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_color_cache.webp rename to tests/Images/Input/Webp/alpha_color_cache.webp diff --git a/tests/Images/Input/WebP/alpha_filter_0_method_0.webp b/tests/Images/Input/Webp/alpha_filter_0_method_0.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_0_method_0.webp rename to tests/Images/Input/Webp/alpha_filter_0_method_0.webp diff --git a/tests/Images/Input/WebP/alpha_filter_0_method_1.webp b/tests/Images/Input/Webp/alpha_filter_0_method_1.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_0_method_1.webp rename to tests/Images/Input/Webp/alpha_filter_0_method_1.webp diff --git a/tests/Images/Input/WebP/alpha_filter_1.webp b/tests/Images/Input/Webp/alpha_filter_1.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_1.webp rename to tests/Images/Input/Webp/alpha_filter_1.webp diff --git a/tests/Images/Input/WebP/alpha_filter_1_method_0.webp b/tests/Images/Input/Webp/alpha_filter_1_method_0.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_1_method_0.webp rename to tests/Images/Input/Webp/alpha_filter_1_method_0.webp diff --git a/tests/Images/Input/WebP/alpha_filter_1_method_1.webp b/tests/Images/Input/Webp/alpha_filter_1_method_1.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_1_method_1.webp rename to tests/Images/Input/Webp/alpha_filter_1_method_1.webp diff --git a/tests/Images/Input/WebP/alpha_filter_2.webp b/tests/Images/Input/Webp/alpha_filter_2.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_2.webp rename to tests/Images/Input/Webp/alpha_filter_2.webp diff --git a/tests/Images/Input/WebP/alpha_filter_2_method_0.webp b/tests/Images/Input/Webp/alpha_filter_2_method_0.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_2_method_0.webp rename to tests/Images/Input/Webp/alpha_filter_2_method_0.webp diff --git a/tests/Images/Input/WebP/alpha_filter_2_method_1.webp b/tests/Images/Input/Webp/alpha_filter_2_method_1.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_2_method_1.webp rename to tests/Images/Input/Webp/alpha_filter_2_method_1.webp diff --git a/tests/Images/Input/WebP/alpha_filter_3.webp b/tests/Images/Input/Webp/alpha_filter_3.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_3.webp rename to tests/Images/Input/Webp/alpha_filter_3.webp diff --git a/tests/Images/Input/WebP/alpha_filter_3_method_0.webp b/tests/Images/Input/Webp/alpha_filter_3_method_0.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_3_method_0.webp rename to tests/Images/Input/Webp/alpha_filter_3_method_0.webp diff --git a/tests/Images/Input/WebP/alpha_filter_3_method_1.webp b/tests/Images/Input/Webp/alpha_filter_3_method_1.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_filter_3_method_1.webp rename to tests/Images/Input/Webp/alpha_filter_3_method_1.webp diff --git a/tests/Images/Input/WebP/alpha_no_compression.webp b/tests/Images/Input/Webp/alpha_no_compression.webp similarity index 100% rename from tests/Images/Input/WebP/alpha_no_compression.webp rename to tests/Images/Input/Webp/alpha_no_compression.webp diff --git a/tests/Images/Input/WebP/animated-webp.webp b/tests/Images/Input/Webp/animated-webp.webp similarity index 100% rename from tests/Images/Input/WebP/animated-webp.webp rename to tests/Images/Input/Webp/animated-webp.webp diff --git a/tests/Images/Input/WebP/animated2.webp b/tests/Images/Input/Webp/animated2.webp similarity index 100% rename from tests/Images/Input/WebP/animated2.webp rename to tests/Images/Input/Webp/animated2.webp diff --git a/tests/Images/Input/WebP/animated3.webp b/tests/Images/Input/Webp/animated3.webp similarity index 100% rename from tests/Images/Input/WebP/animated3.webp rename to tests/Images/Input/Webp/animated3.webp diff --git a/tests/Images/Input/WebP/animated_lossy.webp b/tests/Images/Input/Webp/animated_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/animated_lossy.webp rename to tests/Images/Input/Webp/animated_lossy.webp diff --git a/tests/Images/Input/WebP/bad_palette_index.webp b/tests/Images/Input/Webp/bad_palette_index.webp similarity index 100% rename from tests/Images/Input/WebP/bad_palette_index.webp rename to tests/Images/Input/Webp/bad_palette_index.webp diff --git a/tests/Images/Input/WebP/big_endian_bug_393.webp b/tests/Images/Input/Webp/big_endian_bug_393.webp similarity index 100% rename from tests/Images/Input/WebP/big_endian_bug_393.webp rename to tests/Images/Input/Webp/big_endian_bug_393.webp diff --git a/tests/Images/Input/WebP/bike_lossless.webp b/tests/Images/Input/Webp/bike_lossless.webp similarity index 100% rename from tests/Images/Input/WebP/bike_lossless.webp rename to tests/Images/Input/Webp/bike_lossless.webp diff --git a/tests/Images/Input/WebP/bike_lossless_small.webp b/tests/Images/Input/Webp/bike_lossless_small.webp similarity index 100% rename from tests/Images/Input/WebP/bike_lossless_small.webp rename to tests/Images/Input/Webp/bike_lossless_small.webp diff --git a/tests/Images/Input/WebP/bike_lossy.webp b/tests/Images/Input/Webp/bike_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/bike_lossy.webp rename to tests/Images/Input/Webp/bike_lossy.webp diff --git a/tests/Images/Input/WebP/bike_lossy_complex_filter.webp b/tests/Images/Input/Webp/bike_lossy_complex_filter.webp similarity index 100% rename from tests/Images/Input/WebP/bike_lossy_complex_filter.webp rename to tests/Images/Input/Webp/bike_lossy_complex_filter.webp diff --git a/tests/Images/Input/WebP/bryce.webp b/tests/Images/Input/Webp/bryce.webp similarity index 100% rename from tests/Images/Input/WebP/bryce.webp rename to tests/Images/Input/Webp/bryce.webp diff --git a/tests/Images/Input/WebP/bug3.webp b/tests/Images/Input/Webp/bug3.webp similarity index 100% rename from tests/Images/Input/WebP/bug3.webp rename to tests/Images/Input/Webp/bug3.webp diff --git a/tests/Images/Input/WebP/color_cache_bits_11.webp b/tests/Images/Input/Webp/color_cache_bits_11.webp similarity index 100% rename from tests/Images/Input/WebP/color_cache_bits_11.webp rename to tests/Images/Input/Webp/color_cache_bits_11.webp diff --git a/tests/Images/Input/WebP/earth_lossless.webp b/tests/Images/Input/Webp/earth_lossless.webp similarity index 100% rename from tests/Images/Input/WebP/earth_lossless.webp rename to tests/Images/Input/Webp/earth_lossless.webp diff --git a/tests/Images/Input/WebP/earth_lossy.webp b/tests/Images/Input/Webp/earth_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/earth_lossy.webp rename to tests/Images/Input/Webp/earth_lossy.webp diff --git a/tests/Images/Input/WebP/exif_lossless.webp b/tests/Images/Input/Webp/exif_lossless.webp similarity index 100% rename from tests/Images/Input/WebP/exif_lossless.webp rename to tests/Images/Input/Webp/exif_lossless.webp diff --git a/tests/Images/Input/WebP/exif_lossy.webp b/tests/Images/Input/Webp/exif_lossy.webp similarity index 100% rename from tests/Images/Input/WebP/exif_lossy.webp rename to tests/Images/Input/Webp/exif_lossy.webp diff --git a/tests/Images/Input/WebP/flag_of_germany.png b/tests/Images/Input/Webp/flag_of_germany.png similarity index 100% rename from tests/Images/Input/WebP/flag_of_germany.png rename to tests/Images/Input/Webp/flag_of_germany.png diff --git a/tests/Images/Input/WebP/issues/Issue1594.webp b/tests/Images/Input/Webp/issues/Issue1594.webp similarity index 100% rename from tests/Images/Input/WebP/issues/Issue1594.webp rename to tests/Images/Input/Webp/issues/Issue1594.webp diff --git a/tests/Images/Input/WebP/lossless1.webp b/tests/Images/Input/Webp/lossless1.webp similarity index 100% rename from tests/Images/Input/WebP/lossless1.webp rename to tests/Images/Input/Webp/lossless1.webp diff --git a/tests/Images/Input/WebP/lossless2.webp b/tests/Images/Input/Webp/lossless2.webp similarity index 100% rename from tests/Images/Input/WebP/lossless2.webp rename to tests/Images/Input/Webp/lossless2.webp diff --git a/tests/Images/Input/WebP/lossless3.webp b/tests/Images/Input/Webp/lossless3.webp similarity index 100% rename from tests/Images/Input/WebP/lossless3.webp rename to tests/Images/Input/Webp/lossless3.webp diff --git a/tests/Images/Input/WebP/lossless4.webp b/tests/Images/Input/Webp/lossless4.webp similarity index 100% rename from tests/Images/Input/WebP/lossless4.webp rename to tests/Images/Input/Webp/lossless4.webp diff --git a/tests/Images/Input/WebP/lossless_alpha_small.webp b/tests/Images/Input/Webp/lossless_alpha_small.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_alpha_small.webp rename to tests/Images/Input/Webp/lossless_alpha_small.webp diff --git a/tests/Images/Input/WebP/lossless_big_random_alpha.webp b/tests/Images/Input/Webp/lossless_big_random_alpha.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_big_random_alpha.webp rename to tests/Images/Input/Webp/lossless_big_random_alpha.webp diff --git a/tests/Images/Input/WebP/lossless_color_transform.webp b/tests/Images/Input/Webp/lossless_color_transform.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_color_transform.webp rename to tests/Images/Input/Webp/lossless_color_transform.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_0.webp b/tests/Images/Input/Webp/lossless_vec_1_0.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_0.webp rename to tests/Images/Input/Webp/lossless_vec_1_0.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_1.webp b/tests/Images/Input/Webp/lossless_vec_1_1.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_1.webp rename to tests/Images/Input/Webp/lossless_vec_1_1.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_10.webp b/tests/Images/Input/Webp/lossless_vec_1_10.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_10.webp rename to tests/Images/Input/Webp/lossless_vec_1_10.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_11.webp b/tests/Images/Input/Webp/lossless_vec_1_11.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_11.webp rename to tests/Images/Input/Webp/lossless_vec_1_11.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_12.webp b/tests/Images/Input/Webp/lossless_vec_1_12.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_12.webp rename to tests/Images/Input/Webp/lossless_vec_1_12.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_13.webp b/tests/Images/Input/Webp/lossless_vec_1_13.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_13.webp rename to tests/Images/Input/Webp/lossless_vec_1_13.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_14.webp b/tests/Images/Input/Webp/lossless_vec_1_14.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_14.webp rename to tests/Images/Input/Webp/lossless_vec_1_14.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_15.webp b/tests/Images/Input/Webp/lossless_vec_1_15.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_15.webp rename to tests/Images/Input/Webp/lossless_vec_1_15.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_2.webp b/tests/Images/Input/Webp/lossless_vec_1_2.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_2.webp rename to tests/Images/Input/Webp/lossless_vec_1_2.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_3.webp b/tests/Images/Input/Webp/lossless_vec_1_3.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_3.webp rename to tests/Images/Input/Webp/lossless_vec_1_3.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_4.webp b/tests/Images/Input/Webp/lossless_vec_1_4.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_4.webp rename to tests/Images/Input/Webp/lossless_vec_1_4.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_5.webp b/tests/Images/Input/Webp/lossless_vec_1_5.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_5.webp rename to tests/Images/Input/Webp/lossless_vec_1_5.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_6.webp b/tests/Images/Input/Webp/lossless_vec_1_6.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_6.webp rename to tests/Images/Input/Webp/lossless_vec_1_6.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_7.webp b/tests/Images/Input/Webp/lossless_vec_1_7.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_7.webp rename to tests/Images/Input/Webp/lossless_vec_1_7.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_8.webp b/tests/Images/Input/Webp/lossless_vec_1_8.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_8.webp rename to tests/Images/Input/Webp/lossless_vec_1_8.webp diff --git a/tests/Images/Input/WebP/lossless_vec_1_9.webp b/tests/Images/Input/Webp/lossless_vec_1_9.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_1_9.webp rename to tests/Images/Input/Webp/lossless_vec_1_9.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_0.webp b/tests/Images/Input/Webp/lossless_vec_2_0.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_0.webp rename to tests/Images/Input/Webp/lossless_vec_2_0.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_1.webp b/tests/Images/Input/Webp/lossless_vec_2_1.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_1.webp rename to tests/Images/Input/Webp/lossless_vec_2_1.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_10.webp b/tests/Images/Input/Webp/lossless_vec_2_10.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_10.webp rename to tests/Images/Input/Webp/lossless_vec_2_10.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_11.webp b/tests/Images/Input/Webp/lossless_vec_2_11.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_11.webp rename to tests/Images/Input/Webp/lossless_vec_2_11.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_12.webp b/tests/Images/Input/Webp/lossless_vec_2_12.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_12.webp rename to tests/Images/Input/Webp/lossless_vec_2_12.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_13.webp b/tests/Images/Input/Webp/lossless_vec_2_13.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_13.webp rename to tests/Images/Input/Webp/lossless_vec_2_13.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_14.webp b/tests/Images/Input/Webp/lossless_vec_2_14.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_14.webp rename to tests/Images/Input/Webp/lossless_vec_2_14.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_15.webp b/tests/Images/Input/Webp/lossless_vec_2_15.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_15.webp rename to tests/Images/Input/Webp/lossless_vec_2_15.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_2.webp b/tests/Images/Input/Webp/lossless_vec_2_2.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_2.webp rename to tests/Images/Input/Webp/lossless_vec_2_2.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_3.webp b/tests/Images/Input/Webp/lossless_vec_2_3.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_3.webp rename to tests/Images/Input/Webp/lossless_vec_2_3.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_4.webp b/tests/Images/Input/Webp/lossless_vec_2_4.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_4.webp rename to tests/Images/Input/Webp/lossless_vec_2_4.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_5.webp b/tests/Images/Input/Webp/lossless_vec_2_5.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_5.webp rename to tests/Images/Input/Webp/lossless_vec_2_5.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_6.webp b/tests/Images/Input/Webp/lossless_vec_2_6.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_6.webp rename to tests/Images/Input/Webp/lossless_vec_2_6.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_7.webp b/tests/Images/Input/Webp/lossless_vec_2_7.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_7.webp rename to tests/Images/Input/Webp/lossless_vec_2_7.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_8.webp b/tests/Images/Input/Webp/lossless_vec_2_8.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_8.webp rename to tests/Images/Input/Webp/lossless_vec_2_8.webp diff --git a/tests/Images/Input/WebP/lossless_vec_2_9.webp b/tests/Images/Input/Webp/lossless_vec_2_9.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_vec_2_9.webp rename to tests/Images/Input/Webp/lossless_vec_2_9.webp diff --git a/tests/Images/Input/WebP/lossless_with_iccp.webp b/tests/Images/Input/Webp/lossless_with_iccp.webp similarity index 100% rename from tests/Images/Input/WebP/lossless_with_iccp.webp rename to tests/Images/Input/Webp/lossless_with_iccp.webp diff --git a/tests/Images/Input/WebP/lossy_alpha1.webp b/tests/Images/Input/Webp/lossy_alpha1.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_alpha1.webp rename to tests/Images/Input/Webp/lossy_alpha1.webp diff --git a/tests/Images/Input/WebP/lossy_alpha2.webp b/tests/Images/Input/Webp/lossy_alpha2.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_alpha2.webp rename to tests/Images/Input/Webp/lossy_alpha2.webp diff --git a/tests/Images/Input/WebP/lossy_alpha3.webp b/tests/Images/Input/Webp/lossy_alpha3.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_alpha3.webp rename to tests/Images/Input/Webp/lossy_alpha3.webp diff --git a/tests/Images/Input/WebP/lossy_alpha4.webp b/tests/Images/Input/Webp/lossy_alpha4.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_alpha4.webp rename to tests/Images/Input/Webp/lossy_alpha4.webp diff --git a/tests/Images/Input/WebP/lossy_extreme_probabilities.webp b/tests/Images/Input/Webp/lossy_extreme_probabilities.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_extreme_probabilities.webp rename to tests/Images/Input/Webp/lossy_extreme_probabilities.webp diff --git a/tests/Images/Input/WebP/lossy_q0_f100.webp b/tests/Images/Input/Webp/lossy_q0_f100.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_q0_f100.webp rename to tests/Images/Input/Webp/lossy_q0_f100.webp diff --git a/tests/Images/Input/WebP/lossy_with_iccp.webp b/tests/Images/Input/Webp/lossy_with_iccp.webp similarity index 100% rename from tests/Images/Input/WebP/lossy_with_iccp.webp rename to tests/Images/Input/Webp/lossy_with_iccp.webp diff --git a/tests/Images/Input/WebP/near_lossless_75.webp b/tests/Images/Input/Webp/near_lossless_75.webp similarity index 100% rename from tests/Images/Input/WebP/near_lossless_75.webp rename to tests/Images/Input/Webp/near_lossless_75.webp diff --git a/tests/Images/Input/WebP/peak.png b/tests/Images/Input/Webp/peak.png similarity index 100% rename from tests/Images/Input/WebP/peak.png rename to tests/Images/Input/Webp/peak.png diff --git a/tests/Images/Input/WebP/rgb_pattern_100x100.png b/tests/Images/Input/Webp/rgb_pattern_100x100.png similarity index 100% rename from tests/Images/Input/WebP/rgb_pattern_100x100.png rename to tests/Images/Input/Webp/rgb_pattern_100x100.png diff --git a/tests/Images/Input/WebP/rgb_pattern_63x63.png b/tests/Images/Input/Webp/rgb_pattern_63x63.png similarity index 100% rename from tests/Images/Input/WebP/rgb_pattern_63x63.png rename to tests/Images/Input/Webp/rgb_pattern_63x63.png diff --git a/tests/Images/Input/WebP/rgb_pattern_80x80.png b/tests/Images/Input/Webp/rgb_pattern_80x80.png similarity index 100% rename from tests/Images/Input/WebP/rgb_pattern_80x80.png rename to tests/Images/Input/Webp/rgb_pattern_80x80.png diff --git a/tests/Images/Input/WebP/segment01.webp b/tests/Images/Input/Webp/segment01.webp similarity index 100% rename from tests/Images/Input/WebP/segment01.webp rename to tests/Images/Input/Webp/segment01.webp diff --git a/tests/Images/Input/WebP/segment02.webp b/tests/Images/Input/Webp/segment02.webp similarity index 100% rename from tests/Images/Input/WebP/segment02.webp rename to tests/Images/Input/Webp/segment02.webp diff --git a/tests/Images/Input/WebP/segment03.webp b/tests/Images/Input/Webp/segment03.webp similarity index 100% rename from tests/Images/Input/WebP/segment03.webp rename to tests/Images/Input/Webp/segment03.webp diff --git a/tests/Images/Input/WebP/small_13x1.webp b/tests/Images/Input/Webp/small_13x1.webp similarity index 100% rename from tests/Images/Input/WebP/small_13x1.webp rename to tests/Images/Input/Webp/small_13x1.webp diff --git a/tests/Images/Input/WebP/small_1x1.webp b/tests/Images/Input/Webp/small_1x1.webp similarity index 100% rename from tests/Images/Input/WebP/small_1x1.webp rename to tests/Images/Input/Webp/small_1x1.webp diff --git a/tests/Images/Input/WebP/small_1x13.webp b/tests/Images/Input/Webp/small_1x13.webp similarity index 100% rename from tests/Images/Input/WebP/small_1x13.webp rename to tests/Images/Input/Webp/small_1x13.webp diff --git a/tests/Images/Input/WebP/small_31x13.webp b/tests/Images/Input/Webp/small_31x13.webp similarity index 100% rename from tests/Images/Input/WebP/small_31x13.webp rename to tests/Images/Input/Webp/small_31x13.webp diff --git a/tests/Images/Input/WebP/sticker.webp b/tests/Images/Input/Webp/sticker.webp similarity index 100% rename from tests/Images/Input/WebP/sticker.webp rename to tests/Images/Input/Webp/sticker.webp diff --git a/tests/Images/Input/WebP/test-nostrong.webp b/tests/Images/Input/Webp/test-nostrong.webp similarity index 100% rename from tests/Images/Input/WebP/test-nostrong.webp rename to tests/Images/Input/Webp/test-nostrong.webp diff --git a/tests/Images/Input/WebP/test.webp b/tests/Images/Input/Webp/test.webp similarity index 100% rename from tests/Images/Input/WebP/test.webp rename to tests/Images/Input/Webp/test.webp diff --git a/tests/Images/Input/WebP/testpattern_opaque.png b/tests/Images/Input/Webp/testpattern_opaque.png similarity index 100% rename from tests/Images/Input/WebP/testpattern_opaque.png rename to tests/Images/Input/Webp/testpattern_opaque.png diff --git a/tests/Images/Input/WebP/testpattern_opaque_small.png b/tests/Images/Input/Webp/testpattern_opaque_small.png similarity index 100% rename from tests/Images/Input/WebP/testpattern_opaque_small.png rename to tests/Images/Input/Webp/testpattern_opaque_small.png diff --git a/tests/Images/Input/WebP/very_short.webp b/tests/Images/Input/Webp/very_short.webp similarity index 100% rename from tests/Images/Input/WebP/very_short.webp rename to tests/Images/Input/Webp/very_short.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-001.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-001.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-001.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-001.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-002.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-002.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-002.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-002.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-003.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-003.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-003.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-003.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-004.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-004.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-004.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-004.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-005.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-005.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-005.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-005.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-006.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-006.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-006.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-006.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-007.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-007.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-007.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-007.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-008.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-008.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-008.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-008.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-009.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-009.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-009.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-009.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-010.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-010.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-010.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-010.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-011.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-011.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-011.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-011.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-012.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-012.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-012.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-012.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-013.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-013.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-013.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-013.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-014.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-014.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-014.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-014.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-015.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-015.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-015.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-015.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-016.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-016.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-016.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-016.webp diff --git a/tests/Images/Input/WebP/vp80-00-comprehensive-017.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-017.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-00-comprehensive-017.webp rename to tests/Images/Input/Webp/vp80-00-comprehensive-017.webp diff --git a/tests/Images/Input/WebP/vp80-01-intra-1400.webp b/tests/Images/Input/Webp/vp80-01-intra-1400.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-01-intra-1400.webp rename to tests/Images/Input/Webp/vp80-01-intra-1400.webp diff --git a/tests/Images/Input/WebP/vp80-01-intra-1411.webp b/tests/Images/Input/Webp/vp80-01-intra-1411.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-01-intra-1411.webp rename to tests/Images/Input/Webp/vp80-01-intra-1411.webp diff --git a/tests/Images/Input/WebP/vp80-01-intra-1416.webp b/tests/Images/Input/Webp/vp80-01-intra-1416.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-01-intra-1416.webp rename to tests/Images/Input/Webp/vp80-01-intra-1416.webp diff --git a/tests/Images/Input/WebP/vp80-01-intra-1417.webp b/tests/Images/Input/Webp/vp80-01-intra-1417.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-01-intra-1417.webp rename to tests/Images/Input/Webp/vp80-01-intra-1417.webp diff --git a/tests/Images/Input/WebP/vp80-02-inter-1402.webp b/tests/Images/Input/Webp/vp80-02-inter-1402.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-02-inter-1402.webp rename to tests/Images/Input/Webp/vp80-02-inter-1402.webp diff --git a/tests/Images/Input/WebP/vp80-02-inter-1412.webp b/tests/Images/Input/Webp/vp80-02-inter-1412.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-02-inter-1412.webp rename to tests/Images/Input/Webp/vp80-02-inter-1412.webp diff --git a/tests/Images/Input/WebP/vp80-02-inter-1418.webp b/tests/Images/Input/Webp/vp80-02-inter-1418.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-02-inter-1418.webp rename to tests/Images/Input/Webp/vp80-02-inter-1418.webp diff --git a/tests/Images/Input/WebP/vp80-02-inter-1424.webp b/tests/Images/Input/Webp/vp80-02-inter-1424.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-02-inter-1424.webp rename to tests/Images/Input/Webp/vp80-02-inter-1424.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1401.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1401.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1401.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1401.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1403.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1403.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1403.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1403.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1407.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1407.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1407.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1407.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1408.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1408.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1408.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1408.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1409.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1409.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1409.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1409.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1410.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1410.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1410.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1410.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1413.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1413.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1413.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1413.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1414.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1414.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1414.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1414.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1415.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1415.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1415.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1415.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1425.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1425.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1425.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1425.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1426.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1426.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1426.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1426.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1427.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1427.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1427.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1427.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1432.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1432.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1432.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1432.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1435.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1435.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1435.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1435.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1436.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1436.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1436.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1436.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1437.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1437.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1437.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1437.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1441.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1441.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1441.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1441.webp diff --git a/tests/Images/Input/WebP/vp80-03-segmentation-1442.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1442.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-03-segmentation-1442.webp rename to tests/Images/Input/Webp/vp80-03-segmentation-1442.webp diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1404.webp b/tests/Images/Input/Webp/vp80-04-partitions-1404.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-04-partitions-1404.webp rename to tests/Images/Input/Webp/vp80-04-partitions-1404.webp diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1405.webp b/tests/Images/Input/Webp/vp80-04-partitions-1405.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-04-partitions-1405.webp rename to tests/Images/Input/Webp/vp80-04-partitions-1405.webp diff --git a/tests/Images/Input/WebP/vp80-04-partitions-1406.webp b/tests/Images/Input/Webp/vp80-04-partitions-1406.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-04-partitions-1406.webp rename to tests/Images/Input/Webp/vp80-04-partitions-1406.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1428.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1428.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1428.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1428.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1429.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1429.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1429.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1429.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1430.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1430.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1430.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1430.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1431.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1431.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1431.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1431.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1433.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1433.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1433.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1433.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1434.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1434.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1434.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1434.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1438.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1438.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1438.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1438.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1439.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1439.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1439.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1439.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1440.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1440.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1440.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1440.webp diff --git a/tests/Images/Input/WebP/vp80-05-sharpness-1443.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1443.webp similarity index 100% rename from tests/Images/Input/WebP/vp80-05-sharpness-1443.webp rename to tests/Images/Input/Webp/vp80-05-sharpness-1443.webp diff --git a/tests/Images/Input/WebP/yuv_test.png b/tests/Images/Input/Webp/yuv_test.png similarity index 100% rename from tests/Images/Input/WebP/yuv_test.png rename to tests/Images/Input/Webp/yuv_test.png From 60cde0c0481de639b319cf86a5ce358aa13d92f7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 3 Oct 2021 19:16:53 +0200 Subject: [PATCH 328/359] Fix index out of bound issue in OptimizeHuffmanForRle --- src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 2d94c9280..2964c3b7c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -93,8 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint sum = 0; for (int i = 0; i < length + 1; ++i) { - bool valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); - if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage) + if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit)) { if (stride >= 4 || (stride >= 3 && sum == 0)) { From 91f3f6a49f50d5e88ad154b768c50812aba58de8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 4 Oct 2021 14:38:21 +0200 Subject: [PATCH 329/359] Change ConvertRgbToYuv with alpha test to use a fixed image as input --- .../Formats/WebP/YuvConversionTests.cs | 12 +----------- tests/ImageSharp.Tests/TestImages.cs | 2 ++ .../Input/Png/testpattern31x31-halftransparent.png | 3 +++ tests/Images/Input/Png/testpattern31x31.png | 3 +++ 4 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 tests/Images/Input/Png/testpattern31x31-halftransparent.png create mode 100644 tests/Images/Input/Png/testpattern31x31.png diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 271a48c90..1126755a5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithTestPatternImages(31, 31, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.TestPattern31x31HalfTransparent, PixelTypes.Rgba32)] public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -146,16 +146,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - // Make half of the the image transparent. - image.Mutate(c => c.ProcessPixelRowsAsVector4(row => - { - int halfWidth = row.Length / 2; - for (int x = 0; x < halfWidth; x++) - { - row[x] = new Vector4(row[x].X, row[x].Y, row[x].Z, 0.0f); - } - })); - Span y = yBuffer.GetSpan(); Span u = uBuffer.GetSpan(); Span v = vBuffer.GetSpan(); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 75a7f63f9..90965b352 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -59,6 +59,8 @@ namespace SixLabors.ImageSharp.Tests public const string PngWithMetadata = "Png/PngWithMetaData.png"; public const string InvalidTextData = "Png/InvalidTextData.png"; public const string David = "Png/david.png"; + public const string TestPattern31x31 = "Png/testpattern31x31.png"; + public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/Input/Png/testpattern31x31-halftransparent.png b/tests/Images/Input/Png/testpattern31x31-halftransparent.png new file mode 100644 index 000000000..56b8a16b2 --- /dev/null +++ b/tests/Images/Input/Png/testpattern31x31-halftransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:415483fde21637bdf919244c0625665f4914f7678eb4e047507b9bcd2262ec50 +size 472 diff --git a/tests/Images/Input/Png/testpattern31x31.png b/tests/Images/Input/Png/testpattern31x31.png new file mode 100644 index 000000000..f7abd7959 --- /dev/null +++ b/tests/Images/Input/Png/testpattern31x31.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:233dc410d723204dfd63c296ee3ca165f4a3641475627eb82f5515f31d25afe5 +size 484 From 5675485b8788aea267e703f3f8f143f9b2eff6c6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 4 Oct 2021 15:03:48 +0200 Subject: [PATCH 330/359] Allocate clean buffers, update expected uv --- .../Formats/WebP/YuvConversionTests.cs | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 1126755a5..76657d66b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp @@ -26,9 +24,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp MemoryAllocator memoryAllocator = config.MemoryAllocator; int pixels = image.Width * image.Height; int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); Span y = yBuffer.GetSpan(); Span u = uBuffer.GetSpan(); Span v = vBuffer.GetSpan(); @@ -142,9 +140,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp MemoryAllocator memoryAllocator = config.MemoryAllocator; int pixels = image.Width * image.Height; int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height); + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); Span y = yBuffer.GetSpan(); Span u = uBuffer.GetSpan(); @@ -209,14 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 149, 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 107, 164, 117, 149, 127, 128, 166, 107, 129, 159, 159, 159, 159, 159, 159, 159, 161, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, 240, 240, 240, 137, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 109, - 150, 108, 140, 161, 80, 157, 162, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 150, 108, 140, 161, 80, 157, 162, 128 }; byte[] expectedV = { @@ -232,14 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp 81, 81, 143, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 63, 147, 133, 119, 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 135, 109, 129, 122, 124, 107, 108, 128, 138, 110, 110, 110, 110, 110, 110, 110, 117, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, - 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85 }; // act From 3c2cc0c52efcb59799d98b524f2dda6ea3b8a4f4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 8 Oct 2021 11:41:42 +0200 Subject: [PATCH 331/359] Fix warnings --- .../Formats/WebP/BitReader/BitReaderBase.cs | 1 - .../WebP/Lossless/BackwardReferenceEncoder.cs | 2 +- .../Formats/WebP/Lossless/LosslessUtils.cs | 211 +++++++++--------- .../Formats/WebP/Lossless/NearLosslessEnc.cs | 4 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 10 +- .../WebP/Lossless/WebpLosslessDecoder.cs | 3 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 3 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 1 - .../Formats/WebP/Lossy/WebpLossyDecoder.cs | 1 - .../Formats/WebP/Lossy/YuvConversion.cs | 22 +- src/ImageSharp/Formats/WebP/WebpDecoder.cs | 1 - .../Formats/WebP/WebpLookupTables.cs | 2 +- .../Formats/WebP/WebpDecoderTests.cs | 7 +- 13 files changed, 134 insertions(+), 134 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 76a2f1128..47d158123 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.BitReader diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index ea9752db8..a37082638 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -639,7 +639,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Figure out if we should use the offset/length from the previous pixel // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. - bool usePrev = bestLengthPrev > 1 && bestLengthPrev < MaxLength; + bool usePrev = bestLengthPrev is > 1 and < MaxLength; int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; bestLength = usePrev ? bestLengthPrev - 1 : 0; bestOffset = usePrev ? bestOffsetPrev : 0; diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index dc6f4843b..15bd8c5e6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -5,7 +5,6 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; - #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -67,30 +66,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { if (distance < PrefixLookupIdxMax) { - (int code, int extraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = prefixCode.extraBits; - return prefixCode.code; - } - else - { - return PrefixEncodeBitsNoLut(distance, ref extraBits); + (int Code, int ExtraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; + extraBits = prefixCode.ExtraBits; + return prefixCode.Code; } + + return PrefixEncodeBitsNoLut(distance, ref extraBits); } public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) { if (distance < PrefixLookupIdxMax) { - (int code, int extraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = prefixCode.extraBits; + (int Code, int ExtraBits) prefixCode = WebpLookupTables.PrefixEncodeCode[distance]; + extraBits = prefixCode.ExtraBits; extraBitsValue = WebpLookupTables.PrefixEncodeExtraBitsValue[distance]; - return prefixCode.code; - } - else - { - return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); + return prefixCode.Code; } + + return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); } /// @@ -402,9 +397,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless var maskalphagreen = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); var maskredblue = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); byte shufflemask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); - int idx; fixed (uint* src = data) { + int idx; for (idx = 0; idx + 4 <= numPixels; idx += 4) { uint* pos = src + idx; @@ -535,104 +530,106 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span outputSpan) { fixed (uint* inputFixed = pixelData) - fixed (uint* outputFixed = outputSpan) { - uint* input = inputFixed; - uint* output = outputFixed; - - int width = transform.XSize; - Span transformData = transform.Data.GetSpan(); - - // First Row follows the L (mode=1) mode. - PredictorAdd0(input, 1, output); - PredictorAdd1(input + 1, width - 1, output + 1); - input += width; - output += width; - - int y = 1; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; - while (y < yEnd) + fixed (uint* outputFixed = outputSpan) { - int predictorModeIdx = predictorModeIdxBase; - int x = 1; + uint* input = inputFixed; + uint* output = outputFixed; - // First pixel follows the T (mode=2) mode. - PredictorAdd2(input, output - width, 1, output); + int width = transform.XSize; + Span transformData = transform.Data.GetSpan(); - // .. the rest: - while (x < width) + // First Row follows the L (mode=1) mode. + PredictorAdd0(input, 1, output); + PredictorAdd1(input + 1, width - 1, output + 1); + input += width; + output += width; + + int y = 1; + int yEnd = transform.YSize; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int tilesPerRow = SubSampleSize(width, transform.Bits); + int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; + while (y < yEnd) { - uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; - int xEnd = (x & ~mask) + tileWidth; - if (xEnd > width) - { - xEnd = width; - } + int predictorModeIdx = predictorModeIdxBase; + int x = 1; + + // First pixel follows the T (mode=2) mode. + PredictorAdd2(input, output - width, 1, output); - // There are 14 different prediction modes. - // In each prediction mode, the current pixel value is predicted from one - // or more neighboring pixels whose values are already known. - switch (predictorMode) + // .. the rest: + while (x < width) { - case 0: - PredictorAdd0(input + x, xEnd - x, output + x); - break; - case 1: - PredictorAdd1(input + x, xEnd - x, output + x); - break; - case 2: - PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); - break; - case 3: - PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); - break; - case 4: - PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); - break; - case 5: - PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); - break; - case 6: - PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); - break; - case 7: - PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); - break; - case 8: - PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); - break; - case 9: - PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); - break; - case 10: - PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); - break; - case 11: - PredictorAdd11(input + x, output + x - width, xEnd - x, output + x); - break; - case 12: - PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); - break; - case 13: - PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); - break; + uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; + int xEnd = (x & ~mask) + tileWidth; + if (xEnd > width) + { + xEnd = width; + } + + // There are 14 different prediction modes. + // In each prediction mode, the current pixel value is predicted from one + // or more neighboring pixels whose values are already known. + switch (predictorMode) + { + case 0: + PredictorAdd0(input + x, xEnd - x, output + x); + break; + case 1: + PredictorAdd1(input + x, xEnd - x, output + x); + break; + case 2: + PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); + break; + case 3: + PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); + break; + case 4: + PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); + break; + case 5: + PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); + break; + case 6: + PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); + break; + case 7: + PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); + break; + case 8: + PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); + break; + case 9: + PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); + break; + case 10: + PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); + break; + case 11: + PredictorAdd11(input + x, output + x - width, xEnd - x, output + x); + break; + case 12: + PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); + break; + case 13: + PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); + break; + } + + x = xEnd; } - x = xEnd; - } + input += width; + output += width; + ++y; - input += width; - output += width; - ++y; - - if ((y & mask) == 0) - { - // Use the same mask, since tiles are squares. - predictorModeIdxBase += tilesPerRow; + if ((y & mask) == 0) + { + // Use the same mask, since tiles are squares. + predictorModeIdxBase += tilesPerRow; + } } } } @@ -839,10 +836,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return (float)log2; } - else - { - return (float)(Log2Reciprocal * Math.Log(v)); - } + + return (float)(Log2Reciprocal * Math.Log(v)); } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs index 46345e728..f71e4fd60 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Adjusts pixel values of image with given maximum error. private static void NearLossless(int xSize, int ySize, Span argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst) { - int x, y; + int y; int limit = 1 << limitBits; Span prevRow = copyBuffer; Span currRow = copyBuffer.Slice(xSize, xSize); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); argbDst[dstOffset] = argbSrc[srcOffset]; argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; - for (x = 1; x < xSize - 1; ++x) + for (int x = 1; x < xSize - 1; ++x) { if (IsSmooth(prevRow, currRow, nextRow, x, limit)) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 159c0c2f8..b06ca619a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -341,6 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { +#pragma warning disable SA1503 // Braces should not be omitted fixed (uint* currentRow = currentRowSpan) fixed (uint* upperRow = upperRowSpan) { @@ -454,6 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } } +#pragma warning restore SA1503 // Braces should not be omitted /// /// Quantize every component of the difference between the actual pixel value and @@ -478,7 +480,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless quantization >>= 1; } - if (value >> 24 == 0 || value >> 24 == 0xff) + if (value >> 24 is 0 or 0xff) { // Preserve transparency of fully transparent or fully opaque pixels. a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); @@ -649,6 +651,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span upperSpan, Span outputSpan) { +#pragma warning disable SA1503 // Braces should not be omitted fixed (uint* current = currentSpan) fixed (uint* upper = upperSpan) fixed (uint* outputFixed = outputSpan) @@ -727,6 +730,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } } +#pragma warning restore SA1503 // Braces should not be omitted private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span maxDiffs, bool usedSubtractGreen) { @@ -990,6 +994,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < tileHeight; ++y) { Span srcSpan = bgra.Slice(y * stride); +#pragma warning disable SA1503 // Braces should not be omitted fixed (uint* src = srcSpan) fixed (ushort* dst = values) { @@ -1016,6 +1021,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } } +#pragma warning restore SA1503 // Braces should not be omitted int leftOver = tileWidth & (span - 1); if (leftOver > 0) @@ -1063,6 +1069,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < tileHeight; ++y) { Span srcSpan = bgra.Slice(y * stride); +#pragma warning disable SA1503 // Braces should not be omitted fixed (uint* src = srcSpan) fixed (ushort* dst = values) { @@ -1092,6 +1099,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } } +#pragma warning restore SA1503 // Braces should not be omitted int leftOver = tileWidth & (span - 1); if (leftOver > 0) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index b206c9aa7..9e05c202d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -147,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. // That is why 11 bits is also considered valid here. - bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebpConstants.MaxColorCacheBits + 1; + bool colorCacheBitsIsValid = colorCacheBits is >= 1 and <= WebpConstants.MaxColorCacheBits + 1; if (!colorCacheBitsIsValid) { WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index b70c733c5..779460ac4 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -400,7 +400,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { byte[] modes = new byte[16]; int maxMode = MaxIntra4Mode; - int i4Alpha; var totalHisto = new Vp8Histogram(); int curHisto = 0; this.StartI4(); @@ -433,7 +432,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. - i4Alpha = totalHisto.GetAlpha(); + var i4Alpha = totalHisto.GetAlpha(); if (i4Alpha > bestAlpha) { this.SetIntra4Mode(modes); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 61c4f4d1d..81ab24a32 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.IO; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index 92a3a65e6..2537f714e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs index 797fbe3b6..ed03c2e71 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -140,17 +140,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R) + GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), 0); + GammaToLinear(bgra3.R), + 0); dst[dstIdx + 1] = (ushort)LinearToGamma( GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G) + GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), 0); + GammaToLinear(bgra3.G), + 0); dst[dstIdx + 2] = (ushort)LinearToGamma( GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B) + GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), 0); + GammaToLinear(bgra3.B), + 0); } if ((width & 1) != 0) @@ -178,23 +181,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Bgra32 bgra3 = nextRowSpan[j + 1]; uint a = (uint)(bgra0.A + bgra1.A + bgra2.A + bgra3.A); int r, g, b; - if (a == 4 * 0xff || a == 0) + if (a is 4 * 0xff or 0) { r = (ushort)LinearToGamma( GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R) + GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), 0); + GammaToLinear(bgra3.R), + 0); g = (ushort)LinearToGamma( GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G) + GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), 0); + GammaToLinear(bgra3.G), + 0); b = (ushort)LinearToGamma( GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B) + GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), 0); + GammaToLinear(bgra3.B), + 0); } else { @@ -215,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy bgra1 = nextRowSpan[j]; uint a = (uint)(2u * (bgra0.A + bgra1.A)); int r, g, b; - if (a == 4 * 0xff || a == 0) + if (a is 4 * 0xff or 0) { r = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); g = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); diff --git a/src/ImageSharp/Formats/WebP/WebpDecoder.cs b/src/ImageSharp/Formats/WebP/WebpDecoder.cs index 84c758c78..b4e6cecd0 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoder.cs @@ -4,7 +4,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; - using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs index 462b65aa6..54ae5661e 100644 --- a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs @@ -974,7 +974,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } }; - public static readonly (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] + public static readonly (int Code, int ExtraBits)[] PrefixEncodeCode = { (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 262e1724b..7d763c05a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -2,13 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System.IO; - using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - using Xunit; - using static SixLabors.ImageSharp.Tests.TestImages.WebP; // ReSharper disable InconsistentNaming @@ -18,9 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpDecoderTests { - private static WebpDecoder WebpDecoder => new WebpDecoder(); + private static WebpDecoder WebpDecoder => new(); - private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + private static MagickReferenceDecoder ReferenceDecoder => new(); [Theory] [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] From 88bb74f39ff9c79a72e1befb5e89e9f6674449ce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 10 Oct 2021 18:21:47 +1100 Subject: [PATCH 332/359] Fix #1712 --- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 25 ----------- .../Formats/Jpeg/JpegEncoderCore.cs | 42 +++++++++++++++++-- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 6f116f4fb..fc6e3189f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -29,7 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); - this.InitializeColorType(image); encoder.Encode(image, stream); } @@ -45,31 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); - this.InitializeColorType(image); return encoder.EncodeAsync(image, stream, cancellationToken); } - - /// - /// If ColorType was not set, set it based on the given image. - /// - private void InitializeColorType(Image image) - where TPixel : unmanaged, IPixel - { - // First inspect the image metadata. - if (this.ColorType == null) - { - JpegMetadata metadata = image.Metadata.GetJpegMetadata(); - this.ColorType = metadata.ColorType; - } - - // Secondly, inspect the pixel type. - if (this.ColorType == null) - { - bool isGrayscale = - typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || - typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); - this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCrRatio420; - } - } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 6ff887667..b39dc1315 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -86,10 +86,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); - // If the color type was not specified by the user, preserve the color type of the input image, if it's a supported color type. - if (!this.colorType.HasValue && IsSupportedColorType(jpegMetadata.ColorType)) + // If the color type was not specified by the user, preserve the color type of the input image. + if (!this.colorType.HasValue) { - this.colorType = jpegMetadata.ColorType; + this.colorType = SetFallbackColorType(image); } // Compute number of components based on color type in options. @@ -156,6 +156,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg stream.Flush(); } + /// + /// If color type was not set, set it based on the given image. + /// Note, if there is no metadata and the image has multiple components this method + /// returns defering the field assignment + /// to . + /// + private static JpegColorType? SetFallbackColorType(Image image) + where TPixel : unmanaged, IPixel + { + // First inspect the image metadata. + JpegColorType? colorType = null; + JpegMetadata metadata = image.Metadata.GetJpegMetadata(); + if (IsSupportedColorType(metadata.ColorType)) + { + colorType = metadata.ColorType; + } + + // Secondly, inspect the pixel type. + // TODO: PixelTypeInfo should contain a component count! + if (colorType is null) + { + bool isGrayscale = + typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || + typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); + + // We don't set multi-component color types here since we can set it based upon + // the quality in InitQuantizationTables. + if (isGrayscale) + { + colorType = JpegColorType.Luminance; + } + } + + return colorType; + } + /// /// Returns true, if the color type is supported by the encoder. /// From d5cea156afb7ddf1431e5203d2df80311e7b9588 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Oct 2021 22:22:41 +1100 Subject: [PATCH 333/359] Feedback fixes --- .../Formats/Jpeg/JpegEncoderCore.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index b39dc1315..d9d42e061 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // If the color type was not specified by the user, preserve the color type of the input image. if (!this.colorType.HasValue) { - this.colorType = SetFallbackColorType(image); + this.colorType = GetFallbackColorType(image); } // Compute number of components based on color type in options. @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// returns defering the field assignment /// to . /// - private static JpegColorType? SetFallbackColorType(Image image) + private static JpegColorType? GetFallbackColorType(Image image) where TPixel : unmanaged, IPixel { // First inspect the image metadata. @@ -170,23 +170,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegMetadata metadata = image.Metadata.GetJpegMetadata(); if (IsSupportedColorType(metadata.ColorType)) { - colorType = metadata.ColorType; + return metadata.ColorType; } // Secondly, inspect the pixel type. // TODO: PixelTypeInfo should contain a component count! - if (colorType is null) - { - bool isGrayscale = - typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || - typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); + bool isGrayscale = + typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || + typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); - // We don't set multi-component color types here since we can set it based upon - // the quality in InitQuantizationTables. - if (isGrayscale) - { - colorType = JpegColorType.Luminance; - } + // We don't set multi-component color types here since we can set it based upon + // the quality in InitQuantizationTables. + if (isGrayscale) + { + colorType = JpegColorType.Luminance; } return colorType; From b233771cb086182b8e6e745f07d916ae1be18575 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 14:57:15 +1100 Subject: [PATCH 334/359] Move filtering setup to the correct place. --- src/ImageSharp/Formats/Png/PngEncoderOptions.cs | 8 ++------ .../Formats/Png/PngEncoderOptionsHelpers.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 3c17c2463..0bcea037a 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -18,11 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Png { this.BitDepth = source.BitDepth; this.ColorType = source.ColorType; - - // Specification recommends default filter method None for paletted images and Paeth for others. - this.FilterMethod = source.FilterMethod ?? (source.ColorType == PngColorType.Palette - ? PngFilterMethod.None - : PngFilterMethod.Paeth); + this.FilterMethod = source.FilterMethod; this.CompressionLevel = source.CompressionLevel; this.TextCompressionThreshold = source.TextCompressionThreshold; this.Gamma = source.Gamma; @@ -41,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Png public PngColorType? ColorType { get; set; } /// - public PngFilterMethod? FilterMethod { get; } + public PngFilterMethod? FilterMethod { get; set; } /// public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index 23ca86993..1250db6fe 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -34,6 +34,18 @@ namespace SixLabors.ImageSharp.Formats.Png // a sensible default based upon the pixel format. options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType(); options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth(); + if (!options.FilterMethod.HasValue) + { + // Specification recommends default filter method None for paletted images and Paeth for others. + if (options.ColorType == PngColorType.Palette) + { + options.FilterMethod = PngFilterMethod.None; + } + else + { + options.FilterMethod = PngFilterMethod.Paeth; + } + } // Ensure bit depth and color type are a supported combination. // Bit8 is the only bit depth supported by all color types. From 743cd629c0d6cf87ae45d85aac269aac396f393b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 14:57:30 +1100 Subject: [PATCH 335/359] Fix todo's --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 38 ++++++++------------ 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 4f6fb7356..f10db7a6c 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -268,35 +268,27 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.use16Bit) { // 16 bit grayscale + alpha - // TODO: Should we consider in the future a GrayAlpha32 type. - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); + using IMemoryOwner laBuffer = this.memoryAllocator.Allocate(rowSpan.Length); + Span laSpan = laBuffer.GetSpan(); + ref La32 laRef = ref MemoryMarshal.GetReference(laSpan); + PixelOperations.Instance.ToLa32(this.configuration, rowSpan, laSpan); - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < rgbaSpan.Length; x++, o += 4) - { - Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); - ushort luminance = ColorNumerics.Get16BitBT709Luminance(rgba.R, rgba.G, rgba.B); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A); - } + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4) + { + La32 la = Unsafe.Add(ref laRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A); } } else { // 8 bit grayscale + alpha - // TODO: Should we consider in the future a GrayAlpha16 type. - Rgba32 rgba = default; - for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) - { - Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba); - Unsafe.Add(ref rawScanlineSpanRef, o) = - ColorNumerics.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A; - } + PixelOperations.Instance.ToLa16Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); } } } From fd7bcaf4d90e5a6f8f4955c0b4f9a992d513c581 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 15:10:27 +1100 Subject: [PATCH 336/359] Update ImageSharp.sln --- ImageSharp.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/ImageSharp.sln b/ImageSharp.sln index e5c6ca2a0..c188d9315 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -710,6 +710,7 @@ Global {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} {5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution From 62ac31815680693c6ff2ce3b4eaf283d6b91bbba Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 08:17:36 +0200 Subject: [PATCH 337/359] Apply change from upstream: 749a8b99 "Better estimate of the cache cost." --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index a37082638..267cedd91 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -181,17 +181,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // We should compute the contribution of the (distance, length) // histograms but those are the same independently from the cache size. // As those constant contributions are in the end added to the other - // histogram contributions, we can safely ignore them. + // histogram contributions, we can ignore them, except for the length + // prefix that is part of the literal_ histogram. int len = v.Len; uint bgraPrev = bgra[pos] ^ 0xffffffffu; - // TODO: Original has this loop? - // int extraBits = 0, extraBitsValue = 0; - // int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); - // for (int i = 0; i <= cacheBitsMax; ++i) - // { - // ++histos[i].Literal[WebPConstants.NumLiteralCodes + code]; - // } + int extraBits = 0, extraBitsValue = 0; + int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); + for (int i = 0; i <= cacheBitsMax; ++i) + { + ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; + } // Update the color caches. do From b9c992111eff21932456b730a1ff856f3bc6cfac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 09:06:36 +0200 Subject: [PATCH 338/359] Rename AlphaCompression -> UseAlphaCompression --- src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebpEncoderCore.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 2cf2cd311..a14921011 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format. /// - bool AlphaCompression { get; } + bool UseAlphaCompression { get; } /// /// Gets the number of entropy-analysis passes (in [1..10]). diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index e9b9fbbe6..fd213cba1 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public int Method { get; set; } = 4; /// - public bool AlphaCompression { get; set; } + public bool UseAlphaCompression { get; set; } /// public int EntropyPasses { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index ae7bce72f..5fe188c8d 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public WebpEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; - this.alphaCompression = options.AlphaCompression; + this.alphaCompression = options.UseAlphaCompression; this.lossy = options.Lossy; this.quality = options.Quality; this.method = options.Method; From 7ff7075e04b6cfad112dfafffc76eee0372e47e5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 09:14:06 +0200 Subject: [PATCH 339/359] Remove ChunkTypes and Animated flag from metadata --- src/ImageSharp/Formats/WebP/WebpDecoderCore.cs | 5 ++--- src/ImageSharp/Formats/WebP/WebpMetadata.cs | 18 +----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs index e9c61ee08..db55bcd95 100644 --- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets the dimensions of the image. /// - public Size Dimensions => new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Webp break; case WebpChunkType.Animation: - this.webpMetadata.Animated = true; + // TODO: Decoding animation is not implemented yet. break; case WebpChunkType.Alpha: @@ -499,7 +499,6 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.currentStream.Read(this.buffer, 0, 4) == 4) { var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); - this.webpMetadata.ChunkTypes.Enqueue(chunkType); return chunkType; } diff --git a/src/ImageSharp/Formats/WebP/WebpMetadata.cs b/src/ImageSharp/Formats/WebP/WebpMetadata.cs index 7020a386a..8144d79b5 100644 --- a/src/ImageSharp/Formats/WebP/WebpMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebpMetadata.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; - namespace SixLabors.ImageSharp.Formats.Webp { /// @@ -21,27 +19,13 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private WebpMetadata(WebpMetadata other) - { - this.Animated = other.Animated; - this.Format = other.Format; - } + private WebpMetadata(WebpMetadata other) => this.Format = other.Format; /// /// Gets or sets the webp format used. Either lossless or lossy. /// public WebpFormatType Format { get; set; } - /// - /// Gets all found chunk types ordered by appearance. - /// - public Queue ChunkTypes { get; } = new Queue(); - - /// - /// Gets or sets a value indicating whether the webp file contains an animation. - /// - public bool Animated { get; set; } - /// public IDeepCloneable DeepClone() => new WebpMetadata(this); } From 8c3f9b9cb2595ec232095fc87cc7dc781077be5b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 09:23:31 +0200 Subject: [PATCH 340/359] Add webp prefix to AlphaCompressionMethod --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 86380a070..e49e25272 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -37,13 +37,13 @@ namespace SixLabors.ImageSharp.Formats.Webp this.LastRow = 0; int totalPixels = width * height; - var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03); - if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression) + var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); + if (compression != WebpAlphaCompressionMethod.NoCompression && compression != WebpAlphaCompressionMethod.WebpLosslessCompression) { WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); } - this.Compressed = compression == AlphaCompressionMethod.WebPLosslessCompression; + this.Compressed = compression == WebpAlphaCompressionMethod.WebpLosslessCompression; // The filtering method used. Only values between 0 and 3 are valid. int filter = (alphaChunkHeader >> 2) & 0x03; @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public void Decode() { - if (this.Compressed == false) + if (!this.Compressed) { Span dataSpan = this.Data.Memory.Span; int pixelCount = this.Width * this.Height; @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { // For vertical and gradient filtering, we need to decode the part above the // cropTop row, in order to have the correct spatial predictors. - int topRow = this.AlphaFilterType == WebpAlphaFilterType.None || this.AlphaFilterType == WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; + int topRow = this.AlphaFilterType is WebpAlphaFilterType.None or WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; int firstRow = this.LastRow < topRow ? topRow : this.LastRow; if (lastRow > firstRow) { From 06014b07769e8eb78a179e0d0e9603a98718d3bc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 09:57:30 +0200 Subject: [PATCH 341/359] Make some webp classes internal --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 4 ++-- src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs | 4 ++-- src/ImageSharp/Formats/WebP/WebpChunkType.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index e49e25272..a305bda28 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - /// Gets the the width of the image. + /// Gets the width of the image. /// public int Width { get; } @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Webp Span deltas = dataSpan; Span dst = alphaSpan; - Span prev = null; + Span prev = default; for (int y = 0; y < this.Height; ++y) { switch (this.AlphaFilterType) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index f451df80b..c5b6aaec7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Five Huffman codes are used at each meta code. /// - public static class HuffIndex + internal static class HuffIndex { /// /// Green + length prefix codes + color cache codes. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index d6a069da2..bde2e52e9 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. /// Transformations can make the final compression more dense. /// - public enum Vp8LTransformType : uint + internal enum Vp8LTransformType : uint { /// /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless PredictorTransform = 0, /// - /// The goal of the color transform is to decorrelate the R, G and B values of each pixel. + /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. /// CrossColorTransform = 1, diff --git a/src/ImageSharp/Formats/WebP/WebpChunkType.cs b/src/ImageSharp/Formats/WebP/WebpChunkType.cs index 1b2a422bc..add40f302 100644 --- a/src/ImageSharp/Formats/WebP/WebpChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebpChunkType.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Contains a list of different webp chunk types. /// /// See WebP Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container - public enum WebpChunkType : uint + internal enum WebpChunkType : uint { /// /// Header signaling the use of the VP8 format. From fa8892b148a5c9fbfdf7352e193739b04e79bcd8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 10:19:55 +0200 Subject: [PATCH 342/359] Change ++i -> i++ --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 22 +++--- .../Formats/WebP/BitReader/Vp8LBitReader.cs | 4 +- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 4 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 28 ++++---- .../Formats/WebP/Lossless/CostManager.cs | 4 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 2 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 12 ++-- .../Formats/WebP/Lossless/LosslessUtils.cs | 68 +++++++++---------- .../Formats/WebP/Lossless/NearLosslessEnc.cs | 8 +-- .../Formats/WebP/Lossless/PredictorEncoder.cs | 22 +++--- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 30 ++++---- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- .../WebP/Lossless/WebpLosslessDecoder.cs | 4 +- .../Formats/WebP/Lossy/LossyUtils.cs | 46 ++++++------- src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 4 +- .../Formats/WebP/Lossy/Vp8Decoder.cs | 2 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 44 ++++++------ .../Formats/WebP/Lossy/Vp8EncProba.cs | 2 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 26 +++---- .../Formats/WebP/Lossy/Vp8Encoding.cs | 38 +++++------ .../Formats/WebP/Lossy/Vp8Histogram.cs | 6 +- .../Formats/WebP/Lossy/Vp8Matrix.cs | 6 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 2 +- .../Formats/WebP/Lossy/WebpLossyDecoder.cs | 32 ++++----- .../Formats/WebP/WebpLookupTables.cs | 8 +-- 26 files changed, 214 insertions(+), 214 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index a305bda28..ba27d9999 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Webp Span deltas = dataSpan; Span dst = alphaSpan; Span prev = default; - for (int y = 0; y < this.Height; ++y) + for (int y = 0; y < this.Height; y++) { switch (this.AlphaFilterType) { @@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Webp Span alphaSpan = this.Alpha.Memory.Span; Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); - for (int y = firstRow; y < lastRow; ++y) + for (int y = firstRow; y < lastRow; y++) { switch (this.AlphaFilterType) { @@ -282,10 +282,10 @@ namespace SixLabors.ImageSharp.Formats.Webp int pixelsPerByte = 1 << transform.Bits; int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; - for (int y = yStart; y < yEnd; ++y) + for (int y = yStart; y < yEnd; y++) { int packedPixels = 0; - for (int x = 0; x < width; ++x) + for (int x = 0; x < width; x++) { if ((x & countMask) == 0) { @@ -309,7 +309,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { byte pred = (byte)(prev == null ? 0 : prev[0]); - for (int i = 0; i < width; ++i) + for (int i = 0; i < width; i++) { byte val = (byte)(pred + input[i]); pred = val; @@ -325,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - for (int i = 0; i < width; ++i) + for (int i = 0; i < width; i++) { dst[i] = (byte)(prev[i] + input[i]); } @@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Webp byte prev0 = prev[0]; byte topLeft = prev0; byte left = prev0; - for (int i = 0; i < width; ++i) + for (int i = 0; i < width; i++) { byte top = prev[i]; left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); @@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Formats.Webp return false; } - for (int i = 0; i < hdr.NumHTreeGroups; ++i) + for (int i = 0; i < hdr.NumHTreeGroups; i++) { List htrees = hdr.HTreeGroups[i].HTrees; if (htrees[HuffIndex.Red][0].BitsUsed > 0) @@ -391,9 +391,9 @@ namespace SixLabors.ImageSharp.Formats.Webp private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) { int offset = 0; - for (int y = yStart; y < yEnd; ++y) + for (int y = yStart; y < yEnd; y++) { - for (int x = 0; x < width; ++x) + for (int x = 0; x < width; x++) { dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); offset++; @@ -414,7 +414,7 @@ namespace SixLabors.ImageSharp.Formats.Webp [MethodImpl(InliningOptions.ShortMethod)] private static void ExtractGreen(Span argb, Span alpha, int size) { - for (int i = 0; i < size; ++i) + for (int i = 0; i < size; i++) { alpha[i] = (byte)(argb[i] >> 8); } diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index fa59d82d9..601336fa4 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader ulong currentValue = 0; System.Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 8; i++) { currentValue |= (ulong)dataSpan[i] << (8 * i); } @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader ulong currentValue = 0; System.Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < length; ++i) + for (int i = 0; i < length; i++) { currentValue |= (ulong)dataSpan[i] << (8 * i); } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 959fbc284..76766f67e 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -597,10 +597,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter else { Span topPred = it.Preds.AsSpan(predIdx - predsWidth); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { int left = it.Preds[predIdx - 1]; - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { byte[] probas = WebpLookupTables.ModesProba[topPred[x], left]; left = bitWriter.PutI4Mode(it.Preds[predIdx + x], probas); diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 267cedd91..70c4efb99 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int extraBits = 0, extraBitsValue = 0; int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); - for (int i = 0; i <= cacheBitsMax; ++i) + for (int i = 0; i <= cacheBitsMax; i++) { ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; } @@ -214,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - for (int i = 0; i <= cacheBitsMax; ++i) + for (int i = 0; i <= cacheBitsMax; i++) { double entropy = histos[i].EstimateBits(); if (i == 0 || entropy < entropyMin) @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Add first pixel as literal. AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); - for (int i = 1; i < pixCount; ++i) + for (int i = 1; i < pixCount; i++) { float prevCost = costManager.Costs[i - 1]; int offset = hashChain.FindOffset(i); @@ -315,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int lenJ = 0; int j; - for (j = i; j <= reach; ++j) + for (j = i; j <= reach; j++) { int offsetJ = hashChain.FindOffset(j + 1); lenJ = hashChain.FindLength(j + 1); @@ -484,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // [i,i+len) + [i+len, length of best match at i+len) // while we check if we can use: // [i,j) (where j<=i+len) + [j, length of best match at j) - for (j = iLastCheck + 1; j <= jMax; ++j) + for (j = iLastCheck + 1; j <= jMax; j++) { int lenJ = hashChain.FindLength(j); int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. @@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); if (useColorCache) { - for (j = i; j < i + len; ++j) + for (j = i; j < i + len; j++) { colorCache.Insert(bgra[j]); } @@ -563,9 +563,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Figure out the window offsets around a pixel. They are stored in a // spiraling order around the pixel as defined by DistanceToPlaneCode. - for (int y = 0; y <= 6; ++y) + for (int y = 0; y <= 6; y++) { - for (int x = -6; x <= 6; ++x) + for (int x = -6; x <= 6; x++) { int offset = (y * xSize) + x; @@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // For narrow images, not all plane codes are reached, so remove those. - for (i = 0; i < WindowOffsetsSizeMax; ++i) + for (i = 0; i < WindowOffsetsSizeMax; i++) { if (windowOffsets[i] == 0) { @@ -598,10 +598,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Given a pixel P, find the offsets that reach pixels unreachable from P-1 // with any of the offsets in windowOffsets[]. - for (i = 0; i < windowOffsetsSize; ++i) + for (i = 0; i < windowOffsetsSize; i++) { bool isReachable = false; - for (int j = 0; j < windowOffsetsSize && !isReachable; ++j) + for (int j = 0; j < windowOffsetsSize && !isReachable; j++) { isReachable |= windowOffsets[i] == windowOffsets[j] + 1; } @@ -614,7 +614,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } hashChain.OffsetLength[0] = 0; - for (i = 1; i < pixelCount; ++i) + for (i = 1; i < pixelCount; i++) { int ind; int bestLength = hashChainBest.FindLength(i); @@ -625,7 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Do not recompute the best match if we already have a maximal one in the window. bestOffset = hashChainBest.FindOffset(i); - for (ind = 0; ind < windowOffsetsSize; ++ind) + for (ind = 0; ind < windowOffsetsSize; ind++) { if (bestOffset == windowOffsets[ind]) { @@ -645,7 +645,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bestOffset = usePrev ? bestOffsetPrev : 0; // Find the longest match in a window around the pixel. - for (ind = 0; ind < numInd; ++ind) + for (ind = 0; ind < numInd; ind++) { int currLength = 0; int j = i; diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index e93c1d2de..94c7bd847 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (len < skipDistance) { - for (int j = position; j < position + len; ++j) + for (int j = position; j < position + len; j++) { int k = j - position; float costTmp = (float)(distanceCost + this.CostCache[k]); @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } CostInterval interval = this.head; - for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; ++i) + for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) { // Define the intersection of the ith interval with the new one. int start = position + this.CacheIntervals[i].Start; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 6021579bb..f2d4fb189 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, ushort[] histogramSymbols) { - for (int clusterId = 0, i = 0; i < origHistograms.Count; ++i) + for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) { Vp8LHistogram origHistogram = origHistograms[i]; origHistogram.UpdateHistogramCost(); diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 2964c3b7c..f2321d681 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Mark any seq of non-0's that is longer as 7 as a goodForRle. uint symbol = counts[0]; int stride = 0; - for (int i = 0; i < length + 1; ++i) + for (int i = 0; i < length + 1; i++) { if (i == length || counts[i] != symbol) { if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) { - for (int k = 0; k < stride; ++k) + for (int k = 0; k < stride; k++) { goodForRle[i - k - 1] = true; } @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless stride = 0; uint limit = counts[0]; uint sum = 0; - for (int i = 0; i < length + 1; ++i) + for (int i = 0; i < length + 1; i++) { if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit)) { @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless count = 0; } - for (k = 0; k < stride; ++k) + for (k = 0; k < stride; k++) { // We don't want to change value at counts[i], // that is already belonging to the next stride. Thus - 1. @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint countMin; int treeSizeOrig = 0; - for (int i = 0; i < histogramSize; ++i) + for (int i = 0; i < histogramSize; i++) { if (histogram[i] != 0) { @@ -505,7 +505,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (repetitions < 3) { int i; - for (i = 0; i < repetitions; ++i) + for (i = 0; i < repetitions; i++) { tokens[pos].Code = (byte)value; tokens[pos].ExtraBits = 0; diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 15bd8c5e6..43e87061c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -296,10 +296,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint[] decodedPixelData = new uint[width * height]; int pixelDataPos = 0; - for (int y = 0; y < height; ++y) + for (int y = 0; y < height; y++) { uint packedPixels = 0; - for (int x = 0; x < width; ++x) + for (int x = 0; x < width; x++) { // We need to load fresh 'packed_pixels' once every // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte @@ -319,9 +319,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - for (int y = 0; y < height; ++y) + for (int y = 0; y < height; y++) { - for (int x = 0; x < width; ++x) + for (int x = 0; x < width; x++) { uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless pixelPos += remainingWidth; } - ++y; + y++; if ((y & mask) == 0) { predRowIdxStart += tilesPerRow; @@ -623,7 +623,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless input += width; output += width; - ++y; + y++; if ((y & mask) == 0) { @@ -867,7 +867,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd0(uint* input, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { output[x] = AddPixels(input[x], WebpConstants.ArgbBlack); } @@ -877,7 +877,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void PredictorAdd1(uint* input, int numberOfPixels, uint* output) { uint left = output[-1]; - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { output[x] = left = AddPixels(input[x], left); } @@ -886,7 +886,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd2(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor2(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -896,7 +896,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd3(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor3(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -906,7 +906,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd4(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor4(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -916,7 +916,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd5(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor5(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -926,7 +926,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd6(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor6(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -936,7 +936,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd7(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor7(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -946,7 +946,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd8(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor8(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -956,7 +956,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd9(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor9(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -966,7 +966,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd10(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor10(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -976,7 +976,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd11(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor11(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -986,7 +986,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd12(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor12(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -996,7 +996,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static void PredictorAdd13(uint* input, uint* upper, int numberOfPixels, uint* output) { - for (int x = 0; x < numberOfPixels; ++x) + for (int x = 0; x < numberOfPixels; x++) { uint pred = Predictor13(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); @@ -1042,7 +1042,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub0(uint* input, int numPixels, uint* output) { - for (int i = 0; i < numPixels; ++i) + for (int i = 0; i < numPixels; i++) { output[i] = SubPixels(input[i], WebpConstants.ArgbBlack); } @@ -1051,7 +1051,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub1(uint* input, int numPixels, uint* output) { - for (int i = 0; i < numPixels; ++i) + for (int i = 0; i < numPixels; i++) { output[i] = SubPixels(input[i], input[i - 1]); } @@ -1060,7 +1060,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub2(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor2(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1070,7 +1070,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub3(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor3(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1080,7 +1080,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub4(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor4(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1090,7 +1090,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub5(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor5(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1100,7 +1100,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub6(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor6(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1110,7 +1110,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub7(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor7(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1120,7 +1120,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub8(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor8(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1130,7 +1130,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub9(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor9(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1140,7 +1140,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub10(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor10(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1150,7 +1150,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub11(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor11(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1160,7 +1160,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub12(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor12(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); @@ -1170,7 +1170,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] public static void PredictorSub13(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; ++x) + for (int x = 0; x < numPixels; x++) { uint pred = Predictor13(input[x - 1], upper + x); output[x] = SubPixels(input[x], pred); diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs index f71e4fd60..7a26a1073 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // For small icon images, don't attempt to apply near-lossless compression. if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) { - for (int i = 0; i < ySize; ++i) + for (int i = 0; i < ySize; i++) { argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); } @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); - for (int i = limitBits - 1; i != 0; --i) + for (int i = limitBits - 1; i != 0; i--) { NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); } @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int srcOffset = 0; int dstOffset = 0; - for (y = 0; y < ySize; ++y) + for (y = 0; y < ySize; y++) { if (y == 0 || y == ySize - 1) { @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); argbDst[dstOffset] = argbSrc[srcOffset]; argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; - for (int x = 1; x < xSize - 1; ++x) + for (int x = 1; x < xSize - 1; x++) { if (IsSmooth(prevRow, currRow, nextRow, x, limit)) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index b06ca619a..0858a3d3f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -60,16 +60,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (lowEffort) { - for (int i = 0; i < tilesPerRow * tilesPerCol; ++i) + for (int i = 0; i < tilesPerRow * tilesPerCol; i++) { image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); } } else { - for (int tileY = 0; tileY < tilesPerCol; ++tileY) + for (int tileY = 0; tileY < tilesPerCol; tileY++) { - for (int tileX = 0; tileX < tilesPerRow; ++tileX) + for (int tileX = 0; tileX < tilesPerRow; tileX++) { int pred = GetBestPredictorForTile( width, @@ -345,7 +345,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless fixed (uint* currentRow = currentRowSpan) fixed (uint* upperRow = upperRowSpan) { - for (int x = xStart; x < xEnd; ++x) + for (int x = xStart; x < xEnd; x++) { uint predict = 0; uint residual; @@ -583,7 +583,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span currentMaxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); Span lowerMaxDiffs = currentMaxDiffs.Slice(width); - for (int y = 0; y < height; ++y) + for (int y = 0; y < height; y++) { Span tmp32 = upperRow; upperRow = currentRow; @@ -747,7 +747,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless right = AddGreenToBlueAndRed(right); } - for (int x = 1; x < width - 1; ++x) + for (int x = 1; x < width - 1; x++) { uint up = argb[offset - stride + x]; uint down = argb[offset + stride + x]; @@ -991,7 +991,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless const int span = 8; Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; ++y) + for (int y = 0; y < tileHeight; y++) { Span srcSpan = bgra.Slice(y * stride); #pragma warning disable SA1503 // Braces should not be omitted @@ -1014,7 +1014,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' Vector128 d = Sse2.And(c, mask.AsByte()); // 0 r' Sse2.Store(dst, d.AsUInt16()); - for (int i = 0; i < span; ++i) + for (int i = 0; i < span; i++) { ++histo[values[i]]; } @@ -1066,7 +1066,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless var shufflerLow = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); var shufflerHigh = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); - for (int y = 0; y < tileHeight; ++y) + for (int y = 0; y < tileHeight; y++) { Span srcSpan = bgra.Slice(y * stride); #pragma warning disable SA1503 // Braces should not be omitted @@ -1092,7 +1092,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 d = Sse2.Subtract(c, a.AsByte()); Vector128 e = Sse2.And(d, maskblue); Sse2.Store(dst, e.AsUInt16()); - for (int i = 0; i < span; ++i) + for (int i = 0; i < span; i++) { ++histo[values[i]]; } @@ -1132,7 +1132,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) { double retVal = 0.0d; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { double kExpValue = 0.94; retVal += PredictionCostSpatial(tile[i], 1, kExpValue); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index e3d7fd28a..bfe4e384e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.Init(); - for (i = 1; i < length; ++i) + for (i = 1; i < length; i++) { uint xi = x[i]; if (xi != xPrev) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index b5277d3be..a57c1c982 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; - for (int i = 0; i < this.Refs.Length; ++i) + for (int i = 0; i < this.Refs.Length; i++) { this.Refs[i] = new Vp8LBackwardRefs { @@ -458,7 +458,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Fill in the different LZ77s. foreach (CrunchConfig crunchConfig in crunchConfigs) { - for (int j = 0; j < nlz77s; ++j) + for (int j = 0; j < nlz77s; j++) { crunchConfig.SubConfigs.Add(new CrunchSubConfig { @@ -559,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); Span histogramBgra = histogramBgraBuffer.GetSpan(); int maxIndex = 0; - for (int i = 0; i < histogramImageXySize; ++i) + for (int i = 0; i < histogramImageXySize; i++) { int symbolIndex = histogramSymbols[i] & 0xffff; histogramBgra[i] = (uint)(symbolIndex << 8); @@ -584,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Store Huffman codes. // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5 * histogramImage.Count; ++i) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -599,7 +599,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless tokens[i] = new HuffmanTreeToken(); } - for (int i = 0; i < 5 * histogramImage.Count; ++i) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -743,7 +743,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5; ++i) + for (int i = 0; i < 5; i++) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -753,13 +753,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } var tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; ++i) + for (int i = 0; i < tokens.Length; i++) { tokens[i] = new HuffmanTreeToken(); } // Store Huffman codes. - for (int i = 0; i < 5; ++i) + for (int i = 0; i < 5; i++) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -778,7 +778,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int maxSymbol = 1 << maxBits; // Check whether it's a small tree. - for (int i = 0; i < huffmanCode.NumSymbols && count < 3; ++i) + for (int i = 0; i < huffmanCode.NumSymbols && count < 3; i++) { if (huffmanCode.CodeLengths[i] != 0) { @@ -1085,7 +1085,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless histo[(int)HistoIx.HistoBluePred * 256]++; histo[(int)HistoIx.HistoAlphaPred * 256]++; - for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) + for (int j = 0; j < (int)HistoIx.HistoTotal; j++) { var bitEntropy = new Vp8LBitEntropy(); Span curHisto = histo.Slice(j * 256, 256); @@ -1486,7 +1486,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { uint predict = 0x000000; byte signFound = 0x00; - for (int i = 0; i < numColors; ++i) + for (int i = 0; i < numColors; i++) { uint diff = LosslessUtils.SubPixels(palette[i], predict); byte rd = (byte)((diff >> 16) & 0xff); @@ -1520,11 +1520,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void GreedyMinimizeDeltas(Span palette, int numColors) { uint predict = 0x00000000; - for (int i = 0; i < numColors; ++i) + for (int i = 0; i < numColors; i++) { int bestIdx = i; uint bestScore = ~0U; - for (int k = i; k < numColors; ++k) + for (int k = i; k < numColors; k++) { uint curScore = PaletteColorDistance(palette[k], predict); if (bestScore > curScore) @@ -1645,7 +1645,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int bitDepth = 1 << (3 - xBits); int mask = (1 << xBits) - 1; uint code = 0xff000000; - for (x = 0; x < width; ++x) + for (x = 0; x < width; x++) { int xSub = x & mask; if (xSub == 0) @@ -1659,7 +1659,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - for (x = 0; x < width; ++x) + for (x = 0; x < width; x++) { dst[x] = (uint)(0xff000000 | (row[x] << 8)); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 20997c3db..42260e2b2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -496,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static double ExtraCost(Span population, int length) { double cost = 0.0d; - for (int i = 2; i < length - 2; ++i) + for (int i = 2; i < length - 2; i++) { cost += (i >> 1) * population[i + 2]; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs index 9e05c202d..938278517 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; // TODO: Isn't huffmanPixels the length of the span? - for (int i = 0; i < huffmanPixels; ++i) + for (int i = 0; i < huffmanPixels; i++) { // The huffman data is stored in red and green bytes. uint group = (huffmanImageSpan[i] >> 8) & 0xffff; @@ -983,7 +983,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span dst = data.Slice(pos); Span src = data.Slice(pos - dist); - for (int i = 0; i < length; ++i) + for (int i = 0; i < length; i++) { dst[i] = src[i]; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index aebd22577..1584237b0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -25,9 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int count = 0; int aOffset = 0; int bOffset = 0; - for (int y = 0; y < h; ++y) + for (int y = 0; y < h; y++) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < w; x++) { int diff = a[aOffset + x] - b[bOffset + x]; count += diff * diff; @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void Copy(Span src, Span dst, int w, int h) { int offset = 0; - for (int y = 0; y < h; ++y) + for (int y = 0; y < h; y++) { src.Slice(offset, w).CopyTo(dst.Slice(offset, w)); offset += WebpConstants.Bps; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int offsetMinus1 = offset - 1; int offsetMinusBps = offset - WebpConstants.Bps; int dc = 16; - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 16; j++) { // DC += dst[-1 + j * BPS] + dst[j - BPS]; dc += yuv[offsetMinus1 + (j * WebpConstants.Bps)] + yuv[offsetMinusBps + j]; @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // vertical Span src = yuv.Slice(offset - WebpConstants.Bps, 16); - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 16; j++) { // memcpy(dst + j * BPS, dst - BPS, 16); src.CopyTo(dst.Slice(j * WebpConstants.Bps)); @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // horizontal offset--; - for (int j = 16; j > 0; --j) + for (int j = 16; j > 0; j--) { // memset(dst, dst[-1], 16); byte v = yuv[offset]; @@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // DC with top samples not available. int dc = 8; - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 16; j++) { // DC += dst[-1 + j * BPS]; dc += yuv[-1 + (j * WebpConstants.Bps) + offset]; @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // DC with left samples not available. int dc = 8; - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 16; i++) { // DC += dst[i - BPS]; dc += yuv[i - WebpConstants.Bps + offset]; @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int dc0 = 8; int offsetMinus1 = offset - 1; int offsetMinusBps = offset - WebpConstants.Bps; - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 8; i++) { // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebpConstants.Bps)]; @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // horizontal offset--; - for (int j = 0; j < 8; ++j) + for (int j = 0; j < 8; j++) { // memset(dst, dst[-1], 8); // dst += BPS; @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // DC with no left samples. int offsetMinusBps = offset - WebpConstants.Bps; int dc0 = 4; - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 8; i++) { // dc0 += dst[i - BPS]; dc0 += yuv[offsetMinusBps + i]; @@ -236,7 +236,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int dc = 4; int offsetMinusBps = offset - WebpConstants.Bps; int offsetMinusOne = offset - 1; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebpConstants.Bps)]; } @@ -507,7 +507,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void TransformWht(Span input, Span output) { int[] tmp = new int[16]; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { int iPlus4 = 4 + i; int iPlus8 = 8 + i; @@ -523,7 +523,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } int outputOffset = 0; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { int imul4 = i * 4; int dc = tmp[0 + imul4] + 3; @@ -551,7 +551,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // horizontal pass. int inputOffset = 0; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { int inputOffsetPlusOne = inputOffset + 1; int inputOffsetPlusTwo = inputOffset + 2; @@ -569,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // vertical pass - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { int a0 = tmp[0 + i] + tmp[8 + i]; int a1 = tmp[4 + i] + tmp[12 + i]; @@ -624,7 +624,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. tmpOffset = 0; int dstOffset = 0; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { // horizontal pass int tmpOffsetPlus4 = tmpOffset + 4; @@ -648,9 +648,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void TransformDc(Span src, Span dst) { int dc = src[0] + 4; - for (int j = 0; j < 4; ++j) + for (int j = 0; j < 4; j++) { - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { Store(dst, i, j, dc); } @@ -705,7 +705,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int thresh2 = (2 * thresh) + 1; int end = 16 + offset; - for (int i = offset; i < end; ++i) + for (int i = offset; i < end; i++) { if (NeedsFilter(p, i, stride, thresh2)) { @@ -841,7 +841,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static void Put16(int v, Span dst) { - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 16; j++) { Memset(dst.Slice(j * WebpConstants.Bps), (byte)v, 0, 16); } @@ -855,9 +855,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte p = yuv[topOffset - 1]; int leftOffset = offset - 1; byte left = yuv[leftOffset]; - for (int y = 0; y < size; ++y) + for (int y = 0; y < size; y++) { - for (int x = 0; x < size; ++x) + for (int x = 0; x < size; x++) { dst[x] = (byte)Clamp255(left + top[x] - p); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs index 8abeab6bf..2ed438166 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -592,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { uint v = src[0] * 0x01010101u; Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 16; i++) { if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) @@ -612,7 +612,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int score = 0; while (numBlocks-- > 0) { - for (int i = 1; i < 16; ++i) + for (int i = 1; i < 16; i++) { // omit DC, we're only interested in AC score += levels[i] != 0 ? 1 : 0; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 499b5c66d..d62d23e17 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy baseLevel = hdr.FilterLevel; } - for (int i4x4 = 0; i4x4 <= 1; ++i4x4) + for (int i4x4 = 0; i4x4 <= 1; i4x4++) { Vp8FilterInfo info = this.FilterStrength[s, i4x4]; int level = baseLevel; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 779460ac4..dab31c7a2 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -238,14 +238,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.I4BoundaryIdx = this.vp8TopLeftI4[0]; // Import the boundary samples. - for (i = 0; i < 17; ++i) + for (i = 0; i < 17; i++) { // left this.I4Boundary[i] = this.YLeft[15 - i + 1]; } Span yTop = this.YTop.AsSpan(this.yTopIdx); - for (i = 0; i < 16; ++i) + for (i = 0; i < 16; i++) { // top this.I4Boundary[17 + i] = yTop[i]; @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // top-right samples have a special case on the far right of the picture. if (this.X < this.mbw - 1) { - for (i = 16; i < 16 + 4; ++i) + for (i = 16; i < 16 + 4; i++) { this.I4Boundary[17 + i] = yTop[i]; } @@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else { // else, replicate the last valid pixel four times - for (i = 16; i < 16 + 4; ++i) + for (i = 16; i < 16 + 4; i++) { this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; } @@ -476,7 +476,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public void SetIntra16Mode(int mode) { Span preds = this.Preds.AsSpan(this.predIdx); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { preds.Slice(0, 4).Fill((byte)mode); preds = preds.Slice(this.predsWidth); @@ -489,7 +489,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int modesIdx = 0; int predIdx = this.predIdx; - for (int y = 4; y > 0; --y) + for (int y = 4; y > 0; y--) { modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); predIdx += this.predsWidth; @@ -514,9 +514,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // AC res.Init(1, 0, proba); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { int ctx = this.TopNz[x] + this.LeftNz[y]; res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); @@ -564,9 +564,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy res.Init(0, 2, proba); for (int ch = 0; ch <= 2; ch += 2) { - for (int y = 0; y < 2; ++y) + for (int y = 0; y < 2; y++) { - for (int x = 0; x < 2; ++x) + for (int x = 0; x < 2; x++) { int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); @@ -643,12 +643,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (x < this.mbw - 1) { // left - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 16; i++) { this.YLeft[i + 1] = ySrc[15 + (i * WebpConstants.Bps)]; } - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 8; i++) { this.UvLeft[i + 1] = uvSrc[7 + (i * WebpConstants.Bps)]; this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebpConstants.Bps)]; @@ -676,7 +676,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int i; // Update the cache with 7 fresh samples. - for (i = 0; i <= 3; ++i) + for (i = 0; i <= 3; i++) { top[topOffset - 4 + i] = blk[i + (3 * WebpConstants.Bps)]; // Store future top samples. } @@ -684,7 +684,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((this.I4 & 3) != 3) { // if not on the right sub-blocks #3, #7, #11, #15 - for (i = 0; i <= 2; ++i) + for (i = 0; i <= 2; i++) { // store future left samples top[topOffset + i] = blk[3 + ((2 - i) * WebpConstants.Bps)]; @@ -693,7 +693,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else { // else replicate top-right samples, as says the specs. - for (i = 0; i <= 3; ++i) + for (i = 0; i <= 3; i++) { top[topOffset + i] = top[topOffset + i + 4]; } @@ -816,12 +816,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void Mean16x4(Span input, Span dc) { - for (int k = 0; k < 4; ++k) + for (int k = 0; k < 4; k++) { uint avg = 0; - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { avg += input[x + (y * WebpConstants.Bps)]; } @@ -836,7 +836,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int dstIdx = 0; int srcIdx = 0; - for (int i = 0; i < h; ++i) + for (int i = 0; i < h; i++) { // memcpy(dst, src, w); src.Slice(srcIdx, w).CopyTo(dst.Slice(dstIdx)); @@ -850,7 +850,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy srcIdx += srcStride; } - for (int i = h; i < size; ++i) + for (int i = h; i < size; i++) { // memcpy(dst, dst - BPS, size); dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst.Slice(dstIdx)); @@ -862,13 +862,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int i; int srcIdx = 0; - for (i = 0; i < len; ++i) + for (i = 0; i < len; i++) { dst[i] = src[srcIdx]; srcIdx += srcStride; } - for (; i < totalLen; ++i) + for (; i < totalLen; i++) { dst[i] = dst[len - 1]; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index 4207e5de9..e12839b3d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int pattern = WebpLookupTables.Vp8LevelCodes[level - 1][0]; int bits = WebpLookupTables.Vp8LevelCodes[level - 1][1]; int cost = 0; - for (int i = 2; pattern != 0; ++i) + for (int i = 2; pattern != 0; i++) { if ((pattern & 1) != 0) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 81ab24a32..f548ddb7a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -515,12 +515,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ Span left = this.Preds.AsSpan(this.PredsWidth - 1); - for (int i = 0; i < 4 * this.Mbw; ++i) + for (int i = 0; i < 4 * this.Mbw; i++) { top[i] = (int)IntraPredictionMode.DcPrediction; } - for (int i = 0; i < 4 * this.Mbh; ++i) + for (int i = 0; i < 4 * this.Mbh; i++) { left[i * this.PredsWidth] = (int)IntraPredictionMode.DcPrediction; } @@ -671,7 +671,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8SegmentInfo[] dqm = this.SegmentInfos; double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d; double cBase = QualityToCompression(quality / 100.0d); - for (int i = 0; i < nb; ++i) + for (int i = 0; i < nb; i++) { // We modulate the base coefficient to accommodate for the quantization // susceptibility and allow denser segments to be quantized more. @@ -717,7 +717,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. int level0 = 5 * this.filterStrength; - for (int i = 0; i < WebpConstants.NumMbSegments; ++i) + for (int i = 0; i < WebpConstants.NumMbSegments; i++) { Vp8SegmentInfo m = this.SegmentInfos[i]; @@ -791,7 +791,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetupMatrices(Vp8SegmentInfo[] dqm) { int tlambdaScale = (this.method >= 4) ? this.spatialNoiseShaping : 0; - for (int i = 0; i < dqm.Length; ++i) + for (int i = 0; i < dqm.Length; i++) { Vp8SegmentInfo m = dqm[i]; int q = m.Quant; @@ -945,9 +945,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // luma-AC - for (y = 0; y < 4; ++y) + for (y = 0; y < 4; y++) { - for (x = 0; x < 4; ++x) + for (x = 0; x < 4; x++) { int ctx = it.TopNz[x] + it.LeftNz[y]; Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); @@ -963,9 +963,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy residual.Init(0, 2, this.Proba); for (ch = 0; ch <= 2; ch += 2) { - for (y = 0; y < 2; ++y) + for (y = 0; y < 2; y++) { - for (x = 0; x < 2; ++x) + for (x = 0; x < 2; x++) { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); @@ -1011,9 +1011,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // luma-AC - for (y = 0; y < 4; ++y) + for (y = 0; y < 4; y++) { - for (x = 0; x < 4; ++x) + for (x = 0; x < 4; x++) { int ctx = it.TopNz[x] + it.LeftNz[y]; Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); @@ -1028,9 +1028,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy residual.Init(0, 2, this.Proba); for (ch = 0; ch <= 2; ch += 2) { - for (y = 0; y < 2; ++y) + for (y = 0; y < 2; y++) { - for (x = 0; x < 2; ++x) + for (x = 0; x < 2; x++) { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs index fe7edd011..f8b4853e2 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy static Vp8Encoding() { - for (int i = -255; i <= 255 + 255; ++i) + for (int i = -255; i <= 255 + 255; i++) { Clip1[255 + i] = Clip8b(i); } @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int[] C = new int[4 * 4]; #pragma warning restore SA1312 // Variable names should begin with lower-case letter Span tmp = C.AsSpan(); - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { // vertical pass. int a = input[0] + input[8]; @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } tmp = C.AsSpan(); - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { // horizontal pass. int dc = tmp[0] + 4; @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int[] tmp = new int[16]; int srcIdx = 0; int refIdx = 0; - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) int d1 = src[srcIdx + 1] - reference[refIdx + 1]; @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy refIdx += WebpConstants.Bps; } - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int a0 = tmp[0 + i] + tmp[12 + i]; // 15b int a1 = tmp[4 + i] + tmp[8 + i]; @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int[] tmp = new int[16]; int i; int inputIdx = 0; - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy inputIdx += 64; } - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int a0 = tmp[0 + i] + tmp[8 + i]; // 15b int a1 = tmp[4 + i] + tmp[12 + i]; @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { if (top != null) { - for (int j = 0; j < size; ++j) + for (int j = 0; j < size; j++) { top.Slice(0, size).CopyTo(dst.Slice(j * WebpConstants.Bps)); } @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (left != null) { left = left.Slice(1); // in the reference implementation, left starts at - 1. - for (int j = 0; j < size; ++j) + for (int j = 0; j < size; j++) { dst.Slice(j * WebpConstants.Bps, size).Fill(left[j]); } @@ -286,10 +286,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (top != null) { Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 - for (int y = 0; y < size; ++y) + for (int y = 0; y < size; y++) { Span clipTable = clip.Slice(left[y + 1]); // left[y] - for (int x = 0; x < size; ++x) + for (int x = 0; x < size; x++) { dst[x] = clipTable[top[x]]; } @@ -325,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int j; if (top != null) { - for (j = 0; j < size; ++j) + for (j = 0; j < size; j++) { dc += top[j]; } @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // top and left present. left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) + for (j = 0; j < size; j++) { dc += left[j]; } @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { // left but no top. left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) + for (j = 0; j < size; j++) { dc += left[j]; } @@ -372,7 +372,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { uint dc = 4; int i; - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); } @@ -383,10 +383,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static void Tm4(Span dst, Span top, int topOffset) { Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { Span clipTable = clip.Slice(top[topOffset - 2 - y]); - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { dst[x] = clipTable[top[topOffset + x]]; } @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) }; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 4; i++) { vals.AsSpan().CopyTo(dst.Slice(i * WebpConstants.Bps)); } @@ -637,7 +637,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static void Fill(Span dst, int value, int size) { - for (int j = 0; j < size; ++j) + for (int j = 0; j < size; j++) { dst.Slice(j * WebpConstants.Bps, size).Fill((byte)value); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs index f4aacf712..5d048514e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int j; int[] distribution = new int[MaxCoeffThresh + 1]; - for (j = startBlock; j < endBlock; ++j) + for (j = startBlock; j < endBlock; j++) { short[] output = new short[16]; @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int i; int[] tmp = new int[16]; - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) int d1 = src[1] - reference[1]; @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { int a0 = tmp[0 + i] + tmp[12 + i]; // 15b int a1 = tmp[4 + i] + tmp[8 + i]; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 9990f9ef9..4276b887f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int sum; int i; - for (i = 0; i < 2; ++i) + for (i = 0; i < 2; i++) { int isAcCoeff = i > 0 ? 1 : 0; int bias = BiasMatrices[type][isAcCoeff]; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.ZThresh[i] = ((1 << WebpConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; } - for (i = 2; i < 16; ++i) + for (i = 2; i < 16; i++) { this.Q[i] = this.Q[1]; this.IQ[i] = this.IQ[1]; @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.ZThresh[i] = this.ZThresh[1]; } - for (sum = 0, i = 0; i < 16; ++i) + for (sum = 0, i = 0; i < 16; i++) { if (type == 0) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 919cc1753..93d76e283 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; int pattern = WebpLookupTables.Vp8LevelCodes[v - 1][0]; int i; - for (i = 0; (pattern >>= 1) != 0; ++i) + for (i = 0; (pattern >>= 1) != 0; i++) { int mask = 2 << i; if ((pattern & 1) != 0) diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs index 2537f714e..70383706b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs @@ -216,10 +216,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else { Span modes = block.Modes.AsSpan(); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { int yMode = left[y]; - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { byte[] prob = WebpLookupTables.ModesProba[top[x], yMode]; int i = WebpConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; @@ -299,26 +299,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // We only need to do this init once at block (0,0). // Afterward, it remains valid for the whole topmost row. Span tmp = yuv.Slice(yOff - WebpConstants.Bps - 1, 16 + 4 + 1); - for (int i = 0; i < tmp.Length; ++i) + for (int i = 0; i < tmp.Length; i++) { tmp[i] = 127; } tmp = yuv.Slice(uOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; ++i) + for (int i = 0; i < tmp.Length; i++) { tmp[i] = 127; } tmp = yuv.Slice(vOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; ++i) + for (int i = 0; i < tmp.Length; i++) { tmp[i] = 127; } } // Reconstruct one row. - for (int mbx = 0; mbx < dec.MbWidth; ++mbx) + for (int mbx = 0; mbx < dec.MbWidth; mbx++) { Vp8MacroBlockData block = dec.MacroBlockData[mbx]; @@ -326,14 +326,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // pixels at a time for alignment reason, and because of in-loop filter. if (mbx > 0) { - for (int i = -1; i < 16; ++i) + for (int i = -1; i < 16; i++) { int srcIdx = (i * WebpConstants.Bps) + 12 + yOff; int dstIdx = (i * WebpConstants.Bps) - 4 + yOff; yuv.Slice(srcIdx, 4).CopyTo(yuv.Slice(dstIdx)); } - for (int i = -1; i < 8; ++i) + for (int i = -1; i < 8; i++) { int srcIdx = (i * WebpConstants.Bps) + 4 + uOff; int dstIdx = (i * WebpConstants.Bps) - 4 + uOff; @@ -511,12 +511,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span yOut = dec.CacheY.Memory.Span.Slice(dec.CacheYOffset + (mbx * 16)); Span uOut = dec.CacheU.Memory.Span.Slice(dec.CacheUvOffset + (mbx * 8)); Span vOut = dec.CacheV.Memory.Span.Slice(dec.CacheUvOffset + (mbx * 8)); - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 16; j++) { yDst.Slice(j * WebpConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut.Slice(j * dec.CacheYStride)); } - for (int j = 0; j < 8; ++j) + for (int j = 0; j < 8; j++) { int jUvStride = j * dec.CacheUvStride; uDst.Slice(j * WebpConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut.Slice(jUvStride)); @@ -748,7 +748,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy LossyUtils.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); } - for (int x = 1; x <= lastPixelPair; ++x) + for (int x = 1; x <= lastPixelPair; x++) { uint tuv = LossyUtils.LoadUv(topU[x], topV[x]); // top sample uint uv = LossyUtils.LoadUv(curU[x], curV[x]); // sample @@ -903,11 +903,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); - for (int y = 0; y < 4; ++y) + for (int y = 0; y < 4; y++) { int l = lnz & 1; uint nzCoeffs = 0; - for (int x = 0; x < 4; ++x) + for (int x = 0; x < 4; x++) { int ctx = l + (tnz & 1); int nz = this.GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); @@ -931,10 +931,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int chPlus4 = 4 + ch; tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); - for (int y = 0; y < 2; ++y) + for (int y = 0; y < 2; y++) { int l = lnz & 1; - for (int x = 0; x < 2; ++x) + for (int x = 0; x < 2; x++) { int ctx = l + (tnz & 1); int nz = this.GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); @@ -1204,7 +1204,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; hasValue = this.bitReader.ReadBool(); int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebpConstants.NumMbSegments; ++i) + for (int i = 0; i < WebpConstants.NumMbSegments; i++) { int q; if (vp8SegmentHeader.UseSegment) diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs index 54ae5661e..57b5739c7 100644 --- a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebpLookupTables.cs @@ -1234,25 +1234,25 @@ namespace SixLabors.ImageSharp.Formats.Webp } Abs0 = new Dictionary(); - for (int i = -255; i <= 255; ++i) + for (int i = -255; i <= 255; i++) { Abs0[i] = (byte)((i < 0) ? -i : i); } Clip1 = new Dictionary(); - for (int i = -255; i <= 255 + 255; ++i) + for (int i = -255; i <= 255 + 255; i++) { Clip1[i] = (byte)(i < 0 ? 0 : i > 255 ? 255 : i); } Sclip1 = new Dictionary(); - for (int i = -1020; i <= 1020; ++i) + for (int i = -1020; i <= 1020; i++) { Sclip1[i] = (sbyte)(i < -128 ? -128 : i > 127 ? 127 : i); } Sclip2 = new Dictionary(); - for (int i = -112; i <= 112; ++i) + for (int i = -112; i <= 112; i++) { Sclip2[i] = (sbyte)(i < -16 ? -16 : i > 15 ? 15 : i); } From 421324f4381f4eaa26c93eb8cd6d275bf9b9a7d1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 12:07:56 +0200 Subject: [PATCH 343/359] Commit renamed file --- ...lphaCompressionMethod.cs => WebpAlphaCompressionMethod.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/ImageSharp/Formats/WebP/{AlphaCompressionMethod.cs => WebpAlphaCompressionMethod.cs} (80%) diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/WebpAlphaCompressionMethod.cs similarity index 80% rename from src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs rename to src/ImageSharp/Formats/WebP/WebpAlphaCompressionMethod.cs index d7bcb846d..75af0b309 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/WebpAlphaCompressionMethod.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { - internal enum AlphaCompressionMethod + internal enum WebpAlphaCompressionMethod { /// /// No compression. @@ -13,6 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Compressed using the WebP lossless format. /// - WebPLosslessCompression = 1 + WebpLosslessCompression = 1 } } From a2a63d9ebf24d4b929dbee314268ce44e6c2a8e0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 21:29:59 +1100 Subject: [PATCH 344/359] Rename folder --- .../Formats/{WebP => Webp}/AlphaDecoder.cs | 0 .../{WebP => Webp}/BitReader/BitReaderBase.cs | 0 .../{WebP => Webp}/BitReader/Vp8BitReader.cs | 0 .../{WebP => Webp}/BitReader/Vp8LBitReader.cs | 0 .../{WebP => Webp}/BitWriter/BitWriterBase.cs | 0 .../{WebP => Webp}/BitWriter/Vp8BitWriter.cs | 0 .../{WebP => Webp}/BitWriter/Vp8LBitWriter.cs | 0 src/ImageSharp/Formats/{WebP => Webp}/EntropyIx.cs | 0 src/ImageSharp/Formats/{WebP => Webp}/HistoIx.cs | 0 .../Formats/{WebP => Webp}/IWebpDecoderOptions.cs | 0 .../Formats/{WebP => Webp}/IWebpEncoderOptions.cs | 0 .../Lossless/BackwardReferenceEncoder.cs | 0 .../Formats/{WebP => Webp}/Lossless/ColorCache.cs | 0 .../{WebP => Webp}/Lossless/CostCacheInterval.cs | 0 .../Formats/{WebP => Webp}/Lossless/CostInterval.cs | 0 .../Formats/{WebP => Webp}/Lossless/CostManager.cs | 0 .../Formats/{WebP => Webp}/Lossless/CostModel.cs | 0 .../Formats/{WebP => Webp}/Lossless/CrunchConfig.cs | 0 .../{WebP => Webp}/Lossless/CrunchSubConfig.cs | 0 .../{WebP => Webp}/Lossless/DominantCostRange.cs | 0 .../Formats/{WebP => Webp}/Lossless/HTreeGroup.cs | 0 .../{WebP => Webp}/Lossless/HistogramBinInfo.cs | 0 .../{WebP => Webp}/Lossless/HistogramEncoder.cs | 0 .../{WebP => Webp}/Lossless/HistogramPair.cs | 0 .../Formats/{WebP => Webp}/Lossless/HuffIndex.cs | 0 .../Formats/{WebP => Webp}/Lossless/HuffmanCode.cs | 0 .../Formats/{WebP => Webp}/Lossless/HuffmanTree.cs | 0 .../{WebP => Webp}/Lossless/HuffmanTreeCode.cs | 0 .../{WebP => Webp}/Lossless/HuffmanTreeToken.cs | 0 .../Formats/{WebP => Webp}/Lossless/HuffmanUtils.cs | 0 .../{WebP => Webp}/Lossless/LosslessUtils.cs | 0 .../{WebP => Webp}/Lossless/NearLosslessEnc.cs | 0 .../Formats/{WebP => Webp}/Lossless/PixOrCopy.cs | 0 .../{WebP => Webp}/Lossless/PixOrCopyMode.cs | 0 .../{WebP => Webp}/Lossless/PredictorEncoder.cs | 0 .../{WebP => Webp}/Lossless/Vp8LBackwardRefs.cs | 0 .../{WebP => Webp}/Lossless/Vp8LBitEntropy.cs | 0 .../Formats/{WebP => Webp}/Lossless/Vp8LDecoder.cs | 0 .../Formats/{WebP => Webp}/Lossless/Vp8LEncoder.cs | 0 .../{WebP => Webp}/Lossless/Vp8LHashChain.cs | 0 .../{WebP => Webp}/Lossless/Vp8LHistogram.cs | 0 .../Formats/{WebP => Webp}/Lossless/Vp8LLz77Type.cs | 0 .../Formats/{WebP => Webp}/Lossless/Vp8LMetadata.cs | 0 .../{WebP => Webp}/Lossless/Vp8LMultipliers.cs | 0 .../Formats/{WebP => Webp}/Lossless/Vp8LStreaks.cs | 0 .../{WebP => Webp}/Lossless/Vp8LTransform.cs | 0 .../{WebP => Webp}/Lossless/Vp8LTransformType.cs | 0 .../{WebP => Webp}/Lossless/WebpLosslessDecoder.cs | 0 .../Webp_Lossless_Bitstream_Specification.pdf | Bin .../{WebP => Webp}/Lossy/IntraPredictionMode.cs | 0 .../Formats/{WebP => Webp}/Lossy/LoopFilter.cs | 0 .../Formats/{WebP => Webp}/Lossy/LossyUtils.cs | 0 .../Formats/{WebP => Webp}/Lossy/PassStats.cs | 0 .../Formats/{WebP => Webp}/Lossy/QuantEnc.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8BandProbas.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8CostArray.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Costs.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Decoder.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8EncIterator.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8EncProba.cs | 0 .../{WebP => Webp}/Lossy/Vp8EncSegmentHeader.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Encoder.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Encoding.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8FilterHeader.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8FilterInfo.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8FrameHeader.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Histogram.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Io.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8MacroBlock.cs | 0 .../{WebP => Webp}/Lossy/Vp8MacroBlockData.cs | 0 .../{WebP => Webp}/Lossy/Vp8MacroBlockInfo.cs | 0 .../{WebP => Webp}/Lossy/Vp8MacroBlockType.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Matrix.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8ModeScore.cs | 0 .../{WebP => Webp}/Lossy/Vp8PictureHeader.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Proba.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8ProbaArray.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8QuantMatrix.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8RDLevel.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Residual.cs | 0 .../{WebP => Webp}/Lossy/Vp8SegmentHeader.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8SegmentInfo.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8Stats.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8StatsArray.cs | 0 .../Formats/{WebP => Webp}/Lossy/Vp8TopSamples.cs | 0 .../{WebP => Webp}/Lossy/WebpLossyDecoder.cs | 0 .../Formats/{WebP => Webp}/Lossy/YuvConversion.cs | 0 .../Lossy/rfc6386_lossy_specification.pdf | Bin .../Formats/{WebP => Webp}/MetadataExtensions.cs | 0 src/ImageSharp/Formats/{WebP => Webp}/Readme.md | 0 .../{WebP => Webp}/WebpAlphaCompressionMethod.cs | 0 .../Formats/{WebP => Webp}/WebpAlphaFilterType.cs | 0 .../Formats/{WebP => Webp}/WebpBitsPerPixel.cs | 0 .../Formats/{WebP => Webp}/WebpChunkType.cs | 0 .../Formats/{WebP => Webp}/WebpCommonUtils.cs | 0 .../{WebP => Webp}/WebpConfigurationModule.cs | 0 .../Formats/{WebP => Webp}/WebpConstants.cs | 0 .../Formats/{WebP => Webp}/WebpDecoder.cs | 0 .../Formats/{WebP => Webp}/WebpDecoderCore.cs | 0 .../Formats/{WebP => Webp}/WebpEncoder.cs | 0 .../Formats/{WebP => Webp}/WebpEncoderCore.cs | 0 .../Formats/{WebP => Webp}/WebpFeatures.cs | 0 src/ImageSharp/Formats/{WebP => Webp}/WebpFormat.cs | 0 .../Formats/{WebP => Webp}/WebpFormatType.cs | 0 .../{WebP => Webp}/WebpImageFormatDetector.cs | 0 .../Formats/{WebP => Webp}/WebpImageInfo.cs | 0 .../Formats/{WebP => Webp}/WebpLookupTables.cs | 0 .../Formats/{WebP => Webp}/WebpMetadata.cs | 0 .../Formats/{WebP => Webp}/WebpThrowHelper.cs | 0 .../{WebP => Webp}/Webp_Container_Specification.pdf | Bin 110 files changed, 0 insertions(+), 0 deletions(-) rename src/ImageSharp/Formats/{WebP => Webp}/AlphaDecoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitReader/BitReaderBase.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitReader/Vp8BitReader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitReader/Vp8LBitReader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitWriter/BitWriterBase.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitWriter/Vp8BitWriter.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/BitWriter/Vp8LBitWriter.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/EntropyIx.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/HistoIx.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/IWebpDecoderOptions.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/IWebpEncoderOptions.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/BackwardReferenceEncoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/ColorCache.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CostCacheInterval.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CostInterval.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CostManager.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CostModel.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CrunchConfig.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/CrunchSubConfig.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/DominantCostRange.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HTreeGroup.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HistogramBinInfo.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HistogramEncoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HistogramPair.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffIndex.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffmanCode.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffmanTree.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffmanTreeCode.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffmanTreeToken.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/HuffmanUtils.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/LosslessUtils.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/NearLosslessEnc.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/PixOrCopy.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/PixOrCopyMode.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/PredictorEncoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LBackwardRefs.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LBitEntropy.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LDecoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LEncoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LHashChain.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LHistogram.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LLz77Type.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LMetadata.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LMultipliers.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LStreaks.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LTransform.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Vp8LTransformType.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/WebpLosslessDecoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossless/Webp_Lossless_Bitstream_Specification.pdf (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/IntraPredictionMode.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/LoopFilter.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/LossyUtils.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/PassStats.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/QuantEnc.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8BandProbas.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8CostArray.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Costs.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Decoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8EncIterator.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8EncProba.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8EncSegmentHeader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Encoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Encoding.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8FilterHeader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8FilterInfo.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8FrameHeader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Histogram.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Io.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8MacroBlock.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8MacroBlockData.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8MacroBlockInfo.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8MacroBlockType.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Matrix.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8ModeScore.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8PictureHeader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Proba.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8ProbaArray.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8QuantMatrix.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8RDLevel.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Residual.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8SegmentHeader.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8SegmentInfo.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8Stats.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8StatsArray.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/Vp8TopSamples.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/WebpLossyDecoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/YuvConversion.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Lossy/rfc6386_lossy_specification.pdf (100%) rename src/ImageSharp/Formats/{WebP => Webp}/MetadataExtensions.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Readme.md (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpAlphaCompressionMethod.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpAlphaFilterType.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpBitsPerPixel.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpChunkType.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpCommonUtils.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpConfigurationModule.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpConstants.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpDecoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpDecoderCore.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpEncoder.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpEncoderCore.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpFeatures.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpFormat.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpFormatType.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpImageFormatDetector.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpImageInfo.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpLookupTables.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpMetadata.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/WebpThrowHelper.cs (100%) rename src/ImageSharp/Formats/{WebP => Webp}/Webp_Container_Specification.pdf (100%) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/AlphaDecoder.cs rename to src/ImageSharp/Formats/Webp/AlphaDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs rename to src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs rename to src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs rename to src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs rename to src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs rename to src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs rename to src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/Webp/EntropyIx.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/EntropyIx.cs rename to src/ImageSharp/Formats/Webp/EntropyIx.cs diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/Webp/HistoIx.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/HistoIx.cs rename to src/ImageSharp/Formats/Webp/HistoIx.cs diff --git a/src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/IWebpDecoderOptions.cs rename to src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs rename to src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs rename to src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs rename to src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs rename to src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CostManager.cs rename to src/ImageSharp/Formats/Webp/Lossless/CostManager.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CostModel.cs rename to src/ImageSharp/Formats/Webp/Lossless/CostModel.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CrunchConfig.cs rename to src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/CrunchSubConfig.cs rename to src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs rename to src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs rename to src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs rename to src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs rename to src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs rename to src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs rename to src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs rename to src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs rename to src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs rename to src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs rename to src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/WebpLosslessDecoder.cs rename to src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Webp_Lossless_Bitstream_Specification.pdf b/src/ImageSharp/Formats/Webp/Lossless/Webp_Lossless_Bitstream_Specification.pdf similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossless/Webp_Lossless_Bitstream_Specification.pdf rename to src/ImageSharp/Formats/Webp/Lossless/Webp_Lossless_Bitstream_Specification.pdf diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs rename to src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs rename to src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs rename to src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/PassStats.cs rename to src/ImageSharp/Formats/Webp/Lossy/PassStats.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs rename to src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8BandProbas.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs rename to src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/WebpLossyDecoder.cs rename to src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs rename to src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/rfc6386_lossy_specification.pdf b/src/ImageSharp/Formats/Webp/Lossy/rfc6386_lossy_specification.pdf similarity index 100% rename from src/ImageSharp/Formats/WebP/Lossy/rfc6386_lossy_specification.pdf rename to src/ImageSharp/Formats/Webp/Lossy/rfc6386_lossy_specification.pdf diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/MetadataExtensions.cs rename to src/ImageSharp/Formats/Webp/MetadataExtensions.cs diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/Webp/Readme.md similarity index 100% rename from src/ImageSharp/Formats/WebP/Readme.md rename to src/ImageSharp/Formats/Webp/Readme.md diff --git a/src/ImageSharp/Formats/WebP/WebpAlphaCompressionMethod.cs b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpAlphaCompressionMethod.cs rename to src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs diff --git a/src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs b/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpAlphaFilterType.cs rename to src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpBitsPerPixel.cs rename to src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs diff --git a/src/ImageSharp/Formats/WebP/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpChunkType.cs rename to src/ImageSharp/Formats/Webp/WebpChunkType.cs diff --git a/src/ImageSharp/Formats/WebP/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpCommonUtils.cs rename to src/ImageSharp/Formats/Webp/WebpCommonUtils.cs diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs rename to src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpConstants.cs rename to src/ImageSharp/Formats/Webp/WebpConstants.cs diff --git a/src/ImageSharp/Formats/WebP/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpDecoder.cs rename to src/ImageSharp/Formats/Webp/WebpDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpDecoderCore.cs rename to src/ImageSharp/Formats/Webp/WebpDecoderCore.cs diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpEncoder.cs rename to src/ImageSharp/Formats/Webp/WebpEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpEncoderCore.cs rename to src/ImageSharp/Formats/Webp/WebpEncoderCore.cs diff --git a/src/ImageSharp/Formats/WebP/WebpFeatures.cs b/src/ImageSharp/Formats/Webp/WebpFeatures.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpFeatures.cs rename to src/ImageSharp/Formats/Webp/WebpFeatures.cs diff --git a/src/ImageSharp/Formats/WebP/WebpFormat.cs b/src/ImageSharp/Formats/Webp/WebpFormat.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpFormat.cs rename to src/ImageSharp/Formats/Webp/WebpFormat.cs diff --git a/src/ImageSharp/Formats/WebP/WebpFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFormatType.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpFormatType.cs rename to src/ImageSharp/Formats/Webp/WebpFormatType.cs diff --git a/src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpImageFormatDetector.cs rename to src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs diff --git a/src/ImageSharp/Formats/WebP/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpImageInfo.cs rename to src/ImageSharp/Formats/Webp/WebpImageInfo.cs diff --git a/src/ImageSharp/Formats/WebP/WebpLookupTables.cs b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpLookupTables.cs rename to src/ImageSharp/Formats/Webp/WebpLookupTables.cs diff --git a/src/ImageSharp/Formats/WebP/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpMetadata.cs rename to src/ImageSharp/Formats/Webp/WebpMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs similarity index 100% rename from src/ImageSharp/Formats/WebP/WebpThrowHelper.cs rename to src/ImageSharp/Formats/Webp/WebpThrowHelper.cs diff --git a/src/ImageSharp/Formats/WebP/Webp_Container_Specification.pdf b/src/ImageSharp/Formats/Webp/Webp_Container_Specification.pdf similarity index 100% rename from src/ImageSharp/Formats/WebP/Webp_Container_Specification.pdf rename to src/ImageSharp/Formats/Webp/Webp_Container_Specification.pdf From f88a95c0f31458d5a9107c823a6d46de23ee0f41 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 22:10:22 +1100 Subject: [PATCH 345/359] Use Webp everywhere --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 6 +-- .../Formats/Webp/BitWriter/BitWriterBase.cs | 2 +- .../Formats/Webp/IWebpEncoderOptions.cs | 4 +- .../Webp/Lossless/WebpLosslessDecoder.cs | 8 ++-- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 6 +-- src/ImageSharp/Formats/Webp/Readme.md | 12 +++--- .../Webp/WebpAlphaCompressionMethod.cs | 2 +- src/ImageSharp/Formats/Webp/WebpChunkType.cs | 2 +- src/ImageSharp/Formats/Webp/WebpConstants.cs | 6 +-- .../Formats/Webp/WebpDecoderCore.cs | 8 ++-- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 4 +- .../Formats/Webp/WebpEncoderCore.cs | 6 +-- src/ImageSharp/Formats/Webp/WebpFormat.cs | 2 +- .../Formats/Webp/WebpImageFormatDetector.cs | 6 +-- src/ImageSharp/Formats/Webp/WebpMetadata.cs | 2 +- .../Codecs/DecodeWebp.cs | 40 +++++++++---------- .../Codecs/EncodeWebp.cs | 8 ++-- .../Formats/WebP/PredictorEncoderTests.cs | 4 +- .../Formats/WebP/WebpDecoderTests.cs | 2 +- .../Formats/WebP/WebpEncoderTests.cs | 2 +- .../Formats/WebP/WebpMetaDataTests.cs | 20 +++++----- .../Formats/WebP/YuvConversionTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 2 +- 23 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index ba27d9999..c2a6a261f 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Webp if (this.Compressed) { var bitReader = new Vp8LBitReader(data); - this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); + this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); this.Use8BDecode = this.Vp8LDec.Transforms.Count > 0 && Is8BOptimizable(this.Vp8LDec.Metadata); } @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. /// - private WebPLosslessDecoder LosslessDecoder { get; } + private WebpLosslessDecoder LosslessDecoder { get; } /// /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Webp // Extract alpha (which is stored in the green plane). int pixelCount = width * numRowsToProcess; - WebPLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); + WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); ExtractGreen(input, output, pixelCount); this.AlphaApplyFilter(0, numRowsToProcess, output, width); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 10ec6201d..41623f287 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter stream.Write(WebpConstants.RiffFourCc); BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize); stream.Write(buf); - stream.Write(WebpConstants.WebPHeader); + stream.Write(WebpConstants.WebpHeader); } /// diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index a14921011..4992f585d 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Configuration options for use during webp encoding. /// - internal interface IWebPEncoderOptions + internal interface IWebpEncoderOptions { /// /// Gets a value indicating whether lossy compression should be used. @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Webp int Method { get; } /// - /// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format. + /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. /// bool UseAlphaCompression { get; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 938278517..960416009 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The lossless specification can be found here: /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification /// - internal sealed class WebPLosslessDecoder + internal sealed class WebpLosslessDecoder { /// /// A bit reader for reading lossless webp streams. @@ -75,12 +75,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless }; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Bitreader to read from the stream. /// Used for allocating memory during processing operations. /// The configuration. - public WebPLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) + public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) { this.bitReader = bitReader; this.memoryAllocator = memoryAllocator; @@ -675,7 +675,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } /// - /// A WebP lossless image can go through four different types of transformation before being entropy encoded. + /// A Webp lossless image can go through four different types of transformation before being entropy encoded. /// This will reverse the transformations, if any are present. /// /// The decoder holding the transformation infos. diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 70383706b..ebb0b0aa4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386 /// - internal sealed class WebPLossyDecoder + internal sealed class WebpLossyDecoder { /// /// A bit reader for reading lossy webp streams. @@ -35,12 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private readonly Configuration configuration; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Bitreader to read from the stream. /// Used for allocating memory during processing operations. /// The configuration. - public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) + public WebpLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) { this.bitReader = bitReader; this.memoryAllocator = memoryAllocator; diff --git a/src/ImageSharp/Formats/Webp/Readme.md b/src/ImageSharp/Formats/Webp/Readme.md index f3daead83..38c1cad9d 100644 --- a/src/ImageSharp/Formats/Webp/Readme.md +++ b/src/ImageSharp/Formats/Webp/Readme.md @@ -1,10 +1,10 @@ -# WebP Format +# Webp Format Reference implementation, specification and stuff like that: - [google webp introduction](https://developers.google.com/speed/webp) -- [WebP Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) -- [WebP VP8 Spec, Lossy](http://tools.ietf.org/html/rfc6386) -- [WebP VP8L Spec, Lossless](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) -- [WebP filefront](https://wiki.fileformat.com/image/webp/) -- [WebP test data](https://github.com/webmproject/libwebp-test-data/) +- [Webp Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) +- [Webp VP8 Spec, Lossy](http://tools.ietf.org/html/rfc6386) +- [Webp VP8L Spec, Lossless](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) +- [Webp filefront](https://wiki.fileformat.com/image/webp/) +- [Webp test data](https://github.com/webmproject/libwebp-test-data/) diff --git a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs index 75af0b309..8875a3c89 100644 --- a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp NoCompression = 0, /// - /// Compressed using the WebP lossless format. + /// Compressed using the Webp lossless format. /// WebpLosslessCompression = 1 } diff --git a/src/ImageSharp/Formats/Webp/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs index add40f302..be17b420c 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkType.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkType.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Contains a list of different webp chunk types. /// - /// See WebP Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container + /// See Webp Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container internal enum WebpChunkType : uint { /// diff --git a/src/ImageSharp/Formats/Webp/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs index 169bc5b7d..fd46bde2b 100644 --- a/src/ImageSharp/Formats/Webp/WebpConstants.cs +++ b/src/ImageSharp/Formats/Webp/WebpConstants.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp internal static class WebpConstants { /// - /// The list of file extensions that equate to WebP. + /// The list of file extensions that equate to Webp. /// public static readonly IEnumerable FileExtensions = new[] { "webp" }; @@ -80,9 +80,9 @@ namespace SixLabors.ImageSharp.Formats.Webp }; /// - /// The header bytes identifying a WebP. + /// The header bytes identifying a Webp. /// - public static readonly byte[] WebPHeader = + public static readonly byte[] WebpHeader = { 0x57, // W 0x45, // E diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index db55bcd95..4a5a15b1c 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -97,12 +97,12 @@ namespace SixLabors.ImageSharp.Formats.Webp Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.webImageInfo.IsLossless) { - var losslessDecoder = new WebPLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); + var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebPLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); + var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); } @@ -502,7 +502,7 @@ namespace SixLabors.ImageSharp.Formats.Webp return chunkType; } - throw new ImageFormatException("Invalid WebP data."); + throw new ImageFormatException("Invalid Webp data."); } /// @@ -518,7 +518,7 @@ namespace SixLabors.ImageSharp.Formats.Webp return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; } - throw new ImageFormatException("Invalid WebP data."); + throw new ImageFormatException("Invalid Webp data."); } /// diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index fd213cba1..bb30e2512 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -10,9 +10,9 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp { /// - /// Image encoder for writing an image to a stream in the WebP format. + /// Image encoder for writing an image to a stream in the Webp format. /// - public sealed class WebpEncoder : IImageEncoder, IWebPEncoderOptions + public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions { /// public bool Lossy { get; set; } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 5fe188c8d..63f54f133 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -12,7 +12,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp { /// - /// Image encoder for writing an image to a stream in the WebP format. + /// Image encoder for writing an image to a stream in the Webp format. /// internal sealed class WebpEncoderCore : IImageEncoderInternals { @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// TODO: not used at the moment. - /// Indicating whether the alpha plane should be compressed with WebP lossless format. + /// Indicating whether the alpha plane should be compressed with Webp lossless format. /// private readonly bool alphaCompression; @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// The encoder options. /// The memory manager. - public WebpEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) + public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.alphaCompression = options.UseAlphaCompression; diff --git a/src/ImageSharp/Formats/Webp/WebpFormat.cs b/src/ImageSharp/Formats/Webp/WebpFormat.cs index 6a23b9067..1f27c4d84 100644 --- a/src/ImageSharp/Formats/Webp/WebpFormat.cs +++ b/src/ImageSharp/Formats/Webp/WebpFormat.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public static WebpFormat Instance { get; } = new WebpFormat(); /// - public string Name => "WebP"; + public string Name => "Webp"; /// public string DefaultMimeType => "image/webp"; diff --git a/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs index a5d7a8201..4bb794f56 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 { @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) => header.Length >= this.HeaderSize && this.IsRiffContainer(header) && this.IsWebPFile(header); + private bool IsSupportedFileFormat(ReadOnlySpan header) => header.Length >= this.HeaderSize && this.IsRiffContainer(header) && this.IsWebpFile(header); /// /// Checks, if the header starts with a valid RIFF FourCC. @@ -30,6 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// The header bytes. /// True, if its a webp file. - private bool IsWebPFile(ReadOnlySpan header) => header.Slice(8, 4).SequenceEqual(WebpConstants.WebPHeader); + private bool IsWebpFile(ReadOnlySpan header) => header.Slice(8, 4).SequenceEqual(WebpConstants.WebpHeader); } } diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 8144d79b5..001ff1fbb 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { /// - /// Provides WebP specific metadata information for the image. + /// Provides Webp specific metadata information for the image. /// public class WebpMetadata : IDeepCloneable { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 98e1f8689..940fe08b1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -26,10 +26,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); - [Params(TestImages.WebP.Lossy.Earth)] + [Params(TestImages.Webp.Lossy.Earth)] public string TestImageLossy { get; set; } - [Params(TestImages.WebP.Lossless.Earth)] + [Params(TestImages.Webp.Lossless.Earth)] public string TestImageLossless { get; set; } [GlobalSetup] @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "Magick Lossy Webp")] public int WebpLossyMagick() { - var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + var settings = new MagickReadSettings { Format = MagickFormat.Webp }; using var memoryStream = new MemoryStream(this.webpLossyBytes); using var image = new MagickImage(memoryStream, settings); return image.Width; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "Magick Lossless Webp")] public int WebpLosslessMagick() { - var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + var settings = new MagickReadSettings { Format = MagickFormat.Webp }; using var memoryStream = new MemoryStream(this.webpLossyBytes); using var image = new MagickImage(memoryStream, settings); return image.Width; @@ -88,22 +88,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs IterationCount=3 LaunchCount=1 WarmupCount=3 | Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------------------- |----------- |-------------- |---------------------- |------------------------- |-----------:|----------:|---------:|----------:|----------:|------:|------------:| - | 'Magick Lossy Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.8 ms | 6.28 ms | 0.34 ms | - | - | - | 17.65 KB | - | 'ImageSharp Lossy Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 1,145.0 ms | 110.82 ms | 6.07 ms | - | - | - | 2779.53 KB | - | 'Magick Lossless Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 145.9 ms | 8.55 ms | 0.47 ms | - | - | - | 18.05 KB | - | 'ImageSharp Lossless Webp' | Job-IERNAB | .NET 4.7.2 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 1,694.1 ms | 55.09 ms | 3.02 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | - | 'Magick Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.7 ms | 1.89 ms | 0.10 ms | - | - | - | 15.75 KB | - | 'ImageSharp Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 741.6 ms | 21.45 ms | 1.18 ms | - | - | - | 2767.85 KB | - | 'Magick Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.1 ms | 9.52 ms | 0.52 ms | - | - | - | 16.54 KB | - | 'ImageSharp Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 522.5 ms | 21.15 ms | 1.16 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | - | 'Magick Lossy Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.9 ms | 5.34 ms | 0.29 ms | - | - | - | 15.45 KB | - | 'ImageSharp Lossy Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 748.8 ms | 290.47 ms | 15.92 ms | - | - | - | 2767.84 KB | - | 'Magick Lossless Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.1 ms | 1.14 ms | 0.06 ms | - | - | - | 15.9 KB | - | 'ImageSharp Lossless Webp' | Job-NAASQX | .NET Core 3.1 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 480.7 ms | 25.25 ms | 1.38 ms | 4000.0000 | 1000.0000 | - | 22859.7 KB | - | 'Magick Lossy Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 105.7 ms | 4.71 ms | 0.26 ms | - | - | - | 15.48 KB | - | 'ImageSharp Lossy Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 645.7 ms | 61.00 ms | 3.34 ms | - | - | - | 2768.13 KB | - | 'Magick Lossless Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 146.5 ms | 18.63 ms | 1.02 ms | - | - | - | 15.8 KB | - | 'ImageSharp Lossless Webp' | Job-GLNACU | .NET Core 5.0 | WebP/earth_lossy.webp | WebP/earth_lossless.webp | 306.7 ms | 32.31 ms | 1.77 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | + | 'Magick Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.8 ms | 6.28 ms | 0.34 ms | - | - | - | 17.65 KB | + | 'ImageSharp Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,145.0 ms | 110.82 ms | 6.07 ms | - | - | - | 2779.53 KB | + | 'Magick Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 145.9 ms | 8.55 ms | 0.47 ms | - | - | - | 18.05 KB | + | 'ImageSharp Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,694.1 ms | 55.09 ms | 3.02 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | + | 'Magick Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 1.89 ms | 0.10 ms | - | - | - | 15.75 KB | + | 'ImageSharp Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 741.6 ms | 21.45 ms | 1.18 ms | - | - | - | 2767.85 KB | + | 'Magick Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 9.52 ms | 0.52 ms | - | - | - | 16.54 KB | + | 'ImageSharp Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 522.5 ms | 21.15 ms | 1.16 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | + | 'Magick Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.9 ms | 5.34 ms | 0.29 ms | - | - | - | 15.45 KB | + | 'ImageSharp Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 748.8 ms | 290.47 ms | 15.92 ms | - | - | - | 2767.84 KB | + | 'Magick Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 1.14 ms | 0.06 ms | - | - | - | 15.9 KB | + | 'ImageSharp Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 480.7 ms | 25.25 ms | 1.38 ms | 4000.0000 | 1000.0000 | - | 22859.7 KB | + | 'Magick Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 4.71 ms | 0.26 ms | - | - | - | 15.48 KB | + | 'ImageSharp Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 645.7 ms | 61.00 ms | 3.34 ms | - | - | - | 2768.13 KB | + | 'Magick Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.5 ms | 18.63 ms | 1.02 ms | - | - | - | 15.8 KB | + | 'ImageSharp Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 306.7 ms | 32.31 ms | 1.77 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | */ } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 8f3869a52..5405fbc1b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void MagickWebpLossy() { using var memoryStream = new MemoryStream(); - this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", false); - this.webpMagick.Write(memoryStream, MagickFormat.WebP); + this.webpMagick.Settings.SetDefine(MagickFormat.Webp, "lossless", false); + this.webpMagick.Write(memoryStream, MagickFormat.Webp); } [Benchmark(Description = "ImageSharp Webp Lossy")] @@ -62,8 +62,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void MagickWebpLossless() { using var memoryStream = new MemoryStream(); - this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", true); - this.webpMagick.Write(memoryStream, MagickFormat.WebP); + this.webpMagick.Settings.SetDefine(MagickFormat.Webp, "lossless", true); + this.webpMagick.Write(memoryStream, MagickFormat.Webp); } [Benchmark(Description = "ImageSharp Webp Lossless")] diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index e63ca2fec..80d48d0ca 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Peak)); + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Peak)); using var image = Image.Load(imgBytes); uint[] bgra = ToBgra(image); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.WebP.Lossless.BikeSmall)); + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossless.BikeSmall)); using var image = Image.Load(imgBytes, new WebpDecoder()); uint[] bgra = ToBgra(image); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 7d763c05a..34fa72c63 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; -using static SixLabors.ImageSharp.Tests.TestImages.WebP; +using static SixLabors.ImageSharp.Tests.TestImages.Webp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Webp diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 8374342e8..a72621241 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -using static SixLabors.ImageSharp.Tests.TestImages.WebP; +using static SixLabors.ImageSharp.Tests.TestImages.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 901da3dbd..1b0dc3e3f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; [Theory] - [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32, true)] - [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, true)] public void IgnoreMetadata_ControlsWhetherExifIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { @@ -42,10 +42,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(TestImages.WebP.Lossy.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.WebP.Lossy.WithIccp, PixelTypes.Rgba32, true)] - [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.WebP.Lossless.WithIccp, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, true)] public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(TestImages.WebP.Lossy.WithExif, PixelTypes.Rgba32)] + [WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32)] public void EncodeLossyWebp_PreservesExif(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } [Theory] - [WithFile(TestImages.WebP.Lossless.WithExif, PixelTypes.Rgba32)] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32)] public void EncodeLosslessWebp_PreservesExif(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 76657d66b..65b4b987e 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public class YuvConversionTests { [Theory] - [WithFile(TestImages.WebP.Yuv, PixelTypes.Rgba32)] + [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] public void ConvertRgbToYuv_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 90965b352..a9af84f9b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -517,7 +517,7 @@ namespace SixLabors.ImageSharp.Tests public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; } - public static class WebP + public static class Webp { // Reference image as png public const string Peak = "Webp/peak.png"; From a1176c933bb37c9c6b15efe3395cc88601641d6e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Oct 2021 22:29:21 +1100 Subject: [PATCH 346/359] Fix benchmarks --- tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs | 4 ++-- tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 940fe08b1..407a4ef3b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "Magick Lossy Webp")] public int WebpLossyMagick() { - var settings = new MagickReadSettings { Format = MagickFormat.Webp }; + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; using var memoryStream = new MemoryStream(this.webpLossyBytes); using var image = new MagickImage(memoryStream, settings); return image.Width; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "Magick Lossless Webp")] public int WebpLosslessMagick() { - var settings = new MagickReadSettings { Format = MagickFormat.Webp }; + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; using var memoryStream = new MemoryStream(this.webpLossyBytes); using var image = new MagickImage(memoryStream, settings); return image.Width; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 5405fbc1b..8f3869a52 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void MagickWebpLossy() { using var memoryStream = new MemoryStream(); - this.webpMagick.Settings.SetDefine(MagickFormat.Webp, "lossless", false); - this.webpMagick.Write(memoryStream, MagickFormat.Webp); + this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", false); + this.webpMagick.Write(memoryStream, MagickFormat.WebP); } [Benchmark(Description = "ImageSharp Webp Lossy")] @@ -62,8 +62,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void MagickWebpLossless() { using var memoryStream = new MemoryStream(); - this.webpMagick.Settings.SetDefine(MagickFormat.Webp, "lossless", true); - this.webpMagick.Write(memoryStream, MagickFormat.Webp); + this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", true); + this.webpMagick.Write(memoryStream, MagickFormat.WebP); } [Benchmark(Description = "ImageSharp Webp Lossless")] From a41631efa3e076d4ec95d6a7d48517d68b2f8bcc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 19:17:07 +0200 Subject: [PATCH 347/359] Add enum for webp encoding method --- .../Formats/Webp/IWebpEncoderOptions.cs | 2 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 27 +++++--- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 2 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 37 ++++++----- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 2 +- .../Formats/Webp/WebpEncodingMethod.cs | 61 +++++++++++++++++++ .../Formats/WebP/WebpEncoderTests.cs | 8 +-- 8 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 4992f585d..6abf34483 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). /// Defaults to 4. /// - int Method { get; } + WebpEncodingMethod Method { get; } /// /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index a57c1c982..e6e8258cd 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible @@ -88,7 +88,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. /// Indicating whether near lossless mode should be used. /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact, bool nearLossless, int nearLosslessQuality) + public Vp8LEncoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + bool exact, + bool nearLossless, + int nearLosslessQuality) { int pixelCount = width * height; int initialSize = pixelCount * 2; @@ -96,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = Numerics.Clamp(method, 0, 6); + this.method = (WebpEncodingMethod)Numerics.Clamp((int)method, 0, 6); this.exact = exact; this.nearLossless = nearLossless; this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); @@ -424,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bool doNotCache = false; var crunchConfigs = new List(); - if (this.method == 6 && this.quality == 100) + if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) { doNotCache = true; @@ -442,7 +451,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Only choose the guessed best transform. crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (this.quality >= 75 && this.method == 5) + if (this.quality >= 75 && this.method == WebpEncodingMethod.Level5) { // Test with and without color cache. doNotCache = true; @@ -1615,10 +1624,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Calculates the huffman image bits. /// - private static int GetHistoBits(int method, bool usePalette, int width, int height) + private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int width, int height) { // Make tile size a function of encoding method (Range: 0 to 6). - int histoBits = (usePalette ? 9 : 7) - method; + int histoBits = (usePalette ? 9 : 7) - (int)method; while (true) { int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); @@ -1678,9 +1687,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Calculates the bits used for the transformation. /// [MethodImpl(InliningOptions.ShortMethod)] - private static int GetTransformBits(int method, int histoBits) + private static int GetTransformBits(WebpEncodingMethod method, int histoBits) { - int maxTransformBits = method < 4 ? 6 : method > 4 ? 4 : 5; + int maxTransformBits = (int)method < 4 ? 6 : (int)method > 4 ? 4 : 5; int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; return res; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index dab31c7a2..ca3f8481e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -432,7 +432,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. - var i4Alpha = totalHisto.GetAlpha(); + int i4Alpha = totalHisto.GetAlpha(); if (i4Alpha > bestAlpha) { this.SetIntra4Mode(modes); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index f548ddb7a..16b84f2c6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// Number of entropy-analysis passes (in [1..10]). @@ -99,20 +99,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Number of entropy-analysis passes (in [1..10]). /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). /// The spatial noise shaping. 0=off, 100=maximum. - public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength, int spatialNoiseShaping) + public Vp8Encoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + int entropyPasses, + int filterStrength, + int spatialNoiseShaping) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.Width = width; this.Height = height; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = Numerics.Clamp(method, 0, 6); + this.method = (WebpEncodingMethod)Numerics.Clamp((int)method, 0, 6); this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); - this.rdOptLevel = method >= 6 ? Vp8RdLevel.RdOptTrellisAll - : method >= 5 ? Vp8RdLevel.RdOptTrellis - : method >= 3 ? Vp8RdLevel.RdOptBasic + this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll + : (int)method >= 5 ? Vp8RdLevel.RdOptTrellis + : (int)method >= 3 ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int pixelCount = width * height; @@ -360,9 +369,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. bool doSearch = targetSize > 0 || targetPsnr > 0; - bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; + bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = (int)this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -371,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) { - if (this.method == 3) + if (this.method == WebpEncodingMethod.Level3) { // We need more stats for method 3 to be reliable. nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; @@ -790,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetupMatrices(Vp8SegmentInfo[] dqm) { - int tlambdaScale = (this.method >= 4) ? this.spatialNoiseShaping : 0; + int tlambdaScale = (int)this.method >= 4 ? this.spatialNoiseShaping : 0; for (int i = 0; i < dqm.Length; i++) { Vp8SegmentInfo m = dqm[i]; @@ -861,14 +870,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.SetSegment(0); // default segment, spec-wise. int bestAlpha; - if (this.method <= 1) + if ((int)this.method <= 1) { bestAlpha = it.FastMbAnalyze(this.quality); } else { bestAlpha = it.MbAnalyzeBestIntra16Mode(); - if (this.method >= 5) + if ((int)this.method >= 5) { // We go and make a fast decision for intra4/intra16. // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. @@ -899,7 +908,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (rdOpt > Vp8RdLevel.RdOptNone) { QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); - if (this.method >= 2) + if ((int)this.method >= 2) { QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } @@ -912,7 +921,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, (int)this.method >= 2, (int)this.method >= 1, this.MbHeaderLimit); } bool isSkipped = rd.Nz == 0; diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index bb30e2512..1eb7b3846 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public int Quality { get; set; } = 75; /// - public int Method { get; set; } = 4; + public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default; /// public bool UseAlphaCompression { get; set; } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 63f54f133..57c696c96 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// The number of entropy-analysis passes (in [1..10]). diff --git a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs new file mode 100644 index 000000000..7d245a7e7 --- /dev/null +++ b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Webp +{ + /// + /// Quality/speed trade-off for the encoding process (0=fast, 6=slower-better). + /// + public enum WebpEncodingMethod + { + /// + /// Fastest, but quality compromise. Equivalent to . + /// + Level0 = 0, + + /// + /// Fastest, but quality compromise. + /// + Fastest = Level0, + + /// + /// Level1. + /// + Level1 = 1, + + /// + /// Level 2. + /// + Level2 = 2, + + /// + /// Level 3. + /// + Level3 = 3, + + /// + /// Level 4. Equivalent to . + /// + Level4 = 4, + + /// + /// BestQuality trade off between speed and quality. + /// + Default = Level4, + + /// + /// Level 5. + /// + Level5 = 5, + + /// + /// Slowest option, but best quality. Equivalent to . + /// + Level6 = 6, + + /// + /// Slowest option, but best quality. + /// + BestQuality = Level6 + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index a72621241..6a0e599d0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { Lossy = false, Quality = 100, - Method = 6 + Method = WebpEncodingMethod.BestQuality }; using Image image = provider.GetImage(); @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) + public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] - public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, int method) + public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, WebpEncodingMethod method) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, int method, int quality) + public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() From b9c67cebef8725c7f0135094d49355c3ae613749 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 08:15:07 +0200 Subject: [PATCH 348/359] Avoid casting WebpEncodingMethod --- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 4 ++-- .../Formats/Webp/Lossy/Vp8Encoder.cs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index e6e8258cd..ccf74e14e 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = (WebpEncodingMethod)Numerics.Clamp((int)method, 0, 6); + this.method = method; this.exact = exact; this.nearLossless = nearLossless; this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); @@ -1689,7 +1689,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless [MethodImpl(InliningOptions.ShortMethod)] private static int GetTransformBits(WebpEncodingMethod method, int histoBits) { - int maxTransformBits = (int)method < 4 ? 6 : (int)method > 4 ? 4 : 5; + int maxTransformBits = (int)method < 4 ? 6 : method > WebpEncodingMethod.Level4 ? 4 : 5; int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; return res; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 16b84f2c6..37808d56c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -115,13 +115,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Width = width; this.Height = height; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = (WebpEncodingMethod)Numerics.Clamp((int)method, 0, 6); + this.method = method; this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll - : (int)method >= 5 ? Vp8RdLevel.RdOptTrellis - : (int)method >= 3 ? Vp8RdLevel.RdOptBasic + : method >= WebpEncodingMethod.Level5 ? Vp8RdLevel.RdOptTrellis + : method >= WebpEncodingMethod.Level3 ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int pixelCount = width * height; @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy bool doSearch = targetSize > 0 || targetPsnr > 0; bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = (int)this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -799,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetupMatrices(Vp8SegmentInfo[] dqm) { - int tlambdaScale = (int)this.method >= 4 ? this.spatialNoiseShaping : 0; + int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0; for (int i = 0; i < dqm.Length; i++) { Vp8SegmentInfo m = dqm[i]; @@ -870,14 +870,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.SetSegment(0); // default segment, spec-wise. int bestAlpha; - if ((int)this.method <= 1) + if (this.method <= WebpEncodingMethod.Level1) { bestAlpha = it.FastMbAnalyze(this.quality); } else { bestAlpha = it.MbAnalyzeBestIntra16Mode(); - if ((int)this.method >= 5) + if (this.method >= WebpEncodingMethod.Level5) { // We go and make a fast decision for intra4/intra16. // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. @@ -908,7 +908,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (rdOpt > Vp8RdLevel.RdOptNone) { QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); - if ((int)this.method >= 2) + if (this.method >= WebpEncodingMethod.Level2) { QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } @@ -921,7 +921,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, (int)this.method >= 2, (int)this.method >= 1, this.MbHeaderLimit); + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= WebpEncodingMethod.Level2, this.method >= WebpEncodingMethod.Level1, this.MbHeaderLimit); } bool isSkipped = rd.Nz == 0; From 7e85b1e27eaa3926e9a2b38c413fc31721e365ef Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 17:19:52 +0200 Subject: [PATCH 349/359] Use dispose pattern --- .../Formats/Webp/BitReader/BitReaderBase.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index 47d158123..f11f2a110 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader /// internal abstract class BitReaderBase : IDisposable { + private bool isDisposed; + /// /// Gets or sets the raw encoded image data. /// @@ -31,7 +33,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader input.Read(dataSpan.Slice(0, bytesToRead), 0, bytesToRead); } + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.Data?.Dispose(); + } + + this.isDisposed = true; + } + /// - public void Dispose() => this.Data?.Dispose(); + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } From 94c49d7c0f74928fd58dc9c16a42b9086772c05f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 18:15:33 +0200 Subject: [PATCH 350/359] Use Numerics.Log2 --- .../Formats/Webp/BitReader/Vp8BitReader.cs | 2 +- .../Formats/Webp/Lossless/LosslessUtils.cs | 4 ++-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 4 ++-- src/ImageSharp/Formats/Webp/WebpCommonUtils.cs | 17 ----------------- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs index 5fc68e095..abf44127a 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitReader range = split + 1; } - int shift = 7 ^ WebpCommonUtils.BitsLog2Floor(range); + int shift = 7 ^ Numerics.Log2(range); range <<= shift; this.bits -= shift; diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 43e87061c..b7f94415b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -847,7 +847,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) { - int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); + int highestBit = Numerics.Log2((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; extraBits = highestBit - 1; int code = (2 * highestBit) + secondHighestBit; @@ -856,7 +856,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static int PrefixEncodeNoLut(int distance, ref int extraBits, ref int extraBitsValue) { - int highestBit = WebpCommonUtils.BitsLog2Floor((uint)--distance); + int highestBit = Numerics.Log2((uint)--distance); int secondHighestBit = (distance >> (highestBit - 1)) & 1; extraBits = highestBit - 1; extraBitsValue = distance & ((1 << extraBits) - 1); diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index ccf74e14e..c95b8e362 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // If using a color cache, do not have it bigger than the number of colors. if (useCache && this.PaletteSize < 1 << WebpConstants.MaxColorCacheBits) { - this.CacheBits = WebpCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; + this.CacheBits = Numerics.Log2((uint)this.PaletteSize) + 1; } } @@ -893,7 +893,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - int nBits = WebpCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nBits = Numerics.Log2((uint)trimmedLength - 2); int nBitPairs = (nBits / 2) + 1; this.bitWriter.PutBits((uint)nBitPairs - 1, 3); this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs index cdd324b07..d6e8d0a06 100644 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; #if SUPPORTS_RUNTIME_INTRINSICS @@ -17,22 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// internal static class WebpCommonUtils { - /// - /// Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int BitsLog2Floor(uint n) - { - int logValue = 0; - while (n >= 256) - { - logValue += 8; - n >>= 8; - } - - return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebpLookupTables.LogTable8Bit), (int)n); - } - /// /// Checks if the pixel row is not opaque. /// From 4ec66a27625eb104a0bca30cafbe4c44c239f7db Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 18:19:57 +0200 Subject: [PATCH 351/359] Use pattern matching --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 4 ++-- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 15 ++++++--------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index c2a6a261f..e63cd27b5 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Webp int totalPixels = width * height; var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); - if (compression != WebpAlphaCompressionMethod.NoCompression && compression != WebpAlphaCompressionMethod.WebpLosslessCompression) + if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) { WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); } @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Webp // The filtering method used. Only values between 0 and 3 are valid. int filter = (alphaChunkHeader >> 2) & 0x03; - if (filter < (int)WebpAlphaFilterType.None || filter > (int)WebpAlphaFilterType.Gradient) + if (filter is < (int)WebpAlphaFilterType.None or > (int)WebpAlphaFilterType.Gradient) { WebpThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 76766f67e..7628247fd 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter public void PutI16Mode(int mode) { - if (this.PutBit(mode == TM_PRED || mode == H_PRED, 156)) + if (this.PutBit(mode is TM_PRED or H_PRED, 156)) { this.PutBit(mode == TM_PRED, 128); // TM or HE } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index c95b8e362..2ebd3a270 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -289,12 +289,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { bgra.CopyTo(encodedData); bool useCache = true; - this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || - crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; - this.UseSubtractGreenTransform = crunchConfig.EntropyIdx == EntropyIx.SubGreen || - crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen; - this.UsePredictorTransform = crunchConfig.EntropyIdx == EntropyIx.Spatial || - crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen; + this.UsePalette = crunchConfig.EntropyIdx is EntropyIx.Palette or EntropyIx.PaletteAndSpatial; + this.UseSubtractGreenTransform = crunchConfig.EntropyIdx is EntropyIx.SubGreen or EntropyIx.SpatialSubGreen; + this.UsePredictorTransform = crunchConfig.EntropyIdx is EntropyIx.Spatial or EntropyIx.SpatialSubGreen; if (lowEffort) { this.UseCrossColorTransform = false; @@ -427,7 +424,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.TransformBits = GetTransformBits(this.method, this.HistoBits); // Try out multiple LZ77 on images with few colors. - int nlz77s = this.PaletteSize > 0 && this.PaletteSize <= 16 ? 2 : 1; + int nlz77s = this.PaletteSize is > 0 and <= 16 ? 2 : 1; EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); bool doNotCache = false; @@ -863,7 +860,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless while (i-- > 0) { int ix = tokens[i].Code; - if (ix == 0 || ix == 17 || ix == 18) + if (ix is 0 or 17 or 18) { trimmedLength--; // Discount trailing zeros. trailingZeroBits += codeLengthBitDepth[ix]; @@ -1345,7 +1342,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - if (i == 0 || i == 1 || i == 2) + if (i is 0 or 1 or 2) { ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); } From d72fbb5783271ca094997ec30b1d73d25ed598e6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 18:51:58 +0200 Subject: [PATCH 352/359] Use TransparentColorMode enum --- .../Formats/Webp/IWebpEncoderOptions.cs | 4 ++-- .../Formats/Webp/Lossless/PredictorEncoder.cs | 18 ++++++++-------- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 11 +++++----- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 6 +++--- .../Formats/Webp/WebpTransparentColorMode.cs | 21 +++++++++++++++++++ .../Formats/WebP/WebpEncoderTests.cs | 4 ++-- 7 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 6abf34483..63d76a598 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -59,9 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. - /// The default value is false. + /// The default value is Clear. /// - bool Exact { get; } + WebpTransparentColorMode TransparentColorMode { get; } /// /// Gets a value indicating whether near lossless mode should be used. diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 0858a3d3f..671e9a043 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span image, bool nearLossless, int nearLosslessQuality, - bool exact, + WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool lowEffort) { @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bgraScratch, bgra, maxQuantization, - exact, + transparentColorMode, usedSubtractGreen, nearLossless, image); @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bgraScratch, bgra, maxQuantization, - exact, + transparentColorMode, usedSubtractGreen, nearLossless, lowEffort); @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span argbScratch, Span argb, int maxQuantization, - bool exact, + WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool nearLossless, Span modes) @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, nearLossless, residuals); + GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, residuals); for (int relativeX = 0; relativeX < maxX; ++relativeX) { UpdateHisto(histoArgb, residuals[relativeX]); @@ -330,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int xEnd, int y, int maxQuantization, - bool exact, + WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool nearLossless, Span output) { - if (exact) + if (transparentColorMode == WebpTransparentColorMode.Preserve) { PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output); } @@ -568,7 +568,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span argbScratch, Span argb, int maxQuantization, - bool exact, + WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool nearLossless, bool lowEffort) @@ -631,7 +631,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless xEnd, y, maxQuantization, - exact, + transparentColorMode, usedSubtractGreen, nearLossless, argb.Slice((y * width) + x)); diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 2ebd3a270..693585637 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. /// - private readonly bool exact; + private readonly WebpTransparentColorMode transparentColorMode; /// /// Indicating whether near lossless mode should be used. @@ -85,7 +85,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). - /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. + /// Flag indicating whether to preserve the exact RGB values under transparent area. + /// Otherwise, discard this invisible RGB information for better compression. /// Indicating whether near lossless mode should be used. /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). public Vp8LEncoder( @@ -95,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int height, int quality, WebpEncodingMethod method, - bool exact, + WebpTransparentColorMode transparentColorMode, bool nearLossless, int nearLosslessQuality) { @@ -106,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); this.method = method; - this.exact = exact; + this.transparentColorMode = transparentColorMode; this.nearLossless = nearLossless; this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); this.bitWriter = new Vp8LBitWriter(initialSize); @@ -676,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.TransformData.GetSpan(), this.nearLossless, nearLosslessStrength, - this.exact, + this.transparentColorMode, this.UseSubtractGreenTransform, lowEffort); diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 1eb7b3846..1b1dab784 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public int FilterStrength { get; set; } = 60; /// - public bool Exact { get; set; } + public WebpTransparentColorMode TransparentColorMode { get; set; } = WebpTransparentColorMode.Clear; /// public bool NearLossless { get; set; } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 57c696c96..ff0246cdd 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. /// - private readonly bool exact; + private readonly WebpTransparentColorMode transparentColorMode; /// /// Indicating whether near lossless mode should be used. @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.entropyPasses = options.EntropyPasses; this.spatialNoiseShaping = options.SpatialNoiseShaping; this.filterStrength = options.FilterStrength; - this.exact = options.Exact; + this.transparentColorMode = options.TransparentColorMode; this.nearLossless = options.NearLossless; this.nearLosslessQuality = options.NearLosslessQuality; } @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Webp image.Height, this.quality, this.method, - this.exact, + this.transparentColorMode, this.nearLossless, this.nearLosslessQuality); enc.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs b/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs new file mode 100644 index 000000000..993033b80 --- /dev/null +++ b/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Webp +{ + /// + /// Enum indicating how the transparency should be handled on encoding. + /// + public enum WebpTransparentColorMode + { + /// + /// Discard the transparency information for better compression. + /// + Clear = 0, + + /// + /// The transparency will be kept as is. + /// + Preserve = 1, + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 6a0e599d0..70bf3e66c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -111,14 +111,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] - public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, WebpEncodingMethod method) + public void Encode_Lossless_WithPreserveTransparentColor_Works(TestImageProvider provider, WebpEncodingMethod method) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() { Lossy = false, Method = method, - Exact = true + TransparentColorMode = WebpTransparentColorMode.Preserve }; using Image image = provider.GetImage(); From 27a867d0b6d2f708fb2c599c8145901b3809d122 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 20 Oct 2021 19:04:46 +0200 Subject: [PATCH 353/359] Remove unknown enum value --- src/ImageSharp/Formats/Webp/WebpFormatType.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/WebpFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFormatType.cs index fdeb1447e..5ef47f6e0 100644 --- a/src/ImageSharp/Formats/Webp/WebpFormatType.cs +++ b/src/ImageSharp/Formats/Webp/WebpFormatType.cs @@ -8,11 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public enum WebpFormatType { - /// - /// Unknown webp format. - /// - Unknown, - /// /// The lossless webp format. /// From 8520b07418a08b76ad0646ab5d2ecd5ab8aeb75d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 21 Oct 2021 15:32:20 +0200 Subject: [PATCH 354/359] Make WebpFormatType nullable --- src/ImageSharp/Formats/Webp/WebpMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 001ff1fbb..6c44688b2 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Gets or sets the webp format used. Either lossless or lossy. /// - public WebpFormatType Format { get; set; } + public WebpFormatType? Format { get; set; } /// public IDeepCloneable DeepClone() => new WebpMetadata(this); From fa8c590e0eb05f44094e170ac3579c30cbc73845 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Oct 2021 15:14:34 +0200 Subject: [PATCH 355/359] Skip tests if Sse2 is not supported --- tests/ImageSharp.Tests/Common/SimdUtilsTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 1f680aa6c..3c50b3652 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -211,6 +211,11 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(ArraySizesDivisibleBy32))] public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count) { + if (!Sse2.IsSupported) + { + return; + } + static void RunTest(string serialized) { TestImpl_BulkConvertByteToNormalizedFloat( @@ -304,6 +309,11 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(ArraySizesDivisibleBy32))] public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) { + if (!Sse2.IsSupported) + { + return; + } + static void RunTest(string serialized) { TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( From 48c0a262819ef7a2b9e34ecc13d77def1fbb910c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Oct 2021 15:14:54 +0200 Subject: [PATCH 356/359] Skip test if Avx is not supported --- tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index b4d3769d7..0a49d20cd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -121,6 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!Avx.IsSupported) { this.Output.WriteLine("No AVX present, skipping test!"); + return; } Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); From 1e299beed6aac97308c520a216e0557b333f9d2a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Oct 2021 15:21:17 +0200 Subject: [PATCH 357/359] A little cleanup --- .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 84 ++++++------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 3c50b3652..f61e2dc8e 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -19,10 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Common { private ITestOutputHelper Output { get; } - public SimdUtilsTests(ITestOutputHelper output) - { - this.Output = output; - } + public SimdUtilsTests(ITestOutputHelper output) => this.Output = output; private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); @@ -63,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Common private static Vector CreateRandomTestVector(int seed, float min, float max) { - var data = new float[Vector.Count]; + float[] data = new float[Vector.Count]; var rnd = new Random(seed); @@ -154,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Common float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); - var dest = new byte[count]; + byte[] dest = new byte[count]; SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(source, dest); @@ -163,25 +160,18 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected, dest); } - public static readonly TheoryData ArraySizesDivisibleBy8 = new TheoryData { 0, 8, 16, 1024 }; - public static readonly TheoryData ArraySizesDivisibleBy4 = new TheoryData { 0, 4, 8, 28, 1020 }; - public static readonly TheoryData ArraySizesDivisibleBy3 = new TheoryData { 0, 3, 9, 36, 957 }; - public static readonly TheoryData ArraySizesDivisibleBy32 = new TheoryData { 0, 32, 512 }; + public static readonly TheoryData ArraySizesDivisibleBy8 = new() { 0, 8, 16, 1024 }; + public static readonly TheoryData ArraySizesDivisibleBy4 = new() { 0, 4, 8, 28, 1020 }; + public static readonly TheoryData ArraySizesDivisibleBy3 = new() { 0, 3, 9, 36, 957 }; + public static readonly TheoryData ArraySizesDivisibleBy32 = new() { 0, 32, 512 }; - public static readonly TheoryData ArbitraryArraySizes = - new TheoryData - { - 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, - }; + public static readonly TheoryData ArbitraryArraySizes = new() { 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 }; [Theory] [MemberData(nameof(ArraySizesDivisibleBy4))] - public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) - { - TestImpl_BulkConvertByteToNormalizedFloat( + public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( count, (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); - } [Theory] [MemberData(nameof(ArraySizesDivisibleBy8))] @@ -199,12 +189,9 @@ namespace SixLabors.ImageSharp.Tests.Common [Theory] [MemberData(nameof(ArraySizesDivisibleBy32))] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) - { - TestImpl_BulkConvertByteToNormalizedFloat( + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( count, (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); - } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] @@ -216,12 +203,9 @@ namespace SixLabors.ImageSharp.Tests.Common return; } - static void RunTest(string serialized) - { - TestImpl_BulkConvertByteToNormalizedFloat( + static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFloat( FeatureTestRunner.Deserialize(serialized), (s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); - } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, @@ -232,20 +216,17 @@ namespace SixLabors.ImageSharp.Tests.Common [Theory] [MemberData(nameof(ArbitraryArraySizes))] - public void BulkConvertByteToNormalizedFloat(int count) - { - TestImpl_BulkConvertByteToNormalizedFloat( + public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( count, (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); - } private static void TestImpl_BulkConvertByteToNormalizedFloat( int count, Action, Memory> convert) { byte[] source = new Random(count).GenerateRandomByteArray(count); - var result = new float[count]; - float[] expected = source.Select(b => (float)b / 255f).ToArray(); + float[] result = new float[count]; + float[] expected = source.Select(b => b / 255f).ToArray(); convert(source, result); @@ -254,12 +235,9 @@ namespace SixLabors.ImageSharp.Tests.Common [Theory] [MemberData(nameof(ArraySizesDivisibleBy4))] - public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( count, (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); - } [Theory] [MemberData(nameof(ArraySizesDivisibleBy8))] @@ -275,12 +253,9 @@ namespace SixLabors.ImageSharp.Tests.Common [Theory] [MemberData(nameof(ArraySizesDivisibleBy32))] - public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( count, (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); - } [Theory] [InlineData(1234)] @@ -314,12 +289,9 @@ namespace SixLabors.ImageSharp.Tests.Common return; } - static void RunTest(string serialized) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + static void RunTest(string serialized) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( FeatureTestRunner.Deserialize(serialized), (s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); - } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, @@ -336,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests.Common TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); // For small values, let's stress test the implementation a bit: - if (count > 0 && count < 10) + if (count is > 0 and < 10) { for (int i = 0; i < 20; i++) { @@ -350,23 +322,17 @@ namespace SixLabors.ImageSharp.Tests.Common [Theory] [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgb24(int count) - { - TestPackFromRgbPlanes( + public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( count, (r, g, b, actual) => SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual)); - } [Theory] [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgba32(int count) - { - TestPackFromRgbPlanes( + public void PackFromRgbPlanes_Rgba32(int count) => TestPackFromRgbPlanes( count, (r, g, b, actual) => SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual)); - } #if SUPPORTS_RUNTIME_INTRINSICS [Fact] @@ -381,7 +347,7 @@ namespace SixLabors.ImageSharp.Tests.Common byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); const int padding = 4; - Rgb24[] d = new Rgb24[32 + padding]; + var d = new Rgb24[32 + padding]; ReadOnlySpan rr = r.AsSpan(); ReadOnlySpan gg = g.AsSpan(); @@ -415,7 +381,7 @@ namespace SixLabors.ImageSharp.Tests.Common byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); - Rgba32[] d = new Rgba32[32]; + var d = new Rgba32[32]; ReadOnlySpan rr = r.AsSpan(); ReadOnlySpan gg = g.AsSpan(); @@ -442,18 +408,18 @@ namespace SixLabors.ImageSharp.Tests.Common internal static void TestPackFromRgbPlanes(int count, Action packMethod) where TPixel : unmanaged, IPixel { - Random rnd = new Random(42); + var rnd = new Random(42); byte[] r = rnd.GenerateRandomByteArray(count); byte[] g = rnd.GenerateRandomByteArray(count); byte[] b = rnd.GenerateRandomByteArray(count); - TPixel[] expected = new TPixel[count]; + var expected = new TPixel[count]; for (int i = 0; i < count; i++) { expected[i].FromRgb24(new Rgb24(r[i], g[i], b[i])); } - TPixel[] actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 + var actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 packMethod(r, g, b, actual); Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan().Slice(0, count))); From bc4e2f7237a7eb220e951987a2cd58759c9a184e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 22 Oct 2021 18:28:13 +0200 Subject: [PATCH 358/359] Preserve lossy/lossless encoding, if input image was webp --- .../Formats/Webp/IWebpEncoderOptions.cs | 2 +- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 17 +++++++++------ .../Formats/WebP/PredictorEncoderTests.cs | 2 +- .../Formats/WebP/WebpEncoderTests.cs | 21 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 +-- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 63d76a598..5059ac73e 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Gets a value indicating whether lossy compression should be used. /// If false, lossless compression will be used. /// - bool Lossy { get; } + bool? Lossy { get; } /// /// Gets the compression quality. Between 0 and 100. diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 1b1dab784..1667eb1db 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions { /// - public bool Lossy { get; set; } + public bool? Lossy { get; set; } /// public int Quality { get; set; } = 75; diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index ff0246cdd..3be5409b1 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -4,9 +4,11 @@ using System.IO; using System.Threading; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp @@ -27,11 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly bool alphaCompression; - /// - /// Indicating whether lossy compression should be used. If false, lossless compression will be used. - /// - private readonly bool lossy; - /// /// Compression quality. Between 0 and 100. /// @@ -73,6 +70,11 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly int nearLosslessQuality; + /// + /// Indicating whether lossy compression should be used. If false, lossless compression will be used. + /// + private bool? lossy; + /// /// The global configuration. /// @@ -112,8 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Webp Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + WebpMetadata webpMetadata = metadata.GetWebpMetadata(); + this.lossy ??= webpMetadata.Format == WebpFormatType.Lossy; - if (this.lossy) + if (this.lossy.GetValueOrDefault()) { using var enc = new Vp8Encoder( this.memoryAllocator, diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 80d48d0ca..b48020198 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }; // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossless.BikeSmall)); + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); using var image = Image.Load(imgBytes, new WebpDecoder()); uint[] bgra = ToBgra(image); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 70bf3e66c..de5a6171a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -3,6 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -14,6 +15,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpEncoderTests { + [Theory] + [WithFile(Flag, PixelTypes.Rgba32, WebpFormatType.Lossless)] // if its not a webp input image, it should default to lossless. + [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFormatType.Lossless)] + [WithFile(Lossy.Bike, PixelTypes.Rgba32, WebpFormatType.Lossy)] + public void Encode_PreserveRatio(TestImageProvider provider, WebpFormatType expectedFormat) + where TPixel : unmanaged, IPixel + { + var options = new WebpEncoder(); + using Image input = provider.GetImage(); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, options); + + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); + + ImageMetadata meta = output.Metadata; + WebpMetadata webpMetaData = meta.GetWebpMetadata(); + Assert.Equal(expectedFormat, webpMetaData.Format); + } + [Theory] [WithFile(Flag, PixelTypes.Rgba32)] [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a9af84f9b..116c5adc3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -603,8 +603,6 @@ namespace SixLabors.ImageSharp.Tests // substract_green, predictor, cross_color public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; - public const string BikeSmall = "Webp/bike_lossless_small.webp"; - // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. @@ -621,6 +619,7 @@ namespace SixLabors.ImageSharp.Tests public const string Earth = "Webp/earth_lossy.webp"; public const string WithExif = "Webp/exif_lossy.webp"; public const string WithIccp = "Webp/lossy_with_iccp.webp"; + public const string BikeSmall = "Webp/bike_lossless_small.webp"; // Lossy images without macroblock filtering. public const string Bike = "Webp/bike_lossy.webp"; From 853c40c23d17423dd33c3224cdad2edbce734dd8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 23 Oct 2021 22:30:30 +1100 Subject: [PATCH 359/359] Use the same property type for metadata & encoder --- .../Formats/Webp/IWebpEncoderOptions.cs | 5 ++- .../Formats/Webp/WebpDecoderCore.cs | 4 +-- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 21 ++++++++---- ...ebpFormatType.cs => WebpFileFormatType.cs} | 4 +-- src/ImageSharp/Formats/Webp/WebpMetadata.cs | 6 ++-- .../Codecs/EncodeWebp.cs | 4 +-- .../Formats/WebP/WebpEncoderTests.cs | 34 +++++++++---------- .../Formats/WebP/WebpMetaDataTests.cs | 6 ++-- .../Profiles/Exif/ExifProfileTests.cs | 8 ++--- 10 files changed, 50 insertions(+), 44 deletions(-) rename src/ImageSharp/Formats/Webp/{WebpFormatType.cs => WebpFileFormatType.cs} (82%) diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 5059ac73e..7dbf49d45 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -9,10 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Webp internal interface IWebpEncoderOptions { /// - /// Gets a value indicating whether lossy compression should be used. - /// If false, lossless compression will be used. + /// Gets the webp file format used. Either lossless or lossy. /// - bool? Lossy { get; } + WebpFileFormatType? FileFormat { get; } /// /// Gets the compression quality. Between 0 and 100. diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 4a5a15b1c..44a55a4c6 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Information about this webp image. private WebpImageInfo ReadVp8Header(WebpFeatures features = null) { - this.webpMetadata.Format = WebpFormatType.Lossy; + this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; // VP8 data size (not including this 4 bytes). this.currentStream.Read(this.buffer, 0, 4); @@ -367,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Information about this image. private WebpImageInfo ReadVp8LHeader(WebpFeatures features = null) { - this.webpMetadata.Format = WebpFormatType.Lossless; + this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; // VP8 data size. uint imageDataSize = this.ReadChunkSize(); diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 1667eb1db..f85f65b63 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions { /// - public bool? Lossy { get; set; } + public WebpFileFormatType? FileFormat { get; set; } /// public int Quality { get; set; } = 75; diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 3be5409b1..a61fc7253 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -71,9 +71,9 @@ namespace SixLabors.ImageSharp.Formats.Webp private readonly int nearLosslessQuality; /// - /// Indicating whether lossy compression should be used. If false, lossless compression will be used. + /// Indicating what file format compression should be used. /// - private bool? lossy; + private readonly WebpFileFormatType? fileFormat; /// /// The global configuration. @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { this.memoryAllocator = memoryAllocator; this.alphaCompression = options.UseAlphaCompression; - this.lossy = options.Lossy; + this.fileFormat = options.FileFormat; this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; @@ -114,11 +114,18 @@ namespace SixLabors.ImageSharp.Formats.Webp Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - WebpMetadata webpMetadata = metadata.GetWebpMetadata(); - this.lossy ??= webpMetadata.Format == WebpFormatType.Lossy; + bool lossy; + if (this.fileFormat is not null) + { + lossy = this.fileFormat == WebpFileFormatType.Lossy; + } + else + { + WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); + lossy = webpMetadata.FileFormat == WebpFileFormatType.Lossy; + } - if (this.lossy.GetValueOrDefault()) + if (lossy) { using var enc = new Vp8Encoder( this.memoryAllocator, diff --git a/src/ImageSharp/Formats/Webp/WebpFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs similarity index 82% rename from src/ImageSharp/Formats/Webp/WebpFormatType.cs rename to src/ImageSharp/Formats/Webp/WebpFileFormatType.cs index 5ef47f6e0..c485f0969 100644 --- a/src/ImageSharp/Formats/Webp/WebpFormatType.cs +++ b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs @@ -4,9 +4,9 @@ namespace SixLabors.ImageSharp.Formats.Webp { /// - /// Info about the webp format used. + /// Info about the webp file format used. /// - public enum WebpFormatType + public enum WebpFileFormatType { /// /// The lossless webp format. diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 6c44688b2..f398d3d87 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private WebpMetadata(WebpMetadata other) => this.Format = other.Format; + private WebpMetadata(WebpMetadata other) => this.FileFormat = other.FileFormat; /// - /// Gets or sets the webp format used. Either lossless or lossy. + /// Gets or sets the webp file format used. Either lossless or lossy. /// - public WebpFormatType? Format { get; set; } + public WebpFileFormatType? FileFormat { get; set; } /// public IDeepCloneable DeepClone() => new WebpMetadata(this); diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 8f3869a52..7d3dfe693 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using var memoryStream = new MemoryStream(); this.webp.Save(memoryStream, new WebpEncoder() { - Lossy = true + FileFormat = WebpFileFormatType.Lossy }); } @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using var memoryStream = new MemoryStream(); this.webp.Save(memoryStream, new WebpEncoder() { - Lossy = false + FileFormat = WebpFileFormatType.Lossless }); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index de5a6171a..70cc487bf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public class WebpEncoderTests { [Theory] - [WithFile(Flag, PixelTypes.Rgba32, WebpFormatType.Lossless)] // if its not a webp input image, it should default to lossless. - [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFormatType.Lossless)] - [WithFile(Lossy.Bike, PixelTypes.Rgba32, WebpFormatType.Lossy)] - public void Encode_PreserveRatio(TestImageProvider provider, WebpFormatType expectedFormat) + [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] // if its not a webp input image, it should default to lossless. + [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] + [WithFile(Lossy.Bike, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] + public void Encode_PreserveRatio(TestImageProvider provider, WebpFileFormatType expectedFormat) where TPixel : unmanaged, IPixel { var options = new WebpEncoder(); @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp ImageMetadata meta = output.Metadata; WebpMetadata webpMetaData = meta.GetWebpMetadata(); - Assert.Equal(expectedFormat, webpMetaData.Format); + Assert.Equal(expectedFormat, webpMetaData.FileFormat); } [Theory] @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = false, + FileFormat = WebpFileFormatType.Lossless, Quality = 100, Method = WebpEncodingMethod.BestQuality }; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = false, + FileFormat = WebpFileFormatType.Lossless, Quality = quality }; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = false, + FileFormat = WebpFileFormatType.Lossless, Method = method, Quality = quality }; @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = false, + FileFormat = WebpFileFormatType.Lossless, NearLossless = true, NearLosslessQuality = nearLosslessQuality }; @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = false, + FileFormat = WebpFileFormatType.Lossless, Method = method, TransparentColorMode = WebpTransparentColorMode.Preserve }; @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using Image image = provider.GetImage(); - var encoder = new WebpEncoder() { Lossy = false }; + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; image.VerifyEncoder(provider, "webp", string.Empty, encoder); } @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. using var image = new Image(1, 1); - var encoder = new WebpEncoder() { Lossy = false }; + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; using (var memStream = new MemoryStream()) { image.SaveAsWebp(memStream, encoder); @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = true, + FileFormat = WebpFileFormatType.Lossy, Quality = quality }; @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = true, + FileFormat = WebpFileFormatType.Lossy, FilterStrength = filterStrength }; @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = true, + FileFormat = WebpFileFormatType.Lossy, SpatialNoiseShaping = snsStrength }; @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { var encoder = new WebpEncoder() { - Lossy = true, + FileFormat = WebpFileFormatType.Lossy, Method = method, Quality = quality }; @@ -267,7 +267,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { using Image image = provider.GetImage(); - var encoder = new WebpEncoder() { Lossy = true }; + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 1b0dc3e3f..81067a41f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpMetaDataTests { - private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; + private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; [Theory] [WithFile(TestImages.Webp.Lossy.WithExif, PixelTypes.Rgba32, false)] @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp ExifProfile expectedExif = input.Metadata.ExifProfile; // act - input.Save(memoryStream, new WebpEncoder() { Lossy = true }); + input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }); memoryStream.Position = 0; // assert @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp ExifProfile expectedExif = input.Metadata.ExifProfile; // act - input.Save(memoryStream, new WebpEncoder() { Lossy = false }); + input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }); memoryStream.Position = 0; // assert diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index b14938ca8..ebc096852 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -506,9 +506,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif case TestImageWriteFormat.Png: return WriteAndReadPng(image); case TestImageWriteFormat.WebpLossless: - return WriteAndReadWebp(image, lossy: false); + return WriteAndReadWebp(image, WebpFileFormatType.Lossless); case TestImageWriteFormat.WebpLossy: - return WriteAndReadWebp(image, lossy: true); + return WriteAndReadWebp(image, WebpFileFormatType.Lossy); default: throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); } @@ -538,11 +538,11 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif } } - private static Image WriteAndReadWebp(Image image, bool lossy) + private static Image WriteAndReadWebp(Image image, WebpFileFormatType fileFormat) { using (var memStream = new MemoryStream()) { - image.SaveAsWebp(memStream, new WebpEncoder() { Lossy = lossy }); + image.SaveAsWebp(memStream, new WebpEncoder() { FileFormat = fileFormat }); image.Dispose(); memStream.Position = 0; /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 6811c9dd9..b517a06f3 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -602,7 +602,6 @@ namespace SixLabors.ImageSharp.Formats.WebP int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { - // TODO: not sure, if this should be treated as an error here return; } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index b82a79dae..ac5981600 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -207,8 +206,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { // Hardcoded 16x16 intra-mode decision tree. int yMode = this.bitReader.GetBit(156) != 0 ? - this.bitReader.GetBit(128) != 0 ? WebPConstants.TmPred : WebPConstants.HPred : - this.bitReader.GetBit(163) != 0 ? WebPConstants.VPred : WebPConstants.DcPred; + this.bitReader.GetBit(128) != 0 ? (int)IntraPredictionMode.TrueMotion : (int)IntraPredictionMode.HPrediction : + this.bitReader.GetBit(163) != 0 ? (int)IntraPredictionMode.VPrediction : (int)IntraPredictionMode.DcPrediction; block.Modes[0] = (byte)yMode; for (int i = 0; i < left.Length; i++) { @@ -864,7 +863,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int dstOffset = 0; Vp8MacroBlockData block = dec.CurrentBlockData; Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; - Vp8BandProbas[,] bands = dec.Probabilities.BandsPtr; + Vp8BandProbas[][] bands = dec.Probabilities.BandsPtr; Vp8BandProbas[] acProba; Vp8MacroBlock leftMb = dec.LeftMacroBlock; short[] dst = block.Coeffs; @@ -876,14 +875,14 @@ namespace SixLabors.ImageSharp.Formats.WebP if (block.IsI4x4) { first = 0; - acProba = GetBandsRow(bands, 3); + acProba = bands[3]; } else { // Parse DC var dc = new short[16]; int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); - int nz = this.GetCoeffs(br, GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc); + int nz = this.GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); if (nz > 1) { @@ -901,7 +900,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } first = 1; - acProba = GetBandsRow(bands, 0); + acProba = bands[0]; } byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); @@ -941,7 +940,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int x = 0; x < 2; ++x) { int ctx = l + (tnz & 1); - int nz = this.GetCoeffs(br, GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); + int nz = this.GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); l = (nz > 0) ? 1 : 0; tnz = (byte)((tnz >> 1) | (l << 3)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); @@ -1164,11 +1163,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.Level = (int)this.bitReader.ReadValue(6); + vp8FilterHeader.FilterLevel = (int)this.bitReader.ReadValue(6); vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); - dec.Filter = (vp8FilterHeader.Level == 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; + dec.Filter = (vp8FilterHeader.FilterLevel == 0) ? LoopFilter.None : vp8FilterHeader.LoopFilter; if (vp8FilterHeader.UseLfDelta) { // Update lf-delta? @@ -1314,7 +1313,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int b = 0; b < 16 + 1; ++b) { - proba.BandsPtr[t, b] = proba.Bands[t, WebPConstants.Bands[b]]; + proba.BandsPtr[t][b] = proba.Bands[t, WebPConstants.Bands[b]]; } } @@ -1391,13 +1390,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return nzCoeffs; } - [MethodImpl(InliningOptions.ShortMethod)] - private static Vp8BandProbas[] GetBandsRow(Vp8BandProbas[,] bands, int rowIdx) - { - Vp8BandProbas[] bandsRow = Enumerable.Range(0, bands.GetLength(1)).Select(x => bands[rowIdx, x]).ToArray(); - return bandsRow; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int CheckMode(int mbx, int mby, int mode) { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f285dbc94..1dc135a6b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -506,10 +506,10 @@ namespace SixLabors.ImageSharp.Tests // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." - public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. - public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. - public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor - public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. + public const string LossLessCorruptImage1 = "WebP/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. + public const string LossLessCorruptImage2 = "WebP/lossless_vec_2_7.webp"; // color_indexing, predictor. + public const string LossLessCorruptImage3 = "WebP/lossless_color_transform.webp"; // cross_color, predictor + public const string LossLessCorruptImage4 = "WebP/near_lossless_75.webp"; // predictor, cross_color. } public static class Lossy diff --git a/tests/Images/Input/WebP/grid.png b/tests/Images/Input/WebP/grid.png deleted file mode 100644 index 33f6ac334..000000000 --- a/tests/Images/Input/WebP/grid.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c53fb4527509058a8a4caf72e03ee8634f4704ab5369a8e5d194e62359d6ad0 -size 117 diff --git a/tests/Images/Input/WebP/lossless_with_iccp.webp b/tests/Images/Input/WebP/lossless_with_iccp.webp index a99d2686f..585c6924c 100644 --- a/tests/Images/Input/WebP/lossless_with_iccp.webp +++ b/tests/Images/Input/WebP/lossless_with_iccp.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:312aea5ac9557bdfa78ec95bab5c3446a97c980317f46c96a20a4b7837d0ae37 -size 68355 +oid sha256:3cdb75584ac3db92d78c2c1ec828cb813d280540e5f1bb262ba0b2c352900f81 +size 69076 diff --git a/tests/Images/Input/WebP/peak.png b/tests/Images/Input/WebP/peak.png deleted file mode 100644 index 5a417b9c0..000000000 --- a/tests/Images/Input/WebP/peak.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9b56ed5c1278664222c77f9a452b824b4f9215c819502b3f6b0e0d44270e7e7 -size 26456 From 62d475506f385fd6e084cc81f269190cd86fd63b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2020 15:13:01 +0200 Subject: [PATCH 163/359] Review changes --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 4 +- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 6 ++- .../Formats/WebP/WebPDecoderCore.cs | 11 +++-- .../Formats/WebP/WebPLosslessDecoder.cs | 23 ++++++++-- .../Formats/WebP/WebPLossyDecoder.cs | 8 ++-- .../Codecs/DecodeWebp.cs | 44 ++++++------------- 6 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 756c1d645..7a23ad6e5 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -273,10 +273,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int bitsPerPixel = 8 >> transform.Bits; int width = transform.XSize; Span colorMap = transform.Data.Memory.Span; - int srcOffset = 0; - int dstOffset = 0; if (bitsPerPixel < 8) { + int srcOffset = 0; + int dstOffset = 0; int pixelsPerByte = 1 << transform.Bits; int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index f49000c6e..6279a5d12 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -279,14 +279,16 @@ namespace SixLabors.ImageSharp.Formats.WebP newColorMap[0] = transformData[0]; Span data = MemoryMarshal.Cast(transformData); Span newData = MemoryMarshal.Cast(newColorMap); + int numColorsX4 = 4 * numColors; int i; - for (i = 4; i < 4 * numColors; ++i) + for (i = 4; i < numColorsX4; i++) { // Equivalent to AddPixelEq(), on a byte-basis. newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); } - for (; i < 4 * newColorMap.Length; ++i) + int colorMapLength4 = 4 * newColorMap.Length; + for (; i < colorMapLength4; i++) { newData[i] = 0; // black tail. } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 180c7c960..b8d450c33 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -235,7 +235,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo() { Width = width, Height = height, Features = features }; } - // TODO: check if VP8 or VP8L info about the dimensions match VP8X info switch (chunkType) { case WebPChunkType.Vp8: @@ -415,7 +414,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { case WebPChunkType.Iccp: uint iccpChunkSize = this.ReadChunkSize(); - if (!this.IgnoreMetadata) + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)iccpChunkSize); + } + else { var iccpData = new byte[iccpChunkSize]; this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); @@ -425,10 +428,6 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Metadata.IccProfile = profile; } } - else - { - this.currentStream.Skip((int)iccpChunkSize); - } break; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index b517a06f3..cbe2fec0f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -929,9 +929,17 @@ namespace SixLabors.ImageSharp.Formats.WebP return (dist >= 1) ? dist : 1; } + /// + /// Copies pixels when a backward reference is used. + /// Copy 'length' number of pixels (in scan-line order) from the sequence of pixels prior to them by 'dist' pixels. + /// + /// The pixel data. + /// The number of so far decoded pixels. + /// The backward reference distance prior to the current decoded pixel. + /// The number of pixels to copy. private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { - if (dist >= length) + if (dist >= length) // no overlap. { Span src = pixelData.Slice(decodedPixels - dist, length); Span dest = pixelData.Slice(decodedPixels); @@ -939,18 +947,27 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { + // There is overlap between the backward reference distance and the pixels to copy. Span src = pixelData.Slice(decodedPixels - dist); Span dest = pixelData.Slice(decodedPixels); - for (int i = 0; i < length; ++i) + for (int i = 0; i < length; i++) { dest[i] = src[i]; } } } + /// + /// Copies alpha values when a backward reference is used. + /// Copy 'length' number of alpha values from the sequence of alpha values prior to them by 'dist'. + /// + /// The alpha values. + /// The position of the so far decoded pixels. + /// The backward reference distance prior to the current decoded pixel. + /// The number of pixels to copy. private static void CopyBlock8B(Span data, int pos, int dist, int length) { - if (dist >= length) + if (dist >= length) // no overlap. { data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index ac5981600..1c524b83b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -699,15 +699,17 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Loop over each output pairs of row. + var bufferStride2 = 2 * bufferStride; + var ioStride2 = 2 * io.YStride; for (; y + 2 < yEnd; y += 2) { topU = curU; topV = curV; curU = curU.Slice(io.UvStride); curV = curV.Slice(io.UvStride); - this.UpSample(curY.Slice(io.YStride), curY.Slice(2 * io.YStride), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(2 * bufferStride), mbw); - curY = curY.Slice(2 * io.YStride); - dst = dst.Slice(2 * bufferStride); + this.UpSample(curY.Slice(io.YStride), curY.Slice(ioStride2), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(bufferStride2), mbw); + curY = curY.Slice(ioStride2); + dst = dst.Slice(bufferStride2); } // Move to last row. diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 0bd0c4e8d..b6631727d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public class DecodeWebp : BenchmarkBase { private byte[] webpLossyBytes; + private byte[] webpLosslessBytes; private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); @@ -30,59 +31,40 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void ReadImages() { - if (this.webpLossyBytes is null) - { - this.webpLossyBytes = File.ReadAllBytes(this.TestImageLossyFullPath); - } - - if (this.webpLosslessBytes is null) - { - this.webpLosslessBytes = File.ReadAllBytes(this.TestImageLosslessFullPath); - } + this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); + this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); } [Benchmark(Description = "Magick Lossy WebP")] public int WebpLossyMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using (var image = new MagickImage(new MemoryStream(this.webpLossyBytes), settings)) - { - return image.Width; - } + using var image = new MagickImage(new MemoryStream(this.webpLossyBytes), settings); + return image.Width; } [Benchmark(Description = "ImageSharp Lossy Webp")] public int WebpLossy() { - using (var memoryStream = new MemoryStream(this.webpLossyBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return image.Height; - } - } + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = Image.Load(memoryStream); + return image.Height; } [Benchmark(Description = "Magick Lossless WebP")] public int WebpLosslessMagick() { var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using (var image = new MagickImage(new MemoryStream(this.webpLosslessBytes), settings)) - { - return image.Width; - } + using var image = new MagickImage(new MemoryStream(this.webpLosslessBytes), settings); + return image.Width; } [Benchmark(Description = "ImageSharp Lossless Webp")] public int WebpLossless() { - using (var memoryStream = new MemoryStream(this.webpLosslessBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return image.Height; - } - } + using var memoryStream = new MemoryStream(this.webpLosslessBytes); + using var image = Image.Load(memoryStream); + return image.Height; } /* Results 18.03.2020 From d3e72142465c020df19954ab6ab1846e9df5d0cc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2020 17:01:51 +0200 Subject: [PATCH 164/359] Move lossy and lossless related files into separate folders --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 2 ++ .../Formats/WebP/{ => BitReader}/BitReaderBase.cs | 2 +- .../Formats/WebP/{ => BitReader}/Vp8BitReader.cs | 2 +- .../Formats/WebP/{ => BitReader}/Vp8LBitReader.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossless}/ColorCache.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossless}/HTreeGroup.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossless}/HuffIndex.cs | 2 +- .../Formats/WebP/{ => Lossless}/HuffmanCode.cs | 2 +- .../Formats/WebP/{ => Lossless}/HuffmanUtils.cs | 2 +- .../Formats/WebP/{ => Lossless}/LosslessUtils.cs | 2 +- .../Formats/WebP/{ => Lossless}/Vp8LDecoder.cs | 2 +- .../Formats/WebP/{ => Lossless}/Vp8LMetadata.cs | 2 +- .../Formats/WebP/{ => Lossless}/Vp8LTransform.cs | 2 +- .../Formats/WebP/{ => Lossless}/Vp8LTransformType.cs | 2 +- .../Formats/WebP/{ => Lossless}/WebPLosslessDecoder.cs | 9 ++++++--- .../Formats/WebP/{ => Lossy}/IntraPredictionMode.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/LoopFilter.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/LossyUtils.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/VP8BandProbas.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Decoder.cs | 3 ++- .../Formats/WebP/{ => Lossy}/Vp8FilterHeader.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8FilterInfo.cs | 2 +- .../Formats/WebP/{ => Lossy}/Vp8FrameHeader.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Io.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8MacroBlock.cs | 2 +- .../Formats/WebP/{ => Lossy}/Vp8MacroBlockData.cs | 2 +- .../Formats/WebP/{ => Lossy}/Vp8PictureHeader.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Proba.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8ProbaArray.cs | 2 +- .../Formats/WebP/{ => Lossy}/Vp8QuantMatrix.cs | 2 +- .../Formats/WebP/{ => Lossy}/Vp8SegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8TopSamples.cs | 2 +- .../Formats/WebP/{ => Lossy}/WebPLossyDecoder.cs | 3 ++- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 3 +++ src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 2 ++ 35 files changed, 46 insertions(+), 34 deletions(-) rename src/ImageSharp/Formats/WebP/{ => BitReader}/BitReaderBase.cs (97%) rename src/ImageSharp/Formats/WebP/{ => BitReader}/Vp8BitReader.cs (99%) rename src/ImageSharp/Formats/WebP/{ => BitReader}/Vp8LBitReader.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/ColorCache.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/HTreeGroup.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/HuffIndex.cs (93%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/HuffmanCode.cs (92%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/HuffmanUtils.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/LosslessUtils.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/Vp8LDecoder.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/Vp8LMetadata.cs (92%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/Vp8LTransform.cs (96%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/Vp8LTransformType.cs (96%) rename src/ImageSharp/Formats/WebP/{ => Lossless}/WebPLosslessDecoder.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/IntraPredictionMode.cs (92%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/LoopFilter.cs (91%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/LossyUtils.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/VP8BandProbas.cs (93%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Decoder.cs (99%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8FilterHeader.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8FilterInfo.cs (98%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8FrameHeader.cs (92%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Io.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8MacroBlock.cs (91%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8MacroBlockData.cs (97%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8PictureHeader.cs (96%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8Proba.cs (96%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8ProbaArray.cs (92%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8QuantMatrix.cs (94%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8SegmentHeader.cs (95%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/Vp8TopSamples.cs (85%) rename src/ImageSharp/Formats/WebP/{ => Lossy}/WebPLossyDecoder.cs (99%) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 7a23ad6e5..a78f76c4b 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -6,6 +6,8 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/BitReaderBase.cs rename to src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index f35368c15..24601c8aa 100644 --- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -7,7 +7,7 @@ using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.BitReader { /// /// Base class for VP8 and VP8L bitreader. diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/Vp8BitReader.cs rename to src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 1601c7339..24ea1b634 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.BitReader { /// /// A bit reader for VP8 streams. diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/Vp8LBitReader.cs rename to src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index 10b7307b3..b4bd48a14 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.BitReader { /// /// A bit reader for reading lossless webp streams. diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/ColorCache.cs rename to src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index d5834a4c8..6a1d0b3f5 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/HTreeGroup.cs rename to src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index 42e0d9d93..f1b5a6e85 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Huffman table group. diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs similarity index 93% rename from src/ImageSharp/Formats/WebP/HuffIndex.cs rename to src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index 7e2b58a8e..4d4980165 100644 --- a/src/ImageSharp/Formats/WebP/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Five Huffman codes are used at each meta code. diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs similarity index 92% rename from src/ImageSharp/Formats/WebP/HuffmanCode.cs rename to src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index ac6c5bec4..52ece88ea 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/HuffmanUtils.cs rename to src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index ee2e5ea6d..222f92915 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Utility functions related to creating the huffman tables. diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/LosslessUtils.cs rename to src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 6279a5d12..f73dd3756 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Utility functions for the lossless decoder. diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/Vp8LDecoder.cs rename to src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index 76010fdb2..2dd3f72de 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Holds information for decoding a lossless webp image. diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs similarity index 92% rename from src/ImageSharp/Formats/WebP/Vp8LMetadata.cs rename to src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index 737def7f4..2959dbf15 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LMetadata { diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs similarity index 96% rename from src/ImageSharp/Formats/WebP/Vp8LTransform.cs rename to src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index ae62123d3..3207bee61 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -4,7 +4,7 @@ using System.Buffers; using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Data associated with a VP8L transformation to reduce the entropy. diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs similarity index 96% rename from src/ImageSharp/Formats/WebP/Vp8LTransformType.cs rename to src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index 7e1be4deb..f9fe87b4e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Enum for the different transform types. Transformations are reversible manipulations of the image data diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs rename to src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index cbe2fec0f..529b649b5 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -8,10 +8,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.WebP.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp @@ -939,8 +940,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The number of pixels to copy. private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { - if (dist >= length) // no overlap. + if (dist >= length) { + // no overlap. Span src = pixelData.Slice(decodedPixels - dist, length); Span dest = pixelData.Slice(decodedPixels); src.CopyTo(dest); @@ -967,8 +969,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The number of pixels to copy. private static void CopyBlock8B(Span data, int pos, int dist, int length) { - if (dist >= length) // no overlap. + if (dist >= length) { + // no overlap. data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); } else diff --git a/src/ImageSharp/Formats/WebP/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs similarity index 92% rename from src/ImageSharp/Formats/WebP/IntraPredictionMode.cs rename to src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index 29c64c765..964a60dd2 100644 --- a/src/ImageSharp/Formats/WebP/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal enum IntraPredictionMode { diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs similarity index 91% rename from src/ImageSharp/Formats/WebP/LoopFilter.cs rename to src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index 8330ca593..837ce10c0 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Enum for the different loop filters used. VP8 supports two types of loop filters. diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/LossyUtils.cs rename to src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 2d71938a1..cfc892d60 100644 --- a/src/ImageSharp/Formats/WebP/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -6,7 +6,7 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal static class LossyUtils { diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs similarity index 93% rename from src/ImageSharp/Formats/WebP/VP8BandProbas.cs rename to src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs index d96e9bd63..e45a4c6ff 100644 --- a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// All the probabilities associated to one band. diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/Vp8Decoder.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index 3c09821b7..e9ae39a9f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -3,9 +3,10 @@ using System; using System.Buffers; +using SixLabors.ImageSharp.Formats.WebP.BitReader; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Holds information for decoding a lossy webp image. diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 2d854664a..316662423 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal class Vp8FilterHeader { diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs similarity index 98% rename from src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index 42756e5e3..cd7632f86 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Filter information. diff --git a/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs similarity index 92% rename from src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index c7bfa67f6..eb7d9c321 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Vp8 frame header information. diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/Vp8Io.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index 527ef02f0..0ae2312e1 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal ref struct Vp8Io { diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs similarity index 91% rename from src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index 8ecaa2c83..86122e187 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Contextual macroblock information. diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index 5e473e02e..6ac5938cd 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Data needed to reconstruct a macroblock. diff --git a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs similarity index 96% rename from src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 3f80b13b2..97f741ada 100644 --- a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal class Vp8PictureHeader { diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs similarity index 96% rename from src/ImageSharp/Formats/WebP/Vp8Proba.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index f6ff52eeb..d8af86b46 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Data for all frame-persistent probabilities. diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs similarity index 92% rename from src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index 6cc87a811..d320028f0 100644 --- a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Probabilities associated to one of the contexts. diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs similarity index 94% rename from src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index 47bc96502..439c181b9 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal class Vp8QuantMatrix { diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs similarity index 95% rename from src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index c7c198bb2..d9e76bf2f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Segment features. diff --git a/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs similarity index 85% rename from src/ImageSharp/Formats/WebP/Vp8TopSamples.cs rename to src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index c6382b5c6..4235de734 100644 --- a/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal class Vp8TopSamples { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs similarity index 99% rename from src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs rename to src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 1c524b83b..1571b0549 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -6,10 +6,11 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.WebP.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.WebP +namespace SixLabors.ImageSharp.Formats.WebP.Lossy { /// /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index b8d450c33..90ad79c87 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -5,6 +5,9 @@ using System; using System.Buffers.Binary; using System.IO; +using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.WebP.Lossless; +using SixLabors.ImageSharp.Formats.WebP.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index cf4a6fef7..6030e4d0e 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Formats.WebP.BitReader; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.WebP { From 82fc1223b1d6d2e91aab3583eeb07183ffba5219 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 3 May 2020 12:42:55 +0200 Subject: [PATCH 165/359] Change license header --- src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs | 2 +- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs | 2 +- src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs | 2 +- src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs | 2 +- src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs | 2 +- src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs | 2 +- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 +- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 2 +- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 2 +- src/ImageSharp/Formats/WebP/WebPFormat.cs | 2 +- src/ImageSharp/Formats/WebP/WebPFormatType.cs | 2 +- src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 2 +- src/ImageSharp/Formats/WebP/WebPLookupTables.cs | 2 +- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 2 +- src/ImageSharp/Formats/WebP/WebPThrowHelper.cs | 2 +- src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs | 2 +- tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs | 2 +- tests/Images/Input/WebP/exif_lossless.webp | 4 ++-- 55 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index abbb7d775..17cf6a8d2 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index a78f76c4b..9a47d567f 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 24601c8aa..4a3a5f84f 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 24ea1b634..5c8e4be21 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Buffers; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index b4bd48a14..421481ba8 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Buffers; using System.IO; diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs index 13bf817c4..276a488c5 100644 --- a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index 6a1d0b3f5..614e76639 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index f1b5a6e85..e54cff0b8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index 4d4980165..7456802bf 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index 52ece88ea..1a444a152 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 222f92915..3eafe6047 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index f73dd3756..00dbc1275 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index 2dd3f72de..9d7049b25 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index 2959dbf15..c6f208cc3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index 3207bee61..29245d5f9 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Buffers; using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index f9fe87b4e..ecb932b44 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 529b649b5..1b3cc2d03 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index 964a60dd2..8aeb04b03 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index 837ce10c0..5fa258a31 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index cfc892d60..081513a73 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs index e45a4c6ff..34867ed9d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index e9ae39a9f..e94b9fc31 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 316662423..bacb9d84e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index cd7632f86..efd1e9e29 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index eb7d9c321..9cb923236 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index 0ae2312e1..09a4c9691 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index 86122e187..6c84d0e8e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index 6ac5938cd..82ea04bd7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 97f741ada..2db20eb3b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index d8af86b46..758f75704 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index d320028f0..ded1f7d77 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index 439c181b9..fd6f3ac29 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index d9e76bf2f..266440e92 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index 4235de734..1c09c3823 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 1571b0549..88c71ef5f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs index 18e311da3..21264f4b3 100644 --- a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs +++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs index 62e3de07b..c4e03c633 100644 --- a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs index 57bc16a66..8a6b635e4 100644 --- a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs +++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index 4743935e1..f1db66a27 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index adeb4291e..24fc5b1bc 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 3273f300d..89dadba42 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.IO; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 90ad79c87..0dc2905bb 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index c4fc0ccba..16e420451 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 48a595258..8f0c665c3 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs index 291281d00..0c8fbf05f 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormatType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index dc0ecadc8..4d86bd0a6 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 6030e4d0e..544de48c3 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using SixLabors.ImageSharp.Formats.WebP.BitReader; diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index a2aa76999..65a082696 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 66deea255..9c0a61a0b 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs index 7cc4df246..7183c526a 100644 --- a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index 051b73853..6911f4605 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index b6631727d..654421a34 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.IO; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index ae85ecb40..7fc7745c3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using System.IO; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index 44f46c05a..be26964a8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the GNU Affero General Public License, Version 3. using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/Images/Input/WebP/exif_lossless.webp b/tests/Images/Input/WebP/exif_lossless.webp index 3990bd8c6..a3eeae555 100644 --- a/tests/Images/Input/WebP/exif_lossless.webp +++ b/tests/Images/Input/WebP/exif_lossless.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30734501a0b1953c392762d6ea11652400efec3f891f0da37749e3674e15b6a0 -size 183280 +oid sha256:21de077dd545c182a36584955918a70643ae2b972b208234f548d95ef8535a3e +size 183286 From 4207950b3bfb1e6ae933c38bf79846ef7af7376f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 3 Jul 2020 15:25:26 +0100 Subject: [PATCH 166/359] Update to match latest trunk --- .../Formats/ImageDecoderUtilities.cs | 2 +- .../Formats/WebP/AlphaCompressionMethod.cs | 4 +- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 4 +- .../Formats/WebP/BitReader/BitReaderBase.cs | 4 +- .../Formats/WebP/BitReader/Vp8BitReader.cs | 4 +- .../Formats/WebP/BitReader/Vp8LBitReader.cs | 4 +- .../Formats/WebP/IWebPDecoderOptions.cs | 4 +- .../Formats/WebP/Lossless/ColorCache.cs | 4 +- .../Formats/WebP/Lossless/HTreeGroup.cs | 4 +- .../Formats/WebP/Lossless/HuffIndex.cs | 4 +- .../Formats/WebP/Lossless/HuffmanCode.cs | 4 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 4 +- .../Formats/WebP/Lossless/LosslessUtils.cs | 4 +- .../Formats/WebP/Lossless/Vp8LDecoder.cs | 4 +- .../Formats/WebP/Lossless/Vp8LMetadata.cs | 4 +- .../Formats/WebP/Lossless/Vp8LTransform.cs | 4 +- .../WebP/Lossless/Vp8LTransformType.cs | 4 +- .../WebP/Lossless/WebPLosslessDecoder.cs | 4 +- .../Formats/WebP/Lossy/IntraPredictionMode.cs | 4 +- .../Formats/WebP/Lossy/LoopFilter.cs | 4 +- .../Formats/WebP/Lossy/LossyUtils.cs | 4 +- .../Formats/WebP/Lossy/VP8BandProbas.cs | 4 +- .../Formats/WebP/Lossy/Vp8Decoder.cs | 4 +- .../Formats/WebP/Lossy/Vp8FilterHeader.cs | 4 +- .../Formats/WebP/Lossy/Vp8FilterInfo.cs | 4 +- .../Formats/WebP/Lossy/Vp8FrameHeader.cs | 4 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs | 4 +- .../Formats/WebP/Lossy/Vp8MacroBlock.cs | 4 +- .../Formats/WebP/Lossy/Vp8MacroBlockData.cs | 4 +- .../Formats/WebP/Lossy/Vp8PictureHeader.cs | 4 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs | 4 +- .../Formats/WebP/Lossy/Vp8ProbaArray.cs | 4 +- .../Formats/WebP/Lossy/Vp8QuantMatrix.cs | 4 +- .../Formats/WebP/Lossy/Vp8SegmentHeader.cs | 4 +- .../Formats/WebP/Lossy/Vp8TopSamples.cs | 4 +- .../Formats/WebP/Lossy/WebPLossyDecoder.cs | 4 +- src/ImageSharp/Formats/WebP/Vp8HeaderType.cs | 4 +- .../Formats/WebP/WebPAlphaFilterType.cs | 4 +- .../Formats/WebP/WebPBitsPerPixel.cs | 4 +- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 4 +- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 26 ++++++- .../Formats/WebP/WebPDecoderCore.cs | 72 +++++++++---------- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 4 +- src/ImageSharp/Formats/WebP/WebPFormat.cs | 4 +- src/ImageSharp/Formats/WebP/WebPFormatType.cs | 4 +- .../Formats/WebP/WebPImageFormatDetector.cs | 4 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 +- .../Formats/WebP/WebPLookupTables.cs | 4 +- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 4 +- .../Formats/WebP/WebPThrowHelper.cs | 4 +- .../Formats/WebP/WebpConfigurationModule.cs | 4 +- .../Codecs/DecodeWebp.cs | 4 +- .../Formats/WebP/WebPDecoderTests.cs | 4 +- .../Formats/WebP/WebPMetaDataTests.cs | 4 +- 55 files changed, 164 insertions(+), 144 deletions(-) diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 6bb9116cd..617b4b8ce 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; diff --git a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs index 17cf6a8d2..45f9b3441 100644 --- a/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/WebP/AlphaCompressionMethod.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 9a47d567f..edb72564e 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 4a3a5f84f..75b846458 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 5c8e4be21..b482a861d 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs index 421481ba8..c2647f559 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8LBitReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; using System.IO; diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs index 276a488c5..7a50a6370 100644 --- a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index 614e76639..d1d46a7a6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs index e54cff0b8..359fbc201 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HTreeGroup.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs index 7456802bf..d1fc50741 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffIndex.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs index 1a444a152..2830538c7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanCode.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 3eafe6047..fb1de6bef 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 00dbc1275..738ed17bb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs index 9d7049b25..02777ec61 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs index c6f208cc3..29d41aa83 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMetadata.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs index 29245d5f9..0dc849362 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransform.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs index ecb932b44..2aa2eb9d4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LTransformType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index 1b3cc2d03..bf59394d3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs index 8aeb04b03..b135c3c5e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/IntraPredictionMode.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs index 5fa258a31..c9a823ef8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LoopFilter.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 081513a73..5d8d8b545 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs index 34867ed9d..50cb1ebfc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/VP8BandProbas.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs index e94b9fc31..f7ef1c8db 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Decoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index bacb9d84e..4f5cad659 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs index efd1e9e29..64f4f1d3f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs index 9cb923236..73a9e1841 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FrameHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs index 09a4c9691..6a4184450 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Io.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs index 6c84d0e8e..cc8c174e8 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlock.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs index 82ea04bd7..1503a467a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockData.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs index 2db20eb3b..8277dccaf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8PictureHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs index 758f75704..98d237d91 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Proba.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs index ded1f7d77..793905b72 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs index fd6f3ac29..c8b0df12b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8QuantMatrix.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index 266440e92..3c399fe2e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs index 1c09c3823..1f4c4de5a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8TopSamples.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossy { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 88c71ef5f..1b1884cdc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs index 21264f4b3..bb1baece7 100644 --- a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs +++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs index c4e03c633..d8a5b3deb 100644 --- a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs index 8a6b635e4..7fd7da1b6 100644 --- a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs +++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index f1db66a27..c448ff344 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 24fc5b1bc..a2e742b68 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 89dadba42..6e905173b 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP @@ -35,5 +36,26 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + return new WebPDecoderCore(configuration, this).DecodeAsync(stream); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream) + => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new WebPDecoderCore(configuration, this).IdentifyAsync(stream); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 0dc2905bb..6953dffce 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; @@ -19,18 +19,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Performs the webp decoding operation. /// - internal sealed class WebPDecoderCore + internal sealed class WebPDecoderCore : IImageDecoderInternals { /// /// Reusable buffer. /// private readonly byte[] buffer = new byte[4]; - /// - /// The global configuration. - /// - private readonly Configuration configuration; - /// /// Used for allocating memory during processing operations. /// @@ -53,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The options. public WebPDecoderCore(Configuration configuration, IWebPDecoderOptions options) { - this.configuration = configuration; + this.Configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; this.IgnoreMetadata = options.IgnoreMetadata; } @@ -68,6 +63,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public ImageMetadata Metadata { get; private set; } + /// + public Configuration Configuration { get; } + /// /// Decodes the image from the specified and sets the data to the image. /// @@ -87,16 +85,16 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); } - var image = new Image(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); + var image = new Image(this.Configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossless) { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo); } @@ -326,11 +324,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } var vp8FrameHeader = new Vp8FrameHeader() - { - KeyFrame = true, - Profile = (sbyte)version, - PartitionLength = partitionLength - }; + { + KeyFrame = true, + Profile = (sbyte)version, + PartitionLength = partitionLength + }; var bitReader = new Vp8BitReader( this.currentStream, @@ -340,18 +338,18 @@ namespace SixLabors.ImageSharp.Formats.WebP bitReader.Remaining = remaining; return new WebPImageInfo() - { - Width = width, - Height = height, - XScale = xScale, - YScale = yScale, - BitsPerPixel = features?.Alpha == true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, - IsLossless = false, - Features = features, - Vp8Profile = (sbyte)version, - Vp8FrameHeader = vp8FrameHeader, - Vp8BitReader = bitReader - }; + { + Width = width, + Height = height, + XScale = xScale, + YScale = yScale, + BitsPerPixel = features?.Alpha == true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, + IsLossless = false, + Features = features, + Vp8Profile = (sbyte)version, + Vp8FrameHeader = vp8FrameHeader, + Vp8BitReader = bitReader + }; } /// @@ -396,14 +394,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } return new WebPImageInfo() - { - Width = width, - Height = height, - BitsPerPixel = WebPBitsPerPixel.Pixel32, - IsLossless = true, - Features = features, - Vp8LBitReader = bitReader - }; + { + Width = width, + Height = height, + BitsPerPixel = WebPBitsPerPixel.Pixel32, + IsLossless = true, + Features = features, + Vp8LBitReader = bitReader + }; } /// diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 16e420451..53ff5f54d 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 8f0c665c3..38787cbf8 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs index 0c8fbf05f..e4ed01e84 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormatType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs index 4d86bd0a6..12b360560 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 544de48c3..526521436 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Formats.WebP.BitReader; diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 65a082696..362bc7889 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 9c0a61a0b..0d8e8b323 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs index 7183c526a..c2abd31a8 100644 --- a/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs +++ b/src/ImageSharp/Formats/WebP/WebPThrowHelper.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index 6911f4605..d5e6eea6d 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 654421a34..5aa1810cc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.IO; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 7fc7745c3..3eb51a597 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.IO; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs index be26964a8..9a5bef8c1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; From b178429414343fe68caa6d9f223fb587104a8f95 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 14 May 2020 13:17:14 +0200 Subject: [PATCH 167/359] Add webp container spec and lossless bitstream specification --- .../WebP_Lossless_Bitstream_Specification.pdf | Bin 0 -> 144069 bytes .../rfc6386_lossy_specification.pdf | Bin .../WebP/WebP_Container_Specification.pdf | Bin 0 -> 122392 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/WebP_Lossless_Bitstream_Specification.pdf rename src/ImageSharp/Formats/WebP/{ => Lossy}/rfc6386_lossy_specification.pdf (100%) create mode 100644 src/ImageSharp/Formats/WebP/WebP_Container_Specification.pdf diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebP_Lossless_Bitstream_Specification.pdf b/src/ImageSharp/Formats/WebP/Lossless/WebP_Lossless_Bitstream_Specification.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4b5ddd57fa1d5784c2f688b54b32c88a61e16914 GIT binary patch literal 144069 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

SQ%rEKan)FqWmMg`t0hoP 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

hok+jcqny8Vy4`qa< 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 qBLuL{ Date: Fri, 15 May 2020 14:20:18 +0200 Subject: [PATCH 168/359] Intial stubs of files needed for webp encoding --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 + .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 12 +++++ .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 12 +++++ .../Formats/WebP/IWebPEncoderOptions.cs | 12 +++++ .../Formats/WebP/ImageExtensions.cs | 36 +++++++++++++ src/ImageSharp/Formats/WebP/WebPEncoder.cs | 23 ++++++++ .../Formats/WebP/WebPEncoderCore.cs | 53 +++++++++++++++++++ .../Formats/WebP/WebpConfigurationModule.cs | 1 + .../Codecs/DecodeWebp.cs | 46 ++++++++-------- .../Formats/GeneralFormatTests.cs | 9 +++- .../Formats/ImageFormatManagerTests.cs | 3 ++ .../TestUtilities/TestEnvironment.Formats.cs | 4 +- .../Tests/TestEnvironmentTests.cs | 4 ++ tests/Images/Input/WebP/earth_lossless.webp | 4 +- tests/Images/Input/WebP/earth_lossy.webp | 4 +- 15 files changed, 197 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs create mode 100644 src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs create mode 100644 src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/WebP/ImageExtensions.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPEncoder.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPEncoderCore.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 2ea456286..6ea9075eb 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -94,6 +94,8 @@ namespace SixLabors.ImageSharp.Advanced AotCodec(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); AotCodec(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder()); AotCodec(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder()); + AotCodec(new Formats.Tga.TgaDecoder(), new Formats.Tga.TgaEncoder()); + AotCodec(new Formats.WebP.WebPDecoder(), new Formats.WebP.WebPEncoder()); // TODO: Do the discovery work to figure out what works and what doesn't. } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs new file mode 100644 index 000000000..974d18c9a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +{ + ///

+ /// A bit writer for writing lossless webp streams. + /// + internal class Vp8BitWriter + { + } +} diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs new file mode 100644 index 000000000..925592fb6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +{ + /// + /// A bit writer for writing lossless webp streams. + /// + internal class Vp8LBitWriter + { + } +} diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs new file mode 100644 index 000000000..e29a446c9 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Configuration options for use during webp encoding. + /// + internal interface IWebPEncoderOptions + { + } +} diff --git a/src/ImageSharp/Formats/WebP/ImageExtensions.cs b/src/ImageSharp/Formats/WebP/ImageExtensions.cs new file mode 100644 index 000000000..5ceb85d74 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/ImageExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.IO; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.WebP; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsWebp(this Image source, Stream stream) => SaveAsWebp(source, stream, null); + + /// + /// Saves the image to the given stream with the webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + public static void SaveAsWebp(this Image source, Stream stream, WebPEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance)); + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs new file mode 100644 index 000000000..b201e0e8d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Image encoder for writing an image to a stream in the WebP format. + /// + public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions + { + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs new file mode 100644 index 000000000..bf437d985 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Image encoder for writing an image to a stream in the WebP format. + /// + internal sealed class WebPEncoderCore + { + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public WebPEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs index d5e6eea6d..2fa4c7e7b 100644 --- a/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/WebP/WebpConfigurationModule.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Configure(Configuration configuration) { configuration.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); + configuration.ImageFormatsManager.SetEncoder(WebPFormat.Instance, new WebPEncoder()); configuration.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 5aa1810cc..43dc8f9d5 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -67,28 +67,30 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs return image.Height; } - /* Results 18.03.2020 - * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 - Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores - .NET Core SDK=3.1.200 - [Host] : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT - Job-TLYXIR : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT - Job-HPKRXU : .NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT - Job-OBFQMR : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT - | Method | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |-------------- |--------------------- |--------------------- |----------:|----------:|---------:|-----------:|----------:|----------:|-------------:| - | 'Magick Lossy WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 70.37 ms | 9.234 ms | 0.506 ms | - | - | - | 32.05 KB | - | 'ImageSharp Lossy Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 211.77 ms | 8.055 ms | 0.442 ms | 19000.0000 | - | - | 82297.31 KB | - | 'Magick Lossless WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.35 ms | 1.099 ms | 0.060 ms | - | - | - | 15.32 KB | - | 'ImageSharp Lossless Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 494.34 ms | 5.505 ms | 0.302 ms | 2000.0000 | 1000.0000 | 1000.0000 | 151801.78 KB | - | 'Magick Lossy WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 70.21 ms | 1.440 ms | 0.079 ms | - | - | - | 14.8 KB | - | 'ImageSharp Lossy Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 142.32 ms | 6.046 ms | 0.331 ms | 9000.0000 | - | - | 40610.23 KB | - | 'Magick Lossless WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.44 ms | 0.258 ms | 0.014 ms | - | - | - | 14.3 KB | - | 'ImageSharp Lossless Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 206.45 ms | 11.093 ms | 0.608 ms | 2666.6667 | 1666.6667 | 1000.0000 | 151758.87 KB | - | 'Magick Lossy WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 69.69 ms | 1.147 ms | 0.063 ms | - | - | - | 14.42 KB | - | 'ImageSharp Lossy Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 121.72 ms | 2.373 ms | 0.130 ms | 9000.0000 | - | - | 40050.06 KB | - | 'Magick Lossless WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 49.37 ms | 1.865 ms | 0.102 ms | - | - | - | 14.27 KB | - | 'ImageSharp Lossless Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 194.03 ms | 37.759 ms | 2.070 ms | 2000.0000 | 1000.0000 | 1000.0000 | 151756.38 KB | + /* Results 15.05.2020 + * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 + Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores + .NET Core SDK=3.1.202 + [Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT + Job-AQFZAV : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT + Job-YCDAPQ : .NET Core 2.1.18 (CoreCLR 4.6.28801.04, CoreFX 4.6.28802.05), X64 RyuJIT + Job-WMTYOZ : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT + + IterationCount=3 LaunchCount=1 WarmupCount=3 + | Method | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |-------------- |--------------------- |--------------------- |-----------:|----------:|--------:|----------:|----------:|------:|-------------:| + | 'Magick Lossy WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 125.2 ms | 7.93 ms | 0.43 ms | - | - | - | 18.05 KB | + | 'ImageSharp Lossy Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,102.1 ms | 67.88 ms | 3.72 ms | 2000.0000 | - | - | 11835.55 KB | + | 'Magick Lossless WebP' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 183.6 ms | 7.11 ms | 0.39 ms | - | - | - | 18.71 KB | + | 'ImageSharp Lossless Webp' | .NET 4.7.2 | WebP/(...).webp [21] | WebP/(...).webp [24] | 1,820.1 ms | 68.66 ms | 3.76 ms | 4000.0000 | 1000.0000 | - | 223765.64 KB | + | 'Magick Lossy WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 124.7 ms | 1.92 ms | 0.11 ms | - | - | - | 15.97 KB | + | 'ImageSharp Lossy Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 739.0 ms | 39.51 ms | 2.17 ms | 2000.0000 | - | - | 11802.98 KB | + | 'Magick Lossless WebP' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 184.0 ms | 21.65 ms | 1.19 ms | - | - | - | 17.96 KB | + | 'ImageSharp Lossless Webp' | .NET Core 2.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 618.3 ms | 16.33 ms | 0.90 ms | 4000.0000 | 1000.0000 | - | 223699.11 KB | + | 'Magick Lossy WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 125.6 ms | 17.51 ms | 0.96 ms | - | - | - | 16.1 KB | + | 'ImageSharp Lossy Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 768.4 ms | 114.73 ms | 6.29 ms | 2000.0000 | - | - | 11802.89 KB | + | 'Magick Lossless WebP' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 183.6 ms | 3.32 ms | 0.18 ms | - | - | - | 17 KB | + | 'ImageSharp Lossless Webp' | .NET Core 3.1 | WebP/(...).webp [21] | WebP/(...).webp [24] | 621.3 ms | 12.12 ms | 0.66 ms | 4000.0000 | 1000.0000 | - | 223698.75 KB | */ } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index c10cd8d29..d5b17ad50 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -127,6 +127,11 @@ namespace SixLabors.ImageSharp.Tests.Formats { image.SaveAsTga(output); } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.webp"))) + { + image.SaveAsWebp(output); + } } } } @@ -174,6 +179,8 @@ namespace SixLabors.ImageSharp.Tests.Formats [InlineData(100, 100, "tga")] [InlineData(100, 10, "tga")] [InlineData(10, 100, "tga")] + [InlineData(100, 10, "webp")] + [InlineData(10, 100, "webp")] public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) @@ -200,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Formats [Fact] public void IdentifyReturnsNullWithInvalidStream() { - byte[] invalid = new byte[10]; + var invalid = new byte[10]; using (var memoryStream = new MemoryStream(invalid)) { diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 98fbac7c0..684b75167 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -36,12 +37,14 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); } [Fact] diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 6e204e2d4..1caa4443a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests @@ -55,7 +56,8 @@ namespace SixLabors.ImageSharp.Tests var cfg = new Configuration( new JpegConfigurationModule(), new GifConfigurationModule(), - new TgaConfigurationModule()); + new TgaConfigurationModule(), + new WebPConfigurationModule()); // Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index e72d953ac..9e386e8f9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -9,6 +9,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.Tests.TestUtilities.ReferenceCodecs; using Xunit; @@ -81,6 +82,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] + [InlineData("lol/foobar.webp", typeof(MagickReferenceDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { if (TestEnvironment.IsLinux) @@ -97,6 +99,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] [InlineData("lol/Baz.gif", typeof(GifEncoder))] + [InlineData("lol/foobar.webp", typeof(WebPEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { if (!TestEnvironment.IsLinux) @@ -113,6 +116,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] + [InlineData("lol/foobar.webp", typeof(MagickReferenceDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { if (!TestEnvironment.IsLinux) diff --git a/tests/Images/Input/WebP/earth_lossless.webp b/tests/Images/Input/WebP/earth_lossless.webp index 8729ece9c..1abcb8668 100644 --- a/tests/Images/Input/WebP/earth_lossless.webp +++ b/tests/Images/Input/WebP/earth_lossless.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ea7fad78cd68e27c1616f4a99de52397f5485259c7adbd674a5c9a362885216 -size 2447273 +oid sha256:35e61613388342baac7f39a4a3c3ae32587a065505269115a134592eee9563b8 +size 7813062 diff --git a/tests/Images/Input/WebP/earth_lossy.webp b/tests/Images/Input/WebP/earth_lossy.webp index 8f1cebc3f..790a194de 100644 --- a/tests/Images/Input/WebP/earth_lossy.webp +++ b/tests/Images/Input/WebP/earth_lossy.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71263c0db7b8cd2e787b8554fd40aff1d46a753646fb2062966ca07ac040e841 -size 852402 +oid sha256:c45c068709fa3f878564d399e539636b9e42926291dde683adb7bb5d98c2c680 +size 467258 From c72dd42406d8efdcd956b79bba252c7351db0274 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 17 May 2020 18:08:56 +0200 Subject: [PATCH 169/359] Analyze and create palette --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 34 +++ .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 88 +++++++ .../Formats/WebP/IWebPEncoderOptions.cs | 23 ++ .../Formats/WebP/Lossless/LosslessUtils.cs | 22 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 36 +++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 15 ++ .../Formats/WebP/WebPDecoderCore.cs | 2 +- src/ImageSharp/Formats/WebP/WebPEncoder.cs | 12 + .../Formats/WebP/WebPEncoderCore.cs | 221 ++++++++++++++++++ 9 files changed, 441 insertions(+), 12 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 974d18c9a..2764abc30 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -8,5 +8,39 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter ///
internal class Vp8BitWriter { + private uint range; + + private uint value; + + /// + /// Number of outstanding bits. + /// + private int run; + + /// + /// Number of pending bits. + /// + private int nbBits; + + private byte[] buffer; + + private int pos; + + private int maxPos; + + private bool error; + + public Vp8BitWriter(int expectedSize) + { + this.range = 255 - 1; + this.value = 0; + this.run = 0; + this.nbBits = -8; + this.pos = 0; + this.maxPos = 0; + this.error = false; + + //BitWriterResize(expected_size); + } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 925592fb6..fecb681d1 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; + namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { /// @@ -8,5 +10,91 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// internal class Vp8LBitWriter { + /// + /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. + /// + private const int MinExtraSize = 32768; + + private const int WriterBytes = 4; + + private const int WriterBits = 32; + + private const int WriterMaxBits = 64; + + /// + /// Bit accumulator. + /// + private ulong bits; + + /// + /// Number of bits used in accumulator. + /// + private int used; + + /// + /// Buffer to write to. + /// + private byte[] buffer; + + /// + /// Current write position. + /// + private int cur; + + private int end; + + private bool error; + + public Vp8LBitWriter(int expectedSize) + { + this.buffer = new byte[expectedSize]; + } + + /// + /// This function writes bits into bytes in increasing addresses (little endian), + /// and within a byte least-significant-bit first. + /// This function can write up to 32 bits in one go, but VP8LBitReader can only + /// read 24 bits max (VP8L_MAX_NUM_BIT_READ). + /// + public void PutBits(uint bits, int nBits) + { + if (nBits > 0) + { + if (this.used >= 32) + { + this.PutBitsFlushBits(); + } + + this.bits |= bits << this.used; + this.used += nBits; + } + } + + /// + /// Internal function for PutBits flushing 32 bits from the written state. + /// + private void PutBitsFlushBits() + { + // If needed, make some room by flushing some bits out. + if (this.cur + WriterBytes > this.end) + { + var extraSize = (this.end - this.cur) + MinExtraSize; + if (!BitWriterResize(extraSize)) + { + this.error = true; + return; + } + } + + //*(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)bw->bits_); + this.cur += WriterBytes; + this.bits >>= WriterBits; + this.used -= WriterBits; + } + + private bool BitWriterResize(int extraSize) + { + return true; + } } } diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index e29a446c9..f87dd954f 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -8,5 +8,28 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
internal interface IWebPEncoderOptions { + /// + /// Gets a value indicating whether lossless compression should be used. + /// If false, lossy compression will be used. + /// + bool Lossless { get; } + + /// + /// Gets the compression quality. Between 0 and 100. + /// For lossy, 0 gives the smallest size and 100 the largest. For lossless, + /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger + /// files compared to the slowest, but best, 100. + /// + float Quality { get; } + + /// + /// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format. + /// + bool AlphaCompression { get; } + + /// + /// Gets the number of entropy-analysis passes (in [1..10]). + /// + int EntropyPasses { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 738ed17bb..d0cbd1e0a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -294,6 +294,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + /// + /// Difference of each component, mod 256. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint SubPixels(uint a, uint b) + { + uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; @@ -629,17 +640,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } - /// - /// Difference of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static uint SubPixels(uint a, uint b) - { - uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); - uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); - } - [MethodImpl(InliningOptions.ShortMethod)] private static uint GetArgbIndex(uint idx) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs new file mode 100644 index 000000000..efa0acc09 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Encoder for lossless webp images. + /// + internal class Vp8LEncoder + { + /// + /// Gets a value indicating whether to use the cross color transform. + /// + public bool UseCrossColorTransform { get; } + + /// + /// Gets a value indicating whether to use the substract green transform. + /// + public bool UseSubtractGreenTransform { get; } + + /// + /// Gets a value indicating whether to use the predictor transform. + /// + public bool UsePredictorTransform { get; } + + /// + /// Gets a value indicating whether to use color indexing transform. + /// + public bool UsePalette { get; } + + /// + /// Gets the palette size. + /// + public int PaletteSize { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index a2e742b68..c696d19b1 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -67,6 +67,16 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public const int Vp8LImageSizeBits = 14; + /// + /// The Vp8L version 0. + /// + public const int Vp8LVersion = 0; + + /// + /// The maximum number of colors for a paletted images. + /// + public const int MaxPaletteSize = 256; + /// /// Maximum number of color cache bits. /// @@ -77,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public const int MaxNumberOfTransforms = 4; + /// + /// The maximum allowed width or height of a webp image. + /// + public const int MaxDimension = 16383; + public const int MaxAllowedCodeLength = 15; public const int DefaultCodeLength = 8; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 6953dffce..8b0a32fab 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Metadata = new ImageMetadata(); this.currentStream = stream; - uint fileSize = this.ReadImageHeader(); + this.ReadImageHeader(); using WebPImageInfo imageInfo = this.ReadVp8Info(); if (imageInfo.Features != null && imageInfo.Features.Animation) { diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index b201e0e8d..062756d0d 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -12,6 +12,18 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions { + /// + public bool Lossless { get; set; } + + /// + public float Quality { get; set; } + + /// + public bool AlphaCompression { get; set; } + + /// + public int EntropyPasses { get; set; } + /// public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index bf437d985..c01984661 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -1,8 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.WebP.BitWriter; +using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -24,6 +28,11 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
private Configuration configuration; + /// + /// A bit writer for writing lossless webp streams. + /// + private Vp8LBitWriter bitWriter; + /// /// Initializes a new instance of the class. /// @@ -48,6 +57,218 @@ namespace SixLabors.ImageSharp.Formats.WebP this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; + + int width = image.Width; + int height = image.Height; + int initialSize = width * height; + this.bitWriter = new Vp8LBitWriter(initialSize); + + // Write image size. + this.WriteImageSize(width, height); + + // Write the non-trivial Alpha flag and lossless version. + bool hasAlpha = false; // TODO: for the start, this will be always false. + this.WriteRealAlphaAndVersion(hasAlpha); + + // Encode the main image stream. + this.EncodeStream(image); + } + + private void WriteImageSize(int inputImgWidth, int inputImgHeight) + { + Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); + Guard.MustBeLessThan(inputImgHeight, WebPConstants.MaxDimension, nameof(inputImgHeight)); + + uint width = (uint)inputImgWidth - 1; + uint height = (uint)inputImgHeight - 1; + + this.bitWriter.PutBits(width, WebPConstants.Vp8LImageSizeBits); + this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); + } + + private void WriteRealAlphaAndVersion(bool hasAlpha) + { + this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); + this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); + } + + private void EncodeStream(Image image) + where TPixel : unmanaged, IPixel + { + var encoder = new Vp8LEncoder(); + + // Analyze image (entropy, num_palettes etc). + this.EncoderAnalyze(image); + } + + /// + /// Analyzes the image and decides what transforms should be used. + /// + private void EncoderAnalyze(Image image) + where TPixel : unmanaged, IPixel + { + // TODO: low effort is always false for now. + bool lowEffort = false; + + // Check if we only deal with a small number of colors and should use a palette. + var usePalette = this.AnalyzeAndCreatePalette(image, lowEffort); + } + + /// + /// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, + /// creates a palette and returns true, else returns false. + /// + /// true, if a palette should be used. + private bool AnalyzeAndCreatePalette(Image image, bool lowEffort) + where TPixel : unmanaged, IPixel + { + int numColors = this.GetColorPalette(image, out uint[] palette); + + if (numColors > WebPConstants.MaxPaletteSize) + { + return false; + } + + // TODO: figure out how the palette needs to be sorted. + Array.Sort(palette); + + if (!lowEffort && PaletteHasNonMonotonousDeltas(palette, numColors)) + { + GreedyMinimizeDeltas(palette, numColors); + } + + return true; + } + + private int GetColorPalette(Image image, out uint[] palette) + where TPixel : unmanaged, IPixel + { + Rgba32 color = default; + palette = null; + var colors = new HashSet(); + for (int y = 0; y < image.Height; y++) + { + System.Span rowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + colors.Add(rowSpan[x]); + if (colors.Count > WebPConstants.MaxPaletteSize) + { + // Exact count is not needed, because a palette will not be used then anyway. + return WebPConstants.MaxPaletteSize + 1; + } + } + } + + // Fill the colors into the palette. + palette = new uint[colors.Count]; + using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); + int idx = 0; + while (colorEnumerator.MoveNext()) + { + colorEnumerator.Current.ToRgba32(ref color); + var bgra = new Bgra32(color.R, color.G, color.B, color.A); + palette[idx++] = bgra.PackedValue; + } + + return colors.Count; + } + + /// + /// The palette has been sorted by alpha. This function checks if the other components of the palette + /// have a monotonic development with regards to position in the palette. + /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development + /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. + /// + /// The palette. + /// Number of colors in the palette. + /// True, if the palette has no monotonous deltas. + private static bool PaletteHasNonMonotonousDeltas(uint[] palette, int numColors) + { + uint predict = 0x000000; + byte signFound = 0x00; + for (int i = 0; i < numColors; ++i) + { + uint diff = LosslessUtils.SubPixels(palette[i], predict); + byte rd = (byte)((diff >> 16) & 0xff); + byte gd = (byte)((diff >> 8) & 0xff); + byte bd = (byte)((diff >> 0) & 0xff); + if (rd != 0x00) + { + signFound |= (byte)((rd < 0x80) ? 1 : 2); + } + + if (gd != 0x00) + { + signFound |= (byte)((gd < 0x80) ? 8 : 16); + } + + if (bd != 0x00) + { + signFound |= (byte)((bd < 0x80) ? 64 : 128); + } + } + + return (signFound & (signFound << 1)) != 0; // two consequent signs. + } + + /// + /// Find greedily always the closest color of the predicted color to minimize + /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. + /// + /// The palette. + /// The number of colors in the palette. + private static void GreedyMinimizeDeltas(uint[] palette, int numColors) + { + uint predict = 0x00000000; + for (int i = 0; i < numColors; ++i) + { + int bestIdx = i; + uint bestScore = ~0U; + for (int k = i; k < numColors; ++k) + { + uint curScore = PaletteColorDistance(palette[k], predict); + if (bestScore > curScore) + { + bestScore = curScore; + bestIdx = k; + } + } + + // swap color(palette[bestIdx], palette[i]); + uint best = palette[bestIdx]; + palette[bestIdx] = palette[i]; + palette[i] = best; + predict = palette[i]; + } + } + + /// + /// Computes a value that is related to the entropy created by the + /// palette entry diff. + /// + /// Note that the last & 0xff is a no-operation in the next statement, but + /// removed by most compilers and is here only for regularity of the code. + /// + /// First color. + /// Second color. + /// The color distance. + private static uint PaletteColorDistance(uint col1, uint col2) + { + uint diff = LosslessUtils.SubPixels(col1, col2); + int moreWeightForRGBThanForAlpha = 9; + uint score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= moreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + + return score; + } + + private static uint PaletteComponentDistance(uint v) + { + return (v <= 128) ? v : (256 - v); } } } From 25382afa8a736b4c2e3756f2d3261dffbe504b82 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 May 2020 14:23:06 +0200 Subject: [PATCH 170/359] Implement AnalyzeEntropy --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- src/ImageSharp/Formats/WebP/EntropyIx.cs | 23 ++ src/ImageSharp/Formats/WebP/HistoIx.cs | 36 ++ .../Formats/WebP/IWebPEncoderOptions.cs | 5 + .../Formats/WebP/Lossless/LosslessUtils.cs | 91 ++++++ .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 116 +++++++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 45 ++- src/ImageSharp/Formats/WebP/WebPConstants.cs | 15 + src/ImageSharp/Formats/WebP/WebPEncoder.cs | 3 + .../Formats/WebP/WebPEncoderCore.cs | 308 ++++++++++++++++-- .../Formats/WebP/WebPLookupTables.cs | 203 ++++++++++++ 11 files changed, 813 insertions(+), 34 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/EntropyIx.cs create mode 100644 src/ImageSharp/Formats/WebP/HistoIx.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index fecb681d1..3cb554a64 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter if (this.cur + WriterBytes > this.end) { var extraSize = (this.end - this.cur) + MinExtraSize; - if (!BitWriterResize(extraSize)) + if (!this.BitWriterResize(extraSize)) { this.error = true; return; diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs new file mode 100644 index 000000000..f39c1981a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// These five modes are evaluated and their respective entropy is computed. + /// + internal enum EntropyIx + { + Direct = 0, + + Spatial = 1, + + SubGreen = 2, + + SpatialSubGreen = 3, + + Palette = 4, + + NumEntropyIx = 5 + } +} diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs new file mode 100644 index 000000000..916a2c074 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum HistoIx + { + HistoAlpha = 0, + + HistoAlphaPred, + + HistoGreen, + + HistoGreenPred, + + HistoRed, + + HistoRedPred, + + HistoBlue, + + HistoBluePred, + + HistoRedSubGreen, + + HistoRedPredSubGreen, + + HistoBlueSubGreen, + + HistoBluePredSubGreen, + + HistoPalette, + + HistoTotal, // Must be last. + } +} diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index f87dd954f..3ecef1b45 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
float Quality { get; } + /// + /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). + /// + int Method { get; } + /// /// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index d0cbd1e0a..350fc0603 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -16,6 +16,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { private const uint Predictor0 = WebPConstants.ArgbBlack; + private const int LogLookupIdxMax = 256; + + private const int ApproxLogMax = 4096; + + private const int ApproxLogWithCorrectionMax = 65536; + + private const double Log2Reciprocal = 1.44269504088896338700465094007086; + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -305,6 +313,89 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } + /// + /// Fast calculation of log2(v) for integer input. + /// + public static float FastLog2(uint v) + { + return (v < LogLookupIdxMax) ? WebPLookupTables.Log2Table[v] : FastLog2Slow(v); + } + + /// + /// Fast calculation of v * log2(v) for integer input. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float FastSLog2(uint v) + { + return (v < LogLookupIdxMax) ? WebPLookupTables.SLog2Table[v] : FastSLog2Slow(v); + } + + private static float FastSLog2Slow(uint v) + { + Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) + { + int logCnt = 0; + uint y = 1; + int correction = 0; + float vF = (float)v; + uint origV = v; + do + { + ++logCnt; + v = v >> 1; + y = y << 1; + } + while (v >= LogLookupIdxMax); + + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + correction = (int)((23 * (origV & (y - 1))) >> 4); + return (vF * (WebPLookupTables.Log2Table[v] + logCnt)) + correction; + } + else + { + return (float)(Log2Reciprocal * v * Math.Log(v)); + } + } + + private static float FastLog2Slow(uint v) + { + Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) + { + int logCnt = 0; + uint y = 1; + uint origV = v; + do + { + ++logCnt; + v = v >> 1; + y = y << 1; + } + while (v >= LogLookupIdxMax); + + double log2 = WebPLookupTables.Log2Table[v] + logCnt; + if (origV >= ApproxLogMax) + { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + int correction = (int)(23 * (origV & (y - 1))) >> 4; + log2 += (double)correction / origV; + } + + return (float)log2; + } + else + { + return (float)(Log2Reciprocal * Math.Log(v)); + } + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs new file mode 100644 index 000000000..a9ea62f84 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Holds bit entropy results and entropy-related functions. + /// + internal class Vp8LBitEntropy + { + /// + /// Not a trivial literal symbol. + /// + private const uint NonTrivialSym = 0xffffffff; + + /// + /// Initializes a new instance of the class. + /// + public Vp8LBitEntropy() + { + this.Entropy = 0.0d; + this.Sum = 0; + this.NoneZeros = 0; + this.MaxVal = 0; + this.NoneZeroCode = NonTrivialSym; + } + + /// + /// Gets or sets the entropy. + /// + public double Entropy { get; set; } + + /// + /// Gets or sets the sum of the population. + /// + public uint Sum { get; set; } + + /// + /// Gets or sets the number of non-zero elements in the population. + /// + public int NoneZeros { get; set; } + + /// + /// Gets or sets the maximum value in the population. + /// + public uint MaxVal { get; set; } + + /// + /// Gets or sets the index of the last non-zero in the population. + /// + public uint NoneZeroCode { get; set; } + + public double BitsEntropyRefine(Span array, int n) + { + double mix; + if (this.NoneZeros < 5) + { + if (this.NoneZeros <= 1) + { + return 0; + } + + // Two symbols, they will be 0 and 1 in a Huffman code. + // Let's mix in a bit of entropy to favor good clustering when + // distributions of these are combined. + if (this.NoneZeros == 2) + { + return (0.99 * this.Sum) + (0.01 * this.Entropy); + } + + // No matter what the entropy says, we cannot be better than min_limit + // with Huffman coding. I am mixing a bit of entropy into the + // min_limit since it produces much better (~0.5 %) compression results + // perhaps because of better entropy clustering. + if (this.NoneZeros == 3) + { + mix = 0.95; + } + else + { + mix = 0.7; // nonzeros == 4. + } + } + else + { + mix = 0.627; + } + + double minLimit = (2 * this.Sum) - this.MaxVal; + minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); + return (this.Entropy < minLimit) ? minLimit : this.Entropy; + } + + public void BitsEntropyUnrefined(Span array, int n) + { + for (int i = 0; i < n; i++) + { + if (array[i] != 0) + { + this.Sum += array[i]; + this.NoneZeroCode = (uint)i; + this.NoneZeros++; + this.Entropy -= LosslessUtils.FastSLog2(array[i]); + if (this.MaxVal < array[i]) + { + this.MaxVal = array[i]; + } + } + } + + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index efa0acc09..9e35cc1cc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -1,13 +1,37 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Encoder for lossless webp images. /// - internal class Vp8LEncoder + internal class Vp8LEncoder : IDisposable { + public Vp8LEncoder(MemoryAllocator memoryAllocator) + { + this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); + } + + /// + /// Gets or sets the huffman image bits. + /// + public int HistoBits { get; set; } + + /// + /// Gets or sets the bits used for the transformation. + /// + public int TransformBits { get; set; } + + /// + /// Gets or sets the cache bits. + /// + public bool CacheBits { get; } + /// /// Gets a value indicating whether to use the cross color transform. /// @@ -24,13 +48,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public bool UsePredictorTransform { get; } /// - /// Gets a value indicating whether to use color indexing transform. + /// Gets or sets a value indicating whether to use color indexing transform. /// - public bool UsePalette { get; } + public bool UsePalette { get; set; } /// - /// Gets the palette size. + /// Gets or sets the palette size. /// - public int PaletteSize { get; } + public int PaletteSize { get; set; } + + /// + /// Gets the palette. + /// + public IMemoryOwner Palette { get; } + + /// + public void Dispose() + { + this.Palette.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index c696d19b1..8437a091b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -72,6 +72,21 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public const int Vp8LVersion = 0; + /// + /// Maximum number of histogram images (sub-blocks). + /// + public const int MaxHuffImageSize = 2600; + + /// + /// Minimum number of Huffman bits. + /// + public const int MinHuffmanBits = 2; + + /// + /// Maximum number of Huffman bits. + /// + public const int MaxHuffmanBits = 9; + /// /// The maximum number of colors for a paletted images. /// diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 062756d0d..3e03724f3 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public float Quality { get; set; } + /// + public int Method { get; set; } + /// public bool AlphaCompression { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index c01984661..f9d2d7b89 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.Lossless; @@ -68,12 +69,17 @@ namespace SixLabors.ImageSharp.Formats.WebP // Write the non-trivial Alpha flag and lossless version. bool hasAlpha = false; // TODO: for the start, this will be always false. - this.WriteRealAlphaAndVersion(hasAlpha); + this.WriteAlphaAndVersion(hasAlpha); // Encode the main image stream. this.EncodeStream(image); } + /// + /// Writes the image size to the stream. + /// + /// The input image width. + /// The input image height. private void WriteImageSize(int inputImgWidth, int inputImgHeight) { Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); @@ -86,32 +92,207 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); } - private void WriteRealAlphaAndVersion(bool hasAlpha) + private void WriteAlphaAndVersion(bool hasAlpha) { this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); } + /// + /// Encodes the image stream using lossless webp format. + /// + /// The pixel type. + /// The image to encode. private void EncodeStream(Image image) where TPixel : unmanaged, IPixel { - var encoder = new Vp8LEncoder(); + var encoder = new Vp8LEncoder(this.memoryAllocator); // Analyze image (entropy, num_palettes etc). - this.EncoderAnalyze(image); + this.EncoderAnalyze(image, encoder); } /// /// Analyzes the image and decides what transforms should be used. /// - private void EncoderAnalyze(Image image) + private void EncoderAnalyze(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { - // TODO: low effort is always false for now. - bool lowEffort = false; - // Check if we only deal with a small number of colors and should use a palette. - var usePalette = this.AnalyzeAndCreatePalette(image, lowEffort); + var usePalette = this.AnalyzeAndCreatePalette(image, enc); + + // Empirical bit sizes. + int method = 4; // TODO: method hardcoded to 4 for now. + enc.HistoBits = GetHistoBits(method, usePalette, image.Width, image.Height); + enc.TransformBits = GetTransformBits(method, enc.HistoBits); + + // Try out multiple LZ77 on images with few colors. + var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; + this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + } + + /// + /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. + /// + /// The pixel type of the image. + /// The image to analyze. + /// Indicates whether a palette should be used. + /// The palette size. + /// The transformation bits. + /// Indicates if red and blue are always zero. + /// The entropy mode to use. + private EntropyIx AnalyzeEntropy(Image image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + + if (usePalette && paletteSize <= 16) + { + // In the case of small palettes, we pack 2, 4 or 8 pixels together. In + // practice, small palettes are better than any other transform. + redAndBlueAlwaysZero = true; + return EntropyIx.Palette; + } + + using System.Buffers.IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + Span histo = histoBuffer.Memory.Span; + Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. + Span prevRow = null; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Span currentRow = image.GetPixelRowSpan(y); + Bgra32 pix = ToBgra32(currentRow[0]); + uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); + pixPrev = pix; + if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) + { + continue; + } + + AddSingle( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoAlpha * 256), + histo.Slice((int)HistoIx.HistoRed * 256), + histo.Slice((int)HistoIx.HistoGreen * 256), + histo.Slice((int)HistoIx.HistoBlue * 256)); + AddSingle( + pixDiff, + histo.Slice((int)HistoIx.HistoAlpha * 256), + histo.Slice((int)HistoIx.HistoRed * 256), + histo.Slice((int)HistoIx.HistoGreen * 256), + histo.Slice((int)HistoIx.HistoBlue * 256)); + AddSingleSubGreen( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoRedSubGreen * 256), + histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + AddSingleSubGreen( + pixDiff, + histo.Slice((int)HistoIx.HistoRedSubGreen * 256), + histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + + // Approximate the palette by the entropy of the multiplicative hash. + uint hash = HashPix(pix.PackedValue); + histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; + + prevRow = currentRow; + } + } + + var entropyComp = new double[(int)HistoIx.HistoTotal]; + var entropy = new double[(int)EntropyIx.NumEntropyIx]; + int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; + + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pix_diff == 0 comparison, at least one of the + // zeros is likely to exist. + histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; + histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; + histo[(int)HistoIx.HistoRedPred * 256]++; + histo[(int)HistoIx.HistoGreenPred * 256]++; + histo[(int)HistoIx.HistoBluePred * 256]++; + histo[(int)HistoIx.HistoAlphaPred * 256]++; + + for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) + { + var bitEntropy = new Vp8LBitEntropy(); + bitEntropy.BitsEntropyUnrefined(histo, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(histo.Slice(j * 256), 256); + } + + entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRed] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlue]; + entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPred] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePred]; + entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRedSubGreen] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlueSubGreen]; + entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPredSubGreen] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePredSubGreen]; + entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; + + // When including transforms, there is an overhead in bits from + // storing them. This overhead is small but matters for small images. + // For spatial, there are 14 transformations. + entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(14); + + // For color transforms: 24 as only 3 channels are considered in a + // ColorTransformElement. + entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(24); + + // For palettes, add the cost of storing the palette. + // We empirically estimate the cost of a compressed entry as 8 bits. + // The palette is differential-coded when compressed hence a much + // lower cost than sizeof(uint32_t)*8. + entropy[(int)EntropyIx.Palette] += paletteSize * 8; + + EntropyIx minEntropyIx = EntropyIx.Direct; + for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; ++k) + { + if (entropy[(int)minEntropyIx] > entropy[k]) + { + minEntropyIx = (EntropyIx)k; + } + } + + redAndBlueAlwaysZero = true; + + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + var histoPairs = new byte[][] + { + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, + new byte[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, + new byte[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, + new byte[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } + }; + Span redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]); + Span blueHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][1]); + for (int i = 1; i < 256; i++) + { + if ((redHisto[i] | blueHisto[i]) != 0) + { + redAndBlueAlwaysZero = false; + break; + } + } + + return minEntropyIx; } /// @@ -119,32 +300,39 @@ namespace SixLabors.ImageSharp.Formats.WebP /// creates a palette and returns true, else returns false. /// /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(Image image, bool lowEffort) + private bool AnalyzeAndCreatePalette(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { - int numColors = this.GetColorPalette(image, out uint[] palette); - - if (numColors > WebPConstants.MaxPaletteSize) + Span palette = enc.Palette.Memory.Span; + enc.PaletteSize = this.GetColorPalette(image, palette); + if (enc.PaletteSize > WebPConstants.MaxPaletteSize) { return false; } // TODO: figure out how the palette needs to be sorted. - Array.Sort(palette); + uint[] paletteArray = palette.Slice(0, enc.PaletteSize).ToArray(); + Array.Sort(paletteArray); + paletteArray.CopyTo(palette); - if (!lowEffort && PaletteHasNonMonotonousDeltas(palette, numColors)) + if (PaletteHasNonMonotonousDeltas(palette, enc.PaletteSize)) { - GreedyMinimizeDeltas(palette, numColors); + GreedyMinimizeDeltas(palette, enc.PaletteSize); } return true; } - private int GetColorPalette(Image image, out uint[] palette) + /// + /// Gets the color palette. + /// + /// The pixel type of the image. + /// The image to get the palette from. + /// The span to store the palette into. + /// The number of palette entries. + private int GetColorPalette(Image image, Span palette) where TPixel : unmanaged, IPixel { - Rgba32 color = default; - palette = null; var colors = new HashSet(); for (int y = 0; y < image.Height; y++) { @@ -161,13 +349,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Fill the colors into the palette. - palette = new uint[colors.Count]; using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); int idx = 0; while (colorEnumerator.MoveNext()) { - colorEnumerator.Current.ToRgba32(ref color); - var bgra = new Bgra32(color.R, color.G, color.B, color.A); + Bgra32 bgra = ToBgra32(colorEnumerator.Current); palette[idx++] = bgra.PackedValue; } @@ -183,7 +369,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The palette. /// Number of colors in the palette. /// True, if the palette has no monotonous deltas. - private static bool PaletteHasNonMonotonousDeltas(uint[] palette, int numColors) + private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) { uint predict = 0x000000; byte signFound = 0x00; @@ -218,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
/// The palette. /// The number of colors in the palette. - private static void GreedyMinimizeDeltas(uint[] palette, int numColors) + private static void GreedyMinimizeDeltas(Span palette, int numColors) { uint predict = 0x00000000; for (int i = 0; i < numColors; ++i) @@ -246,17 +432,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Computes a value that is related to the entropy created by the /// palette entry diff. - /// - /// Note that the last & 0xff is a no-operation in the next statement, but - /// removed by most compilers and is here only for regularity of the code. /// /// First color. /// Second color. /// The color distance. + [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteColorDistance(uint col1, uint col2) { uint diff = LosslessUtils.SubPixels(col1, col2); - int moreWeightForRGBThanForAlpha = 9; + uint moreWeightForRGBThanForAlpha = 9; uint score = PaletteComponentDistance((diff >> 0) & 0xff); score += PaletteComponentDistance((diff >> 8) & 0xff); score += PaletteComponentDistance((diff >> 16) & 0xff); @@ -266,6 +450,74 @@ namespace SixLabors.ImageSharp.Formats.WebP return score; } + /// + /// Calculates the huffman image bits. + /// + private static int GetHistoBits(int method, bool usePalette, int width, int height) + { + // Make tile size a function of encoding method (Range: 0 to 6). + int histoBits = (usePalette ? 9 : 7) - method; + while (true) + { + int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); + if (huffImageSize <= WebPConstants.MaxHuffImageSize) + { + break; + } + + histoBits++; + } + + return (histoBits < WebPConstants.MinHuffmanBits) ? WebPConstants.MinHuffmanBits : + (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; + } + + /// + /// Calculates the bits used for the transformation. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetTransformBits(int method, int histoBits) + { + int maxTransformBits = (method < 4) ? 6 : (method > 4) ? 4 : 5; + int res = (histoBits > maxTransformBits) ? maxTransformBits : histoBits; + return res; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Bgra32 ToBgra32(TPixel color) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); + return bgra; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingle(uint p, Span a, Span r, Span g, Span b) + { + a[(int)(p >> 24) & 0xff]++; + r[(int)(p >> 16) & 0xff]++; + g[(int)(p >> 8) & 0xff]++; + b[(int)(p >> 0) & 0xff]++; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingleSubGreen(uint p, Span r, Span b) + { + int green = (int)p >> 8; // The upper bits are masked away later. + r[(int)((p >> 16) - green) & 0xff]++; + b[(int)((p >> 0) - green) & 0xff]++; + } + + private static uint HashPix(uint pix) + { + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. + return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; + } + + [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteComponentDistance(uint v) { return (v <= 128) ? v : (256 - v); diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 362bc7889..fc1ec3258 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -17,6 +17,209 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly byte[,][] ModesProba = new byte[10, 10][]; + /// + /// Lookup table for small values of log2(int). + /// + public static readonly float[] Log2Table = + { + 0.0000000000000000f, 0.0000000000000000f, + 1.0000000000000000f, 1.5849625007211560f, + 2.0000000000000000f, 2.3219280948873621f, + 2.5849625007211560f, 2.8073549220576041f, + 3.0000000000000000f, 3.1699250014423121f, + 3.3219280948873621f, 3.4594316186372973f, + 3.5849625007211560f, 3.7004397181410921f, + 3.8073549220576041f, 3.9068905956085187f, + 4.0000000000000000f, 4.0874628412503390f, + 4.1699250014423121f, 4.2479275134435852f, + 4.3219280948873626f, 4.3923174227787606f, + 4.4594316186372973f, 4.5235619560570130f, + 4.5849625007211560f, 4.6438561897747243f, + 4.7004397181410917f, 4.7548875021634682f, + 4.8073549220576037f, 4.8579809951275718f, + 4.9068905956085187f, 4.9541963103868749f, + 5.0000000000000000f, 5.0443941193584533f, + 5.0874628412503390f, 5.1292830169449663f, + 5.1699250014423121f, 5.2094533656289501f, + 5.2479275134435852f, 5.2854022188622487f, + 5.3219280948873626f, 5.3575520046180837f, + 5.3923174227787606f, 5.4262647547020979f, + 5.4594316186372973f, 5.4918530963296747f, + 5.5235619560570130f, 5.5545888516776376f, + 5.5849625007211560f, 5.6147098441152083f, + 5.6438561897747243f, 5.6724253419714951f, + 5.7004397181410917f, 5.7279204545631987f, + 5.7548875021634682f, 5.7813597135246599f, + 5.8073549220576037f, 5.8328900141647412f, + 5.8579809951275718f, 5.8826430493618415f, + 5.9068905956085187f, 5.9307373375628866f, + 5.9541963103868749f, 5.9772799234999167f, + 6.0000000000000000f, 6.0223678130284543f, + 6.0443941193584533f, 6.0660891904577720f, + 6.0874628412503390f, 6.1085244567781691f, + 6.1292830169449663f, 6.1497471195046822f, + 6.1699250014423121f, 6.1898245588800175f, + 6.2094533656289501f, 6.2288186904958804f, + 6.2479275134435852f, 6.2667865406949010f, + 6.2854022188622487f, 6.3037807481771030f, + 6.3219280948873626f, 6.3398500028846243f, + 6.3575520046180837f, 6.3750394313469245f, + 6.3923174227787606f, 6.4093909361377017f, + 6.4262647547020979f, 6.4429434958487279f, + 6.4594316186372973f, 6.4757334309663976f, + 6.4918530963296747f, 6.5077946401986963f, + 6.5235619560570130f, 6.5391588111080309f, + 6.5545888516776376f, 6.5698556083309478f, + 6.5849625007211560f, 6.5999128421871278f, + 6.6147098441152083f, 6.6293566200796094f, + 6.6438561897747243f, 6.6582114827517946f, + 6.6724253419714951f, 6.6865005271832185f, + 6.7004397181410917f, 6.7142455176661224f, + 6.7279204545631987f, 6.7414669864011464f, + 6.7548875021634682f, 6.7681843247769259f, + 6.7813597135246599f, 6.7944158663501061f, + 6.8073549220576037f, 6.8201789624151878f, + 6.8328900141647412f, 6.8454900509443747f, + 6.8579809951275718f, 6.8703647195834047f, + 6.8826430493618415f, 6.8948177633079437f, + 6.9068905956085187f, 6.9188632372745946f, + 6.9307373375628866f, 6.9425145053392398f, + 6.9541963103868749f, 6.9657842846620869f, + 6.9772799234999167f, 6.9886846867721654f, + 7.0000000000000000f, 7.0112272554232539f, + 7.0223678130284543f, 7.0334230015374501f, + 7.0443941193584533f, 7.0552824355011898f, + 7.0660891904577720f, 7.0768155970508308f, + 7.0874628412503390f, 7.0980320829605263f, + 7.1085244567781691f, 7.1189410727235076f, + 7.1292830169449663f, 7.1395513523987936f, + 7.1497471195046822f, 7.1598713367783890f, + 7.1699250014423121f, 7.1799090900149344f, + 7.1898245588800175f, 7.1996723448363644f, + 7.2094533656289501f, 7.2191685204621611f, + 7.2288186904958804f, 7.2384047393250785f, + 7.2479275134435852f, 7.2573878426926521f, + 7.2667865406949010f, 7.2761244052742375f, + 7.2854022188622487f, 7.2946207488916270f, + 7.3037807481771030f, 7.3128829552843557f, + 7.3219280948873626f, 7.3309168781146167f, + 7.3398500028846243f, 7.3487281542310771f, + 7.3575520046180837f, 7.3663222142458160f, + 7.3750394313469245f, 7.3837042924740519f, + 7.3923174227787606f, 7.4008794362821843f, + 7.4093909361377017f, 7.4178525148858982f, + 7.4262647547020979f, 7.4346282276367245f, + 7.4429434958487279f, 7.4512111118323289f, + 7.4594316186372973f, 7.4676055500829976f, + 7.4757334309663976f, 7.4838157772642563f, + 7.4918530963296747f, 7.4998458870832056f, + 7.5077946401986963f, 7.5156998382840427f, + 7.5235619560570130f, 7.5313814605163118f, + 7.5391588111080309f, 7.5468944598876364f, + 7.5545888516776376f, 7.5622424242210728f, + 7.5698556083309478f, 7.5774288280357486f, + 7.5849625007211560f, 7.5924570372680806f, + 7.5999128421871278f, 7.6073303137496104f, + 7.6147098441152083f, 7.6220518194563764f, + 7.6293566200796094f, 7.6366246205436487f, + 7.6438561897747243f, 7.6510516911789281f, + 7.6582114827517946f, 7.6653359171851764f, + 7.6724253419714951f, 7.6794800995054464f, + 7.6865005271832185f, 7.6934869574993252f, + 7.7004397181410917f, 7.7073591320808825f, + 7.7142455176661224f, 7.7210991887071855f, + 7.7279204545631987f, 7.7347096202258383f, + 7.7414669864011464f, 7.7481928495894605f, + 7.7548875021634682f, 7.7615512324444795f, + 7.7681843247769259f, 7.7747870596011736f, + 7.7813597135246599f, 7.7879025593914317f, + 7.7944158663501061f, 7.8008998999203047f, + 7.8073549220576037f, 7.8137811912170374f, + 7.8201789624151878f, 7.8265484872909150f, + 7.8328900141647412f, 7.8392037880969436f, + 7.8454900509443747f, 7.8517490414160571f, + 7.8579809951275718f, 7.8641861446542797f, + 7.8703647195834047f, 7.8765169465649993f, + 7.8826430493618415f, 7.8887432488982591f, + 7.8948177633079437f, 7.9008668079807486f, + 7.9068905956085187f, 7.9128893362299619f, + 7.9188632372745946f, 7.9248125036057812f, + 7.9307373375628866f, 7.9366379390025709f, + 7.9425145053392398f, 7.9483672315846778f, + 7.9541963103868749f, 7.9600019320680805f, + 7.9657842846620869f, 7.9715435539507719f, + 7.9772799234999167f, 7.9829935746943103f, + 7.9886846867721654f, 7.9943534368588577f + }; + + public static readonly float[] SLog2Table = + { + 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, + 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, + 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, + 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, + 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, + 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, + 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, + 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, + 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, + 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, + 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, + 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, + 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, + 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, + 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, + 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, + 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, + 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, + 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, + 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, + 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, + 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, + 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, + 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, + 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, + 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, + 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, + 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, + 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, + 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, + 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, + 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, + 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, + 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, + 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, + 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, + 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, + 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, + 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, + 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, + 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, + 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, + 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, + 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, + 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, + 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, + 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, + 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, + 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, + 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, + 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, + 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, + 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, + 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, + 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, + 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, + 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, + 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, + 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, + 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, + 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, + 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, + 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, + 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f + }; + public static readonly int[] CodeToPlane = { 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, From 5547202d394584c69c0bbe50c54098e27aa2e1f9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 23 May 2020 18:29:55 +0200 Subject: [PATCH 171/359] Implement HashChainFill --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 661 ++++++++++++++++++ .../Formats/WebP/Lossless/HuffmanTree.cs | 31 + .../Formats/WebP/Lossless/HuffmanTreeCode.cs | 26 + .../Formats/WebP/Lossless/HuffmanTreeToken.cs | 21 + .../Formats/WebP/Lossless/PixOrCopy.cs | 29 + .../Formats/WebP/Lossless/PixOrCopyMode.cs | 16 + .../Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 13 + .../Formats/WebP/Lossless/Vp8LEncoder.cs | 44 +- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 31 + .../Formats/WebP/Lossless/Vp8LHistogram.cs | 14 + .../Formats/WebP/Lossless/Vp8LLz77Type.cs | 14 + .../Formats/WebP/Lossless/Vp8LRefsCursor.cs | 18 + src/ImageSharp/Formats/WebP/WebPConstants.cs | 7 + .../Formats/WebP/WebPEncoderCore.cs | 157 ++++- .../Formats/WebP/WebPLookupTables.cs | 11 + 15 files changed, 1065 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs new file mode 100644 index 000000000..770cce5ec --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -0,0 +1,661 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class BackwardReferenceEncoder + { + private const int HashBits = 18; + + private const int HashSize = 1 << HashBits; + + private const uint HashMultiplierHi = 0xc6a4a793u; + + private const uint HashMultiplierLo = 0x5bd1e996u; + + private const float MaxEntropy = 1e30f; + + private const int WindowOffsetsSizeMax = 32; + + /// + /// Minimum block size for backward references. + /// + private const int MinBlockSize = 256; + + /// + /// The number of bits for the window size. + /// + private const int WindowSizeBits = 20; + + /// + /// 1M window (4M bytes) minus 120 special codes for short distances. + /// + private const int WindowSize = (1 << WindowSizeBits) - 120; + + /// + /// Maximum bit length. + /// + private const int MaxLengthBits = 12; + + /// + /// We want the max value to be attainable and stored in MaxLengthBits bits. + /// + private const int MaxLength = (1 << MaxLengthBits) - 1; + + /// + /// Minimum number of pixels for which it is cheaper to encode a + /// distance + length instead of each pixel as a literal. + /// + private const int MinLength = 4; + + public static void HashChainFill(Vp8LHashChain p, Span bgra, int quality, int xSize, int ySize) + { + int size = xSize * ySize; + int iterMax = GetMaxItersForQuality(quality); + int windowSize = GetWindowSizeForHashChain(quality, xSize); + int pos; + var hashToFirstIndex = new int[HashSize]; // TODO: use memory allocator + + // Initialize hashToFirstIndex array to -1. + hashToFirstIndex.AsSpan().Fill(-1); + + var chain = new int[size]; // TODO: use memory allocator. + + // Fill the chain linking pixels with the same hash. + var bgraComp = bgra[0] == bgra[1]; + for (pos = 0; pos < size - 2;) + { + uint hashCode; + bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; + if (bgraComp && bgraCompNext) + { + // Consecutive pixels with the same color will share the same hash. + // We therefore use a different hash: the color and its repetition length. + var tmp = new uint[2]; + uint len = 1; + tmp[0] = bgra[pos]; + + // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, + // as its next pixel does not have the same color, so we just need to get to + // the last pixel equal to its follower. + while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) + { + ++len; + } + + if (len > MaxLength) + { + // Skip the pixels that match for distance=1 and length>MaxLength + // because they are linked to their predecessor and we automatically + // check that in the main for loop below. Skipping means setting no + // predecessor in the chain, hence -1. + pos += (int)(len - MaxLength); + len = MaxLength; + } + + // Process the rest of the hash chain. + while (len > 0) + { + tmp[1] = len--; + hashCode = GetPixPairHash64(tmp); + chain[pos] = hashToFirstIndex[hashCode]; + hashToFirstIndex[hashCode] = pos++; + } + + bgraComp = false; + } + else + { + // Just move one pixel forward. + hashCode = GetPixPairHash64(bgra.Slice(pos)); + chain[pos] = hashToFirstIndex[hashCode]; + hashToFirstIndex[hashCode] = pos++; + bgraComp = bgraCompNext; + } + } + + // Process the penultimate pixel. + chain[pos] = hashToFirstIndex[GetPixPairHash64(bgra.Slice(pos))]; + + // Find the best match interval at each pixel, defined by an offset to the + // pixel and a length. The right-most pixel cannot match anything to the right + // (hence a best length of 0) and the left-most pixel nothing to the left + // (hence an offset of 0). + p.OffsetLength[0] = p.OffsetLength[size - 1] = 0; + for (int basePosition = size - 2; basePosition > 0;) + { + int maxLen = MaxFindCopyLength(size - 1 - basePosition); + int bgraStart = basePosition; + int iter = iterMax; + int bestLength = 0; + uint bestDistance = 0; + uint bestBgra; + int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; + int lengthMax = (maxLen < 256) ? maxLen : 256; + uint maxBasePosition; + pos = (int)chain[basePosition]; + int currLength; + + // Heuristic: use the comparison with the above line as an initialization. + if (basePosition >= (uint)xSize) + { + currLength = FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = (uint)xSize; + } + + iter--; + } + + // Heuristic: compare to the previous pixel. + currLength = FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = 1; + } + + iter--; + + if (bestLength == MaxLength) + { + pos = minPos - 1; + } + + bestBgra = bgra.Slice(bgraStart)[bestLength]; + + for (; pos >= minPos && (--iter > 0); pos = chain[pos]) + { + if (bgra[pos + bestLength] != bestBgra) + { + continue; + } + + currLength = VectorMismatch(bgra.Slice(pos), bgra.Slice(bgraStart), maxLen); + if (bestLength < currLength) + { + bestLength = currLength; + bestDistance = (uint)(basePosition - pos); + bestBgra = bgra.Slice(bgraStart)[bestLength]; + + // Stop if we have reached a good enough length. + if (bestLength >= lengthMax) + { + break; + } + } + } + + // We have the best match but in case the two intervals continue matching + // to the left, we have the best matches for the left-extended pixels. + maxBasePosition = (uint)basePosition; + while (true) + { + p.OffsetLength[basePosition] = (bestDistance << MaxLengthBits) | (uint)bestLength; + --basePosition; + + // Stop if we don't have a match or if we are out of bounds. + if (bestDistance == 0 || basePosition == 0) + { + break; + } + + // Stop if we cannot extend the matching intervals to the left. + if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) + { + break; + } + + // Stop if we are matching at its limit because there could be a closer + // matching interval with the same maximum length. Then again, if the + // matching interval is as close as possible (best_distance == 1), we will + // never find anything better so let's continue. + if (bestLength == MaxLength && bestDistance != 1 && basePosition + MaxLength < maxBasePosition) + { + break; + } + + if (bestLength < MaxLength) + { + bestLength++; + maxBasePosition = (uint)basePosition; + } + } + } + + int foo = 0; + } + + /// + /// Evaluates best possible backward references for specified quality. + /// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache + /// bits to use (passing 0 implies disabling the local color cache). + /// The optimal cache bits is evaluated and set for the *cache_bits parameter. + /// The return value is the pointer to the best of the two backward refs viz, + /// refs[0] or refs[1]. + /// + private static Vp8LBackwardRefs[] GetBackwardReferences(int width, int height, uint[] bgra, int quality, + int lz77TypesToTry, int[] cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] best, Vp8LBackwardRefs[] worst) + { + var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits]; + int lz77Type = 0; + int lz77TypeBest = 0; + double bitCostBest = -1; + int[] cacheBitsInitial = cacheBits; + // TODO: var hashChainBox = new Vp8LHashChain(); + for (lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) + { + int res = 0; + double bitCost; + int[] cacheBitsTmp = cacheBitsInitial; + if ((lz77TypesToTry & lz77Type) == 0) + { + continue; + } + + switch ((Vp8LLz77Type)lz77Type) + { + case Vp8LLz77Type.Lz77Rle: + BackwardReferencesRle(width, height, bgra, 0, worst); + break; + case Vp8LLz77Type.Lz77Standard: + // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color + // cache is not that different in practice. + BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); + break; + case Vp8LLz77Type.Lz77Box: + // TODO: HashChainInit(hashChainBox, width * height); + //BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); + break; + } + + // Next, try with a color cache and update the references. + CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); + if (cacheBitsTmp[0] > 0) + { + BackwardRefsWithLocalCache(bgra, cacheBitsTmp[0], worst); + } + + // Keep the best backward references. + // TODO: VP8LHistogramCreate(histo, worst, cacheBitsTmp); + bitCost = histo[0].EstimateBits(); + + if (lz77TypeBest == 0 || bitCost < bitCostBest) + { + Vp8LBackwardRefs[] tmp = worst; + worst = best; + best = tmp; + bitCostBest = bitCost; + //*cacheBits = cacheBitsTmp; + lz77TypeBest = lz77Type; + } + } + + // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). + if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) + { + /*HashChain[] hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox; + if (BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst)) + { + double bitCostTrace; + //HistogramCreate(histo, worst, cacheBits); + bitCostTrace = histo[0].EstimateBits(); + if (bitCostTrace < bitCostBest) + { + best = worst; + } + }*/ + } + + BackwardReferences2DLocality(width, best); + + return best; + } + + /// + /// Evaluate optimal cache bits for the local color cache. + /// The input *best_cache_bits sets the maximum cache bits to use (passing 0 + /// implies disabling the local color cache). The local color cache is also + /// disabled for the lower (<= 25) quality. + /// + private static void CalculateBestCacheSize(uint[] bgra, int quality, Vp8LBackwardRefs[] refs, int[] bestCacheBits) + { + int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits[0]; + double entropyMin = MaxEntropy; + var ccInit = new int[WebPConstants.MaxColorCacheBits + 1]; + var hashers = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; + var c = new Vp8LRefsCursor(refs); + var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; + if (cacheBitsMax == 0) + { + // Local color cache is disabled. + bestCacheBits[0] = 0; + return; + } + + // Find the cache_bits giving the lowest entropy. The search is done in a + // brute-force way as the function (entropy w.r.t cache_bits) can be anything in practice. + //while (VP8LRefsCursorOk(&c)) + /*while (true) + { + //PixOrCopy[] v = c.cur_pos; + if (v.IsLiteral()) + { + uint pix = *bgra++; + uint a = (pix >> 24) & 0xff; + uint r = (pix >> 16) & 0xff; + uint g = (pix >> 8) & 0xff; + uint b = (pix >> 0) & 0xff; + + // The keys of the caches can be derived from the longest one. + int key = HashPix(pix, 32 - cacheBitsMax); + + // Do not use the color cache for cache_bits = 0. + ++histos[0].blue[b]; + ++histos[0].literal[g]; + ++histos[0].red[r]; + ++histos[0].alpha[a]; + + // Deal with cache_bits > 0. + for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) + { + if (VP8LColorCacheLookup(hashers[i], key) == pix) + { + ++histos[i]->literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key]; + } + else + { + VP8LColorCacheSet(hashers[i], key, pix); + ++histos[i].blue[b]; + ++histos[i].literal[g]; + ++histos[i].red[r]; + ++histos[i].alpha[a]; + } + } + } + else + { + // We should compute the contribution of the (distance,length) + // histograms but those are the same independently from the cache size. + // As those constant contributions are in the end added to the other + // histogram contributions, we can safely ignore them. + + } + }*/ + } + + private static void BackwardReferencesTraceBackwards() + { + + } + + private static void BackwardReferencesLz77(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs) + { + int iLastCheck = -1; + int ccInit = 0; + bool useColorCache = cacheBits > 0; + int pixCount = xSize * ySize; + var hashers = new ColorCache(); + if (useColorCache) + { + hashers.Init(cacheBits); + } + + // TODO: VP8LClearBackwardRefs(refs); + for (int i = 0; i < pixCount;) + { + // Alternative #1: Code the pixels starting at 'i' using backward reference. + int offset = 0; + int len = 0; + int j; + // TODO: VP8LHashChainFindCopy(hashChain, i, offset, ref len); + if (len >= MinLength) + { + int lenIni = len; + int maxReach = 0; + int jMax = (i + lenIni >= pixCount) ? pixCount - 1 : i + lenIni; + + // Only start from what we have not checked already. + iLastCheck = (i > iLastCheck) ? i : iLastCheck; + + // We know the best match for the current pixel but we try to find the + // best matches for the current pixel AND the next one combined. + // The naive method would use the intervals: + // [i,i+len) + [i+len, length of best match at i+len) + // while we check if we can use: + // [i,j) (where j<=i+len) + [j, length of best match at j) + for (j = iLastCheck + 1; j <= jMax; j++) + { + int lenJ = 0; // TODO: HashChainFindLength(hashChain, j); + int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. + if (reach > maxReach) + { + len = j - i; + maxReach = reach; + if (maxReach >= pixCount) + { + break; + } + } + } + } + else + { + len = 1; + } + + // Go with literal or backward reference. + /*if (len == 1) + { + AddSingleLiteral(bgra[i], useColorCache, hashers, refs); + } + else + { + VP8LBackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len)); + if (useColorCache) + { + for (j = i; j < i + len; ++j) + { + VP8LColorCacheInsert(hashers, bgra[j]); + } + } + } + */ + i += len; + } + } + + /// + /// Compute an LZ77 by forcing matches to happen within a given distance cost. + /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. + /// + private static void BackwardReferencesLz77Box(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChainBest, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs) + { + int i; + int pixCount = xSize * ySize; + short[] counts; + var windowOffsets = new int[WindowOffsetsSizeMax]; + var windowOffsetsNew = new int[WindowOffsetsSizeMax]; + int windowOffsetsSize = 0; + int windowOffsetsNewSize = 0; + short[] countsIni = new short[xSize * ySize]; + int bestOffsetPrev = -1; + int bestLengthPrev = -1; + + // counts[i] counts how many times a pixel is repeated starting at position i. + i = pixCount - 2; + /*counts = countsIni + i; + counts[1] = 1; + for (; i >= 0; i--, counts--) + { + if (bgra[i] == bgra[i + 1]) + { + // Max out the counts to MAX_LENGTH. + counts[0] = counts[1] + (counts[1] != MaxLength); + } + else + { + counts[0] = 1; + } + }*/ + } + + private static void BackwardReferencesRle(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs) + { + int pixCount = xSize * ySize; + int i, k; + bool useColorCache = cacheBits > 0; + } + + /// + /// Update (in-place) backward references for the specified cacheBits. + /// + private static void BackwardRefsWithLocalCache(uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs) + { + int pixelIndex = 0; + var c = new Vp8LRefsCursor(refs); + var hashers = new ColorCache(); + hashers.Init(cacheBits); + //while (VP8LRefsCursorOk(&c)) + /*while (true) + { + PixOrCopy[] v = c.curPos; + if (v.IsLiteral()) + { + uint bgraLiteral = v.BgraOrDistance; + int ix = VP8LColorCacheContains(hashers, bgraLiteral); + if (ix >= 0) + { + // hashers contains bgraLiteral + v = PixOrCopyCreateCacheIdx(ix); + } + else + { + VP8LColorCacheInsert(hashers, bgraLiteral); + } + + pixelIndex++; + } + else + { + // refs was created without local cache, so it can not have cache indexes. + for (int k = 0; k < v.len; k++) + { + VP8LColorCacheInsert(hashers, bgra[pixelIndex++]); + } + } + + VP8LRefsCursorNext(c); + } + + VP8LColorCacheClear(hashers);*/ + } + + private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs[] refs) + { + var c = new Vp8LRefsCursor(refs); + /*while (VP8LRefsCursorOk(&c)) + { + if (c.cur_pos.IsCopy()) + { + int dist = c.curPos.ArgbOrDistance; + int transformedDist = DistanceToPlaneCode(xSize, dist); + c.curPos.ArgbOrDistance = transformedDist; + } + + VP8LRefsCursorNext(&c); + }*/ + } + + private static int DistanceToPlaneCode(int xSize, int dist) + { + int yOffset = dist / xSize; + int xOffset = dist - (yOffset * xSize); + if (xOffset <= 8 && yOffset < 8) + { + return (int)WebPLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; + } + else if (xOffset > xSize - 8 && yOffset < 7) + { + return (int)WebPLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; + } + + return dist + 120; + } + + /// + /// Returns the exact index where array1 and array2 are different. For an index + /// inferior or equal to best_len_match, the return value just has to be strictly + /// inferior to best_len_match. The current behavior is to return 0 if this index + /// is best_len_match, and the index itself otherwise. + /// If no two elements are the same, it returns max_limit. + /// + private static int FindMatchLength(Span array1, Span array2, int bestLenMatch, int maxLimit) + { + // Before 'expensive' linear match, check if the two arrays match at the + // current best length index. + if (array1[bestLenMatch] != array2[bestLenMatch]) + { + return 0; + } + + return VectorMismatch(array1, array2, maxLimit); + } + + private static int VectorMismatch(Span array1, Span array2, int length) + { + int matchLen = 0; + + while (matchLen < length && array1[matchLen] == array2[matchLen]) + { + matchLen++; + } + + return matchLen; + } + + /// + /// Calculates the hash for a pixel pair. + /// + /// An Span with two pixels. + /// The hash. + private static uint GetPixPairHash64(Span bgra) + { + uint key = bgra[1] * HashMultiplierHi; + key += bgra[0] * HashMultiplierLo; + key = key >> (32 - HashBits); + return key; + } + + /// + /// Returns the maximum number of hash chain lookups to do for a + /// given compression quality. Return value in range [8, 86]. + /// + /// The quality. + /// Number of hash chain lookups. + private static int GetMaxItersForQuality(int quality) + { + return 8 + (quality * quality / 128); + } + + private static int MaxFindCopyLength(int len) + { + return (len < MaxLength) ? len : MaxLength; + } + + private static int GetWindowSizeForHashChain(int quality, int xSize) + { + int maxWindowSize = (quality > 75) ? WindowSize + : (quality > 50) ? (xSize << 8) + : (quality > 25) ? (xSize << 6) + : (xSize << 4); + + return (maxWindowSize > WindowSize) ? WindowSize : maxWindowSize; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs new file mode 100644 index 000000000..3fba3327d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Represents the Huffman tree. + /// + internal class HuffmanTree + { + /// + /// Gets the symbol frequency. + /// + public int TotalCount { get; } + + /// + /// Gets the symbol value. + /// + public int Value { get; } + + /// + /// Gets the index for the left sub-tree. + /// + public int PoolIndexLeft { get; } + + /// + /// Gets the index for the right sub-tree. + /// + public int PoolIndexRight { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs new file mode 100644 index 000000000..fe582b8f2 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Represents the tree codes (depth and bits array). + /// + internal class HuffmanTreeCode + { + /// + /// Gets the number of symbols. + /// + public int NumSymbols { get; } + + /// + /// Gets the code lengths of the symbols. + /// + public byte[] CodeLengths { get; } + + /// + /// Gets the symbol Codes. + /// + public short[] Codes { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs new file mode 100644 index 000000000..a140a2c21 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Holds the tree header in coded form. + /// + internal class HuffmanTreeToken + { + /// + /// Gets the code. Value (0..15) or escape code (16, 17, 18). + /// + public byte Code { get; } + + /// + /// Gets extra bits for escape codes. + /// + public byte ExtraBits { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs new file mode 100644 index 000000000..30ee2f5f4 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class PixOrCopy + { + public PixOrCopyMode Mode { get; } + + public short Len { get; } + + public uint ArgbOrDistance { get; } + + public bool IsLiteral() + { + return this.Mode == PixOrCopyMode.Literal; + } + + public bool IsCacheIdx() + { + return this.Mode == PixOrCopyMode.CacheIdx; + } + + public bool IsCopy() + { + return this.Mode == PixOrCopyMode.Copy; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs new file mode 100644 index 000000000..043a174fc --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal enum PixOrCopyMode + { + Literal, + + CacheIdx, + + Copy, + + None + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs new file mode 100644 index 000000000..b5da33ea3 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class Vp8LBackwardRefs + { + /// + /// Common block-size. + /// + public int BlockSize { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 9e35cc1cc..7df49bac3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -12,9 +12,31 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ///
internal class Vp8LEncoder : IDisposable { - public Vp8LEncoder(MemoryAllocator memoryAllocator) + /// + /// Maximum number of reference blocks the image will be segmented into. + /// + private const int MaxRefsBlockPerImage = 16; + + /// + /// Minimum block size for backward references. + /// + private const int MinBlockSize = 256; + + public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height) { + var pixelCount = width * height; + this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); + this.Refs = new Vp8LBackwardRefs[3]; + this.HashChain = new Vp8LHashChain(pixelCount); + + // We round the block size up, so we're guaranteed to have at most MAX_REFS_BLOCK_PER_IMAGE blocks used: + int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; + for (int i = 0; i < this.Refs.Length; ++i) + { + this.Refs[i] = new Vp8LBackwardRefs(); + this.Refs[i].BlockSize = (refsBlockSize < MinBlockSize) ? MinBlockSize : refsBlockSize; + } } /// @@ -28,24 +50,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public int TransformBits { get; set; } /// - /// Gets or sets the cache bits. + /// Gets or sets a value indicating whether to use a color cache. /// - public bool CacheBits { get; } + public bool UseColorCache { get; set; } /// - /// Gets a value indicating whether to use the cross color transform. + /// Gets or sets a value indicating whether to use the cross color transform. /// - public bool UseCrossColorTransform { get; } + public bool UseCrossColorTransform { get; set; } /// - /// Gets a value indicating whether to use the substract green transform. + /// Gets or sets a value indicating whether to use the substract green transform. /// - public bool UseSubtractGreenTransform { get; } + public bool UseSubtractGreenTransform { get; set; } /// - /// Gets a value indicating whether to use the predictor transform. + /// Gets or sets a value indicating whether to use the predictor transform. /// - public bool UsePredictorTransform { get; } + public bool UsePredictorTransform { get; set; } /// /// Gets or sets a value indicating whether to use color indexing transform. @@ -62,6 +84,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public IMemoryOwner Palette { get; } + public Vp8LBackwardRefs[] Refs { get; } + + public Vp8LHashChain HashChain { get; } + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs new file mode 100644 index 000000000..0639b545a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class Vp8LHashChain + { + /// + /// The 20 most significant bits contain the offset at which the best match is found. + /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). + /// The lower 12 bits contain the length of the match. The 12 bit limit is + /// defined in MaxFindCopyLength with MAX_LENGTH=4096. + /// + public uint[] OffsetLength { get; } + + /// + /// This is the maximum size of the hash_chain that can be constructed. + /// Typically this is the pixel count (width x height) for a given image. + /// + public int Size { get; } + + public Vp8LHashChain(int size) + { + this.OffsetLength = new uint[size]; + this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); + this.Size = size; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs new file mode 100644 index 000000000..af0a57526 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class Vp8LHistogram + { + public double EstimateBits() + { + // TODO: implement this. + return 0.0; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs new file mode 100644 index 000000000..63d9f6e02 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal enum Vp8LLz77Type + { + Lz77Standard = 1, + + Lz77Rle = 2, + + Lz77Box = 4 + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs new file mode 100644 index 000000000..ce423c39a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class Vp8LRefsCursor + { + public Vp8LRefsCursor(Vp8LBackwardRefs[] refs) + { + //this.Refs = refs; + this.CurrentPos = 0; + } + + public PixOrCopy[] Refs { get; } + + public int CurrentPos { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 8437a091b..97d5a57c9 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -102,6 +102,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public const int MaxNumberOfTransforms = 4; + /// + /// The bit to be written when next data to be read is a transform. + /// + public const int TransformPresent = 1; + /// /// The maximum allowed width or height of a webp image. /// @@ -123,6 +128,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int NumDistanceCodes = 40; + public const int CodeLengthCodes = 19; + public const int LengthTableBits = 7; public const uint CodeLengthLiterals = 16; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index f9d2d7b89..5a5de9909 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodeStream(Image image) where TPixel : unmanaged, IPixel { - var encoder = new Vp8LEncoder(this.memoryAllocator); + var encoder = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height); // Analyze image (entropy, num_palettes etc). this.EncoderAnalyze(image, encoder); @@ -118,17 +118,134 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncoderAnalyze(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { + int width = image.Width; + int height = image.Height; + // Check if we only deal with a small number of colors and should use a palette. var usePalette = this.AnalyzeAndCreatePalette(image, enc); // Empirical bit sizes. int method = 4; // TODO: method hardcoded to 4 for now. - enc.HistoBits = GetHistoBits(method, usePalette, image.Width, image.Height); + enc.HistoBits = GetHistoBits(method, usePalette, width, height); enc.TransformBits = GetTransformBits(method, enc.HistoBits); + // Convert image pixels to bgra array. + using System.Buffers.IMemoryOwner bgraBuffer = this.memoryAllocator.Allocate(width * height); + Span bgra = bgraBuffer.Memory.Span; + int idx = 0; + for (int y = 0; y < height; y++) + { + Span rowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + } + } + // Try out multiple LZ77 on images with few colors. var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; - this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + + enc.UsePalette = entropyIdx == EntropyIx.Palette; + enc.UseSubtractGreenTransform = (entropyIdx == EntropyIx.SubGreen) || (entropyIdx == EntropyIx.SpatialSubGreen); + enc.UsePredictorTransform = (entropyIdx == EntropyIx.Spatial) || (entropyIdx == EntropyIx.SpatialSubGreen); + enc.UseCrossColorTransform = redAndBlueAlwaysZero ? false : enc.UsePredictorTransform; + enc.UseColorCache = false; + + // Encode palette. + if (enc.UsePalette) + { + this.EncodePalette(image, bgra, enc); + } + } + + /// + /// Save the palette to the bitstream. + /// + /// The image. + /// The Vp8L Encoder. + private void EncodePalette(Image image, Span bgra, Vp8LEncoder enc) + where TPixel : unmanaged, IPixel + { + var tmpPalette = new uint[WebPConstants.MaxPaletteSize]; + int paletteSize = enc.PaletteSize; + Span palette = enc.Palette.Memory.Span; + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); + this.bitWriter.PutBits((uint)paletteSize - 1, 8); + for (int i = paletteSize - 1; i >= 1; i--) + { + tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); + } + + tmpPalette[0] = palette[0]; + this.EncodeImageNoHuffman(image, tmpPalette, enc); + } + + private void EncodeImageNoHuffman(Image image, Span bgra, Vp8LEncoder enc) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + int paletteSize = enc.PaletteSize; + Vp8LHashChain hashChain = enc.HashChain; + var huffmanCodes = new HuffmanTreeCode[5]; + HuffmanTreeToken[] tokens; + var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; + + int quality = 20; // TODO: hardcoded for now. + + // Calculate backward references from ARGB image. + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, paletteSize, 1); + + //var refs = GetBackwardReferences(width, height, argb, quality, 0, kLZ77Standard | kLZ77RLE, cacheBits, hashChain, refsTmp1, refsTmp2); + + // Build histogram image and symbols from backward references. + //VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]); + + // Create Huffman bit lengths and codes for each histogram image. + //GetHuffBitLengthsAndCodes(histogram_image, huffman_codes) + + // No color cache, no Huffman image. + this.bitWriter.PutBits(0, 1); + + // Find maximum number of symbols for the huffman tree-set. + /*for (i = 0; i < 5; ++i) + { + HuffmanTreeCode * const codes = &huffman_codes[i]; + if (max_tokens < codes->num_symbols) + { + max_tokens = codes->num_symbols; + } + }*/ + + // Store Huffman codes. + /* + for (i = 0; i < 5; ++i) + { + HuffmanTreeCode * const codes = &huffman_codes[i]; + StoreHuffmanCode(bw, huff_tree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + StoreImageToBitMask(bw, width, 0, refs, histogram_symbols, huffman_codes); + */ + } + + private void StoreImageToBitMask(int width, int histoBits, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + { + int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; + int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); + + // x and y trace the position in the image. + int x = 0; + int y = 0; + int tileX = x & tileMask; + int tileY = y & tileMask; + int histogramIx = histogramSymbols[0]; + Span codes = huffmanCodes.AsSpan(5 * histogramIx); + } /// @@ -161,10 +278,10 @@ namespace SixLabors.ImageSharp.Formats.WebP Span prevRow = null; for (int y = 0; y < height; y++) { + Span currentRow = image.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { - Span currentRow = image.GetPixelRowSpan(y); - Bgra32 pix = ToBgra32(currentRow[0]); + Bgra32 pix = ToBgra32(currentRow[x]); uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); pixPrev = pix; if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) @@ -180,25 +297,26 @@ namespace SixLabors.ImageSharp.Formats.WebP histo.Slice((int)HistoIx.HistoBlue * 256)); AddSingle( pixDiff, - histo.Slice((int)HistoIx.HistoAlpha * 256), - histo.Slice((int)HistoIx.HistoRed * 256), - histo.Slice((int)HistoIx.HistoGreen * 256), - histo.Slice((int)HistoIx.HistoBlue * 256)); + histo.Slice((int)HistoIx.HistoAlphaPred * 256), + histo.Slice((int)HistoIx.HistoRedPred * 256), + histo.Slice((int)HistoIx.HistoGreenPred * 256), + histo.Slice((int)HistoIx.HistoBluePred * 256)); AddSingleSubGreen( pix.PackedValue, histo.Slice((int)HistoIx.HistoRedSubGreen * 256), histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); AddSingleSubGreen( pixDiff, - histo.Slice((int)HistoIx.HistoRedSubGreen * 256), - histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + histo.Slice((int)HistoIx.HistoRedPredSubGreen * 256), + histo.Slice((int)HistoIx.HistoBluePredSubGreen * 256)); // Approximate the palette by the entropy of the multiplicative hash. uint hash = HashPix(pix.PackedValue); histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; - - prevRow = currentRow; } + + var histo0 = histo[0]; + prevRow = currentRow; } var entropyComp = new double[(int)HistoIx.HistoTotal]; @@ -206,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; // Let's add one zero to the predicted histograms. The zeros are removed - // too efficiently by the pix_diff == 0 comparison, at least one of the + // too efficiently by the pixDiff == 0 comparison, at least one of the // zeros is likely to exist. histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; @@ -218,8 +336,9 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) { var bitEntropy = new Vp8LBitEntropy(); - bitEntropy.BitsEntropyUnrefined(histo, 256); - entropyComp[j] = bitEntropy.BitsEntropyRefine(histo.Slice(j * 256), 256); + Span curHisto = histo.Slice(j * 256, 256); + bitEntropy.BitsEntropyUnrefined(curHisto, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(curHisto, 256); } entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + @@ -247,8 +366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP LosslessUtils.SubSampleSize(height, transformBits) * LosslessUtils.FastLog2(14); - // For color transforms: 24 as only 3 channels are considered in a - // ColorTransformElement. + // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * LosslessUtils.SubSampleSize(height, transformBits) * LosslessUtils.FastLog2(24); @@ -260,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.WebP entropy[(int)EntropyIx.Palette] += paletteSize * 8; EntropyIx minEntropyIx = EntropyIx.Direct; - for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; ++k) + for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) { if (entropy[(int)minEntropyIx] > entropy[k]) { @@ -307,6 +425,7 @@ namespace SixLabors.ImageSharp.Formats.WebP enc.PaletteSize = this.GetColorPalette(image, palette); if (enc.PaletteSize > WebPConstants.MaxPaletteSize) { + enc.PaletteSize = 0; return false; } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index fc1ec3258..8b1466c23 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -236,6 +236,17 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 }; + public static readonly uint[] PlaneToCodeLut = { + 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, + 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, + 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, + 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, + 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, + 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, + 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 + }; + // 31 ^ clz(i) public static readonly byte[] LogTable8bit = { From 5860f8c84fc6ea6b642059b8629f2bfdc7836679 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 25 May 2020 18:40:48 +0200 Subject: [PATCH 172/359] Implement backward reference encoder --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 471 +++++++++++++----- .../Formats/WebP/Lossless/ColorCache.cs | 46 +- .../Formats/WebP/Lossless/PixOrCopy.cs | 42 +- .../Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 14 + .../Formats/WebP/Lossless/Vp8LHashChain.cs | 10 + .../Formats/WebP/Lossless/Vp8LHistogram.cs | 52 ++ .../Formats/WebP/Lossless/Vp8LRefsCursor.cs | 8 +- .../Formats/WebP/WebPEncoderCore.cs | 30 +- 8 files changed, 533 insertions(+), 140 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 770cce5ec..49e9edd2f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -2,12 +2,17 @@ // Licensed under the GNU Affero General Public License, Version 3. using System; -using System.Runtime.InteropServices; +using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class BackwardReferenceEncoder { + /// + /// Maximum bit length. + /// + public const int MaxLengthBits = 12; + private const int HashBits = 18; private const int HashSize = 1 << HashBits; @@ -35,11 +40,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private const int WindowSize = (1 << WindowSizeBits) - 120; - /// - /// Maximum bit length. - /// - private const int MaxLengthBits = 12; - /// /// We want the max value to be attainable and stored in MaxLengthBits bits. /// @@ -122,8 +122,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Find the best match interval at each pixel, defined by an offset to the // pixel and a length. The right-most pixel cannot match anything to the right - // (hence a best length of 0) and the left-most pixel nothing to the left - // (hence an offset of 0). + // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). p.OffsetLength[0] = p.OffsetLength[size - 1] = 0; for (int basePosition = size - 2; basePosition > 0;) { @@ -227,8 +226,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } } - - int foo = 0; } /// @@ -239,20 +236,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The return value is the pointer to the best of the two backward refs viz, /// refs[0] or refs[1]. /// - private static Vp8LBackwardRefs[] GetBackwardReferences(int width, int height, uint[] bgra, int quality, - int lz77TypesToTry, int[] cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] best, Vp8LBackwardRefs[] worst) + public static Vp8LBackwardRefs GetBackwardReferences(int width, int height, Span bgra, int quality, + int lz77TypesToTry, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) { var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits]; int lz77Type = 0; int lz77TypeBest = 0; double bitCostBest = -1; - int[] cacheBitsInitial = cacheBits; - // TODO: var hashChainBox = new Vp8LHashChain(); + int cacheBitsInitial = cacheBits; + Vp8LHashChain hashChainBox = null; for (lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) { int res = 0; double bitCost; - int[] cacheBitsTmp = cacheBitsInitial; + int cacheBitsTmp = cacheBitsInitial; if ((lz77TypesToTry & lz77Type) == 0) { continue; @@ -264,34 +261,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless BackwardReferencesRle(width, height, bgra, 0, worst); break; case Vp8LLz77Type.Lz77Standard: - // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color - // cache is not that different in practice. + // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache is not that different in practice. BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); break; case Vp8LLz77Type.Lz77Box: - // TODO: HashChainInit(hashChainBox, width * height); - //BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); + hashChainBox = new Vp8LHashChain(width * height); + BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); break; } // Next, try with a color cache and update the references. - CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); - if (cacheBitsTmp[0] > 0) + cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); + if (cacheBitsTmp > 0) { - BackwardRefsWithLocalCache(bgra, cacheBitsTmp[0], worst); + BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst); } // Keep the best backward references. - // TODO: VP8LHistogramCreate(histo, worst, cacheBitsTmp); + histo[0] = new Vp8LHistogram(worst, cacheBitsTmp); bitCost = histo[0].EstimateBits(); if (lz77TypeBest == 0 || bitCost < bitCostBest) { - Vp8LBackwardRefs[] tmp = worst; + Vp8LBackwardRefs tmp = worst; worst = best; best = tmp; bitCostBest = bitCost; - //*cacheBits = cacheBitsTmp; + cacheBits = cacheBitsTmp; lz77TypeBest = lz77Type; } } @@ -299,17 +295,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) { - /*HashChain[] hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox; - if (BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst)) + Vp8LHashChain hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox; + BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); + histo[0] = new Vp8LHistogram(worst, cacheBits); + double bitCostTrace = histo[0].EstimateBits(); + if (bitCostTrace < bitCostBest) { - double bitCostTrace; - //HistogramCreate(histo, worst, cacheBits); - bitCostTrace = histo[0].EstimateBits(); - if (bitCostTrace < bitCostBest) - { - best = worst; - } - }*/ + best = worst; + } } BackwardReferences2DLocality(width, best); @@ -319,62 +312,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Evaluate optimal cache bits for the local color cache. - /// The input *best_cache_bits sets the maximum cache bits to use (passing 0 - /// implies disabling the local color cache). The local color cache is also - /// disabled for the lower (<= 25) quality. + /// The input *best_cache_bits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). + /// The local color cache is also disabled for the lower (<= 25) quality. /// - private static void CalculateBestCacheSize(uint[] bgra, int quality, Vp8LBackwardRefs[] refs, int[] bestCacheBits) + private static int CalculateBestCacheSize(Span bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { - int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits[0]; + int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits; double entropyMin = MaxEntropy; + int pos = 0; var ccInit = new int[WebPConstants.MaxColorCacheBits + 1]; - var hashers = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; - var c = new Vp8LRefsCursor(refs); + var colorCache = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; if (cacheBitsMax == 0) { // Local color cache is disabled. - bestCacheBits[0] = 0; - return; + return 0; } // Find the cache_bits giving the lowest entropy. The search is done in a // brute-force way as the function (entropy w.r.t cache_bits) can be anything in practice. - //while (VP8LRefsCursorOk(&c)) - /*while (true) + using List.Enumerator c = refs.Refs.GetEnumerator(); + while (c.MoveNext()) { - //PixOrCopy[] v = c.cur_pos; + PixOrCopy v = c.Current; if (v.IsLiteral()) { - uint pix = *bgra++; + uint pix = bgra[pos++]; uint a = (pix >> 24) & 0xff; uint r = (pix >> 16) & 0xff; uint g = (pix >> 8) & 0xff; uint b = (pix >> 0) & 0xff; // The keys of the caches can be derived from the longest one. - int key = HashPix(pix, 32 - cacheBitsMax); + int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); // Do not use the color cache for cache_bits = 0. - ++histos[0].blue[b]; - ++histos[0].literal[g]; - ++histos[0].red[r]; - ++histos[0].alpha[a]; + ++histos[0].Blue[b]; + ++histos[0].Literal[g]; + ++histos[0].Red[r]; + ++histos[0].Alpha[a]; // Deal with cache_bits > 0. - for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) + for (int i = cacheBitsMax; i >= 1; i--, key >>= 1) { - if (VP8LColorCacheLookup(hashers[i], key) == pix) + if (colorCache[i].Lookup(key) == pix) { - ++histos[i]->literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key]; + ++histos[i].Literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key]; } else { - VP8LColorCacheSet(hashers[i], key, pix); - ++histos[i].blue[b]; - ++histos[i].literal[g]; - ++histos[i].red[r]; - ++histos[i].alpha[a]; + colorCache[i].Set((uint)key, pix); + ++histos[i].Blue[b]; + ++histos[i].Literal[g]; + ++histos[i].Red[r]; + ++histos[i].Alpha[a]; } } } @@ -384,36 +375,75 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // histograms but those are the same independently from the cache size. // As those constant contributions are in the end added to the other // histogram contributions, we can safely ignore them. + int len = v.Len; + uint bgraPrev = bgra[pos] ^ 0xffffffffu; + + // Update the color caches. + do + { + if (bgra[pos] != bgraPrev) + { + // Efficiency: insert only if the color changes. + int key = ColorCache.HashPix(bgra[pos], 32 - cacheBitsMax); + for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) + { + colorCache[i].Colors[key] = bgra[pos]; + } + bgraPrev = bgra[pos]; + } + + pos++; + } + while (--len != 0); } - }*/ + } + + for (int i = 0; i <= cacheBitsMax; i++) + { + double entropy = histos[i].EstimateBits(); + if (i == 0 || entropy < entropyMin) + { + entropyMin = entropy; + bestCacheBits = i; + } + } + + return bestCacheBits; } - private static void BackwardReferencesTraceBackwards() + private static void BackwardReferencesTraceBackwards(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refsSrc, Vp8LBackwardRefs refsDst) { - + int distArraySize = xSize * ySize; + var distArray = new short[distArraySize]; + short[] chosenPath; + int chosenPathSize = 0; + + // TODO: + // BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); + // TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize); + // BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); } - private static void BackwardReferencesLz77(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs) + private static void BackwardReferencesLz77(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int iLastCheck = -1; int ccInit = 0; bool useColorCache = cacheBits > 0; int pixCount = xSize * ySize; - var hashers = new ColorCache(); + var colorCache = new ColorCache(); if (useColorCache) { - hashers.Init(cacheBits); + colorCache.Init(cacheBits); } // TODO: VP8LClearBackwardRefs(refs); for (int i = 0; i < pixCount;) { // Alternative #1: Code the pixels starting at 'i' using backward reference. - int offset = 0; - int len = 0; int j; - // TODO: VP8LHashChainFindCopy(hashChain, i, offset, ref len); + int offset = hashChain.FindOffset(i); + int len = hashChain.FindLength(i); if (len >= MinLength) { int lenIni = len; @@ -431,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // [i,j) (where j<=i+len) + [j, length of best match at j) for (j = iLastCheck + 1; j <= jMax; j++) { - int lenJ = 0; // TODO: HashChainFindLength(hashChain, j); + int lenJ = hashChain.FindLength(j); int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. if (reach > maxReach) { @@ -450,22 +480,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Go with literal or backward reference. - /*if (len == 1) + if (len == 1) { - AddSingleLiteral(bgra[i], useColorCache, hashers, refs); + AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); } else { - VP8LBackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len)); + refs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len)); if (useColorCache) { for (j = i; j < i + len; ++j) { - VP8LColorCacheInsert(hashers, bgra[j]); + colorCache.Insert(bgra[j]); } } } - */ + i += len; } } @@ -474,69 +504,260 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Compute an LZ77 by forcing matches to happen within a given distance cost. /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. ///
- private static void BackwardReferencesLz77Box(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChainBest, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs) + private static void BackwardReferencesLz77Box(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { - int i; - int pixCount = xSize * ySize; - short[] counts; + int pixelCount = xSize * ySize; var windowOffsets = new int[WindowOffsetsSizeMax]; var windowOffsetsNew = new int[WindowOffsetsSizeMax]; int windowOffsetsSize = 0; int windowOffsetsNewSize = 0; - short[] countsIni = new short[xSize * ySize]; + var counts = new short[xSize * ySize]; int bestOffsetPrev = -1; int bestLengthPrev = -1; // counts[i] counts how many times a pixel is repeated starting at position i. - i = pixCount - 2; - /*counts = countsIni + i; - counts[1] = 1; - for (; i >= 0; i--, counts--) + int i = pixelCount - 2; + int countsPos = i; + counts[countsPos + 1] = 1; + for (; i >= 0; i--, countsPos--) { if (bgra[i] == bgra[i + 1]) { // Max out the counts to MAX_LENGTH. - counts[0] = counts[1] + (counts[1] != MaxLength); + counts[countsPos] = counts[countsPos + 1]; // TODO: + (counts[1] != MaxLength); } else { - counts[0] = 1; + counts[countsPos] = 1; + } + } + + // Figure out the window offsets around a pixel. They are stored in a + // spiraling order around the pixel as defined by VP8LDistanceToPlaneCode. + for (int y = 0; y <= 6; y++) + { + for (int x = -6; x <= 6; x++) + { + int offset = (y * xSize) + x; + + // Ignore offsets that bring us after the pixel. + if (offset <= 0) + { + continue; + } + + int planeCode = DistanceToPlaneCode(xSize, offset) - 1; + if (planeCode >= WindowOffsetsSizeMax) + { + continue; + } + + windowOffsets[planeCode] = offset; + } + } + + // For narrow images, not all plane codes are reached, so remove those. + for (i = 0; i < WindowOffsetsSizeMax; i++) + { + if (windowOffsets[i] == 0) + { + continue; + } + + windowOffsets[windowOffsetsSize++] = windowOffsets[i]; + } + + // Given a pixel P, find the offsets that reach pixels unreachable from P-1 + // with any of the offsets in windowOffsets[]. + for (i = 0; i < windowOffsetsSize; i++) + { + bool isReachable = false; + for (int j = 0; j < windowOffsetsSize && !isReachable; ++j) + { + isReachable |= windowOffsets[i] == windowOffsets[j] + 1; + } + + if (!isReachable) + { + windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i]; + windowOffsetsNewSize++; + } + } + + hashChain.OffsetLength[0] = 0; + for (i = 1; i < pixelCount; ++i) + { + int ind; + int bestLength = hashChainBest.FindLength(i); + int bestOffset = 0; + bool doCompute = true; + + if (bestLength >= MaxLength) + { + // Do not recompute the best match if we already have a maximal one in the window. + bestOffset = hashChainBest.FindOffset(i); + for (ind = 0; ind < windowOffsetsSize; ++ind) + { + if (bestOffset == windowOffsets[ind]) + { + doCompute = false; + break; + } + } } - }*/ + + if (doCompute) + { + // Figure out if we should use the offset/length from the previous pixel + // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. + bool usePrev = (bestLengthPrev > 1) && (bestLengthPrev < MaxLength); + int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; + bestLength = usePrev ? bestLengthPrev - 1 : 0; + bestOffset = usePrev ? bestOffsetPrev : 0; + + // Find the longest match in a window around the pixel. + for (ind = 0; ind < numInd; ++ind) + { + int currLength = 0; + int j = i; + int jOffset = usePrev ? i - windowOffsetsNew[ind] : i - windowOffsets[ind]; + if (jOffset < 0 || bgra[jOffset] != bgra[i]) + { + continue; + } + + // The longest match is the sum of how many times each pixel is repeated. + do + { + int countsJOffset = counts[jOffset]; + int countsJ = counts[j]; + if (countsJOffset != countsJ) + { + currLength += (countsJOffset < countsJ) ? countsJOffset : countsJ; + break; + } + + // The same color is repeated counts_pos times at j_offset and j. + currLength += countsJOffset; + jOffset += countsJOffset; + j += countsJOffset; + } + while (currLength <= MaxLength && j < pixelCount && bgra[jOffset] == bgra[j]); + + if (bestLength < currLength) + { + bestOffset = usePrev ? windowOffsetsNew[ind] : windowOffsets[ind]; + if (currLength >= MaxLength) + { + bestLength = MaxLength; + break; + } + else + { + bestLength = currLength; + } + } + } + } + + if (bestLength <= MinLength) + { + hashChain.OffsetLength[i] = 0; + bestOffsetPrev = 0; + bestLengthPrev = 0; + } + else + { + hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); + bestOffsetPrev = bestOffset; + bestLengthPrev = bestLength; + } + } + + hashChain.OffsetLength[0] = 0; + BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); } - private static void BackwardReferencesRle(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs) + private static void BackwardReferencesRle(int xSize, int ySize, Span bgra, int cacheBits, Vp8LBackwardRefs refs) { - int pixCount = xSize * ySize; - int i, k; + int pixelCount = xSize * ySize; bool useColorCache = cacheBits > 0; + var colorCache = new ColorCache(); + + if (useColorCache) + { + colorCache.Init(cacheBits); + } + + // VP8LClearBackwardRefs(refs); + + // Add first pixel as literal. + AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); + int i = 1; + while (i < pixelCount) + { + int maxLen = MaxFindCopyLength(pixelCount - i); + int rleLen = FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen); + int prevRowLen = (i < xSize) ? 0 : FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); + if (rleLen >= prevRowLen && rleLen >= MinLength) + { + refs.Add(PixOrCopy.CreateCopy(1, (short)rleLen)); + + // We don't need to update the color cache here since it is always the + // same pixel being copied, and that does not change the color cache + // state. + i += rleLen; + } + else if (prevRowLen >= MinLength) + { + refs.Add(PixOrCopy.CreateCopy((uint)xSize, (short)prevRowLen)); + if (useColorCache) + { + for (int k = 0; k < prevRowLen; k++) + { + colorCache.Insert(bgra[i + k]); + } + } + + i += prevRowLen; + } + else + { + AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); + i++; + } + } + + if (useColorCache) + { + // VP8LColorCacheClear(); + } } /// /// Update (in-place) backward references for the specified cacheBits. /// - private static void BackwardRefsWithLocalCache(uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs) + private static void BackwardRefsWithLocalCache(Span bgra, int cacheBits, Vp8LBackwardRefs refs) { int pixelIndex = 0; - var c = new Vp8LRefsCursor(refs); - var hashers = new ColorCache(); - hashers.Init(cacheBits); - //while (VP8LRefsCursorOk(&c)) - /*while (true) + using List.Enumerator c = refs.Refs.GetEnumerator(); + var colorCache = new ColorCache(); + colorCache.Init(cacheBits); + while (c.MoveNext()) { - PixOrCopy[] v = c.curPos; + PixOrCopy v = c.Current; if (v.IsLiteral()) { uint bgraLiteral = v.BgraOrDistance; - int ix = VP8LColorCacheContains(hashers, bgraLiteral); + int ix = colorCache.Contains(bgraLiteral); if (ix >= 0) { - // hashers contains bgraLiteral - v = PixOrCopyCreateCacheIdx(ix); + // color cache contains bgraLiteral + v = PixOrCopy.CreateCacheIdx(ix); } else { - VP8LColorCacheInsert(hashers, bgraLiteral); + colorCache.Insert(bgraLiteral); } pixelIndex++; @@ -544,32 +765,52 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless else { // refs was created without local cache, so it can not have cache indexes. - for (int k = 0; k < v.len; k++) + for (int k = 0; k < v.Len; k++) { - VP8LColorCacheInsert(hashers, bgra[pixelIndex++]); + colorCache.Insert(bgra[pixelIndex++]); } } - - VP8LRefsCursorNext(c); } - VP8LColorCacheClear(hashers);*/ + // VP8LColorCacheClear(colorCache); } - private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs[] refs) + private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) { - var c = new Vp8LRefsCursor(refs); - /*while (VP8LRefsCursorOk(&c)) + using List.Enumerator c = refs.Refs.GetEnumerator(); + while (c.MoveNext()) { - if (c.cur_pos.IsCopy()) + if (c.Current.IsCopy()) { - int dist = c.curPos.ArgbOrDistance; + int dist = (int)c.Current.BgraOrDistance; int transformedDist = DistanceToPlaneCode(xSize, dist); - c.curPos.ArgbOrDistance = transformedDist; + c.Current.BgraOrDistance = (uint)transformedDist; } + } + } + + private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs) + { + PixOrCopy v; + if (useColorCache) + { + int key = colorCache.GetIndex(pixel); + if (colorCache.Lookup(key) == pixel) + { + v = PixOrCopy.CreateCacheIdx(key); + } + else + { + v = PixOrCopy.CreateLiteral(pixel); + colorCache.Set((uint)key, pixel); + } + } + else + { + v = PixOrCopy.CreateLiteral(pixel); + } - VP8LRefsCursorNext(&c); - }*/ + refs.Add(v); } private static int DistanceToPlaneCode(int xSize, int dist) diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs index d1d46a7a6..96f70641c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs @@ -40,19 +40,55 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Inserts a new color into the cache. /// - /// The color to insert. - public void Insert(uint argb) + /// The color to insert. + public void Insert(uint bgra) { - int key = this.HashPix(argb, this.HashShift); - this.Colors[key] = argb; + int key = HashPix(bgra, this.HashShift); + this.Colors[key] = bgra; } + /// + /// Gets a color for a given key. + /// + /// The key to lookup. + /// The color for the key. public uint Lookup(int key) { return this.Colors[key]; } - private int HashPix(uint argb, int shift) + /// + /// Returns the index of the given color. + /// + /// The color to check. + /// The index of the color in the cache or -1 if its not present. + public int Contains(uint bgra) + { + int key = HashPix(bgra, this.HashShift); + return (this.Colors[key] == bgra) ? key : -1; + } + + /// + /// Gets the index of a color. + /// + /// The color. + /// The index for the color. + public int GetIndex(uint bgra) + { + return HashPix(bgra, this.HashShift); + } + + /// + /// Adds a new color to the cache. + /// + /// The key. + /// The color to add. + public void Set(uint key, uint bgra) + { + this.Colors[key] = bgra; + } + + public static int HashPix(uint argb, int shift) { return (int)((argb * HashMul) >> shift); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 30ee2f5f4..fe85e95a9 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -5,11 +5,47 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class PixOrCopy { - public PixOrCopyMode Mode { get; } + public PixOrCopyMode Mode { get; set; } - public short Len { get; } + public short Len { get; set; } - public uint ArgbOrDistance { get; } + public uint BgraOrDistance { get; set; } + + public static PixOrCopy CreateCacheIdx(int idx) + { + var retval = new PixOrCopy() + { + Mode = PixOrCopyMode.CacheIdx, + BgraOrDistance = (uint)idx, + Len = 1 + }; + + return retval; + } + + public static PixOrCopy CreateLiteral(uint bgra) + { + var retval = new PixOrCopy() + { + Mode = PixOrCopyMode.Literal, + BgraOrDistance = bgra, + Len = 1 + }; + + return retval; + } + + public static PixOrCopy CreateCopy(uint distance, short len) + { + var retval = new PixOrCopy() + { + Mode = PixOrCopyMode.Copy, + BgraOrDistance = distance, + Len = len + }; + + return retval; + } public bool IsLiteral() { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index b5da33ea3..7f35d08e4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -1,13 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Collections.Generic; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LBackwardRefs { + public Vp8LBackwardRefs() + { + this.Refs = new List(); + } + /// /// Common block-size. /// public int BlockSize { get; set; } + + public List Refs { get; } + + public void Add(PixOrCopy pixOrCopy) + { + this.Refs.Add(pixOrCopy); + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 0639b545a..e8d383917 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -27,5 +27,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); this.Size = size; } + + public int FindLength(int basePosition) + { + return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); + } + + public int FindOffset(int basePosition) + { + return (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index af0a57526..98b791bb0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -5,6 +5,58 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LHistogram { + public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) + { + if (paletteCodeBits >= 0) + { + this.PaletteCodeBits = paletteCodeBits; + } + + //HistogramClear(); + // TODO: VP8LHistogramStoreRefs(refs); + } + + public Vp8LHistogram() + { + this.Red = new uint[WebPConstants.NumLiteralCodes]; + this.Blue = new uint[WebPConstants.NumLiteralCodes]; + this.Alpha = new uint[WebPConstants.NumLiteralCodes]; + this.Distance = new uint[WebPConstants.NumLiteralCodes]; + this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough? + } + + public int PaletteCodeBits { get; } + + /// + /// Cached value of bit cost. + /// + public double BitCost { get; } + + /// + /// Cached value of literal entropy costs. + /// + public double LiteralCost { get; } + + /// + /// Cached value of red entropy costs. + /// + public double RedCost { get; } + + /// + /// Cached value of blue entropy costs. + /// + public double BlueCost { get; } + + public uint[] Red { get; } + + public uint[] Blue { get; } + + public uint[] Alpha { get; } + + public uint[] Literal { get; } + + public uint[] Distance { get; } + public double EstimateBits() { // TODO: implement this. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs index ce423c39a..6578d3f51 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs @@ -5,14 +5,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LRefsCursor { - public Vp8LRefsCursor(Vp8LBackwardRefs[] refs) + public Vp8LRefsCursor(Vp8LBackwardRefs refs) { //this.Refs = refs; - this.CurrentPos = 0; + //this.CurrentPos = 0; } - public PixOrCopy[] Refs { get; } + //public PixOrCopy Refs { get; } - public int CurrentPos { get; } + public PixOrCopy CurrentPos { get; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 5a5de9909..7f985a29e 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; @@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodePalette(Image image, Span bgra, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { - var tmpPalette = new uint[WebPConstants.MaxPaletteSize]; + Span tmpPalette = new uint[WebPConstants.MaxPaletteSize]; int paletteSize = enc.PaletteSize; Span palette = enc.Palette.Memory.Span; this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); @@ -179,26 +180,29 @@ namespace SixLabors.ImageSharp.Formats.WebP } tmpPalette[0] = palette[0]; - this.EncodeImageNoHuffman(image, tmpPalette, enc); + this.EncodeImageNoHuffman(tmpPalette, enc.HashChain, enc.Refs[0], enc.Refs[1], width: paletteSize, height: 1, quality: 20); } - private void EncodeImageNoHuffman(Image image, Span bgra, Vp8LEncoder enc) - where TPixel : unmanaged, IPixel + private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { - int width = image.Width; - int height = image.Height; - int paletteSize = enc.PaletteSize; - Vp8LHashChain hashChain = enc.HashChain; var huffmanCodes = new HuffmanTreeCode[5]; + int cacheBits = 0; HuffmanTreeToken[] tokens; var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; - int quality = 20; // TODO: hardcoded for now. - // Calculate backward references from ARGB image. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, paletteSize, 1); - - //var refs = GetBackwardReferences(width, height, argb, quality, 0, kLZ77Standard | kLZ77RLE, cacheBits, hashChain, refsTmp1, refsTmp2); + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + + Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( + width, + height, + bgra, + quality, + (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, + cacheBits, + hashChain, + refsTmp1, + refsTmp2); // Build histogram image and symbols from backward references. //VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]); From 21880f74e268ec8d5fef3b16e56c8cb18787cfc6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 29 May 2020 12:26:32 +0200 Subject: [PATCH 173/359] Build huffman tree --- .../Formats/WebP/BitReader/Vp8BitReader.cs | 16 +- .../Formats/WebP/Lossless/HuffmanTree.cs | 32 +- .../Formats/WebP/Lossless/HuffmanTreeCode.cs | 12 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 305 ++++++++++++++++++ .../Formats/WebP/Lossless/LosslessUtils.cs | 30 ++ .../Formats/WebP/Lossless/PixOrCopy.cs | 20 ++ .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 37 ++- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 146 ++++++++- .../Formats/WebP/Lossless/Vp8LStreaks.cs | 26 ++ .../Formats/WebP/WebPCommonUtils.cs | 27 ++ .../Formats/WebP/WebPEncoderCore.cs | 61 +++- .../Formats/WebP/WebPLookupTables.cs | 68 ++++ 12 files changed, 737 insertions(+), 43 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs create mode 100644 src/ImageSharp/Formats/WebP/WebPCommonUtils.cs diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index b482a861d..40f0ae5f7 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader range = split + 1; } - int shift = 7 ^ this.BitsLog2Floor(range); + int shift = 7 ^ WebPCommonUtils.BitsLog2Floor(range); range <<= shift; this.bits -= shift; @@ -229,19 +229,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); return x; } - - // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). - [MethodImpl(InliningOptions.ShortMethod)] - private int BitsLog2Floor(uint n) - { - int logValue = 0; - while (n >= 256) - { - logValue += 8; - n >>= 8; - } - - return logValue + WebPLookupTables.LogTable8bit[n]; - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index 3fba3327d..ae96c8579 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -9,23 +9,39 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless internal class HuffmanTree { /// - /// Gets the symbol frequency. + /// Gets or sets the symbol frequency. /// - public int TotalCount { get; } + public int TotalCount { get; set; } /// - /// Gets the symbol value. + /// Gets or sets the symbol value. /// - public int Value { get; } + public int Value { get; set; } /// - /// Gets the index for the left sub-tree. + /// Gets or sets the index for the left sub-tree. /// - public int PoolIndexLeft { get; } + public int PoolIndexLeft { get; set; } /// - /// Gets the index for the right sub-tree. + /// Gets or sets the index for the right sub-tree. /// - public int PoolIndexRight { get; } + public int PoolIndexRight { get; set; } + + public static int Compare(HuffmanTree t1, HuffmanTree t2) + { + if (t1.TotalCount > t2.TotalCount) + { + return -1; + } + else if (t1.TotalCount < t2.TotalCount) + { + return 1; + } + else + { + return (t1.Value < t2.Value) ? -1 : 1; + } + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index fe582b8f2..459a53de8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -9,18 +9,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless internal class HuffmanTreeCode { /// - /// Gets the number of symbols. + /// Gets or sets the number of symbols. /// - public int NumSymbols { get; } + public int NumSymbols { get; set; } /// - /// Gets the code lengths of the symbols. + /// Gets or sets the code lengths of the symbols. /// - public byte[] CodeLengths { get; } + public byte[] CodeLengths { get; set; } /// - /// Gets the symbol Codes. + /// Gets or sets the symbol Codes. /// - public short[] Codes { get; } + public short[] Codes { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index fb1de6bef..067c68ecc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -18,6 +18,241 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; + // Pre-reversed 4-bit values. + private static byte[] reversedBits = + { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf + }; + + public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) + { + int numSymbols = huffCode.NumSymbols; + OptimizeHuffmanForRle(numSymbols, bufRle, histogram); + GenerateOptimalTree(huffTree, histogram, numSymbols, huffCode.CodeLengths); + + // Create the actual bit codes for the bit lengths. + ConvertBitDepthsToSymbols(huffCode); + } + + /// + /// Change the population counts in a way that the consequent + /// Huffman tree compression, especially its RLE-part, give smaller output. + /// + public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) + { + // 1) Let's make the Huffman code more compatible with rle encoding. + for (; length >= 0; --length) + { + if (length == 0) + { + return; // All zeros. + } + + if (counts[length - 1] != 0) + { + // Now counts[0..length - 1] does not have trailing zeros. + break; + } + } + + // 2) Let's mark all population counts that already can be encoded with an rle code. + // Let's not spoil any of the existing good rle codes. + // Mark any seq of 0's that is longer as 5 as a good_for_rle. + // Mark any seq of non-0's that is longer as 7 as a good_for_rle. + uint symbol = counts[0]; + int stride = 0; + for (int i = 0; i < length + 1; ++i) + { + if (i == length || counts[i] != symbol) + { + if ((symbol == 0 && stride >= 5) || + (symbol != 0 && stride >= 7)) + { + int k; + for (k = 0; k < stride; ++k) + { + goodForRle[i - k - 1] = true; + } + } + + stride = 1; + if (i != length) + { + symbol = counts[i]; + } + } + else + { + ++stride; + } + } + + // 3) Let's replace those population counts that lead to more rle codes. + stride = 0; + uint limit = counts[0]; + uint sum = 0; + for (int i = 0; i < length + 1; i++) + { + if (i == length || goodForRle[i] || + (i != 0 && goodForRle[i - 1]) || + !ValuesShouldBeCollapsedToStrideAverage(counts[i], limit)) + { + if (stride >= 4 || (stride >= 3 && sum == 0)) + { + uint k; + + // The stride must end, collapse what we have, if we have enough (4). + uint count = (uint)((sum + (stride / 2)) / stride); + if (count < 1) + { + count = 1; + } + + if (sum == 0) + { + // Don't make an all zeros stride to be upgraded to ones. + count = 0; + } + + for (k = 0; k < stride; ++k) + { + // We don't want to change value at counts[i], + // that is already belonging to the next stride. Thus - 1. + counts[i - k - 1] = count; + } + } + + stride = 0; + sum = 0; + if (i < length - 3) + { + // All interesting strides have a count of at least 4, + // at least when non-zeros. + limit = (counts[i] + counts[i + 1] + + counts[i + 2] + counts[i + 3] + 2) / 4; + } + else if (i < length) + { + limit = counts[i]; + } + else + { + limit = 0; + } + } + + ++stride; + if (i != length) + { + sum += counts[i]; + if (stride >= 4) + { + limit = (uint)((sum + (stride / 2)) / stride); + } + } + } + } + + /// + /// Create an optimal Huffman tree. + /// + /// The catch here is that the tree cannot be arbitrarily deep + /// + /// This algorithm is not of excellent performance for very long data blocks, + /// especially when population counts are longer than 2**tree_limit, but + /// we are not planning to use this with extremely long blocks. + /// + /// + /// The huffman tree. + /// The historgram. + /// The size of the histogram. + /// How many bits are used for the symbol. + public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, byte[] bitDepths) + { + uint countMin; + int treeSizeOrig = 0; + + for (int i = 0; i < histogramSize; i++) + { + if (histogram[i] != 0) + { + treeSizeOrig++; + } + } + + if (treeSizeOrig == 0) + { + return; + } + + Span treePool = tree.AsSpan(treeSizeOrig); + + // For block sizes with less than 64k symbols we never need to do a + // second iteration of this loop. + for (countMin = 1; ; countMin *= 2) + { + int treeSize = treeSizeOrig; + + // We need to pack the Huffman tree in treeDepthLimit bits. + // So, we try by faking histogram entries to be at least 'countMin'. + int idx = 0; + for (int j = 0; j < histogramSize; j++) + { + if (histogram[j] != 0) + { + uint count = (histogram[j] < countMin) ? countMin : histogram[j]; + tree[idx].TotalCount = (int)count; + tree[idx].Value = j; + tree[idx].PoolIndexLeft = -1; + tree[idx].PoolIndexRight = -1; + idx++; + } + } + + // Build the Huffman tree. + Array.Sort(tree, HuffmanTree.Compare); + + if (treeSize > 1) + { + // Normal case. + int treePoolSize = 0; + while (treeSize > 1) + { + // Finish when we have only one root. + treePool[treePoolSize++] = tree[treeSize - 1]; + treePool[treePoolSize++] = tree[treeSize - 2]; + int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; + treeSize -= 2; + + // Search for the insertion point. + int k; + for (k = 0; k < treeSize; k++) + { + if (tree[k].TotalCount <= count) + { + break; + } + } + + tree[k].TotalCount = count; + tree[k].Value = -1; + + tree[k].PoolIndexLeft = treePoolSize - 1; + tree[k].PoolIndexRight = treePoolSize - 2; + treeSize = treeSize + 1; + } + + SetBitDepths(tree, treePool, bitDepths, 0); + } + else if (treeSize == 1) + { + // Trivial case: only one element. + bitDepths[tree[0].Value] = 1; + } + } + } + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); @@ -165,6 +400,68 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return totalSize; } + /// + /// Get the actual bit values for a tree of bit depths. + /// + /// The hiffman tree. + private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) + { + // 0 bit-depth means that the symbol does not exist. + uint[] nextCode = new uint[WebPConstants.MaxAllowedCodeLength + 1]; + int[] depthCount = new int[WebPConstants.MaxAllowedCodeLength + 1]; + + int len = tree.NumSymbols; + for (int i = 0; i < len; i++) + { + int codeLength = tree.CodeLengths[i]; + depthCount[codeLength]++; + } + + depthCount[0] = 0; // ignore unused symbol. + nextCode[0] = 0; + + uint code = 0; + for (int i = 1; i <= WebPConstants.MaxAllowedCodeLength; i++) + { + code = (uint)((code + depthCount[i - 1]) << 1); + nextCode[i] = code; + } + + for (int i = 0; i < len; i++) + { + int codeLength = tree.CodeLengths[i]; + tree.Codes[i] = (short)ReverseBits(codeLength, nextCode[codeLength]++); + } + } + + private static void SetBitDepths(Span tree, Span pool, byte[] bitDepths, int level) + { + if (tree[0].PoolIndexLeft >= 0) + { + SetBitDepths(pool.Slice(tree[0].PoolIndexLeft), pool, bitDepths, level + 1); + SetBitDepths(pool.Slice(tree[0].PoolIndexRight), pool, bitDepths, level + 1); + } + else + { + bitDepths[tree[0].Value] = (byte)level; + } + } + + private static uint ReverseBits(int numBits, uint bits) + { + uint retval = 0; + int i = 0; + while (i < numBits) + { + i += 4; + retval |= (uint)(reversedBits[bits & 0xf] << (WebPConstants.MaxAllowedCodeLength + 1 - i)); + bits >>= 4; + } + + retval >>= WebPConstants.MaxAllowedCodeLength + 1 - numBits; + return retval; + } + /// /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, /// len is the code length of the next processed symbol. @@ -217,5 +514,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return step != 0 ? (key & (step - 1)) + step : key; } + + /// + /// Heuristics for selecting the stride ranges to collapse. + /// + private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint b) + { + return Math.Abs(a - b) < 4; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 350fc0603..0197c66db 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { private const uint Predictor0 = WebPConstants.ArgbBlack; + private const int PrefixLookupIdxMax = 512; + private const int LogLookupIdxMax = 256; private const int ApproxLogMax = 4096; @@ -24,6 +26,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const double Log2Reciprocal = 1.44269504088896338700465094007086; + public static int PrefixEncodeBits(int distance, ref int extraBits) + { + if (distance < PrefixLookupIdxMax) + { + (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance]; + extraBits = prefixCode.extraBits; + return prefixCode.code; + } + else + { + return PrefixEncodeBitsNoLut(distance, ref extraBits); + } + } + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -396,6 +412,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + /// + /// Splitting of distance and length codes into prefixes and + /// extra bits. The prefixes are encoded with an entropy code + /// while the extra bits are stored just as normal bits. + /// + private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) + { + int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance); + int secondHighestBit = (distance >> (highestBit - 1)) & 1; + extraBits = highestBit - 1; + var code = (2 * highestBit) + secondHighestBit; + return code; + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index fe85e95a9..356db2e92 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -47,6 +47,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return retval; } + public uint Literal(int component) + { + return (this.BgraOrDistance >> (component * 8)) & 0xff; + } + + public uint CacheIdx() + { + return this.BgraOrDistance; + } + + public short Length() + { + return this.Len; + } + + public uint Distance() + { + return this.BgraOrDistance; + } + public bool IsLiteral() { return this.Mode == PixOrCopyMode.Literal; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index a9ea62f84..c16ff5297 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public uint NoneZeroCode { get; set; } - public double BitsEntropyRefine(Span array, int n) + public double BitsEntropyRefine() { double mix; if (this.NoneZeros < 5) @@ -112,5 +112,40 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Entropy += LosslessUtils.FastSLog2(this.Sum); } + + /// + /// Get the entropy for the distribution 'X'. + /// + public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xPrev = x[0]; + for (i = 1; i < length; ++i) + { + uint xi = x[i]; + if (xi != xPrev) + { + this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); + } + } + + this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); + + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } + + private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) + { + // Gather info for the bit entropy. + int streak = i - iPrev; + + // Gather info for the Huffman cost. + stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; + stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; + + valPrev = val; + iPrev = i; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 98b791bb0..1a6734a98 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -1,19 +1,28 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; +using System.Collections.Generic; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LHistogram { public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) + : this() { if (paletteCodeBits >= 0) { this.PaletteCodeBits = paletteCodeBits; } - //HistogramClear(); - // TODO: VP8LHistogramStoreRefs(refs); + this.StoreRefs(refs); + } + + public Vp8LHistogram(int paletteCodeBits) + : this() + { + this.PaletteCodeBits = paletteCodeBits; } public Vp8LHistogram() @@ -23,27 +32,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Alpha = new uint[WebPConstants.NumLiteralCodes]; this.Distance = new uint[WebPConstants.NumLiteralCodes]; this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough? + + // 5 for literal, red, blue, alpha, distance. + this.IsUsed = new bool[5]; } + /// + /// Gets the palette code bits. + /// public int PaletteCodeBits { get; } /// - /// Cached value of bit cost. + /// Gets the cached value of bit cost. /// public double BitCost { get; } /// - /// Cached value of literal entropy costs. + /// Gets the cached value of literal entropy costs. /// public double LiteralCost { get; } /// - /// Cached value of red entropy costs. + /// Gets the cached value of red entropy costs. /// public double RedCost { get; } /// - /// Cached value of blue entropy costs. + /// Gets the cached value of blue entropy costs. /// public double BlueCost { get; } @@ -57,10 +72,125 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public uint[] Distance { get; } + public bool[] IsUsed { get; } + + public void StoreRefs(Vp8LBackwardRefs refs) + { + using List.Enumerator c = refs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + this.AddSinglePixOrCopy(c.Current, false); + } + } + + public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier) + { + if (v.IsLiteral()) + { + this.Alpha[v.Literal(3)]++; + this.Red[v.Literal(2)]++; + this.Literal[v.Literal(1)]++; + this.Blue[v.Literal(0)]++; + } + else if (v.IsCacheIdx()) + { + int literalIx = (int)(WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + v.CacheIdx()); + this.Literal[literalIx]++; + } + else + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); + this.Literal[WebPConstants.NumLiteralCodes + code]++; + if (!useDistanceModifier) + { + code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); + } + else + { + // TODO: VP8LPrefixEncodeBits(distance_modifier(distance_modifier_arg0, PixOrCopyDistance(v)), &code, &extra_bits); + } + + this.Distance[code]++; + } + } + + public int NumCodes() + { + return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + } + public double EstimateBits() { - // TODO: implement this. - return 0.0; + return + PopulationCost(this.Literal, this.NumCodes(), ref this.IsUsed[0]) + + PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref this.IsUsed[1]) + + PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref this.IsUsed[2]) + + PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref this.IsUsed[3]) + + PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref this.IsUsed[4]) + + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes) + + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); + } + + /// + /// Get the symbol entropy for the distribution 'population'. + /// + private static double PopulationCost(uint[] population, int length, ref bool isUsed) + { + var bitEntropy = new Vp8LBitEntropy(); + var stats = new Vp8LStreaks(); + bitEntropy.BitsEntropyUnrefined(population, length, stats); + + // The histogram is used if there is at least one non-zero streak. + isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; + + return bitEntropy.BitsEntropyRefine() + FinalHuffmanCost(stats); + } + + /// + /// Finalize the Huffman cost based on streak numbers and length type (<3 or >=3). + /// + private static double FinalHuffmanCost(Vp8LStreaks stats) + { + // The constants in this function are experimental and got rounded from + // their original values in 1/8 when switched to 1/1024. + double retval = InitialHuffmanCost(); + + // Second coefficient: Many zeros in the histogram are covered efficiently + // by a run-length encode. Originally 2/8. + retval += (stats.Counts[0] * 1.5625) + (0.234375 * stats.Streaks[0][1]); + + // Second coefficient: Constant values are encoded less efficiently, but still + // RLE'ed. Originally 6/8. + retval += (stats.Counts[1] * 2.578125) + 0.703125 * stats.Streaks[1][1]; + + // 0s are usually encoded more efficiently than non-0s. + // Originally 15/8. + retval += 1.796875 * stats.Streaks[0][0]; + + // Originally 26/8. + retval += 3.28125 * stats.Streaks[1][0]; + + return retval; + } + + private static double InitialHuffmanCost() + { + // Small bias because Huffman code length is typically not stored in full length. + int huffmanCodeOfHuffmanCodeSize = WebPConstants.CodeLengthCodes * 3; + double smallBias = 9.1; + return huffmanCodeOfHuffmanCodeSize - smallBias; + } + + private static double ExtraCost(Span population, int length) + { + double cost = 0.0d; + for (int i = 2; i < length - 2; ++i) + { + cost += (i >> 1) * population[i + 2]; + } + + return cost; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs new file mode 100644 index 000000000..41947ae68 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class Vp8LStreaks + { + public Vp8LStreaks() + { + this.Counts = new int[2]; + this.Streaks = new int[2][]; + this.Streaks[0] = new int[2]; + this.Streaks[1] = new int[2]; + } + + /// + /// index: 0=zero streak, 1=non-zero streak. + /// + public int[] Counts { get; } + + /// + /// [zero/non-zero][streak < 3 / streak >= 3]. + /// + public int[][] Streaks { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs new file mode 100644 index 000000000..52027192b --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Utility methods for lossy and lossless webp format. + /// + public static class WebPCommonUtils + { + // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). + [MethodImpl(InliningOptions.ShortMethod)] + public static int BitsLog2Floor(uint n) + { + int logValue = 0; + while (n >= 256) + { + logValue += 8; + n >>= 8; + } + + return logValue + WebPLookupTables.LogTable8bit[n]; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 7f985a29e..895384250 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -204,11 +204,16 @@ namespace SixLabors.ImageSharp.Formats.WebP refsTmp1, refsTmp2); + var histogramImage = new List() + { + new Vp8LHistogram(cacheBits) + }; + // Build histogram image and symbols from backward references. - //VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]); + histogramImage[0].StoreRefs(refs); // Create Huffman bit lengths and codes for each histogram image. - //GetHuffBitLengthsAndCodes(histogram_image, huffman_codes) + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); // No color cache, no Huffman image. this.bitWriter.PutBits(0, 1); @@ -342,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitEntropy = new Vp8LBitEntropy(); Span curHisto = histo.Slice(j * 256, 256); bitEntropy.BitsEntropyUnrefined(curHisto, 256); - entropyComp[j] = bitEntropy.BitsEntropyRefine(curHisto, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(); } entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + @@ -552,9 +557,55 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) + { + long totalLengthSize = 0; + int maxNumSymbols = 0; + + // Iterate over all histograms and get the aggregate number of codes used. + for (int i = 0; i < histogramImage.Count; i++) + { + Vp8LHistogram histo = histogramImage[i]; + int startIdx = 5 * i; + for (int k = 0; k < 5; k++) + { + int numSymbols = + (k == 0) ? histo.NumCodes() : + (k == 4) ? WebPConstants.NumDistanceCodes : 256; + huffmanCodes[startIdx + k].NumSymbols = numSymbols; + totalLengthSize += numSymbols; + } + } + + var end = 5 * histogramImage.Count; + for (int i = 0; i < end; i++) + { + int bitLength = huffmanCodes[i].NumSymbols; + huffmanCodes[i].Codes = new short[bitLength]; + huffmanCodes[i].CodeLengths = new byte[bitLength]; + if (maxNumSymbols < bitLength) + { + maxNumSymbols = bitLength; + } + } + + // Create Huffman trees. + bool[] bufRle = new bool[maxNumSymbols]; + var huffTree = new HuffmanTree[3 * maxNumSymbols]; + for (int i = 0; i < histogramImage.Count; i++) + { + int codesStartIdx = 5 * i; + Vp8LHistogram histo = histogramImage[i]; + HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); + HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); + HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); + HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); + HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); + } + } + /// - /// Computes a value that is related to the entropy created by the - /// palette entry diff. + /// Computes a value that is related to the entropy created by the palette entry diff. /// /// First color. /// Second color. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 8b1466c23..ae66e90e4 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -656,6 +656,74 @@ namespace SixLabors.ImageSharp.Formats.WebP } }; + public static (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] + { + (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), + (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), + (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), + (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), + (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + }; + static WebPLookupTables() { Abs0 = new Dictionary(); From 0ff8e48a61603b5538eea040fa32b26754915f12 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 30 May 2020 17:45:01 +0200 Subject: [PATCH 174/359] StoreHuffmanCode --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 15 + .../WebP/Lossless/BackwardReferenceEncoder.cs | 8 +- .../Formats/WebP/Lossless/HuffmanTree.cs | 26 +- .../Formats/WebP/Lossless/HuffmanTreeToken.cs | 11 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 197 ++++++++++-- .../Formats/WebP/Lossless/LosslessUtils.cs | 26 ++ .../Formats/WebP/Lossless/Vp8LHistogram.cs | 12 +- .../Formats/WebP/WebPEncoderCore.cs | 294 +++++++++++++++++- .../Formats/WebP/WebPLookupTables.cs | 169 ++++++---- 9 files changed, 634 insertions(+), 124 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 3cb554a64..afb91b43a 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System; +using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { @@ -70,6 +71,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } + public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)symbol, depth); + } + + public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)((bits << depth) | symbol), depth + nBits); + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 49e9edd2f..d9532c91b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -419,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless short[] chosenPath; int chosenPathSize = 0; - // TODO: + // TODO: implement this // BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); // TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize); // BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); @@ -524,7 +524,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (bgra[i] == bgra[i + 1]) { // Max out the counts to MAX_LENGTH. - counts[countsPos] = counts[countsPos + 1]; // TODO: + (counts[1] != MaxLength); + counts[countsPos] = counts[countsPos + 1]; + if (counts[countsPos + 1] != MaxLength) + { + counts[countsPos]++; + } } else { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index ae96c8579..daf807335 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -1,13 +1,35 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Represents the Huffman tree. /// - internal class HuffmanTree + [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] + internal class HuffmanTree : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public HuffmanTree() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The HuffmanTree to create an instance from. + private HuffmanTree(HuffmanTree other) + { + this.TotalCount = other.TotalCount; + this.Value = other.Value; + this.PoolIndexLeft = other.PoolIndexLeft; + this.PoolIndexRight = other.PoolIndexRight; + } + /// /// Gets or sets the symbol frequency. /// @@ -43,5 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (t1.Value < t2.Value) ? -1 : 1; } } + + public IDeepCloneable DeepClone() => new HuffmanTree(this); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index a140a2c21..3708838c2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -1,21 +1,24 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Holds the tree header in coded form. /// + [DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")] internal class HuffmanTreeToken { /// - /// Gets the code. Value (0..15) or escape code (16, 17, 18). + /// Gets or sets the code. Value (0..15) or escape code (16, 17, 18). /// - public byte Code { get; } + public byte Code { get; set; } /// - /// Gets extra bits for escape codes. + /// Gets or sets the extra bits for escape codes. /// - public byte ExtraBits { get; } + public byte ExtraBits { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 067c68ecc..885d99a13 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -28,8 +28,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) { int numSymbols = huffCode.NumSymbols; + bufRle.AsSpan().Fill(false); OptimizeHuffmanForRle(numSymbols, bufRle, histogram); - GenerateOptimalTree(huffTree, histogram, numSymbols, huffCode.CodeLengths); + GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); // Create the actual bit codes for the bit lengths. ConvertBitDepthsToSymbols(huffCode); @@ -42,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) { // 1) Let's make the Huffman code more compatible with rle encoding. - for (; length >= 0; --length) + for (; length >= 0; length--) { if (length == 0) { @@ -58,19 +59,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // 2) Let's mark all population counts that already can be encoded with an rle code. // Let's not spoil any of the existing good rle codes. - // Mark any seq of 0's that is longer as 5 as a good_for_rle. - // Mark any seq of non-0's that is longer as 7 as a good_for_rle. + // Mark any seq of 0's that is longer as 5 as a goodForRle. + // Mark any seq of non-0's that is longer as 7 as a goodForRle. uint symbol = counts[0]; int stride = 0; - for (int i = 0; i < length + 1; ++i) + for (int i = 0; i < length + 1; i++) { if (i == length || counts[i] != symbol) { - if ((symbol == 0 && stride >= 5) || - (symbol != 0 && stride >= 7)) + if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) { - int k; - for (k = 0; k < stride; ++k) + for (int k = 0; k < stride; k++) { goodForRle[i - k - 1] = true; } @@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - ++stride; + stride++; } } @@ -94,9 +93,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint sum = 0; for (int i = 0; i < length + 1; i++) { - if (i == length || goodForRle[i] || - (i != 0 && goodForRle[i - 1]) || - !ValuesShouldBeCollapsedToStrideAverage(counts[i], limit)) + var valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); + if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage) { if (stride >= 4 || (stride >= 3 && sum == 0)) { @@ -115,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless count = 0; } - for (k = 0; k < stride; ++k) + for (k = 0; k < stride; k++) { // We don't want to change value at counts[i], // that is already belonging to the next stride. Thus - 1. @@ -127,8 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless sum = 0; if (i < length - 3) { - // All interesting strides have a count of at least 4, - // at least when non-zeros. + // All interesting strides have a count of at least 4, at least when non-zeros. limit = (counts[i] + counts[i + 1] + counts[i + 2] + counts[i + 3] + 2) / 4; } @@ -142,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - ++stride; + stride++; if (i != length) { sum += counts[i]; @@ -156,19 +153,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Create an optimal Huffman tree. - /// - /// The catch here is that the tree cannot be arbitrarily deep - /// - /// This algorithm is not of excellent performance for very long data blocks, - /// especially when population counts are longer than 2**tree_limit, but - /// we are not planning to use this with extremely long blocks. /// /// /// The huffman tree. /// The historgram. /// The size of the histogram. + /// The tree depth limit. /// How many bits are used for the symbol. - public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, byte[] bitDepths) + public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) { uint countMin; int treeSizeOrig = 0; @@ -211,7 +203,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Build the Huffman tree. - Array.Sort(tree, HuffmanTree.Compare); + HuffmanTree[] treeCopy = tree.AsSpan().Slice(0, treeSize).ToArray(); + Array.Sort(treeCopy, HuffmanTree.Compare); + treeCopy.AsSpan().CopyTo(tree); if (treeSize > 1) { @@ -220,8 +214,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless while (treeSize > 1) { // Finish when we have only one root. - treePool[treePoolSize++] = tree[treeSize - 1]; - treePool[treePoolSize++] = tree[treeSize - 2]; + treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 1].DeepClone(); + treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 2].DeepClone(); int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; treeSize -= 2; @@ -235,9 +229,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + var endIdx = k + 1; + var num = treeSize - k; + var startIdx = endIdx + num - 1; + for (int i = startIdx; i >= endIdx; i--) + { + tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); + } + tree[k].TotalCount = count; tree[k].Value = -1; - tree[k].PoolIndexLeft = treePoolSize - 1; tree[k].PoolIndexRight = treePoolSize - 2; treeSize = treeSize + 1; @@ -250,9 +251,59 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Trivial case: only one element. bitDepths[tree[0].Value] = 1; } + + // Test if this Huffman tree satisfies our 'treeDepthLimit' criteria. + int maxDepth = bitDepths[0]; + for (int j = 1; j < histogramSize; j++) + { + if (maxDepth < bitDepths[j]) + { + maxDepth = bitDepths[j]; + } + } + + if (maxDepth <= treeDepthLimit) + { + break; + } } } + public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokens) + { + int depthSize = tree.NumSymbols; + int prevValue = 8; // 8 is the initial value for rle. + int i = 0; + int tokenIdx = 0; + Span tokenSpan = tokens.AsSpan(); + while (i < depthSize) + { + int value = tree.CodeLengths[i]; + int k = i + 1; + int runs; + while (k < depthSize && tree.CodeLengths[k] == value) + { + k++; + } + + runs = k - i; + if (value == 0) + { + tokenIdx += CodeRepeatedZeros(runs, tokens); + } + else + { + tokenIdx += CodeRepeatedValues(runs, tokens, value, prevValue); + prevValue = value; + } + + tokenSpan.Slice(tokenIdx); + i += runs; + } + + return tokenIdx; + } + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); @@ -400,6 +451,94 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return totalSize; } + private static int CodeRepeatedZeros(int repetitions, Span tokens) + { + int pos = 0; + while (repetitions >= 1) + { + if (repetitions < 3) + { + int i; + for (i = 0; i < repetitions; ++i) + { + tokens[pos].Code = 0; // 0-value + tokens[pos].ExtraBits = 0; + pos++; + } + + break; + } + else if (repetitions < 11) + { + tokens[pos].Code = 17; + tokens[pos].ExtraBits = (byte)(repetitions - 3); + pos++; + break; + } + else if (repetitions < 139) + { + tokens[pos].Code = 18; + tokens[pos].ExtraBits = (byte)(repetitions - 11); + pos++; + break; + } + else + { + tokens[pos].Code = 18; + tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + pos++; + repetitions -= 138; + } + } + + return pos; + } + + private static int CodeRepeatedValues(int repetitions, Span tokens, int value, int prevValue) + { + int pos = 0; + + if (value != prevValue) + { + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; + pos++; + repetitions--; + } + + while (repetitions >= 1) + { + if (repetitions < 3) + { + int i; + for (i = 0; i < repetitions; ++i) + { + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; + pos++; + } + + break; + } + else if (repetitions < 7) + { + tokens[pos].Code = 16; + tokens[pos].ExtraBits = (byte)(repetitions - 3); + pos++; + break; + } + else + { + tokens[pos].Code = 16; + tokens[pos].ExtraBits = 3; + pos++; + repetitions -= 6; + } + } + + return pos; + } + /// /// Get the actual bit values for a tree of bit depths. /// @@ -518,7 +657,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Heuristics for selecting the stride ranges to collapse. /// - private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint b) + private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) { return Math.Abs(a - b) < 4; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 0197c66db..c3cf4cb2d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -40,6 +40,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) + { + if (distance < PrefixLookupIdxMax) + { + (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance]; + extraBits = prefixCode.extraBits; + extraBitsValue = WebPLookupTables.PrefixEncodeExtraBitsValue[distance]; + + return prefixCode.code; + } + else + { + return PrefixEncodeNoLUT(distance, ref extraBits, ref extraBitsValue); + } + } + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -426,6 +442,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return code; } + private static int PrefixEncodeNoLUT(int distance, ref int extraBits, ref int extraBitsValue) + { + int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance); + int secondHighestBit = (distance >> (highestBit - 1)) & 1; + extraBits = highestBit - 1; + extraBitsValue = distance & ((1 << extraBits) - 1); + int code = (2 * highestBit) + secondHighestBit; + return code; + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 1a6734a98..ec326905a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -27,11 +27,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public Vp8LHistogram() { - this.Red = new uint[WebPConstants.NumLiteralCodes]; - this.Blue = new uint[WebPConstants.NumLiteralCodes]; - this.Alpha = new uint[WebPConstants.NumLiteralCodes]; - this.Distance = new uint[WebPConstants.NumLiteralCodes]; - this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough? + this.Red = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Blue = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Distance = new uint[WebPConstants.NumDistanceCodes]; + + var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + this.Literal = new uint[literalSize]; // 5 for literal, red, blue, alpha, distance. this.IsUsed = new bool[5]; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 895384250..39f01e30d 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -185,10 +185,19 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { - var huffmanCodes = new HuffmanTreeCode[5]; int cacheBits = 0; - HuffmanTreeToken[] tokens; + var histogramSymbols = new short[1]; // Only one tree, one symbol. + var huffmanCodes = new HuffmanTreeCode[5]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = new HuffmanTreeCode(); + } + var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } // Calculate backward references from ARGB image. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); @@ -219,30 +228,206 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitWriter.PutBits(0, 1); // Find maximum number of symbols for the huffman tree-set. - /*for (i = 0; i < 5; ++i) + int maxTokens = 0; + for (int i = 0; i < 5; i++) { - HuffmanTreeCode * const codes = &huffman_codes[i]; - if (max_tokens < codes->num_symbols) + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) { - max_tokens = codes->num_symbols; + maxTokens = codes.NumSymbols; } - }*/ + } + + var tokens = new HuffmanTreeToken[maxTokens]; + for(int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); + } // Store Huffman codes. - /* - for (i = 0; i < 5; ++i) + for (int i = 0; i < 5; i++) { - HuffmanTreeCode * const codes = &huffman_codes[i]; - StoreHuffmanCode(bw, huff_tree, tokens, codes); + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); ClearHuffmanTreeIfOnlyOneSymbol(codes); } // Store actual literals. - StoreImageToBitMask(bw, width, 0, refs, histogram_symbols, huffman_codes); - */ + this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); } - private void StoreImageToBitMask(int width, int histoBits, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + { + int count = 0; + int[] symbols = { 0, 0 }; + int maxBits = 8; + int maxSymbol = 1 << maxBits; + + // Check whether it's a small tree. + for (int i = 0; i < huffmanCode.NumSymbols && count < 3; ++i) + { + if (huffmanCode.CodeLengths[i] != 0) + { + if (count < 2) + { + symbols[count] = i; + } + + count++; + } + } + + if (count == 0) + { + // emit minimal tree for empty cases + // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 + this.bitWriter.PutBits(0x01, 4); + } + else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) + { + this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. + this.bitWriter.PutBits((uint)(count - 1), 1); + if (symbols[0] <= 1) + { + this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. + this.bitWriter.PutBits((uint)symbols[0], 1); + } + else + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)symbols[0], 8); + } + + if (count == 2) + { + this.bitWriter.PutBits((uint)symbols[1], 8); + } + } + else + { + this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); + } + } + + private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + { + int numTokens; + int i; + byte[] codeLengthBitdepth = new byte[WebPConstants.CodeLengthCodes]; + short[] codeLengthBitdepthSymbols = new short[WebPConstants.CodeLengthCodes]; + var huffmanCode = new HuffmanTreeCode(); + huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; + huffmanCode.CodeLengths = codeLengthBitdepth; + huffmanCode.Codes = codeLengthBitdepthSymbols; + + this.bitWriter.PutBits(0, 1); + numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + uint[] histogram = new uint[WebPConstants.CodeLengthCodes + 1]; + bool[] bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; + for (i = 0; i < numTokens; i++) + { + histogram[tokens[i].Code]++; + } + + HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); + this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitdepth); + ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); + + int trailingZeroBits = 0; + int trimmedLength = numTokens; + bool writeTrimmedLength; + int length; + i = numTokens; + while (i-- > 0) + { + int ix = tokens[i].Code; + if (ix == 0 || ix == 17 || ix == 18) + { + trimmedLength--; // discount trailing zeros. + trailingZeroBits += codeLengthBitdepth[ix]; + if (ix == 17) + { + trailingZeroBits += 3; + } + else if (ix == 18) + { + trailingZeroBits += 7; + } + } + else + { + break; + } + } + + writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + length = writeTrimmedLength ? trimmedLength : numTokens; + this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); + if (writeTrimmedLength) + { + if (trimmedLength == 2) + { + this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmed_length=2 + } + else + { + int nbits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nbitpairs = (nbits / 2) + 1; + this.bitWriter.PutBits((uint)nbitpairs - 1, 3); + this.bitWriter.PutBits((uint)trimmedLength - 2, nbitpairs * 2); + } + } + + this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); + } + + private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) + { + for (int i = 0; i < numTokens; i++) + { + int ix = tokens[i].Code; + int extraBits = tokens[i].ExtraBits; + this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); + switch (ix) + { + case 16: + this.bitWriter.PutBits((uint)extraBits, 2); + break; + case 17: + this.bitWriter.PutBits((uint)extraBits, 3); + break; + case 18: + this.bitWriter.PutBits((uint)extraBits, 7); + break; + } + } + } + + private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitdepth) + { + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // Throw away trailing zeros: + int codesToStore = WebPConstants.CodeLengthCodes; + for (; codesToStore > 4; codesToStore--) + { + if (codeLengthBitdepth[storageOrder[codesToStore - 1]] != 0) + { + break; + } + } + + this.bitWriter.PutBits((uint)codesToStore - 4, 4); + for (int i = 0; i < codesToStore; i++) + { + this.bitWriter.PutBits(codeLengthBitdepth[storageOrder[i]], 3); + } + } + + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); @@ -254,7 +439,56 @@ namespace SixLabors.ImageSharp.Formats.WebP int tileY = y & tileMask; int histogramIx = histogramSymbols[0]; Span codes = huffmanCodes.AsSpan(5 * histogramIx); + using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + PixOrCopy v = c.Current; + if ((tileX != (x & tileMask)) || (tileY != (y & tileMask))) + { + tileX = x & tileMask; + tileY = y & tileMask; + histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; + codes = huffmanCodes.AsSpan(5 * histogramIx); + } + if (v.IsLiteral()) + { + byte[] order = { 1, 2, 0, 3 }; + for (int k = 0; k < 4; k++) + { + int code = (int)v.Literal(order[k]); + this.bitWriter.WriteHuffmanCode(codes[k], code); + } + } + else if (v.IsCacheIdx()) + { + int code = (int)v.CacheIdx(); + int literalIx = 256 + WebPConstants.NumLengthCodes + code; + this.bitWriter.WriteHuffmanCode(codes[0], literalIx); + } + else + { + int bits = 0; + int nBits = 0; + int distance = (int)v.Distance(); + int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); + + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCode(codes[4], code); + this.bitWriter.PutBits((uint)bits, nBits); + } + + x += v.Length(); + while (x >= width) + { + x -= width; + y++; + } + } } /// @@ -438,7 +672,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return false; } - // TODO: figure out how the palette needs to be sorted. uint[] paletteArray = palette.Slice(0, enc.PaletteSize).ToArray(); Array.Sort(paletteArray); paletteArray.CopyTo(palette); @@ -464,7 +697,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var colors = new HashSet(); for (int y = 0; y < image.Height; y++) { - System.Span rowSpan = image.GetPixelRowSpan(y); + Span rowSpan = image.GetPixelRowSpan(y); for (int x = 0; x < rowSpan.Length; x++) { colors.Add(rowSpan[x]); @@ -488,6 +721,28 @@ namespace SixLabors.ImageSharp.Formats.WebP return colors.Count; } + private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) + { + int count = 0; + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + if (huffmanCode.CodeLengths[k] != 0) + { + count++; + if (count > 1) + { + return; + } + } + } + + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + huffmanCode.CodeLengths[k] = 0; + huffmanCode.Codes[k] = 0; + } + } + /// /// The palette has been sorted by alpha. This function checks if the other components of the palette /// have a monotonic development with regards to position in the palette. @@ -549,7 +804,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // swap color(palette[bestIdx], palette[i]); + // Swap color(palette[bestIdx], palette[i]); uint best = palette[bestIdx]; palette[bestIdx] = palette[i]; palette[i] = best; @@ -592,6 +847,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // Create Huffman trees. bool[] bufRle = new bool[maxNumSymbols]; var huffTree = new HuffmanTree[3 * maxNumSymbols]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } + for (int i = 0; i < histogramImage.Count; i++) { int codesStartIdx = 5 * i; diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index ae66e90e4..43ded2c51 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -656,72 +656,109 @@ namespace SixLabors.ImageSharp.Formats.WebP } }; - public static (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] - { - (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), - (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), - (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), - (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), - (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + public static readonly (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] + { + (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), + (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), + (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), + (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), + (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + }; + + public static readonly byte[] PrefixEncodeExtraBitsValue = + { + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126 }; static WebPLookupTables() From 35176976596c8f021f874c38c9018cf8ccb596eb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 5 Jun 2020 14:37:55 +0200 Subject: [PATCH 175/359] Apply transformations --- .../Formats/WebP/Lossless/LosslessUtils.cs | 309 +++++- .../Formats/WebP/Lossless/PredictorEncoder.cs | 908 ++++++++++++++++++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 58 +- .../Formats/WebP/Lossless/Vp8LMultipliers.cs | 14 + src/ImageSharp/Formats/WebP/WebPConstants.cs | 5 + .../Formats/WebP/WebPEncoderCore.cs | 318 +++++- 6 files changed, 1580 insertions(+), 32 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index c3cf4cb2d..4132991a7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -73,6 +73,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + public static void SubtractGreenFromBlueAndRed(Span pixelData, int numPixels) + { + for (int i = 0; i < numPixels; i++) + { + uint argb = pixelData[i]; + uint green = (argb >> 8) & 0xff; + uint newR = (((argb >> 16) & 0xff) - green) & 0xff; + uint newB = (((argb >> 0) & 0xff) - green) & 0xff; + pixelData[i] = (argb & 0xff00ff00u) | (newR << 16) | newB; + } + } + /// /// If there are not many unique pixel values, it is more efficient to create a color index array and replace the pixel values by the array's indices. /// This will reverse the color index transform. @@ -179,6 +191,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + public static void TransformColor(Vp8LMultipliers m, Span data, int numPixels) + { + for (int i = 0; i < numPixels; i++) + { + uint argb = data[i]; + sbyte green = U32ToS8(argb >> 8); + sbyte red = U32ToS8(argb >> 16); + int newRed = red & 0xff; + int newBlue = (int)(argb & 0xff); + newRed -= ColorTransformDelta((sbyte)m.GreenToRed, green); + newRed &= 0xff; + newBlue -= ColorTransformDelta((sbyte)m.GreenToBlue, green); + newBlue -= ColorTransformDelta((sbyte)m.RedToBlue, red); + newBlue &= 0xff; + data[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + } + } + /// /// Reverses the color space transform. /// @@ -345,6 +375,86 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } + /// + /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. + /// + public static void BundleColorMap(Span row, int width, int xBits, Span dst) + { + int x; + if (xBits > 0) + { + int bitDepth = 1 << (3 - xBits); + int mask = (1 << xBits) - 1; + uint code = 0xff000000; + for (x = 0; x < width; x++) + { + int xsub = x & mask; + if (xsub == 0) + { + code = 0xff000000; + } + + code |= (uint)(row[x] << (8 + (bitDepth * xsub))); + dst[x >> xBits] = code; + } + } + else + { + for (x = 0; x < width; x++) + { + dst[x] = (uint)(0xff000000 | (row[x] << 8)); + } + } + } + + /// + /// Compute the combined Shanon's entropy for distribution {X} and {X+Y}. + /// + /// Shanon entropy. + public static float CombinedShannonEntropy(int[] x, int[] y) + { + double retVal = 0.0d; + uint sumX = 0, sumXY = 0; + for (int i = 0; i < 256; i++) + { + uint xi = (uint)x[i]; + if (xi != 0) + { + uint xy = xi + (uint)y[i]; + sumX += xi; + retVal -= FastSLog2(xi); + sumXY += xy; + retVal -= FastSLog2(xy); + } + else if (y[i] != 0) + { + sumXY += (uint)y[i]; + retVal -= FastSLog2((uint)y[i]); + } + } + + retVal += FastSLog2(sumX) + FastSLog2(sumXY); + return (float)retVal; + } + + public static sbyte TransformColorRed(sbyte greenToRed, uint argb) + { + sbyte green = U32ToS8(argb >> 8); + int newRed = (int)(argb >> 16); + newRed -= ColorTransformDelta(greenToRed, green); + return (sbyte)(newRed & 0xff); + } + + public static sbyte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) + { + sbyte green = U32ToS8(argb >> 8); + sbyte red = U32ToS8(argb >> 16); + int newBlue = (int)(argb & 0xff); + newBlue -= ColorTransformDelta(greenToBlue, green); + newBlue -= ColorTransformDelta(redToBlue, red); + return (sbyte)(newBlue & 0xff); + } + /// /// Fast calculation of log2(v) for integer input. /// @@ -362,6 +472,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (v < LogLookupIdxMax) ? WebPLookupTables.SLog2Table[v] : FastSLog2Slow(v); } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) + { + m.GreenToRed = (byte)(colorCode & 0xff); + m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); + m.RedToBlue = (byte)((colorCode >> 16) & 0xff); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int NearLosslessBits(int nearLosslessQuality) + { + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 + return 5 - (nearLosslessQuality / 20); + } + private static float FastSLog2Slow(uint v) { Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); @@ -605,86 +735,224 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor2(Span top, int idx) + public static uint Predictor2(Span top, int idx) { return top[idx]; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor3(Span top, int idx) + public static uint Predictor3(Span top, int idx) { return top[idx + 1]; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor4(Span top, int idx) + public static uint Predictor4(Span top, int idx) { return top[idx - 1]; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor5(uint left, Span top, int idx) + public static uint Predictor5(uint left, Span top, int idx) { uint pred = Average3(left, top[idx], top[idx + 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor6(uint left, Span top, int idx) + public static uint Predictor6(uint left, Span top, int idx) { uint pred = Average2(left, top[idx - 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor7(uint left, Span top, int idx) + public static uint Predictor7(uint left, Span top, int idx) { uint pred = Average2(left, top[idx]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor8(Span top, int idx) + public static uint Predictor8(Span top, int idx) { uint pred = Average2(top[idx - 1], top[idx]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor9(Span top, int idx) + public static uint Predictor9(Span top, int idx) { uint pred = Average2(top[idx], top[idx + 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor10(uint left, Span top, int idx) + public static uint Predictor10(uint left, Span top, int idx) { uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor11(uint left, Span top, int idx) + public static uint Predictor11(uint left, Span top, int idx) { uint pred = Select(top[idx], left, top[idx - 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor12(uint left, Span top, int idx) + public static uint Predictor12(uint left, Span top, int idx) { uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); return pred; } [MethodImpl(InliningOptions.ShortMethod)] - private static uint Predictor13(uint left, Span top, int idx) + public static uint Predictor13(uint left, Span top, int idx) { uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); return pred; } + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub0(Span input, int numPixels, Span output) + { + for (int i = 0; i < numPixels; i++) + { + output[i] = SubPixels(input[i], WebPConstants.ArgbBlack); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub1(Span input, int idx, int numPixels, Span output) + { + for (int i = 0; i < numPixels; i++) + { + output[i] = SubPixels(input[idx + i], input[idx + i - 1]); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub2(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor2(upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub3(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor3(upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub4(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor4(upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub5(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor5(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub6(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor6(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub7(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor7(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub8(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor8(upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub9(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor9(upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub10(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor10(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub11(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor11(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub12(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor12(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub13(Span input, int idx, Span upper, int numPixels, Span output) + { + for (int x = 0; x < numPixels; x++) + { + uint pred = Predictor13(input[idx - 1], upper, x); + output[x] = SubPixels(input[idx + x], pred); + } + } + private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { int a = AddSubtractComponentFull( @@ -780,7 +1048,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Sum of each component, mod 256. /// [MethodImpl(InliningOptions.ShortMethod)] - private static uint AddPixels(uint a, uint b) + public static uint AddPixels(uint a, uint b) { uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); @@ -800,20 +1068,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) + private static sbyte U32ToS8(uint v) { - m.GreenToRed = (byte)(colorCode & 0xff); - m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); - m.RedToBlue = (byte)((colorCode >> 16) & 0xff); - } - - internal struct Vp8LMultipliers - { - public byte GreenToRed; - - public byte GreenToBlue; - - public byte RedToBlue; + return (sbyte)(v & 0xff); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs new file mode 100644 index 000000000..c954e18b7 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -0,0 +1,908 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Image transform methods for the lossless webp encoder. + /// + internal static class PredictorEncoder + { + private const int GreenRedToBlueNumAxis = 8; + + private const int GreenRedToBlueMaxIters = 7; + + private const float MaxDiffCost = 1e30f; + + private const uint MaskAlpha = 0xff000000; + + private const float SpatialPredictorBias = 15.0f; + + /// + /// Finds the best predictor for each tile, and converts the image to residuals + /// with respect to predictions. If nearLosslessQuality < 100, applies + /// near lossless processing, shaving off more bits of residuals for lower qualities. + /// + public static void ResidualImage(int width, int height, int bits, Span argb, Span argbScratch, Span image, int nearLosslessQuality, bool exact, bool usedSubtractGreen) + { + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); + int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); + int[][] histo = new int[4][]; + for (int i = 0; i < 4; i++) + { + histo[i] = new int[256]; + } + + for (int tileY = 0; tileY < tilesPerCol; tileY++) + { + for (int tileX = 0; tileX < tilesPerRow; tileX++) + { + int pred = GetBestPredictorForTile(width, height, tileX, tileY, bits, histo, argbScratch, argb, maxQuantization, exact, usedSubtractGreen, image); + image[(tileY * tilesPerRow) + tileX] = (uint)(WebPConstants.ArgbBlack | (pred << 8)); + } + } + + CopyImageWithPrediction(width, height, bits, image, argbScratch, argb, maxQuantization, exact, usedSubtractGreen); + } + + public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span argb, Span image) + { + int maxTileSize = 1 << bits; + int tileXSize = LosslessUtils.SubSampleSize(width, bits); + int tileYSize = LosslessUtils.SubSampleSize(height, bits); + int[] accumulatedRedHisto = new int[256]; + int[] accumulatedBlueHisto = new int[256]; + var prevX = default(Vp8LMultipliers); + var prevY = default(Vp8LMultipliers); + for (int tileY = 0; tileY < tileYSize; tileY++) + { + for (int tileX = 0; tileX < tileXSize; tileX++) + { + int tileXOffset = tileX * maxTileSize; + int tileYOffset = tileY * maxTileSize; + int allXMax = GetMin(tileXOffset + maxTileSize, width); + int allYMax = GetMin(tileYOffset + maxTileSize, height); + int offset = (tileY * tileXSize) + tileX; + if (tileY != 0) + { + LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); + } + + prevX = GetBestColorTransformForTile(tileX, tileY, bits, + prevX, prevY, + quality, width, height, + accumulatedRedHisto, + accumulatedBlueHisto, + argb); + + image[offset] = MultipliersToColorCode(prevX); + CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, argb); + + // Gather accumulated histogram data. + for (int y = tileYOffset; y < allYMax; y++) + { + int ix = (y * width) + tileXOffset; + int ixEnd = ix + allXMax - tileXOffset; + + for (; ix < ixEnd; ix++) + { + uint pix = argb[ix]; + if (ix >= 2 && pix == argb[ix - 2] && pix == argb[ix - 1]) + { + continue; // Repeated pixels are handled by backward references. + } + + if (ix >= width + 2 && argb[ix - 2] == argb[ix - width - 2] && argb[ix - 1] == argb[ix - width - 1] && pix == argb[ix - width]) + { + continue; // Repeated pixels are handled by backward references. + } + + accumulatedRedHisto[(pix >> 16) & 0xff]++; + accumulatedBlueHisto[(pix >> 0) & 0xff]++; + } + } + } + } + } + + /// + /// Returns best predictor and updates the accumulated histogram. + /// If max_quantization > 1, assumes that near lossless processing will be + /// applied, quantizing residuals to multiples of quantization levels up to + /// maxQuantization (the actual quantization level depends on smoothness near + /// the given pixel). + /// + /// Best predictor. + private static int GetBestPredictorForTile(int width, int height, int tileX, int tileY, + int bits, int[][] accumulated, Span argbScratch, Span argb, + int maxQuantization, bool exact, bool usedSubtractGreen, Span modes) + { + const int numPredModes = 14; + int startX = tileX << bits; + int startY = tileY << bits; + int tileSize = 1 << bits; + int maxY = GetMin(tileSize, height - startY); + int maxX = GetMin(tileSize, width - startX); + + // Whether there exist columns just outside the tile. + int haveLeft = (startX > 0) ? 1 : 0; + + // Position and size of the strip covering the tile and adjacent columns if they exist. + int contextStartX = startX - haveLeft; + int contextWidth = maxX + haveLeft + (maxX < width ? 1 : 0) - startX; + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + + // Prediction modes of the left and above neighbor tiles. + int leftMode = (int)((tileX > 0) ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); + int aboveMode = (int)((tileY > 0) ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); + + // The width of upper_row and current_row is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + Span upperRow = argbScratch; + Span currentRow = upperRow.Slice(width + 1); + Span maxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); + float bestDiff = MaxDiffCost; + int bestMode = 0; + uint[] residuals = new uint[1 << WebPConstants.MaxTransformBits]; + int[][] histoArgb = new int[4][]; + int[][] bestHisto = new int[4][]; + for (int i = 0; i < 4; i++) + { + histoArgb[i] = new int[256]; + bestHisto[i] = new int[256]; + } + + for (int mode = 0; mode < numPredModes; mode++) + { + float curDiff; + for (int i = 0; i < 4; i++) + { + histoArgb[i].AsSpan().Fill(0); + } + + if (startY > 0) + { + // Read the row above the tile which will become the first upper_row. + // Include a pixel to the left if it exists; include a pixel to the right + // in all cases (wrapping to the leftmost pixel of the next row if it does + // not exist). + Span src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1); + Span dst = currentRow.Slice(contextStartX); + src.CopyTo(dst); + } + + for (int relativeY = 0; relativeY < maxY; relativeY++) + { + int y = startY + relativeY; + Span tmp = upperRow; + upperRow = currentRow; + currentRow = tmp; + + // Read current_row. Include a pixel to the left if it exists; include a + // pixel to the right in all cases except at the bottom right corner of + // the image (wrapping to the leftmost pixel of the next row if it does + // not exist in the current row). + Span src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); + Span dst = currentRow.Slice(contextStartX); + src.CopyTo(dst); + if (maxQuantization > 1 && y >= 1 && y + 1 < height) + { + MaxDiffsForRow(contextWidth, width, argb.Slice((y * width) + contextStartX), maxDiffs.Slice(contextStartX), usedSubtractGreen); + } + + GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, residuals); + for (int relativeX = 0; relativeX < maxX; relativeX++) + { + UpdateHisto(histoArgb, residuals[relativeX]); + } + } + + curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); + + // Favor keeping the areas locally similar. + if (mode == leftMode) + { + curDiff -= SpatialPredictorBias; + } + + if (mode == aboveMode) + { + curDiff -= SpatialPredictorBias; + } + + if (curDiff < bestDiff) + { + for (int i = 0; i < 4; i++) + { + histoArgb[i].AsSpan().CopyTo(bestHisto[i]); + } + + bestDiff = curDiff; + bestMode = mode; + } + } + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 256; j++) + { + accumulated[i][j] += bestHisto[i][j]; + } + } + + return bestMode; + } + + /// + /// Stores the difference between the pixel and its prediction in "output". + /// In case of a lossy encoding, updates the source image to avoid propagating + /// the deviation further to pixels which depend on the current pixel for their + /// predictions. + /// + private static void GetResidual(int width, int height, Span upperRow, Span currentRow, Span maxDiffs, int mode, int xStart, int xEnd, int y, int maxQuantization, bool exact, bool usedSubtractGreen, Span output) + { + if (exact) + { + PredictBatch(mode, xStart, y, xEnd - xStart, currentRow, upperRow, output); + } + else + { + for (int x = xStart; x < xEnd; x++) + { + uint predict = 0; + uint residual; + if (y == 0) + { + predict = (x == 0) ? WebPConstants.ArgbBlack : currentRow[x - 1]; // Left. + } + else if (x == 0) + { + predict = upperRow[x]; // Top. + } + else + { + switch (mode) + { + case 0: + predict = WebPConstants.ArgbBlack; + break; + case 1: + predict = currentRow[x - 1]; + break; + case 2: + predict = LosslessUtils.Predictor2(upperRow, x); + break; + case 3: + predict = LosslessUtils.Predictor3(upperRow, x); + break; + case 4: + predict = LosslessUtils.Predictor4(upperRow, x); + break; + case 5: + predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow, x); + break; + case 6: + predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow, x); + break; + case 7: + predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow, x); + break; + case 8: + predict = LosslessUtils.Predictor8(upperRow, x); + break; + case 9: + predict = LosslessUtils.Predictor9(upperRow, x); + break; + case 10: + predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow, x); + break; + case 11: + predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow, x); + break; + case 12: + predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow, x); + break; + case 13: + predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow, x); + break; + } + } + + if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) + { + residual = LosslessUtils.SubPixels(currentRow[x], predict); + } + else + { + residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); + + // Update the source image. + currentRow[x] = LosslessUtils.AddPixels(predict, residual); + + // x is never 0 here so we do not need to update upper_row like below. + } + + if ((currentRow[x] & MaskAlpha) == 0) + { + // If alpha is 0, cleanup RGB. We can choose the RGB values of the + // residual for best compression. The prediction of alpha itself can be + // non-zero and must be kept though. We choose RGB of the residual to be + // 0. + residual &= MaskAlpha; + + // Update the source image. + currentRow[x] = predict & ~MaskAlpha; + + // The prediction for the rightmost pixel in a row uses the leftmost + // pixel + // in that row as its top-right context pixel. Hence if we change the + // leftmost pixel of current_row, the corresponding change must be + // applied + // to upper_row as well where top-right context is being read from. + if (x == 0 && y != 0) + { + upperRow[width] = currentRow[0]; + } + } + + output[x - xStart] = residual; + } + } + } + + /// + /// Quantize every component of the difference between the actual pixel value and + /// its prediction to a multiple of a quantization (a power of 2, not larger than + /// maxQuantization which is a power of 2, smaller than maxDiff). Take care if + /// value and predict have undergone subtract green, which means that red and + /// blue are represented as offsets from green. + /// + private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) + { + int quantization; + byte newGreen = 0; + byte greenDiff = 0; + byte a, r, g, b; + if (maxDiff <= 2) + { + return LosslessUtils.SubPixels(value, predict); + } + + quantization = maxQuantization; + while (quantization >= maxDiff) + { + quantization >>= 1; + } + + if ((value >> 24) == 0 || (value >> 24) == 0xff) + { + // Preserve transparency of fully transparent or fully opaque pixels. + a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); + } + else + { + a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); + } + + g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); + + if (usedSubtractGreen) + { + // The green offset will be added to red and blue components during decoding + // to obtain the actual red and blue values. + newGreen = (byte)(((predict >> 8) + g) & 0xff); + + // The amount by which green has been adjusted during quantization. It is + // subtracted from red and blue for compensation, to avoid accumulating two + // quantization errors in them. + greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); + } + + r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); + b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); + + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; + } + + /// + /// Quantize the difference between the actual component value and its prediction + /// to a multiple of quantization, working modulo 256, taking care not to cross + /// a boundary (inclusive upper limit). + /// + private static byte NearLosslessComponent(byte value, byte predict, byte boundary, int quantization) + { + int residual = (value - predict) & 0xff; + int boundaryResidual = (boundary - predict) & 0xff; + int lower = residual & ~(quantization - 1); + int upper = lower + quantization; + + // Resolve ties towards a value closer to the prediction (i.e. towards lower + // if value comes after prediction and towards upper otherwise). + int bias = ((boundary - value) & 0xff) < boundaryResidual ? 1 : 0; + + if (residual - lower < upper - residual + bias) + { + // lower is closer to residual than upper. + if (residual > boundaryResidual && lower <= boundaryResidual) + { + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint >= residual + // (since lower is closer than upper) and residual is above the boundary. + return (byte)(lower + (quantization >> 1)); + } + + return (byte)lower; + } + else + { + // upper is closer to residual than lower. + if (residual <= boundaryResidual && upper > boundaryResidual) + { + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint <= residual + // (since upper is closer than lower) and residual is below the boundary. + return (byte)(lower + (quantization >> 1)); + } + + return (byte)(upper & 0xff); + } + } + + /// + /// Converts pixels of the image to residuals with respect to predictions. + /// If max_quantization > 1, applies near lossless processing, quantizing + /// residuals to multiples of quantization levels up to max_quantization + /// (the actual quantization level depends on smoothness near the given pixel). + /// + private static void CopyImageWithPrediction(int width, int height, int bits, Span modes, Span argbScratch, Span argb, int maxQuantization, bool exact, bool usedSubtractGreen) + { + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + + // The width of upper_row and current_row is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + Span upperRow = argbScratch; + Span currentRow = upperRow.Slice(width + 1); + Span currentMaxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); + Span lowerMaxDiffs = currentMaxDiffs.Slice(width); + for (int y = 0; y < height; y++) + { + Span tmp32 = upperRow; + upperRow = currentRow; + currentRow = tmp32; + argb.Slice(y * width, width + y + (1 < height ? 1 : 0)).CopyTo(currentRow); + if (maxQuantization > 1) + { + // Compute max_diffs for the lower row now, because that needs the + // contents of argb for the current row, which we will overwrite with + // residuals before proceeding with the next row. + Span tmp8 = currentMaxDiffs; + currentMaxDiffs = lowerMaxDiffs; + lowerMaxDiffs = tmp8; + if (y + 2 < height) + { + MaxDiffsForRow(width, width, argb.Slice((y + 1) * width), lowerMaxDiffs, usedSubtractGreen); + } + } + + for (int x = 0; x < width;) + { + int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); + int xEnd = x + (1 << bits); + if (xEnd > width) + { + xEnd = width; + } + + GetResidual(width, height, upperRow, currentRow, currentMaxDiffs, + mode, x, xEnd, y, maxQuantization, exact, + usedSubtractGreen, argb.Slice((y * width) + x)); + x = xEnd; + } + } + } + + private static void PredictBatch(int mode, int xStart, int y, int numPixels, Span current, Span upper, Span output) + { + if (xStart == 0) + { + if (y == 0) + { + // ARGB_BLACK. + LosslessUtils.PredictorSub0(current, 1, output); + } + else + { + // Top one. + LosslessUtils.PredictorSub2(current, 0, upper, 1, output); + } + + xStart++; + output = output.Slice(1); + numPixels--; + } + + if (y == 0) + { + // Left one. + LosslessUtils.PredictorSub1(current, xStart, numPixels, output); + } + else + { + switch (mode) + { + case 0: + LosslessUtils.PredictorSub0(current, numPixels, output); + break; + case 1: + LosslessUtils.PredictorSub1(current, xStart, numPixels, output); + break; + case 2: + LosslessUtils.PredictorSub2(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 3: + LosslessUtils.PredictorSub3(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 4: + LosslessUtils.PredictorSub4(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 5: + LosslessUtils.PredictorSub5(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 6: + LosslessUtils.PredictorSub6(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 7: + LosslessUtils.PredictorSub7(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 8: + LosslessUtils.PredictorSub8(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 9: + LosslessUtils.PredictorSub9(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 10: + LosslessUtils.PredictorSub10(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 11: + LosslessUtils.PredictorSub11(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 12: + LosslessUtils.PredictorSub12(current, xStart, upper.Slice(xStart), numPixels, output); + break; + case 13: + LosslessUtils.PredictorSub13(current, xStart, upper.Slice(xStart), numPixels, output); + break; + } + } + } + + private static void MaxDiffsForRow(int width, int stride, Span argb, Span maxDiffs, bool usedSubtractGreen) + { + if (width <= 2) + { + return; + } + + uint current = argb[0]; + uint right = argb[1]; + if (usedSubtractGreen) + { + current = AddGreenToBlueAndRed(current); + right = AddGreenToBlueAndRed(right); + } + + for (int x = 1; x < width - 1; x++) + { + uint up = argb[-stride + x]; // TODO: -stride! + uint down = argb[stride + x]; + uint left = current; + current = right; + right = argb[x + 1]; + if (usedSubtractGreen) + { + up = AddGreenToBlueAndRed(up); + down = AddGreenToBlueAndRed(down); + right = AddGreenToBlueAndRed(right); + } + + maxDiffs[x] = (byte)MaxDiffAroundPixel(current, up, down, left, right); + } + } + + private static int MaxDiffBetweenPixels(uint p1, uint p2) + { + int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); + int diffR = Math.Abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); + int diffG = Math.Abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); + int diffB = Math.Abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); + return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); + } + + private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) + { + int diffUp = MaxDiffBetweenPixels(current, up); + int diffDown = MaxDiffBetweenPixels(current, down); + int diffLeft = MaxDiffBetweenPixels(current, left); + int diffRight = MaxDiffBetweenPixels(current, right); + return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); + } + + private static void UpdateHisto(int[][] histoArgb, uint argb) + { + ++histoArgb[0][argb >> 24]; + ++histoArgb[1][(argb >> 16) & 0xff]; + ++histoArgb[2][(argb >> 8) & 0xff]; + ++histoArgb[3][argb & 0xff]; + } + + private static uint AddGreenToBlueAndRed(uint argb) + { + uint green = (argb >> 8) & 0xff; + uint redBlue = argb & 0x00ff00ffu; + redBlue += (green << 16) | green; + redBlue &= 0x00ff00ffu; + return (argb & 0xff00ff00u) | redBlue; + } + + private static void CopyTileWithColorTransform(int xSize, int ySize, int tileX, int tileY, int maxTileSize, Vp8LMultipliers colorTransform, Span argb) + { + int xScan = GetMin(maxTileSize, xSize - tileX); + int yScan = GetMin(maxTileSize, ySize - tileY); + argb = argb.Slice((tileY * xSize) + tileX); + while (yScan-- > 0) + { + LosslessUtils.TransformColor(colorTransform, argb, xScan); + argb = argb.Slice(xSize); + } + } + + private static Vp8LMultipliers GetBestColorTransformForTile(int tile_x, int tile_y, int bits, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int xSize, int ySize, int[] accumulatedRedHisto, int[] accumulatedBlueHisto, Span argb) + { + int maxTileSize = 1 << bits; + int tileYOffset = tile_y * maxTileSize; + int tileXOffset = tile_x * maxTileSize; + int allXMax = GetMin(tileXOffset + maxTileSize, xSize); + int allYMax = GetMin(tileYOffset + maxTileSize, ySize); + int tileWidth = allXMax - tileXOffset; + int tileHeight = allYMax - tileYOffset; + Span tileArgb = argb.Slice((tileYOffset * xSize) + tileXOffset); + + var bestTx = default(Vp8LMultipliers); + + GetBestGreenToRed(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); + + GetBestGreenRedToBlue(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); + + return bestTx; + } + + private static void GetBestGreenToRed(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedRedHisto, ref Vp8LMultipliers bestTx) + { + int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6] + int greenToRedBest = 0; + float bestDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); + for (int iter = 0; iter < maxIters; iter++) + { + // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to + // one in color computation. Having initial delta here as 1 is sufficient + // to explore the range of (-2, 2). + int delta = 32 >> iter; + + // Try a negative and a positive delta from the best known value. + for (int offset = -delta; offset <= delta; offset += 2 * delta) + { + int greenToRedCur = offset + greenToRedBest; + float curDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); + if (curDiff < bestDiff) + { + bestDiff = curDiff; + greenToRedBest = greenToRedCur; + } + } + } + + bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); + } + + private static void GetBestGreenRedToBlue(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) + { + int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; + int greenToBlueBest = 0; + int redToBlueBest = 0; + sbyte[][] offset = { new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 } }; + sbyte[] deltaLut = { 16, 16, 8, 4, 2, 2, 2 }; + + // Initial value at origin: + float bestDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); + for (int iter = 0; iter < iters; iter++) + { + int delta = deltaLut[iter]; + for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) + { + int greenToBlueCur = (offset[axis][0] * delta) + greenToBlueBest; + int redToBlueCur = (offset[axis][1] * delta) + redToBlueBest; + float curDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); + if (curDiff < bestDiff) + { + bestDiff = curDiff; + greenToBlueBest = greenToBlueCur; + redToBlueBest = redToBlueCur; + } + + if (quality < 25 && iter == 4) + { + // Only axis aligned diffs for lower quality. + break; // next iter. + } + } + + if (delta == 2 && greenToBlueBest == 0 && redToBlueBest == 0) + { + // Further iterations would not help. + break; // out of iter-loop. + } + } + + bestTx.GreenToBlue = (byte)(greenToBlueBest & 0xff); + bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); + } + + private static float GetPredictionCostCrossColorRed(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToRed, int[] accumulatedRedHisto) + { + int[] histo = new int[256]; + + CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); + float curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); + + if ((byte)greenToRed == prevX.GreenToRed) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if ((byte)greenToRed == prevY.GreenToRed) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if (greenToRed == 0) + { + curDiff -= 3; + } + + return curDiff; + } + + private static float GetPredictionCostCrossColorBlue(Span argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToBlue, int redToBlue, int[] accumulatedBlueHisto) + { + int[] histo = new int[256]; + + CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + float curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); + if ((byte)greenToBlue == prevX.GreenToBlue) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if ((byte)greenToBlue == prevY.GreenToBlue) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if ((byte)redToBlue == prevX.RedToBlue) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if ((byte)redToBlue == prevY.RedToBlue) + { + curDiff -= 3; // Favor keeping the areas locally similar. + } + + if (greenToBlue == 0) + { + curDiff -= 3; + } + + if (redToBlue == 0) + { + curDiff -= 3; + } + + return curDiff; + } + + private static void CollectColorRedTransforms(Span argb, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) + { + int pos = 0; + while (tileHeight-- > 0) + { + for (int x = 0; x < tileWidth; x++) + { + ++histo[LosslessUtils.TransformColorRed((sbyte)greenToRed, argb[pos + x])]; + } + + pos += stride; + } + } + + private static void CollectColorBlueTransforms(Span argb, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) + { + int pos = 0; + while (tileHeight-- > 0) + { + for (int x = 0; x < tileWidth; x++) + { + ++histo[LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, argb[pos + x])]; + } + + pos += stride; + } + } + + private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) + { + double retVal = 0.0d; + for (int i = 0; i < 4; i++) + { + double kExpValue = 0.94; + retVal += PredictionCostSpatial(tile[i], 1, kExpValue); + retVal += LosslessUtils.CombinedShannonEntropy(tile[i], accumulated[i]); + } + + return (float)retVal; + } + + private static float PredictionCostCrossColor(int[] accumulated, int[] counts) + { + // Favor low entropy, locally and globally. + // Favor small absolute values for PredictionCostSpatial. + const double expValue = 2.4d; + return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); + } + + private static float PredictionCostSpatial(int[] counts, int weight0, double expVal) + { + int significantSymbols = 256 >> 4; + double expDecayFactor = 0.6; + double bits = weight0 * counts[0]; + for (int i = 1; i < significantSymbols; i++) + { + bits += expVal * (counts[i] + counts[256 - i]); + expVal *= expDecayFactor; + } + + return (float)(-0.1 * bits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte NearLosslessDiff(byte a, byte b) + { + return (byte)((a - b) & 0xff); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint MultipliersToColorCode(Vp8LMultipliers m) + { + return 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMin(int a, int b) + { + return (a > b) ? b : a; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMax(int a, int b) + { + return (a < b) ? b : a; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 7df49bac3..0f54285a5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -22,15 +22,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private const int MinBlockSize = 256; + private MemoryAllocator memoryAllocator; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The width of the input image. + /// The height of the input image. public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height) { var pixelCount = width * height; + this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; this.HashChain = new Vp8LHashChain(pixelCount); + this.memoryAllocator = memoryAllocator; - // We round the block size up, so we're guaranteed to have at most MAX_REFS_BLOCK_PER_IMAGE blocks used: + // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; for (int i = 0; i < this.Refs.Length; ++i) { @@ -39,6 +49,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + /// + /// Gets transformed image data. + /// + public IMemoryOwner Bgra { get; } + + /// + /// Gets the scratch memory for bgra rows used for prediction. + /// + public IMemoryOwner BgraScratch { get; set; } + + /// + /// Gets or sets the packed image width. + /// + public int CurrentWidth { get; set; } + /// /// Gets or sets the huffman image bits. /// @@ -50,9 +75,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public int TransformBits { get; set; } /// - /// Gets or sets a value indicating whether to use a color cache. + /// Gets or sets the transform data. /// - public bool UseColorCache { get; set; } + public IMemoryOwner TransformData { get; set; } + + /// + /// Gets or sets the cache bits. If equal to 0, don't use color cache. + /// + public int CacheBits { get; set; } /// /// Gets or sets a value indicating whether to use the cross color transform. @@ -84,14 +114,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public IMemoryOwner Palette { get; } + /// + /// Gets the backward references. + /// public Vp8LBackwardRefs[] Refs { get; } + /// + /// Gets the hash chain. + /// public Vp8LHashChain HashChain { get; } + public void AllocateTransformBuffer(int width, int height) + { + int imageSize = width * height; + + // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra + // pixel in each, plus 2 regular scanlines of bytes. + int argbScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; + int transformDataSize = (this.UsePredictorTransform || this.UseCrossColorTransform) ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; + + this.BgraScratch = this.memoryAllocator.Allocate(argbScratchSize); + this.TransformData = this.memoryAllocator.Allocate(transformDataSize); + } + /// public void Dispose() { + this.Bgra.Dispose(); + this.BgraScratch.Dispose(); this.Palette.Dispose(); + this.TransformData.Dispose(); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs new file mode 100644 index 000000000..3ce9a93ec --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal struct Vp8LMultipliers + { + public byte GreenToRed; + + public byte GreenToBlue; + + public byte RedToBlue; + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 97d5a57c9..5bf313917 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -102,6 +102,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public const int MaxNumberOfTransforms = 4; + /// + /// Maximum value of transformBits in VP8LEncoder. + /// + public const int MaxTransformBits = 6; + /// /// The bit to be written when next data to be read is a transform. /// diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 39f01e30d..f53636cca 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -35,6 +35,12 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
private Vp8LBitWriter bitWriter; + private const int ApplyPaletteGreedyMax = 4; + + private const int PaletteInvSizeBits = 11; + + private const int PaletteInvSize = 1 << PaletteInvSizeBits; + /// /// Initializes a new instance of the class. /// @@ -119,6 +125,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncoderAnalyze(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { + int method = 4; // TODO: method hardcoded to 4 for now. + int quality = 100; // TODO: quality is hardcoded for now. + bool useCache = true; // TODO: useCache is hardcoded for now. int width = image.Width; int height = image.Height; @@ -126,7 +135,6 @@ namespace SixLabors.ImageSharp.Formats.WebP var usePalette = this.AnalyzeAndCreatePalette(image, enc); // Empirical bit sizes. - int method = 4; // TODO: method hardcoded to 4 for now. enc.HistoBits = GetHistoBits(method, usePalette, width, height); enc.TransformBits = GetTransformBits(method, enc.HistoBits); @@ -151,13 +159,42 @@ namespace SixLabors.ImageSharp.Formats.WebP enc.UseSubtractGreenTransform = (entropyIdx == EntropyIx.SubGreen) || (entropyIdx == EntropyIx.SpatialSubGreen); enc.UsePredictorTransform = (entropyIdx == EntropyIx.Spatial) || (entropyIdx == EntropyIx.SpatialSubGreen); enc.UseCrossColorTransform = redAndBlueAlwaysZero ? false : enc.UsePredictorTransform; - enc.UseColorCache = false; + enc.CacheBits = 0; // Encode palette. if (enc.UsePalette) { this.EncodePalette(image, bgra, enc); + this.MapImageFromPalette(enc, width, height); + + // If using a color cache, do not have it bigger than the number of + // colors. + if (useCache && enc.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) + { + enc.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)enc.PaletteSize) + 1; + } } + + // Apply transforms and write transform data. + if (enc.UseSubtractGreenTransform) + { + this.ApplySubtractGreen(enc, enc.CurrentWidth, height); + } + + if (enc.UsePredictorTransform) + { + this.ApplyPredictFilter(enc, enc.CurrentWidth, height, quality, enc.UseSubtractGreenTransform); + } + + if (enc.UseCrossColorTransform) + { + this.ApplyCrossColorFilter(enc, enc.CurrentWidth, height, quality); + } + + this.bitWriter.PutBits(0, 1); // No more transforms. + + // Encode and write the transformed image. + //EncodeImageInternal(); } /// @@ -183,6 +220,51 @@ namespace SixLabors.ImageSharp.Formats.WebP this.EncodeImageNoHuffman(tmpPalette, enc.HashChain, enc.Refs[0], enc.Refs[1], width: paletteSize, height: 1, quality: 20); } + /// + /// Applies the substract green transformation to the pixel data of the image. + /// + /// The VP8 Encoder. + /// The width of the image. + /// The height of the image. + private void ApplySubtractGreen(Vp8LEncoder enc, int width, int height) + { + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); + LosslessUtils.SubtractGreenFromBlueAndRed(enc.Bgra.GetSpan(), width * height); + } + + private void ApplyPredictFilter(Vp8LEncoder enc, int width, int height, int quality, bool usedSubtractGreen) + { + int nearLosslessStrength = 100; // TODO: for now always 100 + bool exact = true; // TODO: always true for now. + int predBits = enc.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, predBits); + int transformHeight = LosslessUtils.SubSampleSize(height, predBits); + + PredictorEncoder.ResidualImage(width, height, predBits, enc.Bgra.GetSpan(), enc.BgraScratch.GetSpan(), enc.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); + + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); + this.bitWriter.PutBits((uint)(predBits - 2), 3); + + this.EncodeImageNoHuffman(enc.TransformData.GetSpan(), enc.HashChain, enc.Refs[0], enc.Refs[1], transformWidth, transformHeight, quality); + } + + private void ApplyCrossColorFilter(Vp8LEncoder enc, int width, int height, int quality) + { + int colorTransformBits = enc.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); + + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, quality, enc.Bgra.GetSpan(), enc.TransformData.GetSpan()); + + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); + this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); + + this.EncodeImageNoHuffman(enc.TransformData.GetSpan(), enc.HashChain, enc.Refs[0], enc.Refs[1], transformWidth, transformHeight, quality); + } + private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { int cacheBits = 0; @@ -239,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } var tokens = new HuffmanTreeToken[maxTokens]; - for(int i = 0; i < tokens.Length; i++) + for (int i = 0; i < tokens.Length; i++) { tokens[i] = new HuffmanTreeToken(); } @@ -721,6 +803,208 @@ namespace SixLabors.ImageSharp.Formats.WebP return colors.Count; } + private void MapImageFromPalette(Vp8LEncoder enc, int width, int height) + { + Span src = enc.Bgra.GetSpan(); + int srcStride = enc.CurrentWidth; + Span dst = enc.Bgra.GetSpan(); // Applying the palette will be done in place. + Span palette = enc.Palette.GetSpan(); + int paletteSize = enc.PaletteSize; + int xBits; + + // Replace each input pixel by corresponding palette index. + // This is done line by line. + if (paletteSize <= 4) + { + xBits = (paletteSize <= 2) ? 3 : 2; + } + else + { + xBits = (paletteSize <= 16) ? 1 : 0; + } + + enc.AllocateTransformBuffer(LosslessUtils.SubSampleSize(width, xBits), height); + + this.ApplyPalette(src, srcStride, dst, enc.CurrentWidth, palette, paletteSize, width, height, xBits); + } + + /// + /// Remap argb values in src[] to packed palettes entries in dst[] + /// using 'row' as a temporary buffer of size 'width'. + /// We assume that all src[] values have a corresponding entry in the palette. + /// Note: src[] can be the same as dst[] + /// + private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) + { + using System.Buffers.IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); + Span tmpRow = tmpRowBuffer.GetSpan(); + + if (paletteSize < ApplyPaletteGreedyMax) + { + // TODO: APPLY_PALETTE_FOR(SearchColorGreedy(palette, palette_size, pix)); + } + else + { + uint[] buffer = new uint[PaletteInvSize]; + + // Try to find a perfect hash function able to go from a color to an index + // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. + int i; + for (i = 0; i < 3; i++) + { + bool useLUT = true; + + // Set each element in buffer to max value. + buffer.AsSpan().Fill(uint.MaxValue); + + for (int j = 0; j < paletteSize; j++) + { + uint ind = 0; + switch (i) + { + case 0: + ind = ApplyPaletteHash0(palette[j]); + break; + case 1: + ind = ApplyPaletteHash1(palette[j]); + break; + case 2: + ind = ApplyPaletteHash2(palette[j]); + break; + } + + if (buffer[ind] != uint.MaxValue) + { + useLUT = false; + break; + } + else + { + buffer[ind] = (uint)j; + } + } + + if (useLUT) + { + break; + } + } + + if (i == 0 || i == 1 || i == 2) + { + ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); + } + else + { + uint[] idxMap = new uint[paletteSize]; + uint[] paletteSorted = new uint[paletteSize]; + PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); + ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); + } + } + } + + private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint pix = src[x]; + if (pix != prevPix) + { + switch (hashIdx) + { + case 0: + prevIdx = buffer[ApplyPaletteHash0(pix)]; + break; + case 1: + prevIdx = buffer[ApplyPaletteHash1(pix)]; + break; + case 2: + prevIdx = buffer[ApplyPaletteHash2(pix)]; + break; + } + + prevPix = pix; + } + + tmpRow[x] = (byte)prevIdx; + } + + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src.Slice((int)srcStride); + dst = dst.Slice((int)dstStride); + } + } + + private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint pix = src[x]; + if (pix != prevPix) + { + prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; + prevPix = pix; + } + + tmpRow[x] = (byte)prevIdx; + } + + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src.Slice((int)srcStride); + dst = dst.Slice((int)dstStride); + } + } + + /// + /// Sort palette in increasing order and prepare an inverse mapping array. + /// + private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) + { + palette.Slice(numColors).CopyTo(sorted); + Array.Sort(sorted, PaletteCompareColorsForSort); + for (int i = 0; i < numColors; i++) + { + idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; + } + } + + private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) + { + int low = 0; + if (sorted[low] == color) + { + return low; // loop invariant: sorted[low] != color + } + + while (true) + { + int mid = (low + hi) >> 1; + if (sorted[mid] == color) + { + return mid; + } + else if (sorted[mid] < color) + { + low = mid; + } + else + { + hi = mid; + } + } + } + private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) { int count = 0; @@ -944,6 +1228,28 @@ namespace SixLabors.ImageSharp.Formats.WebP b[(int)((p >> 0) - green) & 0xff]++; } + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash0(uint color) + { + // Focus on the green color. + return (color >> 8) & 0xff; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash1(uint color) + { + // Forget about alpha. + return ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash2(uint color) + { + // Forget about alpha. + return ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); + } + + [MethodImpl(InliningOptions.ShortMethod)] private static uint HashPix(uint pix) { // Note that masking with 0xffffffffu is for preventing an @@ -951,6 +1257,12 @@ namespace SixLabors.ImageSharp.Formats.WebP return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; } + [MethodImpl(InliningOptions.ShortMethod)] + private static int PaletteCompareColorsForSort(uint p1, uint p2) + { + return (p1 < p2) ? -1 : 1; + } + [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteComponentDistance(uint v) { From 40f18c11e917ffc5c5b235e493c63b89ff5fc160 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Jun 2020 12:34:40 +0200 Subject: [PATCH 176/359] Encode image --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 14 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 58 +- .../WebP/Lossless/DominantCostRange.cs | 92 +++ .../Formats/WebP/Lossless/HistogramEncoder.cs | 627 ++++++++++++++++++ .../Formats/WebP/Lossless/HistogramPair.cs | 19 + .../Formats/WebP/Lossless/LosslessUtils.cs | 8 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 25 +- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 58 ++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 15 + .../Formats/WebP/Lossless/Vp8LHistogram.cs | 121 ++-- .../Formats/WebP/Lossless/Vp8LStreaks.cs | 32 + src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 +- .../Formats/WebP/WebPEncoderCore.cs | 200 ++++-- 13 files changed, 1136 insertions(+), 135 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index afb91b43a..26b58377f 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System; +using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -49,13 +50,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public Vp8LBitWriter(int expectedSize) { this.buffer = new byte[expectedSize]; + this.end = this.buffer.Length; } /// /// This function writes bits into bytes in increasing addresses (little endian), - /// and within a byte least-significant-bit first. - /// This function can write up to 32 bits in one go, but VP8LBitReader can only - /// read 24 bits max (VP8L_MAX_NUM_BIT_READ). + /// and within a byte least-significant-bit first. This function can write up to 32 bits in one go. /// public void PutBits(uint bits, int nBits) { @@ -85,6 +85,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.PutBits((uint)((bits << depth) | symbol), depth + nBits); } + public int NumBytes() + { + return this.cur + ((this.used + 7) >> 3); + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// @@ -101,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } - //*(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)bw->bits_); + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer.AsSpan(this.cur), this.bits); this.cur += WriterBytes; this.bits >>= WriterBits; this.used -= WriterBits; @@ -109,6 +114,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private bool BitWriterResize(int extraSize) { + // TODO: resize buffer return true; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index d9532c91b..048ddde99 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -232,22 +232,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Evaluates best possible backward references for specified quality. /// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache /// bits to use (passing 0 implies disabling the local color cache). - /// The optimal cache bits is evaluated and set for the *cache_bits parameter. + /// The optimal cache bits is evaluated and set for the cacheBits parameter. /// The return value is the pointer to the best of the two backward refs viz, /// refs[0] or refs[1]. /// public static Vp8LBackwardRefs GetBackwardReferences(int width, int height, Span bgra, int quality, - int lz77TypesToTry, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) + int lz77TypesToTry, ref int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) { var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits]; - int lz77Type = 0; int lz77TypeBest = 0; double bitCostBest = -1; int cacheBitsInitial = cacheBits; Vp8LHashChain hashChainBox = null; - for (lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) + for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) { - int res = 0; double bitCost; int cacheBitsTmp = cacheBitsInitial; if ((lz77TypesToTry & lz77Type) == 0) @@ -312,25 +310,30 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Evaluate optimal cache bits for the local color cache. - /// The input *best_cache_bits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). - /// The local color cache is also disabled for the lower (<= 25) quality. + /// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). + /// The local color cache is also disabled for the lower (smaller then 25) quality. /// private static int CalculateBestCacheSize(Span bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits; + if (cacheBitsMax == 0) + { + // Local color cache is disabled. + return 0; + } + double entropyMin = MaxEntropy; int pos = 0; - var ccInit = new int[WebPConstants.MaxColorCacheBits + 1]; var colorCache = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; - if (cacheBitsMax == 0) + for (int i = 0; i < WebPConstants.MaxColorCacheBits + 1; i++) { - // Local color cache is disabled. - return 0; + histos[i] = new Vp8LHistogram(); + colorCache[i] = new ColorCache(); + colorCache[i].Init(i); } - // Find the cache_bits giving the lowest entropy. The search is done in a - // brute-force way as the function (entropy w.r.t cache_bits) can be anything in practice. + // Find the cache_bits giving the lowest entropy. using List.Enumerator c = refs.Refs.GetEnumerator(); while (c.MoveNext()) { @@ -346,13 +349,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // The keys of the caches can be derived from the longest one. int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); - // Do not use the color cache for cache_bits = 0. + // Do not use the color cache for cacheBits = 0. ++histos[0].Blue[b]; ++histos[0].Literal[g]; ++histos[0].Red[r]; ++histos[0].Alpha[a]; - // Deal with cache_bits > 0. + // Deal with cacheBits > 0. for (int i = cacheBitsMax; i >= 1; i--, key >>= 1) { if (colorCache[i].Lookup(key) == pix) @@ -371,7 +374,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - // We should compute the contribution of the (distance,length) + // We should compute the contribution of the (distance, length) // histograms but those are the same independently from the cache size. // As those constant contributions are in the end added to the other // histogram contributions, we can safely ignore them. @@ -437,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless colorCache.Init(cacheBits); } - // TODO: VP8LClearBackwardRefs(refs); + refs.Refs.Clear(); for (int i = 0; i < pixCount;) { // Alternative #1: Code the pixels starting at 'i' using backward reference. @@ -641,7 +644,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless break; } - // The same color is repeated counts_pos times at j_offset and j. + // The same color is repeated counts_pos times at jOffset and j. currLength += countsJOffset; jOffset += countsJOffset; j += countsJOffset; @@ -693,7 +696,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless colorCache.Init(cacheBits); } - // VP8LClearBackwardRefs(refs); + refs.Refs.Clear(); // Add first pixel as literal. AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); @@ -708,8 +711,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless refs.Add(PixOrCopy.CreateCopy(1, (short)rleLen)); // We don't need to update the color cache here since it is always the - // same pixel being copied, and that does not change the color cache - // state. + // same pixel being copied, and that does not change the color cache state. i += rleLen; } else if (prevRowLen >= MinLength) @@ -734,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (useColorCache) { - // VP8LColorCacheClear(); + // TODO: VP8LColorCacheClear(); } } @@ -756,7 +758,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int ix = colorCache.Contains(bgraLiteral); if (ix >= 0) { - // color cache contains bgraLiteral + // Color cache contains bgraLiteral v = PixOrCopy.CreateCacheIdx(ix); } else @@ -776,7 +778,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - // VP8LColorCacheClear(colorCache); + // TODO: VP8LColorCacheClear(colorCache); } private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) @@ -835,10 +837,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Returns the exact index where array1 and array2 are different. For an index - /// inferior or equal to best_len_match, the return value just has to be strictly - /// inferior to best_len_match. The current behavior is to return 0 if this index - /// is best_len_match, and the index itself otherwise. - /// If no two elements are the same, it returns max_limit. + /// inferior or equal to bestLenMatch, the return value just has to be strictly + /// inferior to best_lenMatch. The current behavior is to return 0 if this index + /// is bestLenMatch, and the index itself otherwise. + /// If no two elements are the same, it returns maxLimit. /// private static int FindMatchLength(Span array1, Span array2, int bestLenMatch, int maxLimit) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs new file mode 100644 index 000000000..f81cc2c9f --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Data container to keep track of cost range for the three dominant entropy symbols. + /// + internal class DominantCostRange + { + /// + /// Initializes a new instance of the class. + /// + public DominantCostRange() + { + this.LiteralMax = 0.0d; + this.LiteralMin = double.MaxValue; + this.RedMax = 0.0d; + this.RedMin = double.MaxValue; + this.BlueMax = 0.0d; + this.BlueMin = double.MaxValue; + } + + public double LiteralMax { get; set; } + + public double LiteralMin { get; set; } + + public double RedMax { get; set; } + + public double RedMin { get; set; } + + public double BlueMax { get; set; } + + public double BlueMin { get; set; } + + public void UpdateDominantCostRange(Vp8LHistogram h) + { + if (this.LiteralMax < h.LiteralCost) + { + this.LiteralMax = h.LiteralCost; + } + + if (this.LiteralMin > h.LiteralCost) + { + this.LiteralMin = h.LiteralCost; + } + + if (this.RedMax < h.RedCost) + { + this.RedMax = h.RedCost; + } + + if (this.RedMin > h.RedCost) + { + this.RedMin = h.RedCost; + } + + if (this.BlueMax < h.BlueCost) + { + this.BlueMax = h.BlueCost; + } + + if (this.BlueMin > h.BlueCost) + { + this.BlueMin = h.BlueCost; + } + } + + public int GetHistoBinIndex(Vp8LHistogram h, int numPartitions) + { + int binId = GetBinIdForEntropy(this.LiteralMin, this.LiteralMax, h.LiteralCost, numPartitions); + binId = (binId * numPartitions) + GetBinIdForEntropy(this.RedMin, this.RedMax, h.RedCost, numPartitions); + binId = (binId * numPartitions) + GetBinIdForEntropy(this.BlueMin, this.BlueMax, h.BlueCost, numPartitions); + + return binId; + } + + private static int GetBinIdForEntropy(double min, double max, double val, int numPartitions) + { + double range = max - min; + if (range > 0.0d) + { + double delta = val - min; + return (int)((numPartitions - 1e-6) * delta / range); + } + else + { + return 0; + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs new file mode 100644 index 000000000..330d3afb2 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -0,0 +1,627 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class HistogramEncoder + { + /// + /// Number of partitions for the three dominant (literal, red and blue) symbol costs. + /// + private const int NumPartitions = 4; + + /// + /// The size of the bin-hash corresponding to the three dominant costs. + /// + private const int BinSize = NumPartitions * NumPartitions * NumPartitions; + + /// + /// Maximum number of histograms allowed in greedy combining algorithm. + /// + private const int MaxHistoGreedy = 100; + + private const uint NonTrivialSym = 0xffffffff; + + public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) + { + int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; + int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; + int imageHistoRawSize = histoXSize * histoYSize; + int entropyCombineNumBins = BinSize; + short[] mapTmp = new short[imageHistoRawSize]; + short[] clusterMappings = new short[imageHistoRawSize]; + int numUsed = imageHistoRawSize; + var origHisto = new List(imageHistoRawSize); + for (int i = 0; i < imageHistoRawSize; i++) + { + origHisto.Add(new Vp8LHistogram(cacheBits)); + } + + // Construct the histograms from backward references. + HistogramBuild(xSize, histoBits, refs, origHisto); + + // Copies the histograms and computes its bit_cost. histogramSymbols is optimized. + HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols); + + var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); + if (entropyCombine) + { + var binMap = mapTmp; + var numClusters = numUsed; + double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); + HistogramAnalyzeEntropyBin(imageHisto, binMap); + + // Collapse histograms with similar entropy. + HistogramCombineEntropyBin(imageHisto, ref numUsed, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); + + OptimizeHistogramSymbols(imageHisto, clusterMappings, numClusters, mapTmp, histogramSymbols); + } + + if (!entropyCombine) + { + float x = quality / 100.0f; + + // Cubic ramp between 1 and MaxHistoGreedy: + int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); + bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize); + if (doGreedy) + { + HistogramCombineGreedy(imageHisto, ref numUsed); + } + } + } + + /// + /// Construct the histograms from backward references. + /// + private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms) + { + int x = 0, y = 0; + int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); + using List.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator(); + while (backwardRefsEnumerator.MoveNext()) + { + PixOrCopy v = backwardRefsEnumerator.Current; + int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); + histograms[ix].AddSinglePixOrCopy(v, false); + x += v.Len; + while (x >= xSize) + { + x -= xSize; + y++; + } + } + } + + /// + /// Partition histograms to different entropy bins for three dominant (literal, + /// red and blue) symbol costs and compute the histogram aggregate bitCost. + /// + private static void HistogramAnalyzeEntropyBin(List histograms, short[] binMap) + { + int histoSize = histograms.Count; + var costRange = new DominantCostRange(); + + // Analyze the dominant (literal, red and blue) entropy costs. + for (int i = 0; i < histoSize; i++) + { + costRange.UpdateDominantCostRange(histograms[i]); + } + + // bin-hash histograms on three of the dominant (literal, red and blue) + // symbol costs and store the resulting bin_id for each histogram. + for (int i = 0; i < histoSize; i++) + { + binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions); + } + } + + private static void HistogramCopyAndAnalyze(List origHistograms, List histograms, ref int numUsed, short[] histogramSymbols) + { + int numUsedOrig = numUsed; + var indicesToRemove = new List(); + for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) + { + Vp8LHistogram histo = origHistograms[i]; + histo.UpdateHistogramCost(); + + // Skip the histogram if it is completely empty, which can happen for tiles + // with no information (when they are skipped because of LZ77). + if (!histo.IsUsed[0] && !histo.IsUsed[1] && !histo.IsUsed[2] && !histo.IsUsed[3] && !histo.IsUsed[4]) + { + indicesToRemove.Add(i); + } + else + { + // TODO: HistogramCopy(histo, histograms[i]); + histogramSymbols[i] = (short)clusterId++; + } + } + + foreach (int indice in indicesToRemove.OrderByDescending(v => v)) + { + origHistograms.RemoveAt(indice); + histograms.RemoveAt(indice); + } + } + + private static void HistogramCombineEntropyBin(List histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) + { + for (int idx = 0; idx < histograms.Count; idx++) + { + clusterMappings[idx] = (short)idx; + } + } + + /// + /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the + /// current assignment of the cells in 'symbols', merge the clusters and + /// assign the smallest possible clusters values. + /// + private static void OptimizeHistogramSymbols(List histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) + { + int clusterMax; + bool doContinue = true; + + // First, assign the lowest cluster to each pixel. + while (doContinue) + { + doContinue = false; + for (int i = 0; i < numClusters; i++) + { + int k; + k = clusterMappings[i]; + while (k != clusterMappings[k]) + { + clusterMappings[k] = clusterMappings[clusterMappings[k]]; + k = clusterMappings[k]; + } + + if (k != clusterMappings[i]) + { + doContinue = true; + clusterMappings[i] = (short)k; + } + } + } + + // Create a mapping from a cluster id to its minimal version. + clusterMax = 0; + clusterMappingsTmp.AsSpan().Fill(0); + + // Re-map the ids. + for (int i = 0; i < histograms.Count; i++) + { + int cluster; + cluster = clusterMappings[symbols[i]]; + if (cluster > 0 && clusterMappingsTmp[cluster] == 0) + { + clusterMax++; + clusterMappingsTmp[cluster] = (short)clusterMax; + } + + symbols[i] = clusterMappingsTmp[cluster]; + } + + // Make sure all cluster values are used. + clusterMax = 0; + for (int i = 0; i < histograms.Count; i++) + { + if (symbols[i] <= clusterMax) + { + continue; + } + + clusterMax++; + } + } + + /// + /// Perform histogram aggregation using a stochastic approach. + /// + /// true if a greedy approach needs to be performed afterwards, false otherwise. + private static bool HistogramCombineStochastic(List histograms, ref int numUsed, int minClusterSize) + { + var rand = new Random(); + int triesWithNoSuccess = 0; + int outerIters = numUsed; + int numTriesNoSuccess = outerIters / 2; + + // Priority queue of histogram pairs. Its size impacts the quality of the compression and the speed: + // the smaller the faster but the worse for the compression. + var histoPriorityList = new List(); + int histoQueueMaxSize = histograms.Count * histograms.Count; + + // Fill the initial mapping. + int[] mappings = new int[histograms.Count]; + for (int j = 0, iter = 0; iter < histograms.Count; iter++) + { + mappings[j++] = iter; + } + + // Collapse similar histograms + for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) + { + double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; + int bestIdx1 = -1; + int bestIdx2 = 1; + int numTries = numUsed / 2; // TODO: should that be histogram.Count/2? + uint randRange = (uint)((numUsed - 1) * numUsed); + + // Pick random samples. + for (int j = 0; numUsed >= 2 && j < numTries; j++) + { + // Choose two different histograms at random and try to combine them. + uint tmp = (uint)(rand.Next() % randRange); + double currCost; + int idx1 = (int)(tmp / (numUsed - 1)); + int idx2 = (int)(tmp % (numUsed - 1)); + if (idx2 >= idx1) + { + idx2++; + } + + idx1 = mappings[idx1]; + idx2 = mappings[idx2]; + + // Calculate cost reduction on combination. + currCost = HistoQueuePush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); + + // Found a better pair? + if (currCost < 0) + { + bestCost = currCost; + + // Empty the queue if we reached full capacity. + if (histoPriorityList.Count == histoQueueMaxSize) + { + break; + } + } + } + + if (histoPriorityList.Count == 0) + { + continue; + } + + // Get the best histograms. + bestIdx1 = histoPriorityList[0].Idx1; + bestIdx2 = histoPriorityList[0].Idx2; + + // Pop bestIdx2 from mappings. + var mappingIndex = Array.BinarySearch(mappings, bestIdx2); + // TODO: memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1)); + + // Merge the histograms and remove bestIdx2 from the queue. + HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); + histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; + histograms.RemoveAt(bestIdx2); + numUsed--; + + var indicesToRemove = new List(); + int lastIndex = histoPriorityList.Count - 1; + for (int j = 0; j < histoPriorityList.Count;) + { + HistogramPair p = histoPriorityList.ElementAt(j); + bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; + bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; + bool doEval = false; + + // The front pair could have been duplicated by a random pick so + // check for it all the time nevertheless. + if (isIdx1Best && isIdx2Best) + { + indicesToRemove.Add(lastIndex); + numUsed--; + lastIndex--; + continue; + } + + // Any pair containing one of the two best indices should only refer to + // best_idx1. Its cost should also be updated. + if (isIdx1Best) + { + p.Idx1 = bestIdx1; + doEval = true; + } + else if (isIdx2Best) + { + p.Idx2 = bestIdx1; + doEval = true; + } + + // Make sure the index order is respected. + if (p.Idx1 > p.Idx2) + { + int tmp = p.Idx2; + p.Idx2 = p.Idx1; + p.Idx1 = tmp; + } + + if (doEval) + { + // Re-evaluate the cost of an updated pair. + HistoQueueUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); + if (p.CostDiff >= 0.0d) + { + indicesToRemove.Add(lastIndex); + lastIndex--; + numUsed--; + continue; + } + } + + HistoQueueUpdateHead(histoPriorityList, p); + j++; + } + + triesWithNoSuccess = 0; + } + + bool doGreedy = numUsed <= minClusterSize; + + return doGreedy; + } + + private static void HistogramCombineGreedy(List histograms, ref int numUsed) + { + int histoSize = histograms.Count; + + // Priority list of histogram pairs. + var histoPriorityList = new List(); + int maxHistoQueueSize = histoSize * histoSize; + + for (int i = 0; i < histograms.Count; i++) + { + for (int j = i + 1; j < histograms.Count; j++) + { + // Initialize queue. + HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, i, j, 0.0d); + } + } + + while (histoPriorityList.Count > 0) + { + int idx1 = histoPriorityList[0].Idx1; + int idx2 = histoPriorityList[0].Idx2; + HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); + histograms[idx1].BitCost = histoPriorityList[0].CostCombo; + + // Remove merged histogram. + histograms.RemoveAt(idx2); + numUsed--; + + // Remove pairs intersecting the just combined best pair. + for (int i = 0; i < histoPriorityList.Count;) + { + HistogramPair p = histoPriorityList.ElementAt(i); + if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) + { + // Remove last entry from the queue. + p = histoPriorityList.ElementAt(histoPriorityList.Count - 1); + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); // TODO: use list instead Queue? + } + else + { + HistoQueueUpdateHead(histoPriorityList, p); + i++; + } + } + + // Push new pairs formed with combined histogram to the queue. + for (int i = 0; i < histograms.Count; i++) + { + if (i == idx1) + { + continue; + } + + HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, idx1, i, 0.0d); + } + } + } + + /// + /// // Create a pair from indices "idx1" and "idx2" provided its cost + /// is inferior to "threshold", a negative entropy. + /// + /// The cost of the pair, or 0. if it superior to threshold. + private static double HistoQueuePush(List histoQueue, int queueMaxSize, List histograms, int idx1, int idx2, double threshold) + { + var pair = new HistogramPair(); + + // Stop here if the queue is full. + if (histoQueue.Count == queueMaxSize) + { + return 0.0d; + } + + if (idx1 > idx2) + { + int tmp = idx2; + idx2 = idx1; + idx1 = tmp; + } + + pair.Idx1 = idx1; + pair.Idx2 = idx2; + Vp8LHistogram h1 = histograms[idx1]; + Vp8LHistogram h2 = histograms[idx2]; + + HistoQueueUpdatePair(h1, h2, threshold, pair); + + // Do not even consider the pair if it does not improve the entropy. + if (pair.CostDiff >= threshold) + { + return 0.0d; + } + + histoQueue.Add(pair); + + HistoQueueUpdateHead(histoQueue, pair); + + return pair.CostDiff; + } + + /// + /// Update the cost diff and combo of a pair of histograms. This needs to be + /// called when the the histograms have been merged with a third one. + /// + private static void HistoQueueUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair) + { + double sumCost = h1.BitCost + h2.BitCost; + pair.CostCombo = GetCombinedHistogramEntropy(h1, h2, sumCost + threshold); + pair.CostDiff = pair.CostCombo - sumCost; + } + + private static double GetCombinedHistogramEntropy(Vp8LHistogram a, Vp8LHistogram b, double costThreshold) + { + double cost = 0.0d; + int paletteCodeBits = a.PaletteCodeBits; + bool trivialAtEnd = false; + + cost += GetCombinedEntropy(a.Literal, b.Literal, Vp8LHistogram.HistogramNumCodes(paletteCodeBits), a.IsUsed[0], b.IsUsed[0], false); + + cost += ExtraCostCombined(a.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); + + if (cost > costThreshold) + { + return 0; + } + + if (a.TrivialSymbol != NonTrivialSym && a.TrivialSymbol == b.TrivialSymbol) + { + // A, R and B are all 0 or 0xff. + uint color_a = (a.TrivialSymbol >> 24) & 0xff; + uint color_r = (a.TrivialSymbol >> 16) & 0xff; + uint color_b = (a.TrivialSymbol >> 0) & 0xff; + if ((color_a == 0 || color_a == 0xff) && + (color_r == 0 || color_r == 0xff) && + (color_b == 0 || color_b == 0xff)) + { + trivialAtEnd = true; + } + } + + cost += GetCombinedEntropy(a.Red, b.Red, WebPConstants.NumLiteralCodes, a.IsUsed[1], b.IsUsed[1], trivialAtEnd); + + return cost; + } + + private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd) + { + var stats = new Vp8LStreaks(); + if (trivialAtEnd) + { + // This configuration is due to palettization that transforms an indexed + // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. + // BitsEntropyRefine is 0 for histograms with only one non-zero value. + // Only FinalHuffmanCost needs to be evaluated. + + // Deal with the non-zero value at index 0 or length-1. + stats.Streaks[1][0] = 1; + + // Deal with the following/previous zero streak. + stats.Counts[0] = 1; + stats.Streaks[0][1] = length - 1; + + return stats.FinalHuffmanCost(); + } + + var bitEntropy = new Vp8LBitEntropy(); + if (isXUsed) + { + if (isYUsed) + { + bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); + } + else + { + bitEntropy.GetEntropyUnrefined(x, length, stats); + } + } + else + { + if (isYUsed) + { + bitEntropy.GetEntropyUnrefined(y, length, stats); + } + else + { + stats.Counts[0] = 1; + stats.Streaks[0][length > 3 ? 1 : 0] = length; + bitEntropy.Init(); + } + } + + return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); + } + + private static double ExtraCostCombined(Span x, Span y, int length) + { + double cost = 0.0d; + for (int i = 2; i < length - 2; i++) + { + int xy = (int)(x[i + 2] + y[i + 2]); + cost += (i >> 1) * xy; + } + + return cost; + } + + private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) + { + // TODO: VP8LHistogramAdd(a, b, out); + output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol) + ? a.TrivialSymbol + : NonTrivialSym; + } + + /// + /// Check whether a pair in the list should be updated as head or not. + /// + private static void HistoQueueUpdateHead(List histoQueue, HistogramPair pair) + { + if (pair.CostDiff < histoQueue[0].CostDiff) + { + // Replace the best pair. + histoQueue.RemoveAt(0); + histoQueue.Insert(0, pair); + } + } + + private static double GetCombineCostFactor(int histoSize, int quality) + { + double combineCostFactor = 0.16d; + if (quality < 90) + { + if (histoSize > 256) + { + combineCostFactor /= 2.0d; + } + + if (histoSize > 512) + { + combineCostFactor /= 2.0d; + } + + if (histoSize > 1024) + { + combineCostFactor /= 2.0d; + } + + if (quality <= 50) + { + combineCostFactor /= 2.0d; + } + } + + return combineCostFactor; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs new file mode 100644 index 000000000..8e314c561 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. + /// + internal class HistogramPair + { + public int Idx1 { get; set; } + + public int Idx2 { get; set; } + + public double CostDiff { get; set; } + + public double CostCombo { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 4132991a7..94510efd2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -437,22 +437,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (float)retVal; } - public static sbyte TransformColorRed(sbyte greenToRed, uint argb) + public static byte TransformColorRed(sbyte greenToRed, uint argb) { sbyte green = U32ToS8(argb >> 8); int newRed = (int)(argb >> 16); newRed -= ColorTransformDelta(greenToRed, green); - return (sbyte)(newRed & 0xff); + return (byte)(newRed & 0xff); } - public static sbyte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) + public static byte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) { sbyte green = U32ToS8(argb >> 8); sbyte red = U32ToS8(argb >> 16); int newBlue = (int)(argb & 0xff); newBlue -= ColorTransformDelta(greenToBlue, green); newBlue -= ColorTransformDelta(redToBlue, red); - return (sbyte)(newBlue & 0xff); + return (byte)(newBlue & 0xff); } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index c954e18b7..467bba031 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Returns best predictor and updates the accumulated histogram. - /// If max_quantization > 1, assumes that near lossless processing will be + /// If maxQuantization > 1, assumes that near lossless processing will be /// applied, quantizing residuals to multiples of quantization levels up to /// maxQuantization (the actual quantization level depends on smoothness near /// the given pixel). @@ -184,10 +184,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless upperRow = currentRow; currentRow = tmp; - // Read current_row. Include a pixel to the left if it exists; include a + // Read currentRow. Include a pixel to the left if it exists; include a // pixel to the right in all cases except at the bottom right corner of // the image (wrapping to the leftmost pixel of the next row if it does - // not exist in the current row). + // not exist in the currentRow). Span src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); Span dst = currentRow.Slice(contextStartX); src.CopyTo(dst); @@ -476,7 +476,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span tmp32 = upperRow; upperRow = currentRow; currentRow = tmp32; - argb.Slice(y * width, width + y + (1 < height ? 1 : 0)).CopyTo(currentRow); + Span src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); + src.CopyTo(currentRow); if (maxQuantization > 1) { // Compute max_diffs for the lower row now, because that needs the @@ -659,7 +660,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless while (yScan-- > 0) { LosslessUtils.TransformColor(colorTransform, argb, xScan); - argb = argb.Slice(xSize); + + if (argb.Length > xSize) + { + argb = argb.Slice(xSize); + } } } @@ -820,15 +825,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void CollectColorRedTransforms(Span argb, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) { - int pos = 0; + int startIdx = 0; while (tileHeight-- > 0) { for (int x = 0; x < tileWidth; x++) { - ++histo[LosslessUtils.TransformColorRed((sbyte)greenToRed, argb[pos + x])]; + int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, argb[startIdx + x]); + ++histo[idx]; } - pos += stride; + startIdx += stride; } } @@ -839,7 +845,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { for (int x = 0; x < tileWidth; x++) { - ++histo[LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, argb[pos + x])]; + int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, argb[pos + x]); + ++histo[idx]; } pos += stride; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index c16ff5297..8e5864146 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -52,6 +52,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public uint NoneZeroCode { get; set; } + public void Init() + { + this.Entropy = 0.0d; + this.Sum = 0; + this.NoneZeros = 0; + this.MaxVal = 0; + this.NoneZeroCode = NonTrivialSym; + } + public double BitsEntropyRefine() { double mix; @@ -95,6 +104,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public void BitsEntropyUnrefined(Span array, int n) { + this.Init(); + for (int i = 0; i < n; i++) { if (array[i] != 0) @@ -121,6 +132,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int i; int iPrev = 0; uint xPrev = x[0]; + + this.Init(); + for (i = 1; i < length; ++i) { uint xi = x[i]; @@ -135,6 +149,50 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Entropy += LosslessUtils.FastSLog2(this.Sum); } + public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xyPrev = x[0] + y[0]; + + this.Init(); + + for (i = 1; i < length; i++) + { + uint xy = x[i] + y[i]; + if (xy != xyPrev) + { + this.GetEntropyUnrefined(xy, i, ref xyPrev, ref iPrev, stats); + } + } + + this.GetEntropyUnrefined(0, i, ref xyPrev, ref iPrev, stats); + + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } + + public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xPrev = x[0]; + + this.Init(); + + for (i = 1; i < length; i++) + { + uint xi = x[i]; + if (xi != xPrev) + { + this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); + } + } + + this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); + + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } + private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) { // Gather info for the bit entropy. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 0f54285a5..32cc7d771 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -22,6 +22,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private const int MinBlockSize = 256; + /// + /// The to use for buffer allocations. + /// private MemoryAllocator memoryAllocator; /// @@ -135,6 +138,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.BgraScratch = this.memoryAllocator.Allocate(argbScratchSize); this.TransformData = this.memoryAllocator.Allocate(transformDataSize); + this.CurrentWidth = width; + } + + /// + /// Clears the backward references. + /// + public void ClearRefs() + { + for (int i = 0; i < this.Refs.Length; i++) + { + this.Refs[i].Refs.Clear(); + } } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index ec326905a..fe47e4548 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LHistogram { + private const uint NonTrivialSym = 0xffffffff; + + /// + /// Initializes a new instance of the class. + /// + /// The backward references to initialize the histogram with. + /// The palette code bits. public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) : this() { @@ -19,12 +26,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.StoreRefs(refs); } + /// + /// Initializes a new instance of the class. + /// + /// The palette code bits. public Vp8LHistogram(int paletteCodeBits) : this() { this.PaletteCodeBits = paletteCodeBits; } + /// + /// Initializes a new instance of the class. + /// public Vp8LHistogram() { this.Red = new uint[WebPConstants.NumLiteralCodes + 1]; @@ -45,24 +59,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public int PaletteCodeBits { get; } /// - /// Gets the cached value of bit cost. + /// Gets or sets the cached value of bit cost. /// - public double BitCost { get; } + public double BitCost { get; set; } /// - /// Gets the cached value of literal entropy costs. + /// Gets or sets the cached value of literal entropy costs. /// - public double LiteralCost { get; } + public double LiteralCost { get; set; } /// - /// Gets the cached value of red entropy costs. + /// Gets or sets the cached value of red entropy costs. /// - public double RedCost { get; } + public double RedCost { get; set; } /// - /// Gets the cached value of blue entropy costs. + /// Gets or sets the cached value of blue entropy costs. /// - public double BlueCost { get; } + public double BlueCost { get; set; } public uint[] Red { get; } @@ -74,8 +88,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public uint[] Distance { get; } + public uint TrivialSymbol { get; set; } + public bool[] IsUsed { get; } + /// + /// Collect all the references into a histogram (without reset). + /// + /// The backward references. public void StoreRefs(Vp8LBackwardRefs refs) { using List.Enumerator c = refs.Refs.GetEnumerator(); @@ -85,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + /// + /// Accumulate a token 'v' into a histogram. + /// + /// The token to add. + /// Indicates whether to use the distance modifier. public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier) { if (v.IsLiteral()) @@ -122,22 +147,48 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); } + /// + /// Estimate how many bits the combined entropy of literals and distance approximately maps to. + /// + /// Estimated bits. public double EstimateBits() { + uint notUsed = 0; return - PopulationCost(this.Literal, this.NumCodes(), ref this.IsUsed[0]) - + PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref this.IsUsed[1]) - + PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref this.IsUsed[2]) - + PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref this.IsUsed[3]) - + PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref this.IsUsed[4]) + PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0]) + + PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1]) + + PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2]) + + PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3]) + + PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes) + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); } + public void UpdateHistogramCost() + { + uint alphaSym = 0, redSym = 0, blueSym = 0; + uint notUsed = 0; + double alphaCost = PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]); + double distanceCost = PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); + int numCodes = HistogramNumCodes(this.PaletteCodeBits); + this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); + this.RedCost = PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]); + this.BlueCost = PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]); + this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; + if ((alphaSym | redSym | blueSym) == NonTrivialSym) + { + this.TrivialSymbol = NonTrivialSym; + } + else + { + this.TrivialSymbol = ((uint)alphaSym << 24) | (redSym << 16) | (blueSym << 0); + } + } + /// /// Get the symbol entropy for the distribution 'population'. /// - private static double PopulationCost(uint[] population, int length, ref bool isUsed) + private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed) { var bitEntropy = new Vp8LBitEntropy(); var stats = new Vp8LStreaks(); @@ -146,42 +197,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // The histogram is used if there is at least one non-zero streak. isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; - return bitEntropy.BitsEntropyRefine() + FinalHuffmanCost(stats); - } - - /// - /// Finalize the Huffman cost based on streak numbers and length type (<3 or >=3). - /// - private static double FinalHuffmanCost(Vp8LStreaks stats) - { - // The constants in this function are experimental and got rounded from - // their original values in 1/8 when switched to 1/1024. - double retval = InitialHuffmanCost(); - - // Second coefficient: Many zeros in the histogram are covered efficiently - // by a run-length encode. Originally 2/8. - retval += (stats.Counts[0] * 1.5625) + (0.234375 * stats.Streaks[0][1]); - - // Second coefficient: Constant values are encoded less efficiently, but still - // RLE'ed. Originally 6/8. - retval += (stats.Counts[1] * 2.578125) + 0.703125 * stats.Streaks[1][1]; - - // 0s are usually encoded more efficiently than non-0s. - // Originally 15/8. - retval += 1.796875 * stats.Streaks[0][0]; - - // Originally 26/8. - retval += 3.28125 * stats.Streaks[1][0]; - - return retval; - } - - private static double InitialHuffmanCost() - { - // Small bias because Huffman code length is typically not stored in full length. - int huffmanCodeOfHuffmanCodeSize = WebPConstants.CodeLengthCodes * 3; - double smallBias = 9.1; - return huffmanCodeOfHuffmanCodeSize - smallBias; + return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); } private static double ExtraCost(Span population, int length) @@ -194,5 +210,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return cost; } + + public static int HistogramNumCodes(int paletteCodeBits) + { + return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((paletteCodeBits > 0) ? (1 << paletteCodeBits) : 0); + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index 41947ae68..728e4e893 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -22,5 +22,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// [zero/non-zero][streak < 3 / streak >= 3]. /// public int[][] Streaks { get; } + + public double FinalHuffmanCost() + { + // The constants in this function are experimental and got rounded from + // their original values in 1/8 when switched to 1/1024. + double retval = InitialHuffmanCost(); + + // Second coefficient: Many zeros in the histogram are covered efficiently + // by a run-length encode. Originally 2/8. + retval += (this.Counts[0] * 1.5625) + (0.234375 * this.Streaks[0][1]); + + // Second coefficient: Constant values are encoded less efficiently, but still + // RLE'ed. Originally 6/8. + retval += (this.Counts[1] * 2.578125) + (0.703125 * this.Streaks[1][1]); + + // 0s are usually encoded more efficiently than non-0s. + // Originally 15/8. + retval += 1.796875 * this.Streaks[0][0]; + + // Originally 26/8. + retval += 3.28125 * this.Streaks[1][0]; + + return retval; + } + + private static double InitialHuffmanCost() + { + // Small bias because Huffman code length is typically not stored in full length. + int huffmanCodeOfHuffmanCodeSize = WebPConstants.CodeLengthCodes * 3; + double smallBias = 9.1; + return huffmanCodeOfHuffmanCodeSize - smallBias; + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 5bf313917..bbc736b59 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Maximum number of color cache bits. /// - public const int MaxColorCacheBits = 11; + public const int MaxColorCacheBits = 10; /// /// The maximum number of allowed transforms in a VP8L bitstream. diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index f53636cca..ee47e33d8 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = image.Width; int height = image.Height; - int initialSize = width * height; + int initialSize = width * height * 2; this.bitWriter = new Vp8LBitWriter(initialSize); // Write image size. @@ -113,34 +113,13 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodeStream(Image image) where TPixel : unmanaged, IPixel { - var encoder = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height); - - // Analyze image (entropy, num_palettes etc). - this.EncoderAnalyze(image, encoder); - } - - /// - /// Analyzes the image and decides what transforms should be used. - /// - private void EncoderAnalyze(Image image, Vp8LEncoder enc) - where TPixel : unmanaged, IPixel - { - int method = 4; // TODO: method hardcoded to 4 for now. - int quality = 100; // TODO: quality is hardcoded for now. - bool useCache = true; // TODO: useCache is hardcoded for now. int width = image.Width; int height = image.Height; - - // Check if we only deal with a small number of colors and should use a palette. - var usePalette = this.AnalyzeAndCreatePalette(image, enc); - - // Empirical bit sizes. - enc.HistoBits = GetHistoBits(method, usePalette, width, height); - enc.TransformBits = GetTransformBits(method, enc.HistoBits); + int bytePosition = this.bitWriter.NumBytes(); + var enc = new Vp8LEncoder(this.memoryAllocator, width, height); // Convert image pixels to bgra array. - using System.Buffers.IMemoryOwner bgraBuffer = this.memoryAllocator.Allocate(width * height); - Span bgra = bgraBuffer.Memory.Span; + Span bgra = enc.Bgra.GetSpan(); int idx = 0; for (int y = 0; y < height; y++) { @@ -151,15 +130,25 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // Try out multiple LZ77 on images with few colors. - var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + // Analyze image (entropy, numPalettes etc). + this.EncoderAnalyze(image, enc, bgra); + + var entropyIdx = 3; // TODO: hardcoded for now. + int quality = 75; // TODO: quality is hardcoded for now. + bool useCache = true; // TODO: useCache is hardcoded for now. + bool redAndBlueAlwaysZero = false; - enc.UsePalette = entropyIdx == EntropyIx.Palette; - enc.UseSubtractGreenTransform = (entropyIdx == EntropyIx.SubGreen) || (entropyIdx == EntropyIx.SpatialSubGreen); - enc.UsePredictorTransform = (entropyIdx == EntropyIx.Spatial) || (entropyIdx == EntropyIx.SpatialSubGreen); + enc.UsePalette = entropyIdx == (int)EntropyIx.Palette; + enc.UseSubtractGreenTransform = (entropyIdx == (int)EntropyIx.SubGreen) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); + enc.UsePredictorTransform = (entropyIdx == (int)EntropyIx.Spatial) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); enc.UseCrossColorTransform = redAndBlueAlwaysZero ? false : enc.UsePredictorTransform; + enc.AllocateTransformBuffer(width, height); + + // Reset any parameter in the encoder that is set in the previous iteration. enc.CacheBits = 0; + enc.ClearRefs(); + + // TODO: Apply near-lossless preprocessing. // Encode palette. if (enc.UsePalette) @@ -167,8 +156,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.EncodePalette(image, bgra, enc); this.MapImageFromPalette(enc, width, height); - // If using a color cache, do not have it bigger than the number of - // colors. + // If using a color cache, do not have it bigger than the number of colors. if (useCache && enc.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) { enc.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)enc.PaletteSize) + 1; @@ -194,7 +182,143 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitWriter.PutBits(0, 1); // No more transforms. // Encode and write the transformed image. - //EncodeImageInternal(); + this.EncodeImage(bgra, enc.HashChain, enc.Refs, enc.CurrentWidth, height, quality, useCache, enc.CacheBits, enc.HistoBits, bytePosition); + } + + /// + /// Analyzes the image and decides what transforms should be used. + /// + private void EncoderAnalyze(Image image, Vp8LEncoder enc, Span bgra) + where TPixel : unmanaged, IPixel + { + int method = 4; // TODO: method hardcoded to 4 for now. + int width = image.Width; + int height = image.Height; + + // Check if we only deal with a small number of colors and should use a palette. + var usePalette = this.AnalyzeAndCreatePalette(image, enc); + + // Empirical bit sizes. + enc.HistoBits = GetHistoBits(method, usePalette, width, height); + enc.TransformBits = GetTransformBits(method, enc.HistoBits); + + // Try out multiple LZ77 on images with few colors. + var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; + EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + + // TODO: Fill CrunchConfig + } + + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition) + { + int lz77sTypesToTrySize = 1; // TODO: harcoded for now. + int[] lz77sTypesToTry = { 3 }; + int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); + short[] histogramSymbols = new short[histogramImageXySize]; + var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; + + if (useCache) + { + if (cacheBits == 0) + { + cacheBits = WebPConstants.MaxColorCacheBits; + } + } + else + { + cacheBits = 0; + } + + // Calculate backward references from ARGB image. + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + // TODO: BitWriterInit(&bw_best, 0) + // BitWriterClone(bw, &bw_best)) + + for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) + { + Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, lz77sTypesToTry[lz77sIdx], ref cacheBits, hashChain, refsArray[0], refsArray[1]); + + // Keep the best references aside and use the other element from the first + // two as a temporary for later usage. + Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; + + var tmpHisto = new Vp8LHistogram(cacheBits); + var histogramImage = new List(histogramImageXySize); + for (int i = 0; i < histogramImageXySize; i++) + { + histogramImage.Add(new Vp8LHistogram(cacheBits)); + } + + // Build histogram image and symbols from backward references. + HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + + // Create Huffman bit lengths and codes for each histogram image. + var histogramImageSize = histogramImage.Count; + var bitArraySize = 5 * histogramImageSize; + var huffmanCodes = new HuffmanTreeCode[bitArraySize]; + + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); + + // Color Cache parameters. + if (cacheBits > 0) + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)cacheBits, 4); + } + else + { + this.bitWriter.PutBits(0, 1); + } + + // Huffman image + meta huffman. + bool writeHistogramImage = histogramImageSize > 1; + this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); + if (writeHistogramImage) + { + using System.Buffers.IMemoryOwner histogramArgbBuffer = this.memoryAllocator.Allocate(histogramImageXySize); + Span histogramArgb = histogramArgbBuffer.GetSpan(); + int maxIndex = 0; + for (int i = 0; i < histogramImageXySize; i++) + { + int symbolIndex = histogramSymbols[i] & 0xffff; + histogramArgb[i] = (uint)(symbolIndex << 8); + if (symbolIndex >= maxIndex) + { + maxIndex = symbolIndex + 1; + } + } + + histogramImageSize = maxIndex; + this.bitWriter.PutBits((uint)(histogramBits - 2), 3); + this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); + } + + // Store Huffman codes. + // Find maximum number of symbols for the huffman tree-set. + int maxTokens = 0; + for (int i = 0; i < 5 * histogramImageSize; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) + { + maxTokens = codes.NumSymbols; + } + } + + var tokens = new HuffmanTreeToken[maxTokens]; + for (int i = 0; i < 5 * histogramImageSize; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + var hdrSizeTmp = (int)(this.bitWriter.NumBytes() - initBytePosition); + this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); + + // TODO: Keep track of the smallest image so far. + } } /// @@ -236,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ApplyPredictFilter(Vp8LEncoder enc, int width, int height, int quality, bool usedSubtractGreen) { int nearLosslessStrength = 100; // TODO: for now always 100 - bool exact = true; // TODO: always true for now. + bool exact = false; // TODO: always false for now. int predBits = enc.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits); @@ -281,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.WebP huffTree[i] = new HuffmanTree(); } - // Calculate backward references from ARGB image. + // Calculate backward references from the image pixels. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( @@ -290,7 +414,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bgra, quality, (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, - cacheBits, + ref cacheBits, hashChain, refsTmp1, refsTmp2); @@ -823,8 +947,6 @@ namespace SixLabors.ImageSharp.Formats.WebP xBits = (paletteSize <= 16) ? 1 : 0; } - enc.AllocateTransformBuffer(LosslessUtils.SubSampleSize(width, xBits), height); - this.ApplyPalette(src, srcStride, dst, enc.CurrentWidth, palette, paletteSize, width, height, xBits); } From affec6a9abdb5b34cc5c03219897b5ef591d5c0f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 14 Jun 2020 16:00:35 +0200 Subject: [PATCH 177/359] BackwardReferencesTraceBackwards --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 218 ++++++++++- .../WebP/Lossless/CostCacheInterval.cs | 20 + .../Formats/WebP/Lossless/CostInterval.cs | 19 + .../Formats/WebP/Lossless/CostManager.cs | 250 +++++++++++++ .../Formats/WebP/Lossless/CostModel.cs | 105 ++++++ .../Formats/WebP/Lossless/HistogramBinInfo.cs | 18 + .../Formats/WebP/Lossless/HistogramEncoder.cs | 350 +++++++++-------- .../Formats/WebP/Lossless/HistogramPair.cs | 3 + .../Formats/WebP/Lossless/HuffmanUtils.cs | 17 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 4 +- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 18 +- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 21 +- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 354 ++++++++++++++++-- .../Formats/WebP/Lossless/Vp8LStreaks.cs | 3 + .../Formats/WebP/WebPEncoderCore.cs | 46 ++- 16 files changed, 1205 insertions(+), 243 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CostManager.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/CostModel.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 26b58377f..ab7d03c7a 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.PutBitsFlushBits(); } - this.bits |= bits << this.used; + this.bits |= (ulong)bits << this.used; this.used += nBits; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 048ddde99..e23b6bf8e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// We want the max value to be attainable and stored in MaxLengthBits bits. /// - private const int MaxLength = (1 << MaxLengthBits) - 1; + public const int MaxLength = (1 << MaxLengthBits) - 1; /// /// Minimum number of pixels for which it is cheaper to encode a @@ -51,6 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private const int MinLength = 4; + // TODO: move to Hashchain? public static void HashChainFill(Vp8LHashChain p, Span bgra, int quality, int xSize, int ySize) { int size = xSize * ySize; @@ -230,7 +232,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Evaluates best possible backward references for specified quality. - /// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache + /// The input cacheBits to 'GetBackwardReferences' sets the maximum cache /// bits to use (passing 0 implies disabling the local color cache). /// The optimal cache bits is evaluated and set for the cacheBits parameter. /// The return value is the pointer to the best of the two backward refs viz, @@ -313,6 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). /// The local color cache is also disabled for the lower (smaller then 25) quality. /// + /// Best cache size. private static int CalculateBestCacheSize(Span bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits; @@ -328,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; for (int i = 0; i < WebPConstants.MaxColorCacheBits + 1; i++) { - histos[i] = new Vp8LHistogram(); + histos[i] = new Vp8LHistogram(bestCacheBits); colorCache[i] = new ColorCache(); colorCache[i].Init(i); } @@ -419,19 +422,210 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { int distArraySize = xSize * ySize; var distArray = new short[distArraySize]; - short[] chosenPath; + + BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); + int chosenPathSize = TraceBackwards(distArray, distArraySize); + Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); + BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); + } + + private static void BackwardReferencesHashChainDistanceOnly(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, short[] distArray) + { + int pixCount = xSize * ySize; + bool useColorCache = cacheBits > 0; + var literalArraySize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0); + var costModel = new CostModel(literalArraySize); + int offsetPrev = -1; + int lenPrev = -1; + double offsetCost = -1; + int firstOffsetIsConstant = -1; // initialized with 'impossible' value + int reach = 0; + var colorCache = new ColorCache(); + + if (useColorCache) + { + colorCache.Init(cacheBits); + } + + costModel.Build(xSize, cacheBits, refs); + var costManager = new CostManager(distArray, pixCount, costModel); + + // We loop one pixel at a time, but store all currently best points to + // non-processed locations from this point. + distArray[0] = 0; + + // Add first pixel as literal. + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); + + for (int i = 1; i < pixCount; i++) + { + float prevCost = costManager.Costs[i - 1]; + int offset = hashChain.FindOffset(i); + int len = hashChain.FindLength(i); + + // Try adding the pixel as a literal. + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray); + + // If we are dealing with a non-literal. + if (len >= 2) + { + if (offset != offsetPrev) + { + int code = DistanceToPlaneCode(xSize, offset); + offsetCost = costModel.GetDistanceCost(code); + firstOffsetIsConstant = 1; + costManager.PushInterval(prevCost + offsetCost, i, len); + } + else + { + // Instead of considering all contributions from a pixel i by calling: + // costManager.PushInterval(prevCost + offsetCost, i, len); + // we optimize these contributions in case offsetCost stays the same + // for consecutive pixels. This describes a set of pixels similar to a + // previous set (e.g. constant color regions). + if (firstOffsetIsConstant != 0) + { + reach = i - 1 + lenPrev - 1; + firstOffsetIsConstant = 0; + } + + if (i + len - 1 > reach) + { + int offsetJ = 0; + int lenJ = 0; + int j; + for (j = i; j <= reach; ++j) + { + offset = hashChain.FindOffset(j + 1); + len = hashChain.FindLength(j + 1); + if (offsetJ != offset) + { + offset = hashChain.FindOffset(j); + len = hashChain.FindLength(j); + break; + } + } + + // Update the cost at j - 1 and j. + costManager.UpdateCostAtIndex(j - 1, false); + costManager.UpdateCostAtIndex(j, false); + + costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ); + reach = j + lenJ - 1; + } + } + } + + costManager.UpdateCostAtIndex(i, true); + offsetPrev = offset; + lenPrev = len; + } + } + + private static int TraceBackwards(short[] distArray, int distArraySize) + { int chosenPathSize = 0; + int pathPos = distArraySize; + int curPos = distArraySize - 1; + while (curPos >= 0) + { + short cur = distArray[curPos]; + pathPos--; + chosenPathSize++; + distArray[pathPos] = cur; + curPos -= cur; + } - // TODO: implement this - // BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); - // TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize); - // BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); + return chosenPathSize; + } + + private static void BackwardReferencesHashChainFollowChosenPath(Span bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) + { + bool useColorCache = cacheBits > 0; + var colorCache = new ColorCache(); + int i = 0; + + if (useColorCache) + { + colorCache.Init(cacheBits); + } + + backwardRefs.Refs.Clear(); + for (int ix = 0; ix < chosenPathSize; ix++) + { + int len = chosenPath[ix]; + if (len != 1) + { + int offset = hashChain.FindOffset(i); + backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len)); + + if (useColorCache) + { + for (int k = 0; k < len; k++) + { + colorCache.Insert(bgra[i + k]); + } + } + + i += len; + } + else + { + PixOrCopy v; + int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1; + if (idx >= 0) + { + // useColorCache is true and color cache contains bgra[i] + // Push pixel as a color cache index. + v = PixOrCopy.CreateCacheIdx(idx); + } + else + { + if (useColorCache) + { + colorCache.Insert(bgra[i]); + } + + v = PixOrCopy.CreateLiteral(bgra[i]); + } + + backwardRefs.Add(v); + i++; + } + } + } + + private static void AddSingleLiteralWithCostModel(Span bgra, ColorCache colorCache, CostModel costModel, int idx, bool useColorCache, float prevCost, float[] cost, short[] distArray) + { + double costVal = prevCost; + uint color = bgra[idx]; + int ix = useColorCache ? colorCache.Contains(color) : -1; + if (ix >= 0) + { + double mul0 = 0.68; + costVal += costModel.GetCacheCost((uint)ix) * mul0; + } + else + { + double mul1 = 0.82; + if (useColorCache) + { + colorCache.Insert(color); + } + + costVal += costModel.GetLiteralCost(color) * mul1; + } + + if (cost[idx] > costVal) + { + cost[idx] = (float)costVal; + distArray[idx] = 1; // only one is inserted. + } } private static void BackwardReferencesLz77(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int iLastCheck = -1; - int ccInit = 0; bool useColorCache = cacheBits > 0; int pixCount = xSize * ySize; var colorCache = new ColorCache(); @@ -526,7 +720,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (bgra[i] == bgra[i + 1]) { - // Max out the counts to MAX_LENGTH. + // Max out the counts to MaxLength. counts[countsPos] = counts[countsPos + 1]; if (counts[countsPos + 1] != MaxLength) { @@ -540,7 +734,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Figure out the window offsets around a pixel. They are stored in a - // spiraling order around the pixel as defined by VP8LDistanceToPlaneCode. + // spiraling order around the pixel as defined by DistanceToPlaneCode. for (int y = 0; y <= 6; y++) { for (int x = -6; x <= 6; x++) @@ -819,7 +1013,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless refs.Add(v); } - private static int DistanceToPlaneCode(int xSize, int dist) + public static int DistanceToPlaneCode(int xSize, int dist) { int yOffset = dist / xSize; int xOffset = dist - (yOffset * xSize); diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs new file mode 100644 index 000000000..68bd7df11 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. + /// + [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] + internal class CostCacheInterval + { + public double Cost { get; set; } + + public int Start { get; set; } + + public int End { get; set; } // Exclusive. + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs new file mode 100644 index 000000000..3f20b3dd6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] + internal class CostInterval + { + public float Cost { get; set; } + + public int Start { get; set; } + + public int End { get; set; } + + public int Index { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs new file mode 100644 index 000000000..1f2241191 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -0,0 +1,250 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// The CostManager is in charge of managing intervals and costs. + /// It caches the different CostCacheInterval, caches the different + /// GetLengthCost(cost_model, k) in cost_cache_ and the CostInterval's. + /// + internal class CostManager + { + public CostManager(short[] distArray, int pixCount, CostModel costModel) + { + int costCacheSize = (pixCount > BackwardReferenceEncoder.MaxLength) ? BackwardReferenceEncoder.MaxLength : pixCount; + + this.Intervals = new List(); + this.CacheIntervals = new List(); + this.CostCache = new List(); + this.Costs = new float[pixCount]; + this.DistArray = distArray; + this.Count = 0; + + // Fill in the cost cache. + this.CacheIntervalsSize++; + this.CostCache.Add(costModel.GetLengthCost(0)); + for (int i = 1; i < costCacheSize; i++) + { + this.CostCache.Add(costModel.GetLengthCost(i)); + + // Get the number of bound intervals. + if (this.CostCache[i] != this.CostCache[i - 1]) + { + this.CacheIntervalsSize++; + } + } + + // Fill in the cache intervals. + var cur = new CostCacheInterval() + { + Start = 0, + End = 1, + Cost = this.CostCache[0] + }; + this.CacheIntervals.Add(cur); + + for (int i = 1; i < costCacheSize; i++) + { + double costVal = this.CostCache[i]; + if (costVal != cur.Cost) + { + cur = new CostCacheInterval() + { + Start = i, + Cost = costVal + }; + this.CacheIntervals.Add(cur); + } + + cur.End = i + 1; + } + + // Set the initial costs_ high for every pixel as we will keep the minimum. + for (int i = 0; i < pixCount; i++) + { + this.Costs[i] = 1e38f; + } + } + + /// + /// Gets the number of stored intervals. + /// + public int Count { get; } + + /// + /// Gets the costs cache. Contains the GetLengthCost(costModel, k). + /// + public List CostCache { get; } + + public int CacheIntervalsSize { get; } + + public float[] Costs { get; } + + public short[] DistArray { get; } + + public List Intervals { get; } + + public List CacheIntervals { get; } + + /// + /// Update the cost at index i by going over all the stored intervals that overlap with i. + /// + /// The index to update. + /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. + public void UpdateCostAtIndex(int i, bool doCleanIntervals) + { + var indicesToRemove = new List(); + using List.Enumerator intervalEnumerator = this.Intervals.GetEnumerator(); + while (intervalEnumerator.MoveNext() && intervalEnumerator.Current.Start <= i) + { + if (intervalEnumerator.Current.End <= i) + { + if (doCleanIntervals) + { + // We have an outdated interval, remove it. + indicesToRemove.Add(i); + } + } + else + { + this.UpdateCost(i, intervalEnumerator.Current.Index, intervalEnumerator.Current.Cost); + } + } + + foreach (int index in indicesToRemove.OrderByDescending(i => i)) + { + this.Intervals.RemoveAt(index); + } + } + + /// + /// Given a new cost interval defined by its start at position, its length value + /// and distanceCost, add its contributions to the previous intervals and costs. + /// If handling the interval or one of its subintervals becomes to heavy, its + /// contribution is added to the costs right away. + /// + public void PushInterval(double distanceCost, int position, int len) + { + // If the interval is small enough, no need to deal with the heavy + // interval logic, just serialize it right away. This constant is empirical. + int skipDistance = 10; + + if (len < skipDistance) + { + for (int j = position; j < position + len; ++j) + { + int k = j - position; + float costTmp = (float)(distanceCost + this.CostCache[k]); + + if (this.Costs[j] > costTmp) + { + this.Costs[j] = costTmp; + this.DistArray[j] = (short)(k + 1); + } + } + + return; + } + + for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) + { + // Define the intersection of the ith interval with the new one. + int start = position + this.CacheIntervals[i].Start; + int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); + float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); + + var idx = i; + CostCacheInterval interval = this.CacheIntervals[idx]; + var indicesToRemove = new List(); + for (; interval.Start < end; idx++) + { + // Make sure we have some overlap. + if (start >= interval.End) + { + continue; + } + + if (cost >= interval.Cost) + { + int startNew = interval.End; + this.InsertInterval(cost, position, start, interval.Start); + start = startNew; + if (start >= end) + { + break; + } + + continue; + } + + if (start <= interval.Start) + { + if (interval.End <= end) + { + indicesToRemove.Add(idx); + } + else + { + interval.Start = end; + break; + } + } + else + { + if (end < interval.End) + { + int endOriginal = interval.End; + interval.End = start; + this.InsertInterval(interval.Cost, idx, end, endOriginal); + break; + } + else + { + interval.End = start; + } + } + } + + foreach (int indice in indicesToRemove.OrderByDescending(i => i)) + { + this.Intervals.RemoveAt(indice); + } + + // Insert the remaining interval from start to end. + this.InsertInterval(cost, position, start, end); + } + } + + private void InsertInterval(double cost, int position, int start, int end) + { + // TODO: use COST_CACHE_INTERVAL_SIZE_MAX + + var interval = new CostCacheInterval() + { + Cost = cost, + Start = start, + End = end + }; + + this.CacheIntervals.Insert(position, interval); + } + + /// + /// Given the cost and the position that define an interval, update the cost at + /// pixel 'i' if it is smaller than the previously computed value. + /// + private void UpdateCost(int i, int position, float cost) + { + int k = i - position; + if (this.Costs[i] > cost) + { + this.Costs[i] = cost; + this.DistArray[i] = (short)(k + 1); + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs new file mode 100644 index 000000000..d0226bbbf --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal class CostModel + { + private const int ValuesInBytes = 256; + + /// + /// Initializes a new instance of the class. + /// + /// The literal array size. + public CostModel(int literalArraySize) + { + this.Alpha = new double[ValuesInBytes]; + this.Red = new double[ValuesInBytes]; + this.Blue = new double[ValuesInBytes]; + this.Distance = new double[WebPConstants.NumDistanceCodes]; + this.Literal = new double[literalArraySize]; + } + + public double[] Alpha { get; } + + public double[] Red { get; } + + public double[] Blue { get; } + + public double[] Distance { get; } + + public double[] Literal { get; } + + public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs) + { + var histogram = new Vp8LHistogram(cacheBits); + using System.Collections.Generic.List.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator(); + + // The following code is similar to HistogramCreate but converts the distance to plane code. + while (refsEnumerator.MoveNext()) + { + histogram.AddSinglePixOrCopy(refsEnumerator.Current, true, xSize); + } + + ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha); + ConvertPopulationCountTableToBitEstimates(WebPConstants.NumDistanceCodes, histogram.Distance, this.Distance); + } + + public double GetLengthCost(int length) + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits); + return this.Literal[ValuesInBytes + code] + extraBits; + } + + public double GetDistanceCost(int distance) + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits); + return this.Distance[code] + extraBits; + } + + public double GetCacheCost(uint idx) + { + int literalIdx = (int)(ValuesInBytes + WebPConstants.NumLengthCodes + idx); + return this.Literal[literalIdx]; + } + + public double GetLiteralCost(uint v) + { + return this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; + } + + private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) + { + uint sum = 0; + int nonzeros = 0; + for (int i = 0; i < numSymbols; i++) + { + sum += populationCounts[i]; + if (populationCounts[i] > 0) + { + nonzeros++; + } + } + + if (nonzeros <= 1) + { + output.AsSpan(0, numSymbols).Fill(0); + } + else + { + double logsum = LosslessUtils.FastLog2(sum); + for (int i = 0; i < numSymbols; i++) + { + output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]); + } + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs new file mode 100644 index 000000000..eb58c6290 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + internal struct HistogramBinInfo + { + /// + /// Position of the histogram that accumulates all histograms with the same binId. + /// + public short First; + + /// + /// Number of combine failures per binId. + /// + public short NumCombineFailures; + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 330d3afb2..6e1b6ab70 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -41,10 +41,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless origHisto.Add(new Vp8LHistogram(cacheBits)); } - // Construct the histograms from backward references. + // Construct the histograms from the backward references. HistogramBuild(xSize, histoBits, refs, origHisto); - // Copies the histograms and computes its bit_cost. histogramSymbols is optimized. + // Copies the histograms and computes its bitCost. histogramSymbols is optimized. HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols); var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); @@ -61,22 +61,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless OptimizeHistogramSymbols(imageHisto, clusterMappings, numClusters, mapTmp, histogramSymbols); } - if (!entropyCombine) + float x = quality / 100.0f; + + // Cubic ramp between 1 and MaxHistoGreedy: + int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); + bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize); + if (doGreedy) { - float x = quality / 100.0f; + HistogramCombineGreedy(imageHisto); + } - // Cubic ramp between 1 and MaxHistoGreedy: - int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); - bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize); - if (doGreedy) + // Find the optimal map from original histograms to the final ones. + RemoveEmptyHistograms(imageHisto); + HistogramRemap(origHisto, imageHisto, histogramSymbols); + } + + private static void RemoveEmptyHistograms(List histograms) + { + int size = 0; + var indicesToRemove = new List(); + for (int i = 0; i < histograms.Count; i++) + { + if (histograms[i] == null) { - HistogramCombineGreedy(imageHisto, ref numUsed); + indicesToRemove.Add(i); + continue; } + + histograms[size++] = histograms[i]; + } + + foreach (int index in indicesToRemove.OrderByDescending(i => i)) + { + histograms.RemoveAt(index); } } /// - /// Construct the histograms from backward references. + /// Construct the histograms from the backward references. /// private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms) { @@ -137,30 +159,91 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - // TODO: HistogramCopy(histo, histograms[i]); + histograms[i] = (Vp8LHistogram)histo.DeepClone(); histogramSymbols[i] = (short)clusterId++; } } - foreach (int indice in indicesToRemove.OrderByDescending(v => v)) + foreach (int index in indicesToRemove.OrderByDescending(v => v)) { - origHistograms.RemoveAt(indice); - histograms.RemoveAt(indice); + origHistograms.RemoveAt(index); + histograms.RemoveAt(index); } } private static void HistogramCombineEntropyBin(List histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) { + var binInfo = new HistogramBinInfo[BinSize]; + for (int idx = 0; idx < numBins; idx++) + { + binInfo[idx].First = -1; + binInfo[idx].NumCombineFailures = 0; + } + + // By default, a cluster matches itself. for (int idx = 0; idx < histograms.Count; idx++) { clusterMappings[idx] = (short)idx; } + + var indicesToRemove = new List(); + for (int idx = 0; idx < histograms.Count; idx++) + { + if (histograms[idx] == null) + { + continue; + } + + int binId = binMap[idx]; + int first = binInfo[binId].First; + if (first == -1) + { + binInfo[binId].First = (short)idx; + } + else + { + // Try to merge #idx into #first (both share the same binId) + double bitCost = histograms[idx].BitCost; + double bitCostThresh = -bitCost * combineCostFactor; + double currCostDiff = histograms[first].AddEval(histograms[idx], bitCostThresh, curCombo); + + if (currCostDiff < bitCostThresh) + { + // Try to merge two histograms only if the combo is a trivial one or + // the two candidate histograms are already non-trivial. + // For some images, 'tryCombine' turns out to be false for a lot of + // histogram pairs. In that case, we fallback to combining + // histograms as usual to avoid increasing the header size. + bool tryCombine = (curCombo.TrivialSymbol != NonTrivialSym) || ((histograms[idx].TrivialSymbol == NonTrivialSym) && (histograms[first].TrivialSymbol == NonTrivialSym)); + int maxCombineFailures = 32; + if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) + { + // Move the (better) merged histogram to its final slot. + Vp8LHistogram tmp = curCombo; + curCombo = histograms[first]; + histograms[first] = tmp; + + histograms[idx] = null; + indicesToRemove.Add(idx); + clusterMappings[clusters[idx]] = clusters[first]; + } + else + { + binInfo[binId].NumCombineFailures++; + } + } + } + } + + foreach (int index in indicesToRemove.OrderByDescending(i => i)) + { + histograms.RemoveAt(index); + } } /// /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the - /// current assignment of the cells in 'symbols', merge the clusters and - /// assign the smallest possible clusters values. + /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. /// private static void OptimizeHistogramSymbols(List histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) { @@ -194,10 +277,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless clusterMappingsTmp.AsSpan().Fill(0); // Re-map the ids. - for (int i = 0; i < histograms.Count; i++) + for (int i = 0; i < symbols.Length; i++) { - int cluster; - cluster = clusterMappings[symbols[i]]; + int cluster = clusterMappings[symbols[i]]; if (cluster > 0 && clusterMappingsTmp[cluster] == 0) { clusterMax++; @@ -206,18 +288,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless symbols[i] = clusterMappingsTmp[cluster]; } - - // Make sure all cluster values are used. - clusterMax = 0; - for (int i = 0; i < histograms.Count; i++) - { - if (symbols[i] <= clusterMax) - { - continue; - } - - clusterMax++; - } } /// @@ -231,6 +301,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int outerIters = numUsed; int numTriesNoSuccess = outerIters / 2; + if (histograms.Count < minClusterSize) + { + return true; + } + // Priority queue of histogram pairs. Its size impacts the quality of the compression and the speed: // the smaller the faster but the worse for the compression. var histoPriorityList = new List(); @@ -269,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless idx2 = mappings[idx2]; // Calculate cost reduction on combination. - currCost = HistoQueuePush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); + currCost = HistoPriorityListPush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); // Found a better pair? if (currCost < 0) @@ -346,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (doEval) { // Re-evaluate the cost of an updated pair. - HistoQueueUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); + HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); if (p.CostDiff >= 0.0d) { indicesToRemove.Add(lastIndex); @@ -356,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - HistoQueueUpdateHead(histoPriorityList, p); + HistoListUpdateHead(histoPriorityList, p); j++; } @@ -368,20 +443,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return doGreedy; } - private static void HistogramCombineGreedy(List histograms, ref int numUsed) + private static void HistogramCombineGreedy(List histograms) { int histoSize = histograms.Count; // Priority list of histogram pairs. var histoPriorityList = new List(); - int maxHistoQueueSize = histoSize * histoSize; + int maxSize = histoSize * histoSize; for (int i = 0; i < histograms.Count; i++) { for (int j = i + 1; j < histograms.Count; j++) { - // Initialize queue. - HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, i, j, 0.0d); + HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d); } } @@ -393,8 +467,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless histograms[idx1].BitCost = histoPriorityList[0].CostCombo; // Remove merged histogram. - histograms.RemoveAt(idx2); - numUsed--; + // TODO: can the element be removed instead? histograms.RemoveAt(idx2); + histograms[idx2] = null; // Remove pairs intersecting the just combined best pair. for (int i = 0; i < histoPriorityList.Count;) @@ -402,41 +476,83 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistogramPair p = histoPriorityList.ElementAt(i); if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) { - // Remove last entry from the queue. - p = histoPriorityList.ElementAt(histoPriorityList.Count - 1); - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); // TODO: use list instead Queue? + // Replace item at pos i with the last one and shrinking the list. + histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); } else { - HistoQueueUpdateHead(histoPriorityList, p); + HistoListUpdateHead(histoPriorityList, p); i++; } } - // Push new pairs formed with combined histogram to the queue. + // Push new pairs formed with combined histogram to the list. for (int i = 0; i < histograms.Count; i++) { - if (i == idx1) + if (i == idx1 || histograms[i] == null) { continue; } - HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, idx1, i, 0.0d); + HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d); } } } + private static void HistogramRemap(List input, List output, short[] symbols) + { + int inSize = symbols.Length; + int outSize = output.Count; + if (outSize > 1) + { + for (int i = 0; i < inSize; i++) + { + int bestOut = 0; + double bestBits = double.MaxValue; + for (int k = 0; k < outSize; k++) + { + double curBits = output[k].AddThresh(input[i], bestBits); + if (k == 0 || curBits < bestBits) + { + bestBits = curBits; + bestOut = k; + } + } + + symbols[i] = (short)bestOut; + } + } + else + { + for (int i = 0; i < inSize; i++) + { + symbols[i] = 0; + } + } + + for (int i = 0; i < inSize; i++) + { + if (input[i] == null) + { + continue; + } + + int idx = symbols[i]; + input[i].Add(output[idx], output[idx]); + } + } + /// - /// // Create a pair from indices "idx1" and "idx2" provided its cost + /// Create a pair from indices "idx1" and "idx2" provided its cost /// is inferior to "threshold", a negative entropy. /// /// The cost of the pair, or 0. if it superior to threshold. - private static double HistoQueuePush(List histoQueue, int queueMaxSize, List histograms, int idx1, int idx2, double threshold) + private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold) { var pair = new HistogramPair(); - // Stop here if the queue is full. - if (histoQueue.Count == queueMaxSize) + if (histoList.Count == maxSize) { return 0.0d; } @@ -453,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Vp8LHistogram h1 = histograms[idx1]; Vp8LHistogram h2 = histograms[idx2]; - HistoQueueUpdatePair(h1, h2, threshold, pair); + HistoListUpdatePair(h1, h2, threshold, pair); // Do not even consider the pair if it does not improve the entropy. if (pair.CostDiff >= threshold) @@ -461,140 +577,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return 0.0d; } - histoQueue.Add(pair); + histoList.Add(pair); - HistoQueueUpdateHead(histoQueue, pair); + HistoListUpdateHead(histoList, pair); return pair.CostDiff; } /// - /// Update the cost diff and combo of a pair of histograms. This needs to be - /// called when the the histograms have been merged with a third one. + /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one. /// - private static void HistoQueueUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair) + private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair) { double sumCost = h1.BitCost + h2.BitCost; - pair.CostCombo = GetCombinedHistogramEntropy(h1, h2, sumCost + threshold); + h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out var cost); + pair.CostCombo = cost; pair.CostDiff = pair.CostCombo - sumCost; } - private static double GetCombinedHistogramEntropy(Vp8LHistogram a, Vp8LHistogram b, double costThreshold) - { - double cost = 0.0d; - int paletteCodeBits = a.PaletteCodeBits; - bool trivialAtEnd = false; - - cost += GetCombinedEntropy(a.Literal, b.Literal, Vp8LHistogram.HistogramNumCodes(paletteCodeBits), a.IsUsed[0], b.IsUsed[0], false); - - cost += ExtraCostCombined(a.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); - - if (cost > costThreshold) - { - return 0; - } - - if (a.TrivialSymbol != NonTrivialSym && a.TrivialSymbol == b.TrivialSymbol) - { - // A, R and B are all 0 or 0xff. - uint color_a = (a.TrivialSymbol >> 24) & 0xff; - uint color_r = (a.TrivialSymbol >> 16) & 0xff; - uint color_b = (a.TrivialSymbol >> 0) & 0xff; - if ((color_a == 0 || color_a == 0xff) && - (color_r == 0 || color_r == 0xff) && - (color_b == 0 || color_b == 0xff)) - { - trivialAtEnd = true; - } - } - - cost += GetCombinedEntropy(a.Red, b.Red, WebPConstants.NumLiteralCodes, a.IsUsed[1], b.IsUsed[1], trivialAtEnd); - - return cost; - } - - private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd) - { - var stats = new Vp8LStreaks(); - if (trivialAtEnd) - { - // This configuration is due to palettization that transforms an indexed - // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. - // BitsEntropyRefine is 0 for histograms with only one non-zero value. - // Only FinalHuffmanCost needs to be evaluated. - - // Deal with the non-zero value at index 0 or length-1. - stats.Streaks[1][0] = 1; - - // Deal with the following/previous zero streak. - stats.Counts[0] = 1; - stats.Streaks[0][1] = length - 1; - - return stats.FinalHuffmanCost(); - } - - var bitEntropy = new Vp8LBitEntropy(); - if (isXUsed) - { - if (isYUsed) - { - bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); - } - else - { - bitEntropy.GetEntropyUnrefined(x, length, stats); - } - } - else - { - if (isYUsed) - { - bitEntropy.GetEntropyUnrefined(y, length, stats); - } - else - { - stats.Counts[0] = 1; - stats.Streaks[0][length > 3 ? 1 : 0] = length; - bitEntropy.Init(); - } - } - - return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); - } - - private static double ExtraCostCombined(Span x, Span y, int length) - { - double cost = 0.0d; - for (int i = 2; i < length - 2; i++) - { - int xy = (int)(x[i + 2] + y[i + 2]); - cost += (i >> 1) * xy; - } - - return cost; - } - - private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) - { - // TODO: VP8LHistogramAdd(a, b, out); - output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol) - ? a.TrivialSymbol - : NonTrivialSym; - } - /// /// Check whether a pair in the list should be updated as head or not. /// - private static void HistoQueueUpdateHead(List histoQueue, HistogramPair pair) + private static void HistoListUpdateHead(List histoList, HistogramPair pair) { - if (pair.CostDiff < histoQueue[0].CostDiff) + if (pair.CostDiff < histoList[0].CostDiff) { // Replace the best pair. - histoQueue.RemoveAt(0); - histoQueue.Insert(0, pair); + var oldIdx = histoList.IndexOf(pair); + histoList[oldIdx] = histoList[0]; + histoList[0] = pair; } } + private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) + { + a.Add(b, output); + output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol) ? a.TrivialSymbol : NonTrivialSym; + } + private static double GetCombineCostFactor(int histoSize, int quality) { double combineCostFactor = 0.16d; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs index 8e314c561..edd8a2ba7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -1,11 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. /// + [DebuggerDisplay("Idx1: {Idx1}, Idx2: {Idx2}, CostDiff: {CostDiff}, CostCombo: {CostCombo}")] internal class HistogramPair { public int Idx1 { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 885d99a13..47f5ec128 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// /// The huffman tree. - /// The historgram. + /// The histogram. /// The size of the histogram. /// The tree depth limit. /// How many bits are used for the symbol. @@ -269,13 +269,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokens) + public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokensArray) { int depthSize = tree.NumSymbols; int prevValue = 8; // 8 is the initial value for rle. int i = 0; - int tokenIdx = 0; - Span tokenSpan = tokens.AsSpan(); + int tokenPos = 0; while (i < depthSize) { int value = tree.CodeLengths[i]; @@ -289,19 +288,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless runs = k - i; if (value == 0) { - tokenIdx += CodeRepeatedZeros(runs, tokens); + tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); } else { - tokenIdx += CodeRepeatedValues(runs, tokens, value, prevValue); + tokenPos += CodeRepeatedValues(runs, tokensArray.AsSpan(tokenPos), value, prevValue); prevValue = value; } - tokenSpan.Slice(tokenIdx); i += runs; } - return tokenIdx; + return tokenPos; } public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) @@ -458,8 +456,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (repetitions < 3) { - int i; - for (i = 0; i < repetitions; ++i) + for (int i = 0; i < repetitions; i++) { tokens[pos].Code = 0; // 0-value tokens[pos].ExtraBits = 0; diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 467bba031..ea11fcfe4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -325,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Update the source image. currentRow[x] = LosslessUtils.AddPixels(predict, residual); - // x is never 0 here so we do not need to update upper_row like below. + // x is never 0 here so we do not need to update upperRow like below. } if ((currentRow[x] & MaskAlpha) == 0) @@ -344,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // in that row as its top-right context pixel. Hence if we change the // leftmost pixel of current_row, the corresponding change must be // applied - // to upper_row as well where top-right context is being read from. + // to upperRow as well where top-right context is being read from. if (x == 0 && y != 0) { upperRow[width] = currentRow[0]; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index 8e5864146..0433f3eed 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -79,9 +79,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (0.99 * this.Sum) + (0.01 * this.Entropy); } - // No matter what the entropy says, we cannot be better than min_limit + // No matter what the entropy says, we cannot be better than minLimit // with Huffman coding. I am mixing a bit of entropy into the - // min_limit since it produces much better (~0.5 %) compression results + // minLimit since it produces much better (~0.5 %) compression results // perhaps because of better entropy clustering. if (this.NoneZeros == 3) { @@ -195,9 +195,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) { - // Gather info for the bit entropy. int streak = i - iPrev; + // Gather info for the bit entropy. + if (valPrev != 0) + { + this.Sum += (uint)(valPrev * streak); + this.NoneZeros += streak; + this.NoneZeroCode = (uint)iPrev; + this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak; + if (this.MaxVal < valPrev) + { + this.MaxVal = valPrev; + } + } + // Gather info for the Huffman cost. stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index e8d383917..60b795c15 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -7,11 +7,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LHashChain { + /// + /// Initializes a new instance of the class. + /// + /// The size off the chain. + public Vp8LHashChain(int size) + { + this.OffsetLength = new uint[size]; + this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); + this.Size = size; + } + /// /// The 20 most significant bits contain the offset at which the best match is found. /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). - /// The lower 12 bits contain the length of the match. The 12 bit limit is - /// defined in MaxFindCopyLength with MAX_LENGTH=4096. + /// The lower 12 bits contain the length of the match. /// public uint[] OffsetLength { get; } @@ -21,13 +31,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public int Size { get; } - public Vp8LHashChain(int size) - { - this.OffsetLength = new uint[size]; - this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); - this.Size = size; - } - public int FindLength(int basePosition) { return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index fe47e4548..d18910574 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -6,41 +6,55 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { - internal class Vp8LHistogram + internal class Vp8LHistogram : IDeepCloneable { private const uint NonTrivialSym = 0xffffffff; /// /// Initializes a new instance of the class. /// - /// The backward references to initialize the histogram with. - /// The palette code bits. - public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) - : this() + public Vp8LHistogram() { - if (paletteCodeBits >= 0) - { - this.PaletteCodeBits = paletteCodeBits; - } + } - this.StoreRefs(refs); + /// + /// Initializes a new instance of the class. + /// + /// The histogram to create an instance from. + private Vp8LHistogram(Vp8LHistogram other) + : this(other.PaletteCodeBits) + { + other.Red.AsSpan().CopyTo(this.Red); + other.Blue.AsSpan().CopyTo(this.Blue); + other.Alpha.AsSpan().CopyTo(this.Alpha); + other.Literal.AsSpan().CopyTo(this.Literal); + other.IsUsed.AsSpan().CopyTo(this.IsUsed); + this.LiteralCost = other.LiteralCost; + this.RedCost = other.RedCost; + this.BlueCost = other.BlueCost; + this.BitCost = other.BitCost; + this.TrivialSymbol = other.TrivialSymbol; + this.PaletteCodeBits = other.PaletteCodeBits; } /// /// Initializes a new instance of the class. /// + /// The backward references to initialize the histogram with. /// The palette code bits. - public Vp8LHistogram(int paletteCodeBits) - : this() + public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) + : this(paletteCodeBits) { - this.PaletteCodeBits = paletteCodeBits; + this.StoreRefs(refs); } /// /// Initializes a new instance of the class. /// - public Vp8LHistogram() + /// The palette code bits. + public Vp8LHistogram(int paletteCodeBits) { + this.PaletteCodeBits = paletteCodeBits; this.Red = new uint[WebPConstants.NumLiteralCodes + 1]; this.Blue = new uint[WebPConstants.NumLiteralCodes + 1]; this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1]; @@ -53,10 +67,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.IsUsed = new bool[5]; } + /// + public IDeepCloneable DeepClone() => new Vp8LHistogram(this); + /// - /// Gets the palette code bits. + /// Gets or sets the palette code bits. /// - public int PaletteCodeBits { get; } + public int PaletteCodeBits { get; set; } /// /// Gets or sets the cached value of bit cost. @@ -110,7 +127,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// The token to add. /// Indicates whether to use the distance modifier. - public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier) + /// xSize is only used when useDistanceModifier is true. + public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0) { if (v.IsLiteral()) { @@ -135,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - // TODO: VP8LPrefixEncodeBits(distance_modifier(distance_modifier_arg0, PixOrCopyDistance(v)), &code, &extra_bits); + code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits); } this.Distance[code]++; @@ -170,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint notUsed = 0; double alphaCost = PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]); double distanceCost = PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes); - int numCodes = HistogramNumCodes(this.PaletteCodeBits); + int numCodes = this.NumCodes(); this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); this.RedCost = PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]); this.BlueCost = PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]); @@ -181,10 +199,295 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - this.TrivialSymbol = ((uint)alphaSym << 24) | (redSym << 16) | (blueSym << 0); + this.TrivialSymbol = (alphaSym << 24) | (redSym << 16) | (blueSym << 0); } } + /// + /// Performs output = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing + /// to the threshold value 'costThreshold'. The score returned is + /// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. + /// Since the previous score passed is 'costThreshold', we only need to compare + /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early. + /// + public double AddEval(Vp8LHistogram b, double costThreshold, Vp8LHistogram output) + { + double sumCost = this.BitCost + b.BitCost; + costThreshold += sumCost; + if (this.GetCombinedHistogramEntropy(b, costThreshold, costInitial: 0, out var cost)) + { + this.Add(b, output); + output.BitCost = cost; + output.PaletteCodeBits = this.PaletteCodeBits; + } + + return cost - sumCost; + } + + public double AddThresh(Vp8LHistogram b, double costThreshold) + { + double costInitial = -this.BitCost; + this.GetCombinedHistogramEntropy(b, costThreshold, costInitial, out var cost); + return cost; + } + + public void Add(Vp8LHistogram b, Vp8LHistogram output) + { + int literalSize = this.NumCodes(); + + this.AddLiteral(b, output, literalSize); + this.AddRed(b, output, WebPConstants.NumLiteralCodes); + this.AddBlue(b, output, WebPConstants.NumLiteralCodes); + this.AddAlpha(b, output, WebPConstants.NumLiteralCodes); + this.AddDistance(b, output, WebPConstants.NumDistanceCodes); + + for (int i = 0; i < 5; i++) + { + output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i]; + } + + output.TrivialSymbol = (this.TrivialSymbol == b.TrivialSymbol) + ? this.TrivialSymbol + : NonTrivialSym; + } + + public bool GetCombinedHistogramEntropy(Vp8LHistogram b, double costThreshold, double costInitial, out double cost) + { + bool trivialAtEnd = false; + cost = costInitial; + + cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false); + + cost += ExtraCostCombined(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes); + + if (cost > costThreshold) + { + return false; + } + + if (this.TrivialSymbol != NonTrivialSym && this.TrivialSymbol == b.TrivialSymbol) + { + // A, R and B are all 0 or 0xff. + uint colorA = (this.TrivialSymbol >> 24) & 0xff; + uint colorR = (this.TrivialSymbol >> 16) & 0xff; + uint colorB = (this.TrivialSymbol >> 0) & 0xff; + if ((colorA == 0 || colorA == 0xff) && + (colorR == 0 || colorR == 0xff) && + (colorB == 0 || colorB == 0xff)) + { + trivialAtEnd = true; + } + } + + cost += GetCombinedEntropy(this.Red, b.Red, WebPConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd); + if (cost > costThreshold) + { + return false; + } + + cost += GetCombinedEntropy(this.Blue, b.Blue, WebPConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd); + if (cost > costThreshold) + { + return false; + } + + cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebPConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd); + if (cost > costThreshold) + { + return false; + } + + cost += GetCombinedEntropy(this.Distance, b.Distance, WebPConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false); + if (cost > costThreshold) + { + return false; + } + + cost += ExtraCostCombined(this.Distance, b.Distance, WebPConstants.NumDistanceCodes); + if (cost > costThreshold) + { + return false; + } + + return true; + } + + private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) + { + if (this.IsUsed[0]) + { + if (b.IsUsed[0]) + { + AddVector(this.Literal, b.Literal, output.Literal, literalSize); + } + else + { + this.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + } + } + else if (b.IsUsed[0]) + { + b.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + } + else + { + output.Literal.AsSpan(0, literalSize).Fill(0); + } + } + + private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[1]) + { + if (b.IsUsed[1]) + { + AddVector(this.Red, b.Red, output.Red, size); + } + else + { + this.Red.AsSpan(0, size).CopyTo(output.Red); + } + } + else if (b.IsUsed[1]) + { + b.Red.AsSpan(0, size).CopyTo(output.Red); + } + else + { + output.Red.AsSpan(0, size).Fill(0); + } + } + + private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[2]) + { + if (b.IsUsed[2]) + { + AddVector(this.Blue, b.Blue, output.Blue, size); + } + else + { + this.Blue.AsSpan(0, size).CopyTo(output.Blue); + } + } + else if (b.IsUsed[2]) + { + b.Blue.AsSpan(0, size).CopyTo(output.Blue); + } + else + { + output.Blue.AsSpan(0, size).Fill(0); + } + } + + private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[3]) + { + if (b.IsUsed[3]) + { + AddVector(this.Alpha, b.Alpha, output.Alpha, size); + } + else + { + this.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + } + } + else if (b.IsUsed[3]) + { + b.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + } + else + { + output.Alpha.AsSpan(0, size).Fill(0); + } + } + + private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[4]) + { + if (b.IsUsed[4]) + { + AddVector(this.Distance, b.Distance, output.Distance, size); + } + else + { + this.Distance.AsSpan(0, size).CopyTo(output.Distance); + } + } + else if (b.IsUsed[4]) + { + b.Distance.AsSpan(0, size).CopyTo(output.Distance); + } + else + { + output.Distance.AsSpan(0, size).Fill(0); + } + } + + private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd) + { + var stats = new Vp8LStreaks(); + if (trivialAtEnd) + { + // This configuration is due to palettization that transforms an indexed + // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. + // BitsEntropyRefine is 0 for histograms with only one non-zero value. + // Only FinalHuffmanCost needs to be evaluated. + + // Deal with the non-zero value at index 0 or length-1. + stats.Streaks[1][0] = 1; + + // Deal with the following/previous zero streak. + stats.Counts[0] = 1; + stats.Streaks[0][1] = length - 1; + + return stats.FinalHuffmanCost(); + } + + var bitEntropy = new Vp8LBitEntropy(); + if (isXUsed) + { + if (isYUsed) + { + bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); + } + else + { + bitEntropy.GetEntropyUnrefined(x, length, stats); + } + } + else + { + if (isYUsed) + { + bitEntropy.GetEntropyUnrefined(y, length, stats); + } + else + { + stats.Counts[0] = 1; + stats.Streaks[0][length > 3 ? 1 : 0] = length; + bitEntropy.Init(); + } + } + + return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); + } + + private static double ExtraCostCombined(Span x, Span y, int length) + { + double cost = 0.0d; + for (int i = 2; i < length - 2; i++) + { + int xy = (int)(x[i + 2] + y[i + 2]); + cost += (i >> 1) * xy; + } + + return cost; + } + /// /// Get the symbol entropy for the distribution 'population'. /// @@ -194,13 +497,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var stats = new Vp8LStreaks(); bitEntropy.BitsEntropyUnrefined(population, length, stats); + trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; + // The histogram is used if there is at least one non-zero streak. isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); } - private static double ExtraCost(Span population, int length) + private static double ExtraCost(Span population, int length) { double cost = 0.0d; for (int i = 2; i < length - 2; ++i) @@ -211,9 +516,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return cost; } - public static int HistogramNumCodes(int paletteCodeBits) + private static void AddVector(uint[] a, uint[] b, uint[] output, int size) { - return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((paletteCodeBits > 0) ? (1 << paletteCodeBits) : 0); + for (int i = 0; i < size; i++) + { + output[i] = a[i] + b[i]; + } } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index 728e4e893..bec2cc099 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -5,6 +5,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { internal class Vp8LStreaks { + /// + /// Initializes a new instance of the class. + /// public Vp8LStreaks() { this.Counts = new int[2]; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index ee47e33d8..d7fc395b1 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; @@ -211,11 +211,15 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition) { - int lz77sTypesToTrySize = 1; // TODO: harcoded for now. + int lz77sTypesToTrySize = 1; // TODO: hardcoded for now. int[] lz77sTypesToTry = { 3 }; int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); - short[] histogramSymbols = new short[histogramImageXySize]; + var histogramSymbols = new short[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } if (useCache) { @@ -256,6 +260,10 @@ namespace SixLabors.ImageSharp.Formats.WebP var histogramImageSize = histogramImage.Count; var bitArraySize = 5 * histogramImageSize; var huffmanCodes = new HuffmanTreeCode[bitArraySize]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = new HuffmanTreeCode(); + } GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); @@ -306,6 +314,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } var tokens = new HuffmanTreeToken[maxTokens]; + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); + } + for (int i = 0; i < 5 * histogramImageSize; i++) { HuffmanTreeCode codes = huffmanCodes[i]; @@ -347,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Applies the substract green transformation to the pixel data of the image. /// - /// The VP8 Encoder. + /// The VP8L Encoder. /// The width of the image. /// The height of the image. private void ApplySubtractGreen(Vp8LEncoder enc, int width, int height) @@ -517,32 +530,29 @@ namespace SixLabors.ImageSharp.Formats.WebP private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) { - int numTokens; int i; - byte[] codeLengthBitdepth = new byte[WebPConstants.CodeLengthCodes]; - short[] codeLengthBitdepthSymbols = new short[WebPConstants.CodeLengthCodes]; + var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes]; + var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes]; var huffmanCode = new HuffmanTreeCode(); huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; - huffmanCode.CodeLengths = codeLengthBitdepth; - huffmanCode.Codes = codeLengthBitdepthSymbols; + huffmanCode.CodeLengths = codeLengthBitDepth; + huffmanCode.Codes = codeLengthBitDepthSymbols; this.bitWriter.PutBits(0, 1); - numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - uint[] histogram = new uint[WebPConstants.CodeLengthCodes + 1]; - bool[] bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; + var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + var histogram = new uint[WebPConstants.CodeLengthCodes + 1]; + var bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; for (i = 0; i < numTokens; i++) { histogram[tokens[i].Code]++; } HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); - this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitdepth); + this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); int trailingZeroBits = 0; int trimmedLength = numTokens; - bool writeTrimmedLength; - int length; i = numTokens; while (i-- > 0) { @@ -550,7 +560,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (ix == 0 || ix == 17 || ix == 18) { trimmedLength--; // discount trailing zeros. - trailingZeroBits += codeLengthBitdepth[ix]; + trailingZeroBits += codeLengthBitDepth[ix]; if (ix == 17) { trailingZeroBits += 3; @@ -566,8 +576,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; - length = writeTrimmedLength ? trimmedLength : numTokens; + var writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + var length = writeTrimmedLength ? trimmedLength : numTokens; this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); if (writeTrimmedLength) { From 7d34ceacc3c33fe7eb3c69b91d5dba9708f2ce1a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 25 Jul 2020 07:49:06 +0200 Subject: [PATCH 178/359] Split webp encoder into lossless and lossy --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 4 +- .../WebP/Lossless/DominantCostRange.cs | 4 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 4 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 1363 ++++++++++++++++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 46 + src/ImageSharp/Formats/WebP/WebPEncoder.cs | 15 +- .../Formats/WebP/WebPEncoderCore.cs | 1361 +--------------- 7 files changed, 1455 insertions(+), 1342 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index ab7d03c7a..9ff457648 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; diff --git a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs index f81cc2c9f..7ce42b5bb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/DominantCostRange.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 6e1b6ab70..064152c02 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 32cc7d771..aac3bd52e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -1,9 +1,14 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -27,6 +32,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private MemoryAllocator memoryAllocator; + /// + /// A bit writer for writing lossless webp streams. + /// + private Vp8LBitWriter bitWriter; + + private const int ApplyPaletteGreedyMax = 4; + + private const int PaletteInvSizeBits = 11; + + private const int PaletteInvSize = 1 << PaletteInvSizeBits; + /// /// Initializes a new instance of the class. /// @@ -36,7 +52,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height) { var pixelCount = width * height; + int initialSize = pixelCount * 2; + this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; @@ -47,8 +65,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; for (int i = 0; i < this.Refs.Length; ++i) { - this.Refs[i] = new Vp8LBackwardRefs(); - this.Refs[i].BlockSize = (refsBlockSize < MinBlockSize) ? MinBlockSize : refsBlockSize; + this.Refs[i] = new Vp8LBackwardRefs + { + BlockSize = (refsBlockSize < MinBlockSize) ? MinBlockSize : refsBlockSize + }; } } @@ -58,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public IMemoryOwner Bgra { get; } /// - /// Gets the scratch memory for bgra rows used for prediction. + /// Gets or sets the scratch memory for bgra rows used for prediction. /// public IMemoryOwner BgraScratch { get; set; } @@ -127,6 +147,1339 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ///
public Vp8LHashChain HashChain { get; } + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + // Write the image size. + int width = image.Width; + int height = image.Height; + this.WriteImageSize(width, height); + + // Write the non-trivial Alpha flag and lossless version. + bool hasAlpha = false; // TODO: for the start, this will be always false. + this.WriteAlphaAndVersion(hasAlpha); + + // Encode the main image stream. + this.EncodeStream(image); + + // TODO: write bytes from the bitwriter to the stream. + } + + /// + /// Writes the image size to the stream. + /// + /// The input image width. + /// The input image height. + private void WriteImageSize(int inputImgWidth, int inputImgHeight) + { + Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); + Guard.MustBeLessThan(inputImgHeight, WebPConstants.MaxDimension, nameof(inputImgHeight)); + + uint width = (uint)inputImgWidth - 1; + uint height = (uint)inputImgHeight - 1; + + this.bitWriter.PutBits(width, WebPConstants.Vp8LImageSizeBits); + this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); + } + + private void WriteAlphaAndVersion(bool hasAlpha) + { + this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); + this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); + } + + /// + /// Encodes the image stream using lossless webp format. + /// + /// The pixel type. + /// The image to encode. + private void EncodeStream(Image image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + int bytePosition = this.bitWriter.NumBytes(); + + // Convert image pixels to bgra array. + Span bgra = this.Bgra.GetSpan(); + int idx = 0; + for (int y = 0; y < height; y++) + { + Span rowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + } + } + + // Analyze image (entropy, numPalettes etc). + this.EncoderAnalyze(image); + + var entropyIdx = 3; // TODO: hardcoded for now. + int quality = 75; // TODO: quality is hardcoded for now. + bool useCache = true; // TODO: useCache is hardcoded for now. + bool redAndBlueAlwaysZero = false; + + this.UsePalette = entropyIdx == (int)EntropyIx.Palette; + this.UseSubtractGreenTransform = (entropyIdx == (int)EntropyIx.SubGreen) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); + this.UsePredictorTransform = (entropyIdx == (int)EntropyIx.Spatial) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); + this.UseCrossColorTransform = redAndBlueAlwaysZero ? false : this.UsePredictorTransform; + this.AllocateTransformBuffer(width, height); + + // Reset any parameter in the encoder that is set in the previous iteration. + this.CacheBits = 0; + this.ClearRefs(); + + // TODO: Apply near-lossless preprocessing. + + // Encode palette. + if (this.UsePalette) + { + this.EncodePalette(); + this.MapImageFromPalette(width, height); + + // If using a color cache, do not have it bigger than the number of colors. + if (useCache && this.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) + { + this.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; + } + } + + // Apply transforms and write transform data. + if (this.UseSubtractGreenTransform) + { + this.ApplySubtractGreen(this.CurrentWidth, height); + } + + if (this.UsePredictorTransform) + { + this.ApplyPredictFilter(this.CurrentWidth, height, quality, this.UseSubtractGreenTransform); + } + + if (this.UseCrossColorTransform) + { + this.ApplyCrossColorFilter(this.CurrentWidth, height, quality); + } + + this.bitWriter.PutBits(0, 1); // No more transforms. + + // Encode and write the transformed image. + this.EncodeImage(bgra, this.HashChain, this.Refs, this.CurrentWidth, height, quality, useCache, this.CacheBits, this.HistoBits, bytePosition); + } + + /// + /// Analyzes the image and decides what transforms should be used. + /// + private void EncoderAnalyze(Image image) + where TPixel : unmanaged, IPixel + { + int method = 4; // TODO: method hardcoded to 4 for now. + int width = image.Width; + int height = image.Height; + + // Check if we only deal with a small number of colors and should use a palette. + var usePalette = this.AnalyzeAndCreatePalette(image); + + // Empirical bit sizes. + this.HistoBits = GetHistoBits(method, usePalette, width, height); + this.TransformBits = GetTransformBits(method, this.HistoBits); + + // Try out multiple LZ77 on images with few colors. + var nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; + EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out bool redAndBlueAlwaysZero); + + // TODO: Fill CrunchConfig + } + + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition) + { + int lz77sTypesToTrySize = 1; // TODO: hardcoded for now. + int[] lz77sTypesToTry = { 3 }; + int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); + var histogramSymbols = new short[histogramImageXySize]; + var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } + + if (useCache) + { + if (cacheBits == 0) + { + cacheBits = WebPConstants.MaxColorCacheBits; + } + } + else + { + cacheBits = 0; + } + + // Calculate backward references from ARGB image. + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + + // TODO: BitWriterInit(&bw_best, 0) + // BitWriterClone(bw, &bw_best)) + + for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) + { + Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, lz77sTypesToTry[lz77sIdx], ref cacheBits, hashChain, refsArray[0], refsArray[1]); + + // Keep the best references aside and use the other element from the first + // two as a temporary for later usage. + Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; + + var tmpHisto = new Vp8LHistogram(cacheBits); + var histogramImage = new List(histogramImageXySize); + for (int i = 0; i < histogramImageXySize; i++) + { + histogramImage.Add(new Vp8LHistogram(cacheBits)); + } + + // Build histogram image and symbols from backward references. + HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + + // Create Huffman bit lengths and codes for each histogram image. + var histogramImageSize = histogramImage.Count; + var bitArraySize = 5 * histogramImageSize; + var huffmanCodes = new HuffmanTreeCode[bitArraySize]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = new HuffmanTreeCode(); + } + + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); + + // Color Cache parameters. + if (cacheBits > 0) + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)cacheBits, 4); + } + else + { + this.bitWriter.PutBits(0, 1); + } + + // Huffman image + meta huffman. + bool writeHistogramImage = histogramImageSize > 1; + this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); + if (writeHistogramImage) + { + using IMemoryOwner histogramArgbBuffer = this.memoryAllocator.Allocate(histogramImageXySize); + Span histogramArgb = histogramArgbBuffer.GetSpan(); + int maxIndex = 0; + for (int i = 0; i < histogramImageXySize; i++) + { + int symbolIndex = histogramSymbols[i] & 0xffff; + histogramArgb[i] = (uint)(symbolIndex << 8); + if (symbolIndex >= maxIndex) + { + maxIndex = symbolIndex + 1; + } + } + + histogramImageSize = maxIndex; + this.bitWriter.PutBits((uint)(histogramBits - 2), 3); + this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); + } + + // Store Huffman codes. + // Find maximum number of symbols for the huffman tree-set. + int maxTokens = 0; + for (int i = 0; i < 5 * histogramImageSize; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) + { + maxTokens = codes.NumSymbols; + } + } + + var tokens = new HuffmanTreeToken[maxTokens]; + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); + } + + for (int i = 0; i < 5 * histogramImageSize; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + var hdrSizeTmp = (int)(this.bitWriter.NumBytes() - initBytePosition); + this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); + + // TODO: Keep track of the smallest image so far. + } + } + + /// + /// Save the palette to the bitstream. + /// + private void EncodePalette() + { + Span tmpPalette = new uint[WebPConstants.MaxPaletteSize]; + int paletteSize = this.PaletteSize; + Span palette = this.Palette.Memory.Span; + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); + this.bitWriter.PutBits((uint)paletteSize - 1, 8); + for (int i = paletteSize - 1; i >= 1; i--) + { + tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); + } + + tmpPalette[0] = palette[0]; + this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20); + } + + /// + /// Applies the subtract green transformation to the pixel data of the image. + /// + /// The width of the image. + /// The height of the image. + private void ApplySubtractGreen(int width, int height) + { + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); + LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan(), width * height); + } + + private void ApplyPredictFilter(int width, int height, int quality, bool usedSubtractGreen) + { + int nearLosslessStrength = 100; // TODO: for now always 100 + bool exact = false; // TODO: always false for now. + int predBits = this.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, predBits); + int transformHeight = LosslessUtils.SubSampleSize(height, predBits); + + PredictorEncoder.ResidualImage(width, height, predBits, this.Bgra.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); + + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); + this.bitWriter.PutBits((uint)(predBits - 2), 3); + + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, quality); + } + + private void ApplyCrossColorFilter(int width, int height, int quality) + { + int colorTransformBits = this.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); + + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); + + this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); + this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); + + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, quality); + } + + private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) + { + int cacheBits = 0; + var histogramSymbols = new short[1]; // Only one tree, one symbol. + var huffmanCodes = new HuffmanTreeCode[5]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = new HuffmanTreeCode(); + } + + var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } + + // Calculate backward references from the image pixels. + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + + Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( + width, + height, + bgra, + quality, + (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, + ref cacheBits, + hashChain, + refsTmp1, + refsTmp2); + + var histogramImage = new List() + { + new Vp8LHistogram(cacheBits) + }; + + // Build histogram image and symbols from backward references. + histogramImage[0].StoreRefs(refs); + + // Create Huffman bit lengths and codes for each histogram image. + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); + + // No color cache, no Huffman image. + this.bitWriter.PutBits(0, 1); + + // Find maximum number of symbols for the huffman tree-set. + int maxTokens = 0; + for (int i = 0; i < 5; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) + { + maxTokens = codes.NumSymbols; + } + } + + var tokens = new HuffmanTreeToken[maxTokens]; + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); + } + + // Store Huffman codes. + for (int i = 0; i < 5; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); + } + + private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + { + int count = 0; + int[] symbols = { 0, 0 }; + int maxBits = 8; + int maxSymbol = 1 << maxBits; + + // Check whether it's a small tree. + for (int i = 0; i < huffmanCode.NumSymbols && count < 3; ++i) + { + if (huffmanCode.CodeLengths[i] != 0) + { + if (count < 2) + { + symbols[count] = i; + } + + count++; + } + } + + if (count == 0) + { + // emit minimal tree for empty cases + // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 + this.bitWriter.PutBits(0x01, 4); + } + else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) + { + this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. + this.bitWriter.PutBits((uint)(count - 1), 1); + if (symbols[0] <= 1) + { + this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. + this.bitWriter.PutBits((uint)symbols[0], 1); + } + else + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)symbols[0], 8); + } + + if (count == 2) + { + this.bitWriter.PutBits((uint)symbols[1], 8); + } + } + else + { + this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); + } + } + + private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + { + int i; + var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes]; + var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes]; + var huffmanCode = new HuffmanTreeCode(); + huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; + huffmanCode.CodeLengths = codeLengthBitDepth; + huffmanCode.Codes = codeLengthBitDepthSymbols; + + this.bitWriter.PutBits(0, 1); + var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + var histogram = new uint[WebPConstants.CodeLengthCodes + 1]; + var bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; + for (i = 0; i < numTokens; i++) + { + histogram[tokens[i].Code]++; + } + + HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); + this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); + ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); + + int trailingZeroBits = 0; + int trimmedLength = numTokens; + i = numTokens; + while (i-- > 0) + { + int ix = tokens[i].Code; + if (ix == 0 || ix == 17 || ix == 18) + { + trimmedLength--; // discount trailing zeros. + trailingZeroBits += codeLengthBitDepth[ix]; + if (ix == 17) + { + trailingZeroBits += 3; + } + else if (ix == 18) + { + trailingZeroBits += 7; + } + } + else + { + break; + } + } + + var writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + var length = writeTrimmedLength ? trimmedLength : numTokens; + this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); + if (writeTrimmedLength) + { + if (trimmedLength == 2) + { + this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmed_length=2 + } + else + { + int nbits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nbitpairs = (nbits / 2) + 1; + this.bitWriter.PutBits((uint)nbitpairs - 1, 3); + this.bitWriter.PutBits((uint)trimmedLength - 2, nbitpairs * 2); + } + } + + this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); + } + + private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) + { + for (int i = 0; i < numTokens; i++) + { + int ix = tokens[i].Code; + int extraBits = tokens[i].ExtraBits; + this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); + switch (ix) + { + case 16: + this.bitWriter.PutBits((uint)extraBits, 2); + break; + case 17: + this.bitWriter.PutBits((uint)extraBits, 3); + break; + case 18: + this.bitWriter.PutBits((uint)extraBits, 7); + break; + } + } + } + + private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitdepth) + { + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // Throw away trailing zeros: + int codesToStore = WebPConstants.CodeLengthCodes; + for (; codesToStore > 4; codesToStore--) + { + if (codeLengthBitdepth[storageOrder[codesToStore - 1]] != 0) + { + break; + } + } + + this.bitWriter.PutBits((uint)codesToStore - 4, 4); + for (int i = 0; i < codesToStore; i++) + { + this.bitWriter.PutBits(codeLengthBitdepth[storageOrder[i]], 3); + } + } + + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + { + int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; + int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); + + // x and y trace the position in the image. + int x = 0; + int y = 0; + int tileX = x & tileMask; + int tileY = y & tileMask; + int histogramIx = histogramSymbols[0]; + Span codes = huffmanCodes.AsSpan(5 * histogramIx); + using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + PixOrCopy v = c.Current; + if ((tileX != (x & tileMask)) || (tileY != (y & tileMask))) + { + tileX = x & tileMask; + tileY = y & tileMask; + histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; + codes = huffmanCodes.AsSpan(5 * histogramIx); + } + + if (v.IsLiteral()) + { + byte[] order = { 1, 2, 0, 3 }; + for (int k = 0; k < 4; k++) + { + int code = (int)v.Literal(order[k]); + this.bitWriter.WriteHuffmanCode(codes[k], code); + } + } + else if (v.IsCacheIdx()) + { + int code = (int)v.CacheIdx(); + int literalIx = 256 + WebPConstants.NumLengthCodes + code; + this.bitWriter.WriteHuffmanCode(codes[0], literalIx); + } + else + { + int bits = 0; + int nBits = 0; + int distance = (int)v.Distance(); + int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); + + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCode(codes[4], code); + this.bitWriter.PutBits((uint)bits, nBits); + } + + x += v.Length(); + while (x >= width) + { + x -= width; + y++; + } + } + } + + /// + /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. + /// + /// The pixel type of the image. + /// The image to analyze. + /// Indicates whether a palette should be used. + /// The palette size. + /// The transformation bits. + /// Indicates if red and blue are always zero. + /// The entropy mode to use. + private EntropyIx AnalyzeEntropy(Image image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + + if (usePalette && paletteSize <= 16) + { + // In the case of small palettes, we pack 2, 4 or 8 pixels together. In + // practice, small palettes are better than any other transform. + redAndBlueAlwaysZero = true; + return EntropyIx.Palette; + } + + using System.Buffers.IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + Span histo = histoBuffer.Memory.Span; + Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. + Span prevRow = null; + for (int y = 0; y < height; y++) + { + Span currentRow = image.GetPixelRowSpan(y); + for (int x = 0; x < width; x++) + { + Bgra32 pix = ToBgra32(currentRow[x]); + uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); + pixPrev = pix; + if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) + { + continue; + } + + AddSingle( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoAlpha * 256), + histo.Slice((int)HistoIx.HistoRed * 256), + histo.Slice((int)HistoIx.HistoGreen * 256), + histo.Slice((int)HistoIx.HistoBlue * 256)); + AddSingle( + pixDiff, + histo.Slice((int)HistoIx.HistoAlphaPred * 256), + histo.Slice((int)HistoIx.HistoRedPred * 256), + histo.Slice((int)HistoIx.HistoGreenPred * 256), + histo.Slice((int)HistoIx.HistoBluePred * 256)); + AddSingleSubGreen( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoRedSubGreen * 256), + histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + AddSingleSubGreen( + pixDiff, + histo.Slice((int)HistoIx.HistoRedPredSubGreen * 256), + histo.Slice((int)HistoIx.HistoBluePredSubGreen * 256)); + + // Approximate the palette by the entropy of the multiplicative hash. + uint hash = HashPix(pix.PackedValue); + histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; + } + + var histo0 = histo[0]; + prevRow = currentRow; + } + + var entropyComp = new double[(int)HistoIx.HistoTotal]; + var entropy = new double[(int)EntropyIx.NumEntropyIx]; + int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; + + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pixDiff == 0 comparison, at least one of the + // zeros is likely to exist. + histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; + histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; + histo[(int)HistoIx.HistoRedPred * 256]++; + histo[(int)HistoIx.HistoGreenPred * 256]++; + histo[(int)HistoIx.HistoBluePred * 256]++; + histo[(int)HistoIx.HistoAlphaPred * 256]++; + + for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) + { + var bitEntropy = new Vp8LBitEntropy(); + Span curHisto = histo.Slice(j * 256, 256); + bitEntropy.BitsEntropyUnrefined(curHisto, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(); + } + + entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRed] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlue]; + entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPred] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePred]; + entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRedSubGreen] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlueSubGreen]; + entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPredSubGreen] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePredSubGreen]; + entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; + + // When including transforms, there is an overhead in bits from + // storing them. This overhead is small but matters for small images. + // For spatial, there are 14 transformations. + entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(14); + + // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. + entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(24); + + // For palettes, add the cost of storing the palette. + // We empirically estimate the cost of a compressed entry as 8 bits. + // The palette is differential-coded when compressed hence a much + // lower cost than sizeof(uint32_t)*8. + entropy[(int)EntropyIx.Palette] += paletteSize * 8; + + EntropyIx minEntropyIx = EntropyIx.Direct; + for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) + { + if (entropy[(int)minEntropyIx] > entropy[k]) + { + minEntropyIx = (EntropyIx)k; + } + } + + redAndBlueAlwaysZero = true; + + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + var histoPairs = new byte[][] + { + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, + new byte[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, + new byte[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, + new byte[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } + }; + Span redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]); + Span blueHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][1]); + for (int i = 1; i < 256; i++) + { + if ((redHisto[i] | blueHisto[i]) != 0) + { + redAndBlueAlwaysZero = false; + break; + } + } + + return minEntropyIx; + } + + /// + /// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, + /// creates a palette and returns true, else returns false. + /// + /// true, if a palette should be used. + private bool AnalyzeAndCreatePalette(Image image) + where TPixel : unmanaged, IPixel + { + Span palette = this.Palette.Memory.Span; + this.PaletteSize = this.GetColorPalette(image, palette); + if (this.PaletteSize > WebPConstants.MaxPaletteSize) + { + this.PaletteSize = 0; + return false; + } + + uint[] paletteArray = palette.Slice(0, this.PaletteSize).ToArray(); + Array.Sort(paletteArray); + paletteArray.CopyTo(palette); + + if (PaletteHasNonMonotonousDeltas(palette, this.PaletteSize)) + { + GreedyMinimizeDeltas(palette, this.PaletteSize); + } + + return true; + } + + /// + /// Gets the color palette. + /// + /// The pixel type of the image. + /// The image to get the palette from. + /// The span to store the palette into. + /// The number of palette entries. + private int GetColorPalette(Image image, Span palette) + where TPixel : unmanaged, IPixel + { + var colors = new HashSet(); + for (int y = 0; y < image.Height; y++) + { + Span rowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + colors.Add(rowSpan[x]); + if (colors.Count > WebPConstants.MaxPaletteSize) + { + // Exact count is not needed, because a palette will not be used then anyway. + return WebPConstants.MaxPaletteSize + 1; + } + } + } + + // Fill the colors into the palette. + using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); + int idx = 0; + while (colorEnumerator.MoveNext()) + { + Bgra32 bgra = ToBgra32(colorEnumerator.Current); + palette[idx++] = bgra.PackedValue; + } + + return colors.Count; + } + + private void MapImageFromPalette(int width, int height) + { + Span src = this.Bgra.GetSpan(); + int srcStride = this.CurrentWidth; + Span dst = this.Bgra.GetSpan(); // Applying the palette will be done in place. + Span palette = this.Palette.GetSpan(); + int paletteSize = this.PaletteSize; + int xBits; + + // Replace each input pixel by corresponding palette index. + // This is done line by line. + if (paletteSize <= 4) + { + xBits = (paletteSize <= 2) ? 3 : 2; + } + else + { + xBits = (paletteSize <= 16) ? 1 : 0; + } + + this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits); + } + + /// + /// Remap argb values in src[] to packed palettes entries in dst[] + /// using 'row' as a temporary buffer of size 'width'. + /// We assume that all src[] values have a corresponding entry in the palette. + /// Note: src[] can be the same as dst[] + /// + private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) + { + using System.Buffers.IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); + Span tmpRow = tmpRowBuffer.GetSpan(); + + if (paletteSize < ApplyPaletteGreedyMax) + { + // TODO: APPLY_PALETTE_FOR(SearchColorGreedy(palette, palette_size, pix)); + } + else + { + uint[] buffer = new uint[PaletteInvSize]; + + // Try to find a perfect hash function able to go from a color to an index + // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. + int i; + for (i = 0; i < 3; i++) + { + bool useLUT = true; + + // Set each element in buffer to max value. + buffer.AsSpan().Fill(uint.MaxValue); + + for (int j = 0; j < paletteSize; j++) + { + uint ind = 0; + switch (i) + { + case 0: + ind = ApplyPaletteHash0(palette[j]); + break; + case 1: + ind = ApplyPaletteHash1(palette[j]); + break; + case 2: + ind = ApplyPaletteHash2(palette[j]); + break; + } + + if (buffer[ind] != uint.MaxValue) + { + useLUT = false; + break; + } + else + { + buffer[ind] = (uint)j; + } + } + + if (useLUT) + { + break; + } + } + + if (i == 0 || i == 1 || i == 2) + { + ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); + } + else + { + uint[] idxMap = new uint[paletteSize]; + uint[] paletteSorted = new uint[paletteSize]; + PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); + ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); + } + } + } + + private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint pix = src[x]; + if (pix != prevPix) + { + switch (hashIdx) + { + case 0: + prevIdx = buffer[ApplyPaletteHash0(pix)]; + break; + case 1: + prevIdx = buffer[ApplyPaletteHash1(pix)]; + break; + case 2: + prevIdx = buffer[ApplyPaletteHash2(pix)]; + break; + } + + prevPix = pix; + } + + tmpRow[x] = (byte)prevIdx; + } + + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src.Slice((int)srcStride); + dst = dst.Slice((int)dstStride); + } + } + + private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint pix = src[x]; + if (pix != prevPix) + { + prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; + prevPix = pix; + } + + tmpRow[x] = (byte)prevIdx; + } + + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src.Slice((int)srcStride); + dst = dst.Slice((int)dstStride); + } + } + + /// + /// Sort palette in increasing order and prepare an inverse mapping array. + /// + private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) + { + palette.Slice(numColors).CopyTo(sorted); + Array.Sort(sorted, PaletteCompareColorsForSort); + for (int i = 0; i < numColors; i++) + { + idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; + } + } + + private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) + { + int low = 0; + if (sorted[low] == color) + { + return low; // loop invariant: sorted[low] != color + } + + while (true) + { + int mid = (low + hi) >> 1; + if (sorted[mid] == color) + { + return mid; + } + else if (sorted[mid] < color) + { + low = mid; + } + else + { + hi = mid; + } + } + } + + private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) + { + int count = 0; + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + if (huffmanCode.CodeLengths[k] != 0) + { + count++; + if (count > 1) + { + return; + } + } + } + + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + huffmanCode.CodeLengths[k] = 0; + huffmanCode.Codes[k] = 0; + } + } + + /// + /// The palette has been sorted by alpha. This function checks if the other components of the palette + /// have a monotonic development with regards to position in the palette. + /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development + /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. + /// + /// The palette. + /// Number of colors in the palette. + /// True, if the palette has no monotonous deltas. + private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) + { + uint predict = 0x000000; + byte signFound = 0x00; + for (int i = 0; i < numColors; ++i) + { + uint diff = LosslessUtils.SubPixels(palette[i], predict); + byte rd = (byte)((diff >> 16) & 0xff); + byte gd = (byte)((diff >> 8) & 0xff); + byte bd = (byte)((diff >> 0) & 0xff); + if (rd != 0x00) + { + signFound |= (byte)((rd < 0x80) ? 1 : 2); + } + + if (gd != 0x00) + { + signFound |= (byte)((gd < 0x80) ? 8 : 16); + } + + if (bd != 0x00) + { + signFound |= (byte)((bd < 0x80) ? 64 : 128); + } + } + + return (signFound & (signFound << 1)) != 0; // two consequent signs. + } + + /// + /// Find greedily always the closest color of the predicted color to minimize + /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. + /// + /// The palette. + /// The number of colors in the palette. + private static void GreedyMinimizeDeltas(Span palette, int numColors) + { + uint predict = 0x00000000; + for (int i = 0; i < numColors; ++i) + { + int bestIdx = i; + uint bestScore = ~0U; + for (int k = i; k < numColors; ++k) + { + uint curScore = PaletteColorDistance(palette[k], predict); + if (bestScore > curScore) + { + bestScore = curScore; + bestIdx = k; + } + } + + // Swap color(palette[bestIdx], palette[i]); + uint best = palette[bestIdx]; + palette[bestIdx] = palette[i]; + palette[i] = best; + predict = palette[i]; + } + } + + private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) + { + long totalLengthSize = 0; + int maxNumSymbols = 0; + + // Iterate over all histograms and get the aggregate number of codes used. + for (int i = 0; i < histogramImage.Count; i++) + { + Vp8LHistogram histo = histogramImage[i]; + int startIdx = 5 * i; + for (int k = 0; k < 5; k++) + { + int numSymbols = + (k == 0) ? histo.NumCodes() : + (k == 4) ? WebPConstants.NumDistanceCodes : 256; + huffmanCodes[startIdx + k].NumSymbols = numSymbols; + totalLengthSize += numSymbols; + } + } + + var end = 5 * histogramImage.Count; + for (int i = 0; i < end; i++) + { + int bitLength = huffmanCodes[i].NumSymbols; + huffmanCodes[i].Codes = new short[bitLength]; + huffmanCodes[i].CodeLengths = new byte[bitLength]; + if (maxNumSymbols < bitLength) + { + maxNumSymbols = bitLength; + } + } + + // Create Huffman trees. + bool[] bufRle = new bool[maxNumSymbols]; + var huffTree = new HuffmanTree[3 * maxNumSymbols]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } + + for (int i = 0; i < histogramImage.Count; i++) + { + int codesStartIdx = 5 * i; + Vp8LHistogram histo = histogramImage[i]; + HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); + HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); + HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); + HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); + HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); + } + } + + /// + /// Computes a value that is related to the entropy created by the palette entry diff. + /// + /// First color. + /// Second color. + /// The color distance. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint PaletteColorDistance(uint col1, uint col2) + { + uint diff = LosslessUtils.SubPixels(col1, col2); + uint moreWeightForRGBThanForAlpha = 9; + uint score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= moreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + + return score; + } + + /// + /// Calculates the huffman image bits. + /// + private static int GetHistoBits(int method, bool usePalette, int width, int height) + { + // Make tile size a function of encoding method (Range: 0 to 6). + int histoBits = (usePalette ? 9 : 7) - method; + while (true) + { + int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); + if (huffImageSize <= WebPConstants.MaxHuffImageSize) + { + break; + } + + histoBits++; + } + + return (histoBits < WebPConstants.MinHuffmanBits) ? WebPConstants.MinHuffmanBits : + (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; + } + + /// + /// Calculates the bits used for the transformation. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetTransformBits(int method, int histoBits) + { + int maxTransformBits = (method < 4) ? 6 : (method > 4) ? 4 : 5; + int res = (histoBits > maxTransformBits) ? maxTransformBits : histoBits; + return res; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Bgra32 ToBgra32(TPixel color) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); + return bgra; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingle(uint p, Span a, Span r, Span g, Span b) + { + a[(int)(p >> 24) & 0xff]++; + r[(int)(p >> 16) & 0xff]++; + g[(int)(p >> 8) & 0xff]++; + b[(int)(p >> 0) & 0xff]++; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingleSubGreen(uint p, Span r, Span b) + { + int green = (int)p >> 8; // The upper bits are masked away later. + r[(int)((p >> 16) - green) & 0xff]++; + b[(int)((p >> 0) - green) & 0xff]++; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash0(uint color) + { + // Focus on the green color. + return (color >> 8) & 0xff; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash1(uint color) + { + // Forget about alpha. + return ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash2(uint color) + { + // Forget about alpha. + return ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint HashPix(uint pix) + { + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. + return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int PaletteCompareColorsForSort(uint p1, uint p2) + { + return (p1 < p2) ? -1 : 1; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint PaletteComponentDistance(uint v) + { + return (v <= 128) ? v : (256 - v); + } + public void AllocateTransformBuffer(int width, int height) { int imageSize = width * height; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs new file mode 100644 index 000000000..546de6624 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.WebP.BitWriter; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Encoder for lossy webp images. + /// + internal class Vp8Encoder + { + /// + /// The to use for buffer allocations. + /// + private MemoryAllocator memoryAllocator; + + /// + /// A bit writer for writing lossy webp streams. + /// + private Vp8BitWriter bitWriter; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The width of the input image. + /// The height of the input image. + public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height) + { + this.memoryAllocator = memoryAllocator; + + // TODO: initialize bitwriter + } + + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 3e03724f3..0100c648d 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -1,14 +1,15 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Image encoder for writing an image to a stream in the WebP format. + /// Image encoder for writing an image to a stream in the WebP format. /// public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions { @@ -34,5 +35,13 @@ namespace SixLabors.ImageSharp.Formats.WebP var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index d7fc395b1..2d37b802a 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -1,14 +1,11 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.Lossless; +using SixLabors.ImageSharp.Formats.WebP.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -31,15 +28,19 @@ namespace SixLabors.ImageSharp.Formats.WebP private Configuration configuration; /// - /// A bit writer for writing lossless webp streams. + /// Indicating whether the alpha plane should be compressed with WebP lossless format. /// - private Vp8LBitWriter bitWriter; - - private const int ApplyPaletteGreedyMax = 4; + private bool alphaCompression; - private const int PaletteInvSizeBits = 11; + /// + /// Indicating whether lossless compression should be used. If false, lossy compression will be used. + /// + private bool lossless; - private const int PaletteInvSize = 1 << PaletteInvSizeBits; + /// + /// Compression quality. Between 0 and 100. + /// + private float quality; /// /// Initializes a new instance of the class. @@ -49,6 +50,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public WebPEncoderCore(IWebPEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; + this.alphaCompression = options.AlphaCompression; + this.lossless = options.Lossless; + this.quality = options.Quality; } /// @@ -66,1339 +70,40 @@ namespace SixLabors.ImageSharp.Formats.WebP this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; - int width = image.Width; - int height = image.Height; - int initialSize = width * height * 2; - this.bitWriter = new Vp8LBitWriter(initialSize); - - // Write image size. - this.WriteImageSize(width, height); - - // Write the non-trivial Alpha flag and lossless version. - bool hasAlpha = false; // TODO: for the start, this will be always false. - this.WriteAlphaAndVersion(hasAlpha); - - // Encode the main image stream. - this.EncodeStream(image); - } - - /// - /// Writes the image size to the stream. - /// - /// The input image width. - /// The input image height. - private void WriteImageSize(int inputImgWidth, int inputImgHeight) - { - Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); - Guard.MustBeLessThan(inputImgHeight, WebPConstants.MaxDimension, nameof(inputImgHeight)); - - uint width = (uint)inputImgWidth - 1; - uint height = (uint)inputImgHeight - 1; - - this.bitWriter.PutBits(width, WebPConstants.Vp8LImageSizeBits); - this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); - } - - private void WriteAlphaAndVersion(bool hasAlpha) - { - this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); - this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); - } - - /// - /// Encodes the image stream using lossless webp format. - /// - /// The pixel type. - /// The image to encode. - private void EncodeStream(Image image) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - int bytePosition = this.bitWriter.NumBytes(); - var enc = new Vp8LEncoder(this.memoryAllocator, width, height); - - // Convert image pixels to bgra array. - Span bgra = enc.Bgra.GetSpan(); - int idx = 0; - for (int y = 0; y < height; y++) - { - Span rowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) - { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; - } - } - - // Analyze image (entropy, numPalettes etc). - this.EncoderAnalyze(image, enc, bgra); - - var entropyIdx = 3; // TODO: hardcoded for now. - int quality = 75; // TODO: quality is hardcoded for now. - bool useCache = true; // TODO: useCache is hardcoded for now. - bool redAndBlueAlwaysZero = false; - - enc.UsePalette = entropyIdx == (int)EntropyIx.Palette; - enc.UseSubtractGreenTransform = (entropyIdx == (int)EntropyIx.SubGreen) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); - enc.UsePredictorTransform = (entropyIdx == (int)EntropyIx.Spatial) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); - enc.UseCrossColorTransform = redAndBlueAlwaysZero ? false : enc.UsePredictorTransform; - enc.AllocateTransformBuffer(width, height); - - // Reset any parameter in the encoder that is set in the previous iteration. - enc.CacheBits = 0; - enc.ClearRefs(); - - // TODO: Apply near-lossless preprocessing. - - // Encode palette. - if (enc.UsePalette) - { - this.EncodePalette(image, bgra, enc); - this.MapImageFromPalette(enc, width, height); - - // If using a color cache, do not have it bigger than the number of colors. - if (useCache && enc.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) - { - enc.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)enc.PaletteSize) + 1; - } - } - - // Apply transforms and write transform data. - if (enc.UseSubtractGreenTransform) - { - this.ApplySubtractGreen(enc, enc.CurrentWidth, height); - } - - if (enc.UsePredictorTransform) - { - this.ApplyPredictFilter(enc, enc.CurrentWidth, height, quality, enc.UseSubtractGreenTransform); - } - - if (enc.UseCrossColorTransform) - { - this.ApplyCrossColorFilter(enc, enc.CurrentWidth, height, quality); - } - - this.bitWriter.PutBits(0, 1); // No more transforms. - - // Encode and write the transformed image. - this.EncodeImage(bgra, enc.HashChain, enc.Refs, enc.CurrentWidth, height, quality, useCache, enc.CacheBits, enc.HistoBits, bytePosition); - } - - /// - /// Analyzes the image and decides what transforms should be used. - /// - private void EncoderAnalyze(Image image, Vp8LEncoder enc, Span bgra) - where TPixel : unmanaged, IPixel - { - int method = 4; // TODO: method hardcoded to 4 for now. - int width = image.Width; - int height = image.Height; - - // Check if we only deal with a small number of colors and should use a palette. - var usePalette = this.AnalyzeAndCreatePalette(image, enc); - - // Empirical bit sizes. - enc.HistoBits = GetHistoBits(method, usePalette, width, height); - enc.TransformBits = GetTransformBits(method, enc.HistoBits); - - // Try out multiple LZ77 on images with few colors. - var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); - - // TODO: Fill CrunchConfig - } - - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition) - { - int lz77sTypesToTrySize = 1; // TODO: hardcoded for now. - int[] lz77sTypesToTry = { 3 }; - int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); - var histogramSymbols = new short[histogramImageXySize]; - var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = new HuffmanTree(); - } - - if (useCache) - { - if (cacheBits == 0) - { - cacheBits = WebPConstants.MaxColorCacheBits; - } - } - else - { - cacheBits = 0; - } - - // Calculate backward references from ARGB image. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); - // TODO: BitWriterInit(&bw_best, 0) - // BitWriterClone(bw, &bw_best)) - - for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) - { - Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, lz77sTypesToTry[lz77sIdx], ref cacheBits, hashChain, refsArray[0], refsArray[1]); - - // Keep the best references aside and use the other element from the first - // two as a temporary for later usage. - Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; - - var tmpHisto = new Vp8LHistogram(cacheBits); - var histogramImage = new List(histogramImageXySize); - for (int i = 0; i < histogramImageXySize; i++) - { - histogramImage.Add(new Vp8LHistogram(cacheBits)); - } - - // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); - - // Create Huffman bit lengths and codes for each histogram image. - var histogramImageSize = histogramImage.Count; - var bitArraySize = 5 * histogramImageSize; - var huffmanCodes = new HuffmanTreeCode[bitArraySize]; - for (int i = 0; i < huffmanCodes.Length; i++) - { - huffmanCodes[i] = new HuffmanTreeCode(); - } - - GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); - - // Color Cache parameters. - if (cacheBits > 0) - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)cacheBits, 4); - } - else - { - this.bitWriter.PutBits(0, 1); - } - - // Huffman image + meta huffman. - bool writeHistogramImage = histogramImageSize > 1; - this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); - if (writeHistogramImage) - { - using System.Buffers.IMemoryOwner histogramArgbBuffer = this.memoryAllocator.Allocate(histogramImageXySize); - Span histogramArgb = histogramArgbBuffer.GetSpan(); - int maxIndex = 0; - for (int i = 0; i < histogramImageXySize; i++) - { - int symbolIndex = histogramSymbols[i] & 0xffff; - histogramArgb[i] = (uint)(symbolIndex << 8); - if (symbolIndex >= maxIndex) - { - maxIndex = symbolIndex + 1; - } - } - - histogramImageSize = maxIndex; - this.bitWriter.PutBits((uint)(histogramBits - 2), 3); - this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); - } - - // Store Huffman codes. - // Find maximum number of symbols for the huffman tree-set. - int maxTokens = 0; - for (int i = 0; i < 5 * histogramImageSize; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - if (maxTokens < codes.NumSymbols) - { - maxTokens = codes.NumSymbols; - } - } - - var tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) - { - tokens[i] = new HuffmanTreeToken(); - } - - for (int i = 0; i < 5 * histogramImageSize; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - this.StoreHuffmanCode(huffTree, tokens, codes); - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - - // Store actual literals. - var hdrSizeTmp = (int)(this.bitWriter.NumBytes() - initBytePosition); - this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); - - // TODO: Keep track of the smallest image so far. - } - } - - /// - /// Save the palette to the bitstream. - /// - /// The image. - /// The Vp8L Encoder. - private void EncodePalette(Image image, Span bgra, Vp8LEncoder enc) - where TPixel : unmanaged, IPixel - { - Span tmpPalette = new uint[WebPConstants.MaxPaletteSize]; - int paletteSize = enc.PaletteSize; - Span palette = enc.Palette.Memory.Span; - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); - this.bitWriter.PutBits((uint)paletteSize - 1, 8); - for (int i = paletteSize - 1; i >= 1; i--) - { - tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); - } - - tmpPalette[0] = palette[0]; - this.EncodeImageNoHuffman(tmpPalette, enc.HashChain, enc.Refs[0], enc.Refs[1], width: paletteSize, height: 1, quality: 20); - } - - /// - /// Applies the substract green transformation to the pixel data of the image. - /// - /// The VP8L Encoder. - /// The width of the image. - /// The height of the image. - private void ApplySubtractGreen(Vp8LEncoder enc, int width, int height) - { - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); - LosslessUtils.SubtractGreenFromBlueAndRed(enc.Bgra.GetSpan(), width * height); - } - - private void ApplyPredictFilter(Vp8LEncoder enc, int width, int height, int quality, bool usedSubtractGreen) - { - int nearLosslessStrength = 100; // TODO: for now always 100 - bool exact = false; // TODO: always false for now. - int predBits = enc.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, predBits); - int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - - PredictorEncoder.ResidualImage(width, height, predBits, enc.Bgra.GetSpan(), enc.BgraScratch.GetSpan(), enc.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); - - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); - this.bitWriter.PutBits((uint)(predBits - 2), 3); - - this.EncodeImageNoHuffman(enc.TransformData.GetSpan(), enc.HashChain, enc.Refs[0], enc.Refs[1], transformWidth, transformHeight, quality); - } - - private void ApplyCrossColorFilter(Vp8LEncoder enc, int width, int height, int quality) - { - int colorTransformBits = enc.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, quality, enc.Bgra.GetSpan(), enc.TransformData.GetSpan()); - - this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); - this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); - - this.EncodeImageNoHuffman(enc.TransformData.GetSpan(), enc.HashChain, enc.Refs[0], enc.Refs[1], transformWidth, transformHeight, quality); - } - - private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) - { - int cacheBits = 0; - var histogramSymbols = new short[1]; // Only one tree, one symbol. - var huffmanCodes = new HuffmanTreeCode[5]; - for (int i = 0; i < huffmanCodes.Length; i++) - { - huffmanCodes[i] = new HuffmanTreeCode(); - } - - var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = new HuffmanTree(); - } - - // Calculate backward references from the image pixels. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); - - Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( - width, - height, - bgra, - quality, - (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, - ref cacheBits, - hashChain, - refsTmp1, - refsTmp2); - - var histogramImage = new List() - { - new Vp8LHistogram(cacheBits) - }; - - // Build histogram image and symbols from backward references. - histogramImage[0].StoreRefs(refs); - - // Create Huffman bit lengths and codes for each histogram image. - GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); - - // No color cache, no Huffman image. - this.bitWriter.PutBits(0, 1); - - // Find maximum number of symbols for the huffman tree-set. - int maxTokens = 0; - for (int i = 0; i < 5; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - if (maxTokens < codes.NumSymbols) - { - maxTokens = codes.NumSymbols; - } - } - - var tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) - { - tokens[i] = new HuffmanTreeToken(); - } - - // Store Huffman codes. - for (int i = 0; i < 5; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - this.StoreHuffmanCode(huffTree, tokens, codes); - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - - // Store actual literals. - this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); - } - - private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) - { - int count = 0; - int[] symbols = { 0, 0 }; - int maxBits = 8; - int maxSymbol = 1 << maxBits; - - // Check whether it's a small tree. - for (int i = 0; i < huffmanCode.NumSymbols && count < 3; ++i) - { - if (huffmanCode.CodeLengths[i] != 0) - { - if (count < 2) - { - symbols[count] = i; - } - - count++; - } - } - - if (count == 0) + if (this.lossless) { - // emit minimal tree for empty cases - // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 - this.bitWriter.PutBits(0x01, 4); - } - else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) - { - this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. - this.bitWriter.PutBits((uint)(count - 1), 1); - if (symbols[0] <= 1) - { - this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. - this.bitWriter.PutBits((uint)symbols[0], 1); - } - else - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)symbols[0], 8); - } - - if (count == 2) - { - this.bitWriter.PutBits((uint)symbols[1], 8); - } + var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height); + enc.Encode(image, stream); } else { - this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); - } - } - - private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) - { - int i; - var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes]; - var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes]; - var huffmanCode = new HuffmanTreeCode(); - huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; - huffmanCode.CodeLengths = codeLengthBitDepth; - huffmanCode.Codes = codeLengthBitDepthSymbols; - - this.bitWriter.PutBits(0, 1); - var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - var histogram = new uint[WebPConstants.CodeLengthCodes + 1]; - var bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; - for (i = 0; i < numTokens; i++) - { - histogram[tokens[i].Code]++; - } - - HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); - this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); - ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); - - int trailingZeroBits = 0; - int trimmedLength = numTokens; - i = numTokens; - while (i-- > 0) - { - int ix = tokens[i].Code; - if (ix == 0 || ix == 17 || ix == 18) - { - trimmedLength--; // discount trailing zeros. - trailingZeroBits += codeLengthBitDepth[ix]; - if (ix == 17) - { - trailingZeroBits += 3; - } - else if (ix == 18) - { - trailingZeroBits += 7; - } - } - else - { - break; - } - } - - var writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; - var length = writeTrimmedLength ? trimmedLength : numTokens; - this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); - if (writeTrimmedLength) - { - if (trimmedLength == 2) - { - this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmed_length=2 - } - else - { - int nbits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); - int nbitpairs = (nbits / 2) + 1; - this.bitWriter.PutBits((uint)nbitpairs - 1, 3); - this.bitWriter.PutBits((uint)trimmedLength - 2, nbitpairs * 2); - } - } - - this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); - } - - private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) - { - for (int i = 0; i < numTokens; i++) - { - int ix = tokens[i].Code; - int extraBits = tokens[i].ExtraBits; - this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); - switch (ix) - { - case 16: - this.bitWriter.PutBits((uint)extraBits, 2); - break; - case 17: - this.bitWriter.PutBits((uint)extraBits, 3); - break; - case 18: - this.bitWriter.PutBits((uint)extraBits, 7); - break; - } - } - } - - private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitdepth) - { - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - - // Throw away trailing zeros: - int codesToStore = WebPConstants.CodeLengthCodes; - for (; codesToStore > 4; codesToStore--) - { - if (codeLengthBitdepth[storageOrder[codesToStore - 1]] != 0) - { - break; - } - } - - this.bitWriter.PutBits((uint)codesToStore - 4, 4); - for (int i = 0; i < codesToStore; i++) - { - this.bitWriter.PutBits(codeLengthBitdepth[storageOrder[i]], 3); - } - } - - private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) - { - int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; - int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); - - // x and y trace the position in the image. - int x = 0; - int y = 0; - int tileX = x & tileMask; - int tileY = y & tileMask; - int histogramIx = histogramSymbols[0]; - Span codes = huffmanCodes.AsSpan(5 * histogramIx); - using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); - while (c.MoveNext()) - { - PixOrCopy v = c.Current; - if ((tileX != (x & tileMask)) || (tileY != (y & tileMask))) - { - tileX = x & tileMask; - tileY = y & tileMask; - histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; - codes = huffmanCodes.AsSpan(5 * histogramIx); - } - - if (v.IsLiteral()) - { - byte[] order = { 1, 2, 0, 3 }; - for (int k = 0; k < 4; k++) - { - int code = (int)v.Literal(order[k]); - this.bitWriter.WriteHuffmanCode(codes[k], code); - } - } - else if (v.IsCacheIdx()) - { - int code = (int)v.CacheIdx(); - int literalIx = 256 + WebPConstants.NumLengthCodes + code; - this.bitWriter.WriteHuffmanCode(codes[0], literalIx); - } - else - { - int bits = 0; - int nBits = 0; - int distance = (int)v.Distance(); - int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); - - // Don't write the distance with the extra bits code since - // the distance can be up to 18 bits of extra bits, and the prefix - // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. - code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCode(codes[4], code); - this.bitWriter.PutBits((uint)bits, nBits); - } - - x += v.Length(); - while (x >= width) - { - x -= width; - y++; - } - } - } - - /// - /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. - /// - /// The pixel type of the image. - /// The image to analyze. - /// Indicates whether a palette should be used. - /// The palette size. - /// The transformation bits. - /// Indicates if red and blue are always zero. - /// The entropy mode to use. - private EntropyIx AnalyzeEntropy(Image image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - - if (usePalette && paletteSize <= 16) - { - // In the case of small palettes, we pack 2, 4 or 8 pixels together. In - // practice, small palettes are better than any other transform. - redAndBlueAlwaysZero = true; - return EntropyIx.Palette; - } - - using System.Buffers.IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); - Span histo = histoBuffer.Memory.Span; - Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. - Span prevRow = null; - for (int y = 0; y < height; y++) - { - Span currentRow = image.GetPixelRowSpan(y); - for (int x = 0; x < width; x++) - { - Bgra32 pix = ToBgra32(currentRow[x]); - uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); - pixPrev = pix; - if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) - { - continue; - } - - AddSingle( - pix.PackedValue, - histo.Slice((int)HistoIx.HistoAlpha * 256), - histo.Slice((int)HistoIx.HistoRed * 256), - histo.Slice((int)HistoIx.HistoGreen * 256), - histo.Slice((int)HistoIx.HistoBlue * 256)); - AddSingle( - pixDiff, - histo.Slice((int)HistoIx.HistoAlphaPred * 256), - histo.Slice((int)HistoIx.HistoRedPred * 256), - histo.Slice((int)HistoIx.HistoGreenPred * 256), - histo.Slice((int)HistoIx.HistoBluePred * 256)); - AddSingleSubGreen( - pix.PackedValue, - histo.Slice((int)HistoIx.HistoRedSubGreen * 256), - histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); - AddSingleSubGreen( - pixDiff, - histo.Slice((int)HistoIx.HistoRedPredSubGreen * 256), - histo.Slice((int)HistoIx.HistoBluePredSubGreen * 256)); - - // Approximate the palette by the entropy of the multiplicative hash. - uint hash = HashPix(pix.PackedValue); - histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; - } - - var histo0 = histo[0]; - prevRow = currentRow; - } - - var entropyComp = new double[(int)HistoIx.HistoTotal]; - var entropy = new double[(int)EntropyIx.NumEntropyIx]; - int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; - - // Let's add one zero to the predicted histograms. The zeros are removed - // too efficiently by the pixDiff == 0 comparison, at least one of the - // zeros is likely to exist. - histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; - histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; - histo[(int)HistoIx.HistoRedPred * 256]++; - histo[(int)HistoIx.HistoGreenPred * 256]++; - histo[(int)HistoIx.HistoBluePred * 256]++; - histo[(int)HistoIx.HistoAlphaPred * 256]++; - - for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) - { - var bitEntropy = new Vp8LBitEntropy(); - Span curHisto = histo.Slice(j * 256, 256); - bitEntropy.BitsEntropyUnrefined(curHisto, 256); - entropyComp[j] = bitEntropy.BitsEntropyRefine(); - } - - entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRed] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlue]; - entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPred] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePred]; - entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRedSubGreen] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlueSubGreen]; - entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPredSubGreen] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePredSubGreen]; - entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; - - // When including transforms, there is an overhead in bits from - // storing them. This overhead is small but matters for small images. - // For spatial, there are 14 transformations. - entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(14); - - // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. - entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(24); - - // For palettes, add the cost of storing the palette. - // We empirically estimate the cost of a compressed entry as 8 bits. - // The palette is differential-coded when compressed hence a much - // lower cost than sizeof(uint32_t)*8. - entropy[(int)EntropyIx.Palette] += paletteSize * 8; - - EntropyIx minEntropyIx = EntropyIx.Direct; - for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) - { - if (entropy[(int)minEntropyIx] > entropy[k]) - { - minEntropyIx = (EntropyIx)k; - } - } - - redAndBlueAlwaysZero = true; - - // Let's check if the histogram of the chosen entropy mode has - // non-zero red and blue values. If all are zero, we can later skip - // the cross color optimization. - var histoPairs = new byte[][] - { - new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, - new byte[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, - new byte[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, - new byte[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, - new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } - }; - Span redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]); - Span blueHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][1]); - for (int i = 1; i < 256; i++) - { - if ((redHisto[i] | blueHisto[i]) != 0) - { - redAndBlueAlwaysZero = false; - break; - } - } - - return minEntropyIx; - } - - /// - /// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, - /// creates a palette and returns true, else returns false. - /// - /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(Image image, Vp8LEncoder enc) - where TPixel : unmanaged, IPixel - { - Span palette = enc.Palette.Memory.Span; - enc.PaletteSize = this.GetColorPalette(image, palette); - if (enc.PaletteSize > WebPConstants.MaxPaletteSize) - { - enc.PaletteSize = 0; - return false; - } - - uint[] paletteArray = palette.Slice(0, enc.PaletteSize).ToArray(); - Array.Sort(paletteArray); - paletteArray.CopyTo(palette); - - if (PaletteHasNonMonotonousDeltas(palette, enc.PaletteSize)) - { - GreedyMinimizeDeltas(palette, enc.PaletteSize); + var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height); + enc.Encode(image, stream); } - - return true; } /// - /// Gets the color palette. + /// Encodes the image to the specified stream from the . /// - /// The pixel type of the image. - /// The image to get the palette from. - /// The span to store the palette into. - /// The number of palette entries. - private int GetColorPalette(Image image, Span palette) + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var colors = new HashSet(); - for (int y = 0; y < image.Height; y++) + if (stream.CanSeek) { - Span rowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) - { - colors.Add(rowSpan[x]); - if (colors.Count > WebPConstants.MaxPaletteSize) - { - // Exact count is not needed, because a palette will not be used then anyway. - return WebPConstants.MaxPaletteSize + 1; - } - } - } - - // Fill the colors into the palette. - using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); - int idx = 0; - while (colorEnumerator.MoveNext()) - { - Bgra32 bgra = ToBgra32(colorEnumerator.Current); - palette[idx++] = bgra.PackedValue; - } - - return colors.Count; - } - - private void MapImageFromPalette(Vp8LEncoder enc, int width, int height) - { - Span src = enc.Bgra.GetSpan(); - int srcStride = enc.CurrentWidth; - Span dst = enc.Bgra.GetSpan(); // Applying the palette will be done in place. - Span palette = enc.Palette.GetSpan(); - int paletteSize = enc.PaletteSize; - int xBits; - - // Replace each input pixel by corresponding palette index. - // This is done line by line. - if (paletteSize <= 4) - { - xBits = (paletteSize <= 2) ? 3 : 2; - } - else - { - xBits = (paletteSize <= 16) ? 1 : 0; - } - - this.ApplyPalette(src, srcStride, dst, enc.CurrentWidth, palette, paletteSize, width, height, xBits); - } - - /// - /// Remap argb values in src[] to packed palettes entries in dst[] - /// using 'row' as a temporary buffer of size 'width'. - /// We assume that all src[] values have a corresponding entry in the palette. - /// Note: src[] can be the same as dst[] - /// - private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) - { - using System.Buffers.IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); - Span tmpRow = tmpRowBuffer.GetSpan(); - - if (paletteSize < ApplyPaletteGreedyMax) - { - // TODO: APPLY_PALETTE_FOR(SearchColorGreedy(palette, palette_size, pix)); + this.Encode(image, stream); } else { - uint[] buffer = new uint[PaletteInvSize]; - - // Try to find a perfect hash function able to go from a color to an index - // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. - int i; - for (i = 0; i < 3; i++) - { - bool useLUT = true; - - // Set each element in buffer to max value. - buffer.AsSpan().Fill(uint.MaxValue); - - for (int j = 0; j < paletteSize; j++) - { - uint ind = 0; - switch (i) - { - case 0: - ind = ApplyPaletteHash0(palette[j]); - break; - case 1: - ind = ApplyPaletteHash1(palette[j]); - break; - case 2: - ind = ApplyPaletteHash2(palette[j]); - break; - } - - if (buffer[ind] != uint.MaxValue) - { - useLUT = false; - break; - } - else - { - buffer[ind] = (uint)j; - } - } - - if (useLUT) - { - break; - } - } - - if (i == 0 || i == 1 || i == 2) - { - ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); - } - else - { - uint[] idxMap = new uint[paletteSize]; - uint[] paletteSorted = new uint[paletteSize]; - PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); - ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); - } - } - } - - private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) - { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - switch (hashIdx) - { - case 0: - prevIdx = buffer[ApplyPaletteHash0(pix)]; - break; - case 1: - prevIdx = buffer[ApplyPaletteHash1(pix)]; - break; - case 2: - prevIdx = buffer[ApplyPaletteHash2(pix)]; - break; - } - - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - - src = src.Slice((int)srcStride); - dst = dst.Slice((int)dstStride); - } - } - - private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) - { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - - src = src.Slice((int)srcStride); - dst = dst.Slice((int)dstStride); - } - } - - /// - /// Sort palette in increasing order and prepare an inverse mapping array. - /// - private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) - { - palette.Slice(numColors).CopyTo(sorted); - Array.Sort(sorted, PaletteCompareColorsForSort); - for (int i = 0; i < numColors; i++) - { - idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; - } - } - - private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) - { - int low = 0; - if (sorted[low] == color) - { - return low; // loop invariant: sorted[low] != color - } - - while (true) - { - int mid = (low + hi) >> 1; - if (sorted[mid] == color) - { - return mid; - } - else if (sorted[mid] < color) - { - low = mid; - } - else - { - hi = mid; - } - } - } - - private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) - { - int count = 0; - for (int k = 0; k < huffmanCode.NumSymbols; k++) - { - if (huffmanCode.CodeLengths[k] != 0) - { - count++; - if (count > 1) - { - return; - } - } - } - - for (int k = 0; k < huffmanCode.NumSymbols; k++) - { - huffmanCode.CodeLengths[k] = 0; - huffmanCode.Codes[k] = 0; - } - } - - /// - /// The palette has been sorted by alpha. This function checks if the other components of the palette - /// have a monotonic development with regards to position in the palette. - /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development - /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. - /// - /// The palette. - /// Number of colors in the palette. - /// True, if the palette has no monotonous deltas. - private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) - { - uint predict = 0x000000; - byte signFound = 0x00; - for (int i = 0; i < numColors; ++i) - { - uint diff = LosslessUtils.SubPixels(palette[i], predict); - byte rd = (byte)((diff >> 16) & 0xff); - byte gd = (byte)((diff >> 8) & 0xff); - byte bd = (byte)((diff >> 0) & 0xff); - if (rd != 0x00) - { - signFound |= (byte)((rd < 0x80) ? 1 : 2); - } - - if (gd != 0x00) - { - signFound |= (byte)((gd < 0x80) ? 8 : 16); - } - - if (bd != 0x00) - { - signFound |= (byte)((bd < 0x80) ? 64 : 128); - } - } - - return (signFound & (signFound << 1)) != 0; // two consequent signs. - } - - /// - /// Find greedily always the closest color of the predicted color to minimize - /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. - /// - /// The palette. - /// The number of colors in the palette. - private static void GreedyMinimizeDeltas(Span palette, int numColors) - { - uint predict = 0x00000000; - for (int i = 0; i < numColors; ++i) - { - int bestIdx = i; - uint bestScore = ~0U; - for (int k = i; k < numColors; ++k) + using (var ms = new MemoryStream()) { - uint curScore = PaletteColorDistance(palette[k], predict); - if (bestScore > curScore) - { - bestScore = curScore; - bestIdx = k; - } + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); } - - // Swap color(palette[bestIdx], palette[i]); - uint best = palette[bestIdx]; - palette[bestIdx] = palette[i]; - palette[i] = best; - predict = palette[i]; } } - - private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) - { - long totalLengthSize = 0; - int maxNumSymbols = 0; - - // Iterate over all histograms and get the aggregate number of codes used. - for (int i = 0; i < histogramImage.Count; i++) - { - Vp8LHistogram histo = histogramImage[i]; - int startIdx = 5 * i; - for (int k = 0; k < 5; k++) - { - int numSymbols = - (k == 0) ? histo.NumCodes() : - (k == 4) ? WebPConstants.NumDistanceCodes : 256; - huffmanCodes[startIdx + k].NumSymbols = numSymbols; - totalLengthSize += numSymbols; - } - } - - var end = 5 * histogramImage.Count; - for (int i = 0; i < end; i++) - { - int bitLength = huffmanCodes[i].NumSymbols; - huffmanCodes[i].Codes = new short[bitLength]; - huffmanCodes[i].CodeLengths = new byte[bitLength]; - if (maxNumSymbols < bitLength) - { - maxNumSymbols = bitLength; - } - } - - // Create Huffman trees. - bool[] bufRle = new bool[maxNumSymbols]; - var huffTree = new HuffmanTree[3 * maxNumSymbols]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = new HuffmanTree(); - } - - for (int i = 0; i < histogramImage.Count; i++) - { - int codesStartIdx = 5 * i; - Vp8LHistogram histo = histogramImage[i]; - HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); - HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); - HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); - HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); - HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); - } - } - - /// - /// Computes a value that is related to the entropy created by the palette entry diff. - /// - /// First color. - /// Second color. - /// The color distance. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteColorDistance(uint col1, uint col2) - { - uint diff = LosslessUtils.SubPixels(col1, col2); - uint moreWeightForRGBThanForAlpha = 9; - uint score = PaletteComponentDistance((diff >> 0) & 0xff); - score += PaletteComponentDistance((diff >> 8) & 0xff); - score += PaletteComponentDistance((diff >> 16) & 0xff); - score *= moreWeightForRGBThanForAlpha; - score += PaletteComponentDistance((diff >> 24) & 0xff); - - return score; - } - - /// - /// Calculates the huffman image bits. - /// - private static int GetHistoBits(int method, bool usePalette, int width, int height) - { - // Make tile size a function of encoding method (Range: 0 to 6). - int histoBits = (usePalette ? 9 : 7) - method; - while (true) - { - int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); - if (huffImageSize <= WebPConstants.MaxHuffImageSize) - { - break; - } - - histoBits++; - } - - return (histoBits < WebPConstants.MinHuffmanBits) ? WebPConstants.MinHuffmanBits : - (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; - } - - /// - /// Calculates the bits used for the transformation. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetTransformBits(int method, int histoBits) - { - int maxTransformBits = (method < 4) ? 6 : (method > 4) ? 4 : 5; - int res = (histoBits > maxTransformBits) ? maxTransformBits : histoBits; - return res; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Bgra32 ToBgra32(TPixel color) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - color.ToRgba32(ref rgba); - var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); - return bgra; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingle(uint p, Span a, Span r, Span g, Span b) - { - a[(int)(p >> 24) & 0xff]++; - r[(int)(p >> 16) & 0xff]++; - g[(int)(p >> 8) & 0xff]++; - b[(int)(p >> 0) & 0xff]++; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingleSubGreen(uint p, Span r, Span b) - { - int green = (int)p >> 8; // The upper bits are masked away later. - r[(int)((p >> 16) - green) & 0xff]++; - b[(int)((p >> 0) - green) & 0xff]++; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash0(uint color) - { - // Focus on the green color. - return (color >> 8) & 0xff; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash1(uint color) - { - // Forget about alpha. - return ((uint)((color & 0x00ffffffu) * 4222244071ul)) >> (32 - PaletteInvSizeBits); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash2(uint color) - { - // Forget about alpha. - return ((uint)((color & 0x00ffffffu) * ((1ul << 31) - 1))) >> (32 - PaletteInvSizeBits); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint HashPix(uint pix) - { - // Note that masking with 0xffffffffu is for preventing an - // 'unsigned int overflow' warning. Doesn't impact the compiled code. - return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int PaletteCompareColorsForSort(uint p1, uint p2) - { - return (p1 < p2) ? -1 : 1; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteComponentDistance(uint v) - { - return (v <= 128) ? v : (256 - v); - } } } From 9408737cfcd3b166f43f9cd13d71657abff0ba6b Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Sat, 25 Jul 2020 23:16:46 +0100 Subject: [PATCH 179/359] Added Clone and Resize to Vp8LBitWriter Also looked at some TODOs in Vp8LEncoder --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 53 +++++++++++++++---- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 17 +++++- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 9ff457648..7428c8bcc 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -45,14 +45,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private int end; - private bool error; - public Vp8LBitWriter(int expectedSize) { this.buffer = new byte[expectedSize]; this.end = this.buffer.Length; } + /// + /// Initializes a new instance of the class. + /// Used internally for cloning + /// + private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) + { + this.buffer = buffer; + this.bits = bits; + this.used = used; + this.cur = cur; + } + /// /// This function writes bits into bytes in increasing addresses (little endian), /// and within a byte least-significant-bit first. This function can write up to 32 bits in one go. @@ -99,11 +109,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter if (this.cur + WriterBytes > this.end) { var extraSize = (this.end - this.cur) + MinExtraSize; - if (!this.BitWriterResize(extraSize)) - { - this.error = true; - return; - } + this.BitWriterResize(extraSize); } BinaryPrimitives.WriteUInt64LittleEndian(this.buffer.AsSpan(this.cur), this.bits); @@ -112,10 +118,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.used -= WriterBits; } - private bool BitWriterResize(int extraSize) + private void BitWriterResize(int extraSize) + { + int maxBytes = this.end + this.buffer.Length; + int sizeRequired = this.cur + extraSize; + + if (maxBytes > 0 && sizeRequired < maxBytes) + { + return; + } + + int newSize = (3 * maxBytes) >> 1; + if (newSize < sizeRequired) + { + newSize = sizeRequired; + } + + // make new size multiple of 1k + newSize = ((newSize >> 10) + 1) << 10; + if (this.cur > 0) + { + Array.Resize(ref this.buffer, newSize); + } + + this.end = this.buffer.Length; + } + + public Vp8LBitWriter Clone() { - // TODO: resize buffer - return true; + byte[] clonedBuffer = new byte[this.buffer.Length]; + Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur); + return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index aac3bd52e..1663c32dd 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -318,8 +318,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Calculate backward references from ARGB image. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); - // TODO: BitWriterInit(&bw_best, 0) - // BitWriterClone(bw, &bw_best)) + Vp8LBitWriter bitWriterBest = null; + if (lz77sTypesToTrySize > 1) + { + bitWriterBest = this.bitWriter.Clone(); + } for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) { @@ -329,6 +332,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // two as a temporary for later usage. Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; + // TODO: this.bitWriter.Reset(); + var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); for (int i = 0; i < histogramImageXySize; i++) @@ -414,7 +419,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); // TODO: Keep track of the smallest image so far. + + if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()) + { + // TODO : This was done in the reference by swapping references, this will be slower + bitWriterBest = this.bitWriter.Clone(); + } } + + this.bitWriter = bitWriterBest; } /// From 231f19b13d9989b2965332f859ed0609b661d898 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Sat, 25 Jul 2020 23:22:47 +0100 Subject: [PATCH 180/359] Move public method above privates in Vp8LBitWriter --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 7428c8bcc..a3e35448b 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -100,6 +100,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return this.cur + ((this.used + 7) >> 3); } + public Vp8LBitWriter Clone() + { + byte[] clonedBuffer = new byte[this.buffer.Length]; + Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur); + return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// @@ -143,12 +150,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.end = this.buffer.Length; } - - public Vp8LBitWriter Clone() - { - byte[] clonedBuffer = new byte[this.buffer.Length]; - Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur); - return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); - } } } From ee08904d7be5d989c1cf8bd81dd7ed3e1fea421c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Jul 2020 11:33:13 +0200 Subject: [PATCH 181/359] Fix warnings and license headers --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 10 ++-- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- src/ImageSharp/Formats/WebP/EntropyIx.cs | 4 +- src/ImageSharp/Formats/WebP/HistoIx.cs | 4 +- .../Formats/WebP/IWebPEncoderOptions.cs | 4 +- .../Formats/WebP/ImageExtensions.cs | 4 +- .../WebP/Lossless/BackwardReferenceEncoder.cs | 20 ++++--- .../WebP/Lossless/CostCacheInterval.cs | 4 +- .../Formats/WebP/Lossless/CostInterval.cs | 4 +- .../Formats/WebP/Lossless/CostManager.cs | 5 +- .../Formats/WebP/Lossless/CostModel.cs | 4 +- .../Formats/WebP/Lossless/HistogramBinInfo.cs | 4 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 5 +- .../Formats/WebP/Lossless/HistogramPair.cs | 4 +- .../Formats/WebP/Lossless/HuffmanTree.cs | 4 +- .../Formats/WebP/Lossless/HuffmanTreeCode.cs | 4 +- .../Formats/WebP/Lossless/HuffmanTreeToken.cs | 4 +- .../Formats/WebP/Lossless/HuffmanUtils.cs | 2 +- .../Formats/WebP/Lossless/LosslessUtils.cs | 2 +- .../Formats/WebP/Lossless/PixOrCopy.cs | 7 ++- .../Formats/WebP/Lossless/PixOrCopyMode.cs | 4 +- .../Formats/WebP/Lossless/PredictorEncoder.cs | 60 ++++++++++++++----- .../Formats/WebP/Lossless/Vp8LBackwardRefs.cs | 9 ++- .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 4 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 12 ++-- .../Formats/WebP/Lossless/Vp8LHashChain.cs | 8 ++- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 10 ++-- .../Formats/WebP/Lossless/Vp8LLz77Type.cs | 4 +- .../Formats/WebP/Lossless/Vp8LMultipliers.cs | 4 +- .../Formats/WebP/Lossless/Vp8LRefsCursor.cs | 18 ------ .../Formats/WebP/Lossless/Vp8LStreaks.cs | 8 ++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 5 +- .../Formats/WebP/WebPCommonUtils.cs | 12 ++-- .../Formats/WebP/WebPLookupTables.cs | 5 +- 34 files changed, 146 insertions(+), 118 deletions(-) delete mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 2764abc30..a0dd88113 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// internal class Vp8BitWriter { - private uint range; + /*private uint range; private uint value; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.maxPos = 0; this.error = false; - //BitWriterResize(expected_size); - } + // BitWriterResize(expected_size); + }*/ } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index a3e35448b..bd7d260e4 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public Vp8LBitWriter Clone() { - byte[] clonedBuffer = new byte[this.buffer.Length]; + var clonedBuffer = new byte[this.buffer.Length]; Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur); return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs index f39c1981a..5bd354345 100644 --- a/src/ImageSharp/Formats/WebP/EntropyIx.cs +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs index 916a2c074..51a77ccd0 100644 --- a/src/ImageSharp/Formats/WebP/HistoIx.cs +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index 3ecef1b45..469421037 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP { diff --git a/src/ImageSharp/Formats/WebP/ImageExtensions.cs b/src/ImageSharp/Formats/WebP/ImageExtensions.cs index 5ceb85d74..0ed072a88 100644 --- a/src/ImageSharp/Formats/WebP/ImageExtensions.cs +++ b/src/ImageSharp/Formats/WebP/ImageExtensions.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.IO; diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index e23b6bf8e..d50937258 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -238,8 +237,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The return value is the pointer to the best of the two backward refs viz, /// refs[0] or refs[1]. /// - public static Vp8LBackwardRefs GetBackwardReferences(int width, int height, Span bgra, int quality, - int lz77TypesToTry, ref int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) + public static Vp8LBackwardRefs GetBackwardReferences( + int width, + int height, + Span bgra, + int quality, + int lz77TypesToTry, + ref int cacheBits, + Vp8LHashChain hashChain, + Vp8LBackwardRefs best, + Vp8LBackwardRefs worst) { var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits]; int lz77TypeBest = 0; @@ -248,7 +255,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Vp8LHashChain hashChainBox = null; for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) { - double bitCost; int cacheBitsTmp = cacheBitsInitial; if ((lz77TypesToTry & lz77Type) == 0) { @@ -279,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Keep the best backward references. histo[0] = new Vp8LHistogram(worst, cacheBitsTmp); - bitCost = histo[0].EstimateBits(); + var bitCost = histo[0].EstimateBits(); if (lz77TypeBest == 0 || bitCost < bitCostBest) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index 68bd7df11..47f85b738 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs index 3f20b3dd6..16f2edc15 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index 1f2241191..f648ae75c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using System.Linq; @@ -222,7 +222,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private void InsertInterval(double cost, int position, int start, int end) { // TODO: use COST_CACHE_INTERVAL_SIZE_MAX - var interval = new CostCacheInterval() { Cost = cost, diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs index d0226bbbf..3d050ab42 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs index eb58c6290..b75df6505 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 064152c02..50c77a6e5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; int imageHistoRawSize = histoXSize * histoYSize; int entropyCombineNumBins = BinSize; - short[] mapTmp = new short[imageHistoRawSize]; - short[] clusterMappings = new short[imageHistoRawSize]; + var mapTmp = new short[imageHistoRawSize]; + var clusterMappings = new short[imageHistoRawSize]; int numUsed = imageHistoRawSize; var origHisto = new List(imageHistoRawSize); for (int i = 0; i < imageHistoRawSize; i++) @@ -370,6 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Pop bestIdx2 from mappings. var mappingIndex = Array.BinarySearch(mappings, bestIdx2); + // TODO: memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1)); // Merge the histograms and remove bestIdx2 from the queue. diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs index edd8a2ba7..7458909e5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index daf807335..d7179ad12 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index 459a53de8..6b6e234d2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index 3708838c2..bd791b70c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Diagnostics; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 47f5ec128..62ecfa37a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Create an optimal Huffman tree. /// - /// + /// /// The huffman tree. /// The histogram. /// The size of the histogram. diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 94510efd2..eac88609c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless newBlue -= ColorTransformDelta((sbyte)m.GreenToBlue, green); newBlue -= ColorTransformDelta((sbyte)m.RedToBlue, red); newBlue &= 0xff; - data[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + data[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 356db2e92..b8378d6cd 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -1,8 +1,11 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { + [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] internal class PixOrCopy { public PixOrCopyMode Mode { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs index 043a174fc..ae0fbec84 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index ea11fcfe4..fcbac5267 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Finds the best predictor for each tile, and converts the image to residuals - /// with respect to predictions. If nearLosslessQuality < 100, applies + /// with respect to predictions. If nearLosslessQuality < 100, applies /// near lossless processing, shaving off more bits of residuals for lower qualities. /// public static void ResidualImage(int width, int height, int bits, Span argb, Span argbScratch, Span image, int nearLosslessQuality, bool exact, bool usedSubtractGreen) @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); - int[][] histo = new int[4][]; + var histo = new int[4][]; for (int i = 0; i < 4; i++) { histo[i] = new int[256]; @@ -73,12 +73,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); } - prevX = GetBestColorTransformForTile(tileX, tileY, bits, - prevX, prevY, - quality, width, height, - accumulatedRedHisto, - accumulatedBlueHisto, - argb); + prevX = GetBestColorTransformForTile( + tileX, + tileY, + bits, + prevX, + prevY, + quality, + width, + height, + accumulatedRedHisto, + accumulatedBlueHisto, + argb); image[offset] = MultipliersToColorCode(prevX); CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, argb); @@ -118,9 +124,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// the given pixel). /// /// Best predictor. - private static int GetBestPredictorForTile(int width, int height, int tileX, int tileY, - int bits, int[][] accumulated, Span argbScratch, Span argb, - int maxQuantization, bool exact, bool usedSubtractGreen, Span modes) + private static int GetBestPredictorForTile( + int width, + int height, + int tileX, + int tileY, + int bits, + int[][] accumulated, + Span argbScratch, + Span argb, + int maxQuantization, + bool exact, + bool usedSubtractGreen, + Span modes) { const int numPredModes = 14; int startX = tileX << bits; @@ -501,9 +517,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless xEnd = width; } - GetResidual(width, height, upperRow, currentRow, currentMaxDiffs, - mode, x, xEnd, y, maxQuantization, exact, - usedSubtractGreen, argb.Slice((y * width) + x)); + GetResidual( + width, + height, + upperRow, + currentRow, + currentMaxDiffs, + mode, + x, + xEnd, + y, + maxQuantization, + exact, + usedSubtractGreen, + argb.Slice((y * width) + x)); + x = xEnd; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs index 7f35d08e4..f80d26697 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,10 +13,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// Common block-size. + /// Gets or sets the common block-size. /// public int BlockSize { get; set; } + /// + /// Gets the backward references. + /// public List Refs { get; } public void Add(PixOrCopy pixOrCopy) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs index 0433f3eed..bdc8d853f 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 1663c32dd..658a2f58c 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -315,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless cacheBits = 0; } - // Calculate backward references from ARGB image. + // Calculate backward references from BGRA image. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); Vp8LBitWriter bitWriterBest = null; @@ -333,7 +333,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; // TODO: this.bitWriter.Reset(); - var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); for (int i = 0; i < histogramImageXySize; i++) @@ -419,7 +418,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); // TODO: Keep track of the smallest image so far. - if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()) { // TODO : This was done in the reference by swapping references, this will be slower @@ -711,7 +709,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitdepth) + private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) { // RFC 1951 will calm you down if you are worried about this funny sequence. // This sequence is tuned from that, but more weighted for lower symbol count, @@ -722,7 +720,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int codesToStore = WebPConstants.CodeLengthCodes; for (; codesToStore > 4; codesToStore--) { - if (codeLengthBitdepth[storageOrder[codesToStore - 1]] != 0) + if (codeLengthBitDepth[storageOrder[codesToStore - 1]] != 0) { break; } @@ -731,7 +729,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits((uint)codesToStore - 4, 4); for (int i = 0; i < codesToStore; i++) { - this.bitWriter.PutBits(codeLengthBitdepth[storageOrder[i]], 3); + this.bitWriter.PutBits(codeLengthBitDepth[storageOrder[i]], 3); } } @@ -1353,7 +1351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Create Huffman trees. - bool[] bufRle = new bool[maxNumSymbols]; + var bufRle = new bool[maxNumSymbols]; var huffTree = new HuffmanTree[3 * maxNumSymbols]; for (int i = 0; i < huffTree.Length; i++) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs index 60b795c15..0262ac332 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; @@ -19,13 +19,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// + /// Gets the offset length. /// The 20 most significant bits contain the offset at which the best match is found. - /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). + /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). /// The lower 12 bits contain the length of the match. /// public uint[] OffsetLength { get; } /// + /// Gets the size of the hash chain. /// This is the maximum size of the hash_chain that can be constructed. /// Typically this is the pixel count (width x height) for a given image. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index d18910574..9b591dcd0 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; @@ -67,9 +67,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.IsUsed = new bool[5]; } - /// - public IDeepCloneable DeepClone() => new Vp8LHistogram(this); - /// /// Gets or sets the palette code bits. /// @@ -109,6 +106,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public bool[] IsUsed { get; } + /// + public IDeepCloneable DeepClone() => new Vp8LHistogram(this); + /// /// Collect all the references into a histogram (without reset). /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs index 63d9f6e02..734aa5dce 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs index 3ce9a93ec..8a9088b57 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LMultipliers.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs deleted file mode 100644 index 6578d3f51..000000000 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. - -namespace SixLabors.ImageSharp.Formats.WebP.Lossless -{ - internal class Vp8LRefsCursor - { - public Vp8LRefsCursor(Vp8LBackwardRefs refs) - { - //this.Refs = refs; - //this.CurrentPos = 0; - } - - //public PixOrCopy Refs { get; } - - public PixOrCopy CurrentPos { get; } - } -} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs index bec2cc099..e0e976068 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -17,12 +17,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// + /// Gets the streak count. /// index: 0=zero streak, 1=non-zero streak. /// public int[] Counts { get; } /// - /// [zero/non-zero][streak < 3 / streak >= 3]. + /// Gets the streaks. + /// [zero/non-zero][streak < 3 / streak >= 3]. /// public int[][] Streaks { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 546de6624..89cf1e3b4 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// A bit writer for writing lossy webp streams. /// - private Vp8BitWriter bitWriter; + private readonly Vp8BitWriter bitWriter; /// /// Initializes a new instance of the class. @@ -34,7 +34,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.memoryAllocator = memoryAllocator; - // TODO: initialize bitwriter + // TODO: properly initialize the bitwriter + this.bitWriter = new Vp8BitWriter(); } public void Encode(Image image, Stream stream) diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs index 52027192b..6098d1b69 100644 --- a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; @@ -8,9 +8,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Utility methods for lossy and lossless webp format. /// - public static class WebPCommonUtils + internal static class WebPCommonUtils { - // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). + /// + /// Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n). + /// [MethodImpl(InliningOptions.ShortMethod)] public static int BitsLog2Floor(uint n) { @@ -21,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.WebP n >>= 8; } - return logValue + WebPLookupTables.LogTable8bit[n]; + return logValue + WebPLookupTables.LogTable8Bit[n]; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 43ded2c51..148dd0a54 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -236,7 +236,8 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 }; - public static readonly uint[] PlaneToCodeLut = { + public static readonly uint[] PlaneToCodeLut = + { 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, @@ -248,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.WebP }; // 31 ^ clz(i) - public static readonly byte[] LogTable8bit = + public static readonly byte[] LogTable8Bit = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, From 56c1917be590537a45a876c22ee69c54c00dd10e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Jul 2020 14:51:06 +0200 Subject: [PATCH 182/359] Fix in HistogramRemap: Recompute each output --- .../Formats/WebP/Lossless/HistogramEncoder.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 50c77a6e5..203f254b7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); if (entropyCombine) { - var binMap = mapTmp; + short[] binMap = mapTmp; var numClusters = numUsed; double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); HistogramAnalyzeEntropyBin(imageHisto, binMap); @@ -247,7 +247,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private static void OptimizeHistogramSymbols(List histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) { - int clusterMax; bool doContinue = true; // First, assign the lowest cluster to each pixel. @@ -273,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Create a mapping from a cluster id to its minimal version. - clusterMax = 0; + var clusterMax = 0; clusterMappingsTmp.AsSpan().Fill(0); // Re-map the ids. @@ -312,7 +311,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int histoQueueMaxSize = histograms.Count * histograms.Count; // Fill the initial mapping. - int[] mappings = new int[histograms.Count]; + var mappings = new int[histograms.Count]; for (int j = 0, iter = 0; iter < histograms.Count; iter++) { mappings[j++] = iter; @@ -332,7 +331,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { // Choose two different histograms at random and try to combine them. uint tmp = (uint)(rand.Next() % randRange); - double currCost; int idx1 = (int)(tmp / (numUsed - 1)); int idx2 = (int)(tmp % (numUsed - 1)); if (idx2 >= idx1) @@ -344,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless idx2 = mappings[idx2]; // Calculate cost reduction on combination. - currCost = HistoPriorityListPush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); + var currCost = HistoPriorityListPush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); // Found a better pair? if (currCost < 0) @@ -532,6 +530,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + // Recompute each output. + var paletteCodeBits = output.First().PaletteCodeBits; + output.Clear(); + for (int i = 0; i < outSize; i++) + { + output.Add(new Vp8LHistogram(paletteCodeBits)); + } + for (int i = 0; i < inSize; i++) { if (input[i] == null) From fe8193a9826bc2c7b89ee11f9e9d8a9e827d47ee Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Jul 2020 17:43:03 +0200 Subject: [PATCH 183/359] Write bytes from the bitwriter to the stream --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 39 +++++++++++++-- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 47 ++++++++++++++++++- src/ImageSharp/Formats/WebP/WebPConstants.cs | 31 ++++++++++-- 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index bd7d260e4..cd6249632 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.IO; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -21,8 +22,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private const int WriterBits = 32; - private const int WriterMaxBits = 64; - /// /// Bit accumulator. /// @@ -45,15 +44,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private int end; + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. public Vp8LBitWriter(int expectedSize) { + // TODO: maybe use memory allocator here. this.buffer = new byte[expectedSize]; this.end = this.buffer.Length; } /// /// Initializes a new instance of the class. - /// Used internally for cloning + /// Used internally for cloning. /// private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) { @@ -107,6 +111,31 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } + /// + /// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this. + /// + /// The stream to write to. + public void WriteToStream(Stream stream) + { + stream.Write(this.buffer.AsSpan(0, this.NumBytes())); + } + + /// + /// Flush leftover bits. + /// + public void BitWriterFinish() + { + this.BitWriterResize((this.used + 7) >> 3); + while (this.used > 0) + { + this.buffer[this.cur++] = (byte)this.bits; + this.bits >>= 8; + this.used -= 8; + } + + this.used = 0; + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// @@ -125,6 +154,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.used -= WriterBits; } + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. private void BitWriterResize(int extraSize) { int maxBytes = this.end + this.buffer.Length; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 658a2f58c..22b111fbb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -3,8 +3,10 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Memory; @@ -147,6 +149,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public Vp8LHashChain HashChain { get; } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { @@ -162,11 +170,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Encode the main image stream. this.EncodeStream(image); - // TODO: write bytes from the bitwriter to the stream. + // Write bytes from the bitwriter buffer to the stream. + this.bitWriter.BitWriterFinish(); + var numBytes = this.bitWriter.NumBytes(); + var vp8LSize = 1 + numBytes; // One byte extra for the VP8L signature. + var pad = vp8LSize & 1; + var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8LSize + pad; + this.WriteRiffHeader(riffSize, vp8LSize, stream); + this.bitWriter.WriteToStream(stream); } /// - /// Writes the image size to the stream. + /// Writes the image size to the bitwriter buffer. /// /// The input image width. /// The input image height. @@ -182,12 +197,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); } + /// + /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer. + /// + /// Indicates if a alpha channel is present. private void WriteAlphaAndVersion(bool hasAlpha) { this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); } + /// + /// Writes the RIFF header to the stream. + /// + /// The block length. + /// The size in bytes of the compressed image. + /// The stream to write to. + private void WriteRiffHeader(int riffSize, int vp8LSize, Stream stream) + { + Span buffer = stackalloc byte[4]; + + stream.Write(WebPConstants.RiffFourCc); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize); + stream.Write(buffer); + stream.Write(WebPConstants.WebPHeader); + stream.Write(WebPConstants.Vp8LTag); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)vp8LSize); + stream.Write(buffer); + stream.WriteByte(WebPConstants.Vp8LMagicByte); + } + /// /// Encodes the image stream using lossless webp format. /// @@ -323,6 +362,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { bitWriterBest = this.bitWriter.Clone(); } + else + { + bitWriterBest = this.bitWriter; + } for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index bbc736b59..d90a0c51f 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -30,6 +30,22 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x2A }; + /// + /// Signature byte which identifies a VP8L header. + /// + public const byte Vp8LMagicByte = 0x2F; + + /// + /// Header bytes identifying a lossless image. + /// + public static readonly byte[] Vp8LTag = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x4C // L + }; + /// /// The header bytes identifying RIFF file. /// @@ -52,11 +68,6 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x50 // P }; - /// - /// Signature byte which identifies a VP8L header. - /// - public const byte Vp8LMagicByte = 0x2F; - /// /// 3 bits reserved for version. /// @@ -67,6 +78,16 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public const int Vp8LImageSizeBits = 14; + /// + /// Size of a chunk header. + /// + public const int ChunkHeaderSize = 8; + + /// + /// Size of a chunk tag (e.g. "VP8L"). + /// + public const int TagSize = 4; + /// /// The Vp8L version 0. /// From af7eae82ac12df18c10fe2a4bfd333415edf4cef Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Jul 2020 19:03:06 +0200 Subject: [PATCH 184/359] Change iccp test images --- tests/Images/Input/WebP/lossless_with_iccp.webp | 4 ++-- tests/Images/Input/WebP/lossy_with_iccp.webp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Images/Input/WebP/lossless_with_iccp.webp b/tests/Images/Input/WebP/lossless_with_iccp.webp index 585c6924c..56897125a 100644 --- a/tests/Images/Input/WebP/lossless_with_iccp.webp +++ b/tests/Images/Input/WebP/lossless_with_iccp.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cdb75584ac3db92d78c2c1ec828cb813d280540e5f1bb262ba0b2c352900f81 -size 69076 +oid sha256:863db3c8970769ec4fc6ab729abbd172a14e3fbb22bc3530d0288761506d751e +size 75858 diff --git a/tests/Images/Input/WebP/lossy_with_iccp.webp b/tests/Images/Input/WebP/lossy_with_iccp.webp index fa85b7b54..2f50e7673 100644 --- a/tests/Images/Input/WebP/lossy_with_iccp.webp +++ b/tests/Images/Input/WebP/lossy_with_iccp.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4323099f032940d9596265ac59529b6810d1fd84d2effc993e71b52778c18ebf -size 25242 +oid sha256:434cfe308cfcaef8d79f030906fe783df70e568d66df2e906dd98f2ffd5bcc1b +size 63036 From 84cfe3b3864819ec1a0808a4826ba8cfc1c1e7a3 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Mon, 27 Jul 2020 00:18:15 +0100 Subject: [PATCH 185/359] Work out the crunch configs for lossless webp Start threading the configs through the encoding and comparing results --- src/ImageSharp/Formats/WebP/EntropyIx.cs | 4 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 182 ++++++++++++------ 2 files changed, 126 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs index 5bd354345..0a8e9fb4c 100644 --- a/src/ImageSharp/Formats/WebP/EntropyIx.cs +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -18,6 +18,8 @@ namespace SixLabors.ImageSharp.Formats.WebP Palette = 4, - NumEntropyIx = 5 + PaletteAndSpatial = 5, + + NumEntropyIx = 6 } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 22b111fbb..61eb58930 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; @@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// A bit writer for writing lossless webp streams. ///
private Vp8LBitWriter bitWriter; - + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -252,66 +253,74 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Analyze image (entropy, numPalettes etc). - this.EncoderAnalyze(image); + CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero); - var entropyIdx = 3; // TODO: hardcoded for now. int quality = 75; // TODO: quality is hardcoded for now. - bool useCache = true; // TODO: useCache is hardcoded for now. - bool redAndBlueAlwaysZero = false; - - this.UsePalette = entropyIdx == (int)EntropyIx.Palette; - this.UseSubtractGreenTransform = (entropyIdx == (int)EntropyIx.SubGreen) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); - this.UsePredictorTransform = (entropyIdx == (int)EntropyIx.Spatial) || (entropyIdx == (int)EntropyIx.SpatialSubGreen); - this.UseCrossColorTransform = redAndBlueAlwaysZero ? false : this.UsePredictorTransform; - this.AllocateTransformBuffer(width, height); - // Reset any parameter in the encoder that is set in the previous iteration. - this.CacheBits = 0; - this.ClearRefs(); + // TODO : Do we want to do this multi-threaded, this will probably require a second class: + // one which co-ordinates the threading and comparison and another which does the actual encoding + foreach (CrunchConfig crunchConfig in crunchConfigs) + { + bool useCache = true; + this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; + this.UseSubtractGreenTransform = (crunchConfig.EntropyIdx == EntropyIx.SubGreen) || + (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); + this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || + (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); + this.UseCrossColorTransform = redAndBlueAlwaysZero ? false : this.UsePredictorTransform; + this.AllocateTransformBuffer(width, height); + + // Reset any parameter in the encoder that is set in the previous iteration. + this.CacheBits = 0; + this.ClearRefs(); + + // TODO: Apply near-lossless preprocessing. + + // Encode palette. + if (this.UsePalette) + { + this.EncodePalette(); + this.MapImageFromPalette(width, height); - // TODO: Apply near-lossless preprocessing. + // If using a color cache, do not have it bigger than the number of colors. + if (useCache && this.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) + { + this.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; + } + } - // Encode palette. - if (this.UsePalette) - { - this.EncodePalette(); - this.MapImageFromPalette(width, height); + // Apply transforms and write transform data. + if (this.UseSubtractGreenTransform) + { + this.ApplySubtractGreen(this.CurrentWidth, height); + } - // If using a color cache, do not have it bigger than the number of colors. - if (useCache && this.PaletteSize < (1 << WebPConstants.MaxColorCacheBits)) + if (this.UsePredictorTransform) { - this.CacheBits = WebPCommonUtils.BitsLog2Floor((uint)this.PaletteSize) + 1; + this.ApplyPredictFilter(this.CurrentWidth, height, quality, this.UseSubtractGreenTransform); } - } - // Apply transforms and write transform data. - if (this.UseSubtractGreenTransform) - { - this.ApplySubtractGreen(this.CurrentWidth, height); - } + if (this.UseCrossColorTransform) + { + this.ApplyCrossColorFilter(this.CurrentWidth, height, quality); + } - if (this.UsePredictorTransform) - { - this.ApplyPredictFilter(this.CurrentWidth, height, quality, this.UseSubtractGreenTransform); - } + this.bitWriter.PutBits(0, 1); // No more transforms. - if (this.UseCrossColorTransform) - { - this.ApplyCrossColorFilter(this.CurrentWidth, height, quality); + // Encode and write the transformed image. + this.EncodeImage(bgra, this.HashChain, this.Refs, this.CurrentWidth, height, quality, useCache, crunchConfig, + this.CacheBits, this.HistoBits, bytePosition); } - - this.bitWriter.PutBits(0, 1); // No more transforms. - - // Encode and write the transformed image. - this.EncodeImage(bgra, this.HashChain, this.Refs, this.CurrentWidth, height, quality, useCache, this.CacheBits, this.HistoBits, bytePosition); + // TODO: Comparison and picking of best (smallest) encoding } /// /// Analyzes the image and decides what transforms should be used. /// - private void EncoderAnalyze(Image image) + private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) where TPixel : unmanaged, IPixel { + var configQuality = 75; // TODO: hardcoded quality for now int method = 4; // TODO: method hardcoded to 4 for now. int width = image.Width; int height = image.Height; @@ -325,15 +334,61 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Try out multiple LZ77 on images with few colors. var nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out bool redAndBlueAlwaysZero); + EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); + + bool doNotCache = false; + var crunchConfigs = new List(); + + if (method == 6 && configQuality == 100) + { + doNotCache = true; - // TODO: Fill CrunchConfig + // Go brute force on all transforms. + foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast()) + { + // We can only apply kPalette or kPaletteAndSpatial if we can indeed use + // a palette. + if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) + { + crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx }); + } + } + } + else + { + // Only choose the guessed best transform. + crunchConfigs.Add(new CrunchConfig {EntropyIdx = entropyIdx}); + if (configQuality >= 75 && method == 5) + { + // Test with and without color cache. + doNotCache = true; + + // If we have a palette, also check in combination with spatial. + if (entropyIdx == EntropyIx.Palette) + { + crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial}); + } + } + } + + // Fill in the different LZ77s. + foreach (CrunchConfig crunchConfig in crunchConfigs) + { + for (var j = 0; j < nlz77s; ++j) + { + crunchConfig.SubConfigs.Add(new CrunchSubConfig + { + Lz77 = (j == 0) ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, + DoNotCache = doNotCache + }); + } + } + + return crunchConfigs.ToArray(); } - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition) + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) { - int lz77sTypesToTrySize = 1; // TODO: hardcoded for now. - int[] lz77sTypesToTry = { 3 }; int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); var histogramSymbols = new short[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; @@ -357,24 +412,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Calculate backward references from BGRA image. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); - Vp8LBitWriter bitWriterBest = null; - if (lz77sTypesToTrySize > 1) - { - bitWriterBest = this.bitWriter.Clone(); - } - else - { - bitWriterBest = this.bitWriter; - } + Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; - for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) + foreach (CrunchSubConfig subConfig in config.SubConfigs) { - Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, lz77sTypesToTry[lz77sIdx], ref cacheBits, hashChain, refsArray[0], refsArray[1]); - + Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, subConfig.Lz77, ref cacheBits, hashChain, refsArray[0], refsArray[1]); // TODO : Pass do not cache // Keep the best references aside and use the other element from the first // two as a temporary for later usage. Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; + // TODO : Loop based on cache/no cache + // TODO: this.bitWriter.Reset(); var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); @@ -1567,5 +1615,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Palette.Dispose(); this.TransformData.Dispose(); } + + // TODO : Not a fan of private classes + private class CrunchConfig + { + public EntropyIx EntropyIdx { get; set; } + public List SubConfigs { get; } = new List(); + } + + // TODO : Not a fan of private classes + private class CrunchSubConfig + { + public int Lz77 { get; set; } + public bool DoNotCache { get; set; } + } } + + } From 91ff2ada2e39b8cf6f9771a5fbac5907a08a91d2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jul 2020 11:32:33 +0200 Subject: [PATCH 186/359] Some bug fixes: - Distance was not copied in histogram DeepClone - Do not remove entries from the histogram: instead set them to null like the original code - Fix invalid upper bound in for loop of HistogramRemap --- .../Formats/WebP/Lossless/HistogramEncoder.cs | 62 ++++++++++++------- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 18 +++--- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 1 + 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 203f254b7..6366beadc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -26,6 +26,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const uint NonTrivialSym = 0xffffffff; + private const short InvalidHistogramSymbol = Int16.MaxValue; + public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; @@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistogramBuild(xSize, histoBits, refs, origHisto); // Copies the histograms and computes its bitCost. histogramSymbols is optimized. - HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols); + HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); if (entropyCombine) @@ -56,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistogramAnalyzeEntropyBin(imageHisto, binMap); // Collapse histograms with similar entropy. - HistogramCombineEntropyBin(imageHisto, ref numUsed, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); + HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); - OptimizeHistogramSymbols(imageHisto, clusterMappings, numClusters, mapTmp, histogramSymbols); + OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); } float x = quality / 100.0f; @@ -131,6 +133,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Analyze the dominant (literal, red and blue) entropy costs. for (int i = 0; i < histoSize; i++) { + if (histograms[i] == null) + { + continue; + } + costRange.UpdateDominantCostRange(histograms[i]); } @@ -138,40 +145,38 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // symbol costs and store the resulting bin_id for each histogram. for (int i = 0; i < histoSize; i++) { + if (histograms[i] == null) + { + continue; + } + binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions); } } - private static void HistogramCopyAndAnalyze(List origHistograms, List histograms, ref int numUsed, short[] histogramSymbols) + private static void HistogramCopyAndAnalyze(List origHistograms, List histograms, short[] histogramSymbols) { - int numUsedOrig = numUsed; - var indicesToRemove = new List(); for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) { - Vp8LHistogram histo = origHistograms[i]; - histo.UpdateHistogramCost(); + Vp8LHistogram origHistogram = origHistograms[i]; + origHistogram.UpdateHistogramCost(); - // Skip the histogram if it is completely empty, which can happen for tiles - // with no information (when they are skipped because of LZ77). - if (!histo.IsUsed[0] && !histo.IsUsed[1] && !histo.IsUsed[2] && !histo.IsUsed[3] && !histo.IsUsed[4]) + // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77). + if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4]) { - indicesToRemove.Add(i); + origHistograms[i] = null; + histograms[i] = null; + histogramSymbols[i] = InvalidHistogramSymbol; } else { - histograms[i] = (Vp8LHistogram)histo.DeepClone(); + histograms[i] = (Vp8LHistogram)origHistogram.DeepClone(); histogramSymbols[i] = (short)clusterId++; } } - - foreach (int index in indicesToRemove.OrderByDescending(v => v)) - { - origHistograms.RemoveAt(index); - histograms.RemoveAt(index); - } } - private static void HistogramCombineEntropyBin(List histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) + private static void HistogramCombineEntropyBin(List histograms, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) { var binInfo = new HistogramBinInfo[BinSize]; for (int idx = 0; idx < numBins; idx++) @@ -245,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. ///
- private static void OptimizeHistogramSymbols(List histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) + private static void OptimizeHistogramSymbols(short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) { bool doContinue = true; @@ -278,6 +283,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Re-map the ids. for (int i = 0; i < symbols.Length; i++) { + if (symbols[i] == InvalidHistogramSymbol) + { + continue; + } + int cluster = clusterMappings[symbols[i]]; if (cluster > 0 && clusterMappingsTmp[cluster] == 0) { @@ -466,7 +476,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless histograms[idx1].BitCost = histoPriorityList[0].CostCombo; // Remove merged histogram. - // TODO: can the element be removed instead? histograms.RemoveAt(idx2); histograms[idx2] = null; // Remove pairs intersecting the just combined best pair. @@ -501,12 +510,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void HistogramRemap(List input, List output, short[] symbols) { - int inSize = symbols.Length; + int inSize = input.Count; int outSize = output.Count; if (outSize > 1) { for (int i = 0; i < inSize; i++) { + if (input[i] == null) + { + // Arbitrarily set to the previous value if unused to help future LZ77. + symbols[i] = symbols[i - 1]; + continue; + } + int bestOut = 0; double bestBits = double.MaxValue; for (int k = 0; k < outSize; k++) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 61eb58930..2e48bf7dc 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -417,12 +418,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless foreach (CrunchSubConfig subConfig in config.SubConfigs) { Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, subConfig.Lz77, ref cacheBits, hashChain, refsArray[0], refsArray[1]); // TODO : Pass do not cache + // Keep the best references aside and use the other element from the first // two as a temporary for later usage. Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; // TODO : Loop based on cache/no cache - + // TODO: this.bitWriter.Reset(); var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); @@ -474,7 +476,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - histogramImageSize = maxIndex; this.bitWriter.PutBits((uint)(histogramBits - 2), 3); this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); } @@ -482,7 +483,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Store Huffman codes. // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5 * histogramImageSize; i++) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -497,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless tokens[i] = new HuffmanTreeToken(); } - for (int i = 0; i < 5 * histogramImageSize; i++) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -912,7 +913,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return EntropyIx.Palette; } - using System.Buffers.IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); Span histo = histoBuffer.Memory.Span; Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. Span prevRow = null; @@ -955,7 +956,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; } - var histo0 = histo[0]; prevRow = currentRow; } @@ -1149,7 +1149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ///
private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) { - using System.Buffers.IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); + using IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); Span tmpRow = tmpRowBuffer.GetSpan(); if (paletteSize < ApplyPaletteGreedyMax) @@ -1209,8 +1209,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - uint[] idxMap = new uint[paletteSize]; - uint[] paletteSorted = new uint[paletteSize]; + var idxMap = new uint[paletteSize]; + var paletteSorted = new uint[paletteSize]; PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 9b591dcd0..6fe3496a4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless other.Blue.AsSpan().CopyTo(this.Blue); other.Alpha.AsSpan().CopyTo(this.Alpha); other.Literal.AsSpan().CopyTo(this.Literal); + other.Distance.AsSpan().CopyTo(this.Distance); other.IsUsed.AsSpan().CopyTo(this.IsUsed); this.LiteralCost = other.LiteralCost; this.RedCost = other.RedCost; From eb7a9089f884c4a943418a6744270fbc12f34f5f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jul 2020 12:36:44 +0200 Subject: [PATCH 187/359] Add missing null checks --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 29 +++++++++++------ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 32 ++++++++++++------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index cd6249632..73f808b63 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } /// - /// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this. + /// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this. /// /// The stream to write to. public void WriteToStream(Stream stream) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 6366beadc..1893e6015 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const uint NonTrivialSym = 0xffffffff; - private const short InvalidHistogramSymbol = Int16.MaxValue; + private const short InvalidHistogramSymbol = short.MaxValue; public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) { @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Cubic ramp between 1 and MaxHistoGreedy: int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); - bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize); + bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); if (doGreedy) { HistogramCombineGreedy(imageHisto); @@ -303,14 +303,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Perform histogram aggregation using a stochastic approach. ///
/// true if a greedy approach needs to be performed afterwards, false otherwise. - private static bool HistogramCombineStochastic(List histograms, ref int numUsed, int minClusterSize) + private static bool HistogramCombineStochastic(List histograms, int minClusterSize) { var rand = new Random(); int triesWithNoSuccess = 0; + var numUsed = histograms.Count(h => h != null); int outerIters = numUsed; int numTriesNoSuccess = outerIters / 2; - if (histograms.Count < minClusterSize) + if (numUsed < minClusterSize) { return true; } @@ -333,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; int bestIdx1 = -1; int bestIdx2 = 1; - int numTries = numUsed / 2; // TODO: should that be histogram.Count/2? + int numTries = numUsed / 2; uint randRange = (uint)((numUsed - 1) * numUsed); // Pick random samples. @@ -454,16 +455,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void HistogramCombineGreedy(List histograms) { - int histoSize = histograms.Count; + int histoSize = histograms.Count(h => h != null); // Priority list of histogram pairs. var histoPriorityList = new List(); int maxSize = histoSize * histoSize; - for (int i = 0; i < histograms.Count; i++) + for (int i = 0; i < histoSize; i++) { - for (int j = i + 1; j < histograms.Count; j++) + if (histograms[i] == null) + { + continue; + } + + for (int j = i + 1; j < histoSize; j++) { + if (histograms[j] == null) + { + continue; + } + HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d); } } @@ -496,7 +507,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Push new pairs formed with combined histogram to the list. - for (int i = 0; i < histograms.Count; i++) + for (int i = 0; i < histoSize; i++) { if (i == idx1 || histograms[i] == null) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 2e48bf7dc..f2f117589 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -7,7 +7,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; @@ -34,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// The to use for buffer allocations. /// - private MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; /// /// A bit writer for writing lossless webp streams. /// private Vp8LBitWriter bitWriter; - + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -309,9 +308,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits(0, 1); // No more transforms. // Encode and write the transformed image. - this.EncodeImage(bgra, this.HashChain, this.Refs, this.CurrentWidth, height, quality, useCache, crunchConfig, - this.CacheBits, this.HistoBits, bytePosition); + this.EncodeImage( + bgra, + this.HashChain, + this.Refs, + this.CurrentWidth, + height, + quality, + useCache, + crunchConfig, + this.CacheBits, + this.HistoBits, + bytePosition); } + // TODO: Comparison and picking of best (smallest) encoding } @@ -336,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Try out multiple LZ77 on images with few colors. var nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); - + bool doNotCache = false; var crunchConfigs = new List(); @@ -358,7 +368,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless else { // Only choose the guessed best transform. - crunchConfigs.Add(new CrunchConfig {EntropyIdx = entropyIdx}); + crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); if (configQuality >= 75 && method == 5) { // Test with and without color cache. @@ -367,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // If we have a palette, also check in combination with spatial. if (entropyIdx == EntropyIx.Palette) { - crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial}); + crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial }); } } } @@ -1584,8 +1594,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public void AllocateTransformBuffer(int width, int height) { - int imageSize = width * height; - // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra // pixel in each, plus 2 regular scanlines of bytes. int argbScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; @@ -1620,6 +1628,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private class CrunchConfig { public EntropyIx EntropyIdx { get; set; } + public List SubConfigs { get; } = new List(); } @@ -1627,9 +1636,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private class CrunchSubConfig { public int Lz77 { get; set; } + public bool DoNotCache { get; set; } } } - - } From dd3aa98641ed92b4456240e3c1f640bf070000d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jul 2020 13:08:03 +0200 Subject: [PATCH 188/359] Fix build issues after merging the master branch --- src/ImageSharp/Formats/WebP/WebPDecoder.cs | 37 ++++++++- .../Formats/WebP/WebPDecoderCore.cs | 77 ++++++++++--------- .../Formats/WebP/WebPEncoderCore.cs | 1 + 3 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs index 6e905173b..f15b6c7af 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs @@ -3,6 +3,9 @@ using System.IO; using System.Threading.Tasks; + +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP @@ -23,7 +26,19 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.NotNull(stream, nameof(stream)); - return new WebPDecoderCore(configuration, this).Decode(stream); + var decoder = new WebPDecoderCore(configuration, this); + + try + { + using var bufferedStream = new BufferedReadStream(configuration, stream); + return decoder.Decode(bufferedStream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); + } } /// @@ -31,7 +46,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.NotNull(stream, nameof(stream)); - return new WebPDecoderCore(configuration, this).Identify(stream); + using var bufferedStream = new BufferedReadStream(configuration, stream); + return new WebPDecoderCore(configuration, this).Identify(bufferedStream); } /// @@ -43,7 +59,19 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.NotNull(stream, nameof(stream)); - return new WebPDecoderCore(configuration, this).DecodeAsync(stream); + var decoder = new WebPDecoderCore(configuration, this); + + try + { + using var bufferedStream = new BufferedReadStream(configuration, stream); + return decoder.DecodeAsync(bufferedStream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); + } } /// @@ -55,7 +83,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.NotNull(stream, nameof(stream)); - return new WebPDecoderCore(configuration, this).IdentifyAsync(stream); + using var bufferedStream = new BufferedReadStream(configuration, stream); + return new WebPDecoderCore(configuration, this).IdentifyAsync(bufferedStream); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 8b0a32fab..cb94f49b0 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -8,6 +8,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.WebP.BitReader; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Formats.WebP.Lossy; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -41,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
private WebPMetadata webpMetadata; + /// + /// Information about the webp image. + /// + private WebPImageInfo webImageInfo; + /// /// Initializes a new instance of the class. /// @@ -67,58 +73,59 @@ namespace SixLabors.ImageSharp.Formats.WebP public Configuration Configuration { get; } /// - /// Decodes the image from the specified and sets the data to the image. + /// Gets the dimensions of the image. /// - /// The pixel format. - /// The stream, where the image should be. - /// The decoded image. - public Image Decode(Stream stream) + public Size Dimensions => new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + + /// + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { this.Metadata = new ImageMetadata(); this.currentStream = stream; this.ReadImageHeader(); - using WebPImageInfo imageInfo = this.ReadVp8Info(); - if (imageInfo.Features != null && imageInfo.Features.Animation) - { - WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); - } - var image = new Image(this.Configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (imageInfo.IsLossless) - { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); - losslessDecoder.Decode(pixels, image.Width, image.Height); - } - else + using (this.webImageInfo = this.ReadVp8Info()) { - var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); - lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo); - } + if (this.webImageInfo.Features != null && this.webImageInfo.Features.Animation) + { + WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); + } - // There can be optional chunks after the image data, like EXIF and XMP. - if (imageInfo.Features != null) - { - this.ParseOptionalChunks(imageInfo.Features); - } + var image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (this.webImageInfo.IsLossless) + { + var losslessDecoder = new WebPLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); + losslessDecoder.Decode(pixels, image.Width, image.Height); + } + else + { + var lossyDecoder = new WebPLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); + } + + // There can be optional chunks after the image data, like EXIF and XMP. + if (this.webImageInfo.Features != null) + { + this.ParseOptionalChunks(this.webImageInfo.Features); + } - return image; + return image; + } } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) + /// + public IImageInfo Identify(BufferedReadStream stream) { this.currentStream = stream; this.ReadImageHeader(); - WebPImageInfo imageInfo = this.ReadVp8Info(); - - return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); + using (this.webImageInfo = this.ReadVp8Info()) + { + return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); + } } /// diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 2d37b802a..f99c524ba 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Formats.WebP.Lossy; From 8d08d40c2e37ffc25ccca583c5ba9e24c1ffce58 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 28 Jul 2020 16:55:56 +0200 Subject: [PATCH 189/359] Fix some issues in the CostManager --- .../WebP/Lossless/CostCacheInterval.cs | 2 +- .../Formats/WebP/Lossless/CostInterval.cs | 20 +++ .../Formats/WebP/Lossless/CostManager.cs | 136 ++++++++++++++---- .../Formats/WebP/Lossless/PredictorEncoder.cs | 2 +- 4 files changed, 128 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs index 47f85b738..457e24cbe 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. /// - [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] + [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}, Position: {Position}")] internal class CostCacheInterval { public double Cost { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs index 16f2edc15..75afac6f2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs @@ -5,6 +5,22 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { + /// + /// To perform backward reference every pixel at index index_ is considered and + /// the cost for the MAX_LENGTH following pixels computed. Those following pixels + /// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of: + /// cost = distance cost at index + GetLengthCost(costModel, k) + /// and the minimum value is kept. GetLengthCost(costModel, k) is cached in an + /// array of size MAX_LENGTH. + /// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the + /// minimal values using intervals of constant cost. + /// An interval is defined by the index_ of the pixel that generated it and + /// is only useful in a range of indices from start to end (exclusive), i.e. + /// it contains the minimum value for pixels between start and end. + /// Intervals are stored in a linked list and ordered by start. When a new + /// interval has a better value, old intervals are split or removed. There are + /// therefore no overlapping intervals. + /// [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] internal class CostInterval { @@ -15,5 +31,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public int End { get; set; } public int Index { get; set; } + + public CostInterval Previous { get; set; } + + public CostInterval Next { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index f648ae75c..279aa6f51 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// The CostManager is in charge of managing intervals and costs. /// It caches the different CostCacheInterval, caches the different - /// GetLengthCost(cost_model, k) in cost_cache_ and the CostInterval's. + /// GetLengthCost(costModel, k) in cost_cache_ and the CostInterval's. /// internal class CostManager { @@ -70,10 +70,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + private CostInterval head; + /// - /// Gets the number of stored intervals. + /// Gets or sets the number of stored intervals. /// - public int Count { get; } + public int Count { get; set; } /// /// Gets the costs cache. Contains the GetLengthCost(costModel, k). @@ -97,34 +99,32 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. public void UpdateCostAtIndex(int i, bool doCleanIntervals) { - var indicesToRemove = new List(); + CostInterval current = this.head; using List.Enumerator intervalEnumerator = this.Intervals.GetEnumerator(); - while (intervalEnumerator.MoveNext() && intervalEnumerator.Current.Start <= i) + while (current != null && current.Start <= i) { - if (intervalEnumerator.Current.End <= i) + CostInterval next = current.Next; + if (current.End <= i) { if (doCleanIntervals) { // We have an outdated interval, remove it. - indicesToRemove.Add(i); + this.PopInterval(current); } } else { - this.UpdateCost(i, intervalEnumerator.Current.Index, intervalEnumerator.Current.Cost); + this.UpdateCost(i, current.Index, current.Cost); } - } - foreach (int index in indicesToRemove.OrderByDescending(i => i)) - { - this.Intervals.RemoveAt(index); + current = next; } } /// /// Given a new cost interval defined by its start at position, its length value /// and distanceCost, add its contributions to the previous intervals and costs. - /// If handling the interval or one of its subintervals becomes to heavy, its + /// If handling the interval or one of its sub-intervals becomes to heavy, its /// contribution is added to the costs right away. /// public void PushInterval(double distanceCost, int position, int len) @@ -150,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return; } + CostInterval interval = this.head; for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) { // Define the intersection of the ith interval with the new one. @@ -158,10 +159,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); var idx = i; - CostCacheInterval interval = this.CacheIntervals[idx]; - var indicesToRemove = new List(); - for (; interval.Start < end; idx++) + CostInterval intervalNext; + for (; interval != null && interval.Start < end; interval = intervalNext) { + intervalNext = interval.Next; + // Make sure we have some overlap. if (start >= interval.End) { @@ -170,8 +172,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (cost >= interval.Cost) { + // If we are worse than what we already have, add whatever we have so far up to interval. int startNew = interval.End; - this.InsertInterval(cost, position, start, interval.Start); + this.InsertInterval(interval, cost, position, start, interval.Start); start = startNew; if (start >= end) { @@ -185,7 +188,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (interval.End <= end) { - indicesToRemove.Add(idx); + // We can safely remove the old interval as it is fully included. + this.PopInterval(interval); } else { @@ -197,9 +201,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (end < interval.End) { + // We have to split the old interval as it fully contains the new one. int endOriginal = interval.End; interval.End = start; - this.InsertInterval(interval.Cost, idx, end, endOriginal); + this.InsertInterval(interval, interval.Cost, idx, end, endOriginal); break; } else @@ -209,27 +214,98 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - foreach (int indice in indicesToRemove.OrderByDescending(i => i)) - { - this.Intervals.RemoveAt(indice); - } - // Insert the remaining interval from start to end. - this.InsertInterval(cost, position, start, end); + this.InsertInterval(interval, cost, position, start, end); + } + } + + /// + /// Pop an interval from the manager. + /// + /// The interval to remove. + private void PopInterval(CostInterval interval) + { + if (interval == null) + { + return; } + + ConnectIntervals(interval.Previous, interval.Next); + this.Count--; } - private void InsertInterval(double cost, int position, int start, int end) + private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) { - // TODO: use COST_CACHE_INTERVAL_SIZE_MAX - var interval = new CostCacheInterval() + if (start >= end) + { + return; + } + + // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? + var intervalNew = new CostInterval() { Cost = cost, Start = start, - End = end + End = end, + Index = position }; - this.CacheIntervals.Insert(position, interval); + this.PositionOrphanInterval(intervalNew, intervalIn); + this.Count++; + } + + /// + /// Given a current orphan interval and its previous interval, before + /// it was orphaned (which can be NULL), set it at the right place in the list + /// of intervals using the start_ ordering and the previous interval as a hint. + /// + private void PositionOrphanInterval(CostInterval current, CostInterval previous) + { + if (previous == null) + { + previous = this.head; + } + + while (previous != null && current.Start < previous.Start) + { + previous = previous.Previous; + } + + while (previous != null && previous.Next != null && previous.Next.Start < current.Start) + { + previous = previous.Next; + } + + if (previous != null) + { + this.ConnectIntervals(current, previous.Next); + } + else + { + this.ConnectIntervals(current, this.head); + } + + this.ConnectIntervals(previous, current); + } + + /// + /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. + /// + private void ConnectIntervals(CostInterval prev, CostInterval next) + { + if (prev != null) + { + prev.Next = next; + } + else + { + this.head = next; + } + + if (next != null) + { + next.Previous = prev; + } } /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index fcbac5267..bc2c7b594 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless for (int x = 1; x < width - 1; x++) { - uint up = argb[-stride + x]; // TODO: -stride! + uint up = argb[-stride + x]; uint down = argb[stride + x]; uint left = current; current = right; From d6aa3f6c9c1624e3add3146f7e2ee1d989fab25a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 30 Jul 2020 20:08:16 +0200 Subject: [PATCH 190/359] Fix issues in HistogramCombineStochastic (still needs another review) --- .../Formats/WebP/Lossless/CostManager.cs | 11 +++---- .../Formats/WebP/Lossless/HistogramEncoder.cs | 32 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index 279aa6f51..d912e2e28 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -2,17 +2,18 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; -using System.Linq; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// The CostManager is in charge of managing intervals and costs. /// It caches the different CostCacheInterval, caches the different - /// GetLengthCost(costModel, k) in cost_cache_ and the CostInterval's. + /// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// internal class CostManager { + private CostInterval head; + public CostManager(short[] distArray, int pixCount, CostModel costModel) { int costCacheSize = (pixCount > BackwardReferenceEncoder.MaxLength) ? BackwardReferenceEncoder.MaxLength : pixCount; @@ -63,15 +64,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless cur.End = i + 1; } - // Set the initial costs_ high for every pixel as we will keep the minimum. + // Set the initial costs high for every pixel as we will keep the minimum. for (int i = 0; i < pixCount; i++) { this.Costs[i] = 1e38f; } } - private CostInterval head; - /// /// Gets or sets the number of stored intervals. /// @@ -230,7 +229,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return; } - ConnectIntervals(interval.Previous, interval.Next); + this.ConnectIntervals(interval.Previous, interval.Next); this.Count--; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 1893e6015..324a2848b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -48,6 +48,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Copies the histograms and computes its bitCost. histogramSymbols is optimized. HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); + numUsed = imageHisto.Count(h => h != null); var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); if (entropyCombine) @@ -319,12 +320,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Priority queue of histogram pairs. Its size impacts the quality of the compression and the speed: // the smaller the faster but the worse for the compression. var histoPriorityList = new List(); - int histoQueueMaxSize = histograms.Count * histograms.Count; + int maxSize = 9; // Fill the initial mapping. var mappings = new int[histograms.Count]; for (int j = 0, iter = 0; iter < histograms.Count; iter++) { + if (histograms[iter] == null) + { + continue; + } + mappings[j++] = iter; } @@ -353,15 +359,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless idx2 = mappings[idx2]; // Calculate cost reduction on combination. - var currCost = HistoPriorityListPush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); + var currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost); // Found a better pair? if (currCost < 0) { bestCost = currCost; - // Empty the queue if we reached full capacity. - if (histoPriorityList.Count == histoQueueMaxSize) + if (histoPriorityList.Count == maxSize) { break; } @@ -377,10 +382,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless bestIdx1 = histoPriorityList[0].Idx1; bestIdx2 = histoPriorityList[0].Idx2; + // TODO: Review this again, not sure why this is needed in the reference implementation. // Pop bestIdx2 from mappings. - var mappingIndex = Array.BinarySearch(mappings, bestIdx2); - - // TODO: memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1)); + // var mappingIndex = Array.BinarySearch(mappings, bestIdx2); + // memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1)); // Merge the histograms and remove bestIdx2 from the queue. HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); @@ -388,8 +393,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless histograms.RemoveAt(bestIdx2); numUsed--; - var indicesToRemove = new List(); - int lastIndex = histoPriorityList.Count - 1; for (int j = 0; j < histoPriorityList.Count;) { HistogramPair p = histoPriorityList.ElementAt(j); @@ -401,9 +404,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // check for it all the time nevertheless. if (isIdx1Best && isIdx2Best) { - indicesToRemove.Add(lastIndex); + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); numUsed--; - lastIndex--; continue; } @@ -434,8 +436,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); if (p.CostDiff >= 0.0d) { - indicesToRemove.Add(lastIndex); - lastIndex--; + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); numUsed--; continue; } @@ -578,10 +579,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// Create a pair from indices "idx1" and "idx2" provided its cost - /// is inferior to "threshold", a negative entropy. + /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. /// - /// The cost of the pair, or 0. if it superior to threshold. + /// The cost of the pair, or 0 if it superior to threshold. private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold) { var pair = new HistogramPair(); From f5d7d2c71af3e70ca373864010aee981d6e6a1ea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 12 Aug 2020 11:21:58 +0200 Subject: [PATCH 191/359] Remove empty histograms --- src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 324a2848b..a1ca4108a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -68,9 +68,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Cubic ramp between 1 and MaxHistoGreedy: int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); + RemoveEmptyHistograms(imageHisto); bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); if (doGreedy) { + RemoveEmptyHistograms(imageHisto); HistogramCombineGreedy(imageHisto); } From 10470b42fb9417f52af8a0f16916ffd341c52f62 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 12 Aug 2020 11:26:01 +0200 Subject: [PATCH 192/359] Apply palette --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 82 +++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index f2f117589..1f72f3807 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -1164,18 +1164,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (paletteSize < ApplyPaletteGreedyMax) { - // TODO: APPLY_PALETTE_FOR(SearchColorGreedy(palette, palette_size, pix)); + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint pix = src[x]; + if (pix != prevPix) + { + prevIdx = SearchColorGreedy(palette, pix); + prevPix = pix; + } + + tmpRow[x] = (byte)prevIdx; + } + + BundleColorMap(tmpRow, width, xBits, dst); + src = src.Slice(srcStride); + dst = dst.Slice(dstStride); + } } else { - uint[] buffer = new uint[PaletteInvSize]; + var buffer = new uint[PaletteInvSize]; // Try to find a perfect hash function able to go from a color to an index // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. int i; for (i = 0; i < 3; i++) { - bool useLUT = true; + bool useLut = true; // Set each element in buffer to max value. buffer.AsSpan().Fill(uint.MaxValue); @@ -1198,7 +1217,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (buffer[ind] != uint.MaxValue) { - useLUT = false; + useLut = false; break; } else @@ -1207,7 +1226,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - if (useLUT) + if (useLut) { break; } @@ -1513,6 +1532,38 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; } + /// + /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. + /// + private static void BundleColorMap(Span row, int width, int xBits, Span dst) + { + int x; + if (xBits > 0) + { + int bitDepth = 1 << (3 - xBits); + int mask = (1 << xBits) - 1; + uint code = 0xff000000; + for (x = 0; x < width; ++x) + { + int xSub = x & mask; + if (xSub == 0) + { + code = 0xff000000; + } + + code |= (uint)(row[x] << (8 + (bitDepth * xSub))); + dst[x >> xBits] = code; + } + } + else + { + for (x = 0; x < width; ++x) + { + dst[x] = (uint)(0xff000000 | (row[x] << 8)); + } + } + } + /// /// Calculates the bits used for the transformation. /// @@ -1551,6 +1602,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless b[(int)((p >> 0) - green) & 0xff]++; } + [MethodImpl(InliningOptions.ShortMethod)] + private static uint SearchColorGreedy(Span palette, uint color) + { + if (color == palette[0]) + { + return 0; + } + + if (color == palette[1]) + { + return 1; + } + + if (color == palette[2]) + { + return 2; + } + + return 3; + } + [MethodImpl(InliningOptions.ShortMethod)] private static uint ApplyPaletteHash0(uint color) { From 8538db75e414f494642057e161571fc14c8348b3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Aug 2020 16:57:48 +0200 Subject: [PATCH 193/359] Fix some issues: - Fix wrong palette code bits init in CalculateBestCacheSize - Fix wrong slice start index in PrepareMapToPalette - PixOrCopy: Change len from short to ushort --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 26 +++++++-------- .../Formats/WebP/Lossless/HuffmanUtils.cs | 3 +- .../Formats/WebP/Lossless/PixOrCopy.cs | 6 ++-- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 33 ++++++++++++------- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index d50937258..a0ade54fa 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -230,12 +230,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// Evaluates best possible backward references for specified quality. - /// The input cacheBits to 'GetBackwardReferences' sets the maximum cache - /// bits to use (passing 0 implies disabling the local color cache). + /// Evaluates best possible backward references for specified quality. The input cacheBits to 'GetBackwardReferences' + /// sets the maximum cache bits to use (passing 0 implies disabling the local color cache). /// The optimal cache bits is evaluated and set for the cacheBits parameter. - /// The return value is the pointer to the best of the two backward refs viz, - /// refs[0] or refs[1]. + /// The return value is the pointer to the best of the two backward refs viz, refs[0] or refs[1]. /// public static Vp8LBackwardRefs GetBackwardReferences( int width, @@ -335,9 +333,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int pos = 0; var colorCache = new ColorCache[WebPConstants.MaxColorCacheBits + 1]; var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1]; - for (int i = 0; i < WebPConstants.MaxColorCacheBits + 1; i++) + for (int i = 0; i <= WebPConstants.MaxColorCacheBits; i++) { - histos[i] = new Vp8LHistogram(bestCacheBits); + histos[i] = new Vp8LHistogram(paletteCodeBits: i); colorCache[i] = new ColorCache(); colorCache[i].Init(i); } @@ -369,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (colorCache[i].Lookup(key) == pix) { - ++histos[i].Literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key]; + ++histos[i].Literal[WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + key]; } else { @@ -563,7 +561,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (len != 1) { int offset = hashChain.FindOffset(i); - backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len)); + backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); if (useColorCache) { @@ -689,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - refs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len)); + refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); if (useColorCache) { for (j = i; j < i + len; ++j) @@ -908,7 +906,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int prevRowLen = (i < xSize) ? 0 : FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); if (rleLen >= prevRowLen && rleLen >= MinLength) { - refs.Add(PixOrCopy.CreateCopy(1, (short)rleLen)); + refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); // We don't need to update the color cache here since it is always the // same pixel being copied, and that does not change the color cache state. @@ -916,7 +914,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else if (prevRowLen >= MinLength) { - refs.Add(PixOrCopy.CreateCopy((uint)xSize, (short)prevRowLen)); + refs.Add(PixOrCopy.CreateCopy((uint)xSize, (ushort)prevRowLen)); if (useColorCache) { for (int k = 0; k < prevRowLen; k++) @@ -936,7 +934,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (useColorCache) { - // TODO: VP8LColorCacheClear(); + // TODO: VP8LColorCacheClear()? } } @@ -978,7 +976,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - // TODO: VP8LColorCacheClear(colorCache); + // TODO: VP8LColorCacheClear(colorCache)? } private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index db6b2e8f9..414c607ad 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -279,13 +279,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { int value = tree.CodeLengths[i]; int k = i + 1; - int runs; while (k < depthSize && tree.CodeLengths[k] == value) { k++; } - runs = k - i; + var runs = k - i; if (value == 0) { tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index b8378d6cd..8974092e6 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { public PixOrCopyMode Mode { get; set; } - public short Len { get; set; } + public ushort Len { get; set; } public uint BgraOrDistance { get; set; } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return retval; } - public static PixOrCopy CreateCopy(uint distance, short len) + public static PixOrCopy CreateCopy(uint distance, ushort len) { var retval = new PixOrCopy() { @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return this.BgraOrDistance; } - public short Length() + public ushort Length() { return this.Len; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 1f72f3807..9df6591f3 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -427,7 +427,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless foreach (CrunchSubConfig subConfig in config.SubConfigs) { - Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, subConfig.Lz77, ref cacheBits, hashChain, refsArray[0], refsArray[1]); // TODO : Pass do not cache + Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( + width, + height, + bgra, + quality, + subConfig.Lz77, + ref cacheBits, + hashChain, + refsArray[0], + refsArray[1]); // TODO : Pass do not cache // Keep the best references aside and use the other element from the first // two as a temporary for later usage. @@ -473,13 +482,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); if (writeHistogramImage) { - using IMemoryOwner histogramArgbBuffer = this.memoryAllocator.Allocate(histogramImageXySize); - Span histogramArgb = histogramArgbBuffer.GetSpan(); + using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); + Span histogramBgra = histogramBgraBuffer.GetSpan(); int maxIndex = 0; for (int i = 0; i < histogramImageXySize; i++) { int symbolIndex = histogramSymbols[i] & 0xffff; - histogramArgb[i] = (uint)(symbolIndex << 8); + histogramBgra[i] = (uint)(symbolIndex << 8); if (symbolIndex >= maxIndex) { maxIndex = symbolIndex + 1; @@ -487,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } this.bitWriter.PutBits((uint)(histogramBits - 2), 3); - this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); + this.EncodeImageNoHuffman(histogramBgra, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); } // Store Huffman codes. @@ -690,7 +699,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (count == 0) { - // emit minimal tree for empty cases + // Emit minimal tree for empty cases. // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 this.bitWriter.PutBits(0x01, 4); } @@ -725,10 +734,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int i; var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes]; var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes]; - var huffmanCode = new HuffmanTreeCode(); - huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; - huffmanCode.CodeLengths = codeLengthBitDepth; - huffmanCode.Codes = codeLengthBitDepthSymbols; + var huffmanCode = new HuffmanTreeCode + { + NumSymbols = WebPConstants.CodeLengthCodes, + CodeLengths = codeLengthBitDepth, + Codes = codeLengthBitDepthSymbols + }; this.bitWriter.PutBits(0, 1); var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); @@ -1313,7 +1324,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) { - palette.Slice(numColors).CopyTo(sorted); + palette.Slice(0, numColors).CopyTo(sorted); Array.Sort(sorted, PaletteCompareColorsForSort); for (int i = 0; i < numColors; i++) { diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 6fe3496a4..78420185b 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1]; this.Distance = new uint[WebPConstants.NumDistanceCodes]; - var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + (1 << WebPConstants.MaxColorCacheBits); this.Literal = new uint[literalSize]; // 5 for literal, red, blue, alpha, distance. From 2e17ce1619920738d4d624b3e5bd8e11f047000b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 19 Aug 2020 23:38:48 +0100 Subject: [PATCH 194/359] So I broke the inverse predictor --- .../Formats/WebP/Lossless/LosslessUtils.cs | 477 +++++++++--------- .../Formats/WebP/Lossless/PredictorEncoder.cs | 392 ++++++++------ 2 files changed, 464 insertions(+), 405 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index eac88609c..ac6990665 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Utility functions for the lossless decoder. /// - internal static class LosslessUtils + internal static unsafe class LosslessUtils { private const uint Predictor0 = WebPConstants.ArgbBlack; @@ -244,104 +244,117 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// The transform data. /// The pixel data to apply the inverse transform. - /// The resulting pixel data with the reversed transformation data. - public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) - { - int processedPixels = 0; - int width = transform.XSize; - Span transformData = transform.Data.GetSpan(); - - // First Row follows the L (mode=1) mode. - PredictorAdd0(pixelData, processedPixels, 1, output); - PredictorAdd1(pixelData, 1, width - 1, output); - processedPixels += width; - - int y = 1; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; - while (y < yEnd) + /// The resulting pixel data with the reversed transformation data. + public static void PredictorInverseTransform( + Vp8LTransform transform, + Span pixelData, + Span outputSpan) + { + fixed (uint* inputFixed = pixelData) + fixed (uint* outputFixed = outputSpan) { - int predictorModeIdx = predictorModeIdxBase; - int x = 1; + uint* input = inputFixed; + uint* output = outputFixed; + + int width = transform.XSize; + Span transformData = transform.Data.GetSpan(); + + // First Row follows the L (mode=1) mode. + PredictorAdd0(input, null, 1, output); + PredictorAdd1(input + 1, null, width - 1, output); + input += width; + output += width; + + int y = 1; + int yEnd = transform.YSize; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int tilesPerRow = SubSampleSize(width, transform.Bits); + int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; + while (y < yEnd) + { + int predictorModeIdx = predictorModeIdxBase; + int x = 1; - // First pixel follows the T (mode=2) mode. - PredictorAdd2(pixelData, processedPixels, 1, width, output); + // First pixel follows the T (mode=2) mode. + PredictorAdd2(input, output - width, 1, output); - while (x < width) - { - uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; - int xEnd = (x & ~mask) + tileWidth; - if (xEnd > width) + // .. the rest: + while (x < width) { - xEnd = width; - } + uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; + int xEnd = (x & ~mask) + tileWidth; + if (xEnd > width) + { + xEnd = width; + } - // There are 14 different prediction modes. - // In each prediction mode, the current pixel value is predicted from one or more neighboring pixels whose values are already known. - int startIdx = processedPixels + x; - int numberOfPixels = xEnd - x; - switch (predictorMode) - { - case 0: - PredictorAdd0(pixelData, startIdx, numberOfPixels, output); - break; - case 1: - PredictorAdd1(pixelData, startIdx, numberOfPixels, output); - break; - case 2: - PredictorAdd2(pixelData, startIdx, numberOfPixels, width, output); - break; - case 3: - PredictorAdd3(pixelData, startIdx, numberOfPixels, width, output); - break; - case 4: - PredictorAdd4(pixelData, startIdx, numberOfPixels, width, output); - break; - case 5: - PredictorAdd5(pixelData, startIdx, numberOfPixels, width, output); - break; - case 6: - PredictorAdd6(pixelData, startIdx, numberOfPixels, width, output); - break; - case 7: - PredictorAdd7(pixelData, startIdx, numberOfPixels, width, output); - break; - case 8: - PredictorAdd8(pixelData, startIdx, numberOfPixels, width, output); - break; - case 9: - PredictorAdd9(pixelData, startIdx, numberOfPixels, width, output); - break; - case 10: - PredictorAdd10(pixelData, startIdx, numberOfPixels, width, output); - break; - case 11: - PredictorAdd11(pixelData, startIdx, numberOfPixels, width, output); - break; - case 12: - PredictorAdd12(pixelData, startIdx, numberOfPixels, width, output); - break; - case 13: - PredictorAdd13(pixelData, startIdx, numberOfPixels, width, output); - break; + // There are 14 different prediction modes. + // In each prediction mode, the current pixel value is predicted from one + // or more neighboring pixels whose values are already known. + switch (predictorMode) + { + case 0: + PredictorAdd0(input + x, output + x - width, xEnd - x, output + x); + break; + case 1: + PredictorAdd1(input + x, output + x - width, xEnd - x, output + x); + break; + case 2: + PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); + break; + case 3: + PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); + break; + case 4: + PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); + break; + case 5: + PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); + break; + case 6: + PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); + break; + case 7: + PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); + break; + case 8: + PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); + break; + case 9: + PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); + break; + case 10: + PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); + break; + case 11: + PredictorAdd11(input + x, output + x - width, xEnd - x, output + x); + break; + case 12: + PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); + break; + case 13: + PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); + break; + } + + x = xEnd; } - x = xEnd; - } + input += width; + output += width; + ++y; - processedPixels += width; - ++y; - if ((y & mask) == 0) - { - // Use the same mask, since tiles are squares. - predictorModeIdxBase += tilesPerRow; + if ((y & mask) == 0) + { + // Use the same mask, since tiles are squares. + predictorModeIdxBase += tilesPerRow; + } } } - output.CopyTo(pixelData); + // TODO: I can't see the equivalent code in the source? + outputSpan.CopyTo(pixelData); } public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) @@ -582,374 +595,352 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return code; } - private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd0(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - uint pred = Predictor0; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - output[x] = AddPixels(input[x], pred); + output[x] = AddPixels(input[x], WebPConstants.ArgbBlack); } } - private static void PredictorAdd1(Span input, int startIdx, int numberOfPixels, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd1(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - uint left = output[startIdx - 1]; - for (int x = startIdx; x < endIdx; ++x) + uint left = output[-1]; + for (int x = 0; x < numberOfPixels; ++x) { output[x] = left = AddPixels(input[x], left); } } - private static void PredictorAdd2(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd2(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor2(output, offset++); + uint pred = Predictor2(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd3(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd3(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor3(output, offset++); + uint pred = Predictor3(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd4(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd4(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor4(output, offset++); + uint pred = Predictor4(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd5(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd5(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor5(output[x - 1], output, offset++); + uint pred = Predictor5(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd6(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd6(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor6(output[x - 1], output, offset++); + uint pred = Predictor6(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd7(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd7(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor7(output[x - 1], output, offset++); + uint pred = Predictor7(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd8(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd8(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor8(output, offset++); + uint pred = Predictor8(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd9(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd9(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor9(output, offset++); + uint pred = Predictor9(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd10(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd10(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor10(output[x - 1], output, offset++); + uint pred = Predictor10(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd11(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd11(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor11(output[x - 1], output, offset++); + uint pred = Predictor11(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd12(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd12(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor12(output[x - 1], output, offset++); + uint pred = Predictor12(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd13(Span input, int startIdx, int numberOfPixels, int width, Span output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd13(uint* input, uint* upper, int numberOfPixels, uint* output) { - int endIdx = startIdx + numberOfPixels; - int offset = startIdx - width; - for (int x = startIdx; x < endIdx; ++x) + for (int x = 0; x < numberOfPixels; ++x) { - uint pred = Predictor13(output[x - 1], output, offset++); + uint pred = Predictor13(output[x - 1], upper + x); output[x] = AddPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor2(Span top, int idx) + public static uint Predictor2(uint left, uint* top) { - return top[idx]; + return top[0]; } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor3(Span top, int idx) + public static uint Predictor3(uint left, uint* top) { - return top[idx + 1]; + return top[1]; } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor4(Span top, int idx) + public static uint Predictor4(uint left, uint* top) { - return top[idx - 1]; + return top[-1]; } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor5(uint left, Span top, int idx) + public static uint Predictor5(uint left, uint* top) { - uint pred = Average3(left, top[idx], top[idx + 1]); - return pred; + return Average3(left, top[0], top[1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor6(uint left, Span top, int idx) + public static uint Predictor6(uint left, uint* top) { - uint pred = Average2(left, top[idx - 1]); - return pred; + return Average2(left, top[-1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor7(uint left, Span top, int idx) + public static uint Predictor7(uint left, uint* top) { - uint pred = Average2(left, top[idx]); - return pred; + return Average2(left, top[0]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor8(Span top, int idx) + public static uint Predictor8(uint left, uint* top) { - uint pred = Average2(top[idx - 1], top[idx]); - return pred; + return Average2(top[-1], top[0]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor9(Span top, int idx) + public static uint Predictor9(uint left, uint* top) { - uint pred = Average2(top[idx], top[idx + 1]); - return pred; + return Average2(top[0], top[1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor10(uint left, Span top, int idx) + public static uint Predictor10(uint left, uint* top) { - uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); - return pred; + return Average4(left, top[-1], top[0], top[1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor11(uint left, Span top, int idx) + public static uint Predictor11(uint left, uint* top) { - uint pred = Select(top[idx], left, top[idx - 1]); - return pred; + return Select(top[0], left, top[-1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor12(uint left, Span top, int idx) + public static uint Predictor12(uint left, uint* top) { - uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); - return pred; + return ClampedAddSubtractFull(left, top[0], top[-1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor13(uint left, Span top, int idx) + public static uint Predictor13(uint left, uint* top) { - uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); - return pred; + return ClampedAddSubtractHalf(left, top[0], top[-1]); } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub0(Span input, int numPixels, Span output) + public static void PredictorSub0(uint* input, int numPixels, uint* output) { - for (int i = 0; i < numPixels; i++) + for (int i = 0; i < numPixels; ++i) { output[i] = SubPixels(input[i], WebPConstants.ArgbBlack); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub1(Span input, int idx, int numPixels, Span output) + public static void PredictorSub1(uint* input, int numPixels, uint* output) { - for (int i = 0; i < numPixels; i++) + for (int i = 0; i < numPixels; ++i) { - output[i] = SubPixels(input[idx + i], input[idx + i - 1]); + output[i] = SubPixels(input[i], input[i - 1]); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub2(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub2(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor2(upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor2(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub3(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub3(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor3(upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor3(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub4(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub4(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor4(upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor4(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub5(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub5(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor5(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor5(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub6(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub6(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor6(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor6(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub7(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub7(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor7(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor7(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub8(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub8(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor8(upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor8(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub9(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub9(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor9(upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor9(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub10(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub10(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor10(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor10(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub11(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub11(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor11(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor11(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub12(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub12(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor12(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor12(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub13(Span input, int idx, Span upper, int numPixels, Span output) + public static void PredictorSub13(uint* input, uint* upper, int numPixels, uint* output) { - for (int x = 0; x < numPixels; x++) + for (int x = 0; x < numPixels; ++x) { - uint pred = Predictor13(input[idx - 1], upper, x); - output[x] = SubPixels(input[idx + x], pred); + uint pred = Predictor13(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index bc2c7b594..c7783de40 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Image transform methods for the lossless webp encoder. /// - internal static class PredictorEncoder + internal static unsafe class PredictorEncoder { private const int GreenRedToBlueNumAxis = 8; @@ -27,27 +27,61 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// with respect to predictions. If nearLosslessQuality < 100, applies /// near lossless processing, shaving off more bits of residuals for lower qualities. /// - public static void ResidualImage(int width, int height, int bits, Span argb, Span argbScratch, Span image, int nearLosslessQuality, bool exact, bool usedSubtractGreen) + public static void ResidualImage( + int width, + int height, + int bits, + Span argb, + Span argbScratch, + Span image, + int nearLosslessQuality, + bool exact, + bool usedSubtractGreen) { int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); + + // TODO: Can we optimize this? var histo = new int[4][]; for (int i = 0; i < 4; i++) { histo[i] = new int[256]; } - for (int tileY = 0; tileY < tilesPerCol; tileY++) + // TODO: Low Effort + for (int tileY = 0; tileY < tilesPerCol; ++tileY) { - for (int tileX = 0; tileX < tilesPerRow; tileX++) + for (int tileX = 0; tileX < tilesPerRow; ++tileX) { - int pred = GetBestPredictorForTile(width, height, tileX, tileY, bits, histo, argbScratch, argb, maxQuantization, exact, usedSubtractGreen, image); + int pred = GetBestPredictorForTile( + width, + height, + tileX, + tileY, + bits, + histo, + argbScratch, + argb, + maxQuantization, + exact, + usedSubtractGreen, + image); + image[(tileY * tilesPerRow) + tileX] = (uint)(WebPConstants.ArgbBlack | (pred << 8)); } } - CopyImageWithPrediction(width, height, bits, image, argbScratch, argb, maxQuantization, exact, usedSubtractGreen); + CopyImageWithPrediction( + width, + height, + bits, + image, + argbScratch, + argb, + maxQuantization, + exact, + usedSubtractGreen); } public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span argb, Span image) @@ -261,113 +295,130 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// the deviation further to pixels which depend on the current pixel for their /// predictions. ///
- private static void GetResidual(int width, int height, Span upperRow, Span currentRow, Span maxDiffs, int mode, int xStart, int xEnd, int y, int maxQuantization, bool exact, bool usedSubtractGreen, Span output) + private static void GetResidual( + int width, + int height, + Span upperRowSpan, + Span currentRowSpan, + Span maxDiffs, + int mode, + int xStart, + int xEnd, + int y, + int maxQuantization, + bool exact, + bool usedSubtractGreen, + Span output) { if (exact) { - PredictBatch(mode, xStart, y, xEnd - xStart, currentRow, upperRow, output); + PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output); } else { - for (int x = xStart; x < xEnd; x++) + fixed (uint* currentRow = currentRowSpan) + fixed (uint* upperRow = upperRowSpan) { - uint predict = 0; - uint residual; - if (y == 0) - { - predict = (x == 0) ? WebPConstants.ArgbBlack : currentRow[x - 1]; // Left. - } - else if (x == 0) - { - predict = upperRow[x]; // Top. - } - else + for (int x = xStart; x < xEnd; ++x) { - switch (mode) + uint predict = 0; + uint residual; + if (y == 0) { - case 0: - predict = WebPConstants.ArgbBlack; - break; - case 1: - predict = currentRow[x - 1]; - break; - case 2: - predict = LosslessUtils.Predictor2(upperRow, x); - break; - case 3: - predict = LosslessUtils.Predictor3(upperRow, x); - break; - case 4: - predict = LosslessUtils.Predictor4(upperRow, x); - break; - case 5: - predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow, x); - break; - case 6: - predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow, x); - break; - case 7: - predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow, x); - break; - case 8: - predict = LosslessUtils.Predictor8(upperRow, x); - break; - case 9: - predict = LosslessUtils.Predictor9(upperRow, x); - break; - case 10: - predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow, x); - break; - case 11: - predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow, x); - break; - case 12: - predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow, x); - break; - case 13: - predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow, x); - break; + predict = (x == 0) ? WebPConstants.ArgbBlack : currentRow[x - 1]; // Left. + } + else if (x == 0) + { + predict = upperRow[x]; // Top. + } + else + { + switch (mode) + { + case 0: + predict = WebPConstants.ArgbBlack; + break; + case 1: + predict = currentRow[x - 1]; + break; + case 2: + predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x); + break; + case 3: + predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x); + break; + case 4: + predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x); + break; + case 5: + predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x); + break; + case 6: + predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x); + break; + case 7: + predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x); + break; + case 8: + predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x); + break; + case 9: + predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x); + break; + case 10: + predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x); + break; + case 11: + predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x); + break; + case 12: + predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x); + break; + case 13: + predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x); + break; + } } - } - if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) - { - residual = LosslessUtils.SubPixels(currentRow[x], predict); - } - else - { - residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); + if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) + { + residual = LosslessUtils.SubPixels(currentRow[x], predict); + } + else + { + residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); - // Update the source image. - currentRow[x] = LosslessUtils.AddPixels(predict, residual); + // Update the source image. + currentRow[x] = LosslessUtils.AddPixels(predict, residual); - // x is never 0 here so we do not need to update upperRow like below. - } + // x is never 0 here so we do not need to update upperRow like below. + } - if ((currentRow[x] & MaskAlpha) == 0) - { - // If alpha is 0, cleanup RGB. We can choose the RGB values of the - // residual for best compression. The prediction of alpha itself can be - // non-zero and must be kept though. We choose RGB of the residual to be - // 0. - residual &= MaskAlpha; - - // Update the source image. - currentRow[x] = predict & ~MaskAlpha; - - // The prediction for the rightmost pixel in a row uses the leftmost - // pixel - // in that row as its top-right context pixel. Hence if we change the - // leftmost pixel of current_row, the corresponding change must be - // applied - // to upperRow as well where top-right context is being read from. - if (x == 0 && y != 0) + if ((currentRow[x] & MaskAlpha) == 0) { - upperRow[width] = currentRow[0]; + // If alpha is 0, cleanup RGB. We can choose the RGB values of the + // residual for best compression. The prediction of alpha itself can be + // non-zero and must be kept though. We choose RGB of the residual to be + // 0. + residual &= MaskAlpha; + + // Update the source image. + currentRow[x] = predict & ~MaskAlpha; + + // The prediction for the rightmost pixel in a row uses the leftmost + // pixel + // in that row as its top-right context pixel. Hence if we change the + // leftmost pixel of current_row, the corresponding change must be + // applied + // to upperRow as well where top-right context is being read from. + if (x == 0 && y != 0) + { + upperRow[width] = currentRow[0]; + } } - } - output[x - xStart] = residual; + output[x - xStart] = residual; + } } } } @@ -486,14 +537,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span upperRow = argbScratch; Span currentRow = upperRow.Slice(width + 1); Span currentMaxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); + + // TODO: This should be wrapped in a condition. Span lowerMaxDiffs = currentMaxDiffs.Slice(width); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; ++y) { Span tmp32 = upperRow; upperRow = currentRow; currentRow = tmp32; Span src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); src.CopyTo(currentRow); + + // TODO: Near lossless conditional. if (maxQuantization > 1) { // Compute max_diffs for the lower row now, because that needs the @@ -537,77 +592,90 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private static void PredictBatch(int mode, int xStart, int y, int numPixels, Span current, Span upper, Span output) + private static void PredictBatch( + int mode, + int xStart, + int y, + int numPixels, + Span currentSpan, + Span upperSpan, + Span outputSpan) { - if (xStart == 0) + fixed (uint* current = currentSpan) + fixed (uint* upper = upperSpan) + fixed (uint* outputFixed = outputSpan) { - if (y == 0) + uint* output = outputFixed; + if (xStart == 0) { - // ARGB_BLACK. - LosslessUtils.PredictorSub0(current, 1, output); + if (y == 0) + { + // ARGB_BLACK. + LosslessUtils.PredictorSub0(current, 1, output); + } + else + { + // Top one. + LosslessUtils.PredictorSub2(current, upper, 1, output); + } + + ++xStart; + ++output; + --numPixels; } - else + + if (y == 0) { - // Top one. - LosslessUtils.PredictorSub2(current, 0, upper, 1, output); + // Left one. + LosslessUtils.PredictorSub1(current + xStart, numPixels, output); } - - xStart++; - output = output.Slice(1); - numPixels--; - } - - if (y == 0) - { - // Left one. - LosslessUtils.PredictorSub1(current, xStart, numPixels, output); - } - else - { - switch (mode) + else { - case 0: - LosslessUtils.PredictorSub0(current, numPixels, output); - break; - case 1: - LosslessUtils.PredictorSub1(current, xStart, numPixels, output); - break; - case 2: - LosslessUtils.PredictorSub2(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 3: - LosslessUtils.PredictorSub3(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 4: - LosslessUtils.PredictorSub4(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 5: - LosslessUtils.PredictorSub5(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 6: - LosslessUtils.PredictorSub6(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 7: - LosslessUtils.PredictorSub7(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 8: - LosslessUtils.PredictorSub8(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 9: - LosslessUtils.PredictorSub9(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 10: - LosslessUtils.PredictorSub10(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 11: - LosslessUtils.PredictorSub11(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 12: - LosslessUtils.PredictorSub12(current, xStart, upper.Slice(xStart), numPixels, output); - break; - case 13: - LosslessUtils.PredictorSub13(current, xStart, upper.Slice(xStart), numPixels, output); - break; + switch (mode) + { + case 0: + LosslessUtils.PredictorSub0(current, numPixels, output); + break; + case 1: + LosslessUtils.PredictorSub1(current + xStart, numPixels, output); + break; + case 2: + LosslessUtils.PredictorSub2(current + xStart, upper + xStart, numPixels, output); + break; + case 3: + LosslessUtils.PredictorSub3(current + xStart, upper + xStart, numPixels, output); + break; + case 4: + LosslessUtils.PredictorSub4(current + xStart, upper + xStart, numPixels, output); + break; + case 5: + LosslessUtils.PredictorSub5(current + xStart, upper + xStart, numPixels, output); + break; + case 6: + LosslessUtils.PredictorSub6(current + xStart, upper + xStart, numPixels, output); + break; + case 7: + LosslessUtils.PredictorSub7(current + xStart, upper + xStart, numPixels, output); + break; + case 8: + LosslessUtils.PredictorSub8(current + xStart, upper + xStart, numPixels, output); + break; + case 9: + LosslessUtils.PredictorSub9(current + xStart, upper + xStart, numPixels, output); + break; + case 10: + LosslessUtils.PredictorSub10(current + xStart, upper + xStart, numPixels, output); + break; + case 11: + LosslessUtils.PredictorSub11(current + xStart, upper + xStart, numPixels, output); + break; + case 12: + LosslessUtils.PredictorSub12(current + xStart, upper + xStart, numPixels, output); + break; + case 13: + LosslessUtils.PredictorSub13(current + xStart, upper + xStart, numPixels, output); + break; + } } } } @@ -627,7 +695,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless right = AddGreenToBlueAndRed(right); } - for (int x = 1; x < width - 1; x++) + for (int x = 1; x < width - 1; ++x) { uint up = argb[-stride + x]; uint down = argb[stride + x]; From 465f49c8d39b7d4b444da999ecf9141b8f02d8ca Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Aug 2020 10:46:13 +0100 Subject: [PATCH 195/359] Fix decoder --- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index ac6990665..e8dd3c40a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // First Row follows the L (mode=1) mode. PredictorAdd0(input, null, 1, output); - PredictorAdd1(input + 1, null, width - 1, output); + PredictorAdd1(input + 1, null, width - 1, output + 1); input += width; output += width; From 6924d155d337878170cae66d8e105ed171a70db7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Aug 2020 10:50:23 +0100 Subject: [PATCH 196/359] Update LosslessUtils.cs --- src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index e8dd3c40a..16b5bce06 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -353,7 +353,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - // TODO: I can't see the equivalent code in the source? outputSpan.CopyTo(pixelData); } From 0d8aa134de69af2c768c345df7f0fe8e7010cd1b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Aug 2020 21:46:57 +0100 Subject: [PATCH 197/359] Car.bmp works now. --- .../Formats/WebP/Lossless/PredictorEncoder.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index c7783de40..646734aa5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -241,13 +241,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); Span dst = currentRow.Slice(contextStartX); src.CopyTo(dst); + + // TODO: Source wraps this in conditional + // WEBP_NEAR_LOSSLESS == 1 if (maxQuantization > 1 && y >= 1 && y + 1 < height) { MaxDiffsForRow(contextWidth, width, argb.Slice((y * width) + contextStartX), maxDiffs.Slice(contextStartX), usedSubtractGreen); } GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, residuals); - for (int relativeX = 0; relativeX < maxX; relativeX++) + for (int relativeX = 0; relativeX < maxX; ++relativeX) { UpdateHisto(histoArgb, residuals[relativeX]); } @@ -268,6 +271,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (curDiff < bestDiff) { + // TODO: Consider swapping references for (int i = 0; i < 4; i++) { histoArgb[i].AsSpan().CopyTo(bestHisto[i]); @@ -538,7 +542,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span currentRow = upperRow.Slice(width + 1); Span currentMaxDiffs = MemoryMarshal.Cast(currentRow.Slice(width + 1)); - // TODO: This should be wrapped in a condition. + // TODO: This should be wrapped in a condition? Span lowerMaxDiffs = currentMaxDiffs.Slice(width); for (int y = 0; y < height; ++y) { @@ -548,7 +552,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); src.CopyTo(currentRow); - // TODO: Near lossless conditional. + // TODO: Near lossless conditional? if (maxQuantization > 1) { // Compute max_diffs for the lower row now, because that needs the @@ -952,7 +956,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) { double retVal = 0.0d; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 4; ++i) { double kExpValue = 0.94; retVal += PredictionCostSpatial(tile[i], 1, kExpValue); From 56765ee1ed9714a0220622f2641abc6885776347 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Aug 2020 22:51:00 +0100 Subject: [PATCH 198/359] Calliphora now works. --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 32 ++++++++++++------- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 3 ++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index a0ade54fa..942579487 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -340,6 +340,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless colorCache[i].Init(i); } + // TODO: Don't use the enumerator here. // Find the cache_bits giving the lowest entropy. using List.Enumerator c = refs.Refs.GetEnumerator(); while (c.MoveNext()) @@ -363,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ++histos[0].Alpha[a]; // Deal with cacheBits > 0. - for (int i = cacheBitsMax; i >= 1; i--, key >>= 1) + for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) { if (colorCache[i].Lookup(key) == pix) { @@ -388,6 +389,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int len = v.Len; uint bgraPrev = bgra[pos] ^ 0xffffffffu; + // TODO: Original has this loop? + // VP8LPrefixEncode(len, &code, &extra_bits, &extra_bits_value); + // for (i = 0; i <= cache_bits_max; ++i) + // { + // ++histos[i]->literal_[NUM_LITERAL_CODES + code]; + // } // Update the color caches. do { @@ -409,7 +416,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - for (int i = 0; i <= cacheBitsMax; i++) + for (int i = 0; i <= cacheBitsMax; ++i) { double entropy = histos[i].EstimateBits(); if (i == 0 || entropy < entropyMin) @@ -461,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Add first pixel as literal. AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); - for (int i = 1; i < pixCount; i++) + for (int i = 1; i < pixCount; ++i) { float prevCost = costManager.Costs[i - 1]; int offset = hashChain.FindOffset(i); @@ -660,7 +667,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // [i,i+len) + [i+len, length of best match at i+len) // while we check if we can use: // [i,j) (where j<=i+len) + [j, length of best match at j) - for (j = iLastCheck + 1; j <= jMax; j++) + for (j = iLastCheck + 1; j <= jMax; ++j) { int lenJ = hashChain.FindLength(j); int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. @@ -720,7 +727,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int i = pixelCount - 2; int countsPos = i; counts[countsPos + 1] = 1; - for (; i >= 0; i--, countsPos--) + for (; i >= 0; --i, --countsPos) { if (bgra[i] == bgra[i + 1]) { @@ -739,9 +746,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Figure out the window offsets around a pixel. They are stored in a // spiraling order around the pixel as defined by DistanceToPlaneCode. - for (int y = 0; y <= 6; y++) + for (int y = 0; y <= 6; ++y) { - for (int x = -6; x <= 6; x++) + for (int x = -6; x <= 6; ++x) { int offset = (y * xSize) + x; @@ -762,7 +769,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // For narrow images, not all plane codes are reached, so remove those. - for (i = 0; i < WindowOffsetsSizeMax; i++) + for (i = 0; i < WindowOffsetsSizeMax; ++i) { if (windowOffsets[i] == 0) { @@ -774,7 +781,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Given a pixel P, find the offsets that reach pixels unreachable from P-1 // with any of the offsets in windowOffsets[]. - for (i = 0; i < windowOffsetsSize; i++) + for (i = 0; i < windowOffsetsSize; ++i) { bool isReachable = false; for (int j = 0; j < windowOffsetsSize && !isReachable; ++j) @@ -785,7 +792,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (!isReachable) { windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i]; - windowOffsetsNewSize++; + ++windowOffsetsNewSize; } } @@ -917,7 +924,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless refs.Add(PixOrCopy.CreateCopy((uint)xSize, (ushort)prevRowLen)); if (useColorCache) { - for (int k = 0; k < prevRowLen; k++) + for (int k = 0; k < prevRowLen; ++k) { colorCache.Insert(bgra[i + k]); } @@ -943,6 +950,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ///
private static void BackwardRefsWithLocalCache(Span bgra, int cacheBits, Vp8LBackwardRefs refs) { + // TODO: Don't use enumerator. int pixelIndex = 0; using List.Enumerator c = refs.Refs.GetEnumerator(); var colorCache = new ColorCache(); @@ -969,7 +977,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless else { // refs was created without local cache, so it can not have cache indexes. - for (int k = 0; k < v.Len; k++) + for (int k = 0; k < v.Len; ++k) { colorCache.Insert(bgra[pixelIndex++]); } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 9df6591f3..8b1a44fe5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -607,12 +607,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { int cacheBits = 0; var histogramSymbols = new short[1]; // Only one tree, one symbol. + + // TODO: Can HuffmanTreeCode be struct var huffmanCodes = new HuffmanTreeCode[5]; for (int i = 0; i < huffmanCodes.Length; i++) { huffmanCodes[i] = new HuffmanTreeCode(); } + // TODO: Can HuffmanTree be struct var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { From 90006635e814e1aff76035fc8b2e52e8c84118e1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 27 Aug 2020 13:00:00 +0200 Subject: [PATCH 199/359] Fix issue with encoding Car.bmp --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 34 ++++++--------- .../Formats/WebP/Lossless/CostManager.cs | 17 +++----- .../Formats/WebP/Lossless/HistogramBinInfo.cs | 2 +- .../Formats/WebP/Lossless/HistogramEncoder.cs | 41 ++++++++++--------- .../Formats/WebP/Lossless/HuffmanUtils.cs | 18 ++++---- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 21 +++++----- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 2 +- .../WebP/Lossless/WebPLosslessDecoder.cs | 10 ++--- src/ImageSharp/Formats/WebP/WebPConstants.cs | 4 +- 9 files changed, 67 insertions(+), 82 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 942579487..8af42a8b4 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const int WindowOffsetsSizeMax = 32; - /// - /// Minimum block size for backward references. - /// - private const int MinBlockSize = 256; - /// /// The number of bits for the window size. /// @@ -135,7 +130,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint bestBgra; int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; int lengthMax = (maxLen < 256) ? maxLen : 256; - uint maxBasePosition; pos = (int)chain[basePosition]; int currLength; @@ -193,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // We have the best match but in case the two intervals continue matching // to the left, we have the best matches for the left-extended pixels. - maxBasePosition = (uint)basePosition; + var maxBasePosition = (uint)basePosition; while (true) { p.OffsetLength[basePosition] = (bestDistance << MaxLengthBits) | (uint)bestLength; @@ -432,15 +426,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void BackwardReferencesTraceBackwards(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refsSrc, Vp8LBackwardRefs refsDst) { int distArraySize = xSize * ySize; - var distArray = new short[distArraySize]; + var distArray = new ushort[distArraySize]; BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); int chosenPathSize = TraceBackwards(distArray, distArraySize); - Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); + Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); } - private static void BackwardReferencesHashChainDistanceOnly(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, short[] distArray) + private static void BackwardReferencesHashChainDistanceOnly(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, ushort[] distArray) { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; @@ -502,17 +496,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (i + len - 1 > reach) { - int offsetJ = 0; int lenJ = 0; int j; for (j = i; j <= reach; ++j) { - offset = hashChain.FindOffset(j + 1); - len = hashChain.FindLength(j + 1); + int offsetJ = hashChain.FindOffset(j + 1); + lenJ = hashChain.FindLength(j + 1); if (offsetJ != offset) { - offset = hashChain.FindOffset(j); - len = hashChain.FindLength(j); + lenJ = hashChain.FindLength(j); break; } } @@ -533,14 +525,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private static int TraceBackwards(short[] distArray, int distArraySize) + private static int TraceBackwards(ushort[] distArray, int distArraySize) { int chosenPathSize = 0; int pathPos = distArraySize; int curPos = distArraySize - 1; while (curPos >= 0) { - short cur = distArray[curPos]; + ushort cur = distArray[curPos]; pathPos--; chosenPathSize++; distArray[pathPos] = cur; @@ -550,7 +542,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return chosenPathSize; } - private static void BackwardReferencesHashChainFollowChosenPath(Span bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) + private static void BackwardReferencesHashChainFollowChosenPath(Span bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) { bool useColorCache = cacheBits > 0; var colorCache = new ColorCache(); @@ -606,7 +598,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private static void AddSingleLiteralWithCostModel(Span bgra, ColorCache colorCache, CostModel costModel, int idx, bool useColorCache, float prevCost, float[] cost, short[] distArray) + private static void AddSingleLiteralWithCostModel(Span bgra, ColorCache colorCache, CostModel costModel, int idx, bool useColorCache, float prevCost, float[] cost, ushort[] distArray) { double costVal = prevCost; uint color = bgra[idx]; @@ -965,7 +957,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (ix >= 0) { // Color cache contains bgraLiteral - v = PixOrCopy.CreateCacheIdx(ix); + PixOrCopy.CreateCacheIdx(ix); } else { @@ -983,8 +975,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } } - - // TODO: VP8LColorCacheClear(colorCache)? } private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs index d912e2e28..bba82d10e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs @@ -14,11 +14,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { private CostInterval head; - public CostManager(short[] distArray, int pixCount, CostModel costModel) + public CostManager(ushort[] distArray, int pixCount, CostModel costModel) { int costCacheSize = (pixCount > BackwardReferenceEncoder.MaxLength) ? BackwardReferenceEncoder.MaxLength : pixCount; - this.Intervals = new List(); this.CacheIntervals = new List(); this.CostCache = new List(); this.Costs = new float[pixCount]; @@ -85,9 +84,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public float[] Costs { get; } - public short[] DistArray { get; } - - public List Intervals { get; } + public ushort[] DistArray { get; } public List CacheIntervals { get; } @@ -99,7 +96,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public void UpdateCostAtIndex(int i, bool doCleanIntervals) { CostInterval current = this.head; - using List.Enumerator intervalEnumerator = this.Intervals.GetEnumerator(); while (current != null && current.Start <= i) { CostInterval next = current.Next; @@ -142,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (this.Costs[j] > costTmp) { this.Costs[j] = costTmp; - this.DistArray[j] = (short)(k + 1); + this.DistArray[j] = (ushort)(k + 1); } } @@ -150,14 +146,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } CostInterval interval = this.head; - for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) + for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; ++i) { // Define the intersection of the ith interval with the new one. int start = position + this.CacheIntervals[i].Start; int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); - var idx = i; CostInterval intervalNext; for (; interval != null && interval.Start < end; interval = intervalNext) { @@ -203,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // We have to split the old interval as it fully contains the new one. int endOriginal = interval.End; interval.End = start; - this.InsertInterval(interval, interval.Cost, idx, end, endOriginal); + this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); break; } else @@ -317,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (this.Costs[i] > cost) { this.Costs[i] = cost; - this.DistArray[i] = (short)(k + 1); + this.DistArray[i] = (ushort)(k + 1); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs index b75df6505..e04557959 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs @@ -13,6 +13,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Number of combine failures per binId. /// - public short NumCombineFailures; + public ushort NumCombineFailures; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index a1ca4108a..1497ca244 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -26,17 +26,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private const uint NonTrivialSym = 0xffffffff; - private const short InvalidHistogramSymbol = short.MaxValue; + private const ushort InvalidHistogramSymbol = ushort.MaxValue; - public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) + public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; int imageHistoRawSize = histoXSize * histoYSize; int entropyCombineNumBins = BinSize; - var mapTmp = new short[imageHistoRawSize]; - var clusterMappings = new short[imageHistoRawSize]; - int numUsed = imageHistoRawSize; + var mapTmp = new ushort[imageHistoRawSize]; + var clusterMappings = new ushort[imageHistoRawSize]; var origHisto = new List(imageHistoRawSize); for (int i = 0; i < imageHistoRawSize; i++) { @@ -47,13 +46,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistogramBuild(xSize, histoBits, refs, origHisto); // Copies the histograms and computes its bitCost. histogramSymbols is optimized. - HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); - numUsed = imageHisto.Count(h => h != null); + int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); if (entropyCombine) { - short[] binMap = mapTmp; + ushort[] binMap = mapTmp; var numClusters = numUsed; double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); HistogramAnalyzeEntropyBin(imageHisto, binMap); @@ -128,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Partition histograms to different entropy bins for three dominant (literal, /// red and blue) symbol costs and compute the histogram aggregate bitCost. ///
- private static void HistogramAnalyzeEntropyBin(List histograms, short[] binMap) + private static void HistogramAnalyzeEntropyBin(List histograms, ushort[] binMap) { int histoSize = histograms.Count; var costRange = new DominantCostRange(); @@ -153,13 +151,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless continue; } - binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions); + binMap[i] = (ushort)costRange.GetHistoBinIndex(histograms[i], NumPartitions); } } - private static void HistogramCopyAndAnalyze(List origHistograms, List histograms, short[] histogramSymbols) + private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, ushort[] histogramSymbols) { - for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) + for (int clusterId = 0, i = 0; i < origHistograms.Count; ++i) { Vp8LHistogram origHistogram = origHistograms[i]; origHistogram.UpdateHistogramCost(); @@ -174,12 +172,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless else { histograms[i] = (Vp8LHistogram)origHistogram.DeepClone(); - histogramSymbols[i] = (short)clusterId++; + histogramSymbols[i] = (ushort)clusterId++; } } + + int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol); + return numUsed; } - private static void HistogramCombineEntropyBin(List histograms, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) + private static void HistogramCombineEntropyBin(List histograms, ushort[] clusters, ushort[] clusterMappings, Vp8LHistogram curCombo, ushort[] binMap, int numBins, double combineCostFactor) { var binInfo = new HistogramBinInfo[BinSize]; for (int idx = 0; idx < numBins; idx++) @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // By default, a cluster matches itself. for (int idx = 0; idx < histograms.Count; idx++) { - clusterMappings[idx] = (short)idx; + clusterMappings[idx] = (ushort)idx; } var indicesToRemove = new List(); @@ -253,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. ///
- private static void OptimizeHistogramSymbols(short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) + private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols) { bool doContinue = true; @@ -274,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (k != clusterMappings[i]) { doContinue = true; - clusterMappings[i] = (short)k; + clusterMappings[i] = (ushort)k; } } } @@ -295,7 +296,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (cluster > 0 && clusterMappingsTmp[cluster] == 0) { clusterMax++; - clusterMappingsTmp[cluster] = (short)clusterMax; + clusterMappingsTmp[cluster] = (ushort)clusterMax; } symbols[i] = clusterMappingsTmp[cluster]; @@ -522,7 +523,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private static void HistogramRemap(List input, List output, short[] symbols) + private static void HistogramRemap(List input, List output, ushort[] symbols) { int inSize = input.Count; int outSize = output.Count; @@ -549,7 +550,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - symbols[i] = (short)bestOut; + symbols[i] = (ushort)bestOut; } } else diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 414c607ad..2e0805c28 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) { // 1) Let's make the Huffman code more compatible with rle encoding. - for (; length >= 0; length--) + for (; length >= 0; --length) { if (length == 0) { @@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Mark any seq of non-0's that is longer as 7 as a goodForRle. uint symbol = counts[0]; int stride = 0; - for (int i = 0; i < length + 1; i++) + for (int i = 0; i < length + 1; ++i) { if (i == length || counts[i] != symbol) { if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) { - for (int k = 0; k < stride; k++) + for (int k = 0; k < stride; ++k) { goodForRle[i - k - 1] = true; } @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - stride++; + ++stride; } } @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless stride = 0; uint limit = counts[0]; uint sum = 0; - for (int i = 0; i < length; i++) + for (int i = 0; i < length + 1; ++i) { var valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage) @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless count = 0; } - for (k = 0; k < stride; k++) + for (k = 0; k < stride; ++k) { // We don't want to change value at counts[i], // that is already belonging to the next stride. Thus - 1. @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - stride++; + ++stride; if (i != length) { sum += counts[i]; @@ -165,11 +165,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint countMin; int treeSizeOrig = 0; - for (int i = 0; i < histogramSize; i++) + for (int i = 0; i < histogramSize; ++i) { if (histogram[i] != 0) { - treeSizeOrig++; + ++treeSizeOrig; } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 8b1a44fe5..e5b53c6f8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -401,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); - var histogramSymbols = new short[histogramImageXySize]; + var histogramSymbols = new ushort[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { @@ -412,6 +412,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (cacheBits == 0) { + // TODO: not sure if this should be 10 or 11. Original code comment says "The maximum allowed limit is 11.", but the value itself is 10. cacheBits = WebPConstants.MaxColorCacheBits; } } @@ -485,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); Span histogramBgra = histogramBgraBuffer.GetSpan(); int maxIndex = 0; - for (int i = 0; i < histogramImageXySize; i++) + for (int i = 0; i < histogramImageXySize; ++i) { int symbolIndex = histogramSymbols[i] & 0xffff; histogramBgra[i] = (uint)(symbolIndex << 8); @@ -502,7 +503,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Store Huffman codes. // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5 * histogramImage.Count; i++) + for (int i = 0; i < 5 * histogramImage.Count; ++i) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -517,7 +518,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless tokens[i] = new HuffmanTreeToken(); } - for (int i = 0; i < 5 * histogramImage.Count; i++) + for (int i = 0; i < 5 * histogramImage.Count; ++i) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -531,7 +532,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // TODO: Keep track of the smallest image so far. if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()) { - // TODO : This was done in the reference by swapping references, this will be slower + // TODO: This was done in the reference by swapping references, this will be slower bitWriterBest = this.bitWriter.Clone(); } } @@ -606,7 +607,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { int cacheBits = 0; - var histogramSymbols = new short[1]; // Only one tree, one symbol. + var histogramSymbols = new ushort[1]; // Only one tree, one symbol. // TODO: Can HuffmanTreeCode be struct var huffmanCodes = new HuffmanTreeCode[5]; @@ -652,7 +653,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5; ++i) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -662,13 +663,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } var tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) + for (int i = 0; i < tokens.Length; ++i) { tokens[i] = new HuffmanTreeToken(); } // Store Huffman codes. - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5; ++i) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -849,7 +850,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 78420185b..dd5c9ebda 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Distance = new uint[WebPConstants.NumDistanceCodes]; var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + (1 << WebPConstants.MaxColorCacheBits); - this.Literal = new uint[literalSize]; + this.Literal = new uint[literalSize + 1]; // 5 for literal, red, blue, alpha, distance. this.IsUsed = new bool[5]; diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index bf59394d3..943d18397 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -145,7 +145,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (isColorCachePresent) { colorCacheBits = (int)this.bitReader.ReadValue(4); - bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits; + + // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. + // That is why 11 bits is also considered valid here. + bool colorCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= (WebPConstants.MaxColorCacheBits + 1); if (!colorCacheBitsIsValid) { WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); @@ -162,11 +165,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless decoder.Metadata.ColorCache = new ColorCache(); colorCacheSize = 1 << colorCacheBits; decoder.Metadata.ColorCacheSize = colorCacheSize; - if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) - { - WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); - } - decoder.Metadata.ColorCache.Init(colorCacheBits); } else diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index edec77ad8..71fab497d 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -114,9 +114,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int MaxPaletteSize = 256; /// - /// Maximum number of color cache bits is 11. + /// Maximum number of color cache bits is 10. /// - public const int MaxColorCacheBits = 11; + public const int MaxColorCacheBits = 10; /// /// The maximum number of allowed transforms in a VP8L bitstream. From a38015c90d13067144bdf46af58d1cd015436a7b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 6 Oct 2020 14:32:38 +0200 Subject: [PATCH 200/359] Change HuffmanTree and HuffmanTreeCode to struct --- .../Formats/WebP/Lossless/HuffmanTree.cs | 11 +---- .../Formats/WebP/Lossless/HuffmanTreeCode.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 49 +++++++++---------- .../Formats/WebP/WebPEncoderTests.cs | 27 ++++++++++ 4 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index d7179ad12..a4578912e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -9,17 +9,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// Represents the Huffman tree. /// [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] - internal class HuffmanTree : IDeepCloneable + internal struct HuffmanTree : IDeepCloneable { /// - /// Initializes a new instance of the class. - /// - public HuffmanTree() - { - } - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The HuffmanTree to create an instance from. private HuffmanTree(HuffmanTree other) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs index 6b6e234d2..ef88aae84 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Represents the tree codes (depth and bits array). /// - internal class HuffmanTreeCode + internal struct HuffmanTreeCode { /// /// Gets or sets the number of symbols. diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index e5b53c6f8..8194c54b8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -321,12 +321,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.HistoBits, bytePosition); } - - // TODO: Comparison and picking of best (smallest) encoding } /// - /// Analyzes the image and decides what transforms should be used. + /// Analyzes the image and decides which transforms should be used. /// private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) where TPixel : unmanaged, IPixel @@ -462,7 +460,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var huffmanCodes = new HuffmanTreeCode[bitArraySize]; for (int i = 0; i < huffmanCodes.Length; i++) { - huffmanCodes[i] = new HuffmanTreeCode(); + huffmanCodes[i] = default; } GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); @@ -526,10 +524,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Store actual literals. - var hdrSizeTmp = (int)(this.bitWriter.NumBytes() - initBytePosition); this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); - // TODO: Keep track of the smallest image so far. + // Keep track of the smallest image so far. if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()) { // TODO: This was done in the reference by swapping references, this will be slower @@ -609,18 +606,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless int cacheBits = 0; var histogramSymbols = new ushort[1]; // Only one tree, one symbol. - // TODO: Can HuffmanTreeCode be struct var huffmanCodes = new HuffmanTreeCode[5]; for (int i = 0; i < huffmanCodes.Length; i++) { - huffmanCodes[i] = new HuffmanTreeCode(); + huffmanCodes[i] = default; } - // TODO: Can HuffmanTree be struct var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { - huffTree[i] = new HuffmanTree(); + huffTree[i] = default; } // Calculate backward references from the image pixels. @@ -790,14 +785,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { if (trimmedLength == 2) { - this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmed_length=2 + this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmedLength=2 } else { - int nbits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); - int nbitpairs = (nbits / 2) + 1; - this.bitWriter.PutBits((uint)nbitpairs - 1, 3); - this.bitWriter.PutBits((uint)trimmedLength - 2, nbitpairs * 2); + int nBits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nBitPairs = (nBits / 2) + 1; + this.bitWriter.PutBits((uint)nBitPairs - 1, 3); + this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); } } @@ -1056,13 +1051,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Let's check if the histogram of the chosen entropy mode has // non-zero red and blue values. If all are zero, we can later skip // the cross color optimization. - var histoPairs = new byte[][] + byte[][] histoPairs = { - new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, - new byte[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, - new byte[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, - new byte[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, - new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } + new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, + new[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, + new[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, + new[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, + new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } }; Span redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]); Span blueHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][1]); @@ -1079,7 +1074,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, + /// If number of colors in the image is less than or equal to MaxPaletteSize, /// creates a palette and returns true, else returns false. /// /// true, if a palette should be used. @@ -1167,7 +1162,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// Remap argb values in src[] to packed palettes entries in dst[] + /// Remap bgra values in src[] to packed palettes entries in dst[] /// using 'row' as a temporary buffer of size 'width'. /// We assume that all src[] values have a corresponding entry in the palette. /// Note: src[] can be the same as dst[] @@ -1293,8 +1288,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - src = src.Slice((int)srcStride); - dst = dst.Slice((int)dstStride); + src = src.Slice(srcStride); + dst = dst.Slice(dstStride); } } @@ -1318,8 +1313,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - src = src.Slice((int)srcStride); - dst = dst.Slice((int)dstStride); + src = src.Slice(srcStride); + dst = dst.Slice(dstStride); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs new file mode 100644 index 000000000..2cc2c88fe --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -0,0 +1,27 @@ +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + using static TestImages.WebP; + + public class WebPEncoderTests + { + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32)] + public void Encode_Lossless_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoder() + { + Lossy = false + }; + + using (Image image = provider.GetImage()) + { + image.VerifyEncoder(provider, "webp", "lossless", encoder); + } + } + } +} From 733f83a2a058b6e6ef1c3ad220cc9d44e9356120 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 6 Oct 2020 15:52:53 +0200 Subject: [PATCH 201/359] Use quality parameter --- .../Formats/WebP/IWebPEncoderOptions.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 25 +++++++++++-------- .../Formats/WebP/MetadataExtensions.cs | 18 +++++++++++++ src/ImageSharp/Formats/WebP/WebPEncoder.cs | 2 +- .../Formats/WebP/WebPEncoderCore.cs | 4 +-- src/ImageSharp/Formats/WebP/WebPFormat.cs | 4 +++ .../Formats/WebP/WebPEncoderTests.cs | 9 ++++--- 7 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/MetadataExtensions.cs diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index 36d3460da..ab3757131 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger /// files compared to the slowest, but best, 100. /// - float Quality { get; } + int Quality { get; } /// /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 8194c54b8..181fbaac1 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -40,6 +40,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private Vp8LBitWriter bitWriter; + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -52,11 +57,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The memory allocator. /// The width of the input image. /// The height of the input image. - public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height) + /// The encoding quality. + public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality) { var pixelCount = width * height; int initialSize = pixelCount * 2; + this.quality = quality.Clamp(1, 100); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); @@ -255,8 +262,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero); - int quality = 75; // TODO: quality is hardcoded for now. - // TODO : Do we want to do this multi-threaded, this will probably require a second class: // one which co-ordinates the threading and comparison and another which does the actual encoding foreach (CrunchConfig crunchConfig in crunchConfigs) @@ -297,12 +302,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (this.UsePredictorTransform) { - this.ApplyPredictFilter(this.CurrentWidth, height, quality, this.UseSubtractGreenTransform); + this.ApplyPredictFilter(this.CurrentWidth, height, this.quality, this.UseSubtractGreenTransform); } if (this.UseCrossColorTransform) { - this.ApplyCrossColorFilter(this.CurrentWidth, height, quality); + this.ApplyCrossColorFilter(this.CurrentWidth, height, this.quality); } this.bitWriter.PutBits(0, 1); // No more transforms. @@ -314,7 +319,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Refs, this.CurrentWidth, height, - quality, useCache, crunchConfig, this.CacheBits, @@ -329,7 +333,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) where TPixel : unmanaged, IPixel { - var configQuality = 75; // TODO: hardcoded quality for now int method = 4; // TODO: method hardcoded to 4 for now. int width = image.Width; int height = image.Height; @@ -348,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless bool doNotCache = false; var crunchConfigs = new List(); - if (method == 6 && configQuality == 100) + if (method == 6 && this.quality == 100) { doNotCache = true; @@ -367,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { // Only choose the guessed best transform. crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (configQuality >= 75 && method == 5) + if (this.quality >= 75 && method == 5) { // Test with and without color cache. doNotCache = true; @@ -396,14 +399,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); var histogramSymbols = new ushort[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { - huffTree[i] = new HuffmanTree(); + huffTree[i] = default; } if (useCache) diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs new file mode 100644 index 000000000..3d0aca6db --- /dev/null +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -0,0 +1,18 @@ +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the webp format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static WebPMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebPFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 787cff67b..c6770daeb 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool Lossy { get; set; } /// - public float Quality { get; set; } + public int Quality { get; set; } /// public int Method { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index f286c4461..a5bce7f8c 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Compression quality. Between 0 and 100. /// - private float quality; + private readonly int quality; /// /// Initializes a new instance of the class. @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height); + var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height, this.quality); enc.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 38787cbf8..2bb606f7f 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -10,6 +10,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public sealed class WebPFormat : IImageFormat { + private WebPFormat() + { + } + /// /// Gets the current instance. /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 2cc2c88fe..44ffa2be3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -9,8 +9,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public class WebPEncoderTests { [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32)] - public void Encode_Lossless_Works(TestImageProvider provider) + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] + public void Encode_Lossless_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebPEncoder() @@ -20,7 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP using (Image image = provider.GetImage()) { - image.VerifyEncoder(provider, "webp", "lossless", encoder); + var testOutputDetails = string.Concat("lossless", "_", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } } } From 9dbd320b62f6c3409f0aefe7c9fb2a305550c838 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 6 Oct 2020 19:15:47 +0200 Subject: [PATCH 202/359] Use encoding method parameter --- .../Formats/WebP/IWebPEncoderOptions.cs | 2 + .../Formats/WebP/Lossless/Vp8LEncoder.cs | 44 +++++++++++-------- .../Formats/WebP/MetadataExtensions.cs | 3 ++ src/ImageSharp/Formats/WebP/WebPEncoder.cs | 4 +- .../Formats/WebP/WebPEncoderCore.cs | 8 +++- .../Formats/WebP/WebPEncoderTests.cs | 35 +++++++++++++-- 6 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index ab3757131..f08ef8a7f 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -19,11 +19,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// For lossy, 0 gives the smallest size and 100 the largest. For lossless, /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger /// files compared to the slowest, but best, 100. + /// Defaults to 75. /// int Quality { get; } /// /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). + /// Defaults to 4. /// int Method { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 181fbaac1..8ead78f86 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -45,6 +45,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless ///
private readonly int quality; + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly int method; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -58,12 +63,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The width of the input image. /// The height of the input image. /// The encoding quality. - public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality) + /// Quality/speed trade-off (0=fast, 6=slower-better). + public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method) { var pixelCount = width * height; int initialSize = pixelCount * 2; - this.quality = quality.Clamp(1, 100); + this.quality = quality.Clamp(0, 100); + this.method = method.Clamp(0, 6); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); @@ -302,12 +309,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (this.UsePredictorTransform) { - this.ApplyPredictFilter(this.CurrentWidth, height, this.quality, this.UseSubtractGreenTransform); + this.ApplyPredictFilter(this.CurrentWidth, height, this.UseSubtractGreenTransform); } if (this.UseCrossColorTransform) { - this.ApplyCrossColorFilter(this.CurrentWidth, height, this.quality); + this.ApplyCrossColorFilter(this.CurrentWidth, height); } this.bitWriter.PutBits(0, 1); // No more transforms. @@ -333,7 +340,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) where TPixel : unmanaged, IPixel { - int method = 4; // TODO: method hardcoded to 4 for now. int width = image.Width; int height = image.Height; @@ -341,8 +347,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var usePalette = this.AnalyzeAndCreatePalette(image); // Empirical bit sizes. - this.HistoBits = GetHistoBits(method, usePalette, width, height); - this.TransformBits = GetTransformBits(method, this.HistoBits); + this.HistoBits = GetHistoBits(this.method, usePalette, width, height); + this.TransformBits = GetTransformBits(this.method, this.HistoBits); // Try out multiple LZ77 on images with few colors. var nlz77s = (this.PaletteSize > 0 && this.PaletteSize <= 16) ? 2 : 1; @@ -351,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless bool doNotCache = false; var crunchConfigs = new List(); - if (method == 6 && this.quality == 100) + if (this.method == 6 && this.quality == 100) { doNotCache = true; @@ -370,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { // Only choose the guessed best transform. crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (this.quality >= 75 && method == 5) + if (this.quality >= 75 && this.method == 5) { // Test with and without color cache. doNotCache = true; @@ -423,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Calculate backward references from BGRA image. - BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); + BackwardReferenceEncoder.HashChainFill(hashChain, bgra, this.quality, width, height); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; @@ -433,7 +439,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless width, height, bgra, - quality, + this.quality, subConfig.Lz77, ref cacheBits, hashChain, @@ -455,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); // Create Huffman bit lengths and codes for each histogram image. var histogramImageSize = histogramImage.Count; @@ -498,7 +504,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } this.bitWriter.PutBits((uint)(histogramBits - 2), 3); - this.EncodeImageNoHuffman(histogramBgra, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); + this.EncodeImageNoHuffman(histogramBgra, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), this.quality); } // Store Huffman codes. @@ -572,7 +578,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan(), width * height); } - private void ApplyPredictFilter(int width, int height, int quality, bool usedSubtractGreen) + private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) { int nearLosslessStrength = 100; // TODO: for now always 100 bool exact = false; // TODO: always false for now. @@ -586,22 +592,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); this.bitWriter.PutBits((uint)(predBits - 2), 3); - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, quality); + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); } - private void ApplyCrossColorFilter(int width, int height, int quality) + private void ApplyCrossColorFilter(int width, int height) { int colorTransformBits = this.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); this.bitWriter.PutBits(WebPConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, quality); + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); } private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) @@ -1488,7 +1494,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var huffTree = new HuffmanTree[3 * maxNumSymbols]; for (int i = 0; i < huffTree.Length; i++) { - huffTree[i] = new HuffmanTree(); + huffTree[i] = default; } for (int i = 0; i < histogramImage.Count; i++) diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs index 3d0aca6db..a0a4674d1 100644 --- a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.Metadata; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index c6770daeb..0ee881880 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -18,10 +18,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool Lossy { get; set; } /// - public int Quality { get; set; } + public int Quality { get; set; } = 75; /// - public int Method { get; set; } + public int Method { get; set; } = 4; /// public bool AlphaCompression { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index a5bce7f8c..8943ce9bd 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -43,6 +43,11 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
private readonly int quality; + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly int method; + /// /// Initializes a new instance of the class. /// @@ -54,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.alphaCompression = options.AlphaCompression; this.lossy = options.Lossy; this.quality = options.Quality; + this.method = options.Method; } /// @@ -78,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height, this.quality); + var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method); enc.Encode(image, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 44ffa2be3..4777adb4f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -12,17 +15,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] - public void Encode_Lossless_Works(TestImageProvider provider, int quality) + public void Encode_Lossless_WithDifferentQuality_Works(TestImageProvider provider, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoder() + { + Lossy = false, + Quality = quality + }; + + using (Image image = provider.GetImage()) + { + var testOutputDetails = string.Concat("lossless", "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } + } + + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] + public void Encode_Lossless_WithDifferentMethods_Works(TestImageProvider provider, int method) where TPixel : unmanaged, IPixel { var encoder = new WebPEncoder() { - Lossy = false + Lossy = false, + Method = method, + Quality = 100 }; using (Image image = provider.GetImage()) { - var testOutputDetails = string.Concat("lossless", "_", quality); + var testOutputDetails = string.Concat("lossless", "_m", method); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } } From 86c038172910a75d202d798862c4e3f90d63717d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 7 Oct 2020 17:30:16 +0200 Subject: [PATCH 203/359] Select bitwriter which produces the least amount of bytes for the image --- .../Formats/WebP/BitReader/BitReaderBase.cs | 2 +- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 7 +++ .../Formats/WebP/Lossless/HistogramEncoder.cs | 21 +++++--- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 48 ++++++++++++++----- 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs index 75b846458..ed937201c 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader if (bytesToRead > 0) { - WebPThrowHelper.ThrowImageFormatException("image file has insufficient data"); + WebPThrowHelper.ThrowImageFormatException("webp image file has insufficient data"); } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 73f808b63..5a931259f 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -85,6 +85,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } + public void Reset(Vp8LBitWriter bwInit) + { + this.bits = bwInit.bits; + this.used = bwInit.used; + this.cur = bwInit.cur; + } + public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) { int depth = code.CodeLengths[codeIndex]; diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index 1497ca244..cdf259765 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -309,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// true if a greedy approach needs to be performed afterwards, false otherwise. private static bool HistogramCombineStochastic(List histograms, int minClusterSize) { - var rand = new Random(); + uint seed = 1; int triesWithNoSuccess = 0; var numUsed = histograms.Count(h => h != null); int outerIters = numUsed; @@ -350,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless for (int j = 0; numUsed >= 2 && j < numTries; j++) { // Choose two different histograms at random and try to combine them. - uint tmp = (uint)(rand.Next() % randRange); + uint tmp = MyRand(ref seed) % randRange; int idx1 = (int)(tmp / (numUsed - 1)); int idx2 = (int)(tmp % (numUsed - 1)); if (idx2 >= idx1) @@ -385,10 +386,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless bestIdx1 = histoPriorityList[0].Idx1; bestIdx2 = histoPriorityList[0].Idx2; - // TODO: Review this again, not sure why this is needed in the reference implementation. - // Pop bestIdx2 from mappings. - // var mappingIndex = Array.BinarySearch(mappings, bestIdx2); - // memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1)); + var mappingIndex = Array.IndexOf(mappings, bestIdx2); + Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); + Span dst = mappings.AsSpan(mappingIndex); + src.CopyTo(dst); // Merge the histograms and remove bestIdx2 from the queue. HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); @@ -680,5 +681,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return combineCostFactor; } + + // Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint MyRand(ref uint seed) + { + seed = (uint)(((ulong)seed * 48271u) % 2147483647u); + return seed; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 8ead78f86..c94d67438 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -252,7 +252,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { int width = image.Width; int height = image.Height; - int bytePosition = this.bitWriter.NumBytes(); // Convert image pixels to bgra array. Span bgra = this.Bgra.GetSpan(); @@ -269,12 +268,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero); - // TODO : Do we want to do this multi-threaded, this will probably require a second class: - // one which co-ordinates the threading and comparison and another which does the actual encoding + int bestSize = 0; + Vp8LBitWriter bitWriterInit = this.bitWriter; + Vp8LBitWriter bitWriterBest = this.bitWriter.Clone(); + bool isFirstConfig = true; foreach (CrunchConfig crunchConfig in crunchConfigs) { bool useCache = true; - this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; + this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || + crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; this.UseSubtractGreenTransform = (crunchConfig.EntropyIdx == EntropyIx.SubGreen) || (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || @@ -329,9 +331,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless useCache, crunchConfig, this.CacheBits, - this.HistoBits, - bytePosition); + this.HistoBits); + + // If we are better than what we already have. + if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) + { + bestSize = this.bitWriter.NumBytes(); + this.BitWriterSwap(ref this.bitWriter, ref bitWriterBest); + } + + // Reset the bit writer for the following iteration if any. + if (crunchConfigs.Length > 1) + { + this.bitWriter.Reset(bitWriterInit); + } + + isFirstConfig = false; } + + this.BitWriterSwap(ref bitWriterBest, ref this.bitWriter); } /// @@ -364,8 +382,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Go brute force on all transforms. foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast()) { - // We can only apply kPalette or kPaletteAndSpatial if we can indeed use - // a palette. + // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette. if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) { crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx }); @@ -405,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); var histogramSymbols = new ushort[histogramImageXySize]; @@ -432,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless BackwardReferenceEncoder.HashChainFill(hashChain, bgra, this.quality, width, height); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; - + Vp8LBitWriter bwInit = this.bitWriter; foreach (CrunchSubConfig subConfig in config.SubConfigs) { Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( @@ -451,8 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; // TODO : Loop based on cache/no cache - - // TODO: this.bitWriter.Reset(); + this.bitWriter.Reset(bwInit); var tmpHisto = new Vp8LHistogram(cacheBits); var histogramImage = new List(histogramImageXySize); for (int i = 0; i < histogramImageXySize; i++) @@ -1583,6 +1599,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + [MethodImpl(InliningOptions.ShortMethod)] + private void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst) + { + Vp8LBitWriter tmp = src; + src = dst; + dst = tmp; + } + /// /// Calculates the bits used for the transformation. /// From abe02719660cc4afd93d2f4f9be59bd360919083 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 10 Oct 2020 19:10:17 +0200 Subject: [PATCH 204/359] Fix in BackwardRefsWithLocalCache: mode was not changed to CacheIdx when there was a color cache hit --- .../WebP/Lossless/BackwardReferenceEncoder.cs | 25 +++++++++++-------- .../Formats/WebP/Lossless/PixOrCopy.cs | 12 +++------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 8af42a8b4..0be198c15 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint bestBgra; int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0; int lengthMax = (maxLen < 256) ? maxLen : 256; - pos = (int)chain[basePosition]; + pos = chain[basePosition]; int currLength; // Heuristic: use the comparison with the above line as an initialization. @@ -240,7 +240,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Vp8LBackwardRefs best, Vp8LBackwardRefs worst) { - var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits]; int lz77TypeBest = 0; double bitCostBest = -1; int cacheBitsInitial = cacheBits; @@ -276,8 +275,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Keep the best backward references. - histo[0] = new Vp8LHistogram(worst, cacheBitsTmp); - var bitCost = histo[0].EstimateBits(); + var histo = new Vp8LHistogram(worst, cacheBitsTmp); + var bitCost = histo.EstimateBits(); if (lz77TypeBest == 0 || bitCost < bitCostBest) { @@ -295,8 +294,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { Vp8LHashChain hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox; BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); - histo[0] = new Vp8LHistogram(worst, cacheBits); - double bitCostTrace = histo[0].EstimateBits(); + var histo = new Vp8LHistogram(worst, cacheBits); + double bitCostTrace = histo.EstimateBits(); if (bitCostTrace < bitCostBest) { best = worst; @@ -335,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // TODO: Don't use the enumerator here. - // Find the cache_bits giving the lowest entropy. + // Find the cacheBits giving the lowest entropy. using List.Enumerator c = refs.Refs.GetEnumerator(); while (c.MoveNext()) { @@ -384,11 +383,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint bgraPrev = bgra[pos] ^ 0xffffffffu; // TODO: Original has this loop? - // VP8LPrefixEncode(len, &code, &extra_bits, &extra_bits_value); - // for (i = 0; i <= cache_bits_max; ++i) + // int extraBits = 0, extraBitsValue = 0; + // int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); + // for (int i = 0; i <= cacheBitsMax; ++i) // { - // ++histos[i]->literal_[NUM_LITERAL_CODES + code]; + // ++histos[i].Literal[WebPConstants.NumLiteralCodes + code]; // } + // Update the color caches. do { @@ -957,7 +958,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (ix >= 0) { // Color cache contains bgraLiteral - PixOrCopy.CreateCacheIdx(ix); + v.Mode = PixOrCopyMode.CacheIdx; + v.BgraOrDistance = (uint)ix; + v.Len = 1; } else { diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs index 8974092e6..96f90c029 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs @@ -16,38 +16,32 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static PixOrCopy CreateCacheIdx(int idx) { - var retval = new PixOrCopy() + return new PixOrCopy() { Mode = PixOrCopyMode.CacheIdx, BgraOrDistance = (uint)idx, Len = 1 }; - - return retval; } public static PixOrCopy CreateLiteral(uint bgra) { - var retval = new PixOrCopy() + return new PixOrCopy() { Mode = PixOrCopyMode.Literal, BgraOrDistance = bgra, Len = 1 }; - - return retval; } public static PixOrCopy CreateCopy(uint distance, ushort len) { - var retval = new PixOrCopy() + return new PixOrCopy() { Mode = PixOrCopyMode.Copy, BgraOrDistance = distance, Len = len }; - - return retval; } public uint Literal(int component) From bc4d4ccc34368c71584fd01086a3ca629b71c90c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 11 Oct 2020 20:49:39 +0200 Subject: [PATCH 205/359] Write missing padding byte --- .../Formats/WebP/Lossless/HistogramEncoder.cs | 25 ++++++++----------- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 4 +++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs index cdf259765..68f2e5ff2 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs @@ -67,7 +67,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Cubic ramp between 1 and MaxHistoGreedy: int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); - RemoveEmptyHistograms(imageHisto); bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); if (doGreedy) { @@ -83,22 +82,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void RemoveEmptyHistograms(List histograms) { int size = 0; - var indicesToRemove = new List(); for (int i = 0; i < histograms.Count; i++) { if (histograms[i] == null) { - indicesToRemove.Add(i); continue; } histograms[size++] = histograms[i]; } - foreach (int index in indicesToRemove.OrderByDescending(i => i)) - { - histograms.RemoveAt(index); - } + histograms.RemoveRange(size, histograms.Count - size); } /// @@ -321,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return true; } - // Priority queue of histogram pairs. Its size impacts the quality of the compression and the speed: + // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: // the smaller the faster but the worse for the compression. var histoPriorityList = new List(); int maxSize = 9; @@ -338,7 +332,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless mappings[j++] = iter; } - // Collapse similar histograms + // Collapse similar histograms. for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) { double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; @@ -391,15 +385,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless Span dst = mappings.AsSpan(mappingIndex); src.CopyTo(dst); - // Merge the histograms and remove bestIdx2 from the queue. + // Merge the histograms and remove bestIdx2 from the list. HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; - histograms.RemoveAt(bestIdx2); + histograms[bestIdx2] = null; numUsed--; for (int j = 0; j < histoPriorityList.Count;) { - HistogramPair p = histoPriorityList.ElementAt(j); + HistogramPair p = histoPriorityList[j]; bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; bool doEval = false; @@ -408,13 +402,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // check for it all the time nevertheless. if (isIdx1Best && isIdx2Best) { + histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - numUsed--; continue; } // Any pair containing one of the two best indices should only refer to - // best_idx1. Its cost should also be updated. + // bestIdx1. Its cost should also be updated. if (isIdx1Best) { p.Idx1 = bestIdx1; @@ -440,8 +434,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); if (p.CostDiff >= 0.0d) { + histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - numUsed--; continue; } } @@ -628,6 +622,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair) { double sumCost = h1.BitCost + h2.BitCost; + pair.CostCombo = 0.0d; h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out var cost); pair.CostCombo = cost; pair.CostDiff = pair.CostCombo - sumCost; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index c94d67438..0fc1a6474 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -193,6 +193,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8LSize + pad; this.WriteRiffHeader(riffSize, vp8LSize, stream); this.bitWriter.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); + } } /// From 3ba7ebb55b71747eda7c89c1a0b9ffdaca3128a8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 14 Oct 2020 19:16:43 +0200 Subject: [PATCH 206/359] Convert RGB to YUV --- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 4 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 186 +++++++++++++++++- src/ImageSharp/Formats/WebP/WebPConstants.cs | 17 +- .../Formats/WebP/WebPLookupTables.cs | 17 ++ 4 files changed, 219 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 0fc1a6474..e951c70e7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -90,12 +90,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } /// - /// Gets transformed image data. + /// Gets memory for the transformed image data. /// public IMemoryOwner Bgra { get; } /// - /// Gets or sets the scratch memory for bgra rows used for prediction. + /// Gets or sets the scratch memory for bgra rows used for predictions. /// public IMemoryOwner BgraScratch { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 89cf1e3b4..365cd9ab2 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; +using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -12,18 +15,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Encoder for lossy webp images. /// - internal class Vp8Encoder + internal class Vp8Encoder : IDisposable { /// /// The to use for buffer allocations. /// - private MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; /// /// A bit writer for writing lossy webp streams. /// private readonly Vp8BitWriter bitWriter; + /// + /// Fixed-point precision for RGB->YUV. + /// + private const int YuvFix = 16; + + private const int YuvHalf = 1 << (YuvFix - 1); + /// /// Initializes a new instance of the class. /// @@ -34,14 +44,186 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.memoryAllocator = memoryAllocator; + var pixelCount = width * height; + var uvSize = (width >> 1) * (height >> 1); + this.Y = this.memoryAllocator.Allocate(pixelCount); + this.U = this.memoryAllocator.Allocate(uvSize); + this.V = this.memoryAllocator.Allocate(uvSize); + // TODO: properly initialize the bitwriter this.bitWriter = new Vp8BitWriter(); } + private IMemoryOwner Y { get; } + + private IMemoryOwner U { get; } + + private IMemoryOwner V { get; } + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { + int uvWidth = (image.Width + 1) >> 1; + + // Temporary storage for accumulated R/G/B values during conversion to U/V. + using (IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth)) + { + Span tmpRgbSpan = tmpRgb.GetSpan(); + int uvRowIndex = 0; + for (int rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) + { + // Downsample U/V planes, two rows at a time. + // TODO: RGBA case AccumulateRgba + Span rowSpan = image.GetPixelRowSpan(rowIndex); + Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); + uvRowIndex++; + + this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); + } + + // TODO: last row + } + throw new NotImplementedException(); } + + private void ConvertRgbaToY(Span rowSpan, Span y, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int x = 0; x < width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + y[x] = (byte)this.RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); + } + } + + /// + public void Dispose() + { + this.Y.Dispose(); + this.U.Dispose(); + this.V.Dispose(); + } + + private void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) + { + int rgbIdx = 0; + for (int i = 0; i < width; i += 1, rgbIdx += 4) + { + int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; + u[i] = (byte)this.RgbToU(r, g, b, YuvHalf << 2); + v[i] = (byte)this.RgbToV(r, g, b, YuvHalf << 2); + } + } + + private void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + + dst[dstIdx] = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.R) + + this.GammaToLinear(rgba1.R) + + this.GammaToLinear(rgba2.R) + + this.GammaToLinear(rgba3.R), 0); + dst[dstIdx + 1] = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.G) + + this.GammaToLinear(rgba1.G) + + this.GammaToLinear(rgba2.G) + + this.GammaToLinear(rgba3.G), 0); + dst[dstIdx + 2] = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.B) + + this.GammaToLinear(rgba1.B) + + this.GammaToLinear(rgba2.B) + + this.GammaToLinear(rgba3.B), 0); + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + + dst[dstIdx] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1); + dst[dstIdx + 1] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1); + dst[dstIdx + 2] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1); + } + } + + // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision + // U/V value, suitable for RGBToU/V calls. + [MethodImpl(InliningOptions.ShortMethod)] + private int LinearToGamma(uint baseValue, int shift) + { + int y = this.Interpolate((int)(baseValue << shift)); // Final uplifted value. + return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private uint GammaToLinear(byte v) + { + return WebPLookupTables.GammaToLinearTab[v]; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Interpolate(int v) + { + int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part + int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part + int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; + int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; + int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate + + return y; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int RgbToY(byte r, byte g, byte b, int rounding) + { + int luma = (16839 * r) + (33059 * g) + (6420 * b); + return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int RgbToU(int r, int g, int b, int rounding) + { + int u = (-9719 * r) - (19081 * g) + (28800 * b); + return this.ClipUv(u, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int RgbToV(int r, int g, int b, int rounding) + { + int v = (+28800 * r) - (24116 * g) - (4684 * b); + return this.ClipUv(v, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int ClipUv(int uv, int rounding) + { + uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 71fab497d..263caf673 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -185,9 +185,24 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int NumCtx = 3; - // this is the common stride for enc/dec + // This is the common stride for enc/dec. public const int Bps = 32; + // gamma-compensates loss of resolution during chroma subsampling. + public const double Gamma = 0.80d; + + public const int GammaFix = 12; // Fixed-point precision for linear values. + + public const int GammaScale = (1 << GammaFix) - 1; + + public const int GammaTabFix = 7; // Fixed-point fractional bits precision. + + public const int GammaTabSize = 1 << (GammaFix - GammaTabFix); + + public const int GammaTabScale = 1 << GammaTabFix; + + public const int GammaTabRounder = GammaTabScale >> 1; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 148dd0a54..59c8042be 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP @@ -17,6 +18,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly byte[,][] ModesProba = new byte[10, 10][]; + public static readonly ushort[] GammaToLinearTab = new ushort[256]; + + public static readonly int[] LinearToGammaTab = new int[WebPConstants.GammaTabSize + 1]; + /// /// Lookup table for small values of log2(int). /// @@ -764,6 +769,18 @@ namespace SixLabors.ImageSharp.Formats.WebP static WebPLookupTables() { + double scale = (double)(1 << WebPConstants.GammaTabFix) / WebPConstants.GammaScale; + double norm = 1.0d / 255.0d; + for (int v = 0; v < 256; ++v) + { + GammaToLinearTab[v] = (ushort)((Math.Pow(norm * v, WebPConstants.Gamma) * WebPConstants.GammaScale) + .5); + } + + for (int v = 0; v <= WebPConstants.GammaTabSize; ++v) + { + LinearToGammaTab[v] = (int)((255.0d * Math.Pow(scale * v, 1.0d / WebPConstants.Gamma)) + .5); + } + Abs0 = new Dictionary(); for (int i = -255; i <= 255; ++i) { From 5aaefa452ddf5d4235ddc835544a2151b423f353 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 15 Oct 2020 19:37:06 +0200 Subject: [PATCH 207/359] Convert RGBA to YUV420 --- .../Formats/WebP/Lossy/Vp8Encoder.cs | 136 +++++++++++++++-- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 + .../Formats/WebP/WebPLookupTables.cs | 140 ++++++++++++++++++ 3 files changed, 268 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 365cd9ab2..1d1136fdf 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy where TPixel : unmanaged, IPixel { int uvWidth = (image.Width + 1) >> 1; + bool hasAlpha = this.CheckNonOpaque(image); // Temporary storage for accumulated R/G/B values during conversion to U/V. using (IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth)) @@ -73,10 +74,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (int rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) { // Downsample U/V planes, two rows at a time. - // TODO: RGBA case AccumulateRgba Span rowSpan = image.GetPixelRowSpan(rowIndex); Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); - this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + if (!hasAlpha) + { + this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + } + else + { + this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + } + this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; @@ -90,6 +98,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy throw new NotImplementedException(); } + /// + public void Dispose() + { + this.Y.Dispose(); + this.U.Dispose(); + this.V.Dispose(); + } + + // Returns true if alpha has non-0xff values. + private bool CheckNonOpaque(Image image) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + for (int x = 0; x < image.Width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + if (rgba.A != 255) + { + return true; + } + } + } + + return false; + } + private void ConvertRgbaToY(Span rowSpan, Span y, int width) where TPixel : unmanaged, IPixel { @@ -102,14 +140,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - /// - public void Dispose() - { - this.Y.Dispose(); - this.U.Dispose(); - this.V.Dispose(); - } - private void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) { int rgbIdx = 0; @@ -171,6 +201,92 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + private void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.R) + + this.GammaToLinear(rgba1.R) + + this.GammaToLinear(rgba2.R) + + this.GammaToLinear(rgba3.R), 0); + g = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.G) + + this.GammaToLinear(rgba1.G) + + this.GammaToLinear(rgba2.G) + + this.GammaToLinear(rgba3.G), 0); + b = (ushort)this.LinearToGamma( + this.GammaToLinear(rgba0.B) + + this.GammaToLinear(rgba1.B) + + this.GammaToLinear(rgba2.B) + + this.GammaToLinear(rgba3.B), 0); + } + else + { + r = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + uint a = (uint)(2u * (rgba0.A + rgba1.A)); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1); + g = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1); + b = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1); + } + else + { + r = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + } + + private int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) + { + uint sum = (a0 * this.GammaToLinear(rgb0)) + (a1 * this.GammaToLinear(rgb1)) + (a2 * this.GammaToLinear(rgb2)) + (a3 * this.GammaToLinear(rgb3)); + return this.LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); + } + // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision // U/V value, suitable for RGBToU/V calls. [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 263caf673..4293018ef 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -203,6 +203,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int GammaTabRounder = GammaTabScale >> 1; + public const int AlphaFix = 19; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 59c8042be..448a4a3c2 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -767,6 +767,146 @@ namespace SixLabors.ImageSharp.Formats.WebP 118, 119, 120, 121, 122, 123, 124, 125, 126 }; + // Following table is (1 << AlphaFix) / a. The (v * InvAlpha[a]) >> AlphaFix + // formula is then equal to v / a in most (99.6%) cases. Note that this table + // and constant are adjusted very tightly to fit 32b arithmetic. + // In particular, they use the fact that the operands for 'v / a' are actually + // derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3 + // with ai in [0..255] and pi in [0..1< Date: Thu, 15 Oct 2020 19:50:58 +0200 Subject: [PATCH 208/359] Process last row during conversion to YUV when the height is uneven --- .../Formats/WebP/Lossy/Vp8Encoder.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 1d1136fdf..d29a5cf7a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.memoryAllocator = memoryAllocator; var pixelCount = width * height; - var uvSize = (width >> 1) * (height >> 1); + var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); @@ -71,7 +71,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { Span tmpRgbSpan = tmpRgb.GetSpan(); int uvRowIndex = 0; - for (int rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) + int rowIndex; + for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) { // Downsample U/V planes, two rows at a time. Span rowSpan = image.GetPixelRowSpan(rowIndex); @@ -92,7 +93,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); } - // TODO: last row + // Extra last row. + if ((image.Height & 1) != 0) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + if (!hasAlpha) + { + this.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); + } + else + { + this.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); + } + + this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + } } throw new NotImplementedException(); From 8d0df9202952779f8c0866344bf5e54f52914f92 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 17 Oct 2020 17:59:38 +0200 Subject: [PATCH 209/359] Add Vp8EncIterator --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 294 ++++++++++++++++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 59 +++- 2 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs new file mode 100644 index 000000000..da026b199 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -0,0 +1,294 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Iterator structure to iterate through macroblocks, pointing to the + /// right neighbouring data (samples, predictions, contexts, ...) + /// + internal class Vp8EncIterator : IDisposable + { + private const int YOffEnc = 0; + + private const int UOffEnc = 16; + + private const int VOffEnc = 16 + 8; + + private readonly int mbw; + + private readonly int mbh; + + public Vp8EncIterator(MemoryAllocator memoryAllocator, IMemoryOwner yTop, IMemoryOwner uvTop, int mbw, int mbh) + { + this.mbw = mbw; + this.mbh = mbh; + this.YTop = yTop; + this.UvTop = uvTop; + this.YuvIn = memoryAllocator.Allocate(WebPConstants.Bps * 16); + this.YuvOut = memoryAllocator.Allocate(WebPConstants.Bps * 16); + this.YuvOut2 = memoryAllocator.Allocate(WebPConstants.Bps * 16); + this.YLeft = memoryAllocator.Allocate(WebPConstants.Bps + 1); + this.ULeft = memoryAllocator.Allocate(16); + this.VLeft = memoryAllocator.Allocate(16); + this.TopNz = memoryAllocator.Allocate(9); + this.LeftNz = memoryAllocator.Allocate(9); + + this.Reset(); + } + + /// + /// Gets or sets the current macroblock X value. + /// + public int X { get; set; } + + /// + /// Gets or sets the current macroblock Y. + /// + public int Y { get; set; } + + /// + /// Gets or sets the input samples. + /// + public IMemoryOwner YuvIn { get; set; } + + /// + /// Gets or sets the output samples. + /// + public IMemoryOwner YuvOut { get; set; } + + public IMemoryOwner YuvOut2 { get; set; } + + /// + /// Gets or sets the left luma samples. + /// + public IMemoryOwner YLeft { get; set; } + + /// + /// Gets or sets the left u samples. + /// + public IMemoryOwner ULeft { get; set; } + + /// + /// Gets or sets the left v samples. + /// + public IMemoryOwner VLeft { get; set; } + + /// + /// Gets or sets the top luma samples at position 'X'. + /// + public IMemoryOwner YTop { get; set; } + + /// + /// Gets or sets the top u/v samples at position 'X', packed as 16 bytes. + /// + public IMemoryOwner UvTop { get; set; } + + /// + /// Gets or sets the non-zero pattern. + /// + public IMemoryOwner Nz { get; set; } + + /// + /// Gets or sets the top-non-zero context. + /// + public IMemoryOwner TopNz { get; set; } + + /// + /// Gets or sets the left-non-zero. leftNz[8] is independent. + /// + public IMemoryOwner LeftNz { get; set; } + + /// + /// Gets or sets the number of mb still to be processed. + /// + public int CountDown { get; set; } + + public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height) + { + int yStartIdx = ((this.Y * yStride) + this.X) * 16; + int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; + Span ySrc = y.Slice(yStartIdx); + Span uSrc = u.Slice(uvStartIdx); + Span vSrc = v.Slice(uvStartIdx); + int w = Math.Min(width - (this.X * 16), 16); + int h = Math.Min(height - (this.Y * 16), 16); + int uvw = (w + 1) >> 1; + int uvh = (h + 1) >> 1; + + Span yuvIn = this.YuvIn.Slice(YOffEnc); + Span uIn = this.YuvIn.Slice(UOffEnc); + Span vIn = this.YuvIn.Slice(VOffEnc); + this.ImportBlock(ySrc, yStride, yuvIn, w, h, 16); + this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); + this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); + + // Import source (uncompressed) samples into boundary. + if (this.X == 0) + { + this.InitLeft(); + } + else + { + Span yLeft = this.YLeft.GetSpan(); + Span uLeft = this.ULeft.GetSpan(); + Span vLeft = this.VLeft.GetSpan(); + if (this.Y == 0) + { + yLeft[0] = 127; + uLeft[0] = 127; + vLeft[0] = 127; + } + else + { + yLeft[0] = ySrc[-1 - yStride]; + uLeft[0] = uSrc[-1 - uvStride]; + vLeft[0] = vSrc[-1 - uvStride]; + } + + this.ImportLine(y.Slice(yStartIdx - 1), yStride, yLeft.Slice(1), h, 16); + this.ImportLine(u.Slice(uvStartIdx - 1), uvStride, uLeft.Slice(1), uvh, 8); + this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8); + } + + if (this.Y == 0) + { + this.YTop.GetSpan().Fill(127); + this.UvTop.GetSpan().Fill(127); + } + else + { + this.ImportLine(y.Slice(yStartIdx - yStride), 1, this.YTop.GetSpan(), w, 16); + this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8); + this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); + } + } + + public bool IsDone() + { + return this.CountDown <= 0; + } + + /// + public void Dispose() + { + this.YuvIn.Dispose(); + this.YuvOut.Dispose(); + this.YuvOut2.Dispose(); + this.YLeft.Dispose(); + this.ULeft.Dispose(); + this.VLeft.Dispose(); + this.Nz.Dispose(); + this.LeftNz.Dispose(); + this.TopNz.Dispose(); + } + + public bool Next(int mbw) + { + if (++this.X == mbw) + { + this.SetRow(++this.Y); + } + else + { + // TODO: + /* it->preds_ += 4; + it->mb_ += 1; + it->nz_ += 1; + it->y_top_ += 16; + it->uv_top_ += 16;*/ + } + + return --this.CountDown > 0; + } + + private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) + { + int dstIdx = 0; + for (int i = 0; i < h; ++i) + { + src.Slice(0, w).CopyTo(dst.Slice(dstIdx)); + if (w < size) + { + dst.Slice(dstIdx, size - w).Fill(dst[dstIdx + w - 1]); + } + + dstIdx += WebPConstants.Bps; + src = src.Slice(srcStride); + } + + for (int i = h; i < size; ++i) + { + dst.Slice(dstIdx - WebPConstants.Bps, size).CopyTo(dst); + dstIdx += WebPConstants.Bps; + } + } + + private void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen) + { + int i; + for (i = 0; i < len; ++i) + { + dst[i] = src[i]; + src = src.Slice(srcStride); + } + + for (; i < totalLen; ++i) + { + dst[i] = dst[len - 1]; + } + } + + private void Reset() + { + this.SetRow(0); + this.SetCountDown(this.mbw * this.mbh); + this.InitTop(); + // TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_)); + } + + private void SetRow(int y) + { + this.X = 0; + this.Y = y; + + // TODO: + // it->preds_ = enc->preds_ + y * 4 * enc->preds_w_; + // it->nz_ = enc->nz_; + // it->mb_ = enc->mb_info_ + y * enc->mb_w_; + // it->y_top_ = enc->y_top_; + // it->uv_top_ = enc->uv_top_; + } + + private void InitLeft() + { + Span yLeft = this.YLeft.GetSpan(); + Span uLeft = this.ULeft.GetSpan(); + Span vLeft = this.VLeft.GetSpan(); + byte val = (byte)((this.Y > 0) ? 129 : 127); + yLeft[0] = val; + uLeft[0] = val; + vLeft[0] = val; + this.YLeft.Slice(1).Fill(129); + this.ULeft.Slice(1).Fill(129); + this.VLeft.Slice(1).Fill(129); + this.LeftNz.GetSpan()[8] = 0; + } + + private void InitTop() + { + int topSize = this.mbw * 16; + this.YTop.Slice(0, topSize).Fill(127); + // TODO: memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_)); + } + + private void SetCountDown(int countDown) + { + this.CountDown = countDown; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index d29a5cf7a..1fddabaa0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -45,10 +45,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.memoryAllocator = memoryAllocator; var pixelCount = width * height; + int mbw = (width + 15) >> 4; var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); + this.YTop = this.memoryAllocator.Allocate(mbw * 16); + this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2); // TODO: properly initialize the bitwriter this.bitWriter = new Vp8BitWriter(); @@ -60,8 +63,54 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private IMemoryOwner V { get; } + /// + /// Gets the top luma samples. + /// + private IMemoryOwner YTop { get; } + + /// + /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). + /// + private IMemoryOwner UvTop { get; } + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel + { + this.ConvertRgbToYuv(image); + Span y = this.Y.GetSpan(); + Span u = this.U.GetSpan(); + Span v = this.V.GetSpan(); + + int mbw = (image.Width + 15) >> 4; + int mbh = (image.Height + 15) >> 4; + int yStride = image.Width; + int uvStride = (yStride + 1) >> 1; + var it = new Vp8EncIterator(this.memoryAllocator, this.YTop, this.UvTop, mbw, mbh); + if (!it.IsDone()) + { + do + { + it.Import(y, u, v, yStride, uvStride, image.Width, image.Height); + // TODO: MBAnalyze + } + while (it.Next(mbw)); + } + + throw new NotImplementedException(); + } + + /// + public void Dispose() + { + this.Y.Dispose(); + this.U.Dispose(); + this.V.Dispose(); + this.YTop.Dispose(); + this.UvTop.Dispose(); + } + + private void ConvertRgbToYuv(Image image) + where TPixel : unmanaged, IPixel { int uvWidth = (image.Width + 1) >> 1; bool hasAlpha = this.CheckNonOpaque(image); @@ -109,16 +158,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); } } - - throw new NotImplementedException(); - } - - /// - public void Dispose() - { - this.Y.Dispose(); - this.U.Dispose(); - this.V.Dispose(); } // Returns true if alpha has non-0xff values. From 79115d183e0a4fb439fc9e6b3ba269b244d47eae Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 22 Oct 2020 17:53:59 +0200 Subject: [PATCH 210/359] Add macro block analyse --- .../Formats/WebP/Lossless/Vp8LHistogram.cs | 111 ++++ .../Formats/WebP/Lossy/Vp8EncIterator.cs | 558 ++++++++++++++++-- .../Formats/WebP/Lossy/Vp8Encoder.cs | 80 ++- .../Formats/WebP/Lossy/Vp8MacroBlockInfo.cs | 18 + .../Formats/WebP/Lossy/Vp8MacroBlockType.cs | 12 + src/ImageSharp/Formats/WebP/WebPConstants.cs | 4 + .../Formats/WebP/WebPLookupTables.cs | 14 + 7 files changed, 737 insertions(+), 60 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index dd5c9ebda..a97dfc3de 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.WebP.Lossless { @@ -10,6 +11,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { private const uint NonTrivialSym = 0xffffffff; + /// + /// Size of histogram used by CollectHistogram. + /// + private const int MaxCoeffThresh = 31; + + private int maxValue; + + private int lastNonZero; + /// /// Initializes a new instance of the class. /// @@ -313,6 +323,101 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return true; } + public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) + { + int j; + var distribution = new int[MaxCoeffThresh + 1]; + for (j = startBlock; j < endBlock; ++j) + { + var output = new short[16]; + + this.Vp8FTransform(reference.Slice(WebPLookupTables.Vp8DspScan[j]), pred.Slice(WebPLookupTables.Vp8DspScan[j]), output); + + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + int v = Math.Abs(output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++distribution[clippedValue]; + } + } + + this.SetHistogramData(distribution); + } + + public int GetAlpha() + { + // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer + // values which happen to be mostly noise. This leaves the maximum precision + // for handling the useful small values which contribute most. + int maxValue = this.maxValue; + int lastNonZero = this.lastNonZero; + int alpha = (maxValue > 1) ? WebPConstants.AlphaScale * lastNonZero / maxValue : 0; + return alpha; + } + + private void SetHistogramData(int[] distribution) + { + int maxValue = 0; + int lastNonZero = 1; + for (int k = 0; k <= MaxCoeffThresh; ++k) + { + int value = distribution[k]; + if (value > 0) + { + if (value > maxValue) + { + maxValue = value; + } + + lastNonZero = k; + } + } + + this.maxValue = maxValue; + this.lastNonZero = lastNonZero; + } + + private void Vp8FTransform(Span src, Span reference, Span output) + { + int i; + var tmp = new int[16]; + for (i = 0; i < 4; ++i) + { + int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) + int d1 = src[1] - reference[1]; + int d2 = src[2] - reference[2]; + int d3 = src[3] - reference[3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + + // Do not change the span in the last iteration. + if (i < 3) + { + src = src.Slice(WebPConstants.Bps); + reference = reference.Slice(WebPConstants.Bps); + } + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } + } + private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) { if (this.IsUsed[0]) @@ -524,5 +629,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless output[i] = a[i] + b[i]; } } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipMax(int v, int max) + { + return (v > max) ? max : v; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index da026b199..fe7d4e095 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP.Lossy @@ -19,24 +20,92 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int VOffEnc = 16 + 8; + private const int MaxUvMode = 2; + + private const int MaxIntra16Mode = 2; + + private const int MaxIntra4Mode = 2; + private readonly int mbw; private readonly int mbh; - public Vp8EncIterator(MemoryAllocator memoryAllocator, IMemoryOwner yTop, IMemoryOwner uvTop, int mbw, int mbh) + /// + /// Stride of the prediction plane(=4*mbw + 1). + /// + private readonly int predsWidth; + + private const int I16DC16 = 0 * 16 * WebPConstants.Bps; + + private const int I16TM16 = I16DC16 + 16; + + private const int I16VE16 = 1 * 16 * WebPConstants.Bps; + + private const int I16HE16 = I16VE16 + 16; + + private const int C8DC8 = 2 * 16 * WebPConstants.Bps; + + private const int C8TM8 = C8DC8 + (1 * 16); + + private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); + + private const int C8HE8 = C8VE8 + (1 * 16); + + private readonly int[] vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + + private readonly int[] vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + + private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] + + private int currentMbIdx; + + private int nzIdx; + + private int predIdx; + + private int yTopIdx; + + private int uvTopIdx; + + public Vp8EncIterator(IMemoryOwner yTop, IMemoryOwner uvTop, IMemoryOwner preds, IMemoryOwner nz, Vp8MacroBlockInfo[] mb, int mbw, int mbh) { this.mbw = mbw; this.mbh = mbh; + this.Mb = mb; + this.currentMbIdx = 0; + this.nzIdx = 0; + this.predIdx = 0; + this.yTopIdx = 0; + this.uvTopIdx = 0; this.YTop = yTop; this.UvTop = uvTop; - this.YuvIn = memoryAllocator.Allocate(WebPConstants.Bps * 16); - this.YuvOut = memoryAllocator.Allocate(WebPConstants.Bps * 16); - this.YuvOut2 = memoryAllocator.Allocate(WebPConstants.Bps * 16); - this.YLeft = memoryAllocator.Allocate(WebPConstants.Bps + 1); - this.ULeft = memoryAllocator.Allocate(16); - this.VLeft = memoryAllocator.Allocate(16); - this.TopNz = memoryAllocator.Allocate(9); - this.LeftNz = memoryAllocator.Allocate(9); + this.Preds = preds; + this.Nz = nz; + this.predsWidth = (4 * mbw) + 1; + this.YuvIn = new byte[WebPConstants.Bps * 16]; + this.YuvOut = new byte[WebPConstants.Bps * 16]; + this.YuvOut2 = new byte[WebPConstants.Bps * 16]; + this.YuvP = new byte[(32 * WebPConstants.Bps) + (16 * WebPConstants.Bps) + (8 * WebPConstants.Bps)]; // I16+Chroma+I4 preds + this.YLeft = new byte[32]; + this.ULeft = new byte[32]; + this.VLeft = new byte[32]; + this.TopNz = new int[9]; + this.LeftNz = new int[9]; + + // To match the C++ initial values, initialize all with 204. + byte defaultInitVal = 204; + this.YuvIn.AsSpan().Fill(defaultInitVal); + this.YuvOut.AsSpan().Fill(defaultInitVal); + this.YuvOut2.AsSpan().Fill(defaultInitVal); + this.YuvP.AsSpan().Fill(defaultInitVal); + this.YLeft.AsSpan().Fill(defaultInitVal); + this.ULeft.AsSpan().Fill(defaultInitVal); + this.VLeft.AsSpan().Fill(defaultInitVal); + + for (int i = -255; i <= 255 + 255; ++i) + { + this.clip1[255 + i] = this.Clip8b(i); + } this.Reset(); } @@ -54,29 +123,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets or sets the input samples. /// - public IMemoryOwner YuvIn { get; set; } + public byte[] YuvIn { get; set; } /// /// Gets or sets the output samples. /// - public IMemoryOwner YuvOut { get; set; } + public byte[] YuvOut { get; set; } + + /// + /// Gets or sets the secondary buffer swapped with YuvOut. + /// + public byte[] YuvOut2 { get; set; } - public IMemoryOwner YuvOut2 { get; set; } + /// + /// Gets or sets the scratch buffer for prediction. + /// + public byte[] YuvP { get; set; } /// /// Gets or sets the left luma samples. /// - public IMemoryOwner YLeft { get; set; } + public byte[] YLeft { get; set; } /// /// Gets or sets the left u samples. /// - public IMemoryOwner ULeft { get; set; } + public byte[] ULeft { get; set; } /// /// Gets or sets the left v samples. /// - public IMemoryOwner VLeft { get; set; } + public byte[] VLeft { get; set; } /// /// Gets or sets the top luma samples at position 'X'. @@ -93,21 +170,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public IMemoryOwner Nz { get; set; } + /// + /// Gets or sets the intra mode predictors (4x4 blocks). + /// + public IMemoryOwner Preds { get; set; } + /// /// Gets or sets the top-non-zero context. /// - public IMemoryOwner TopNz { get; set; } + public int[] TopNz { get; set; } /// /// Gets or sets the left-non-zero. leftNz[8] is independent. /// - public IMemoryOwner LeftNz { get; set; } + public int[] LeftNz { get; set; } /// /// Gets or sets the number of mb still to be processed. /// public int CountDown { get; set; } + public Vp8MacroBlockInfo CurrentMacroBlockInfo + { + get + { + return this.Mb[this.currentMbIdx]; + } + } + + private Vp8MacroBlockInfo[] Mb { get; } + + // Import uncompressed samples from source. public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height) { int yStartIdx = ((this.Y * yStride) + this.X) * 16; @@ -120,9 +213,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int uvw = (w + 1) >> 1; int uvh = (h + 1) >> 1; - Span yuvIn = this.YuvIn.Slice(YOffEnc); - Span uIn = this.YuvIn.Slice(UOffEnc); - Span vIn = this.YuvIn.Slice(VOffEnc); + Span yuvIn = this.YuvIn.AsSpan(YOffEnc); + Span uIn = this.YuvIn.AsSpan(UOffEnc); + Span vIn = this.YuvIn.AsSpan(VOffEnc); this.ImportBlock(ySrc, yStride, yuvIn, w, h, 16); this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); @@ -134,9 +227,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - Span yLeft = this.YLeft.GetSpan(); - Span uLeft = this.ULeft.GetSpan(); - Span vLeft = this.VLeft.GetSpan(); + Span yLeft = this.YLeft.AsSpan(); + Span uLeft = this.ULeft.AsSpan(); + Span vLeft = this.VLeft.AsSpan(); if (this.Y == 0) { yLeft[0] = 127; @@ -145,9 +238,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - yLeft[0] = ySrc[-1 - yStride]; - uLeft[0] = uSrc[-1 - uvStride]; - vLeft[0] = vSrc[-1 - uvStride]; + yLeft[0] = y[yStartIdx - 1 - yStride]; + uLeft[0] = u[uvStartIdx - 1 - uvStride]; + vLeft[0] = v[uvStartIdx - 1 - uvStride]; } this.ImportLine(y.Slice(yStartIdx - 1), yStride, yLeft.Slice(1), h, 16); @@ -155,19 +248,155 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8); } + Span yTop = this.YTop.Slice(this.yTopIdx); if (this.Y == 0) { - this.YTop.GetSpan().Fill(127); + yTop.Fill(127); this.UvTop.GetSpan().Fill(127); } else { - this.ImportLine(y.Slice(yStartIdx - yStride), 1, this.YTop.GetSpan(), w, 16); + this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16); this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8); this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); } } + public int FastMbAnalyze(int quality) + { + // Empirical cut-off value, should be around 16 (~=block size). We use the + // [8-17] range and favor intra4 at high quality, intra16 for low quality. + int q = quality; + int kThreshold = 8 + ((17 - 8) * q / 100); + int k; + var dc = new uint[16]; + uint m; + uint m2; + for (k = 0; k < 16; k += 4) + { + this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebPConstants.Bps)), dc.AsSpan(k)); + } + + for (m = 0, m2 = 0, k = 0; k < 16; ++k) + { + m += dc[k]; + m2 += dc[k] * dc[k]; + } + + if (kThreshold * m2 < m * m) + { + this.SetIntra16Mode(0); // DC16 + } + else + { + var modes = new byte[16]; // DC4 + this.SetIntra4Mode(modes); + } + + return 0; + } + + public int MbAnalyzeBestIntra16Mode() + { + int maxMode = MaxIntra16Mode; + int mode; + int bestAlpha = -1; + int bestMode = 0; + + this.MakeLuma16Preds(); + for (mode = 0; mode < maxMode; ++mode) + { + var histo = new Vp8LHistogram(); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(this.vp8I16ModeOffsets[mode]), 0, 16); + int alpha = histo.GetAlpha(); + if (alpha > bestAlpha) + { + bestAlpha = alpha; + bestMode = mode; + } + } + + this.SetIntra16Mode(bestMode); + return bestAlpha; + } + + public int MbAnalyzeBestUvMode() + { + int bestAlpha = -1; + int smallestAlpha = 0; + int bestMode = 0; + int maxMode = MaxUvMode; + int mode; + + this.MakeChroma8Preds(); + for (mode = 0; mode < maxMode; ++mode) + { + var histo = new Vp8LHistogram(); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(this.vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + int alpha = histo.GetAlpha(); + if (alpha > bestAlpha) + { + bestAlpha = alpha; + } + + // The best prediction mode tends to be the one with the smallest alpha. + if (mode == 0 || alpha < smallestAlpha) + { + smallestAlpha = alpha; + bestMode = mode; + } + } + + this.SetIntraUvMode(bestMode); + return bestAlpha; + } + + public void SetIntra16Mode(int mode) + { + Span preds = this.Preds.Slice(this.predIdx); + for (int y = 0; y < 4; ++y) + { + preds.Slice(0, 4).Fill((byte)mode); + preds = preds.Slice(this.predsWidth); + } + + this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; + } + + private void SetIntra4Mode(byte[] modes) + { + int modesIdx = 0; + Span preds = this.Preds.Slice(this.predIdx); + for (int y = 4; y > 0; --y) + { + // TODO: + // memcpy(preds, modes, 4 * sizeof(*modes)); + preds = preds.Slice(this.predsWidth); + modesIdx += 4; + } + + this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; + } + + private void SetIntraUvMode(int mode) + { + this.CurrentMacroBlockInfo.UvMode = mode; + } + + public void SetSkip(bool skip) + { + this.CurrentMacroBlockInfo.Skip = skip; + } + + public void SetSegment(int segment) + { + this.CurrentMacroBlockInfo.Segment = segment; + } + + /// + /// Returns true if iteration is finished. + /// + /// True if iterator is finished. public bool IsDone() { return this.CountDown <= 0; @@ -176,36 +405,226 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public void Dispose() { - this.YuvIn.Dispose(); - this.YuvOut.Dispose(); - this.YuvOut2.Dispose(); - this.YLeft.Dispose(); - this.ULeft.Dispose(); - this.VLeft.Dispose(); - this.Nz.Dispose(); - this.LeftNz.Dispose(); - this.TopNz.Dispose(); } - public bool Next(int mbw) + /// + /// Go to next macroblock. + /// + /// Returns false if not finished. + public bool Next() { - if (++this.X == mbw) + if (++this.X == this.mbw) { this.SetRow(++this.Y); } else { - // TODO: - /* it->preds_ += 4; - it->mb_ += 1; - it->nz_ += 1; - it->y_top_ += 16; - it->uv_top_ += 16;*/ + this.currentMbIdx++; + this.nzIdx++; + this.predIdx += 4; + this.yTopIdx += 16; + this.uvTopIdx += 16; } return --this.CountDown > 0; } + private void Mean16x4(Span input, Span dc) + { + int x; + for (int k = 0; k < 4; ++k) + { + uint avg = 0; + for (int y = 0; y < 4; ++y) + { + for (x = 0; x < 4; ++x) + { + avg += input[x + (y * WebPConstants.Bps)]; + } + } + + dc[k] = avg; + input = input.Slice(4); // go to next 4x4 block. + } + } + + private void MakeLuma16Preds() + { + Span left = this.X != 0 ? this.YLeft.AsSpan() : null; + Span top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null; + this.EncPredLuma16(this.YuvP, left, top); + } + + private void MakeChroma8Preds() + { + Span left = this.X != 0 ? this.ULeft.AsSpan() : null; + Span top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; + this.EncPredChroma8(this.YuvP, left, top); + } + + // luma 16x16 prediction (paragraph 12.3) + private void EncPredLuma16(Span dst, Span left, Span top) + { + this.DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); + this.VerticalPred(dst.Slice(I16VE16), top, 16); + this.HorizontalPred(dst.Slice(I16HE16), left, 16); + this.TrueMotion(dst.Slice(I16TM16), left, top, 16); + } + + // Chroma 8x8 prediction (paragraph 12.2) + private void EncPredChroma8(Span dst, Span left, Span top) + { + // U block + this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + this.VerticalPred(dst.Slice(C8VE8), top, 8); + this.HorizontalPred(dst.Slice(C8HE8), left, 8); + this.TrueMotion(dst.Slice(C8TM8), left, top, 8); + + // V block + dst = dst.Slice(8); + if (top != null) + { + top = top.Slice(8); + } + + if (left != null) + { + left = left.Slice(16); + } + + this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + this.VerticalPred(dst.Slice(C8VE8), top, 8); + this.HorizontalPred(dst.Slice(C8HE8), left, 8); + this.TrueMotion(dst.Slice(C8TM8), left, top, 8); + } + + private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) + { + int dc = 0; + int j; + if (top != null) + { + for (j = 0; j < size; ++j) + { + dc += top[j]; + } + + if (left != null) + { + // top and left present. + left = left.Slice(1); // in the reference implementation, left starts at -1. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + } + else + { + // top, but no left. + dc += dc; + } + + dc = (dc + round) >> shift; + } + else if (left != null) + { + // left but no top. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + + dc += dc; + dc = (dc + round) >> shift; + } + else + { + // no top, no left, nothing. + dc = 0x80; + } + + this.Fill(dst, dc, size); + } + + private void VerticalPred(Span dst, Span top, int size) + { + if (top != null) + { + for (int j = 0; j < size; ++j) + { + top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); + } + } + else + { + this.Fill(dst, 127, size); + } + } + + private void HorizontalPred(Span dst, Span left, int size) + { + if (left != null) + { + left = left.Slice(1); // in the reference implementation, left starts at - 1. + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); + } + } + else + { + this.Fill(dst, 129, size); + } + } + + private void TrueMotion(Span dst, Span left, Span top, int size) + { + if (left != null) + { + if (top != null) + { + Span clip = this.clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 + for (int y = 0; y < size; ++y) + { + Span clipTable = clip.Slice(left[y + 1]); // left[y] + for (int x = 0; x < size; ++x) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + else + { + this.HorizontalPred(dst, left, size); + } + } + else + { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != null) + { + this.VerticalPred(dst, top, size); + } + else + { + this.Fill(dst, 129, size); + } + } + } + + private void Fill(Span dst, int value, int size) + { + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); + } + } + private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) { int dstIdx = 0; @@ -243,52 +662,77 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + /// + /// Restart a scan. + /// private void Reset() { this.SetRow(0); this.SetCountDown(this.mbw * this.mbh); this.InitTop(); + // TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_)); } + /// + /// Reset iterator position to row 'y'. + /// + /// The y position. private void SetRow(int y) { this.X = 0; this.Y = y; + this.currentMbIdx = y * this.mbw; + this.nzIdx = 0; + this.yTopIdx = 0; + this.uvTopIdx = 0; + + this.InitLeft(); // TODO: // it->preds_ = enc->preds_ + y * 4 * enc->preds_w_; - // it->nz_ = enc->nz_; - // it->mb_ = enc->mb_info_ + y * enc->mb_w_; - // it->y_top_ = enc->y_top_; - // it->uv_top_ = enc->uv_top_; } private void InitLeft() { - Span yLeft = this.YLeft.GetSpan(); - Span uLeft = this.ULeft.GetSpan(); - Span vLeft = this.VLeft.GetSpan(); + Span yLeft = this.YLeft.AsSpan(); + Span uLeft = this.ULeft.AsSpan(); + Span vLeft = this.VLeft.AsSpan(); byte val = (byte)((this.Y > 0) ? 129 : 127); yLeft[0] = val; uLeft[0] = val; vLeft[0] = val; - this.YLeft.Slice(1).Fill(129); - this.ULeft.Slice(1).Fill(129); - this.VLeft.Slice(1).Fill(129); - this.LeftNz.GetSpan()[8] = 0; + uLeft[16] = val; + vLeft[16] = val; + + yLeft.Slice(1, 16).Fill(129); + uLeft.Slice(1, 8).Fill(129); + vLeft.Slice(1, 8).Fill(129); + uLeft.Slice(1 + 16, 8).Fill(129); + vLeft.Slice(1 + 16, 8).Fill(129); + + this.LeftNz[8] = 0; } private void InitTop() { int topSize = this.mbw * 16; this.YTop.Slice(0, topSize).Fill(127); - // TODO: memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_)); + this.Nz.GetSpan().Fill(0); } + /// + /// Set count down. + /// + /// Number of iterations to go. private void SetCountDown(int countDown) { this.CountDown = countDown; } + + private byte Clip8b(int v) + { + return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 1fddabaa0..7aec1870d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -46,17 +46,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var pixelCount = width * height; int mbw = (width + 15) >> 4; + int mbh = (height + 15) >> 4; var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); this.YTop = this.memoryAllocator.Allocate(mbw * 16); this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2); + this.Preds = this.memoryAllocator.Allocate(((4 * mbw) + 1) * ((4 * mbh) + 1)); + this.Nz = this.memoryAllocator.Allocate(mbw + 1); // TODO: properly initialize the bitwriter this.bitWriter = new Vp8BitWriter(); } + /// + /// Gets or sets the global susceptibility. + /// + public int Alpha { get; set; } + private IMemoryOwner Y { get; } private IMemoryOwner U { get; } @@ -73,6 +81,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private IMemoryOwner UvTop { get; } + /// + /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). + /// + private IMemoryOwner Preds { get; } + + /// + /// Gets the non-zero pattern. + /// + private IMemoryOwner Nz { get; } + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { @@ -85,15 +103,30 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int mbh = (image.Height + 15) >> 4; int yStride = image.Width; int uvStride = (yStride + 1) >> 1; - var it = new Vp8EncIterator(this.memoryAllocator, this.YTop, this.UvTop, mbw, mbh); + var mb = new Vp8MacroBlockInfo[mbw * mbh]; + for (int i = 0; i < mb.Length; i++) + { + mb[i] = new Vp8MacroBlockInfo(); + } + + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh); + int method = 4; // TODO: hardcoded for now + int quality = 100; // TODO: hardcoded for now + var alphas = new int[WebPConstants.MaxAlpha + 1]; + int uvAlpha = 0; + int alpha = 0; if (!it.IsDone()) { do { it.Import(y, u, v, yStride, uvStride, image.Width, image.Height); - // TODO: MBAnalyze + int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); + + // Accumulate for later complexity analysis. + alpha += bestAlpha; + uvAlpha += bestUvAlpha; } - while (it.Next(mbw)); + while (it.Next()); } throw new NotImplementedException(); @@ -107,6 +140,34 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.V.Dispose(); this.YTop.Dispose(); this.UvTop.Dispose(); + this.Preds.Dispose(); + } + + private int MbAnalyze(Vp8EncIterator it, int method, int quality, int[] alphas, out int bestUvAlpha) + { + it.SetIntra16Mode(0); // default: Intra16, DC_PRED + it.SetSkip(false); // not skipped. + it.SetSegment(0); // default segment, spec-wise. + + int bestAlpha; + if (method <= 1) + { + bestAlpha = it.FastMbAnalyze(quality); + } + else + { + bestAlpha = it.MbAnalyzeBestIntra16Mode(); + } + + bestUvAlpha = it.MbAnalyzeBestUvMode(); + + // Final susceptibility mix. + bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2; + bestAlpha = this.FinalAlphaValue(bestAlpha); + alphas[bestAlpha]++; + it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. + + return bestAlpha; // Mixed susceptibility (not just luma) } private void ConvertRgbToYuv(Image image) @@ -395,5 +456,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; } + + [MethodImpl(InliningOptions.ShortMethod)] + private int FinalAlphaValue(int alpha) + { + alpha = WebPConstants.MaxAlpha - alpha; + return this.Clip(alpha, 0, WebPConstants.MaxAlpha); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Clip(int v, int min, int max) + { + return (v < min) ? min : (v > max) ? max : v; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs new file mode 100644 index 000000000..294e548e8 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8MacroBlockInfo + { + public Vp8MacroBlockType MacroBlockType { get; set; } + + public int UvMode { get; set; } + + public bool Skip { get; set; } + + public int Segment { get; set; } + + public int Alpha { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs new file mode 100644 index 000000000..1178851ca --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal enum Vp8MacroBlockType + { + I4X4 = 0, + + I16X16 = 1 + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 4293018ef..cb751e216 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -205,6 +205,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int AlphaFix = 19; + public const int MaxAlpha = 255; + + public const int AlphaScale = 2 * MaxAlpha; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 448a4a3c2..1a38912ea 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -22,6 +22,20 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly int[] LinearToGammaTab = new int[WebPConstants.GammaTabSize + 1]; + // Compute susceptibility based on DCT-coeff histograms: + // the higher, the "easier" the macroblock is to compress. + public static readonly int[] Vp8DspScan = + { + // Luma + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), + 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), + 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), + 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U + 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V + }; + /// /// Lookup table for small values of log2(int). /// From 05f1466a4eb22b26a780032c20e8d7c87128ffe7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Oct 2020 18:44:04 +0200 Subject: [PATCH 211/359] Fix some mistakes during macro block analysis --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 61 ++++++++----------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 6 +- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index fe7d4e095..a87caa34b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -87,20 +87,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YuvOut2 = new byte[WebPConstants.Bps * 16]; this.YuvP = new byte[(32 * WebPConstants.Bps) + (16 * WebPConstants.Bps) + (8 * WebPConstants.Bps)]; // I16+Chroma+I4 preds this.YLeft = new byte[32]; - this.ULeft = new byte[32]; - this.VLeft = new byte[32]; + this.UvLeft = new byte[32]; this.TopNz = new int[9]; this.LeftNz = new int[9]; - // To match the C++ initial values, initialize all with 204. + // To match the C++ initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; this.YuvIn.AsSpan().Fill(defaultInitVal); this.YuvOut.AsSpan().Fill(defaultInitVal); this.YuvOut2.AsSpan().Fill(defaultInitVal); this.YuvP.AsSpan().Fill(defaultInitVal); this.YLeft.AsSpan().Fill(defaultInitVal); - this.ULeft.AsSpan().Fill(defaultInitVal); - this.VLeft.AsSpan().Fill(defaultInitVal); + this.UvLeft.AsSpan().Fill(defaultInitVal); for (int i = -255; i <= 255 + 255; ++i) { @@ -146,14 +144,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public byte[] YLeft { get; set; } /// - /// Gets or sets the left u samples. + /// Gets or sets the left uv samples. /// - public byte[] ULeft { get; set; } - - /// - /// Gets or sets the left v samples. - /// - public byte[] VLeft { get; set; } + public byte[] UvLeft { get; set; } /// /// Gets or sets the top luma samples at position 'X'. @@ -228,8 +221,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy else { Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.ULeft.AsSpan(); - Span vLeft = this.VLeft.AsSpan(); + Span uLeft = this.UvLeft.AsSpan(0, 16); + Span vLeft = this.UvLeft.AsSpan(16, 16); if (this.Y == 0) { yLeft[0] = 127; @@ -366,12 +359,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void SetIntra4Mode(byte[] modes) { int modesIdx = 0; - Span preds = this.Preds.Slice(this.predIdx); + int predIdx = this.predIdx; for (int y = 4; y > 0; --y) { - // TODO: - // memcpy(preds, modes, 4 * sizeof(*modes)); - preds = preds.Slice(this.predsWidth); + modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.Slice(predIdx)); + predIdx += this.predsWidth; modesIdx += 4; } @@ -457,12 +449,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void MakeChroma8Preds() { - Span left = this.X != 0 ? this.ULeft.AsSpan() : null; + Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; this.EncPredChroma8(this.YuvP, left, top); } - // luma 16x16 prediction (paragraph 12.3) + // luma 16x16 prediction (paragraph 12.3). private void EncPredLuma16(Span dst, Span left, Span top) { this.DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); @@ -471,16 +463,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.TrueMotion(dst.Slice(I16TM16), left, top, 16); } - // Chroma 8x8 prediction (paragraph 12.2) + // Chroma 8x8 prediction (paragraph 12.2). private void EncPredChroma8(Span dst, Span left, Span top) { - // U block + // U block. this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); this.VerticalPred(dst.Slice(C8VE8), top, 8); this.HorizontalPred(dst.Slice(C8HE8), left, 8); this.TrueMotion(dst.Slice(C8TM8), left, top, 8); - // V block + // V block. dst = dst.Slice(8); if (top != null) { @@ -529,6 +521,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy else if (left != null) { // left but no top. + left = left.Slice(1); // in the reference implementation, left starts at -1. for (j = 0; j < size; ++j) { dc += left[j]; @@ -628,16 +621,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) { int dstIdx = 0; + int srcIdx = 0; for (int i = 0; i < h; ++i) { - src.Slice(0, w).CopyTo(dst.Slice(dstIdx)); + src.Slice(srcIdx, w).CopyTo(dst.Slice(dstIdx)); if (w < size) { dst.Slice(dstIdx, size - w).Fill(dst[dstIdx + w - 1]); } dstIdx += WebPConstants.Bps; - src = src.Slice(srcStride); + srcIdx += srcStride; } for (int i = h; i < size; ++i) @@ -650,10 +644,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen) { int i; + int srcIdx = 0; for (i = 0; i < len; ++i) { - dst[i] = src[i]; - src = src.Slice(srcStride); + dst[i] = src[srcIdx]; + srcIdx += srcStride; } for (; i < totalLen; ++i) @@ -686,30 +681,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.nzIdx = 0; this.yTopIdx = 0; this.uvTopIdx = 0; + this.predIdx = y * 4 * this.predsWidth; this.InitLeft(); - - // TODO: - // it->preds_ = enc->preds_ + y * 4 * enc->preds_w_; } private void InitLeft() { Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.ULeft.AsSpan(); - Span vLeft = this.VLeft.AsSpan(); + Span uLeft = this.UvLeft.AsSpan(0, 16); + Span vLeft = this.UvLeft.AsSpan(16, 16); byte val = (byte)((this.Y > 0) ? 129 : 127); yLeft[0] = val; uLeft[0] = val; vLeft[0] = val; - uLeft[16] = val; - vLeft[16] = val; yLeft.Slice(1, 16).Fill(129); uLeft.Slice(1, 8).Fill(129); vLeft.Slice(1, 8).Fill(129); - uLeft.Slice(1 + 16, 8).Fill(129); - vLeft.Slice(1 + 16, 8).Fill(129); this.LeftNz[8] = 0; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 7aec1870d..a38b167c0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy alphas[bestAlpha]++; it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. - return bestAlpha; // Mixed susceptibility (not just luma) + return bestAlpha; // Mixed susceptibility (not just luma). } private void ConvertRgbToYuv(Image image) @@ -420,8 +420,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy [MethodImpl(InliningOptions.ShortMethod)] private int Interpolate(int v) { - int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part - int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part + int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. + int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate From 65fb30adc90b52c42e221985660dc306427fa2a1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 26 Oct 2020 11:24:24 +0100 Subject: [PATCH 212/359] Refine intra16/intra4 sub-modes based on distortion (still WIP) --- .../Formats/WebP/Lossy/LossyUtils.cs | 36 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 599 +++++++++++++++++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 301 ++++++++- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 104 +++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 9 + .../Formats/WebP/WebPLookupTables.cs | 116 ++++ 6 files changed, 1101 insertions(+), 64 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 5d8d8b545..83e8e95e1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -722,6 +722,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); } + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg2(byte a, byte b) + { + return (byte)((a + b + 1) >> 1); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg3(byte a, byte b, byte c) + { + return (byte)((a + (2 * b) + c + 2) >> 2); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Dst(Span dst, int x, int y, byte v) + { + dst[x + (y * WebPConstants.Bps)] = v; + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( Span p, @@ -949,24 +967,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Avg2(byte a, byte b) - { - return (byte)((a + b + 1) >> 1); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Avg3(byte a, byte b, byte c) - { - return (byte)((a + (2 * b) + c + 2) >> 2); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Dst(Span dst, int x, int y, byte v) - { - dst[x + (y * WebPConstants.Bps)] = v; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int Clamp255(int x) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index a87caa34b..f1160d8f1 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; @@ -12,13 +13,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// Iterator structure to iterate through macroblocks, pointing to the /// right neighbouring data (samples, predictions, contexts, ...) /// - internal class Vp8EncIterator : IDisposable + internal class Vp8EncIterator { - private const int YOffEnc = 0; + public const int YOffEnc = 0; - private const int UOffEnc = 16; + public const int UOffEnc = 16; - private const int VOffEnc = 16 + 8; + public const int VOffEnc = 16 + 8; private const int MaxUvMode = 2; @@ -51,12 +52,43 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int C8HE8 = C8VE8 + (1 * 16); - private readonly int[] vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; - private readonly int[] vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + + private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; + + private const int I4TM4 = I4DC4 + 4; + + private const int I4VE4 = I4DC4 + 8; + + private const int I4HE4 = I4DC4 + 12; + + private const int I4RD4 = I4DC4 + 16; + + private const int I4VR4 = I4RD4 + 20; + + private const int I4LD4 = I4RD4 + 24; + + private const int I4VL4 = I4RD4 + 28; + + private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); + + private const int I4HU4 = I4HD4 + 4; + + public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] + // Array to record the position of the top sample to pass to the prediction functions. + private readonly byte[] vp8TopLeftI4 = + { + 17, 21, 25, 29, + 13, 17, 21, 25, + 9, 13, 17, 21, + 5, 9, 13, 17 + }; + private int currentMbIdx; private int nzIdx; @@ -90,6 +122,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.UvLeft = new byte[32]; this.TopNz = new int[9]; this.LeftNz = new int[9]; + this.I4Boundary = new byte[37]; // To match the C++ initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; @@ -158,25 +191,40 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public IMemoryOwner UvTop { get; set; } + /// + /// Gets or sets the intra mode predictors (4x4 blocks). + /// + public IMemoryOwner Preds { get; set; } + /// /// Gets or sets the non-zero pattern. /// public IMemoryOwner Nz { get; set; } /// - /// Gets or sets the intra mode predictors (4x4 blocks). + /// Gets 32+5 boundary samples needed by intra4x4. /// - public IMemoryOwner Preds { get; set; } + public byte[] I4Boundary { get; } + + /// + /// Gets or sets the index to the current top boundary sample. + /// + public int I4BoundaryIdx { get; set; } + + /// + /// Gets or sets the current intra4x4 mode being tested. + /// + public int I4 { get; set; } /// /// Gets or sets the top-non-zero context. /// - public int[] TopNz { get; set; } + public int[] TopNz { get; } /// /// Gets or sets the left-non-zero. leftNz[8] is independent. /// - public int[] LeftNz { get; set; } + public int[] LeftNz { get; } /// /// Gets or sets the number of mb still to be processed. @@ -193,6 +241,56 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private Vp8MacroBlockInfo[] Mb { get; } + public void Init() + { + this.Reset(); + } + + public void InitFilter() + { + // TODO: add support for autofilter + } + + public void StartI4() + { + int i; + this.I4 = 0; // first 4x4 sub-block. + this.I4BoundaryIdx = this.vp8TopLeftI4[0]; + + // Import the boundary samples. + for (i = 0; i < 17; ++i) + { + // left + this.I4Boundary[i] = this.YLeft[15 - i]; + } + + Span yTop = this.YTop.GetSpan(); + for (i = 0; i < 16; ++i) + { + // top + this.I4Boundary[17 + i] = yTop[i]; + } + + // top-right samples have a special case on the far right of the picture + if (this.X < this.mbw - 1) + { + for (i = 16; i < 16 + 4; ++i) + { + this.I4Boundary[17 + i] = yTop[i]; + } + } + else + { + // else, replicate the last valid pixel four times + for (i = 16; i < 16 + 4; ++i) + { + this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; + } + } + + NzToBytes(); // import the non-zero context. + } + // Import uncompressed samples from source. public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height) { @@ -300,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(this.vp8I16ModeOffsets[mode]), 0, 16); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -325,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(this.vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -356,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; } - private void SetIntra4Mode(byte[] modes) + public void SetIntra4Mode(byte[] modes) { int modesIdx = 0; int predIdx = this.predIdx; @@ -370,7 +468,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; } - private void SetIntraUvMode(int mode) + public short[] GetCostModeI4(byte[] modes) + { + int predsWidth = this.predsWidth; + int x = this.I4 & 3; + int y = this.I4 >> 2; + int left = (int)((x == 0) ? this.Preds.GetSpan()[(y * predsWidth) - 1] : modes[this.I4 - 1]); + int top = (int)((y == 0) ? this.Preds.GetSpan()[-predsWidth + x] : modes[this.I4 - 4]); + return WebPLookupTables.Vp8FixedCostsI4[top, left]; + } + + public void SetIntraUvMode(int mode) { this.CurrentMacroBlockInfo.UvMode = mode; } @@ -394,11 +502,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return this.CountDown <= 0; } - /// - public void Dispose() - { - } - /// /// Go to next macroblock. /// @@ -421,39 +524,141 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return --this.CountDown > 0; } - private void Mean16x4(Span input, Span dc) + public void SaveBoundary() { - int x; - for (int k = 0; k < 4; ++k) + int x = this.X; + int y = this.Y; + Span ySrc = this.YuvOut.AsSpan(YOffEnc); + Span uvSrc = this.YuvOut.AsSpan(UOffEnc); + if (x < this.mbw - 1) { - uint avg = 0; - for (int y = 0; y < 4; ++y) + // left + for (int i = 0; i < 16; ++i) { - for (x = 0; x < 4; ++x) - { - avg += input[x + (y * WebPConstants.Bps)]; - } + this.YLeft[i + 1] = ySrc[15 + (i * WebPConstants.Bps)]; } - dc[k] = avg; - input = input.Slice(4); // go to next 4x4 block. + for (int i = 0; i < 8; ++i) + { + this.UvLeft[i + 1] = uvSrc[7 + (i * WebPConstants.Bps)]; + this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebPConstants.Bps)]; + } + + // top-left (before 'top'!) + this.YLeft[0] = this.YTop.GetSpan()[15]; + this.UvLeft[0] = this.UvTop.GetSpan()[0 + 7]; + this.UvLeft[16] = this.UvTop.GetSpan()[8 + 7]; + } + + if (y < this.mbh - 1) + { + // top + ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.GetSpan()); + uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.GetSpan()); } } - private void MakeLuma16Preds() + public bool RotateI4(Span yuvOut) + { + Span blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]); + Span top = this.I4Boundary.AsSpan(this.I4BoundaryIdx); + int i; + + // Update the cache with 7 fresh samples. + for (i = 0; i <= 3; ++i) + { + top[-4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples. + } + + if ((this.I4 & 3) != 3) + { + // if not on the right sub-blocks #3, #7, #11, #15 + for (i = 0; i <= 2; ++i) + { + // store future left samples + top[i] = blk[3 + ((2 - i) * WebPConstants.Bps)]; + } + } + else + { + // else replicate top-right samples, as says the specs. + for (i = 0; i <= 3; ++i) + { + top[i] = top[i + 4]; + } + } + + // move pointers to next sub-block + ++this.I4; + if (this.I4 == 16) + { + // we're done + return false; + } + + this.I4BoundaryIdx = this.vp8TopLeftI4[this.I4]; + + return true; + } + + public void ResetAfterSkip() + { + if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) + { + // Reset all predictors. + this.Nz.GetSpan()[0] = 0; + this.LeftNz[8] = 0; + } + else + { + this.Nz.GetSpan()[0] &= 1 << 24; // Preserve the dc_nz bit. + } + } + + public void MakeLuma16Preds() { Span left = this.X != 0 ? this.YLeft.AsSpan() : null; Span top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null; this.EncPredLuma16(this.YuvP, left, top); } - private void MakeChroma8Preds() + public void MakeChroma8Preds() { Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; this.EncPredChroma8(this.YuvP, left, top); } + public void MakeIntra4Preds() + { + this.EncPredLuma4(this.YuvP, this.I4Boundary.AsSpan(this.I4BoundaryIdx)); + } + + public void SwapOut() + { + byte[] tmp = this.YuvOut; + this.YuvOut = this.YuvOut2; + this.YuvOut2 = tmp; + } + + private void Mean16x4(Span input, Span dc) + { + for (int k = 0; k < 4; ++k) + { + uint avg = 0; + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + avg += input[x + (y * WebPConstants.Bps)]; + } + } + + dc[k] = avg; + input = input.Slice(4); // go to next 4x4 block. + } + } + // luma 16x16 prediction (paragraph 12.3). private void EncPredLuma16(Span dst, Span left, Span top) { @@ -490,6 +695,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.TrueMotion(dst.Slice(C8TM8), left, top, 8); } + // Left samples are top[-5 .. -2], top_left is top[-1], top are + // located at top[0..3], and top right is top[4..7] + private void EncPredLuma4(Span dst, Span top) + { + this.Dc4(dst.Slice(I4DC4), top); + this.Tm4(dst.Slice(I4TM4), top); + this.Ve4(dst.Slice(I4VE4), top); + this.He4(dst.Slice(I4HE4), top); + this.Rd4(dst.Slice(I4RD4), top); + this.Vr4(dst.Slice(I4VR4), top); + this.Ld4(dst.Slice(I4LD4), top); + this.Vl4(dst.Slice(I4VL4), top); + this.Hd4(dst.Slice(I4HD4), top); + this.Hu4(dst.Slice(I4HU4), top); + } + private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) { int dc = 0; @@ -610,6 +831,272 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + private void Dc4(Span dst, Span top) + { + uint dc = 4; + int i; + for (i = 0; i < 4; ++i) + { + dc += (uint)(top[i] + top[-5 + i]); + } + + this.Fill(dst, (int)(dc >> 3), 4); + } + + private void Tm4(Span dst, Span top) + { + Span clip = this.clip1.AsSpan(255 - top[-1]); + for (int y = 0; y < 4; ++y) + { + Span clipTable = clip.Slice(top[-2 - y]); + for (int x = 0; x < 4; ++x) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + + private void Ve4(Span dst, Span top) + { + // vertical + byte[] vals = + { + LossyUtils.Avg3(top[-1], top[0], top[1]), + LossyUtils.Avg3(top[ 0], top[1], top[2]), + LossyUtils.Avg3(top[ 1], top[2], top[3]), + LossyUtils.Avg3(top[ 2], top[3], top[4]) + }; + + for (int i = 0; i < 4; ++i) + { + vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); + } + } + + private void He4(Span dst, Span top) + { + // horizontal + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + + uint val = 0x01010101U * LossyUtils.Avg3(X, I, J); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * LossyUtils.Avg3(I, J, K); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(J, K, L); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(K, L, L); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + } + + private void Rd4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(J, K, L)); + var ijk = LossyUtils.Avg3(I, J, K); + LossyUtils.Dst(dst, 0, 2, ijk); + LossyUtils.Dst(dst, 1, 3, ijk); + var xij = LossyUtils.Avg3(X, I, J); + LossyUtils.Dst(dst, 0, 1, xij); + LossyUtils.Dst(dst, 1, 2, xij); + LossyUtils.Dst(dst, 2, 3, xij); + var axi = LossyUtils.Avg3(A, X, I); + LossyUtils.Dst(dst, 0, 0, axi); + LossyUtils.Dst(dst, 1, 1, axi); + LossyUtils.Dst(dst, 2, 2, axi); + LossyUtils.Dst(dst, 3, 3, axi); + var bax = LossyUtils.Avg3(B, A, X); + LossyUtils.Dst(dst, 1, 0, bax); + LossyUtils.Dst(dst, 2, 1, bax); + LossyUtils.Dst(dst, 3, 2, bax); + var cba = LossyUtils.Avg3(C, B, A); + LossyUtils.Dst(dst, 2, 0, cba); + LossyUtils.Dst(dst, 3, 1, cba); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(D, C, B)); + } + + private void Vr4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + + var xa = LossyUtils.Avg2(X, A); + LossyUtils.Dst(dst, 0, 0, xa); + LossyUtils.Dst(dst, 1, 2, xa); + var ab = LossyUtils.Avg2(A, B); + LossyUtils.Dst(dst, 1, 0, ab); + LossyUtils.Dst(dst, 2, 2, ab); + var bc = LossyUtils.Avg2(B, C); + LossyUtils.Dst(dst, 2, 0, bc); + LossyUtils.Dst(dst, 3, 2, bc); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(C, D)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(K, J, I)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(J, I, X)); + var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 0, 1, ixa); + LossyUtils.Dst(dst, 1, 3, ixa); + var xab = LossyUtils.Avg3(X, A, B); + LossyUtils.Dst(dst, 1, 1, xab); + LossyUtils.Dst(dst, 2, 3, xab); + var abc = LossyUtils.Avg3(A, B, C); + LossyUtils.Dst(dst, 2, 1, abc); + LossyUtils.Dst(dst, 3, 3, abc); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(B, C, D)); + } + + private void Ld4(Span dst, Span top) + { + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + byte E = top[4]; + byte F = top[5]; + byte G = top[6]; + byte H = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(A, B, C)); + var bcd = LossyUtils.Avg3(B, C, D); + LossyUtils.Dst(dst, 1, 0, bcd); + LossyUtils.Dst(dst, 0, 1, bcd); + var cde = LossyUtils.Avg3(C, D, E); + LossyUtils.Dst(dst, 2, 0, cde); + LossyUtils.Dst(dst, 1, 1, cde); + LossyUtils.Dst(dst, 0, 2, cde); + var def = LossyUtils.Avg3(D, E, F); + LossyUtils.Dst(dst, 3, 0, def); + LossyUtils.Dst(dst, 2, 1, def); + LossyUtils.Dst(dst, 1, 2, def); + LossyUtils.Dst(dst, 0, 3, def); + var efg = LossyUtils.Avg3(E, F, G); + LossyUtils.Dst(dst, 3, 1, efg); + LossyUtils.Dst(dst, 2, 2, efg); + LossyUtils.Dst(dst, 1, 3, efg); + var fgh = LossyUtils.Avg3(F, G, H); + LossyUtils.Dst(dst, 3, 2, fgh); + LossyUtils.Dst(dst, 2, 3, fgh); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(G, H, H)); + } + + private void Vl4(Span dst, Span top) + { + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + byte E = top[4]; + byte F = top[5]; + byte G = top[6]; + byte H = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(A, B)); + var bc = LossyUtils.Avg2(B, C); + LossyUtils.Dst(dst, 1, 0, bc); + LossyUtils.Dst(dst, 0, 2, bc); + var cd = LossyUtils.Avg2(C, D); + LossyUtils.Dst(dst, 2, 0, cd); + LossyUtils.Dst(dst, 1, 2, cd); + var de = LossyUtils.Avg2(D, E); + LossyUtils.Dst(dst, 3, 0, de); + LossyUtils.Dst(dst, 2, 2, de); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(A, B, C)); + var bcd = LossyUtils.Avg3(B,C,D); + LossyUtils.Dst(dst, 1, 1, bcd); + LossyUtils.Dst(dst, 0, 3, bcd); + var cde = LossyUtils.Avg3(C, D, E); + LossyUtils.Dst(dst, 2, 1, cde); + LossyUtils.Dst(dst, 1, 3, cde); + var def = LossyUtils.Avg3(D, E, F); + LossyUtils.Dst(dst, 3, 1, def); + LossyUtils.Dst(dst, 2, 3, def); + LossyUtils.Dst(dst, 3,2, LossyUtils.Avg3(E, F, G)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(F, G, H)); + } + + private void Hd4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + + var ix = LossyUtils.Avg2(I, X); + LossyUtils.Dst(dst, 0, 0, ix); + LossyUtils.Dst(dst, 2, 1, ix); + var ji = LossyUtils.Avg2(J,I); + LossyUtils.Dst(dst, 0, 1, ji); + LossyUtils.Dst(dst, 2, 2, ji); + var kj = LossyUtils.Avg2(K, J); + LossyUtils.Dst(dst, 0, 2, kj); + LossyUtils.Dst(dst, 2, 3, kj); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(L, K)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(A, B, C)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(X, A, B)); + var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 1, 0, ixa); + LossyUtils.Dst(dst, 3, 1, ixa); + var jix = LossyUtils.Avg3(J, I, X); + LossyUtils.Dst(dst, 1, 1, jix); + LossyUtils.Dst(dst, 3, 2, jix); + var kji = LossyUtils.Avg3(K, J, I); + LossyUtils.Dst(dst, 1, 2, kji); + LossyUtils.Dst(dst, 3, 3, kji); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(L, K, J)); + } + + private void Hu4(Span dst, Span top) + { + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(I, J)); + var jk = LossyUtils.Avg2(J, K); + LossyUtils.Dst(dst, 2, 0, jk); + LossyUtils.Dst(dst, 0, 1, jk); + var kl = LossyUtils.Avg2(K, L); + LossyUtils.Dst(dst, 2, 1, kl); + LossyUtils.Dst(dst, 0, 2, kl); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(I, J, K)); + var jkl = LossyUtils.Avg3(J, K, L); + LossyUtils.Dst(dst, 3, 0, jkl); + LossyUtils.Dst(dst, 1, 1, jkl); + var kll = LossyUtils.Avg3(K, L, L); + LossyUtils.Dst(dst, 3, 1, kll); + LossyUtils.Dst(dst, 1, 2, kll); + LossyUtils.Dst(dst, 3, 2, L); + LossyUtils.Dst(dst, 2, 2, L); + LossyUtils.Dst(dst, 0, 3, L); + LossyUtils.Dst(dst, 1, 3, L); + LossyUtils.Dst(dst, 2, 3, L); + LossyUtils.Dst(dst, 3, 3, L); + } + private void Fill(Span dst, int value, int size) { for (int j = 0; j < size; ++j) @@ -710,6 +1197,54 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Nz.GetSpan().Fill(0); } + private void NzToBytes() + { + Span nz = this.Nz.GetSpan(); + uint tnz = nz[0]; + uint lnz = nz[-1]; // TODO: -1? + Span topNz = this.TopNz; + Span leftNz = this.LeftNz; + + // Top-Y + topNz[0] = this.Bit(tnz, 12); + topNz[1] = this.Bit(tnz, 13); + topNz[2] = this.Bit(tnz, 14); + topNz[3] = this.Bit(tnz, 15); + + // Top-U + topNz[4] = this.Bit(tnz, 18); + topNz[5] = this.Bit(tnz, 19); + + // Top-V + topNz[6] = this.Bit(tnz, 22); + topNz[7] = this.Bit(tnz, 23); + + // DC + topNz[8] = this.Bit(tnz, 24); + + // left-Y + leftNz[0] = this.Bit(lnz, 3); + leftNz[1] = this.Bit(lnz, 7); + leftNz[2] = this.Bit(lnz, 11); + leftNz[3] = this.Bit(lnz, 15); + + // left-U + leftNz[4] = this.Bit(lnz, 17); + leftNz[5] = this.Bit(lnz, 19); + + // left-V + leftNz[6] = this.Bit(lnz, 21); + leftNz[7] = this.Bit(lnz, 23); + + // left-DC is special, iterated separately. + } + + // Convert packed context to byte array. + private int Bit(uint nz, int n) + { + return (int)(nz & (1 << n)); + } + /// /// Set count down. /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index a38b167c0..3374dff01 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2); this.Preds = this.memoryAllocator.Allocate(((4 * mbw) + 1) * ((4 * mbh) + 1)); this.Nz = this.memoryAllocator.Allocate(mbw + 1); + this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (mbw * mbh); // TODO: properly initialize the bitwriter this.bitWriter = new Vp8BitWriter(); @@ -91,17 +92,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private IMemoryOwner Nz { get; } + /// + /// Gets a rough limit for header bits per MB. + /// + private int MbHeaderLimit { get; } + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { + int width = image.Width; + int height = image.Height; this.ConvertRgbToYuv(image); Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - int mbw = (image.Width + 15) >> 4; - int mbh = (image.Height + 15) >> 4; - int yStride = image.Width; + int mbw = (width + 15) >> 4; + int mbh = (height + 15) >> 4; + int yStride = width; int uvStride = (yStride + 1) >> 1; var mb = new Vp8MacroBlockInfo[mbw * mbh]; for (int i = 0; i < mb.Length; i++) @@ -113,21 +121,29 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int method = 4; // TODO: hardcoded for now int quality = 100; // TODO: hardcoded for now var alphas = new int[WebPConstants.MaxAlpha + 1]; - int uvAlpha = 0; - int alpha = 0; - if (!it.IsDone()) + int alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, method, quality, alphas, out int uvAlpha); + + // Analysis is done, proceed to actual coding. + + // TODO: EncodeAlpha(); + it.Init(); + it.InitFilter(); + do { - do + var info = new Vp8ModeScore(); + it.Import(y, u, v, yStride, uvStride, width, height); + if (!this.Decimate(it, info, method)) { - it.Import(y, u, v, yStride, uvStride, image.Width, image.Height); - int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); - - // Accumulate for later complexity analysis. - alpha += bestAlpha; - uvAlpha += bestUvAlpha; + this.CodeResiduals(it); } - while (it.Next()); + else + { + it.ResetAfterSkip(); + } + + it.SaveBoundary(); } + while (it.Next()); throw new NotImplementedException(); } @@ -143,6 +159,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Preds.Dispose(); } + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int method, int quality, int[] alphas, out int uvAlpha) + { + int alpha = 0; + uvAlpha = 0; + if (!it.IsDone()) + { + do + { + it.Import(y, u, v, yStride, uvStride, width, height); + int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); + + // Accumulate for later complexity analysis. + alpha += bestAlpha; + uvAlpha += bestUvAlpha; + } + while (it.Next()); + } + + return alpha; + } + private int MbAnalyze(Vp8EncIterator it, int method, int quality, int[] alphas, out int bestUvAlpha) { it.SetIntra16Mode(0); // default: Intra16, DC_PRED @@ -170,6 +207,187 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } + private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, int method) + { + rd.InitScore(); + + it.MakeLuma16Preds(); + it.MakeChroma8Preds(); + + // TODO: add support for Rate-distortion optimization levels + // At this point we have heuristically decided intra16 / intra4. + // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). + // For method <= 1, we don't re-examine the decision but just go ahead with + // quantization/reconstruction. + this.RefineUsingDistortion(it, rd, method >= 2, method >= 1); + + bool isSkipped = rd.Nz == 0; + it.SetSkip(isSkipped); + + return isSkipped; + } + + // Refine intra16/intra4 sub-modes based on distortion only (not rate). + private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) + { + long bestScore = Vp8ModeScore.MaxCost; + int nz = 0; + int mode; + bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); + + // TODO: VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; + + // Some empiric constants, of approximate order of magnitude. + int lambdaDi16 = 106; + int lambdaDi4 = 11; + int lambdaDuv = 120; + long scoreI4 = 676000; // TODO: hardcoded for now: long scoreI4 = dqm->i4_penalty_; + long i4BitSum = 0; + long bitLimit = tryBothModes + ? this.MbHeaderLimit + : Vp8ModeScore.MaxCost; // no early-out allowed. + int numPredModes = 4; + int numBModes = 10; + + if (isI16) + { + int bestMode = -1; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + for (mode = 0; mode < numPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + long score = (this.Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + + if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) + { + continue; + } + + if (score < bestScore) + { + bestMode = mode; + bestScore = score; + } + } + + if (it.X == 0 || it.Y == 0) + { + // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. + if (this.IsFlatSource16(src)) + { + bestMode = (it.X == 0) ? 0 : 2; + tryBothModes = false; // Stick to i16. + } + } + + it.SetIntra16Mode(bestMode); + + // We'll reconstruct later, if i16 mode actually gets selected. + } + + // Next, evaluate Intra4. + if (tryBothModes || !isI16) + { + // We don't evaluate the rate here, but just account for it through a + // constant penalty (i4 mode usually needs more bits compared to i16). + isI16 = false; + it.StartI4(); + do + { + int bestI4Mode = -1; + long bestI4Score = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + + it.MakeIntra4Preds(); + for (mode = 0; mode < numBModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + long score = (this.Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + if (score < bestI4Score) + { + bestI4Mode = mode; + bestI4Score = score; + } + } + + i4BitSum += modeCosts[bestI4Mode]; + rd.ModesI4[it.I4] = (byte)bestI4Mode; + scoreI4 += bestI4Score; + if (scoreI4 >= bestScore || i4BitSum > bitLimit) + { + // Intra4 won't be better than Intra16. Bail out and pick Intra16. + isI16 = true; + break; + } + else + { + // Reconstruct partial block inside yuv_out2 buffer + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + // TODO: nz |= ReconstructIntra4(it, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4; + } + } + while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); + } + + // Final reconstruction, depending on which mode is selected. + if (!isI16) + { + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + bestScore = scoreI4; + } + else + { + // TODO: nz = ReconstructIntra16(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]); + } + + // ... and UV! + if (refineUvMode) + { + int bestMode = -1; + long bestUvScore = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + for (mode = 0; mode < numPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + long score = (this.Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + if (score < bestUvScore) + { + bestMode = mode; + bestUvScore = score; + } + } + + it.SetIntraUvMode(bestMode); + } + + // TODO: nz |= ReconstructUv(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); + + rd.Nz = (uint)nz; + rd.Score = bestScore; + } + + private void CodeResiduals(Vp8EncIterator it) + { + + } + + private void ReconstructIntra16() + { + + } + + private void ReconstructIntra4() + { + + } + + private void ReconstructUv() + { + + } + private void ConvertRgbToYuv(Image image) where TPixel : unmanaged, IPixel { @@ -469,5 +687,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { return (v < min) ? min : (v > max) ? max : v; } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Vp8Sse16X16(Span a, Span b) + { + return this.GetSse(a, b, 16, 16); + } + + private int Vp8Sse16X8(Span a, Span b) + { + return this.GetSse(a, b, 16, 8); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Vp8Sse4X4(Span a, Span b) + { + return this.GetSse(a, b, 4, 4); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetSse(Span a, Span b, int w, int h) + { + int count = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + int diff = a[x] - b[x]; + count += diff * diff; + } + + a = a.Slice(WebPConstants.Bps); + b = b.Slice(WebPConstants.Bps); + } + + return count; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private bool IsFlatSource16(Span src) + { + uint v = src[0] * 0x01010101u; + Span vSpan = BitConverter.GetBytes(v).AsSpan(); + for (int i = 0; i < 16; ++i) + { + if (src.Slice(0, 4).SequenceEqual(vSpan) || src.Slice(4, 4).SequenceEqual(vSpan) || + src.Slice(0, 8).SequenceEqual(vSpan) || src.Slice(12, 4).SequenceEqual(vSpan)) + { + return false; + } + + src = src.Slice(WebPConstants.Bps); + } + + return true; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs new file mode 100644 index 000000000..3b793762a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Class to accumulate score and info during RD-optimization and mode evaluation. + /// + internal class Vp8ModeScore + { + public const long MaxCost = 0x7fffffffffffffL; + + /// + /// Initializes a new instance of the class. + /// + public Vp8ModeScore() + { + this.YDcLevels = new short[16]; + this.YAcLevels = new short[16][]; + for (int i = 0; i < 16; i++) + { + this.YAcLevels[i] = new short[16]; + } + + this.UvLevels = new short[4 + 4][]; + for (int i = 0; i < 8; i++) + { + this.UvLevels[i] = new short[16]; + } + + this.ModesI4 = new byte[16]; + } + + /// + /// Distortion. + /// + public long D { get; set; } + + /// + /// Spectral distortion. + /// + public long SD { get; set; } + + /// + /// Header bits. + /// + public long H { get; set; } + + /// + /// Rate. + /// + public long R { get; set; } + + /// + /// Score. + /// + public long Score { get; set; } + + /// + /// Quantized levels for luma-DC. + /// + public short[] YDcLevels { get; } + + /// + /// Quantized levels for luma-AC. + /// + public short[][] YAcLevels { get; } + + /// + /// Quantized levels for chroma. + /// + public short[][] UvLevels { get; } + + /// + /// Mode number for intra16 prediction. + /// + public int ModeI16 { get; set; } + + /// + /// Mode numbers for intra4 predictions. + /// + public byte[] ModesI4 { get; } + + /// + /// Mode number of chroma prediction. + /// + public int ModeUv { get; set; } + + /// + /// Non-zero blocks. + /// + public uint Nz { get; set; } + + public void InitScore() + { + this.D = 0; + this.SD = 0; + this.R = 0; + this.H = 0; + this.Nz = 0; + this.Score = MaxCost; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index cb751e216..9a2b3ee8b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -209,6 +209,15 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int AlphaScale = 2 * MaxAlpha; + public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; + + public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; + + /// + /// Distortion multiplier (equivalent of lambda). + /// + public const int RdDistoMult = 256; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 1a38912ea..9cfd9ec4c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -36,6 +36,17 @@ namespace SixLabors.ImageSharp.Formats.WebP 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V }; + public static readonly short[] Vp8Scan = + { + // Luma + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), + 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), + 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), + 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + }; + + public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + /// /// Lookup table for small values of log2(int). /// @@ -960,6 +971,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } InitializeModesProbabilities(); + InitializeFixedCostsI4(); } private static void InitializeModesProbabilities() @@ -1066,5 +1078,109 @@ namespace SixLabors.ImageSharp.Formats.WebP ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; } + + private static void InitializeFixedCostsI4() + { + Vp8FixedCostsI4[0, 0] = new short[] { 40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137 }; + Vp8FixedCostsI4[0, 1] = new short[] { 192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522 }; + Vp8FixedCostsI4[0, 2] = new short[] { 142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657 }; + Vp8FixedCostsI4[0, 3] = new short[] { 559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007 }; + Vp8FixedCostsI4[0, 4] = new short[] { 299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142 }; + Vp8FixedCostsI4[0, 5] = new short[] { 277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291 }; + Vp8FixedCostsI4[0, 6] = new short[] { 214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918 }; + Vp8FixedCostsI4[0, 7] = new short[] { 152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390 }; + Vp8FixedCostsI4[0, 8] = new short[] { 512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207 }; + Vp8FixedCostsI4[0, 9] = new short[] { 424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538 }; + Vp8FixedCostsI4[1, 0] = new short[] { 240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099 }; + Vp8FixedCostsI4[1, 1] = new short[] { 467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391 }; + Vp8FixedCostsI4[1, 2] = new short[] { 500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114 }; + Vp8FixedCostsI4[1, 3] = new short[] { 672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876 }; + Vp8FixedCostsI4[1, 4] = new short[] { 458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033 }; + Vp8FixedCostsI4[1, 5] = new short[] { 504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268 }; + Vp8FixedCostsI4[1, 6] = new short[] { 333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598 }; + Vp8FixedCostsI4[1, 7] = new short[] { 399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015 }; + Vp8FixedCostsI4[1, 8] = new short[] { 622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971 }; + Vp8FixedCostsI4[1, 9] = new short[] { 500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523 }; + Vp8FixedCostsI4[2, 0] = new short[] { 394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181 }; + Vp8FixedCostsI4[2, 1] = new short[] { 655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553 }; + Vp8FixedCostsI4[2, 2] = new short[] { 690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232 }; + Vp8FixedCostsI4[2, 3] = new short[] { 559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784 }; + Vp8FixedCostsI4[2, 4] = new short[] { 690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126 }; + Vp8FixedCostsI4[2, 5] = new short[] { 740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513 }; + Vp8FixedCostsI4[2, 6] = new short[] { 323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655 }; + Vp8FixedCostsI4[2, 7] = new short[] { 488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461 }; + Vp8FixedCostsI4[2, 8] = new short[] { 740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721 }; + Vp8FixedCostsI4[2, 9] = new short[] { 522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697 }; + Vp8FixedCostsI4[3, 0] = new short[] { 105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579 }; + Vp8FixedCostsI4[3, 1] = new short[] { 534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170 }; + Vp8FixedCostsI4[3, 2] = new short[] { 305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242 }; + Vp8FixedCostsI4[3, 3] = new short[] { 683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947 }; + Vp8FixedCostsI4[3, 4] = new short[] { 394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047 }; + Vp8FixedCostsI4[3, 5] = new short[] { 528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358 }; + Vp8FixedCostsI4[3, 6] = new short[] { 347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667 }; + Vp8FixedCostsI4[3, 7] = new short[] { 218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617 }; + Vp8FixedCostsI4[3, 8] = new short[] { 672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087 }; + Vp8FixedCostsI4[3, 9] = new short[] { 592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822 }; + Vp8FixedCostsI4[4, 0] = new short[] { 228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782 }; + Vp8FixedCostsI4[4, 1] = new short[] { 494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363 }; + Vp8FixedCostsI4[4, 2] = new short[] { 512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463 }; + Vp8FixedCostsI4[4, 3] = new short[] { 683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939 }; + Vp8FixedCostsI4[4, 4] = new short[] { 622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219 }; + Vp8FixedCostsI4[4, 5] = new short[] { 631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170 }; + Vp8FixedCostsI4[4, 6] = new short[] { 555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429 }; + Vp8FixedCostsI4[4, 7] = new short[] { 504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406 }; + Vp8FixedCostsI4[4, 8] = new short[] { 683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122 }; + Vp8FixedCostsI4[4, 9] = new short[] { 399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678 }; + Vp8FixedCostsI4[5, 0] = new short[] { 333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219 }; + Vp8FixedCostsI4[5, 1] = new short[] { 512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169 }; + Vp8FixedCostsI4[5, 2] = new short[] { 572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995 }; + Vp8FixedCostsI4[5, 3] = new short[] { 786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768 }; + Vp8FixedCostsI4[5, 4] = new short[] { 690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897 }; + Vp8FixedCostsI4[5, 5] = new short[] { 768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453 }; + Vp8FixedCostsI4[5, 6] = new short[] { 452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813 }; + Vp8FixedCostsI4[5, 7] = new short[] { 394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109 }; + Vp8FixedCostsI4[5, 8] = new short[] { 559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028 }; + Vp8FixedCostsI4[5, 9] = new short[] { 564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764 }; + Vp8FixedCostsI4[6, 0] = new short[] { 266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461 }; + Vp8FixedCostsI4[6, 1] = new short[] { 366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054 }; + Vp8FixedCostsI4[6, 2] = new short[] { 452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150 }; + Vp8FixedCostsI4[6, 3] = new short[] { 555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496 }; + Vp8FixedCostsI4[6, 4] = new short[] { 704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578 }; + Vp8FixedCostsI4[6, 5] = new short[] { 672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949 }; + Vp8FixedCostsI4[6, 6] = new short[] { 296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722 }; + Vp8FixedCostsI4[6, 7] = new short[] { 342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052 }; + Vp8FixedCostsI4[6, 8] = new short[] { 555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494 }; + Vp8FixedCostsI4[6, 9] = new short[] { 288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509 }; + Vp8FixedCostsI4[7, 0] = new short[] { 342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151 }; + Vp8FixedCostsI4[7, 1] = new short[] { 483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266 }; + Vp8FixedCostsI4[7, 2] = new short[] { 488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109 }; + Vp8FixedCostsI4[7, 3] = new short[] { 522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605 }; + Vp8FixedCostsI4[7, 4] = new short[] { 709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057 }; + Vp8FixedCostsI4[7, 5] = new short[] { 512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350 }; + Vp8FixedCostsI4[7, 6] = new short[] { 452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922 }; + Vp8FixedCostsI4[7, 7] = new short[] { 403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547 }; + Vp8FixedCostsI4[7, 8] = new short[] { 559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984 }; + Vp8FixedCostsI4[7, 9] = new short[] { 547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651 }; + Vp8FixedCostsI4[8, 0] = new short[] { 165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612 }; + Vp8FixedCostsI4[8, 1] = new short[] { 592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041 }; + Vp8FixedCostsI4[8, 2] = new short[] { 403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407 }; + Vp8FixedCostsI4[8, 3] = new short[] { 896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035 }; + Vp8FixedCostsI4[8, 4] = new short[] { 640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865 }; + Vp8FixedCostsI4[8, 5] = new short[] { 559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435 }; + Vp8FixedCostsI4[8, 6] = new short[] { 415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522 }; + Vp8FixedCostsI4[8, 7] = new short[] { 406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277 }; + Vp8FixedCostsI4[8, 8] = new short[] { 968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403 }; + Vp8FixedCostsI4[8, 9] = new short[] { 732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755 }; + Vp8FixedCostsI4[9, 0] = new short[] { 111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307 }; + Vp8FixedCostsI4[9, 1] = new short[] { 406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793 }; + Vp8FixedCostsI4[9, 2] = new short[] { 342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502 }; + Vp8FixedCostsI4[9, 3] = new short[] { 559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802 }; + Vp8FixedCostsI4[9, 4] = new short[] { 473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782 }; + Vp8FixedCostsI4[9, 5] = new short[] { 342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057 }; + Vp8FixedCostsI4[9, 6] = new short[] { 208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712 }; + Vp8FixedCostsI4[9, 7] = new short[] { 228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318 }; + Vp8FixedCostsI4[9, 8] = new short[] { 768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825 }; + Vp8FixedCostsI4[9, 9] = new short[] { 305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501 }; + } } } From d981e91b6b9335ffa17b849da33b0ea0b99cd74f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 26 Oct 2020 18:30:45 +0100 Subject: [PATCH 213/359] Reconstruct Intra16/Intra4 and UV --- .../Formats/WebP/Lossy/LossyUtils.cs | 50 ++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 317 +++++++++++++++++- .../Formats/WebP/Lossy/Vp8Matrix.cs | 45 +++ .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 53 +++ .../Formats/WebP/Lossy/WebPLossyDecoder.cs | 40 +-- .../Formats/WebP/WebPLookupTables.cs | 6 + 6 files changed, 454 insertions(+), 57 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 83e8e95e1..8c73c094c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -455,6 +455,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Dst(dst, 3, 3, l); } + /// + /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. + /// + public static void TransformWht(short[] input, short[] output) + { + var tmp = new int[16]; + for (int i = 0; i < 4; ++i) + { + int iPlus4 = 4 + i; + int iPlus8 = 8 + i; + int iPlus12 = 12 + i; + int a0 = input[i] + input[iPlus12]; + int a1 = input[iPlus4] + input[iPlus8]; + int a2 = input[iPlus4] - input[iPlus8]; + int a3 = input[i] - input[iPlus12]; + tmp[i] = a0 + a1; + tmp[iPlus8] = a0 - a1; + tmp[iPlus4] = a3 + a2; + tmp[iPlus12] = a3 - a2; + } + + int outputOffset = 0; + for (int i = 0; i < 4; ++i) + { + int imul4 = i * 4; + int dc = tmp[0 + imul4] + 3; + int a0 = dc + tmp[3 + imul4]; + int a1 = tmp[1 + imul4] + tmp[2 + imul4]; + int a2 = tmp[1 + imul4] - tmp[2 + imul4]; + int a3 = dc - tmp[3 + imul4]; + output[outputOffset + 0] = (short)((a0 + a1) >> 3); + output[outputOffset + 16] = (short)((a3 + a2) >> 3); + output[outputOffset + 32] = (short)((a0 - a1) >> 3); + output[outputOffset + 48] = (short)((a3 - a2) >> 3); + outputOffset += 64; + } + } + public static void TransformTwo(Span src, Span dst) { TransformOne(src, dst); @@ -740,6 +778,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy dst[x + (y * WebPConstants.Bps)] = v; } + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Clip8B(int v) + { + return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( Span p, @@ -933,12 +977,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return (a * 35468) >> 16; } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8B(int v) - { - return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); - } - [MethodImpl(InliningOptions.ShortMethod)] private static byte Clip8(int v) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 3374dff01..2ca51a9cb 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -34,6 +34,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int YuvHalf = 1 << (YuvFix - 1); + private const int KC1 = 20091 + (1 << 16); + + private const int KC2 = 35468; + + private const int MaxLevel = 2047; + + private const int QFix = 17; + + private readonly byte[] zigzag = new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + /// /// Initializes a new instance of the class. /// @@ -117,6 +127,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy mb[i] = new Vp8MacroBlockInfo(); } + var segmentInfos = new Vp8SegmentInfo[4]; + for (int i = 0; i < 4; i++) + { + segmentInfos[i] = new Vp8SegmentInfo(); + } + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh); int method = 4; // TODO: hardcoded for now int quality = 100; // TODO: hardcoded for now @@ -126,13 +142,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Analysis is done, proceed to actual coding. // TODO: EncodeAlpha(); + // Compute segment probabilities. + this.SetSegmentProbas(segmentInfos); + this.SetupMatrices(segmentInfos); it.Init(); it.InitFilter(); do { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height); - if (!this.Decimate(it, info, method)) + if (!this.Decimate(it, segmentInfos, info, method)) { this.CodeResiduals(it); } @@ -159,6 +178,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Preds.Dispose(); } + private void SetSegmentProbas(Vp8SegmentInfo[] dqm) + { + var p = new int[4]; + int n; + + // TODO: SetSegmentProbas + } + + private void SetupMatrices(Vp8SegmentInfo[] dqm) + { + // TODO: SetupMatrices + } + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int method, int quality, int[] alphas, out int uvAlpha) { int alpha = 0; @@ -207,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } - private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, int method) + private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, int method) { rd.InitScore(); @@ -219,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - this.RefineUsingDistortion(it, rd, method >= 2, method >= 1); + this.RefineUsingDistortion(it, segmentInfos, rd, method >= 2, method >= 1); bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); @@ -228,14 +260,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // Refine intra16/intra4 sub-modes based on distortion only (not rate). - private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) + private void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) { long bestScore = Vp8ModeScore.MaxCost; int nz = 0; int mode; bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); - - // TODO: VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; // Some empiric constants, of approximate order of magnitude. int lambdaDi16 = 106; @@ -324,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { // Reconstruct partial block inside yuv_out2 buffer Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); - // TODO: nz |= ReconstructIntra4(it, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4; + nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4; } } while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); @@ -339,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - // TODO: nz = ReconstructIntra16(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]); + nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]); } // ... and UV! @@ -362,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.SetIntraUvMode(bestMode); } - // TODO: nz |= ReconstructUv(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); + nz |= this.ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); rd.Nz = (uint)nz; rd.Score = bestScore; @@ -373,19 +404,263 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } - private void ReconstructIntra16() + private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + int nz = 0; + int n; + var dcTmp = new short[16]; + var tmp = new short[16][]; + for (int i = 0; i < 16; i++) + { + tmp[i] = new short[16]; + } + + for (n = 0; n < 16; n += 2) + { + this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n]); + } + + this.FTransformWht(tmp[0], dcTmp); + nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; + + for (n = 0; n < 16; n += 2) + { + // Zero-out the first coeff, so that: a) nz is correct below, and + // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. + tmp[n][0] = tmp[n + 1][0] = 0; + nz |= this.Quantize2Blocks(tmp[n], rd.YAcLevels[n], dqm.Y1) << n; + } + + // Transform back. + LossyUtils.TransformWht(dcTmp, tmp[0]); + for (n = 0; n < 16; n += 2) + { + this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); + } + + return nz; + } + + private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, short[] levels, Span src, Span yuvOut, int mode) { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + var tmp = new short[16]; + this.FTransform(src, reference, tmp); + var nz = this.QuantizeBlock(tmp, levels, dqm.Y1); + this.ITransform(reference, tmp, yuvOut, false); + return nz; } - private void ReconstructIntra4() + private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + int nz = 0; + int n; + var tmp = new short[8][]; + for (int i = 0; i < 8; i++) + { + tmp[i] = new short[16]; + } + + for (n = 0; n < 8; n += 2) + { + this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n]); + } + + /* TODO: + if (it->top_derr_ != NULL) + { + CorrectDCValues(it, &dqm->uv_, tmp, rd); + }*/ + + for (n = 0; n < 8; n += 2) + { + nz |= this.Quantize2Blocks(tmp[n], rd.UvLevels[n], dqm.Uv) << n; + } + + for (n = 0; n < 8; n += 2) + { + this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); + } + + return nz << 16; + } + private void FTransform2(Span src, Span reference, short[] output) + { + this.FTransform(src, reference, output); + this.FTransform(src.Slice(4), reference.Slice(4), output.AsSpan(16)); } - private void ReconstructUv() + private void FTransform(Span src, Span reference, Span output) { + int i; + var tmp = new int[16]; + for (i = 0; i < 4; ++i) + { + int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) + int d1 = src[1] - reference[1]; + int d2 = src[2] - reference[2]; + int d3 = src[3] - reference[3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + src = src.Slice(WebPConstants.Bps); + reference = reference.Slice(WebPConstants.Bps); + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } + } + + private void FTransformWht(Span input, Span output) + { + var tmp = new int[16]; + int i; + for (i = 0; i < 4; ++i) + { + int a0 = input[0 * 16] + input[2 * 16]; // 13b + int a1 = input[1 * 16] + input[3 * 16]; + int a2 = input[1 * 16] - input[3 * 16]; + int a3 = input[0 * 16] - input[2 * 16]; + tmp[0 + (i * 4)] = a0 + a1; // 14b + tmp[1 + (i * 4)] = a3 + a2; + tmp[2 + (i * 4)] = a3 - a2; + tmp[3 + (i * 4)] = a0 - a1; + + input = input.Slice(64); + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[8 + i]; // 15b + int a1 = tmp[4 + i] + tmp[12 + i]; + int a2 = tmp[4 + i] - tmp[12 + i]; + int a3 = tmp[0 + i] - tmp[8 + i]; + int b0 = a0 + a1; // 16b + int b1 = a3 + a2; + int b2 = a3 - a2; + int b3 = a0 - a1; + output[ 0 + i] = (short)(b0 >> 1); // 15b + output[ 4 + i] = (short)(b1 >> 1); + output[ 8 + i] = (short)(b2 >> 1); + output[12 + i] = (short)(b3 >> 1); + } + } + + private int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) + { + int nz; + nz = this.QuantizeBlock(input, output, mtx) << 0; + nz |= this.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; + return nz; + } + + private int QuantizeBlock(Span input, Span output, Vp8Matrix mtx) + { + int last = -1; + int n; + for (n = 0; n < 16; ++n) + { + int j = zigzag[n]; + bool sign = input[j] < 0; + uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); + if (coeff > mtx.ZThresh[j]) + { + uint Q = (uint)mtx.Q[j]; + uint iQ = (uint)mtx.IQ[j]; + uint B = mtx.Bias[j]; + int level = this.QuantDiv(coeff, iQ, B); + if (level > MaxLevel) + { + level = MaxLevel; + } + + if (sign) + { + level = -level; + } + + input[j] = (short)(level * (int)Q); + output[n] = (short)level; + if (level != 0) + { + last = n; + } + } + else + { + output[n] = 0; + input[j] = 0; + } + } + + return (last >= 0) ? 1 : 0; + } + + private void ITransform(Span reference, short[] input, Span dst, bool doTwo) + { + this.ITransformOne(reference, input, dst); + if (doTwo) + { + this.ITransformOne(reference.Slice(4), input.AsSpan(16), dst.Slice(4)); + } + } + + private void ITransformOne(Span reference, Span input, Span dst) + { + int i; + var C = new int[4 * 4]; + Span tmp = C.AsSpan(); + for (i = 0; i < 4; ++i) + { + // vertical pass. + int a = input[0] + input[8]; + int b = input[0] - input[8]; + int c = this.Mul(input[4], KC2) - this.Mul(input[12], KC1); + int d = this.Mul(input[4], KC1) + this.Mul(input[12], KC2); + tmp[0] = a + d; + tmp[1] = b + c; + tmp[2] = b - c; + tmp[3] = a - d; + tmp = tmp.Slice(4); + input = input.Slice(1); + } + + tmp = C.AsSpan(); + for (i = 0; i < 4; ++i) + { + // horizontal pass. + int dc = tmp[0] + 4; + int a = dc + tmp[8]; + int b = dc - tmp[8]; + int c = this.Mul(tmp[4], KC2) - this.Mul(tmp[12], KC1); + int d = this.Mul(tmp[4], KC1) + this.Mul(tmp[12], KC2); + this.Store(dst, reference, 0, i, (byte)(a + d)); + this.Store(dst, reference, 1, i, (byte)(b + c)); + this.Store(dst, reference, 2, i, (byte)(b - c)); + this.Store(dst, reference, 3, i, (byte)(a - d)); + tmp = tmp.Slice(1); + } } private void ConvertRgbToYuv(Image image) @@ -742,5 +1017,23 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return true; } + + [MethodImpl(InliningOptions.ShortMethod)] + private int QuantDiv(uint n, uint iQ, uint b) + { + return (int)(((n * iQ) + b) >> QFix); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void Store(Span dst, Span reference, int x, int y, byte v) + { + dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Mul(int a, int b) + { + return (a * b) >> 16; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs new file mode 100644 index 000000000..5fe529e5b --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8Matrix + { + /// + /// Initializes a new instance of the class. + /// + public Vp8Matrix() + { + this.Q = new short[16]; + this.IQ = new short[16]; + this.Bias = new uint[16]; + this.ZThresh = new uint[16]; + this.Sharpen = new short[16]; + } + + /// + /// quantizer steps. + /// + public short[] Q { get; } + + /// + /// reciprocals, fixed point. + /// + public short[] IQ { get; } + + /// + /// rounding bias. + /// + public uint[] Bias { get; } + + /// + /// value below which a coefficient is zeroed. + /// + public uint[] ZThresh { get; } + + /// + /// frequency boosters for slight sharpening. + /// + public short[] Sharpen { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs new file mode 100644 index 000000000..85069bf5e --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8SegmentInfo + { + /// + /// quantization matrix y1. + /// + public Vp8Matrix Y1 { get; set; } + + /// + /// quantization matrix y2. + /// + public Vp8Matrix Y2 { get; set; } + + /// + /// quantization matrix uv. + /// + public Vp8Matrix Uv { get; set; } + + /// + /// quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. + /// + public int Alpha { get; set; } + + /// + /// filter-susceptibility, range [0,255]. + /// + public int Beta { get; set; } + + /// + /// final segment quantizer. + /// + public int Quant { get; set; } + + /// + /// final in-loop filtering strength. + /// + public int FStrength { get; set; } + + /// + /// max edge delta (for filtering strength). + /// + public int MaxEdge { get; set; } + + /// + /// penalty for using Intra4. + /// + public long I4Penalty { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 1b1884cdc..351f1a45e 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -890,7 +890,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy if (nz > 1) { // More than just the DC -> perform the full transform. - this.TransformWht(dc, dst); + LossyUtils.TransformWht(dc, dst); } else { @@ -1078,44 +1078,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return v; } - /// - /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. - /// - private void TransformWht(short[] input, short[] output) - { - var tmp = new int[16]; - for (int i = 0; i < 4; ++i) - { - int iPlus4 = 4 + i; - int iPlus8 = 8 + i; - int iPlus12 = 12 + i; - int a0 = input[i] + input[iPlus12]; - int a1 = input[iPlus4] + input[iPlus8]; - int a2 = input[iPlus4] - input[iPlus8]; - int a3 = input[i] - input[iPlus12]; - tmp[i] = a0 + a1; - tmp[iPlus8] = a0 - a1; - tmp[iPlus4] = a3 + a2; - tmp[iPlus12] = a3 - a2; - } - - int outputOffset = 0; - for (int i = 0; i < 4; ++i) - { - int imul4 = i * 4; - int dc = tmp[0 + imul4] + 3; - int a0 = dc + tmp[3 + imul4]; - int a1 = tmp[1 + imul4] + tmp[2 + imul4]; - int a2 = tmp[1 + imul4] - tmp[2 + imul4]; - int a3 = dc - tmp[3 + imul4]; - output[outputOffset + 0] = (short)((a0 + a1) >> 3); - output[outputOffset + 16] = (short)((a3 + a2) >> 3); - output[outputOffset + 32] = (short)((a0 - a1) >> 3); - output[outputOffset + 48] = (short)((a3 - a2) >> 3); - outputOffset += 64; - } - } - private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) { var vp8SegmentHeader = new Vp8SegmentHeader diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 9cfd9ec4c..fe4eef63e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -45,6 +45,12 @@ namespace SixLabors.ImageSharp.Formats.WebP 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), }; + public static readonly short[] Vp8ScanUv = + { + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U + 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V + }; + public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; /// From 7badb8c34991a4495669a3ce8256d83b5e3e3348 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 26 Oct 2020 19:45:05 +0100 Subject: [PATCH 214/359] Fix warnings --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 274 +++++++++--------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 27 +- .../Formats/WebP/Lossy/Vp8Matrix.cs | 10 +- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 24 +- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 18 +- .../Formats/WebP/WebPLookupTables.cs | 12 +- 6 files changed, 183 insertions(+), 182 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index f1160d8f1..b9fed7f52 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -152,9 +152,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public int Y { get; set; } /// - /// Gets or sets the input samples. + /// Gets the input samples. /// - public byte[] YuvIn { get; set; } + public byte[] YuvIn { get; } /// /// Gets or sets the output samples. @@ -167,39 +167,39 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public byte[] YuvOut2 { get; set; } /// - /// Gets or sets the scratch buffer for prediction. + /// Gets the scratch buffer for prediction. /// - public byte[] YuvP { get; set; } + public byte[] YuvP { get; } /// - /// Gets or sets the left luma samples. + /// Gets the left luma samples. /// - public byte[] YLeft { get; set; } + public byte[] YLeft { get; } /// - /// Gets or sets the left uv samples. + /// Gets the left uv samples. /// - public byte[] UvLeft { get; set; } + public byte[] UvLeft { get; } /// - /// Gets or sets the top luma samples at position 'X'. + /// Gets the top luma samples at position 'X'. /// - public IMemoryOwner YTop { get; set; } + public IMemoryOwner YTop { get; } /// - /// Gets or sets the top u/v samples at position 'X', packed as 16 bytes. + /// Gets the top u/v samples at position 'X', packed as 16 bytes. /// - public IMemoryOwner UvTop { get; set; } + public IMemoryOwner UvTop { get; } /// - /// Gets or sets the intra mode predictors (4x4 blocks). + /// Gets the intra mode predictors (4x4 blocks). /// - public IMemoryOwner Preds { get; set; } + public IMemoryOwner Preds { get; } /// - /// Gets or sets the non-zero pattern. + /// Gets the non-zero pattern. /// - public IMemoryOwner Nz { get; set; } + public IMemoryOwner Nz { get; } /// /// Gets 32+5 boundary samples needed by intra4x4. @@ -217,12 +217,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public int I4 { get; set; } /// - /// Gets or sets the top-non-zero context. + /// Gets the top-non-zero context. /// public int[] TopNz { get; } /// - /// Gets or sets the left-non-zero. leftNz[8] is independent. + /// Gets the left-non-zero. leftNz[8] is independent. /// public int[] LeftNz { get; } @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - NzToBytes(); // import the non-zero context. + this.NzToBytes(); // import the non-zero context. } // Import uncompressed samples from source. @@ -864,9 +864,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy byte[] vals = { LossyUtils.Avg3(top[-1], top[0], top[1]), - LossyUtils.Avg3(top[ 0], top[1], top[2]), - LossyUtils.Avg3(top[ 1], top[2], top[3]), - LossyUtils.Avg3(top[ 2], top[3], top[4]) + LossyUtils.Avg3(top[0], top[1], top[2]), + LossyUtils.Avg3(top[1], top[2], top[3]), + LossyUtils.Avg3(top[2], top[3], top[4]) }; for (int i = 0; i < 4; ++i) @@ -878,223 +878,223 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void He4(Span dst, Span top) { // horizontal - byte X = top[-1]; - byte I = top[-2]; - byte J = top[-3]; - byte K = top[-4]; - byte L = top[-5]; + byte x = top[-1]; + byte i = top[-2]; + byte j = top[-3]; + byte k = top[-4]; + byte l = top[-5]; - uint val = 0x01010101U * LossyUtils.Avg3(X, I, J); + uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * LossyUtils.Avg3(I, J, K); + val = 0x01010101U * LossyUtils.Avg3(i, j, k); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(J, K, L); + val = 0x01010101U * LossyUtils.Avg3(j, k, l); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(K, L, L); + val = 0x01010101U * LossyUtils.Avg3(k, l, l); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); } private void Rd4(Span dst, Span top) { - byte X = top[-1]; - byte I = top[-2]; - byte J = top[-3]; - byte K = top[-4]; - byte L = top[-5]; - byte A = top[0]; - byte B = top[1]; - byte C = top[2]; - byte D = top[3]; - - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(J, K, L)); - var ijk = LossyUtils.Avg3(I, J, K); + byte x = top[-1]; + byte i = top[-2]; + byte j = top[-3]; + byte k = top[-4]; + byte l = top[-5]; + byte a = top[0]; + byte b = top[1]; + byte c = top[2]; + byte d = top[3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); + var ijk = LossyUtils.Avg3(i, j, k); LossyUtils.Dst(dst, 0, 2, ijk); LossyUtils.Dst(dst, 1, 3, ijk); - var xij = LossyUtils.Avg3(X, I, J); + var xij = LossyUtils.Avg3(x, i, j); LossyUtils.Dst(dst, 0, 1, xij); LossyUtils.Dst(dst, 1, 2, xij); LossyUtils.Dst(dst, 2, 3, xij); - var axi = LossyUtils.Avg3(A, X, I); + var axi = LossyUtils.Avg3(a, x, i); LossyUtils.Dst(dst, 0, 0, axi); LossyUtils.Dst(dst, 1, 1, axi); LossyUtils.Dst(dst, 2, 2, axi); LossyUtils.Dst(dst, 3, 3, axi); - var bax = LossyUtils.Avg3(B, A, X); + var bax = LossyUtils.Avg3(b, a, x); LossyUtils.Dst(dst, 1, 0, bax); LossyUtils.Dst(dst, 2, 1, bax); LossyUtils.Dst(dst, 3, 2, bax); - var cba = LossyUtils.Avg3(C, B, A); + var cba = LossyUtils.Avg3(c, b, a); LossyUtils.Dst(dst, 2, 0, cba); LossyUtils.Dst(dst, 3, 1, cba); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(D, C, B)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); } private void Vr4(Span dst, Span top) { - byte X = top[-1]; - byte I = top[-2]; - byte J = top[-3]; - byte K = top[-4]; - byte A = top[0]; - byte B = top[1]; - byte C = top[2]; - byte D = top[3]; - - var xa = LossyUtils.Avg2(X, A); + byte x = top[-1]; + byte i = top[-2]; + byte j = top[-3]; + byte k = top[-4]; + byte a = top[0]; + byte b = top[1]; + byte c = top[2]; + byte d = top[3]; + + var xa = LossyUtils.Avg2(x, a); LossyUtils.Dst(dst, 0, 0, xa); LossyUtils.Dst(dst, 1, 2, xa); - var ab = LossyUtils.Avg2(A, B); + var ab = LossyUtils.Avg2(a, b); LossyUtils.Dst(dst, 1, 0, ab); LossyUtils.Dst(dst, 2, 2, ab); - var bc = LossyUtils.Avg2(B, C); + var bc = LossyUtils.Avg2(b, c); LossyUtils.Dst(dst, 2, 0, bc); LossyUtils.Dst(dst, 3, 2, bc); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(C, D)); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(K, J, I)); - LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(J, I, X)); - var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); + var ixa = LossyUtils.Avg3(i, x, a); LossyUtils.Dst(dst, 0, 1, ixa); LossyUtils.Dst(dst, 1, 3, ixa); - var xab = LossyUtils.Avg3(X, A, B); + var xab = LossyUtils.Avg3(x, a, b); LossyUtils.Dst(dst, 1, 1, xab); LossyUtils.Dst(dst, 2, 3, xab); - var abc = LossyUtils.Avg3(A, B, C); + var abc = LossyUtils.Avg3(a, b, c); LossyUtils.Dst(dst, 2, 1, abc); LossyUtils.Dst(dst, 3, 3, abc); - LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(B, C, D)); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); } private void Ld4(Span dst, Span top) { - byte A = top[0]; - byte B = top[1]; - byte C = top[2]; - byte D = top[3]; - byte E = top[4]; - byte F = top[5]; - byte G = top[6]; - byte H = top[7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(A, B, C)); - var bcd = LossyUtils.Avg3(B, C, D); + byte a = top[0]; + byte b = top[1]; + byte c = top[2]; + byte d = top[3]; + byte e = top[4]; + byte f = top[5]; + byte g = top[6]; + byte h = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); LossyUtils.Dst(dst, 1, 0, bcd); LossyUtils.Dst(dst, 0, 1, bcd); - var cde = LossyUtils.Avg3(C, D, E); + var cde = LossyUtils.Avg3(c, d, e); LossyUtils.Dst(dst, 2, 0, cde); LossyUtils.Dst(dst, 1, 1, cde); LossyUtils.Dst(dst, 0, 2, cde); - var def = LossyUtils.Avg3(D, E, F); + var def = LossyUtils.Avg3(d, e, f); LossyUtils.Dst(dst, 3, 0, def); LossyUtils.Dst(dst, 2, 1, def); LossyUtils.Dst(dst, 1, 2, def); LossyUtils.Dst(dst, 0, 3, def); - var efg = LossyUtils.Avg3(E, F, G); + var efg = LossyUtils.Avg3(e, f, g); LossyUtils.Dst(dst, 3, 1, efg); LossyUtils.Dst(dst, 2, 2, efg); LossyUtils.Dst(dst, 1, 3, efg); - var fgh = LossyUtils.Avg3(F, G, H); + var fgh = LossyUtils.Avg3(f, g, h); LossyUtils.Dst(dst, 3, 2, fgh); LossyUtils.Dst(dst, 2, 3, fgh); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(G, H, H)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); } private void Vl4(Span dst, Span top) { - byte A = top[0]; - byte B = top[1]; - byte C = top[2]; - byte D = top[3]; - byte E = top[4]; - byte F = top[5]; - byte G = top[6]; - byte H = top[7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(A, B)); - var bc = LossyUtils.Avg2(B, C); + byte a = top[0]; + byte b = top[1]; + byte c = top[2]; + byte d = top[3]; + byte e = top[4]; + byte f = top[5]; + byte g = top[6]; + byte h = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); + var bc = LossyUtils.Avg2(b, c); LossyUtils.Dst(dst, 1, 0, bc); LossyUtils.Dst(dst, 0, 2, bc); - var cd = LossyUtils.Avg2(C, D); + var cd = LossyUtils.Avg2(c, d); LossyUtils.Dst(dst, 2, 0, cd); LossyUtils.Dst(dst, 1, 2, cd); - var de = LossyUtils.Avg2(D, E); + var de = LossyUtils.Avg2(d, e); LossyUtils.Dst(dst, 3, 0, de); LossyUtils.Dst(dst, 2, 2, de); - LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(A, B, C)); - var bcd = LossyUtils.Avg3(B,C,D); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); LossyUtils.Dst(dst, 1, 1, bcd); LossyUtils.Dst(dst, 0, 3, bcd); - var cde = LossyUtils.Avg3(C, D, E); + var cde = LossyUtils.Avg3(c, d, e); LossyUtils.Dst(dst, 2, 1, cde); LossyUtils.Dst(dst, 1, 3, cde); - var def = LossyUtils.Avg3(D, E, F); + var def = LossyUtils.Avg3(d, e, f); LossyUtils.Dst(dst, 3, 1, def); LossyUtils.Dst(dst, 2, 3, def); - LossyUtils.Dst(dst, 3,2, LossyUtils.Avg3(E, F, G)); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(F, G, H)); + LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); } private void Hd4(Span dst, Span top) { - byte X = top[-1]; - byte I = top[-2]; - byte J = top[-3]; - byte K = top[-4]; - byte L = top[-5]; - byte A = top[0]; - byte B = top[1]; - byte C = top[2]; - - var ix = LossyUtils.Avg2(I, X); + byte x = top[-1]; + byte i = top[-2]; + byte j = top[-3]; + byte k = top[-4]; + byte l = top[-5]; + byte a = top[0]; + byte b = top[1]; + byte c = top[2]; + + var ix = LossyUtils.Avg2(i, x); LossyUtils.Dst(dst, 0, 0, ix); LossyUtils.Dst(dst, 2, 1, ix); - var ji = LossyUtils.Avg2(J,I); + var ji = LossyUtils.Avg2(j, i); LossyUtils.Dst(dst, 0, 1, ji); LossyUtils.Dst(dst, 2, 2, ji); - var kj = LossyUtils.Avg2(K, J); + var kj = LossyUtils.Avg2(k, j); LossyUtils.Dst(dst, 0, 2, kj); LossyUtils.Dst(dst, 2, 3, kj); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(L, K)); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(A, B, C)); - LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(X, A, B)); - var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); + var ixa = LossyUtils.Avg3(i, x, a); LossyUtils.Dst(dst, 1, 0, ixa); LossyUtils.Dst(dst, 3, 1, ixa); - var jix = LossyUtils.Avg3(J, I, X); + var jix = LossyUtils.Avg3(j, i, x); LossyUtils.Dst(dst, 1, 1, jix); LossyUtils.Dst(dst, 3, 2, jix); - var kji = LossyUtils.Avg3(K, J, I); + var kji = LossyUtils.Avg3(k, j, i); LossyUtils.Dst(dst, 1, 2, kji); LossyUtils.Dst(dst, 3, 3, kji); - LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(L, K, J)); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); } private void Hu4(Span dst, Span top) { - byte I = top[-2]; - byte J = top[-3]; - byte K = top[-4]; - byte L = top[-5]; + byte i = top[-2]; + byte j = top[-3]; + byte k = top[-4]; + byte l = top[-5]; - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(I, J)); - var jk = LossyUtils.Avg2(J, K); + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); + var jk = LossyUtils.Avg2(j, k); LossyUtils.Dst(dst, 2, 0, jk); LossyUtils.Dst(dst, 0, 1, jk); - var kl = LossyUtils.Avg2(K, L); + var kl = LossyUtils.Avg2(k, l); LossyUtils.Dst(dst, 2, 1, kl); LossyUtils.Dst(dst, 0, 2, kl); - LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(I, J, K)); - var jkl = LossyUtils.Avg3(J, K, L); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); + var jkl = LossyUtils.Avg3(j, k, l); LossyUtils.Dst(dst, 3, 0, jkl); LossyUtils.Dst(dst, 1, 1, jkl); - var kll = LossyUtils.Avg3(K, L, L); + var kll = LossyUtils.Avg3(k, l, l); LossyUtils.Dst(dst, 3, 1, kll); LossyUtils.Dst(dst, 1, 2, kll); - LossyUtils.Dst(dst, 3, 2, L); - LossyUtils.Dst(dst, 2, 2, L); - LossyUtils.Dst(dst, 0, 3, L); - LossyUtils.Dst(dst, 1, 3, L); - LossyUtils.Dst(dst, 2, 3, L); - LossyUtils.Dst(dst, 3, 3, L); + LossyUtils.Dst(dst, 3, 2, l); + LossyUtils.Dst(dst, 2, 2, l); + LossyUtils.Dst(dst, 0, 3, l); + LossyUtils.Dst(dst, 1, 3, l); + LossyUtils.Dst(dst, 2, 3, l); + LossyUtils.Dst(dst, 3, 3, l); } private void Fill(Span dst, int value, int size) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 2ca51a9cb..61661733a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.Import(y, u, v, yStride, uvStride, width, height); if (!this.Decimate(it, segmentInfos, info, method)) { - this.CodeResiduals(it); + this.CodeResiduals(it, info); } else { @@ -180,8 +180,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void SetSegmentProbas(Vp8SegmentInfo[] dqm) { - var p = new int[4]; - int n; + // var p = new int[4]; + // int n; // TODO: SetSegmentProbas } @@ -399,9 +399,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy rd.Score = bestScore; } - private void CodeResiduals(Vp8EncIterator it) + private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd) { - } private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) @@ -560,9 +559,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int b1 = a3 + a2; int b2 = a3 - a2; int b3 = a0 - a1; - output[ 0 + i] = (short)(b0 >> 1); // 15b - output[ 4 + i] = (short)(b1 >> 1); - output[ 8 + i] = (short)(b2 >> 1); + output[0 + i] = (short)(b0 >> 1); // 15b + output[4 + i] = (short)(b1 >> 1); + output[8 + i] = (short)(b2 >> 1); output[12 + i] = (short)(b3 >> 1); } } @@ -581,15 +580,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int n; for (n = 0; n < 16; ++n) { - int j = zigzag[n]; + int j = this.zigzag[n]; bool sign = input[j] < 0; uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); if (coeff > mtx.ZThresh[j]) { - uint Q = (uint)mtx.Q[j]; + uint q = (uint)mtx.Q[j]; uint iQ = (uint)mtx.IQ[j]; - uint B = mtx.Bias[j]; - int level = this.QuantDiv(coeff, iQ, B); + uint b = mtx.Bias[j]; + int level = this.QuantDiv(coeff, iQ, b); if (level > MaxLevel) { level = MaxLevel; @@ -600,7 +599,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy level = -level; } - input[j] = (short)(level * (int)Q); + input[j] = (short)(level * (int)q); output[n] = (short)level; if (level != 0) { @@ -629,7 +628,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void ITransformOne(Span reference, Span input, Span dst) { int i; +#pragma warning disable SA1312 // Variable names should begin with lower-case letter var C = new int[4 * 4]; +#pragma warning restore SA1312 // Variable names should begin with lower-case letter Span tmp = C.AsSpan(); for (i = 0; i < 4; ++i) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 5fe529e5b..2ee9671ed 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -18,27 +18,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } /// - /// quantizer steps. + /// Gets the quantizer steps. /// public short[] Q { get; } /// - /// reciprocals, fixed point. + /// Gets the reciprocals, fixed point. /// public short[] IQ { get; } /// - /// rounding bias. + /// Gets the rounding bias. /// public uint[] Bias { get; } /// - /// value below which a coefficient is zeroed. + /// Gets the value below which a coefficient is zeroed. /// public uint[] ZThresh { get; } /// - /// frequency boosters for slight sharpening. + /// Gets the frequency boosters for slight sharpening. /// public short[] Sharpen { get; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 3b793762a..7ec02fac6 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -32,62 +32,62 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } /// - /// Distortion. + /// Gets or sets the distortion. /// public long D { get; set; } /// - /// Spectral distortion. + /// Gets or sets the spectral distortion. /// public long SD { get; set; } /// - /// Header bits. + /// Gets or sets the header bits. /// public long H { get; set; } /// - /// Rate. + /// Gets or sets the rate. /// public long R { get; set; } /// - /// Score. + /// Gets or sets the score. /// public long Score { get; set; } /// - /// Quantized levels for luma-DC. + /// Gets the quantized levels for luma-DC. /// public short[] YDcLevels { get; } /// - /// Quantized levels for luma-AC. + /// Gets the quantized levels for luma-AC. /// public short[][] YAcLevels { get; } /// - /// Quantized levels for chroma. + /// Gets the quantized levels for chroma. /// public short[][] UvLevels { get; } /// - /// Mode number for intra16 prediction. + /// Gets or sets the mode number for intra16 prediction. /// public int ModeI16 { get; set; } /// - /// Mode numbers for intra4 predictions. + /// Gets the mode numbers for intra4 predictions. /// public byte[] ModesI4 { get; } /// - /// Mode number of chroma prediction. + /// Gets or sets the mode number of chroma prediction. /// public int ModeUv { get; set; } /// - /// Non-zero blocks. + /// Gets or sets the Non-zero blocks. /// public uint Nz { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs index 85069bf5e..7d03f790a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs @@ -6,47 +6,47 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy internal class Vp8SegmentInfo { /// - /// quantization matrix y1. + /// Gets or sets the quantization matrix y1. /// public Vp8Matrix Y1 { get; set; } /// - /// quantization matrix y2. + /// Gets or sets the quantization matrix y2. /// public Vp8Matrix Y2 { get; set; } /// - /// quantization matrix uv. + /// Gets or sets the quantization matrix uv. /// public Vp8Matrix Uv { get; set; } /// - /// quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. + /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. /// public int Alpha { get; set; } /// - /// filter-susceptibility, range [0,255]. + /// Gets or sets the filter-susceptibility, range [0,255]. /// public int Beta { get; set; } /// - /// final segment quantizer. + /// Gets or sets the final segment quantizer. /// public int Quant { get; set; } /// - /// final in-loop filtering strength. + /// Gets or sets the final in-loop filtering strength. /// public int FStrength { get; set; } /// - /// max edge delta (for filtering strength). + /// Gets or sets the max edge delta (for filtering strength). /// public int MaxEdge { get; set; } /// - /// penalty for using Intra4. + /// Gets or sets the penalty for using Intra4. /// public long I4Penalty { get; set; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index fe4eef63e..a283acd26 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -39,16 +39,16 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly short[] Vp8Scan = { // Luma - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), - 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), - 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), - 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), + 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), + 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), + 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), }; public static readonly short[] Vp8ScanUv = { - 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U - 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U + 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V }; public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; From 7e23212ba101d573e229e066157534cde239fe43 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 28 Oct 2020 15:14:45 +0100 Subject: [PATCH 215/359] Implement CodeResiduals() and Vp8Bitwriter --- .../Formats/WebP/BitWriter/BitWriterBase.cs | 64 +++++ .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 257 +++++++++++++++++- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 46 +--- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 119 +++++--- .../Formats/WebP/Lossy/Vp8Encoder.cs | 66 ++++- .../Formats/WebP/Lossy/Vp8Residual.cs | 62 +++++ .../Formats/WebP/WebPLookupTables.cs | 28 ++ 7 files changed, 555 insertions(+), 87 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs new file mode 100644 index 000000000..fd3cd44d9 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.BitWriter +{ + internal abstract class BitWriterBase + { + /// + /// Buffer to write to. + /// + private byte[] buffer; + + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + protected BitWriterBase(int expectedSize) + { + // TODO: use memory allocator here. + this.buffer = new byte[expectedSize]; + } + + /// + /// Initializes a new instance of the class. + /// Used internally for cloning. + /// + private protected BitWriterBase(byte[] buffer) => this.buffer = buffer; + + public byte[] Buffer + { + get { return this.buffer; } + } + + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public abstract void BitWriterResize(int extraSize); + + protected bool ResizeBuffer(int maxBytes, int sizeRequired) + { + if (maxBytes > 0 && sizeRequired < maxBytes) + { + return true; + } + + int newSize = (3 * maxBytes) >> 1; + if (newSize < sizeRequired) + { + newSize = sizeRequired; + } + + // Make new size multiple of 1k. + newSize = ((newSize >> 10) + 1) << 10; + + // TODO: use memory allocator. + Array.Resize(ref this.buffer, newSize); + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index a0dd88113..d55716159 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -1,16 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.WebP.Lossy; + namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { /// - /// A bit writer for writing lossless webp streams. + /// A bit writer for writing lossy webp streams. /// - internal class Vp8BitWriter + internal class Vp8BitWriter : BitWriterBase { - /*private uint range; + private int range; - private uint value; + private int value; /// /// Number of outstanding bits. @@ -22,15 +24,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// private int nbBits; - private byte[] buffer; - - private int pos; + private uint pos; private int maxPos; - private bool error; + // private bool error; + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. public Vp8BitWriter(int expectedSize) + : base(expectedSize) { this.range = 255 - 1; this.value = 0; @@ -38,9 +43,239 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.nbBits = -8; this.pos = 0; this.maxPos = 0; - this.error = false; - // BitWriterResize(expected_size); - }*/ + // this.error = false; + } + + public uint Pos + { + get { return this.pos; } + } + + public int PutCoeffs(int ctx, Vp8Residual residual) + { + int tabIdx = 0; + int n = residual.First; + Vp8ProbaArray p = residual.Prob[n][ctx]; + if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) + { + return 0; + } + + while (n < 16) + { + int c = residual.Coeffs[n++]; + bool sign = c < 0; + int v = sign ? -c : c; + if (!this.PutBit(v != 0, p.Probabilities[1])) + { + p = residual.Prob[WebPConstants.Bands[n]][0]; + continue; + } + + if (!this.PutBit(v > 1, p.Probabilities[2])) + { + p = residual.Prob[WebPConstants.Bands[n]][1]; + } + else + { + if (!this.PutBit(v > 4, p.Probabilities[3])) + { + if (this.PutBit(v != 2, p.Probabilities[4])) + { + this.PutBit(v == 4, p.Probabilities[5]); + } + } + else if (!this.PutBit(v > 10, p.Probabilities[6])) + { + if (!this.PutBit(v > 6, p.Probabilities[7])) + { + this.PutBit(v == 6, 159); + } + else + { + this.PutBit(v >= 9, 165); + this.PutBit(!((v & 1) != 0), 145); + } + } + else + { + int mask; + byte[] tab; + if (v < 3 + (8 << 1)) + { + // VP8Cat3 (3b) + this.PutBit(0, p.Probabilities[8]); + this.PutBit(0, p.Probabilities[9]); + v -= 3 + (8 << 0); + mask = 1 << 2; + tab = WebPConstants.Cat3; + tabIdx = 0; + } + else if (v < 3 + (8 << 2)) + { + // VP8Cat4 (4b) + this.PutBit(0, p.Probabilities[8]); + this.PutBit(1, p.Probabilities[9]); + v -= 3 + (8 << 1); + mask = 1 << 3; + tab = WebPConstants.Cat4; + tabIdx = 0; + } + else if (v < 3 + (8 << 3)) + { + // VP8Cat5 (5b) + this.PutBit(1, p.Probabilities[8]); + this.PutBit(0, p.Probabilities[10]); + v -= 3 + (8 << 2); + mask = 1 << 4; + tab = WebPConstants.Cat5; + tabIdx = 0; + } + else + { + // VP8Cat6 (11b) + this.PutBit(1, p.Probabilities[8]); + this.PutBit(1, p.Probabilities[10]); + v -= 3 + (8 << 3); + mask = 1 << 10; + tab = WebPConstants.Cat6; + tabIdx = 0; + } + + while (mask != 0) + { + this.PutBit(v & mask, tab[tabIdx++]); + mask >>= 1; + } + } + + p = residual.Prob[WebPConstants.Bands[n]][2]; + } + + this.PutBitUniform(sign ? 1 : 0); + if (n == 16 || !this.PutBit(n <= residual.Last, p.Probabilities[0])) + { + return 1; // EOB + } + } + + return 1; + } + + private bool PutBit(bool bit, int prob) + { + return this.PutBit(bit ? 1 : 0, prob); + } + + private bool PutBit(int bit, int prob) + { + int split = (this.range * prob) >> 8; + if (bit != 0) + { + this.value += split + 1; + this.range -= split + 1; + } + else + { + this.range = split; + } + + if (this.range < 127) + { + // emit 'shift' bits out and renormalize. + int shift = WebPLookupTables.Norm[this.range]; + this.range = WebPLookupTables.NewRange[this.range]; + this.value <<= shift; + this.nbBits += shift; + if (this.nbBits > 0) + { + this.Flush(); + } + } + + return bit != 0; + } + + private int PutBitUniform(int bit) + { + int split = this.range >> 1; + if (bit != 0) + { + this.value += split + 1; + this.range -= split + 1; + } + else + { + this.range = split; + } + + if (this.range < 127) + { + this.range = WebPLookupTables.NewRange[this.range]; + this.value <<= 1; + this.nbBits += 1; + if (this.nbBits > 0) + { + this.Flush(); + } + } + + return bit; + } + + private void Flush() + { + int s = 8 + this.nbBits; + int bits = this.value >> s; + this.value -= bits << s; + this.nbBits -= 8; + if ((bits & 0xff) != 0xff) + { + var pos = this.pos; + this.BitWriterResize(this.run + 1); + + if ((bits & 0x100) != 0) + { + // overflow -> propagate carry over pending 0xff's + if (pos > 0) + { + this.Buffer[pos - 1]++; + } + } + + if (this.run > 0) + { + int value = (bits & 0x100) != 0 ? 0x00 : 0xff; + for (; this.run > 0; --this.run) + { + this.Buffer[pos++] = (byte)value; + } + } + + this.Buffer[pos++] = (byte)(bits & 0xff); + this.pos = pos; + } + else + { + this.run++; // Delay writing of bytes 0xff, pending eventual carry. + } + } + + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public override void BitWriterResize(int extraSize) + { + // TODO: review again if this works as intended. Probably needs a unit test ... + var neededSize = this.pos + extraSize; + if (neededSize <= this.maxPos) + { + return; + } + + this.ResizeBuffer(this.maxPos, (int)neededSize); + } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 5a931259f..99c819f17 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// /// A bit writer for writing lossless webp streams. /// - internal class Vp8LBitWriter + internal class Vp8LBitWriter : BitWriterBase { /// /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. @@ -32,11 +32,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// private int used; - /// - /// Buffer to write to. - /// - private byte[] buffer; - /// /// Current write position. /// @@ -49,10 +44,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// /// The expected size in bytes. public Vp8LBitWriter(int expectedSize) + : base(expectedSize) { - // TODO: maybe use memory allocator here. - this.buffer = new byte[expectedSize]; - this.end = this.buffer.Length; + this.end = this.Buffer.Length; } /// @@ -60,8 +54,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// Used internally for cloning. /// private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) + : base(buffer) { - this.buffer = buffer; this.bits = bits; this.used = used; this.cur = cur; @@ -113,8 +107,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public Vp8LBitWriter Clone() { - var clonedBuffer = new byte[this.buffer.Length]; - Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur); + var clonedBuffer = new byte[this.Buffer.Length]; + System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur); return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } @@ -124,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// The stream to write to. public void WriteToStream(Stream stream) { - stream.Write(this.buffer.AsSpan(0, this.NumBytes())); + stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); } /// @@ -135,7 +129,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.BitWriterResize((this.used + 7) >> 3); while (this.used > 0) { - this.buffer[this.cur++] = (byte)this.bits; + this.Buffer[this.cur++] = (byte)this.bits; this.bits >>= 8; this.used -= 8; } @@ -155,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.BitWriterResize(extraSize); } - BinaryPrimitives.WriteUInt64LittleEndian(this.buffer.AsSpan(this.cur), this.bits); + BinaryPrimitives.WriteUInt64LittleEndian(this.Buffer.AsSpan(this.cur), this.bits); this.cur += WriterBytes; this.bits >>= WriterBits; this.used -= WriterBits; @@ -165,30 +159,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// Resizes the buffer to write to. /// /// The extra size in bytes needed. - private void BitWriterResize(int extraSize) + public override void BitWriterResize(int extraSize) { - int maxBytes = this.end + this.buffer.Length; + // TODO: review again if this works as intended. Probably needs a unit test ... + int maxBytes = this.end + this.Buffer.Length; int sizeRequired = this.cur + extraSize; - if (maxBytes > 0 && sizeRequired < maxBytes) + if (this.ResizeBuffer(maxBytes, sizeRequired)) { return; } - int newSize = (3 * maxBytes) >> 1; - if (newSize < sizeRequired) - { - newSize = sizeRequired; - } - - // make new size multiple of 1k - newSize = ((newSize >> 10) + 1) << 10; - if (this.cur > 0) - { - Array.Resize(ref this.buffer, newSize); - } - - this.end = this.buffer.Length; + this.end = this.Buffer.Length; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index b9fed7f52..818b8ece7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -123,6 +123,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.TopNz = new int[9]; this.LeftNz = new int[9]; this.I4Boundary = new byte[37]; + this.BitCount = new long[4, 3]; // To match the C++ initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; @@ -226,6 +227,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public int[] LeftNz { get; } + /// + /// Gets or sets the macroblock bit-cost for luma. + /// + public long LumaBits { get; set; } + + /// + /// Gets the bit counters for coded levels. + /// + public long[,] BitCount { get; } + + /// + /// Gets or sets the macroblock bit-cost for chroma. + /// + public long UvBits { get; set; } + /// /// Gets or sets the number of mb still to be processed. /// @@ -641,6 +657,67 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YuvOut2 = tmp; } + public void NzToBytes() + { + Span nz = this.Nz.GetSpan(); + uint tnz = nz[0]; + uint lnz = nz[-1]; // TODO: -1? + Span topNz = this.TopNz; + Span leftNz = this.LeftNz; + + // Top-Y + topNz[0] = this.Bit(tnz, 12); + topNz[1] = this.Bit(tnz, 13); + topNz[2] = this.Bit(tnz, 14); + topNz[3] = this.Bit(tnz, 15); + + // Top-U + topNz[4] = this.Bit(tnz, 18); + topNz[5] = this.Bit(tnz, 19); + + // Top-V + topNz[6] = this.Bit(tnz, 22); + topNz[7] = this.Bit(tnz, 23); + + // DC + topNz[8] = this.Bit(tnz, 24); + + // left-Y + leftNz[0] = this.Bit(lnz, 3); + leftNz[1] = this.Bit(lnz, 7); + leftNz[2] = this.Bit(lnz, 11); + leftNz[3] = this.Bit(lnz, 15); + + // left-U + leftNz[4] = this.Bit(lnz, 17); + leftNz[5] = this.Bit(lnz, 19); + + // left-V + leftNz[6] = this.Bit(lnz, 21); + leftNz[7] = this.Bit(lnz, 23); + + // left-DC is special, iterated separately. + } + + public void BytesToNz() + { + uint nz = 0; + int[] topNz = this.TopNz; + int[] leftNz = this.LeftNz; + + // top + nz |= (uint)((topNz[0] << 12) | (topNz[1] << 13)); + nz |= (uint)((topNz[2] << 14) | (topNz[3] << 15)); + nz |= (uint)((topNz[4] << 18) | (topNz[5] << 19)); + nz |= (uint)((topNz[6] << 22) | (topNz[7] << 23)); + nz |= (uint)(topNz[8] << 24); // we propagate the top bit, esp. for intra4 + + // left + nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); + nz |= (uint)(leftNz[2] << 11); + nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); + } + private void Mean16x4(Span input, Span dc) { for (int k = 0; k < 4; ++k) @@ -1197,48 +1274,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Nz.GetSpan().Fill(0); } - private void NzToBytes() - { - Span nz = this.Nz.GetSpan(); - uint tnz = nz[0]; - uint lnz = nz[-1]; // TODO: -1? - Span topNz = this.TopNz; - Span leftNz = this.LeftNz; - - // Top-Y - topNz[0] = this.Bit(tnz, 12); - topNz[1] = this.Bit(tnz, 13); - topNz[2] = this.Bit(tnz, 14); - topNz[3] = this.Bit(tnz, 15); - - // Top-U - topNz[4] = this.Bit(tnz, 18); - topNz[5] = this.Bit(tnz, 19); - - // Top-V - topNz[6] = this.Bit(tnz, 22); - topNz[7] = this.Bit(tnz, 23); - - // DC - topNz[8] = this.Bit(tnz, 24); - - // left-Y - leftNz[0] = this.Bit(lnz, 3); - leftNz[1] = this.Bit(lnz, 7); - leftNz[2] = this.Bit(lnz, 11); - leftNz[3] = this.Bit(lnz, 15); - - // left-U - leftNz[4] = this.Bit(lnz, 17); - leftNz[5] = this.Bit(lnz, 19); - - // left-V - leftNz[6] = this.Bit(lnz, 21); - leftNz[7] = this.Bit(lnz, 23); - - // left-DC is special, iterated separately. - } - // Convert packed context to byte array. private int Bit(uint nz, int n) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 61661733a..16eb7e298 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private readonly byte[] zigzag = new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; + /// /// Initializes a new instance of the class. /// @@ -67,8 +69,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Nz = this.memoryAllocator.Allocate(mbw + 1); this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (mbw * mbh); - // TODO: properly initialize the bitwriter - this.bitWriter = new Vp8BitWriter(); + // Initialize the bitwriter. + var baseQuant = 36; // TODO: hardCoded for now. + int averageBytesPerMacroBlock = this.averageBytesPerMb[baseQuant >> 4]; + int expectedSize = mbw * mbh * averageBytesPerMacroBlock; + this.bitWriter = new Vp8BitWriter(expectedSize); } /// @@ -401,6 +406,63 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd) { + int x, y, ch; + var residual = new Vp8Residual(); + bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; + int segment = it.CurrentMacroBlockInfo.Segment; + + it.NzToBytes(); + + var pos1 = this.bitWriter.Pos; + if (i16) + { + residual.Init(0, 1); + residual.SetCoeffs(rd.YDcLevels); + int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); + it.TopNz[8] = it.LeftNz[8] = res; + residual.Init(1, 0); + } + else + { + residual.Init(0, 3); + } + + // luma-AC + for (y = 0; y < 4; ++y) + { + for (x = 0; x < 4; ++x) + { + int ctx = it.TopNz[x] + it.LeftNz[y]; + residual.SetCoeffs(rd.YAcLevels[x + (y * 4)]); + int res = this.bitWriter.PutCoeffs(ctx, residual); + it.TopNz[x] = it.LeftNz[y] = res; + } + } + + var pos2 = this.bitWriter.Pos; + + // U/V + residual.Init(0, 2); + for (ch = 0; ch <= 2; ch += 2) + { + for (y = 0; y < 2; ++y) + { + for (x = 0; x < 2; ++x) + { + int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; + residual.SetCoeffs(rd.UvLevels[(ch * 2) + x + (y * 2)]); + var res = this.bitWriter.PutCoeffs(ctx, residual); + it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; + } + } + } + + var pos3 = this.bitWriter.Pos; + it.LumaBits = pos2 - pos1; + it.UvBits = pos3 - pos2; + it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits; + it.BitCount[segment, 2] += it.UvBits; + it.BytesToNz(); } private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs new file mode 100644 index 000000000..58e60a76c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// On-the-fly info about the current set of residuals. + /// + internal class Vp8Residual + { + /// + /// Initializes a new instance of the class. + /// + public Vp8Residual() + { + this.Prob = new Vp8ProbaArray[3][]; + for (int i = 0; i < 3; i++) + { + this.Prob[i] = new Vp8ProbaArray[11]; + } + } + + public int First { get; set; } + + public int Last { get; set; } + + public int CoeffType { get; set; } + + public short[] Coeffs { get; set; } + + public Vp8ProbaArray[][] Prob { get; } + + public void Init(int first, int coeffType) + { + this.First = first; + this.CoeffType = coeffType; + + // TODO: + // res->prob = enc->proba_.coeffs_[coeff_type]; + // res->stats = enc->proba_.stats_[coeff_type]; + // res->costs = enc->proba_.remapped_costs_[coeff_type]; + } + + public void SetCoeffs(short[] coeffs) + { + int n; + this.Last = -1; + for (n = 15; n >= 0; --n) + { + if (coeffs[n] != 0) + { + this.Last = n; + break; + } + } + + this.Coeffs = coeffs; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index a283acd26..b23cdd323 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -53,6 +53,34 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + public static readonly byte[] Norm = + { + // renorm_sizes[i] = 8 - log2(i) + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 + }; + + public static readonly byte[] NewRange = + { + // range = ((range + 1) << kVP8Log2Range[range]) - 1 + 127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239, + 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, + 247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, + 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, + 243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, + 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, + 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, + 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 + }; + /// /// Lookup table for small values of log2(int). /// From 0807ede3bec7a27346be6048c379d51d55a2bc2c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 28 Oct 2020 16:02:12 +0100 Subject: [PATCH 216/359] Use quality and method from options in Vp8Encoder --- .../Formats/WebP/Lossy/Vp8Encoder.cs | 53 ++++++++++++++----- .../Formats/WebP/WebPEncoderCore.cs | 2 +- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 16eb7e298..e580421de 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -27,6 +27,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly Vp8BitWriter bitWriter; + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; + + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly int method; + /// /// Fixed-point precision for RGB->YUV. /// @@ -42,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int QFix = 17; - private readonly byte[] zigzag = new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + private readonly byte[] zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; @@ -52,9 +62,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// The memory allocator. /// The width of the input image. /// The height of the input image. - public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height) + /// The encoding quality. + /// Quality/speed trade-off (0=fast, 6=slower-better). + public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method) { this.memoryAllocator = memoryAllocator; + this.quality = quality.Clamp(0, 100); + this.method = method.Clamp(0, 6); var pixelCount = width * height; int mbw = (width + 15) >> 4; @@ -81,10 +95,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public int Alpha { get; set; } + /// + /// Gets the luma component. + /// private IMemoryOwner Y { get; } + /// + /// Gets the chroma U component. + /// private IMemoryOwner U { get; } + /// + /// Gets the chroma U component. + /// private IMemoryOwner V { get; } /// @@ -112,6 +135,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private int MbHeaderLimit { get; } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { @@ -139,10 +168,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh); - int method = 4; // TODO: hardcoded for now - int quality = 100; // TODO: hardcoded for now var alphas = new int[WebPConstants.MaxAlpha + 1]; - int alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, method, quality, alphas, out int uvAlpha); + int alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out int uvAlpha); // Analysis is done, proceed to actual coding. @@ -156,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height); - if (!this.Decimate(it, segmentInfos, info, method)) + if (!this.Decimate(it, segmentInfos, info)) { this.CodeResiduals(it, info); } @@ -196,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: SetupMatrices } - private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int method, int quality, int[] alphas, out int uvAlpha) + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha) { int alpha = 0; uvAlpha = 0; @@ -205,7 +232,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy do { it.Import(y, u, v, yStride, uvStride, width, height); - int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); + int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha); // Accumulate for later complexity analysis. alpha += bestAlpha; @@ -217,16 +244,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return alpha; } - private int MbAnalyze(Vp8EncIterator it, int method, int quality, int[] alphas, out int bestUvAlpha) + private int MbAnalyze(Vp8EncIterator it, int[] alphas, out int bestUvAlpha) { it.SetIntra16Mode(0); // default: Intra16, DC_PRED it.SetSkip(false); // not skipped. it.SetSegment(0); // default segment, spec-wise. int bestAlpha; - if (method <= 1) + if (this.method <= 1) { - bestAlpha = it.FastMbAnalyze(quality); + bestAlpha = it.FastMbAnalyze(this.quality); } else { @@ -244,7 +271,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } - private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, int method) + private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd) { rd.InitScore(); @@ -256,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - this.RefineUsingDistortion(it, segmentInfos, rd, method >= 2, method >= 1); + this.RefineUsingDistortion(it, segmentInfos, rd, this.method >= 2, this.method >= 1); bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 8943ce9bd..1158421f0 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.lossy) { - var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height); + var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method); enc.Encode(image, stream); } else From fec282e8771e51b5f1944fa78f8d3f652fcad35b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 30 Oct 2020 11:04:12 +0100 Subject: [PATCH 217/359] SetupMatrices and ResetBoundaryPredictions --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 49 ++- .../Formats/WebP/Lossy/LossyUtils.cs | 2 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 26 +- .../Formats/WebP/Lossy/Vp8EncSegmentHeader.cs | 34 ++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 397 +++++++++++++++--- .../Formats/WebP/Lossy/Vp8Matrix.cs | 77 +++- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 17 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 32 ++ .../Formats/WebP/WebPLookupTables.cs | 24 +- 10 files changed, 550 insertions(+), 112 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index d55716159..4e6260aa2 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -54,7 +54,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public int PutCoeffs(int ctx, Vp8Residual residual) { - int tabIdx = 0; int n = residual.First; Vp8ProbaArray p = residual.Prob[n][ctx]; if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) @@ -102,6 +101,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { int mask; byte[] tab; + var tabIdx = 0; if (v < 3 + (8 << 1)) { // VP8Cat3 (3b) @@ -163,6 +163,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return 1; } + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public override void BitWriterResize(int extraSize) + { + // TODO: review again if this works as intended. Probably needs a unit test ... + var neededSize = this.pos + extraSize; + if (neededSize <= this.maxPos) + { + return; + } + + this.ResizeBuffer(this.maxPos, (int)neededSize); + } + + public void Finish() + { + this.PutBits(0, 9 - this.nbBits); + this.nbBits = 0; // pad with zeroes. + this.Flush(); + } + + private void PutBits(uint value, int nbBits) + { + for (uint mask = 1u << (nbBits - 1); mask != 0; mask >>= 1) + { + this.PutBitUniform((int)(value & mask)); + } + } + private bool PutBit(bool bit, int prob) { return this.PutBit(bit ? 1 : 0, prob); @@ -261,21 +292,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.run++; // Delay writing of bytes 0xff, pending eventual carry. } } - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public override void BitWriterResize(int extraSize) - { - // TODO: review again if this works as intended. Probably needs a unit test ... - var neededSize = this.pos + extraSize; - if (neededSize <= this.maxPos) - { - return; - } - - this.ResizeBuffer(this.maxPos, (int)neededSize); - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 8c73c094c..a5971b0be 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -458,7 +458,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. /// - public static void TransformWht(short[] input, short[] output) + public static void TransformWht(Span input, Span output) { var tmp = new int[16]; for (int i = 0; i < 4; ++i) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 818b8ece7..5ec73669d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -133,6 +133,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YuvP.AsSpan().Fill(defaultInitVal); this.YLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal); + this.Preds.GetSpan().Fill(defaultInitVal); for (int i = -255; i <= 255 + 255; ++i) { @@ -197,6 +198,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public IMemoryOwner Preds { get; } + /// + /// Gets the current start index of the intra mode predictors. + /// + public int PredIdx + { + get + { + return this.predIdx; + } + } + /// /// Gets the non-zero pattern. /// @@ -277,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (i = 0; i < 17; ++i) { // left - this.I4Boundary[i] = this.YLeft[15 - i]; + this.I4Boundary[i] = this.YLeft[15 - i + 1]; } Span yTop = this.YTop.GetSpan(); @@ -287,7 +299,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.I4Boundary[17 + i] = yTop[i]; } - // top-right samples have a special case on the far right of the picture + // top-right samples have a special case on the far right of the picture. if (this.X < this.mbw - 1) { for (i = 16; i < 16 + 4; ++i) @@ -487,10 +499,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public short[] GetCostModeI4(byte[] modes) { int predsWidth = this.predsWidth; + int predIdx = this.predIdx; int x = this.I4 & 3; int y = this.I4 >> 2; - int left = (int)((x == 0) ? this.Preds.GetSpan()[(y * predsWidth) - 1] : modes[this.I4 - 1]); - int top = (int)((y == 0) ? this.Preds.GetSpan()[-predsWidth + x] : modes[this.I4 - 4]); + int left = (x == 0) ? this.Preds.Slice(predIdx)[(y * predsWidth) - 1] : modes[this.I4 - 1]; + int top = (y == 0) ? this.Preds.Slice(predIdx)[-predsWidth + x] : modes[this.I4 - 4]; return WebPLookupTables.Vp8FixedCostsI4[top, left]; } @@ -660,8 +673,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public void NzToBytes() { Span nz = this.Nz.GetSpan(); + + uint lnz = 0; // TODO: -1? uint tnz = nz[0]; - uint lnz = nz[-1]; // TODO: -1? Span topNz = this.TopNz; Span leftNz = this.LeftNz; @@ -1245,7 +1259,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.nzIdx = 0; this.yTopIdx = 0; this.uvTopIdx = 0; - this.predIdx = y * 4 * this.predsWidth; + this.predIdx = this.predsWidth + (y * 4 * this.predsWidth); this.InitLeft(); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs new file mode 100644 index 000000000..77a5ceecf --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8EncSegmentHeader + { + /// + /// Initializes a new instance of the class. + /// + /// Number of segments. + public Vp8EncSegmentHeader(int numSegments) + { + this.NumSegments = numSegments; + this.UpdateMap = this.NumSegments > 1; + this.Size = 0; + } + + /// + /// Gets the actual number of segments. 1 segment only = unused. + /// + public int NumSegments { get; } + + /// + /// Gets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. + /// + public bool UpdateMap { get; } + + /// + /// Gets the bit-cost for transmitting the segment map. + /// + public int Size { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index e580421de..fd3f541c7 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -37,6 +37,45 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly int method; + /// + /// Stride of the prediction plane (=4*mb_w + 1) + /// + private int predsWidth; + + /// + /// Macroblock width. + /// + private int mbw; + + /// + /// Macroblock height. + /// + private int mbh; + + /// + /// The segment features. + /// + private Vp8EncSegmentHeader segmentHeader; + + /// + /// Contextual macroblock infos. + /// + private Vp8MacroBlockInfo[] mbInfo; + + private int dqUvDc; + + private int dqUvAc; + + /// + /// Global susceptibility. + /// + private int alpha; + + /// + /// U/V quantization susceptibility. + /// + private int uvAlpha; + /// /// Fixed-point precision for RGB->YUV. /// @@ -50,12 +89,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int MaxLevel = 2047; - private const int QFix = 17; - private readonly byte[] zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; + private const int NumMbSegments = 4; + + private const int MaxItersKMeans = 6; + /// /// Initializes a new instance of the class. /// @@ -71,22 +112,34 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.method = method.Clamp(0, 6); var pixelCount = width * height; - int mbw = (width + 15) >> 4; - int mbh = (height + 15) >> 4; + this.mbw = (width + 15) >> 4; + this.mbh = (height + 15) >> 4; var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); - this.YTop = this.memoryAllocator.Allocate(mbw * 16); - this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2); - this.Preds = this.memoryAllocator.Allocate(((4 * mbw) + 1) * ((4 * mbh) + 1)); - this.Nz = this.memoryAllocator.Allocate(mbw + 1); - this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (mbw * mbh); + this.YTop = this.memoryAllocator.Allocate(this.mbw * 16); + this.UvTop = this.memoryAllocator.Allocate(this.mbw * 16 * 2); + this.Nz = this.memoryAllocator.Allocate(this.mbw + 1); + this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); + int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; + + this.mbInfo = new Vp8MacroBlockInfo[this.mbw * this.mbh]; + for (int i = 0; i < this.mbInfo.Length; i++) + { + this.mbInfo[i] = new Vp8MacroBlockInfo(); + } + + // this.Preds = this.memoryAllocator.Allocate(predSize); + this.Preds = this.memoryAllocator.Allocate(predSize * 2); // TODO: figure out how much mem we need here. This is too much. + this.predsWidth = (4 * this.mbw) + 1; + + this.ResetBoundaryPredictions(); // Initialize the bitwriter. var baseQuant = 36; // TODO: hardCoded for now. int averageBytesPerMacroBlock = this.averageBytesPerMb[baseQuant >> 4]; - int expectedSize = mbw * mbh * averageBytesPerMacroBlock; + int expectedSize = this.mbw * this.mbh * averageBytesPerMacroBlock; this.bitWriter = new Vp8BitWriter(expectedSize); } @@ -151,32 +204,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - int mbw = (width + 15) >> 4; - int mbh = (height + 15) >> 4; int yStride = width; int uvStride = (yStride + 1) >> 1; - var mb = new Vp8MacroBlockInfo[mbw * mbh]; - for (int i = 0; i < mb.Length; i++) - { - mb[i] = new Vp8MacroBlockInfo(); - } - var segmentInfos = new Vp8SegmentInfo[4]; for (int i = 0; i < 4; i++) { segmentInfos[i] = new Vp8SegmentInfo(); } - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); var alphas = new int[WebPConstants.MaxAlpha + 1]; - int alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out int uvAlpha); + this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); // Analysis is done, proceed to actual coding. // TODO: EncodeAlpha(); - // Compute segment probabilities. + this.segmentHeader = new Vp8EncSegmentHeader(4); + this.AssignSegments(segmentInfos, alphas); + this.SetSegmentParams(segmentInfos); this.SetSegmentProbas(segmentInfos); - this.SetupMatrices(segmentInfos); it.Init(); it.InitFilter(); do @@ -196,7 +242,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } while (it.Next()); - throw new NotImplementedException(); + this.bitWriter.Finish(); } /// @@ -210,6 +256,183 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Preds.Dispose(); } + private void ResetBoundaryPredictions() + { + Span top = this.Preds.GetSpan(); + Span left = this.Preds.Slice(this.predsWidth - 1); + for (int i = 0; i < 4 * this.mbw; ++i) + { + top[i] = (int)IntraPredictionMode.DcPrediction; + } + + for (int i = 0; i < 4 * this.mbh; ++i) + { + left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction; + } + + // TODO: enc->nz_[-1] = 0; // constant + } + + // Simplified k-Means, to assign Nb segments based on alpha-histogram. + private void AssignSegments(Vp8SegmentInfo[] dqm, int[] alphas) + { + int nb = (this.segmentHeader.NumSegments < NumMbSegments) ? this.segmentHeader.NumSegments : NumMbSegments; + var centers = new int[NumMbSegments]; + int weightedAverage = 0; + var map = new int[WebPConstants.MaxAlpha + 1]; + int a, n, k; + int minA; + int maxA; + int rangeA; + var accum = new int[NumMbSegments]; + var distAccum = new int[NumMbSegments]; + + // Bracket the input. + for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) { } + + minA = n; + for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } + + maxA = n; + rangeA = maxA - minA; + + // Spread initial centers evenly. + for (k = 0, n = 1; k < nb; ++k, n += 2) + { + centers[k] = minA + (n * rangeA / (2 * nb)); + } + + for (k = 0; k < MaxItersKMeans; ++k) + { + // Reset stats. + for (n = 0; n < nb; ++n) + { + accum[n] = 0; + distAccum[n] = 0; + } + + // Assign nearest center for each 'a' + n = 0; // track the nearest center for current 'a' + for (a = minA; a <= maxA; ++a) + { + if (alphas[a] != 0) + { + while (n + 1 < nb && Math.Abs(a - centers[n + 1]) < Math.Abs(a - centers[n])) + { + n++; + } + + map[a] = n; + + // Accumulate contribution into best centroid. + distAccum[n] += a * alphas[a]; + accum[n] += alphas[a]; + } + } + + // All point are classified. Move the centroids to the center of their respective cloud. + var displaced = 0; + weightedAverage = 0; + var totalWeight = 0; + for (n = 0; n < nb; ++n) + { + if (accum[n] != 0) + { + int newCenter = (distAccum[n] + (accum[n] / 2)) / accum[n]; + displaced += Math.Abs(centers[n] - newCenter); + centers[n] = newCenter; + weightedAverage += newCenter * accum[n]; + totalWeight += accum[n]; + } + } + + weightedAverage = (weightedAverage + (totalWeight / 2)) / totalWeight; + if (displaced < 5) + { + break; // no need to keep on looping... + } + } + + // Map each original value to the closest centroid + for (n = 0; n < this.mbw * this.mbh; ++n) + { + Vp8MacroBlockInfo mb = this.mbInfo[n]; + int alpha = mb.Alpha; + mb.Segment = map[alpha]; + mb.Alpha = centers[map[alpha]]; // for the record. + } + + // TODO: add possibility for SmoothSegmentMap + this.SetSegmentAlphas(dqm, centers, weightedAverage); + } + + private void SetSegmentAlphas(Vp8SegmentInfo[] dqm, int[] centers, int mid) + { + int nb = this.segmentHeader.NumSegments; + int min = centers[0], max = centers[0]; + int n; + + if (nb > 1) + { + for (n = 0; n < nb; ++n) + { + if (min > centers[n]) + { + min = centers[n]; + } + + if (max < centers[n]) + { + max = centers[n]; + } + } + } + + if (max == min) + { + max = min + 1; + } + + for (n = 0; n < nb; ++n) + { + int alpha = 255 * (centers[n] - mid) / (max - min); + int beta = 255 * (centers[n] - min) / (max - min); + dqm[n].Alpha = this.Clip(alpha, -127, 127); + dqm[n].Beta = this.Clip(beta, 0, 255); + } + } + + private void SetSegmentParams(Vp8SegmentInfo[] dqm) + { + int nb = this.segmentHeader.NumSegments; + int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. + double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; + double Q = this.quality / 100.0d; + double cBase = this.QualityToCompression(Q); + for (int i = 0; i < nb; ++i) + { + // We modulate the base coefficient to accommodate for the quantization + // susceptibility and allow denser segments to be quantized more. + double expn = 1.0d - (amp * dqm[i].Alpha); + double c = Math.Pow(cBase, expn); + int q = (int)(127.0d * (1.0d - c)); + dqm[i].Quant = this.Clip(q, 0, 127); + } + + // uvAlpha is normally spread around ~60. The useful range is + // typically ~30 (quite bad) to ~100 (ok to decimate UV more). + // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. + this.dqUvAc = (this.uvAlpha - WebPConstants.QuantEncMidAlpha) * (WebPConstants.QuantEncMaxDqUv - WebPConstants.QuantEncMinDqUv) / (WebPConstants.QuantEncMaxAlpha - WebPConstants.QuantEncMinAlpha); + + // We rescale by the user-defined strength of adaptation. + this.dqUvAc = this.dqUvAc * snsStrength / 100; + + // and make it safe. + this.dqUvAc = this.Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); + + this.SetupMatrices(dqm); + } + private void SetSegmentProbas(Vp8SegmentInfo[] dqm) { // var p = new int[4]; @@ -220,7 +443,28 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void SetupMatrices(Vp8SegmentInfo[] dqm) { - // TODO: SetupMatrices + for (int i = 0; i < dqm.Length; ++i) + { + Vp8SegmentInfo m = dqm[i]; + int q = m.Quant; + + m.Y1 = new Vp8Matrix(); + m.Y2 = new Vp8Matrix(); + m.Uv = new Vp8Matrix(); + + m.Y1.Q[0] = WebPLookupTables.DcTable[this.Clip(q, 0, 127)]; + m.Y1.Q[1] = WebPLookupTables.AcTable[this.Clip(q, 0, 127)]; + + m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[this.Clip(q, 0, 127)] * 2); + m.Y2.Q[1] = WebPLookupTables.AcTable2[this.Clip(q, 0, 127)]; + + m.Uv.Q[0] = WebPLookupTables.DcTable[this.Clip(q + this.dqUvDc, 0, 117)]; + m.Uv.Q[1] = WebPLookupTables.AcTable[this.Clip(q + this.dqUvAc, 0, 127)]; + + var qi4 = m.Y1.Expand(0); + + m.I4Penalty = 1000 * qi4 * qi4; + } } private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha) @@ -304,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int lambdaDi16 = 106; int lambdaDi4 = 11; int lambdaDuv = 120; - long scoreI4 = 676000; // TODO: hardcoded for now: long scoreI4 = dqm->i4_penalty_; + long scoreI4 = dqm.I4Penalty; long i4BitSum = 0; long bitLimit = tryBothModes ? this.MbHeaderLimit @@ -387,7 +631,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { // Reconstruct partial block inside yuv_out2 buffer Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); - nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4; + nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4, 16), src, tmpDst, bestI4Mode) << it.I4; } } while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); @@ -402,7 +646,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]); + nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.Slice(it.PredIdx)[0]); } // ... and UV! @@ -460,7 +704,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 4; ++x) { int ctx = it.TopNz[x] + it.LeftNz[y]; - residual.SetCoeffs(rd.YAcLevels[x + (y * 4)]); + residual.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4), 16)); int res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[x] = it.LeftNz[y] = res; } @@ -477,7 +721,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 2; ++x) { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels[(ch * 2) + x + (y * 2)]); + residual.SetCoeffs(rd.UvLevels.AsSpan((ch * 2) + x + (y * 2), 16)); var res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; } @@ -499,39 +743,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int nz = 0; int n; var dcTmp = new short[16]; - var tmp = new short[16][]; - for (int i = 0; i < 16; i++) - { - tmp[i] = new short[16]; - } + var tmp = new short[16 * 16]; + Span tmpSpan = tmp.AsSpan(); for (n = 0; n < 16; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n]); + this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); } - this.FTransformWht(tmp[0], dcTmp); + this.FTransformWht(tmp.AsSpan(0), dcTmp); nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; for (n = 0; n < 16; n += 2) { // Zero-out the first coeff, so that: a) nz is correct below, and // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. - tmp[n][0] = tmp[n + 1][0] = 0; - nz |= this.Quantize2Blocks(tmp[n], rd.YAcLevels[n], dqm.Y1) << n; + tmp[n * 16] = tmp[(n + 1) * 16] = 0; + nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16), rd.YAcLevels.AsSpan(n, 32), dqm.Y1) << n; } // Transform back. - LossyUtils.TransformWht(dcTmp, tmp[0]); + LossyUtils.TransformWht(dcTmp, tmpSpan); for (n = 0; n < 16; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); + this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); } return nz; } - private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, short[] levels, Span src, Span yuvOut, int mode) + private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); var tmp = new short[16]; @@ -548,15 +789,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; - var tmp = new short[8][]; - for (int i = 0; i < 8; i++) - { - tmp[i] = new short[16]; - } + var tmp = new short[8* 16]; for (n = 0; n < 8; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n]); + this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), + reference.Slice(WebPLookupTables.Vp8ScanUv[n]), + tmp.AsSpan(n * 16, 16), + tmp.AsSpan((n + 1) * 16, 16)); } /* TODO: @@ -567,33 +807,35 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 8; n += 2) { - nz |= this.Quantize2Blocks(tmp[n], rd.UvLevels[n], dqm.Uv) << n; + nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n, 32), dqm.Uv) << n; } for (n = 0; n < 8; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); + this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); } return nz << 16; } - private void FTransform2(Span src, Span reference, short[] output) + private void FTransform2(Span src, Span reference, Span output, Span output2) { this.FTransform(src, reference, output); - this.FTransform(src.Slice(4), reference.Slice(4), output.AsSpan(16)); + this.FTransform(src.Slice(4), reference.Slice(4), output2); } private void FTransform(Span src, Span reference, Span output) { int i; var tmp = new int[16]; + int srcIdx = 0; + int refIdx = 0; for (i = 0; i < 4; ++i) { - int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) - int d1 = src[1] - reference[1]; - int d2 = src[2] - reference[2]; - int d3 = src[3] - reference[3]; + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d3 = src[srcIdx + 3] - reference[refIdx + 3]; int a0 = d0 + d3; // 10b [-510,510] int a1 = d1 + d2; int a2 = d1 - d2; @@ -603,8 +845,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy tmp[2 + (i * 4)] = (a0 - a1) * 8; tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - src = src.Slice(WebPConstants.Bps); - reference = reference.Slice(WebPConstants.Bps); + srcIdx += WebPConstants.Bps; + refIdx += WebPConstants.Bps; } for (i = 0; i < 4; ++i) @@ -624,18 +866,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { var tmp = new int[16]; int i; + int inputIdx = 0; for (i = 0; i < 4; ++i) { - int a0 = input[0 * 16] + input[2 * 16]; // 13b - int a1 = input[1 * 16] + input[3 * 16]; - int a2 = input[1 * 16] - input[3 * 16]; - int a3 = input[0 * 16] - input[2 * 16]; + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b + int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; + int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; tmp[0 + (i * 4)] = a0 + a1; // 14b tmp[1 + (i * 4)] = a3 + a2; tmp[2 + (i * 4)] = a3 - a2; tmp[3 + (i * 4)] = a0 - a1; - input = input.Slice(64); + inputIdx += 64; } for (i = 0; i < 4; ++i) @@ -705,12 +948,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return (last >= 0) ? 1 : 0; } - private void ITransform(Span reference, short[] input, Span dst, bool doTwo) + private void ITransform(Span reference, Span input, Span dst, bool doTwo) { this.ITransformOne(reference, input, dst); if (doTwo) { - this.ITransformOne(reference.Slice(4), input.AsSpan(16), dst.Slice(4)); + this.ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); } } @@ -1096,8 +1339,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span vSpan = BitConverter.GetBytes(v).AsSpan(); for (int i = 0; i < 16; ++i) { - if (src.Slice(0, 4).SequenceEqual(vSpan) || src.Slice(4, 4).SequenceEqual(vSpan) || - src.Slice(0, 8).SequenceEqual(vSpan) || src.Slice(12, 4).SequenceEqual(vSpan)) + if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || + !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) { return false; } @@ -1108,10 +1351,30 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return true; } + /// + /// We want to emulate jpeg-like behaviour where the expected "good" quality + /// is around q=75. Internally, our "good" middle is around c=50. So we + /// map accordingly using linear piece-wise function + /// + private double QualityToCompression(double c) + { + double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; + + // The file size roughly scales as pow(quantizer, 3.). Actually, the + // exponent is somewhere between 2.8 and 3.2, but we're mostly interested + // in the mid-quant range. So we scale the compressibility inversely to + // this power-law: quant ~= compression ^ 1/3. This law holds well for + // low quant. Finer modeling for high-quant would make use of AcTable[] + // more explicitly. + double v = Math.Pow(linearC, 1 / 3.0d); + + return v; + } + [MethodImpl(InliningOptions.ShortMethod)] private int QuantDiv(uint n, uint iQ, uint b) { - return (int)(((n * iQ) + b) >> QFix); + return (int)(((n * iQ) + b) >> WebPConstants.QFix); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 2ee9671ed..9c7711352 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -5,13 +5,30 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { internal class Vp8Matrix { + private static readonly int[][] BiasMatrices = + { + // [luma-ac,luma-dc,chroma][dc,ac] + new[] { 96, 110 }, + new[] { 96, 108 }, + new[] { 110, 115 } + }; + + // Sharpening by (slightly) raising the hi-frequency coeffs. + // Hack-ish but helpful for mid-bitrate range. Use with care. + private static readonly byte[] FreqSharpening = { 0, 30, 60, 90, 30, 60, 90, 90, 60, 90, 90, 90, 90, 90, 90, 90 }; + + /// + /// Number of descaling bits for sharpening bias. + /// + private const int SharpenBits = 11; + /// /// Initializes a new instance of the class. /// public Vp8Matrix() { - this.Q = new short[16]; - this.IQ = new short[16]; + this.Q = new ushort[16]; + this.IQ = new ushort[16]; this.Bias = new uint[16]; this.ZThresh = new uint[16]; this.Sharpen = new short[16]; @@ -20,12 +37,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the quantizer steps. /// - public short[] Q { get; } + public ushort[] Q { get; } /// /// Gets the reciprocals, fixed point. /// - public short[] IQ { get; } + public ushort[] IQ { get; } /// /// Gets the rounding bias. @@ -41,5 +58,57 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// Gets the frequency boosters for slight sharpening. /// public short[] Sharpen { get; } + + /// + /// Returns the average quantizer. + /// + /// The average quantizer. + public int Expand(int type) + { + int sum; + int i; + for (i = 0; i < 2; ++i) + { + int isAcCoeff = (i > 0) ? 1 : 0; + int bias = BiasMatrices[type][isAcCoeff]; + this.IQ[i] = (ushort)((1 << WebPConstants.QFix) / this.Q[i]); + this.Bias[i] = (uint)this.BIAS(bias); + + // zthresh_ is the exact value such that QUANTDIV(coeff, iQ, B) is: + // * zero if coeff <= zthresh + // * non-zero if coeff > zthresh + this.ZThresh[i] = (uint)(((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]); + } + + for (i = 2; i < 16; ++i) + { + this.Q[i] = this.Q[1]; + this.IQ[i] = this.IQ[1]; + this.Bias[i] = this.Bias[1]; + this.ZThresh[i] = this.ZThresh[1]; + } + + for (sum = 0, i = 0; i < 16; ++i) + { + if (type == 0) + { + // We only use sharpening for AC luma coeffs. + this.Sharpen[i] = (short)((FreqSharpening[i] * this.Q[i]) >> SharpenBits); + } + else + { + this.Sharpen[i] = 0; + } + + sum += this.Q[i]; + } + + return (sum + 8) >> 4; + } + + private int BIAS(int b) + { + return b << (WebPConstants.QFix - 8); + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index 7ec02fac6..f9316e40b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -16,17 +16,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public Vp8ModeScore() { this.YDcLevels = new short[16]; - this.YAcLevels = new short[16][]; - for (int i = 0; i < 16; i++) - { - this.YAcLevels[i] = new short[16]; - } - - this.UvLevels = new short[4 + 4][]; - for (int i = 0; i < 8; i++) - { - this.UvLevels[i] = new short[16]; - } + this.YAcLevels = new short[16 * 16]; + this.UvLevels = new short[4 + (4 * 16)]; this.ModesI4 = new byte[16]; } @@ -64,12 +55,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the quantized levels for luma-AC. /// - public short[][] YAcLevels { get; } + public short[] YAcLevels { get; } /// /// Gets the quantized levels for chroma. /// - public short[][] UvLevels { get; } + public short[] UvLevels { get; } /// /// Gets or sets the mode number for intra16 prediction. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 58e60a76c..ccc4471ee 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // res->costs = enc->proba_.remapped_costs_[coeff_type]; } - public void SetCoeffs(short[] coeffs) + public void SetCoeffs(Span coeffs) { int n; this.Last = -1; @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - this.Coeffs = coeffs; + this.Coeffs = coeffs.ToArray(); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 9a2b3ee8b..6721b2da1 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -205,10 +205,42 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int AlphaFix = 19; + /// + /// 8b of precision for susceptibilities. + /// public const int MaxAlpha = 255; + /// + /// Scaling factor for alpha. + /// public const int AlphaScale = 2 * MaxAlpha; + /// + /// Neutral value for susceptibility. + /// + public const int QuantEncMidAlpha = 64; + + /// + /// Lowest usable value for susceptibility. + /// + public const int QuantEncMinAlpha = 30; + + /// + /// Higher meaningful value for susceptibility. + /// + public const int QuantEncMaxAlpha = 100; + + /// + /// Scaling constant between the sns (Spatial Noise Shaping) value and the QP power-law modulation. Must be strictly less than 1. + /// + public const double SnsToDq = 0.9; + + public const int QuantEncMaxDqUv = 6; + + public const int QuantEncMinDqUv = -4; + + public const int QFix = 17; + public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index b23cdd323..6474d812d 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.WebP }; // Paragraph 14.1 - public static readonly int[] DcTable = + public static readonly byte[] DcTable = { 4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17, @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.WebP }; // Paragraph 14.1 - public static readonly int[] AcTable = + public static readonly ushort[] AcTable = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, @@ -375,6 +375,26 @@ namespace SixLabors.ImageSharp.Formats.WebP 249, 254, 259, 264, 269, 274, 279, 284 }; + public static readonly ushort[] AcTable2 = + { + 8, 8, 9, 10, 12, 13, 15, 17, + 18, 20, 21, 23, 24, 26, 27, 29, + 31, 32, 34, 35, 37, 38, 40, 41, + 43, 44, 46, 48, 49, 51, 52, 54, + 55, 57, 58, 60, 62, 63, 65, 66, + 68, 69, 71, 72, 74, 75, 77, 79, + 80, 82, 83, 85, 86, 88, 89, 93, + 96, 99, 102, 105, 108, 111, 114, 117, + 120, 124, 127, 130, 133, 136, 139, 142, + 145, 148, 151, 155, 158, 161, 164, 167, + 170, 173, 176, 179, 184, 189, 193, 198, + 203, 207, 212, 217, 221, 226, 230, 235, + 240, 244, 249, 254, 258, 263, 268, 274, + 280, 286, 292, 299, 305, 311, 317, 323, + 330, 336, 342, 348, 354, 362, 370, 379, + 385, 393, 401, 409, 416, 424, 432, 440 + }; + // Paragraph 13 public static readonly byte[,,,] CoeffsUpdateProba = { From 6eee467c623a1788e71f9dc1114ac8d7bffd67a4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 30 Oct 2020 19:25:21 +0100 Subject: [PATCH 218/359] CalculateLevelCosts --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 8 +- .../Formats/WebP/Lossy/Vp8CostArray.cs | 18 ++ .../Formats/WebP/Lossy/Vp8EncProba.cs | 169 ++++++++++++++++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 45 +++-- .../Formats/WebP/Lossy/Vp8Residual.cs | 18 +- .../Formats/WebP/WebPLookupTables.cs | 51 ++++++ 6 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 4e6260aa2..1150168eb 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public int PutCoeffs(int ctx, Vp8Residual residual) { int n = residual.First; - Vp8ProbaArray p = residual.Prob[n][ctx]; + Vp8ProbaArray p = residual.Prob[n].Probabilities[ctx]; if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) { return 0; @@ -68,13 +68,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter int v = sign ? -c : c; if (!this.PutBit(v != 0, p.Probabilities[1])) { - p = residual.Prob[WebPConstants.Bands[n]][0]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[0]; continue; } if (!this.PutBit(v > 1, p.Probabilities[2])) { - p = residual.Prob[WebPConstants.Bands[n]][1]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[1]; } else { @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } - p = residual.Prob[WebPConstants.Bands[n]][2]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[2]; } this.PutBitUniform(sign ? 1 : 0); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs new file mode 100644 index 000000000..f0ff85989 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8CostArray + { + /// + /// Initializes a new instance of the class. + /// + public Vp8CostArray() + { + this.Costs = new ushort[WebPConstants.NumCtx * (67 + 1)]; + } + + public ushort[] Costs { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs new file mode 100644 index 000000000..8ae019ef6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8EncProba + { + /// + /// Last (inclusive) level with variable cost. + /// + private const int MaxVariableLevel = 67; + + /// + /// Initializes a new instance of the class. + /// + public Vp8EncProba() + { + this.Dirty = true; + this.UseSkipProba = false; + this.Segments = new byte[3]; + this.Coeffs = new Vp8BandProbas[WebPConstants.NumTypes][]; + for (int i = 0; i < this.Coeffs.Length; i++) + { + this.Coeffs[i] = new Vp8BandProbas[WebPConstants.NumBands]; + for (int j = 0; j < this.Coeffs[i].Length; j++) + { + this.Coeffs[i][j] = new Vp8BandProbas(); + } + } + + this.LevelCost = new Vp8CostArray[WebPConstants.NumTypes][]; + for (int i = 0; i < this.LevelCost.Length; i++) + { + this.LevelCost[i] = new Vp8CostArray[WebPConstants.NumBands]; + for (int j = 0; j < this.LevelCost[i].Length; j++) + { + this.LevelCost[i][j] = new Vp8CostArray(); + } + } + + this.RemappedCosts = new Vp8CostArray[WebPConstants.NumTypes][]; + for (int i = 0; i < this.RemappedCosts.Length; i++) + { + this.RemappedCosts[i] = new Vp8CostArray[16]; + for (int j = 0; j < this.RemappedCosts[i].Length; j++) + { + this.RemappedCosts[i][j] = new Vp8CostArray(); + } + } + + // Initialize with default probabilities. + this.Segments.AsSpan().Fill(255); + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + dst.Probabilities[p] = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + } + } + } + } + } + + /// + /// Gets the probabilities for segment tree. + /// + public byte[] Segments { get; } + + /// + /// Gets the final probability of being skipped. + /// + public byte SkipProba { get; } + + /// + /// Gets a value indicating whether to use the skip probability. Note: we always use SkipProba for now. + /// + public bool UseSkipProba { get; } + + public Vp8BandProbas[][] Coeffs { get; } + + public Vp8CostArray[][] LevelCost { get; } + + public Vp8CostArray[][] RemappedCosts { get; } + + /// + /// Gets or sets the number of skipped blocks. + /// + public int NbSkip { get; set; } + + /// + /// Gets or sets a value indicating whether CalculateLevelCosts() needs to be called. + /// + public bool Dirty { get; set; } + + public void CalculateLevelCosts() + { + if (!this.Dirty) + { + return; // nothing to do. + } + + for (int ctype = 0; ctype < WebPConstants.NumTypes; ++ctype) + { + for (int band = 0; band < WebPConstants.NumBands; ++band) + { + for (int ctx = 0; ctx < WebPConstants.NumCtx; ++ctx) + { + Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; + Span table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel); + int cost0 = (ctx > 0) ? this.BitCost(1, p.Probabilities[0]) : 0; + int costBase = this.BitCost(1, p.Probabilities[1]) + cost0; + int v; + table[0] = (ushort)(this.BitCost(0, p.Probabilities[1]) + cost0); + for (v = 1; v <= MaxVariableLevel; ++v) + { + table[v] = (ushort)(costBase + this.VariableLevelCost(v, p.Probabilities)); + } + + // Starting at level 67 and up, the variable part of the cost is actually constant. + } + } + + for (int n = 0; n < 16; ++n) + { + for (int ctx = 0; ctx < WebPConstants.NumCtx; ++ctx) + { + Span dst = this.RemappedCosts[ctype][n].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); + Span src = this.LevelCost[ctype][WebPConstants.Bands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); + src.CopyTo(dst); + } + } + } + + this.Dirty = false; + } + + private int VariableLevelCost(int level, Span probas) + { + int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0]; + int bits = WebPLookupTables.Vp8LevelCodes[level - 1][1]; + int cost = 0; + for (int i = 2; pattern != 0; ++i) + { + if ((pattern & 1) != 0) + { + cost += this.BitCost(bits & 1, probas[i]); + } + + bits >>= 1; + pattern >>= 1; + } + + return cost; + } + + // Cost of coding one event with probability 'proba'. + private int BitCost(int bit, byte proba) + { + return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index fd3f541c7..c9426af16 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -62,6 +62,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private Vp8MacroBlockInfo[] mbInfo; + /// + /// Probabilities. + /// + private Vp8EncProba proba; + private int dqUvDc; private int dqUvAc; @@ -130,6 +135,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.mbInfo[i] = new Vp8MacroBlockInfo(); } + this.proba = new Vp8EncProba(); + // this.Preds = this.memoryAllocator.Allocate(predSize); this.Preds = this.memoryAllocator.Allocate(predSize * 2); // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; @@ -223,6 +230,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.AssignSegments(segmentInfos, alphas); this.SetSegmentParams(segmentInfos); this.SetSegmentProbas(segmentInfos); + this.ResetStats(); it.Init(); it.InitFilter(); do @@ -288,10 +296,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var distAccum = new int[NumMbSegments]; // Bracket the input. - for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) { } + for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) + { + } minA = n; - for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } + for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) + { + } maxA = n; rangeA = maxA - minA; @@ -407,8 +419,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int nb = this.segmentHeader.NumSegments; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; - double Q = this.quality / 100.0d; - double cBase = this.QualityToCompression(Q); + double cBase = this.QualityToCompression(this.quality / 100.0d); for (int i = 0; i < nb; ++i) { // We modulate the base coefficient to accommodate for the quantization @@ -430,6 +441,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // and make it safe. this.dqUvAc = this.Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); + // We also boost the dc-uv-quant a little, based on sns-strength, since + // U/V channels are quite more reactive to high quants (flat DC-blocks + // tend to appear, and are unpleasant). + this.dqUvDc = -4 * snsStrength / 100; + this.dqUvDc = this.Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed + this.SetupMatrices(dqm); } @@ -441,6 +458,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: SetSegmentProbas } + private void ResetStats() + { + Vp8EncProba proba = this.proba; + proba.CalculateLevelCosts(); + proba.NbSkip = 0; + } + private void SetupMatrices(Vp8SegmentInfo[] dqm) { for (int i = 0; i < dqm.Length; ++i) @@ -687,15 +711,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var pos1 = this.bitWriter.Pos; if (i16) { - residual.Init(0, 1); + residual.Init(0, 1, this.proba); residual.SetCoeffs(rd.YDcLevels); int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); it.TopNz[8] = it.LeftNz[8] = res; - residual.Init(1, 0); + residual.Init(1, 0, this.proba); } else { - residual.Init(0, 3); + residual.Init(0, 3, this.proba); } // luma-AC @@ -713,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var pos2 = this.bitWriter.Pos; // U/V - residual.Init(0, 2); + residual.Init(0, 2, this.proba); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) @@ -789,11 +813,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; - var tmp = new short[8* 16]; + var tmp = new short[8 * 16]; for (n = 0; n < 8; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), + this.FTransform2( + src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 16), tmp.AsSpan((n + 1) * 16, 16)); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index ccc4471ee..96efe7f4f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -10,18 +10,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// internal class Vp8Residual { - /// - /// Initializes a new instance of the class. - /// - public Vp8Residual() - { - this.Prob = new Vp8ProbaArray[3][]; - for (int i = 0; i < 3; i++) - { - this.Prob[i] = new Vp8ProbaArray[11]; - } - } - public int First { get; set; } public int Last { get; set; } @@ -30,15 +18,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public short[] Coeffs { get; set; } - public Vp8ProbaArray[][] Prob { get; } + public Vp8BandProbas[] Prob { get; set; } - public void Init(int first, int coeffType) + public void Init(int first, int coeffType, Vp8EncProba prob) { this.First = first; this.CoeffType = coeffType; + this.Prob = prob.Coeffs[this.CoeffType]; // TODO: - // res->prob = enc->proba_.coeffs_[coeff_type]; // res->stats = enc->proba_.stats_[coeff_type]; // res->costs = enc->proba_.remapped_costs_[coeff_type]; } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 6474d812d..a550903e0 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -81,6 +81,57 @@ namespace SixLabors.ImageSharp.Formats.WebP 241, 243, 245, 247, 249, 251, 253, 127 }; + public static readonly ushort[] Vp8EntropyCost = + { + 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, + 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, + 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, + 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, + 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, + 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, + 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, + 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, + 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, + 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, + 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, + 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, + 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, + 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, + 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, + 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, + 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, + 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, + 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, + 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, + 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, + 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, + 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, + 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, + 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, + 10, 9, 7, 6, 4, 3 + }; + + public static readonly ushort[][] Vp8LevelCodes = + { + new ushort[] { 0x001, 0x000 }, new ushort[] { 0x007, 0x001 }, new ushort[] { 0x00f, 0x005 }, + new ushort[] { 0x00f, 0x00d }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x023 }, + new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x153 }, + }; + /// /// Lookup table for small values of log2(int). /// From adc2efc650e7d6cae9e85beee53f5f820b919a5c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 31 Oct 2020 17:04:39 +0100 Subject: [PATCH 219/359] Write encoded image data to the stream, fix some indexing issues --- .../Formats/WebP/BitWriter/BitWriterBase.cs | 77 ++++++++++ .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 8 +- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 19 +-- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 32 +---- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 136 +++++++++--------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 22 +-- .../Formats/WebP/Lossy/Vp8Matrix.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 2 +- 8 files changed, 172 insertions(+), 128 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index fd3cd44d9..1b011315c 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; +using System.IO; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { @@ -33,12 +35,32 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter get { return this.buffer; } } + /// + /// Writes the encoded bytes of the image to the stream. Call Finish() before this. + /// + /// The stream to write to. + public void WriteToStream(Stream stream) + { + stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); + } + /// /// Resizes the buffer to write to. /// /// The extra size in bytes needed. public abstract void BitWriterResize(int extraSize); + /// + /// Returns the number of bytes of the encoded image data. + /// + /// The number of bytes of the image data. + public abstract int NumBytes(); + + /// + /// Flush leftover bits. + /// + public abstract void Finish(); + protected bool ResizeBuffer(int maxBytes, int sizeRequired) { if (maxBytes > 0 && sizeRequired < maxBytes) @@ -60,5 +82,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return false; } + + /// + /// Writes the encoded image to the stream. + /// + /// If true, lossy tag will be written, otherwise a lossless tag. + /// The stream to write to. + public void WriteEncodedImageToStream(bool lossy, Stream stream) + { + this.Finish(); + var numBytes = this.NumBytes(); + var size = numBytes; + if (!lossy) + { + size++; // One byte extra for the VP8L signature. + } + + var pad = size & 1; + var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + size + pad; + this.WriteRiffHeader(riffSize, size, lossy, stream); + this.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); + } + } + + /// + /// Writes the RIFF header to the stream. + /// + /// The block length. + /// The size in bytes of the encoded image. + /// If true, lossy tag will be written, otherwise a lossless tag. + /// The stream to write to. + private void WriteRiffHeader(int riffSize, int size, bool lossy, Stream stream) + { + Span buffer = stackalloc byte[4]; + + stream.Write(WebPConstants.RiffFourCc); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize); + stream.Write(buffer); + stream.Write(WebPConstants.WebPHeader); + + if (lossy) + { + stream.Write(WebPConstants.Vp8MagicBytes); + } + else + { + stream.Write(WebPConstants.Vp8LMagicBytes); + } + + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)size); + stream.Write(buffer); + stream.WriteByte(WebPConstants.Vp8LMagicByte); + } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 1150168eb..8d3fe61b4 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -47,9 +47,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter // this.error = false; } - public uint Pos + /// + public override int NumBytes() { - get { return this.pos; } + return (int)this.pos; } public int PutCoeffs(int ctx, Vp8Residual residual) @@ -179,7 +180,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.ResizeBuffer(this.maxPos, (int)neededSize); } - public void Finish() + /// + public override void Finish() { this.PutBits(0, 9 - this.nbBits); this.nbBits = 0; // pad with zeroes. diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 99c819f17..917646bfe 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -100,7 +99,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.PutBits((uint)((bits << depth) | symbol), depth + nBits); } - public int NumBytes() + /// + public override int NumBytes() { return this.cur + ((this.used + 7) >> 3); } @@ -112,19 +112,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } - /// - /// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this. - /// - /// The stream to write to. - public void WriteToStream(Stream stream) - { - stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); - } - - /// - /// Flush leftover bits. - /// - public void BitWriterFinish() + /// + public override void Finish() { this.BitWriterResize((this.used + 7) >> 3); while (this.used > 0) diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index e951c70e7..55e129e7d 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -186,17 +186,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.EncodeStream(image); // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.BitWriterFinish(); - var numBytes = this.bitWriter.NumBytes(); - var vp8LSize = 1 + numBytes; // One byte extra for the VP8L signature. - var pad = vp8LSize & 1; - var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8LSize + pad; - this.WriteRiffHeader(riffSize, vp8LSize, stream); - this.bitWriter.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } + this.bitWriter.WriteEncodedImageToStream(lossy: false, stream); } /// @@ -226,26 +216,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); } - /// - /// Writes the RIFF header to the stream. - /// - /// The block length. - /// The size in bytes of the compressed image. - /// The stream to write to. - private void WriteRiffHeader(int riffSize, int vp8LSize, Stream stream) - { - Span buffer = stackalloc byte[4]; - - stream.Write(WebPConstants.RiffFourCc); - BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize); - stream.Write(buffer); - stream.Write(WebPConstants.WebPHeader); - stream.Write(WebPConstants.Vp8LTag); - BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)vp8LSize); - stream.Write(buffer); - stream.WriteByte(WebPConstants.Vp8LMagicByte); - } - /// /// Encodes the image stream using lossless webp format. /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 5ec73669d..9901d0cff 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -376,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy else { this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16); - this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8); + this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); } } @@ -502,8 +502,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int predIdx = this.predIdx; int x = this.I4 & 3; int y = this.I4 >> 2; - int left = (x == 0) ? this.Preds.Slice(predIdx)[(y * predsWidth) - 1] : modes[this.I4 - 1]; - int top = (y == 0) ? this.Preds.Slice(predIdx)[-predsWidth + x] : modes[this.I4 - 4]; + int left = (x == 0) ? this.Preds.Slice(predIdx + (y * predsWidth) - 1)[0] : modes[this.I4 - 1]; + int top = (y == 0) ? this.Preds.Slice(predIdx - predsWidth + x)[0] : modes[this.I4 - 4]; return WebPLookupTables.Vp8FixedCostsI4[top, left]; } @@ -590,13 +590,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public bool RotateI4(Span yuvOut) { Span blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]); - Span top = this.I4Boundary.AsSpan(this.I4BoundaryIdx); + Span top = this.I4Boundary.AsSpan(); + int topOffset = this.I4BoundaryIdx; int i; // Update the cache with 7 fresh samples. for (i = 0; i <= 3; ++i) { - top[-4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples. + top[topOffset - 4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples. } if ((this.I4 & 3) != 3) @@ -605,7 +606,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (i = 0; i <= 2; ++i) { // store future left samples - top[i] = blk[3 + ((2 - i) * WebPConstants.Bps)]; + top[topOffset + i] = blk[3 + ((2 - i) * WebPConstants.Bps)]; } } else @@ -613,7 +614,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // else replicate top-right samples, as says the specs. for (i = 0; i <= 3; ++i) { - top[i] = top[i + 4]; + top[topOffset + i] = top[topOffset + i + 4]; } } @@ -660,7 +661,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public void MakeIntra4Preds() { - this.EncPredLuma4(this.YuvP, this.I4Boundary.AsSpan(this.I4BoundaryIdx)); + this.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); } public void SwapOut() @@ -788,18 +789,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Left samples are top[-5 .. -2], top_left is top[-1], top are // located at top[0..3], and top right is top[4..7] - private void EncPredLuma4(Span dst, Span top) + private void EncPredLuma4(Span dst, Span top, int topOffset) { - this.Dc4(dst.Slice(I4DC4), top); - this.Tm4(dst.Slice(I4TM4), top); - this.Ve4(dst.Slice(I4VE4), top); - this.He4(dst.Slice(I4HE4), top); - this.Rd4(dst.Slice(I4RD4), top); - this.Vr4(dst.Slice(I4VR4), top); + this.Dc4(dst, top, topOffset); + this.Tm4(dst.Slice(I4TM4), top, topOffset); + this.Ve4(dst.Slice(I4VE4), top, topOffset); + this.He4(dst.Slice(I4HE4), top, topOffset); + this.Rd4(dst.Slice(I4RD4), top, topOffset); + this.Vr4(dst.Slice(I4VR4), top, topOffset); this.Ld4(dst.Slice(I4LD4), top); this.Vl4(dst.Slice(I4VL4), top); - this.Hd4(dst.Slice(I4HD4), top); - this.Hu4(dst.Slice(I4HU4), top); + this.Hd4(dst.Slice(I4HD4), top, topOffset); + this.Hu4(dst.Slice(I4HU4), top, topOffset); } private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) @@ -922,42 +923,42 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private void Dc4(Span dst, Span top) + private void Dc4(Span dst, Span top, int topOffset) { uint dc = 4; int i; for (i = 0; i < 4; ++i) { - dc += (uint)(top[i] + top[-5 + i]); + dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); } this.Fill(dst, (int)(dc >> 3), 4); } - private void Tm4(Span dst, Span top) + private void Tm4(Span dst, Span top, int topOffset) { - Span clip = this.clip1.AsSpan(255 - top[-1]); + Span clip = this.clip1.AsSpan(255 - top[topOffset - 1]); for (int y = 0; y < 4; ++y) { - Span clipTable = clip.Slice(top[-2 - y]); + Span clipTable = clip.Slice(top[topOffset - 2 - y]); for (int x = 0; x < 4; ++x) { - dst[x] = clipTable[top[x]]; + dst[x] = clipTable[top[topOffset + x]]; } dst = dst.Slice(WebPConstants.Bps); } } - private void Ve4(Span dst, Span top) + private void Ve4(Span dst, Span top, int topOffset) { // vertical byte[] vals = { - LossyUtils.Avg3(top[-1], top[0], top[1]), - LossyUtils.Avg3(top[0], top[1], top[2]), - LossyUtils.Avg3(top[1], top[2], top[3]), - LossyUtils.Avg3(top[2], top[3], top[4]) + LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), + LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), + LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), + LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) }; for (int i = 0; i < 4; ++i) @@ -966,14 +967,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private void He4(Span dst, Span top) + private void He4(Span dst, Span top, int topOffset) { // horizontal - byte x = top[-1]; - byte i = top[-2]; - byte j = top[-3]; - byte k = top[-4]; - byte l = top[-5]; + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); BinaryPrimitives.WriteUInt32BigEndian(dst, val); @@ -985,17 +986,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); } - private void Rd4(Span dst, Span top) + private void Rd4(Span dst, Span top, int topOffset) { - byte x = top[-1]; - byte i = top[-2]; - byte j = top[-3]; - byte k = top[-4]; - byte l = top[-5]; - byte a = top[0]; - byte b = top[1]; - byte c = top[2]; - byte d = top[3]; + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); var ijk = LossyUtils.Avg3(i, j, k); @@ -1020,16 +1021,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); } - private void Vr4(Span dst, Span top) + private void Vr4(Span dst, Span top, int topOffset) { - byte x = top[-1]; - byte i = top[-2]; - byte j = top[-3]; - byte k = top[-4]; - byte a = top[0]; - byte b = top[1]; - byte c = top[2]; - byte d = top[3]; + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; var xa = LossyUtils.Avg2(x, a); LossyUtils.Dst(dst, 0, 0, xa); @@ -1124,16 +1125,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); } - private void Hd4(Span dst, Span top) + private void Hd4(Span dst, Span top, int topOffset) { - byte x = top[-1]; - byte i = top[-2]; - byte j = top[-3]; - byte k = top[-4]; - byte l = top[-5]; - byte a = top[0]; - byte b = top[1]; - byte c = top[2]; + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; var ix = LossyUtils.Avg2(i, x); LossyUtils.Dst(dst, 0, 0, ix); @@ -1159,12 +1160,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); } - private void Hu4(Span dst, Span top) + private void Hu4(Span dst, Span top, int topOffset) { - byte i = top[-2]; - byte j = top[-3]; - byte k = top[-4]; - byte l = top[-5]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); var jk = LossyUtils.Avg2(j, k); @@ -1285,6 +1286,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { int topSize = this.mbw * 16; this.YTop.Slice(0, topSize).Fill(127); + this.UvTop.GetSpan().Fill(127); this.Nz.GetSpan().Fill(0); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index c9426af16..c224a58af 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; @@ -250,7 +251,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } while (it.Next()); - this.bitWriter.Finish(); + // Write bytes from the bitwriter buffer to the stream. + this.bitWriter.WriteEncodedImageToStream(lossy: true, stream); } /// @@ -708,7 +710,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.NzToBytes(); - var pos1 = this.bitWriter.Pos; + int pos1 = this.bitWriter.NumBytes(); if (i16) { residual.Init(0, 1, this.proba); @@ -734,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - var pos2 = this.bitWriter.Pos; + int pos2 = this.bitWriter.NumBytes(); // U/V residual.Init(0, 2, this.proba); @@ -752,7 +754,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - var pos3 = this.bitWriter.Pos; + int pos3 = this.bitWriter.NumBytes(); it.LumaBits = pos2 - pos1; it.UvBits = pos3 - pos2; it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits; @@ -942,8 +944,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); if (coeff > mtx.ZThresh[j]) { - uint q = (uint)mtx.Q[j]; - uint iQ = (uint)mtx.IQ[j]; + uint q = mtx.Q[j]; + uint iQ = mtx.IQ[j]; uint b = mtx.Bias[j]; int level = this.QuantDiv(coeff, iQ, b); if (level > MaxLevel) @@ -1342,16 +1344,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int GetSse(Span a, Span b, int w, int h) { int count = 0; + int aOffset = 0; + int bOffset = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - int diff = a[x] - b[x]; + int diff = a[aOffset + x] - b[bOffset + x]; count += diff * diff; } - a = a.Slice(WebPConstants.Bps); - b = b.Slice(WebPConstants.Bps); + aOffset += WebPConstants.Bps; + bOffset += WebPConstants.Bps; } return count; diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs index 9c7711352..8095478df 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs @@ -74,10 +74,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.IQ[i] = (ushort)((1 << WebPConstants.QFix) / this.Q[i]); this.Bias[i] = (uint)this.BIAS(bias); - // zthresh_ is the exact value such that QUANTDIV(coeff, iQ, B) is: + // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: // * zero if coeff <= zthresh // * non-zero if coeff > zthresh - this.ZThresh[i] = (uint)(((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]); + this.ZThresh[i] = ((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; } for (i = 2; i < 16; ++i) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 6721b2da1..a53130e2a 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Header bytes identifying a lossless image. /// - public static readonly byte[] Vp8LTag = + public static readonly byte[] Vp8LMagicBytes = { 0x56, // V 0x50, // P From ca68ecc3a022f8e2628dad7130455587684155a7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 1 Nov 2020 12:41:19 +0100 Subject: [PATCH 220/359] Add VP8 encoding tests --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 8 +-- .../Formats/WebP/Lossy/Vp8Encoder.cs | 2 +- .../Formats/WebP/WebPEncoderTests.cs | 57 +++++++++++++++---- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 9901d0cff..4607bf1c0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -367,17 +367,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8); } - Span yTop = this.YTop.Slice(this.yTopIdx); + Span yTop = this.YTop.Slice(this.yTopIdx, 16); if (this.Y == 0) { yTop.Fill(127); - this.UvTop.GetSpan().Fill(127); + this.UvTop.Slice(this.uvTopIdx, 16).Fill(127); } else { this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16); - this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); - this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8); + this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx, 8), uvw, 8); + this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx + 8, 8), uvw, 8); } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index c224a58af..d4a21c49b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -224,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var alphas = new int[WebPConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); - // Analysis is done, proceed to actual coding. + // Analysis is done, proceed to actual encoding. // TODO: EncodeAlpha(); this.segmentHeader = new Vp8EncSegmentHeader(4); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 4777adb4f..90f3a3f42 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -24,11 +24,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Quality = quality }; - using (Image image = provider.GetImage()) - { - var testOutputDetails = string.Concat("lossless", "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } + using Image image = provider.GetImage(); + var testOutputDetails = string.Concat("lossless", "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } [Theory] @@ -46,14 +44,53 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP { Lossy = false, Method = method, - Quality = 100 + Quality = 75 }; - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + var testOutputDetails = string.Concat("lossless", "_m", method); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } + + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 20)] + public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoder() { - var testOutputDetails = string.Concat("lossless", "_m", method); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } + Lossy = true, + Quality = quality + }; + + using Image image = provider.GetImage(); + var testOutputDetails = string.Concat("lossy", "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } + + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] + public void Encode_Lossy_WithDifferentMethods_Works(TestImageProvider provider, int method) + where TPixel : unmanaged, IPixel + { + var encoder = new WebPEncoder() + { + Lossy = true, + Method = method, + Quality = 75 + }; + + using Image image = provider.GetImage(); + var testOutputDetails = string.Concat("lossy", "_m", method); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } } } From efaebafefea34033cbeffe74b9e0b20e7cdaf31e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Nov 2020 17:44:29 +0100 Subject: [PATCH 221/359] Implement StatsLoop --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 16 +- .../Formats/WebP/Lossy/PassStats.cs | 76 +++++ .../Formats/WebP/Lossy/Vp8EncProba.cs | 121 +++++++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 269 ++++++++++++++++-- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 2 +- .../Formats/WebP/Lossy/Vp8RDLevel.cs | 31 ++ .../Formats/WebP/Lossy/Vp8Residual.cs | 80 +++++- src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs | 22 ++ .../Formats/WebP/Lossy/Vp8StatsArray.cs | 18 ++ .../Formats/WebP/Lossy/WebPLossyDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 19 +- .../Formats/WebP/WebPEncoderCore.cs | 8 +- 12 files changed, 624 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/PassStats.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 8d3fe61b4..e18a945bc 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -28,8 +28,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private int maxPos; - // private bool error; - /// /// Initializes a new instance of the class. /// @@ -43,8 +41,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.nbBits = -8; this.pos = 0; this.maxPos = 0; - - // this.error = false; } /// @@ -69,13 +65,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter int v = sign ? -c : c; if (!this.PutBit(v != 0, p.Probabilities[1])) { - p = residual.Prob[WebPConstants.Bands[n]].Probabilities[0]; + p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[0]; continue; } if (!this.PutBit(v > 1, p.Probabilities[2])) { - p = residual.Prob[WebPConstants.Bands[n]].Probabilities[1]; + p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[1]; } else { @@ -102,7 +98,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { int mask; byte[] tab; - var tabIdx = 0; if (v < 3 + (8 << 1)) { // VP8Cat3 (3b) @@ -111,7 +106,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter v -= 3 + (8 << 0); mask = 1 << 2; tab = WebPConstants.Cat3; - tabIdx = 0; } else if (v < 3 + (8 << 2)) { @@ -121,7 +115,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter v -= 3 + (8 << 1); mask = 1 << 3; tab = WebPConstants.Cat4; - tabIdx = 0; } else if (v < 3 + (8 << 3)) { @@ -131,7 +124,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter v -= 3 + (8 << 2); mask = 1 << 4; tab = WebPConstants.Cat5; - tabIdx = 0; } else { @@ -141,9 +133,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter v -= 3 + (8 << 3); mask = 1 << 10; tab = WebPConstants.Cat6; - tabIdx = 0; } + var tabIdx = 0; while (mask != 0) { this.PutBit(v & mask, tab[tabIdx++]); @@ -151,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } - p = residual.Prob[WebPConstants.Bands[n]].Probabilities[2]; + p = residual.Prob[WebPConstants.Vp8EncBands[n]].Probabilities[2]; } this.PutBitUniform(sign ? 1 : 0); diff --git a/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs new file mode 100644 index 000000000..20f18648f --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/PassStats.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Class for organizing convergence in either size or PSNR. + /// + internal class PassStats + { + public PassStats(long targetSize, float targetPsnr, int qMin, int qMax, int quality) + { + bool doSizeSearch = targetSize != 0; + + this.IsFirst = true; + this.Dq = 10.0f; + this.Qmin = qMin; + this.Qmax = qMax; + this.Q = quality.Clamp(qMin, qMax); + this.LastQ = this.Q; + this.Target = doSizeSearch ? targetSize + : (targetPsnr > 0.0f) ? targetPsnr + : 40.0f; // default, just in case + this.Value = 0.0f; + this.LastValue = 0.0f; + this.DoSizeSearch = doSizeSearch; + } + + public bool IsFirst { get; set; } + + public float Dq { get; set; } + + public float Q { get; set; } + + public float LastQ { get; set; } + + public float Qmin { get; } + + public float Qmax { get; } + + public double Value { get; set; } // PSNR or size + + public double LastValue { get; set; } + + public double Target { get; } + + public bool DoSizeSearch { get; } + + public float ComputeNextQ() + { + float dq; + if (this.IsFirst) + { + dq = (this.Value > this.Target) ? -this.Dq : this.Dq; + this.IsFirst = false; + } + else if (this.Value != this.LastValue) + { + double slope = (this.Target - this.Value) / (this.LastValue - this.Value); + dq = (float)(slope * (this.LastQ - this.Q)); + } + else + { + dq = 0.0f; // we're done?! + } + + // Limit variable to avoid large swings. + this.Dq = dq.Clamp(-30.0f, 30.0f); + this.LastQ = this.Q; + this.LastValue = this.Value; + this.Q = (this.Q + this.Dq).Clamp(this.Qmin, this.Qmax); + + return this.Q; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index 8ae019ef6..6fe23adb3 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private const int MaxVariableLevel = 67; + /// + /// Value below which using skipProba is OK. + /// + private const int SkipProbaThreshold = 250; + /// /// Initializes a new instance of the class. /// @@ -30,6 +35,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + this.Stats = new Vp8Stats[WebPConstants.NumTypes][]; + for (int i = 0; i < this.Coeffs.Length; i++) + { + this.Stats[i] = new Vp8Stats[WebPConstants.NumBands]; + for (int j = 0; j < this.Stats[i].Length; j++) + { + this.Stats[i][j] = new Vp8Stats(); + } + } + this.LevelCost = new Vp8CostArray[WebPConstants.NumTypes][]; for (int i = 0; i < this.LevelCost.Length; i++) { @@ -74,17 +89,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public byte[] Segments { get; } /// - /// Gets the final probability of being skipped. + /// Gets or sets the final probability of being skipped. /// - public byte SkipProba { get; } + public byte SkipProba { get; set; } /// - /// Gets a value indicating whether to use the skip probability. Note: we always use SkipProba for now. + /// Gets or sets a value indicating whether to use the skip probability. Note: we always use SkipProba for now. /// - public bool UseSkipProba { get; } + public bool UseSkipProba { get; set; } public Vp8BandProbas[][] Coeffs { get; } + public Vp8Stats[][] Stats { get; } + public Vp8CostArray[][] LevelCost { get; } public Vp8CostArray[][] RemappedCosts { get; } @@ -132,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (int ctx = 0; ctx < WebPConstants.NumCtx; ++ctx) { Span dst = this.RemappedCosts[ctype][n].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); - Span src = this.LevelCost[ctype][WebPConstants.Bands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); + Span src = this.LevelCost[ctype][WebPConstants.Vp8EncBands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); src.CopyTo(dst); } } @@ -141,6 +158,87 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Dirty = false; } + public int FinalizeTokenProbas() + { + bool hasChanged = false; + int size = 0; + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + var stats = this.Stats[t][b].Stats[c].Stats[p]; + int nb = (int)((stats >> 0) & 0xffff); + int total = (int)((stats >> 16) & 0xffff); + int updateProba = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; + int oldP = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + int newP = this.CalcTokenProba(nb, total); + int oldCost = this.BranchCost(nb, total, oldP) + this.BitCost(0, (byte)updateProba); + int newCost = this.BranchCost(nb, total, newP) + this.BitCost(1, (byte)updateProba) + (8 * 256); + bool useNewP = oldCost > newCost; + size += this.BitCost(useNewP ? 1 : 0, (byte)updateProba); + if (useNewP) + { + // Only use proba that seem meaningful enough. + this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)newP; + hasChanged |= newP != oldP; + size += 8 * 256; + } + else + { + this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)oldP; + } + } + } + } + } + + this.Dirty = hasChanged; + return size; + } + + public int FinalizeSkipProba(int mbw, int mbh) + { + int nbMbs = mbw * mbh; + int nbEvents = this.NbSkip; + this.SkipProba = (byte)this.CalcSkipProba(nbEvents, nbMbs); + this.UseSkipProba = this.SkipProba < SkipProbaThreshold; + + int size = 256; + if (this.UseSkipProba) + { + size += (nbEvents * this.BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * this.BitCost(0, this.SkipProba)); + size += 8 * 256; // cost of signaling the skipProba itself. + } + + return size; + } + + public void ResetTokenStats() + { + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + this.Stats[t][b].Stats[c].Stats[p] = 0; + } + } + } + } + } + + private int CalcSkipProba(long nb, long total) + { + return (int)(total != 0 ? (total - nb) * 255 / total : 255); + } + private int VariableLevelCost(int level, Span probas) { int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0]; @@ -160,6 +258,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return cost; } + // Collect statistics and deduce probabilities for next coding pass. + // Return the total bit-cost for coding the probability updates. + private int CalcTokenProba(int nb, int total) + { + return nb != 0 ? (255 - (nb * 255 / total)) : 255; + } + + // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. + private int BranchCost(int nb, int total, int proba) + { + return (nb * this.BitCost(1, (byte)proba)) + ((total - nb) * this.BitCost(0, (byte)proba)); + } + // Cost of coding one event with probability 'proba'. private int BitCost(int bit, byte proba) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index d4a21c49b..3af091d90 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; @@ -38,20 +37,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly int method; + /// + /// Number of entropy-analysis passes (in [1..10]). + /// + private readonly int entropyPasses; + /// /// Stride of the prediction plane (=4*mb_w + 1) /// - private int predsWidth; + private readonly int predsWidth; /// /// Macroblock width. /// - private int mbw; + private readonly int mbw; /// /// Macroblock height. /// - private int mbh; + private readonly int mbh; /// /// The segment features. @@ -61,17 +65,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Contextual macroblock infos. /// - private Vp8MacroBlockInfo[] mbInfo; + private readonly Vp8MacroBlockInfo[] mbInfo; /// /// Probabilities. /// - private Vp8EncProba proba; + private readonly Vp8EncProba proba; + + private readonly Vp8RdLevel rdOptLevel; private int dqUvDc; private int dqUvAc; + private int maxI4HeaderBits; + /// /// Global susceptibility. /// @@ -103,6 +111,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int MaxItersKMeans = 6; + // Convergence is considered reached if dq < DqLimit + private const float DqLimit = 0.4f; + + private const ulong Partition0SizeLimit = (WebPConstants.Vp8MaxPartition0Size - 2048UL) << 11; + + private const long HeaderSizeEstimate = WebPConstants.RiffHeaderSize + WebPConstants.ChunkHeaderSize + WebPConstants.Vp8FrameHeaderSize; + + private const int QMin = 0; + + private const int QMax = 100; + /// /// Initializes a new instance of the class. /// @@ -111,11 +130,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). - public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method) + /// Number of entropy-analysis passes (in [1..10]). + public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method, int entropyPasses) { this.memoryAllocator = memoryAllocator; this.quality = quality.Clamp(0, 100); this.method = method.Clamp(0, 6); + this.entropyPasses = entropyPasses.Clamp(1, 10); + this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll + : (method >= 5) ? Vp8RdLevel.RdOptTrellis + : (method >= 3) ? Vp8RdLevel.RdOptBasic + : Vp8RdLevel.RdOptNone; var pixelCount = width * height; this.mbw = (width + 15) >> 4; @@ -130,6 +155,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; + // TODO: make partition_limit configurable? + int limit = 100; // original code: limit = 100 - config->partition_limit; + this.maxI4HeaderBits = + 256 * 16 * 16 * // upper bound: up to 16bit per 4x4 block + (limit * limit) / (100 * 100); // ... modulated with a quadratic curve. + this.mbInfo = new Vp8MacroBlockInfo[this.mbw * this.mbh]; for (int i = 0; i < this.mbInfo.Length; i++) { @@ -225,20 +256,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); // Analysis is done, proceed to actual encoding. - - // TODO: EncodeAlpha(); this.segmentHeader = new Vp8EncSegmentHeader(4); this.AssignSegments(segmentInfos, alphas); - this.SetSegmentParams(segmentInfos); + this.SetSegmentParams(segmentInfos, this.quality); this.SetSegmentProbas(segmentInfos); this.ResetStats(); + + // TODO: EncodeAlpha(); + // Stats-collection loop. + this.StatLoop(width, height, yStride, uvStride, segmentInfos); it.Init(); it.InitFilter(); do { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height); - if (!this.Decimate(it, segmentInfos, info)) + if (!this.Decimate(it, segmentInfos, info, this.rdOptLevel)) { this.CodeResiduals(it, info); } @@ -266,6 +299,139 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Preds.Dispose(); } + /// + /// Only collect statistics(number of skips, token usage, ...). + /// This is used for deciding optimal probabilities. It also modifies the + /// quantizer value if some target (size, PSNR) was specified. + /// + private void StatLoop(int width, int height, int yStride, int uvStride, Vp8SegmentInfo[] segmentInfos) + { + int targetSize = 0; // TODO: target size is hardcoded. + float targetPsnr = 0.0f; // TDOO: targetPsnr is hardcoded. + int method = this.method; + bool doSearch = false; // TODO: doSearch hardcoded for now. + bool fastProbe = (method == 0 || method == 3) && !doSearch; + int numPassLeft = this.entropyPasses; + Vp8RdLevel rdOpt = (method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + int nbMbs = this.mbw * this.mbh; + + var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); + this.proba.ResetTokenStats(); + + // Fast mode: quick analysis pass over few mbs. Better than nothing. + if (fastProbe) + { + if (method == 3) + { + // We need more stats for method 3 to be reliable. + nbMbs = (nbMbs > 200) ? nbMbs >> 1 : 100; + } + else + { + nbMbs = (nbMbs > 200) ? nbMbs >> 2 : 50; + } + } + + while (numPassLeft-- > 0) + { + bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); + var sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats, segmentInfos); + if (sizeP0 == 0) + { + return; + } + + if (this.maxI4HeaderBits > 0 && sizeP0 > (long)Partition0SizeLimit) + { + ++numPassLeft; + this.maxI4HeaderBits >>= 1; // strengthen header bit limitation... + continue; // ...and start over + } + + if (isLastPass) + { + break; + } + + // If no target size: just do several pass without changing 'q' + if (doSearch) + { + stats.ComputeNextQ(); + if (MathF.Abs(stats.Dq) <= DqLimit) + { + break; + } + } + } + + if (!doSearch || !stats.DoSizeSearch) + { + // Need to finalize probas now, since it wasn't done during the search. + this.proba.FinalizeSkipProba(this.mbw, this.mbh); + this.proba.FinalizeTokenProbas(); + } + + this.proba.CalculateLevelCosts(); // finalize costs + } + + private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats, Vp8SegmentInfo[] segmentInfos) + { + Span y = this.Y.GetSpan(); + Span u = this.U.GetSpan(); + Span v = this.V.GetSpan(); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); + long size = 0; + long sizeP0 = 0; + long distortion = 0; + long pixelCount = nbMbs * 384; + + this.SetLoopParams(segmentInfos, stats.Q); + do + { + var info = new Vp8ModeScore(); + it.Import(y, u, v, yStride, uvStride, width, height); + if (this.Decimate(it, segmentInfos, info, rdOpt)) + { + // Just record the number of skips and act like skipProba is not used. + ++this.proba.NbSkip; + } + + this.RecordResiduals(it, info); + size += info.R + info.H; + sizeP0 += info.H; + distortion += info.D; + + it.SaveBoundary(); + } + while (it.Next()); + + sizeP0 += this.segmentHeader.Size; + if (stats.DoSizeSearch) + { + size += this.proba.FinalizeSkipProba(this.mbw, this.mbh); + size += this.proba.FinalizeTokenProbas(); + size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; + stats.Value = size; + } + else + { + stats.Value = this.GetPsnr(distortion, pixelCount); + } + + return sizeP0; + } + + private void SetLoopParams(Vp8SegmentInfo[] dqm, float q) + { + // Setup segment quantizations and filters. + this.SetSegmentParams(dqm, q); + + // Compute segment probabilities. + this.SetSegmentProbas(dqm); + + this.ResetStats(); + } + private void ResetBoundaryPredictions() { Span top = this.Preds.GetSpan(); @@ -416,12 +582,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private void SetSegmentParams(Vp8SegmentInfo[] dqm) + private void SetSegmentParams(Vp8SegmentInfo[] dqm, float quality) { int nb = this.segmentHeader.NumSegments; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; - double cBase = this.QualityToCompression(this.quality / 100.0d); + double cBase = this.QualityToCompression(quality / 100.0d); for (int i = 0; i < nb; ++i) { // We modulate the base coefficient to accommodate for the quantization @@ -488,6 +654,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy m.Uv.Q[1] = WebPLookupTables.AcTable[this.Clip(q + this.dqUvAc, 0, 127)]; var qi4 = m.Y1.Expand(0); + var qi16 = m.Y2.Expand(1); + var quv = m.Uv.Expand(2); m.I4Penalty = 1000 * qi4 * qi4; } @@ -541,7 +709,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } - private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd) + private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, Vp8RdLevel rdOpt) { rd.InitScore(); @@ -730,7 +898,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 4; ++x) { int ctx = it.TopNz[x] + it.LeftNz[y]; - residual.SetCoeffs(rd.YAcLevels.AsSpan(x + (y * 4), 16)); + residual.SetCoeffs(rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16)); int res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[x] = it.LeftNz[y] = res; } @@ -747,7 +915,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 2; ++x) { int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels.AsSpan((ch * 2) + x + (y * 2), 16)); + residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); var res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; } @@ -762,6 +930,67 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.BytesToNz(); } + /// + /// Same as CodeResiduals, but doesn't actually write anything. + /// Instead, it just records the event distribution. + /// + private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd) + { + int x, y, ch; + var residual = new Vp8Residual(); + bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; + int segment = it.CurrentMacroBlockInfo.Segment; + + it.NzToBytes(); + + if (i16) + { + // i16x16 + residual.Init(0, 1, this.proba); + residual.SetCoeffs(rd.YDcLevels); + var res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); + it.TopNz[8] = res; + it.LeftNz[8] = res; + residual.Init(1, 0, this.proba); + } + else + { + residual.Init(0, 3, this.proba); + } + + // luma-AC + for (y = 0; y < 4; ++y) + { + for (x = 0; x < 4; ++x) + { + int ctx = it.TopNz[x] + it.LeftNz[y]; + residual.SetCoeffs(rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16)); + var res = residual.RecordCoeffs(ctx); + it.TopNz[x] = res; + it.LeftNz[y] = res; + } + } + + // U/V + residual.Init(0, 2, this.proba); + for (ch = 0; ch <= 2; ch += 2) + { + for (y = 0; y < 2; ++y) + { + for (x = 0; x < 2; ++x) + { + int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; + residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); + var res = residual.RecordCoeffs(ctx); + it.TopNz[4 + ch + x] = res; + it.LeftNz[4 + ch + y] = res; + } + } + } + + it.BytesToNz(); + } + private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); @@ -785,7 +1014,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Zero-out the first coeff, so that: a) nz is correct below, and // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16), rd.YAcLevels.AsSpan(n, 32), dqm.Y1) << n; + nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; } // Transform back. @@ -1400,6 +1629,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return v; } + [MethodImpl(InliningOptions.ShortMethod)] + private double GetPsnr(long mse, long size) + { + return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; + } + [MethodImpl(InliningOptions.ShortMethod)] private int QuantDiv(uint n, uint iQ, uint b) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs index f9316e40b..816752085 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.YDcLevels = new short[16]; this.YAcLevels = new short[16 * 16]; - this.UvLevels = new short[4 + (4 * 16)]; + this.UvLevels = new short[(4 + 4) * 16]; this.ModesI4 = new byte[16]; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs new file mode 100644 index 000000000..763e29f5b --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8RDLevel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Rate-distortion optimization levels + /// + internal enum Vp8RdLevel + { + /// + /// No rd-opt. + /// + RdOptNone = 0, + + /// + /// Basic scoring (no trellis). + /// + RdOptBasic = 1, + + /// + /// Perform trellis-quant on the final decision only. + /// + RdOptTrellis = 2, + + /// + /// Trellis-quant for every scoring (much slower). + /// + RdOptTrellisAll = 3 + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 96efe7f4f..f79e8f91d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// internal class Vp8Residual { + private const int MaxVariableLevel = 67; + public int First { get; set; } public int Last { get; set; } @@ -20,14 +22,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public Vp8BandProbas[] Prob { get; set; } + public Vp8Stats[] Stats { get; set; } + public void Init(int first, int coeffType, Vp8EncProba prob) { this.First = first; this.CoeffType = coeffType; this.Prob = prob.Coeffs[this.CoeffType]; + this.Stats = prob.Stats[this.CoeffType]; // TODO: - // res->stats = enc->proba_.stats_[coeff_type]; // res->costs = enc->proba_.remapped_costs_[coeff_type]; } @@ -46,5 +50,79 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Coeffs = coeffs.ToArray(); } + + // Simulate block coding, but only record statistics. + // Note: no need to record the fixed probas. + public int RecordCoeffs(int ctx) + { + int n = this.First; + Vp8StatsArray s = this.Stats[n].Stats[ctx]; + if (this.Last < 0) + { + this.RecordStats(0, s, 0); + return 0; + } + + while (n <= this.Last) + { + int v; + this.RecordStats(1, s, 0); // order of record doesn't matter + while ((v = this.Coeffs[n++]) == 0) + { + this.RecordStats(0, s, 1); + s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[0]; + this.RecordStats(1, s, 1); + if (this.RecordStats((v + 1) > 2u ? 1 : 0, s, 2) == 0) + { + // v = -1 or 1 + s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1]; + } + else + { + v = Math.Abs(v); + if (v > MaxVariableLevel) + { + v = MaxVariableLevel; + } + + int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1]; + int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0]; + int i; + for (i = 0; (pattern >>= 1) != 0; ++i) + { + int mask = 2 << i; + if ((pattern & 1) != 0) + { + this.RecordStats(bits & mask, s, 3 + i); + } + } + + s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2]; + } + } + } + + if (n < 16) + { + this.RecordStats(0, s, 0); + } + + return 1; + } + + private int RecordStats(int bit, Vp8StatsArray statsArr, int idx) + { + // An overflow is inbound. Note we handle this at 0xfffe0000u instead of + // 0xffff0000u to make sure p + 1u does not overflow. + if (statsArr.Stats[idx] >= 0xfffe0000u) + { + statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. + } + + // record bit count (lower 16 bits) and increment total count (upper 16 bits). + statsArr.Stats[idx] += 0x00010000u + (uint)bit; + + return bit; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs new file mode 100644 index 000000000..374d37960 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Stats.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8Stats + { + /// + /// Initializes a new instance of the class. + /// + public Vp8Stats() + { + this.Stats = new Vp8StatsArray[WebPConstants.NumCtx]; + for (int i = 0; i < WebPConstants.NumCtx; i++) + { + this.Stats[i] = new Vp8StatsArray(); + } + } + + public Vp8StatsArray[] Stats { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs new file mode 100644 index 000000000..69c0fd5bf --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8StatsArray.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8StatsArray + { + /// + /// Initializes a new instance of the class. + /// + public Vp8StatsArray() + { + this.Stats = new uint[WebPConstants.NumProbas]; + } + + public uint[] Stats { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 351f1a45e..53fe0b95d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -1278,7 +1278,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (int b = 0; b < 16 + 1; ++b) { - proba.BandsPtr[t][b] = proba.Bands[t, WebPConstants.Bands[b]]; + proba.BandsPtr[t][b] = proba.Bands[t, WebPConstants.Vp8EncBands[b]]; } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index a53130e2a..08dac5bf0 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Constants used for decoding VP8 and VP8L bitstreams. + /// Constants used for encoding and decoding VP8 and VP8L bitstreams. /// internal static class WebPConstants { @@ -78,11 +78,21 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public const int Vp8LImageSizeBits = 14; + /// + /// Size of the frame header within VP8 data. + /// + public const int Vp8FrameHeaderSize = 10; + /// /// Size of a chunk header. /// public const int ChunkHeaderSize = 8; + /// + /// Size of the RIFF header ("RIFFnnnnWEBP"). + /// + public const int RiffHeaderSize = 12; + /// /// Size of a chunk tag (e.g. "VP8L"). /// @@ -241,6 +251,11 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int QFix = 17; + /// + /// Max size of mode partition. + /// + public const int Vp8MaxPartition0Size = 1 << 19; + public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; @@ -258,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; // Paragraph 9.9 - public static readonly int[] Bands = + public static readonly int[] Vp8EncBands = { 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 }; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 1158421f0..5fbae79e2 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -48,6 +48,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private readonly int method; + /// + /// The number of entropy-analysis passes (in [1..10]). + /// + private readonly int entropyPasses; + /// /// Initializes a new instance of the class. /// @@ -60,6 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.lossy = options.Lossy; this.quality = options.Quality; this.method = options.Method; + this.entropyPasses = options.EntropyPasses; } /// @@ -79,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.lossy) { - var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method); + var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method, this.entropyPasses); enc.Encode(image, stream); } else From 4b30b9294b75327f16a5ebed0e14ab796277d5a8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Nov 2020 15:02:11 +0100 Subject: [PATCH 222/359] SetSegmentProbas --- .../Formats/WebP/Lossy/LossyUtils.cs | 6 + .../Formats/WebP/Lossy/Vp8EncProba.cs | 40 +-- .../Formats/WebP/Lossy/Vp8EncSegmentHeader.cs | 8 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 331 ++++++++++-------- 4 files changed, 213 insertions(+), 172 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index a5971b0be..53360a981 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -784,6 +784,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255); } + // Cost of coding one event with probability 'proba'. + public static int Vp8BitCost(int bit, byte proba) + { + return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( Span p, diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs index 6fe23adb3..4e54c410d 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public byte SkipProba { get; set; } /// - /// Gets or sets a value indicating whether to use the skip probability. Note: we always use SkipProba for now. + /// Gets or sets a value indicating whether to use the skip probability. /// public bool UseSkipProba { get; set; } @@ -131,13 +131,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; Span table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel); - int cost0 = (ctx > 0) ? this.BitCost(1, p.Probabilities[0]) : 0; - int costBase = this.BitCost(1, p.Probabilities[1]) + cost0; + int cost0 = (ctx > 0) ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; + int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; int v; - table[0] = (ushort)(this.BitCost(0, p.Probabilities[1]) + cost0); + table[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); for (v = 1; v <= MaxVariableLevel; ++v) { - table[v] = (ushort)(costBase + this.VariableLevelCost(v, p.Probabilities)); + table[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); } // Starting at level 67 and up, the variable part of the cost is actually constant. @@ -175,11 +175,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int total = (int)((stats >> 16) & 0xffff); int updateProba = WebPLookupTables.CoeffsUpdateProba[t, b, c, p]; int oldP = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; - int newP = this.CalcTokenProba(nb, total); - int oldCost = this.BranchCost(nb, total, oldP) + this.BitCost(0, (byte)updateProba); - int newCost = this.BranchCost(nb, total, newP) + this.BitCost(1, (byte)updateProba) + (8 * 256); + int newP = CalcTokenProba(nb, total); + int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba); + int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256); bool useNewP = oldCost > newCost; - size += this.BitCost(useNewP ? 1 : 0, (byte)updateProba); + size += LossyUtils.Vp8BitCost(useNewP ? 1 : 0, (byte)updateProba); if (useNewP) { // Only use proba that seem meaningful enough. @@ -204,13 +204,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { int nbMbs = mbw * mbh; int nbEvents = this.NbSkip; - this.SkipProba = (byte)this.CalcSkipProba(nbEvents, nbMbs); + this.SkipProba = (byte)CalcSkipProba(nbEvents, nbMbs); this.UseSkipProba = this.SkipProba < SkipProbaThreshold; int size = 256; if (this.UseSkipProba) { - size += (nbEvents * this.BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * this.BitCost(0, this.SkipProba)); + size += (nbEvents * LossyUtils.Vp8BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * LossyUtils.Vp8BitCost(0, this.SkipProba)); size += 8 * 256; // cost of signaling the skipProba itself. } @@ -234,12 +234,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private int CalcSkipProba(long nb, long total) + private static int CalcSkipProba(long nb, long total) { return (int)(total != 0 ? (total - nb) * 255 / total : 255); } - private int VariableLevelCost(int level, Span probas) + private static int VariableLevelCost(int level, Span probas) { int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0]; int bits = WebPLookupTables.Vp8LevelCodes[level - 1][1]; @@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { if ((pattern & 1) != 0) { - cost += this.BitCost(bits & 1, probas[i]); + cost += LossyUtils.Vp8BitCost(bits & 1, probas[i]); } bits >>= 1; @@ -260,21 +260,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Collect statistics and deduce probabilities for next coding pass. // Return the total bit-cost for coding the probability updates. - private int CalcTokenProba(int nb, int total) + private static int CalcTokenProba(int nb, int total) { return nb != 0 ? (255 - (nb * 255 / total)) : 255; } // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. - private int BranchCost(int nb, int total, int proba) + private static int BranchCost(int nb, int total, int proba) { - return (nb * this.BitCost(1, (byte)proba)) + ((total - nb) * this.BitCost(0, (byte)proba)); - } - - // Cost of coding one event with probability 'proba'. - private int BitCost(int bit, byte proba) - { - return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; + return (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs index 77a5ceecf..72dd4e16f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs @@ -22,13 +22,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public int NumSegments { get; } /// - /// Gets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. + /// Gets or sets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. /// - public bool UpdateMap { get; } + public bool UpdateMap { get; set; } /// - /// Gets the bit-cost for transmitting the segment map. + /// Gets or sets the bit-cost for transmitting the segment map. /// - public int Size { get; } + public int Size { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 3af091d90..ef227e53f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -258,9 +258,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Analysis is done, proceed to actual encoding. this.segmentHeader = new Vp8EncSegmentHeader(4); this.AssignSegments(segmentInfos, alphas); - this.SetSegmentParams(segmentInfos, this.quality); - this.SetSegmentProbas(segmentInfos); - this.ResetStats(); + this.SetLoopParams(segmentInfos, this.quality); // TODO: EncodeAlpha(); // Stats-collection loop. @@ -415,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - stats.Value = this.GetPsnr(distortion, pixelCount); + stats.Value = GetPsnr(distortion, pixelCount); } return sizeP0; @@ -427,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.SetSegmentParams(dqm, q); // Compute segment probabilities. - this.SetSegmentProbas(dqm); + this.SetSegmentProbas(); this.ResetStats(); } @@ -577,8 +575,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { int alpha = 255 * (centers[n] - mid) / (max - min); int beta = 255 * (centers[n] - min) / (max - min); - dqm[n].Alpha = this.Clip(alpha, -127, 127); - dqm[n].Beta = this.Clip(beta, 0, 255); + dqm[n].Alpha = Clip(alpha, -127, 127); + dqm[n].Beta = Clip(beta, 0, 255); } } @@ -587,7 +585,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int nb = this.segmentHeader.NumSegments; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; - double cBase = this.QualityToCompression(quality / 100.0d); + double cBase = QualityToCompression(quality / 100.0d); for (int i = 0; i < nb; ++i) { // We modulate the base coefficient to accommodate for the quantization @@ -595,7 +593,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy double expn = 1.0d - (amp * dqm[i].Alpha); double c = Math.Pow(cBase, expn); int q = (int)(127.0d * (1.0d - c)); - dqm[i].Quant = this.Clip(q, 0, 127); + dqm[i].Quant = Clip(q, 0, 127); } // uvAlpha is normally spread around ~60. The useful range is @@ -607,23 +605,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.dqUvAc = this.dqUvAc * snsStrength / 100; // and make it safe. - this.dqUvAc = this.Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); + this.dqUvAc = Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); // We also boost the dc-uv-quant a little, based on sns-strength, since // U/V channels are quite more reactive to high quants (flat DC-blocks // tend to appear, and are unpleasant). this.dqUvDc = -4 * snsStrength / 100; - this.dqUvDc = this.Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed + this.dqUvDc = Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed this.SetupMatrices(dqm); } - private void SetSegmentProbas(Vp8SegmentInfo[] dqm) + private void SetSegmentProbas() { - // var p = new int[4]; - // int n; + var p = new int[NumMbSegments]; + int n; + + for (n = 0; n < this.mbw * this.mbh; ++n) + { + Vp8MacroBlockInfo mb = this.mbInfo[n]; + ++p[mb.Segment]; + } + + if (this.segmentHeader.NumSegments > 1) + { + byte[] probas = this.proba.Segments; + probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]); + probas[1] = (byte)GetProba(p[0], p[1]); + probas[2] = (byte)GetProba(p[2], p[3]); + + this.segmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); + if (!this.segmentHeader.UpdateMap) + { + this.ResetSegments(); + } + + this.segmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + + (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) + + (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) + + (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2]))); + } + else + { + this.segmentHeader.UpdateMap = false; + this.segmentHeader.Size = 0; + } + } - // TODO: SetSegmentProbas + private void ResetSegments() + { + int n; + for (n = 0; n < this.mbw * this.mbh; ++n) + { + this.mbInfo[n].Segment = 0; + } } private void ResetStats() @@ -644,14 +679,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy m.Y2 = new Vp8Matrix(); m.Uv = new Vp8Matrix(); - m.Y1.Q[0] = WebPLookupTables.DcTable[this.Clip(q, 0, 127)]; - m.Y1.Q[1] = WebPLookupTables.AcTable[this.Clip(q, 0, 127)]; + m.Y1.Q[0] = WebPLookupTables.DcTable[Clip(q, 0, 127)]; + m.Y1.Q[1] = WebPLookupTables.AcTable[Clip(q, 0, 127)]; - m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[this.Clip(q, 0, 127)] * 2); - m.Y2.Q[1] = WebPLookupTables.AcTable2[this.Clip(q, 0, 127)]; + m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[Clip(q, 0, 127)] * 2); + m.Y2.Q[1] = WebPLookupTables.AcTable2[Clip(q, 0, 127)]; - m.Uv.Q[0] = WebPLookupTables.DcTable[this.Clip(q + this.dqUvDc, 0, 117)]; - m.Uv.Q[1] = WebPLookupTables.AcTable[this.Clip(q + this.dqUvAc, 0, 127)]; + m.Uv.Q[0] = WebPLookupTables.DcTable[Clip(q + this.dqUvDc, 0, 117)]; + m.Uv.Q[1] = WebPLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)]; var qi4 = m.Y1.Expand(0); var qi16 = m.Y2.Expand(1); @@ -702,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Final susceptibility mix. bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2; - bestAlpha = this.FinalAlphaValue(bestAlpha); + bestAlpha = FinalAlphaValue(bestAlpha); alphas[bestAlpha]++; it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. @@ -757,7 +792,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < numPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); - long score = (this.Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + long score = (Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) { @@ -774,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy if (it.X == 0 || it.Y == 0) { // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. - if (this.IsFlatSource16(src)) + if (IsFlatSource16(src)) { bestMode = (it.X == 0) ? 0 : 2; tryBothModes = false; // Stick to i16. @@ -804,7 +839,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < numBModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); - long score = (this.Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + long score = (Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); if (score < bestI4Score) { bestI4Mode = mode; @@ -852,7 +887,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < numPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); - long score = (this.Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + long score = (Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); if (score < bestUvScore) { bestMode = mode; @@ -1041,7 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); int nz = 0; int n; var tmp = new short[8 * 16]; @@ -1063,7 +1098,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 8; n += 2) { - nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n, 32), dqm.Uv) << n; + nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; } for (n = 0; n < 8; n += 2) @@ -1176,7 +1211,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy uint q = mtx.Q[j]; uint iQ = mtx.IQ[j]; uint b = mtx.Bias[j]; - int level = this.QuantDiv(coeff, iQ, b); + int level = QuantDiv(coeff, iQ, b); if (level > MaxLevel) { level = MaxLevel; @@ -1225,8 +1260,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // vertical pass. int a = input[0] + input[8]; int b = input[0] - input[8]; - int c = this.Mul(input[4], KC2) - this.Mul(input[12], KC1); - int d = this.Mul(input[4], KC1) + this.Mul(input[12], KC2); + int c = Mul(input[4], KC2) - Mul(input[12], KC1); + int d = Mul(input[4], KC1) + Mul(input[12], KC2); tmp[0] = a + d; tmp[1] = b + c; tmp[2] = b - c; @@ -1242,12 +1277,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int dc = tmp[0] + 4; int a = dc + tmp[8]; int b = dc - tmp[8]; - int c = this.Mul(tmp[4], KC2) - this.Mul(tmp[12], KC1); - int d = this.Mul(tmp[4], KC1) + this.Mul(tmp[12], KC2); - this.Store(dst, reference, 0, i, (byte)(a + d)); - this.Store(dst, reference, 1, i, (byte)(b + c)); - this.Store(dst, reference, 2, i, (byte)(b - c)); - this.Store(dst, reference, 3, i, (byte)(a - d)); + int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); + int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); + Store(dst, reference, 0, i, (byte)(a + d)); + Store(dst, reference, 1, i, (byte)(b + c)); + Store(dst, reference, 2, i, (byte)(b - c)); + Store(dst, reference, 3, i, (byte)(a - d)); tmp = tmp.Slice(1); } } @@ -1259,47 +1294,45 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy bool hasAlpha = this.CheckNonOpaque(image); // Temporary storage for accumulated R/G/B values during conversion to U/V. - using (IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth)) + using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); + Span tmpRgbSpan = tmpRgb.GetSpan(); + int uvRowIndex = 0; + int rowIndex; + for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) { - Span tmpRgbSpan = tmpRgb.GetSpan(); - int uvRowIndex = 0; - int rowIndex; - for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) + // Downsample U/V planes, two rows at a time. + Span rowSpan = image.GetPixelRowSpan(rowIndex); + Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + if (!hasAlpha) { - // Downsample U/V planes, two rows at a time. - Span rowSpan = image.GetPixelRowSpan(rowIndex); - Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); - if (!hasAlpha) - { - this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); - } - else - { - this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); - } + this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + } + else + { + this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + } - this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); - uvRowIndex++; + this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); + uvRowIndex++; - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); - } + this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); + } - // Extra last row. - if ((image.Height & 1) != 0) + // Extra last row. + if ((image.Height & 1) != 0) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + if (!hasAlpha) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - if (!hasAlpha) - { - this.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - else - { - this.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + this.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); } + else + { + this.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); + } + + this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); } } @@ -1333,7 +1366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { TPixel color = rowSpan[x]; color.ToRgba32(ref rgba); - y[x] = (byte)this.RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); + y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); } } @@ -1343,8 +1376,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (int i = 0; i < width; i += 1, rgbIdx += 4) { int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; - u[i] = (byte)this.RgbToU(r, g, b, YuvHalf << 2); - v[i] = (byte)this.RgbToV(r, g, b, YuvHalf << 2); + u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); + v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); } } @@ -1368,21 +1401,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy color = nextRowSpan[j + 1]; color.ToRgba32(ref rgba3); - dst[dstIdx] = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.R) + - this.GammaToLinear(rgba1.R) + - this.GammaToLinear(rgba2.R) + - this.GammaToLinear(rgba3.R), 0); - dst[dstIdx + 1] = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.G) + - this.GammaToLinear(rgba1.G) + - this.GammaToLinear(rgba2.G) + - this.GammaToLinear(rgba3.G), 0); - dst[dstIdx + 2] = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.B) + - this.GammaToLinear(rgba1.B) + - this.GammaToLinear(rgba2.B) + - this.GammaToLinear(rgba3.B), 0); + dst[dstIdx] = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + dst[dstIdx + 1] = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + dst[dstIdx + 2] = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); } if ((width & 1) != 0) @@ -1392,9 +1425,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy color = nextRowSpan[j]; color.ToRgba32(ref rgba1); - dst[dstIdx] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1); - dst[dstIdx + 1] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1); - dst[dstIdx + 2] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1); + dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); } } @@ -1421,27 +1454,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int r, g, b; if (a == 4 * 0xff || a == 0) { - r = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.R) + - this.GammaToLinear(rgba1.R) + - this.GammaToLinear(rgba2.R) + - this.GammaToLinear(rgba3.R), 0); - g = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.G) + - this.GammaToLinear(rgba1.G) + - this.GammaToLinear(rgba2.G) + - this.GammaToLinear(rgba3.G), 0); - b = (ushort)this.LinearToGamma( - this.GammaToLinear(rgba0.B) + - this.GammaToLinear(rgba1.B) + - this.GammaToLinear(rgba2.B) + - this.GammaToLinear(rgba3.B), 0); + r = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + g = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + b = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); } else { - r = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); } dst[dstIdx] = (ushort)r; @@ -1460,15 +1493,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int r, g, b; if (a == 4 * 0xff || a == 0) { - r = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1); - g = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1); - b = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1); + r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); } else { - r = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); } dst[dstIdx] = (ushort)r; @@ -1478,29 +1511,29 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) + private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) { - uint sum = (a0 * this.GammaToLinear(rgb0)) + (a1 * this.GammaToLinear(rgb1)) + (a2 * this.GammaToLinear(rgb2)) + (a3 * this.GammaToLinear(rgb3)); - return this.LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); + uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); + return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); } // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision // U/V value, suitable for RGBToU/V calls. [MethodImpl(InliningOptions.ShortMethod)] - private int LinearToGamma(uint baseValue, int shift) + private static int LinearToGamma(uint baseValue, int shift) { - int y = this.Interpolate((int)(baseValue << shift)); // Final uplifted value. + int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. } [MethodImpl(InliningOptions.ShortMethod)] - private uint GammaToLinear(byte v) + private static uint GammaToLinear(byte v) { return WebPLookupTables.GammaToLinearTab[v]; } [MethodImpl(InliningOptions.ShortMethod)] - private int Interpolate(int v) + private static int Interpolate(int v) { int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. @@ -1512,65 +1545,65 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private int RgbToY(byte r, byte g, byte b, int rounding) + private static int RgbToY(byte r, byte g, byte b, int rounding) { int luma = (16839 * r) + (33059 * g) + (6420 * b); return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. } [MethodImpl(InliningOptions.ShortMethod)] - private int RgbToU(int r, int g, int b, int rounding) + private static int RgbToU(int r, int g, int b, int rounding) { int u = (-9719 * r) - (19081 * g) + (28800 * b); - return this.ClipUv(u, rounding); + return ClipUv(u, rounding); } [MethodImpl(InliningOptions.ShortMethod)] - private int RgbToV(int r, int g, int b, int rounding) + private static int RgbToV(int r, int g, int b, int rounding) { int v = (+28800 * r) - (24116 * g) - (4684 * b); - return this.ClipUv(v, rounding); + return ClipUv(v, rounding); } [MethodImpl(InliningOptions.ShortMethod)] - private int ClipUv(int uv, int rounding) + private static int ClipUv(int uv, int rounding) { uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; } [MethodImpl(InliningOptions.ShortMethod)] - private int FinalAlphaValue(int alpha) + private static int FinalAlphaValue(int alpha) { alpha = WebPConstants.MaxAlpha - alpha; - return this.Clip(alpha, 0, WebPConstants.MaxAlpha); + return Clip(alpha, 0, WebPConstants.MaxAlpha); } [MethodImpl(InliningOptions.ShortMethod)] - private int Clip(int v, int min, int max) + private static int Clip(int v, int min, int max) { return (v < min) ? min : (v > max) ? max : v; } [MethodImpl(InliningOptions.ShortMethod)] - private int Vp8Sse16X16(Span a, Span b) + private static int Vp8Sse16X16(Span a, Span b) { - return this.GetSse(a, b, 16, 16); + return GetSse(a, b, 16, 16); } - private int Vp8Sse16X8(Span a, Span b) + private static int Vp8Sse16X8(Span a, Span b) { - return this.GetSse(a, b, 16, 8); + return GetSse(a, b, 16, 8); } [MethodImpl(InliningOptions.ShortMethod)] - private int Vp8Sse4X4(Span a, Span b) + private static int Vp8Sse4X4(Span a, Span b) { - return this.GetSse(a, b, 4, 4); + return GetSse(a, b, 4, 4); } [MethodImpl(InliningOptions.ShortMethod)] - private int GetSse(Span a, Span b, int w, int h) + private static int GetSse(Span a, Span b, int w, int h) { int count = 0; int aOffset = 0; @@ -1591,7 +1624,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private bool IsFlatSource16(Span src) + private static bool IsFlatSource16(Span src) { uint v = src[0] * 0x01010101u; Span vSpan = BitConverter.GetBytes(v).AsSpan(); @@ -1614,7 +1647,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// is around q=75. Internally, our "good" middle is around c=50. So we /// map accordingly using linear piece-wise function /// - private double QualityToCompression(double c) + private static double QualityToCompression(double c) { double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; @@ -1630,27 +1663,35 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private double GetPsnr(long mse, long size) + private static double GetPsnr(long mse, long size) { return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; } [MethodImpl(InliningOptions.ShortMethod)] - private int QuantDiv(uint n, uint iQ, uint b) + private static int QuantDiv(uint n, uint iQ, uint b) { return (int)(((n * iQ) + b) >> WebPConstants.QFix); } [MethodImpl(InliningOptions.ShortMethod)] - private void Store(Span dst, Span reference, int x, int y, byte v) + private static void Store(Span dst, Span reference, int x, int y, byte v) { dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); } [MethodImpl(InliningOptions.ShortMethod)] - private int Mul(int a, int b) + private static int Mul(int a, int b) { return (a * b) >> 16; } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetProba(int a, int b) + { + int total = a + b; + return (total == 0) ? 255 // that's the default probability. + : ((255 * a) + (total / 2)) / total; // rounded proba + } } } From 98fb8af07d258f194c7e0d580f51decd06c51aac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Nov 2020 20:41:10 +0100 Subject: [PATCH 223/359] Fix some VP8 encoding mistakes --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 81 ++++++++++--------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 65 +++++++-------- .../Formats/WebP/Lossy/Vp8Residual.cs | 48 +++++------ 3 files changed, 101 insertions(+), 93 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 4607bf1c0..c6fec2b44 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -99,21 +99,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int uvTopIdx; - public Vp8EncIterator(IMemoryOwner yTop, IMemoryOwner uvTop, IMemoryOwner preds, IMemoryOwner nz, Vp8MacroBlockInfo[] mb, int mbw, int mbh) + public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, int mbw, int mbh) { this.mbw = mbw; this.mbh = mbh; this.Mb = mb; this.currentMbIdx = 0; - this.nzIdx = 0; - this.predIdx = 0; + this.nzIdx = 1; this.yTopIdx = 0; this.uvTopIdx = 0; this.YTop = yTop; this.UvTop = uvTop; - this.Preds = preds; this.Nz = nz; + this.Preds = preds; this.predsWidth = (4 * mbw) + 1; + this.predIdx = this.predsWidth; this.YuvIn = new byte[WebPConstants.Bps * 16]; this.YuvOut = new byte[WebPConstants.Bps * 16]; this.YuvOut2 = new byte[WebPConstants.Bps * 16]; @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.I4Boundary = new byte[37]; this.BitCount = new long[4, 3]; - // To match the C++ initial values of the reference implementation, initialize all with 204. + // To match the C initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; this.YuvIn.AsSpan().Fill(defaultInitVal); this.YuvOut.AsSpan().Fill(defaultInitVal); @@ -133,7 +133,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YuvP.AsSpan().Fill(defaultInitVal); this.YLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal); - this.Preds.GetSpan().Fill(defaultInitVal); for (int i = -255; i <= 255 + 255; ++i) { @@ -186,17 +185,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the top luma samples at position 'X'. /// - public IMemoryOwner YTop { get; } + public byte[] YTop { get; } /// /// Gets the top u/v samples at position 'X', packed as 16 bytes. /// - public IMemoryOwner UvTop { get; } + public byte[] UvTop { get; } /// /// Gets the intra mode predictors (4x4 blocks). /// - public IMemoryOwner Preds { get; } + public byte[] Preds { get; } /// /// Gets the current start index of the intra mode predictors. @@ -212,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the non-zero pattern. /// - public IMemoryOwner Nz { get; } + public uint[] Nz { get; } /// /// Gets 32+5 boundary samples needed by intra4x4. @@ -292,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.I4Boundary[i] = this.YLeft[15 - i + 1]; } - Span yTop = this.YTop.GetSpan(); + Span yTop = this.YTop.AsSpan(this.yTopIdx); for (i = 0; i < 16; ++i) { // top @@ -320,7 +319,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // Import uncompressed samples from source. - public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height) + public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height, bool importBoundarySamples) { int yStartIdx = ((this.Y * yStride) + this.X) * 16; int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; @@ -339,6 +338,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); + if (!importBoundarySamples) + { + return; + } + // Import source (uncompressed) samples into boundary. if (this.X == 0) { @@ -367,17 +371,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8); } - Span yTop = this.YTop.Slice(this.yTopIdx, 16); + Span yTop = this.YTop.AsSpan(this.yTopIdx, 16); if (this.Y == 0) { yTop.Fill(127); - this.UvTop.Slice(this.uvTopIdx, 16).Fill(127); + this.UvTop.AsSpan(this.uvTopIdx, 16).Fill(127); } else { this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16); - this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx, 8), uvw, 8); - this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx + 8, 8), uvw, 8); + this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.AsSpan(this.uvTopIdx, 8), uvw, 8); + this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.AsSpan(this.uvTopIdx + 8, 8), uvw, 8); } } @@ -472,7 +476,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public void SetIntra16Mode(int mode) { - Span preds = this.Preds.Slice(this.predIdx); + Span preds = this.Preds.AsSpan(this.predIdx); for (int y = 0; y < 4; ++y) { preds.Slice(0, 4).Fill((byte)mode); @@ -488,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int predIdx = this.predIdx; for (int y = 4; y > 0; --y) { - modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.Slice(predIdx)); + modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); predIdx += this.predsWidth; modesIdx += 4; } @@ -502,8 +506,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int predIdx = this.predIdx; int x = this.I4 & 3; int y = this.I4 >> 2; - int left = (x == 0) ? this.Preds.Slice(predIdx + (y * predsWidth) - 1)[0] : modes[this.I4 - 1]; - int top = (y == 0) ? this.Preds.Slice(predIdx - predsWidth + x)[0] : modes[this.I4 - 4]; + int left = (x == 0) ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; + int top = (y == 0) ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; return WebPLookupTables.Vp8FixedCostsI4[top, left]; } @@ -574,16 +578,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // top-left (before 'top'!) - this.YLeft[0] = this.YTop.GetSpan()[15]; - this.UvLeft[0] = this.UvTop.GetSpan()[0 + 7]; - this.UvLeft[16] = this.UvTop.GetSpan()[8 + 7]; + this.YLeft[0] = this.YTop[this.yTopIdx + 15]; + this.UvLeft[0] = this.UvTop[this.uvTopIdx + 0 + 7]; + this.UvLeft[16] = this.UvTop[this.uvTopIdx + 8 + 7]; } if (y < this.mbh - 1) { // top - ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.GetSpan()); - uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.GetSpan()); + ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); + uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); } } @@ -636,26 +640,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) { // Reset all predictors. - this.Nz.GetSpan()[0] = 0; + this.Nz[this.nzIdx] = 0; this.LeftNz[8] = 0; } else { - this.Nz.GetSpan()[0] &= 1 << 24; // Preserve the dc_nz bit. + this.Nz[this.nzIdx] &= 1 << 24; // Preserve the dc_nz bit. } } public void MakeLuma16Preds() { Span left = this.X != 0 ? this.YLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null; + Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; this.EncPredLuma16(this.YuvP, left, top); } public void MakeChroma8Preds() { Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; + Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; this.EncPredChroma8(this.YuvP, left, top); } @@ -673,10 +677,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public void NzToBytes() { - Span nz = this.Nz.GetSpan(); + Span nz = this.Nz.AsSpan(); - uint lnz = 0; // TODO: -1? - uint tnz = nz[0]; + uint lnz = nz[this.nzIdx - 1]; + uint tnz = nz[this.nzIdx]; Span topNz = this.TopNz; Span leftNz = this.LeftNz; @@ -731,6 +735,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); nz |= (uint)(leftNz[2] << 11); nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); + + this.Nz[this.nzIdx] = nz; } private void Mean16x4(Span input, Span dc) @@ -1257,7 +1263,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.X = 0; this.Y = y; this.currentMbIdx = y * this.mbw; - this.nzIdx = 0; + this.nzIdx = 1; // note: in reference source nz starts at -1. this.yTopIdx = 0; this.uvTopIdx = 0; this.predIdx = this.predsWidth + (y * 4 * this.predsWidth); @@ -1285,15 +1291,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void InitTop() { int topSize = this.mbw * 16; - this.YTop.Slice(0, topSize).Fill(127); - this.UvTop.GetSpan().Fill(127); - this.Nz.GetSpan().Fill(0); + this.YTop.AsSpan(0, topSize).Fill(127); + this.UvTop.AsSpan().Fill(127); + this.Nz.AsSpan().Fill(0); } - // Convert packed context to byte array. private int Bit(uint nz, int n) { - return (int)(nz & (1 << n)); + return (nz & (1 << n)) != 0 ? 1 : 0; } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index ef227e53f..6747ff6db 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -149,9 +149,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Y = this.memoryAllocator.Allocate(pixelCount); this.U = this.memoryAllocator.Allocate(uvSize); this.V = this.memoryAllocator.Allocate(uvSize); - this.YTop = this.memoryAllocator.Allocate(this.mbw * 16); - this.UvTop = this.memoryAllocator.Allocate(this.mbw * 16 * 2); - this.Nz = this.memoryAllocator.Allocate(this.mbw + 1); + this.YTop = new byte[this.mbw * 16]; + this.UvTop = new byte[this.mbw * 16 * 2]; + this.Nz = new uint[this.mbw + 1]; this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; @@ -169,10 +169,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.proba = new Vp8EncProba(); - // this.Preds = this.memoryAllocator.Allocate(predSize); - this.Preds = this.memoryAllocator.Allocate(predSize * 2); // TODO: figure out how much mem we need here. This is too much. + this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; + // Initialize with default values, which the reference c implementation uses, + // to be able to compare to the original and spot differences. + this.Preds.AsSpan().Fill(205); + this.Nz.AsSpan().Fill(3452816845); + this.ResetBoundaryPredictions(); // Initialize the bitwriter. @@ -205,22 +209,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the top luma samples. /// - private IMemoryOwner YTop { get; } + private byte[] YTop { get; } /// /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). /// - private IMemoryOwner UvTop { get; } + private byte[] UvTop { get; } /// - /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). + /// Gets the non-zero pattern. /// - private IMemoryOwner Preds { get; } + private uint[] Nz { get; } /// - /// Gets the non-zero pattern. + /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). /// - private IMemoryOwner Nz { get; } + private byte[] Preds { get; } /// /// Gets a rough limit for header bits per MB. @@ -251,7 +255,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy segmentInfos[i] = new Vp8SegmentInfo(); } - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); var alphas = new int[WebPConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); @@ -268,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy do { var info = new Vp8ModeScore(); - it.Import(y, u, v, yStride, uvStride, width, height); + it.Import(y, u, v, yStride, uvStride, width, height, false); if (!this.Decimate(it, segmentInfos, info, this.rdOptLevel)) { this.CodeResiduals(it, info); @@ -292,9 +296,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Y.Dispose(); this.U.Dispose(); this.V.Dispose(); - this.YTop.Dispose(); - this.UvTop.Dispose(); - this.Preds.Dispose(); } /// @@ -305,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void StatLoop(int width, int height, int yStride, int uvStride, Vp8SegmentInfo[] segmentInfos) { int targetSize = 0; // TODO: target size is hardcoded. - float targetPsnr = 0.0f; // TDOO: targetPsnr is hardcoded. + float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. int method = this.method; bool doSearch = false; // TODO: doSearch hardcoded for now. bool fastProbe = (method == 0 || method == 3) && !doSearch; @@ -377,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); long size = 0; long sizeP0 = 0; long distortion = 0; @@ -387,7 +388,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy do { var info = new Vp8ModeScore(); - it.Import(y, u, v, yStride, uvStride, width, height); + it.Import(y, u, v, yStride, uvStride, width, height, false); if (this.Decimate(it, segmentInfos, info, rdOpt)) { // Just record the number of skips and act like skipProba is not used. @@ -432,8 +433,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private void ResetBoundaryPredictions() { - Span top = this.Preds.GetSpan(); - Span left = this.Preds.Slice(this.predsWidth - 1); + Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ + Span left = this.Preds.AsSpan(this.predsWidth - 1); for (int i = 0; i < 4 * this.mbw; ++i) { top[i] = (int)IntraPredictionMode.DcPrediction; @@ -444,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction; } - // TODO: enc->nz_[-1] = 0; // constant + this.Nz[0] = 0; // constant } // Simplified k-Means, to assign Nb segments based on alpha-histogram. @@ -704,7 +705,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { do { - it.Import(y, u, v, yStride, uvStride, width, height); + it.Import(y, u, v, yStride, uvStride, width, height, true); int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha); // Accumulate for later complexity analysis. @@ -875,7 +876,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.Slice(it.PredIdx)[0]); + int intra16Mode = it.Preds[it.PredIdx]; + nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); } // ... and UV! @@ -974,7 +976,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int x, y, ch; var residual = new Vp8Residual(); bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - int segment = it.CurrentMacroBlockInfo.Segment; it.NzToBytes(); @@ -1041,7 +1042,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); } - this.FTransformWht(tmp.AsSpan(0), dcTmp); + this.FTransformWht(tmp, dcTmp); nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; for (n = 0; n < 16; n += 2) @@ -1049,7 +1050,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Zero-out the first coeff, so that: a) nz is correct below, and // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; + nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; } // Transform back. @@ -1279,10 +1280,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int b = dc - tmp[8]; int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, (byte)(a + d)); - Store(dst, reference, 1, i, (byte)(b + c)); - Store(dst, reference, 2, i, (byte)(b - c)); - Store(dst, reference, 3, i, (byte)(a - d)); + Store(dst, reference, 0, i, (a + d)); + Store(dst, reference, 1, i, (b + c)); + Store(dst, reference, 2, i, (b - c)); + Store(dst, reference, 3, i, (a - d)); tmp = tmp.Slice(1); } } @@ -1675,7 +1676,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, byte v) + private static void Store(Span dst, Span reference, int x, int y, int v) { dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index f79e8f91d..7b6199748 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -71,34 +71,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.RecordStats(0, s, 1); s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[0]; - this.RecordStats(1, s, 1); - if (this.RecordStats((v + 1) > 2u ? 1 : 0, s, 2) == 0) + } + + this.RecordStats(1, s, 1); + var bit = 2u < (uint)(v + 1); + if (this.RecordStats(bit ? 1 : 0, s, 2) == 0) + { + // v = -1 or 1 + s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1]; + } + else + { + v = Math.Abs(v); + if (v > MaxVariableLevel) { - // v = -1 or 1 - s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1]; + v = MaxVariableLevel; } - else - { - v = Math.Abs(v); - if (v > MaxVariableLevel) - { - v = MaxVariableLevel; - } - int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1]; - int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0]; - int i; - for (i = 0; (pattern >>= 1) != 0; ++i) + int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1]; + int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0]; + int i; + for (i = 0; (pattern >>= 1) != 0; ++i) + { + int mask = 2 << i; + if ((pattern & 1) != 0) { - int mask = 2 << i; - if ((pattern & 1) != 0) - { - this.RecordStats(bits & mask, s, 3 + i); - } + this.RecordStats((bits & mask) != 0 ? 1 : 0, s, 3 + i); } - - s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2]; } + + s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2]; } } @@ -119,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. } - // record bit count (lower 16 bits) and increment total count (upper 16 bits). + // Record bit count (lower 16 bits) and increment total count (upper 16 bits). statsArr.Stats[idx] += 0x00010000u + (uint)bit; return bit; From 92c51d8af12dc531fbcea722889d7075f0e058ac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Nov 2020 19:38:00 +0100 Subject: [PATCH 224/359] Write Vp8 partition 0 and frame header --- .../Formats/WebP/BitWriter/BitWriterBase.cs | 52 +-- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 369 ++++++++++++++++++ .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 28 ++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 2 +- .../Formats/WebP/Lossy/Vp8Encoder.cs | 270 ++++++++++--- .../Formats/WebP/Lossy/Vp8FilterHeader.cs | 10 + .../Formats/WebP/Lossy/Vp8Residual.cs | 2 +- .../Formats/WebP/Lossy/Vp8SegmentHeader.cs | 3 + src/ImageSharp/Formats/WebP/WebPConstants.cs | 25 +- .../Formats/WebP/WebPDecoderCore.cs | 10 +- .../Formats/WebP/WebPLookupTables.cs | 56 ++- 11 files changed, 730 insertions(+), 97 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs index 1b011315c..73e8e889c 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs @@ -61,6 +61,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// public abstract void Finish(); + /// + /// Writes the encoded image to the stream. + /// + /// The stream to write to. + public abstract void WriteEncodedImageToStream(Stream stream); + protected bool ResizeBuffer(int maxBytes, int sizeRequired) { if (maxBytes > 0 && sizeRequired < maxBytes) @@ -83,59 +89,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return false; } - /// - /// Writes the encoded image to the stream. - /// - /// If true, lossy tag will be written, otherwise a lossless tag. - /// The stream to write to. - public void WriteEncodedImageToStream(bool lossy, Stream stream) - { - this.Finish(); - var numBytes = this.NumBytes(); - var size = numBytes; - if (!lossy) - { - size++; // One byte extra for the VP8L signature. - } - - var pad = size & 1; - var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + size + pad; - this.WriteRiffHeader(riffSize, size, lossy, stream); - this.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } - } - /// /// Writes the RIFF header to the stream. /// - /// The block length. - /// The size in bytes of the encoded image. - /// If true, lossy tag will be written, otherwise a lossless tag. /// The stream to write to. - private void WriteRiffHeader(int riffSize, int size, bool lossy, Stream stream) + /// The block length. + protected void WriteRiffHeader(Stream stream, uint riffSize) { Span buffer = stackalloc byte[4]; stream.Write(WebPConstants.RiffFourCc); - BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, riffSize); stream.Write(buffer); stream.Write(WebPConstants.WebPHeader); - - if (lossy) - { - stream.Write(WebPConstants.Vp8MagicBytes); - } - else - { - stream.Write(WebPConstants.Vp8LMagicBytes); - } - - BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)size); - stream.Write(buffer); - stream.WriteByte(WebPConstants.Vp8LMagicByte); } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index e18a945bc..89cff2166 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers.Binary; +using System.IO; using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -10,6 +13,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// internal class Vp8BitWriter : BitWriterBase { +#pragma warning disable SA1310 // Field names should not contain underscore + private const int DC_PRED = 0; + private const int TM_PRED = 1; + private const int V_PRED = 2; + private const int H_PRED = 3; + + // 4x4 modes + private const int B_DC_PRED = 0; + private const int B_TM_PRED = 1; + private const int B_VE_PRED = 2; + private const int B_HE_PRED = 3; + private const int B_RD_PRED = 4; + private const int B_VR_PRED = 5; + private const int B_LD_PRED = 6; + private const int B_VL_PRED = 7; + private const int B_HD_PRED = 8; + private const int B_HU_PRED = 9; +#pragma warning restore SA1310 // Field names should not contain underscore + + private readonly Vp8Encoder enc; + private int range; private int value; @@ -43,6 +67,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.maxPos = 0; } + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + /// The Vp8Encoder. + public Vp8BitWriter(int expectedSize, Vp8Encoder enc) + : this(expectedSize) + { + this.enc = enc; + } + /// public override int NumBytes() { @@ -180,6 +215,74 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.Flush(); } + public void PutSegment(int s, Span p) + { + if (this.PutBit(s >= 2, p[0])) + { + p = p.Slice(1); + } + + this.PutBit(s & 1, p[1]); + } + + public void PutI16Mode(int mode) + { + if (this.PutBit(mode == TM_PRED || mode == H_PRED, 156)) + { + this.PutBit(mode == TM_PRED, 128); // TM or HE + } + else + { + this.PutBit(mode == V_PRED, 163); // VE or DC + } + } + + public int PutI4Mode(int mode, Span prob) + { + if (this.PutBit(mode != B_DC_PRED, prob[0])) + { + if (this.PutBit(mode != B_TM_PRED, prob[1])) + { + if (this.PutBit(mode != B_VE_PRED, prob[2])) + { + if (!this.PutBit(mode >= B_LD_PRED, prob[3])) + { + if (this.PutBit(mode != B_HE_PRED, prob[4])) + { + this.PutBit(mode != B_RD_PRED, prob[5]); + } + } + else + { + if (this.PutBit(mode != B_LD_PRED, prob[6])) + { + if (this.PutBit(mode != B_VL_PRED, prob[7])) + { + this.PutBit(mode != B_HD_PRED, prob[8]); + } + } + } + } + } + } + + return mode; + } + + public void PutUvMode(int uvMode) + { + // DC_PRED + if (this.PutBit(uvMode != DC_PRED, 142)) + { + // V_PRED + if (this.PutBit(uvMode != V_PRED, 114)) + { + // H_PRED + this.PutBit(uvMode != H_PRED, 183); + } + } + } + private void PutBits(uint value, int nbBits) { for (uint mask = 1u << (nbBits - 1); mask != 0; mask >>= 1) @@ -249,6 +352,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return bit; } + private void PutSignedBits(int value, int nbBits) + { + if (this.PutBitUniform(value != 0 ? 1 : 0) == 0) + { + return; + } + + if (value < 0) + { + var valueToWrite = ((-value) << 1) | 1; + this.PutBits((uint)valueToWrite, nbBits + 1); + } + else + { + this.PutBits((uint)(value << 1), nbBits + 1); + } + } + private void Flush() { int s = 8 + this.nbBits; @@ -286,5 +407,253 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.run++; // Delay writing of bytes 0xff, pending eventual carry. } } + + /// + public override void WriteEncodedImageToStream(Stream stream) + { + this.Finish(); + uint numBytes = (uint)this.NumBytes(); + int mbSize = this.enc.Mbw * this.enc.Mbh; + int expectedSize = mbSize * 7 / 8; + + var bitWriterPartZero = new Vp8BitWriter(expectedSize); + + // Partition #0 with header and partition sizes + uint size0 = this.GeneratePartition0(bitWriterPartZero); + + uint vp8Size = WebPConstants.Vp8FrameHeaderSize + size0; + vp8Size += numBytes; + uint pad = vp8Size & 1; + vp8Size += pad; + + // Compute RIFF size + // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. + var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8Size; + + // Emit headers and partition #0 + this.WriteWebPHeaders(stream, size0, vp8Size, riffSize); + bitWriterPartZero.WriteToStream(stream); + + // Write the encoded image to the stream. + this.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); + } + } + + private uint GeneratePartition0(Vp8BitWriter bitWriter) + { + bitWriter.PutBitUniform(0); // colorspace + bitWriter.PutBitUniform(0); // clamp type + + this.WriteSegmentHeader(bitWriter); + this.WriteFilterHeader(bitWriter); + + bitWriter.PutBits(0, 2); + + this.WriteQuant(bitWriter); + bitWriter.PutBitUniform(0); + this.WriteProbas(bitWriter); + this.CodeIntraModes(bitWriter); + + bitWriter.Finish(); + + return (uint)bitWriter.NumBytes(); + } + + private void WriteSegmentHeader(Vp8BitWriter bitWriter) + { + Vp8EncSegmentHeader hdr = this.enc.SegmentHeader; + Vp8EncProba proba = this.enc.Proba; + if (bitWriter.PutBitUniform(hdr.NumSegments > 1 ? 1 : 0) != 0) + { + // We always 'update' the quant and filter strength values. + int updateData = 1; + bitWriter.PutBitUniform(hdr.UpdateMap ? 1 : 0); + if (bitWriter.PutBitUniform(updateData) != 0) + { + // We always use absolute values, not relative ones. + bitWriter.PutBitUniform(1); // (segment_feature_mode = 1. Paragraph 9.3.) + for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + { + bitWriter.PutSignedBits(this.enc.SegmentInfos[s].Quant, 7); + } + + for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + { + bitWriter.PutSignedBits(this.enc.SegmentInfos[s].FStrength, 6); + } + } + + if (hdr.UpdateMap) + { + for (int s = 0; s < 3; ++s) + { + if (bitWriter.PutBitUniform((proba.Segments[s] != 255) ? 1 : 0) != 0) + { + bitWriter.PutBits(proba.Segments[s], 8); + } + } + } + } + } + + private void WriteFilterHeader(Vp8BitWriter bitWriter) + { + Vp8FilterHeader hdr = this.enc.FilterHeader; + var useLfDelta = hdr.I4x4LfDelta != 0; + bitWriter.PutBitUniform(hdr.Simple ? 1 : 0); + bitWriter.PutBits((uint)hdr.FilterLevel, 6); + bitWriter.PutBits((uint)hdr.Sharpness, 3); + if (bitWriter.PutBitUniform(useLfDelta ? 1 : 0) != 0) + { + // '0' is the default value for i4x4LfDelta at frame #0. + bool needUpdate = hdr.I4x4LfDelta != 0; + if (bitWriter.PutBitUniform(needUpdate ? 1 : 0) != 0) + { + // we don't use refLfDelta => emit four 0 bits. + bitWriter.PutBits(0, 4); + + // we use modeLfDelta for i4x4 + bitWriter.PutSignedBits(hdr.I4x4LfDelta, 6); + bitWriter.PutBits(0, 3); // all others unused. + } + } + } + + // Nominal quantization parameters + private void WriteQuant(Vp8BitWriter bitWriter) + { + bitWriter.PutBits((uint)this.enc.BaseQuant, 7); + bitWriter.PutSignedBits(this.enc.DqY1Dc, 4); + bitWriter.PutSignedBits(this.enc.DqY2Dc, 4); + bitWriter.PutSignedBits(this.enc.DqY2Ac, 4); + bitWriter.PutSignedBits(this.enc.DqUvDc, 4); + bitWriter.PutSignedBits(this.enc.DqUvAc, 4); + } + + private void WriteProbas(Vp8BitWriter bitWriter) + { + Vp8EncProba probas = this.enc.Proba; + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + byte p0 = probas.Coeffs[t][b].Probabilities[c].Probabilities[p]; + bool update = p0 != WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + if (bitWriter.PutBit(update, WebPLookupTables.CoeffsUpdateProba[t, b, c, p])) + { + bitWriter.PutBits(p0, 8); + } + } + } + } + } + + if (bitWriter.PutBitUniform(probas.UseSkipProba ? 1 : 0) != 0) + { + bitWriter.PutBits(probas.SkipProba, 8); + } + } + + private void CodeIntraModes(Vp8BitWriter bitWriter) + { + var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.Mbw, this.enc.Mbh); + int predsWidth = this.enc.PredsWidth; + + do + { + Vp8MacroBlockInfo mb = it.CurrentMacroBlockInfo; + Span preds = it.Preds.AsSpan(it.PredIdx); + if (this.enc.SegmentHeader.UpdateMap) + { + bitWriter.PutSegment(mb.Segment, this.enc.Proba.Segments); + } + + if (this.enc.Proba.UseSkipProba) + { + bitWriter.PutBit(mb.Skip, this.enc.Proba.SkipProba); + } + + if (bitWriter.PutBit(mb.MacroBlockType != 0, 145)) + { + // i16x16 + bitWriter.PutI16Mode(preds[0]); + } + else + { + Span topPred = it.Preds.AsSpan(it.PredIdx); + int x, y; + for (y = 0; y < 4; ++y) + { + int left = preds[it.PredIdx - 1]; + for (x = 0; x < 4; ++x) + { + byte[] probas = WebPLookupTables.ModesProba[topPred[x], left]; + left = bitWriter.PutI4Mode(preds[x], probas); + } + + topPred = preds; + preds = preds.Slice(predsWidth); + } + } + + bitWriter.PutUvMode(mb.UvMode); + } + while (it.Next()); + } + + private void WriteWebPHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize) + { + this.WriteRiffHeader(stream, riffSize); + this.WriteVp8Header(stream, vp8Size); + this.WriteFrameHeader(stream, size0); + } + + private void WriteVp8Header(Stream stream, uint size) + { + Span vp8ChunkHeader = stackalloc byte[WebPConstants.ChunkHeaderSize]; + + WebPConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader); + BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader.Slice(4), size); + + stream.Write(vp8ChunkHeader); + } + + private void WriteFrameHeader(Stream stream, uint size0) + { + uint profile = 0; + int width = this.enc.Width; + int height = this.enc.Height; + var vp8FrameHeader = new byte[WebPConstants.Vp8FrameHeaderSize]; + + // Paragraph 9.1. + uint bits = 0 // keyframe (1b) + | (profile << 1) // profile (3b) + | (1 << 4) // visible (1b) + | (size0 << 5); // partition length (19b) + + vp8FrameHeader[0] = (byte)((bits >> 0) & 0xff); + vp8FrameHeader[1] = (byte)((bits >> 8) & 0xff); + vp8FrameHeader[2] = (byte)((bits >> 16) & 0xff); + + // signature + vp8FrameHeader[3] = WebPConstants.Vp8HeaderMagicBytes[0]; + vp8FrameHeader[4] = WebPConstants.Vp8HeaderMagicBytes[1]; + vp8FrameHeader[5] = WebPConstants.Vp8HeaderMagicBytes[2]; + + // dimensions + vp8FrameHeader[6] = (byte)(width & 0xff); + vp8FrameHeader[7] = (byte)(width >> 8); + vp8FrameHeader[8] = (byte)(height & 0xff); + vp8FrameHeader[9] = (byte)(height >> 8); + + stream.Write(vp8FrameHeader); + } } } diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 917646bfe..139eebf9a 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.IO; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -126,6 +127,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.used = 0; } + /// + public override void WriteEncodedImageToStream(Stream stream) + { + Span buffer = stackalloc byte[4]; + + this.Finish(); + uint size = (uint)this.NumBytes(); + size++; // One byte extra for the VP8L signature. + + // Write RIFF header. + uint pad = size & 1; + uint riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + size + pad; + this.WriteRiffHeader(stream, riffSize); + stream.Write(WebPConstants.Vp8LMagicBytes); + + // Write Vp8 Header. + BinaryPrimitives.WriteUInt32LittleEndian(buffer, size); + stream.Write(buffer); + stream.WriteByte(WebPConstants.Vp8LHeaderMagicByte); + + this.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); + } + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 55e129e7d..078710486 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.EncodeStream(image); // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(lossy: false, stream); + this.bitWriter.WriteEncodedImageToStream(stream); } /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 6747ff6db..8972e270f 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -62,6 +62,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private Vp8EncSegmentHeader segmentHeader; + /// + /// The filter header info's. + /// + private Vp8FilterHeader filterHeader; + + /// + /// The segment infos. + /// + private Vp8SegmentInfo[] segmentInfos; + /// /// Contextual macroblock infos. /// @@ -74,6 +84,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private readonly Vp8RdLevel rdOptLevel; + private int dqY1Dc; + + private int dqY2Dc; + + private int dqY2Ac; + private int dqUvDc; private int dqUvAc; @@ -122,6 +138,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int QMax = 100; + // TODO: filterStrength is hardcoded, should be configurable. + private const int FilterStrength = 60; + /// /// Initializes a new instance of the class. /// @@ -133,6 +152,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// Number of entropy-analysis passes (in [1..10]). public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method, int entropyPasses) { + this.Width = width; + this.Height = height; this.memoryAllocator = memoryAllocator; this.quality = quality.Clamp(0, 100); this.method = method.Clamp(0, 6); @@ -167,8 +188,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.mbInfo[i] = new Vp8MacroBlockInfo(); } - this.proba = new Vp8EncProba(); + this.segmentInfos = new Vp8SegmentInfo[4]; + for (int i = 0; i < 4; i++) + { + this.segmentInfos[i] = new Vp8SegmentInfo(); + } + this.filterHeader = new Vp8FilterHeader(); + this.proba = new Vp8EncProba(); this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; @@ -180,10 +207,52 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ResetBoundaryPredictions(); // Initialize the bitwriter. - var baseQuant = 36; // TODO: hardCoded for now. - int averageBytesPerMacroBlock = this.averageBytesPerMb[baseQuant >> 4]; + this.BaseQuant = 36; // TODO: hardCoded for now. + int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4]; int expectedSize = this.mbw * this.mbh * averageBytesPerMacroBlock; - this.bitWriter = new Vp8BitWriter(expectedSize); + this.bitWriter = new Vp8BitWriter(expectedSize, this); + } + + public int BaseQuant { get; } + + /// + /// Gets the probabilities. + /// + public Vp8EncProba Proba + { + get => this.proba; + } + + /// + /// Gets the segment features. + /// + public Vp8EncSegmentHeader SegmentHeader + { + get => this.segmentHeader; + } + + /// + /// Gets the segment infos. + /// + public Vp8SegmentInfo[] SegmentInfos + { + get => this.segmentInfos; + } + + /// + /// Gets the macro block info's. + /// + public Vp8MacroBlockInfo[] MbInfo + { + get => this.mbInfo; + } + + /// + /// Gets the filter header. + /// + public Vp8FilterHeader FilterHeader + { + get => this.filterHeader; } /// @@ -191,6 +260,56 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public int Alpha { get; set; } + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + public int PredsWidth + { + get => this.predsWidth; + } + + public int Mbw + { + get => this.mbw; + } + + public int Mbh + { + get => this.mbh; + } + + public int DqY1Dc + { + get => this.dqY1Dc; + } + + public int DqY2Ac + { + get => this.dqY2Ac; + } + + public int DqY2Dc + { + get => this.dqY2Dc; + } + + public int DqUvAc + { + get => this.dqUvAc; + } + + public int DqUvDc + { + get => this.dqUvDc; + } + /// /// Gets the luma component. /// @@ -209,22 +328,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// Gets the top luma samples. /// - private byte[] YTop { get; } + public byte[] YTop { get; } /// /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). /// - private byte[] UvTop { get; } + public byte[] UvTop { get; } /// /// Gets the non-zero pattern. /// - private uint[] Nz { get; } + public uint[] Nz { get; } /// /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). /// - private byte[] Preds { get; } + public byte[] Preds { get; } /// /// Gets a rough limit for header bits per MB. @@ -249,11 +368,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int yStride = width; int uvStride = (yStride + 1) >> 1; - var segmentInfos = new Vp8SegmentInfo[4]; - for (int i = 0; i < 4; i++) - { - segmentInfos[i] = new Vp8SegmentInfo(); - } var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); var alphas = new int[WebPConstants.MaxAlpha + 1]; @@ -261,19 +375,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Analysis is done, proceed to actual encoding. this.segmentHeader = new Vp8EncSegmentHeader(4); - this.AssignSegments(segmentInfos, alphas); - this.SetLoopParams(segmentInfos, this.quality); + this.AssignSegments(alphas); + this.SetLoopParams(this.quality); // TODO: EncodeAlpha(); // Stats-collection loop. - this.StatLoop(width, height, yStride, uvStride, segmentInfos); + this.StatLoop(width, height, yStride, uvStride); it.Init(); it.InitFilter(); do { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height, false); - if (!this.Decimate(it, segmentInfos, info, this.rdOptLevel)) + if (!this.Decimate(it, info, this.rdOptLevel)) { this.CodeResiduals(it, info); } @@ -286,8 +400,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } while (it.Next()); + // Store filter stats. + this.AdjustFilterStrength(); + // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(lossy: true, stream); + this.bitWriter.WriteEncodedImageToStream(stream); } /// @@ -303,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// This is used for deciding optimal probabilities. It also modifies the /// quantizer value if some target (size, PSNR) was specified. /// - private void StatLoop(int width, int height, int yStride, int uvStride, Vp8SegmentInfo[] segmentInfos) + private void StatLoop(int width, int height, int yStride, int uvStride) { int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. @@ -334,7 +451,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy while (numPassLeft-- > 0) { bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); - var sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats, segmentInfos); + var sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); if (sizeP0 == 0) { return; @@ -373,23 +490,23 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.proba.CalculateLevelCosts(); // finalize costs } - private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats, Vp8SegmentInfo[] segmentInfos) + private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) { Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); + var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.Mbw, this.Mbh); long size = 0; long sizeP0 = 0; long distortion = 0; long pixelCount = nbMbs * 384; - this.SetLoopParams(segmentInfos, stats.Q); + this.SetLoopParams(stats.Q); do { var info = new Vp8ModeScore(); it.Import(y, u, v, yStride, uvStride, width, height, false); - if (this.Decimate(it, segmentInfos, info, rdOpt)) + if (this.Decimate(it, info, rdOpt)) { // Just record the number of skips and act like skipProba is not used. ++this.proba.NbSkip; @@ -420,10 +537,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return sizeP0; } - private void SetLoopParams(Vp8SegmentInfo[] dqm, float q) + private void SetLoopParams(float q) { // Setup segment quantizations and filters. - this.SetSegmentParams(dqm, q); + this.SetSegmentParams(q); // Compute segment probabilities. this.SetSegmentProbas(); @@ -431,6 +548,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.ResetStats(); } + private void AdjustFilterStrength() + { + if (FilterStrength > 0) + { + int maxLevel = 0; + for (int s = 0; s < WebPConstants.NumMbSegments; s++) + { + Vp8SegmentInfo dqm = this.SegmentInfos[s]; + + // this '>> 3' accounts for some inverse WHT scaling + int delta = (dqm.MaxEdge * dqm.Y2.Q[1]) >> 3; + int level = this.FilterStrengthFromDelta(this.filterHeader.Sharpness, delta); + if (level > dqm.FStrength) + { + dqm.FStrength = level; + } + + if (maxLevel < dqm.FStrength) + { + maxLevel = dqm.FStrength; + } + } + + this.filterHeader.FilterLevel = maxLevel; + } + } + private void ResetBoundaryPredictions() { Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ @@ -449,16 +593,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // Simplified k-Means, to assign Nb segments based on alpha-histogram. - private void AssignSegments(Vp8SegmentInfo[] dqm, int[] alphas) + private void AssignSegments(int[] alphas) { int nb = (this.segmentHeader.NumSegments < NumMbSegments) ? this.segmentHeader.NumSegments : NumMbSegments; var centers = new int[NumMbSegments]; int weightedAverage = 0; var map = new int[WebPConstants.MaxAlpha + 1]; int a, n, k; - int minA; - int maxA; - int rangeA; var accum = new int[NumMbSegments]; var distAccum = new int[NumMbSegments]; @@ -467,13 +608,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { } - minA = n; + var minA = n; for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } - maxA = n; - rangeA = maxA - minA; + var maxA = n; + var rangeA = maxA - minA; // Spread initial centers evenly. for (k = 0, n = 1; k < nb; ++k, n += 2) @@ -542,12 +683,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // TODO: add possibility for SmoothSegmentMap - this.SetSegmentAlphas(dqm, centers, weightedAverage); + this.SetSegmentAlphas(centers, weightedAverage); } - private void SetSegmentAlphas(Vp8SegmentInfo[] dqm, int[] centers, int mid) + private void SetSegmentAlphas(int[] centers, int mid) { int nb = this.segmentHeader.NumSegments; + Vp8SegmentInfo[] dqm = this.segmentInfos; int min = centers[0], max = centers[0]; int n; @@ -581,9 +723,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - private void SetSegmentParams(Vp8SegmentInfo[] dqm, float quality) + private void SetSegmentParams(float quality) { int nb = this.segmentHeader.NumSegments; + Vp8SegmentInfo[] dqm = this.SegmentInfos; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; double cBase = QualityToCompression(quality / 100.0d); @@ -614,9 +757,42 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.dqUvDc = -4 * snsStrength / 100; this.dqUvDc = Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed + this.dqY1Dc = 0; + this.dqY2Dc = 0; + this.dqY2Ac = 0; + + // Initialize segments' filtering + this.SetupFilterStrength(); + this.SetupMatrices(dqm); } + private void SetupFilterStrength() + { + var filterSharpness = 0; // TODO: filterSharpness is hardcoded + var filterType = 1; // TODO: filterType is hardcoded + + // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. + int level0 = 5 * FilterStrength; + for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + { + Vp8SegmentInfo m = this.SegmentInfos[i]; + + // We focus on the quantization of AC coeffs. + int qstep = WebPLookupTables.AcTable[Clip(m.Quant, 0, 127)] >> 2; + int baseStrength = this.FilterStrengthFromDelta(this.filterHeader.Sharpness, qstep); + + // Segments with lower complexity ('beta') will be less filtered. + int f = baseStrength * level0 / (256 + m.Beta); + m.FStrength = (f < WebPConstants.FilterStrengthCutoff) ? 0 : (f > 63) ? 63 : f; + } + + // We record the initial strength (mainly for the case of 1-segment only). + this.filterHeader.FilterLevel = this.SegmentInfos[0].FStrength; + this.filterHeader.Simple = filterType == 0; + this.filterHeader.Sharpness = filterSharpness; + } + private void SetSegmentProbas() { var p = new int[NumMbSegments]; @@ -745,7 +921,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } - private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, Vp8RdLevel rdOpt) + private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, Vp8RdLevel rdOpt) { rd.InitScore(); @@ -757,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - this.RefineUsingDistortion(it, segmentInfos, rd, this.method >= 2, this.method >= 1); + this.RefineUsingDistortion(it, rd, this.method >= 2, this.method >= 1); bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); @@ -766,13 +942,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } // Refine intra16/intra4 sub-modes based on distortion only (not rate). - private void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) + private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) { long bestScore = Vp8ModeScore.MaxCost; int nz = 0; int mode; bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + Vp8SegmentInfo dqm = this.segmentInfos[it.CurrentMacroBlockInfo.Segment]; // Some empiric constants, of approximate order of magnitude. int lambdaDi16 = 106; @@ -1280,10 +1456,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int b = dc - tmp[8]; int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, (a + d)); - Store(dst, reference, 1, i, (b + c)); - Store(dst, reference, 2, i, (b - c)); - Store(dst, reference, 3, i, (a - d)); + Store(dst, reference, 0, i, a + d); + Store(dst, reference, 1, i, b + c); + Store(dst, reference, 2, i, b - c); + Store(dst, reference, 3, i, a - d); tmp = tmp.Slice(1); } } @@ -1663,6 +1839,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return v; } + private int FilterStrengthFromDelta(int sharpness, int delta) + { + int pos = (delta < WebPConstants.MaxDelzaSize) ? delta : WebPConstants.MaxDelzaSize - 1; + return WebPLookupTables.LevelsFromDelta[sharpness, pos]; + } + [MethodImpl(InliningOptions.ShortMethod)] private static double GetPsnr(long mse, long size) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs index 4f5cad659..4c314e2fc 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8FilterHeader.cs @@ -53,6 +53,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + /// + /// Gets or sets a value indicating whether the filtering type is: 0=complex, 1=simple. + /// + public bool Simple { get; set; } + + /// + /// Gets or sets delta filter level for i4x4 relative to i16x16. + /// + public int I4x4LfDelta { get; set; } + public bool UseLfDelta { get; set; } public int[] RefLfDelta { get; } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index 7b6199748..7f96b4dba 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } this.RecordStats(1, s, 1); - var bit = 2u < (uint)(v + 1); + var bit = (uint)(v + 1) > 2u; if (this.RecordStats(bit ? 1 : 0, s, 2) == 0) { // v = -1 or 1 diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs index 3c399fe2e..1eb144486 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentHeader.cs @@ -10,6 +10,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { private const int NumMbSegments = 4; + /// + /// Initializes a new instance of the class. + /// public Vp8SegmentHeader() { this.Quantizer = new byte[NumMbSegments]; diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 08dac5bf0..33145e85d 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Signature which identifies a VP8 header. /// - public static readonly byte[] Vp8MagicBytes = + public static readonly byte[] Vp8HeaderMagicBytes = { 0x9D, 0x01, @@ -33,10 +33,21 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Signature byte which identifies a VP8L header. /// - public const byte Vp8LMagicByte = 0x2F; + public const byte Vp8LHeaderMagicByte = 0x2F; + + /// + /// Signature bytes identifying a lossy image. + /// + public static readonly byte[] Vp8MagicBytes = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x20 // ' ' + }; /// - /// Header bytes identifying a lossless image. + /// Signature bytes identifying a lossless image. /// public static readonly byte[] Vp8LMagicBytes = { @@ -251,6 +262,14 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int QFix = 17; + public const int MaxDelzaSize = 64; + + /// + /// Very small filter-strength values have close to no visual effect. So we can + /// save a little decoding-CPU by turning filtering off for these. + /// + public const int FilterStrengthCutoff = 2; + /// /// Max size of mode partition. /// diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index d5bcf1d7a..4270e9efc 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -307,7 +307,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Check for VP8 magic bytes. this.currentStream.Read(this.buffer, 0, 3); - if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8MagicBytes)) + if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8HeaderMagicBytes)) { WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); } @@ -341,8 +341,10 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream, remaining, this.memoryAllocator, - partitionLength); - bitReader.Remaining = remaining; + partitionLength) + { + Remaining = remaining + }; return new WebPImageInfo() { @@ -375,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // One byte signature, should be 0x2f. uint signature = bitReader.ReadValue(8); - if (signature != WebPConstants.Vp8LMagicByte) + if (signature != WebPConstants.Vp8LHeaderMagicByte) { WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index a550903e0..ed84c377c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly int[] LinearToGammaTab = new int[WebPConstants.GammaTabSize + 1]; + public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + // Compute susceptibility based on DCT-coeff histograms: // the higher, the "easier" the macroblock is to compress. public static readonly int[] Vp8DspScan = @@ -51,7 +53,59 @@ namespace SixLabors.ImageSharp.Formats.WebP 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V }; - public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + // This table gives, for a given sharpness, the filtering strength to be + // used (at least) in order to filter a given edge step delta. + public static readonly byte[,] LevelsFromDelta = + { + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, + 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, + 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, + 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, + 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, + 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, + 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, + 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, + 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, + 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, + 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, + 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + } + }; public static readonly byte[] Norm = { From 8f92c467eb1eb0569979061bd1f7090e9d0c8cc5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Nov 2020 13:35:05 +0100 Subject: [PATCH 225/359] Fix issues with EncPredLuma4 and CodeIntraModes --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 19 ++++--- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 57 ++++++++++--------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 42 ++++++++------ 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 89cff2166..598a23e55 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -561,6 +561,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } + // Writes the partition #0 modes (that is: all intra modes) private void CodeIntraModes(Vp8BitWriter bitWriter) { var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.Mbw, this.enc.Mbh); @@ -569,7 +570,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter do { Vp8MacroBlockInfo mb = it.CurrentMacroBlockInfo; - Span preds = it.Preds.AsSpan(it.PredIdx); + int predIdx = it.PredIdx; + Span preds = it.Preds.AsSpan(predIdx); if (this.enc.SegmentHeader.UpdateMap) { bitWriter.PutSegment(mb.Segment, this.enc.Proba.Segments); @@ -587,19 +589,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } else { - Span topPred = it.Preds.AsSpan(it.PredIdx); - int x, y; - for (y = 0; y < 4; ++y) + Span topPred = it.Preds.AsSpan(predIdx - predsWidth); + for (int y = 0; y < 4; ++y) { - int left = preds[it.PredIdx - 1]; - for (x = 0; x < 4; ++x) + int left = it.Preds[predIdx - 1]; + for (int x = 0; x < 4; ++x) { byte[] probas = WebPLookupTables.ModesProba[topPred[x], left]; - left = bitWriter.PutI4Mode(preds[x], probas); + left = bitWriter.PutI4Mode(it.Preds[predIdx + x], probas); } - topPred = preds; - preds = preds.Slice(predsWidth); + topPred = it.Preds.AsSpan(predIdx); + predIdx += predsWidth; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index c6fec2b44..649b1705a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.WebP.Lossy { @@ -66,11 +64,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int I4RD4 = I4DC4 + 16; - private const int I4VR4 = I4RD4 + 20; + private const int I4VR4 = I4DC4 + 20; - private const int I4LD4 = I4RD4 + 24; + private const int I4LD4 = I4DC4 + 24; - private const int I4VL4 = I4RD4 + 28; + private const int I4VL4 = I4DC4 + 28; private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); @@ -797,14 +795,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // located at top[0..3], and top right is top[4..7] private void EncPredLuma4(Span dst, Span top, int topOffset) { - this.Dc4(dst, top, topOffset); + this.Dc4(dst.Slice(I4DC4), top, topOffset); this.Tm4(dst.Slice(I4TM4), top, topOffset); this.Ve4(dst.Slice(I4VE4), top, topOffset); this.He4(dst.Slice(I4HE4), top, topOffset); this.Rd4(dst.Slice(I4RD4), top, topOffset); this.Vr4(dst.Slice(I4VR4), top, topOffset); - this.Ld4(dst.Slice(I4LD4), top); - this.Vl4(dst.Slice(I4VL4), top); + this.Ld4(dst.Slice(I4LD4), top, topOffset); + this.Vl4(dst.Slice(I4VL4), top, topOffset); this.Hd4(dst.Slice(I4HD4), top, topOffset); this.Hu4(dst.Slice(I4HU4), top, topOffset); } @@ -989,7 +987,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy val = 0x01010101U * LossyUtils.Avg3(j, k, l); BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); } private void Rd4(Span dst, Span top, int topOffset) @@ -1062,16 +1060,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); } - private void Ld4(Span dst, Span top) + private void Ld4(Span dst, Span top, int topOffset) { - byte a = top[0]; - byte b = top[1]; - byte c = top[2]; - byte d = top[3]; - byte e = top[4]; - byte f = top[5]; - byte g = top[6]; - byte h = top[7]; + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); var bcd = LossyUtils.Avg3(b, c, d); @@ -1096,16 +1094,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); } - private void Vl4(Span dst, Span top) + private void Vl4(Span dst, Span top, int topOffset) { - byte a = top[0]; - byte b = top[1]; - byte c = top[2]; - byte d = top[3]; - byte e = top[4]; - byte f = top[5]; - byte g = top[6]; - byte h = top[7]; + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); var bc = LossyUtils.Avg2(b, c); @@ -1294,6 +1292,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YTop.AsSpan(0, topSize).Fill(127); this.UvTop.AsSpan().Fill(127); this.Nz.AsSpan().Fill(0); + + int predsW = (4 * this.mbw) + 1; + int predsH = (4 * this.mbh) + 1; + int predsSize = predsW * predsH; + this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Fill(0); } private int Bit(uint nz, int n) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 8972e270f..e92b2fb0a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; @@ -22,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly MemoryAllocator memoryAllocator; - /// - /// A bit writer for writing lossy webp streams. - /// - private readonly Vp8BitWriter bitWriter; - /// /// The quality, that will be used to encode the image. /// @@ -57,6 +53,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly int mbh; + /// + /// A bit writer for writing lossy webp streams. + /// + private Vp8BitWriter bitWriter; + /// /// The segment features. /// @@ -205,15 +206,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Nz.AsSpan().Fill(3452816845); this.ResetBoundaryPredictions(); - - // Initialize the bitwriter. - this.BaseQuant = 36; // TODO: hardCoded for now. - int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4]; - int expectedSize = this.mbw * this.mbh * averageBytesPerMacroBlock; - this.bitWriter = new Vp8BitWriter(expectedSize, this); } - public int BaseQuant { get; } + public int BaseQuant { get; set; } /// /// Gets the probabilities. @@ -378,6 +373,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.AssignSegments(alphas); this.SetLoopParams(this.quality); + // Initialize the bitwriter. + int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4]; + int expectedSize = this.mbw * this.mbh * averageBytesPerMacroBlock; + this.bitWriter = new Vp8BitWriter(expectedSize, this); + // TODO: EncodeAlpha(); // Stats-collection loop. this.StatLoop(width, height, yStride, uvStride); @@ -589,6 +589,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction; } + int predsW = (4 * this.mbw) + 1; + int predsH = (4 * this.mbh) + 1; + int predsSize = predsW * predsH; + this.Preds.AsSpan(predsSize + this.predsWidth - 4, 4).Fill(0); + this.Nz[0] = 0; // constant } @@ -740,6 +745,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy dqm[i].Quant = Clip(q, 0, 127); } + // Purely indicative in the bitstream (except for the 1-segment case). + this.BaseQuant = dqm[0].Quant; + // uvAlpha is normally spread around ~60. The useful range is // typically ~30 (quite bad) to ~100 (ok to decimate UV more). // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. @@ -1035,9 +1043,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } else { - // Reconstruct partial block inside yuv_out2 buffer + // Reconstruct partial block inside YuvOut2 buffer Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); - nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4, 16), src, tmpDst, bestI4Mode) << it.I4; + nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; } } while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); @@ -1111,7 +1119,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 4; ++x) { int ctx = it.TopNz[x] + it.LeftNz[y]; - residual.SetCoeffs(rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16)); + Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); + residual.SetCoeffs(coeffs); int res = this.bitWriter.PutCoeffs(ctx, residual); it.TopNz[x] = it.LeftNz[y] = res; } @@ -1176,7 +1185,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (x = 0; x < 4; ++x) { int ctx = it.TopNz[x] + it.LeftNz[y]; - residual.SetCoeffs(rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16)); + Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); + residual.SetCoeffs(coeffs); var res = residual.RecordCoeffs(ctx); it.TopNz[x] = res; it.LeftNz[y] = res; From dd7032c69355d895cd8ba9a83c4c545044a07b80 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Nov 2020 19:59:40 +0100 Subject: [PATCH 226/359] CorrectDCValues --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 28 +++++-- .../Formats/WebP/Lossy/Vp8Encoder.cs | 84 ++++++++++++++++--- tests/ImageSharp.Tests/TestImages.cs | 3 + tests/Images/Input/WebP/peak.png | 3 + 5 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 tests/Images/Input/WebP/peak.png diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 598a23e55..813fbb7bc 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter // Writes the partition #0 modes (that is: all intra modes) private void CodeIntraModes(Vp8BitWriter bitWriter) { - var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.Mbw, this.enc.Mbh); + var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.TopDerr, this.enc.Mbw, this.enc.Mbh); int predsWidth = this.enc.PredsWidth; do diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 649b1705a..7ddc2edd0 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -97,19 +97,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int uvTopIdx; - public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, int mbw, int mbh) + public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, sbyte[] topDerr, int mbw, int mbh) { + this.YTop = yTop; + this.UvTop = uvTop; + this.Nz = nz; + this.Mb = mb; + this.Preds = preds; + this.TopDerr = topDerr; + this.LeftDerr = new sbyte[2 * 2]; this.mbw = mbw; this.mbh = mbh; - this.Mb = mb; this.currentMbIdx = 0; this.nzIdx = 1; this.yTopIdx = 0; this.uvTopIdx = 0; - this.YTop = yTop; - this.UvTop = uvTop; - this.Nz = nz; - this.Preds = preds; this.predsWidth = (4 * mbw) + 1; this.predIdx = this.predsWidth; this.YuvIn = new byte[WebPConstants.Bps * 16]; @@ -180,6 +182,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public byte[] UvLeft { get; } + /// + /// Gets the left error diffusion (u/v). + /// + public sbyte[] LeftDerr { get; } + /// /// Gets the top luma samples at position 'X'. /// @@ -211,6 +218,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public uint[] Nz { get; } + /// + /// Gets the diffusion error. + /// + public sbyte[] TopDerr { get; } + /// /// Gets 32+5 boundary samples needed by intra4x4. /// @@ -1284,6 +1296,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy vLeft.Slice(1, 8).Fill(129); this.LeftNz[8] = 0; + + this.LeftDerr.AsSpan().Fill(0); } private void InitTop() @@ -1297,6 +1311,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int predsH = (4 * this.mbh) + 1; int predsSize = predsW * predsH; this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Fill(0); + + this.TopDerr.AsSpan().Fill(0); } private int Bit(uint nz, int n) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index e92b2fb0a..3853e56e4 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; @@ -142,6 +141,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: filterStrength is hardcoded, should be configurable. private const int FilterStrength = 60; + // Diffusion weights. We under-correct a bit (15/16th of the error is actually + // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. + private const int C1 = 7; // fraction of error sent to the 4x4 block below + private const int C2 = 8; // fraction of error sent to the 4x4 block on the right + private const int DSHIFT = 4; + private const int DSCALE = 1; // storage descaling, needed to make the error fit byte + /// /// Initializes a new instance of the class. /// @@ -175,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.UvTop = new byte[this.mbw * 16 * 2]; this.Nz = new uint[this.mbw + 1]; this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); - int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; + this.TopDerr = new sbyte[this.mbw * 4]; // TODO: make partition_limit configurable? int limit = 100; // original code: limit = 100 - config->partition_limit; @@ -196,6 +202,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } this.filterHeader = new Vp8FilterHeader(); + int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; this.proba = new Vp8EncProba(); this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; @@ -340,6 +347,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy ///
/// Header signaling the use of VP8 video format. diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index c04a3f242..022ded8aa 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -67,8 +67,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public Image Decode(Stream stream) where TPixel : struct, IPixel { - var metadata = new ImageMetadata(); - WebPMetadata webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); this.currentStream = stream; uint fileSize = this.ReadImageHeader(); @@ -298,11 +296,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // The next 3 bytes are the version. The version_number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. + // TODO: should we throw here when version number is != 0? uint version = bitReader.Read(3); - if (version != 0) - { - WebPThrowHelper.ThrowImageFormatException($"Unexpected webp version number: {version}"); - } // Next bit indicates, if a transformation is present. bool transformPresent = bitReader.ReadBit(); @@ -365,15 +360,15 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { - // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize - 10); // 10 bytes because of VP8X header. + // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. + this.currentStream.Skip(imageDataSize); // TODO: this does not seem to work in all cases } private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { - // TODO: implement decoding, for simulating the decoding, skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize - 10); // 10 bytes because of VP8X header. + // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. + this.currentStream.Skip(imageDataSize); // TODO: this does not seem to work in all cases } private void ReadExtended(Buffer2D pixels, int width, int height) @@ -403,9 +398,14 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private WebPChunkType ReadChunkType() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) - : throw new ImageFormatException("Invalid WebP data."); + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + this.webpMetadata.ChunkTypes.Enqueue(chunkType); + return chunkType; + } + + throw new ImageFormatException("Invalid WebP data."); } /// diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 88c687827..69e9a43f4 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections; +using System.Collections.Generic; + namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -33,6 +36,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public WebPFormatType Format { get; set; } + /// + /// All found chunk types ordered by appearance. + /// + public Queue ChunkTypes { get; set; } = new Queue(); + /// /// Indicates, if the webp file contains a animation. /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index f12ba3231..c0636cb19 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [InlineData(Lossless.Lossless2, 1000, 307, 24)] [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 24)] - //[InlineData(Animated.Animated1, 400, 400, 24)] + [InlineData(Animated.Animated1, 400, 400, 24)] public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); From 85c73017d9efff84a0de432d0f9fa58d279df229 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 25 Oct 2019 19:54:13 +0200 Subject: [PATCH 030/359] Move decoding of lossless into separate class --- .../Formats/WebP/WebPDecoderCore.cs | 54 +----------- .../Formats/WebP/WebPLosslessDecoder.cs | 84 +++++++++++++++++++ 2 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 022ded8aa..f705b44e9 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -299,55 +299,6 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: should we throw here when version number is != 0? uint version = bitReader.Read(3); - // Next bit indicates, if a transformation is present. - bool transformPresent = bitReader.ReadBit(); - int numberOfTransformsPresent = 0; - while (transformPresent) - { - var transformType = (WebPTransformType)bitReader.Read(2); - switch (transformType) - { - case WebPTransformType.SubtractGreen: - // There is no data associated with this transform. - break; - case WebPTransformType.ColorIndexingTransform: - // The transform data contains color table size and the entries in the color table. - // 8 bit value for color table size. - uint colorTableSize = bitReader.Read(8) + 1; - // TODO: color table should follow here? - break; - - case WebPTransformType.PredictorTransform: - { - // The first 3 bits of prediction data define the block width and height in number of bits. - // The number of block columns, block_xsize, is used in indexing two-dimensionally. - uint sizeBits = bitReader.Read(3) + 2; - int blockWidth = 1 << (int)sizeBits; - int blockHeight = 1 << (int)sizeBits; - - break; - } - - case WebPTransformType.ColorTransform: - { - // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, - // just like the predictor transform: - uint sizeBits = bitReader.Read(3) + 2; - int blockWidth = 1 << (int)sizeBits; - int blockHeight = 1 << (int)sizeBits; - break; - } - } - - numberOfTransformsPresent++; - if (numberOfTransformsPresent == 4) - { - break; - } - - transformPresent = bitReader.ReadBit(); - } - return new WebPImageInfo() { Width = (int)width, @@ -361,12 +312,15 @@ namespace SixLabors.ImageSharp.Formats.WebP where TPixel : struct, IPixel { // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize); // TODO: this does not seem to work in all cases + this.currentStream.Skip(imageDataSize - 10); } private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { + var losslessDecoder = new WebPLosslessDecoder(this.currentStream); + losslessDecoder.Decode(pixels, width, height, imageDataSize); + // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. this.currentStream.Skip(imageDataSize); // TODO: this does not seem to work in all cases } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs new file mode 100644 index 000000000..4545e5855 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Decoder for lossless webp images. + /// + internal sealed class WebPLosslessDecoder + { + private Vp8LBitReader bitReader; + + public WebPLosslessDecoder(Stream stream) + { + this.bitReader = new Vp8LBitReader(stream); + } + + public void Decode(Buffer2D pixels, int width, int height, int imageDataSize) + where TPixel : struct, IPixel + { + //ReadTransformations(); + } + + private void ReadTransformations() + { + // Next bit indicates, if a transformation is present. + bool transformPresent = bitReader.ReadBit(); + int numberOfTransformsPresent = 0; + while (transformPresent) + { + var transformType = (WebPTransformType)bitReader.Read(2); + switch (transformType) + { + case WebPTransformType.SubtractGreen: + // There is no data associated with this transform. + break; + case WebPTransformType.ColorIndexingTransform: + // The transform data contains color table size and the entries in the color table. + // 8 bit value for color table size. + uint colorTableSize = bitReader.Read(8) + 1; + + // TODO: color table should follow here? + break; + + case WebPTransformType.PredictorTransform: + { + // The first 3 bits of prediction data define the block width and height in number of bits. + // The number of block columns, block_xsize, is used in indexing two-dimensionally. + uint sizeBits = bitReader.Read(3) + 2; + int blockWidth = 1 << (int)sizeBits; + int blockHeight = 1 << (int)sizeBits; + + break; + } + + case WebPTransformType.ColorTransform: + { + // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, + // just like the predictor transform: + uint sizeBits = bitReader.Read(3) + 2; + int blockWidth = 1 << (int)sizeBits; + int blockHeight = 1 << (int)sizeBits; + break; + } + } + + numberOfTransformsPresent++; + if (numberOfTransformsPresent == 4) + { + break; + } + + transformPresent = bitReader.ReadBit(); + } + + // TODO: return transformation in an appropriate form. + } + } +} From 8a0f53e971ad43d3e908533d766fa431add63d25 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 25 Oct 2019 22:18:14 +0200 Subject: [PATCH 031/359] Add additional image features into separate class --- .../Formats/WebP/WebPDecoderCore.cs | 46 ++++++++++++++----- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 36 +++++++++++++++ src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 +- 3 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPFeatures.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f705b44e9..046e738e7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint fileSize = this.ReadImageHeader(); WebPImageInfo imageInfo = this.ReadVp8Info(); - if (imageInfo.IsAnimation) + if (imageInfo.Features != null && imageInfo.Features.Animation) { WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); } @@ -88,7 +88,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } // There can be optional chunks after the image data, like EXIF, XMP etc. - this.ParseOptionalChunks(); + if (imageInfo.Features != null) + { + this.ParseOptionalChunks(imageInfo.Features); + } return image; } @@ -201,7 +204,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { Width = width, Height = height, - IsAnimation = true + Features = new WebPFeatures() + { + Animation = true + } }; } @@ -212,6 +218,15 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Skip((int)alphaChunkSize); } + var features = new WebPFeatures() + { + Animation = isAnimationPresent, + Alpha = isAlphaPresent, + ExifProfile = isExifPresent, + IccProfile = isIccPresent, + XmpMetaData = isXmpPresent + }; + // A VP8 or VP8L chunk should follow here. chunkType = this.ReadChunkType(); @@ -219,9 +234,9 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (chunkType) { case WebPChunkType.Vp8: - return this.ReadVp8Header(); + return this.ReadVp8Header(features); case WebPChunkType.Vp8L: - return this.ReadVp8LHeader(); + return this.ReadVp8LHeader(features); } WebPThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); @@ -229,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo(); } - private WebPImageInfo ReadVp8Header() + private WebPImageInfo ReadVp8Header(WebPFeatures features = null) { this.webpMetadata.Format = WebPFormatType.Lossy; @@ -268,11 +283,12 @@ namespace SixLabors.ImageSharp.Formats.WebP Width = width, Height = height, IsLossLess = false, - ImageDataSize = dataSize + ImageDataSize = dataSize, + Features = features }; } - private WebPImageInfo ReadVp8LHeader() + private WebPImageInfo ReadVp8LHeader(WebPFeatures features = null) { this.webpMetadata.Format = WebPFormatType.Lossless; @@ -304,7 +320,8 @@ namespace SixLabors.ImageSharp.Formats.WebP Width = (int)width, Height = (int)height, IsLossLess = true, - ImageDataSize = dataSize + ImageDataSize = dataSize, + Features = features }; } @@ -312,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.WebP where TPixel : struct, IPixel { // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize - 10); + this.currentStream.Skip(imageDataSize - 10); // TODO: Not sure why we need to skip 10 bytes less here } private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) @@ -322,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.WebP losslessDecoder.Decode(pixels, width, height, imageDataSize); // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize); // TODO: this does not seem to work in all cases + this.currentStream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment. } private void ReadExtended(Buffer2D pixels, int width, int height) @@ -331,8 +348,13 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: implement decoding } - private void ParseOptionalChunks() + private void ParseOptionalChunks(WebPFeatures features) { + if (features.ExifProfile == false && features.XmpMetaData == false) + { + return; + } + while (this.currentStream.Position < this.currentStream.Length) { // Read chunk header. diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs new file mode 100644 index 000000000..653c8fa67 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Image features of a VP8X image. + /// + public class WebPFeatures + { + /// + /// Gets or sets whether this image has a ICC Profile. + /// + public bool IccProfile { get; set; } + + /// + /// Gets or sets whether this image has a alpha channel. + /// + public bool Alpha { get; set; } + + /// + /// Gets or sets whether this image has a EXIF Profile. + /// + public bool ExifProfile { get; set; } + + /// + /// Gets or sets whether this image has XMP Metadata. + /// + public bool XmpMetaData { get; set; } + + /// + /// Gets or sets whether this image is a animation. + /// + public bool Animation { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index b03244dc7..651c8d895 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -21,9 +21,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool IsLossLess { get; set; } /// - /// Gets or sets whether this image is a animation. + /// Gets or sets additional features present in a VP8X image. /// - public bool IsAnimation { get; set; } + public WebPFeatures Features { get; set; } /// /// The bytes of the image payload. From 5067bf472ed75389031aa8b225f7b7742228a88b Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sat, 26 Oct 2019 11:00:14 +0200 Subject: [PATCH 032/359] introduce WebPLossyDecoder introduce YUVPixel-Plane for the decoding process, start reading header of the bitstream: - Version, - flag if frame is shown (only relevant for animations or VP8 videos, but the bit is present) - size of the first partition. --- .../Formats/WebP/WebPDecoderCore.cs | 4 +- .../Formats/WebP/WebPLossyDecoder.cs | 70 +++++++++++++++++++ .../Formats/WebP/WebPDecoderTests.cs | 13 ++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 046e738e7..3d4c8a8c0 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -328,8 +328,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { - // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize - 10); // TODO: Not sure why we need to skip 10 bytes less here + var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream); + lossyDecoder.Decode(pixels, width, height, imageDataSize); } private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs new file mode 100644 index 000000000..294737a88 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + class WebPLossyDecoder + { + private readonly Configuration configuration; + + private readonly Stream currentStream; + + private MemoryAllocator memoryAllocator; + + public WebPLossyDecoder( + Configuration configuration, + Stream currentStream) + { + this.configuration = configuration; + this.currentStream = currentStream; + this.memoryAllocator = configuration.MemoryAllocator; + } + + public void Decode(Buffer2D pixels, int width, int height, int imageDataSize) + where TPixel : struct, IPixel + { + // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. + this.currentStream.Skip(imageDataSize - 10); // TODO: Not sure why we need to skip 10 bytes less here + + // we need buffers for Y U and V in size of the image + // TODO: increase size to enable using all prediction blocks? (see https://tools.ietf.org/html/rfc6386#page-9 ) + Buffer2D yuvBufferCurrentFrame = + this.memoryAllocator + .Allocate2D(width, height); + + // TODO: var predictionBuffer - macro-block-sized with approximation of the portion of the image being reconstructed. + // those prediction values are the base, the values from DCT processing are added to that + + // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V + // TODO weiter bei S.11 + + // bit STREAM: See https://tools.ietf.org/html/rfc6386#page-29 ("Frame Header") + Vp8LBitReader bitReader = new Vp8LBitReader(this.currentStream); + bool isInterframe = bitReader.ReadBit(); + if (isInterframe) + { + throw new NotImplementedException("only key frames supported yet"); + } + + byte version = (byte)((bitReader.ReadBit() ? 2 : 0) | (bitReader.ReadBit() ? 1 : 0)); + bool isShowFrame = bitReader.ReadBit(); + + uint firstPartitionSize = (bitReader.Read(16) << 3) | bitReader.Read(3); + } + } + + struct YUVPixel + { + public byte Y { get; } + + public byte U { get; } + + public byte V { get; } + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index c0636cb19..3db121bad 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -30,5 +30,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); } } + + [Theory] + [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] + public void DecodeLossyImage_Tmp(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var image = Image.Load(stream); + Assert.Equal(expectedWidth, image.Width); + Assert.Equal(expectedHeight, image.Height); + } + } } } From d7689c1727436b0589299ca19bdf28b42d4f37d1 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Mon, 28 Oct 2019 19:37:34 +0100 Subject: [PATCH 033/359] Decode verison of lossy bitstream to determine Reconstruction- and Loopfilters --- .../Formats/WebP/WebPLossyDecoder.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 294737a88..f09be2faf 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -53,10 +53,47 @@ namespace SixLabors.ImageSharp.Formats.WebP } byte version = (byte)((bitReader.ReadBit() ? 2 : 0) | (bitReader.ReadBit() ? 1 : 0)); + (ReconstructionFilter rec, LoopFilter loop) = DecodeVersion(version); + bool isShowFrame = bitReader.ReadBit(); uint firstPartitionSize = (bitReader.Read(16) << 3) | bitReader.Read(3); } + + private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version) + { + var rec = ReconstructionFilter.None; + var loop = LoopFilter.None; + + switch (version) + { + case 0: + return (ReconstructionFilter.Bicubic, LoopFilter.Normal); + case 1: + return (ReconstructionFilter.Bilinear, LoopFilter.Simple); + case 2: + return (ReconstructionFilter.Bilinear, LoopFilter.None); + case 3: + return (ReconstructionFilter.None, LoopFilter.None); + default: + // https://tools.ietf.org/html/rfc6386#page-30 + throw new NotSupportedException("reserved for future use in Spec"); + } + } + } + + enum ReconstructionFilter + { + None, + Bicubic, + Bilinear + } + + enum LoopFilter + { + Normal, + Simple, + None } struct YUVPixel From 6678b04a31c4e5528051e38bb1323c21a1cc7980 Mon Sep 17 00:00:00 2001 From: Lajos Marton Date: Mon, 28 Oct 2019 22:08:45 +0100 Subject: [PATCH 034/359] More animeted webp test images added --- ImageSharp.sln | 3 +++ tests/ImageSharp.Tests/TestImages.cs | 3 +++ tests/Images/Input/WebP/animated2.webp | 3 +++ tests/Images/Input/WebP/animated3.webp | 3 +++ tests/Images/Input/WebP/animated_lossy.webp | 3 +++ 5 files changed, 15 insertions(+) create mode 100644 tests/Images/Input/WebP/animated2.webp create mode 100644 tests/Images/Input/WebP/animated3.webp create mode 100644 tests/Images/Input/WebP/animated_lossy.webp diff --git a/ImageSharp.sln b/ImageSharp.sln index d6982ee25..09adb3eac 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -372,6 +372,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 tests\Images\Input\WebP\alpha_filter_3_method_1.webp = tests\Images\Input\WebP\alpha_filter_3_method_1.webp tests\Images\Input\WebP\alpha_no_compression.webp = tests\Images\Input\WebP\alpha_no_compression.webp tests\Images\Input\WebP\animated-webp.webp = tests\Images\Input\WebP\animated-webp.webp + tests\Images\Input\WebP\animated2.webp = tests\Images\Input\WebP\animated2.webp + tests\Images\Input\WebP\animated3.webp = tests\Images\Input\WebP\animated3.webp + tests\Images\Input\WebP\animated_lossy.webp = tests\Images\Input\WebP\animated_lossy.webp tests\Images\Input\WebP\bad_palette_index.webp = tests\Images\Input\WebP\bad_palette_index.webp tests\Images\Input\WebP\big_endian_bug_393.webp = tests\Images\Input\WebP\big_endian_bug_393.webp tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 541bc0063..b8598e82e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -371,6 +371,9 @@ namespace SixLabors.ImageSharp.Tests public static class Animated { public const string Animated1 = "WebP/animated-webp.webp"; + public const string Animated2 = "WebP/animated2.webp"; + public const string Animated3 = "WebP/animated3.webp"; + public const string Animated4 = "WebP/animated_lossy.webp"; } public static class Lossless diff --git a/tests/Images/Input/WebP/animated2.webp b/tests/Images/Input/WebP/animated2.webp new file mode 100644 index 000000000..aa08cae87 --- /dev/null +++ b/tests/Images/Input/WebP/animated2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b17cfa1c0f484f1fc03f16d07684831585125817e5c7fb2c12cfed3d6ad863a8 +size 11840 diff --git a/tests/Images/Input/WebP/animated3.webp b/tests/Images/Input/WebP/animated3.webp new file mode 100644 index 000000000..98d4c4114 --- /dev/null +++ b/tests/Images/Input/WebP/animated3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68ba327459ac40a7a054dc5d8b237d3ce0154524854a4f2334e3b839524d13a9 +size 41063 diff --git a/tests/Images/Input/WebP/animated_lossy.webp b/tests/Images/Input/WebP/animated_lossy.webp new file mode 100644 index 000000000..654c2d03f --- /dev/null +++ b/tests/Images/Input/WebP/animated_lossy.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54957c3daa3ab0bf258c00b170fcfc0578d909acd5dfc870b752688b9b64e406 +size 73772 From 7ec964e3b5a1414b2a40d427dd6e6aa35d9775e3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Oct 2019 19:09:22 +0100 Subject: [PATCH 035/359] Fix Bitreader issue: offset was not initialized correctly --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index b801a4c33..bf21a6282 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8LBitReader(Stream inputStream) { this.stream = inputStream; - this.Offset = 0; + this.Offset = inputStream.Position; this.Bit = 0; } From 89d8d50a268b9404ec21d548b279d605ad77a0b4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 30 Oct 2019 19:10:30 +0100 Subject: [PATCH 036/359] Start with parsing huffman codes --- src/ImageSharp/Formats/WebP/HuffmanCode.cs | 18 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 55 ++++- .../Formats/WebP/WebPDecoderCore.cs | 23 +- .../Formats/WebP/WebPLosslessDecoder.cs | 232 +++++++++++++++++- 4 files changed, 289 insertions(+), 39 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/HuffmanCode.cs diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs new file mode 100644 index 000000000..a5511335d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class HuffmanCode + { + /// + /// Gets or sets the number of bits used for this symbol. + /// + public int BitsUsed { get; set; } + + /// + /// Gets or sets the symbol value or table offset. + /// + public int Value { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index d1c451799..7bd1f559b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -27,16 +27,6 @@ namespace SixLabors.ImageSharp.Formats.WebP 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. /// @@ -58,5 +48,50 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x42, // B 0x50 // P }; + + /// + /// Signature byte which identifies a VP8L header. + /// + public static byte Vp8LMagicByte = 0x2F; + + /// + /// 3 bits reserved for version. + /// + public static int Vp8LVersionBits = 3; + + /// + /// Bits for width and height infos of a VPL8 image. + /// + public static int Vp8LImageSizeBits = 14; + + /// + /// Maximum number of color cache bits. + /// + public static int MaxColorCacheBits = 11; + + /// + /// The maximum number of allowed transforms in a bitstream. + /// + public static int MaxNumberOfTransforms = 4; + + public static int MaxAllowedCodeLength = 15; + + public static int HuffmanCodesPerMetaCode = 5; + + public static int NumLiteralCodes = 256; + + public static int NumLengthCodes = 24; + + public static int NumDistanceCodes = 40; + + public static int NumCodeLengthCodes = 19; + + public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + public static int[] kAlphabetSize = { + NumLiteralCodes + NumLengthCodes, + NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, + NumDistanceCodes + }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 3d4c8a8c0..70cfe6ac7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -80,11 +80,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - ReadSimpleLossless(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); + var losslessDecoder = new WebPLosslessDecoder(this.currentStream, (int)imageInfo.ImageDataSize); + losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - ReadSimpleLossy(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); + var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream); + lossyDecoder.Decode(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); } // There can be optional chunks after the image data, like EXIF, XMP etc. @@ -310,10 +312,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. bool alphaIsUsed = bitReader.ReadBit(); - // The next 3 bytes are the version. The version_number is a 3 bit code that must be set to 0. + // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. // TODO: should we throw here when version number is != 0? - uint version = bitReader.Read(3); + uint version = bitReader.Read(WebPConstants.Vp8LVersionBits); return new WebPImageInfo() { @@ -328,18 +330,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) where TPixel : struct, IPixel { - var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream); - lossyDecoder.Decode(pixels, width, height, imageDataSize); - } - - private void ReadSimpleLossless(Buffer2D pixels, int width, int height, int imageDataSize) - where TPixel : struct, IPixel - { - var losslessDecoder = new WebPLosslessDecoder(this.currentStream); - losslessDecoder.Decode(pixels, width, height, imageDataSize); - - // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment. + } private void ReadExtended(Buffer2D pixels, int width, int height) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 4545e5855..7a2c55b37 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Memory; @@ -11,29 +12,226 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Decoder for lossless webp images. /// + /// + /// The lossless specification can be found here: + /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification + /// internal sealed class WebPLosslessDecoder { - private Vp8LBitReader bitReader; + private readonly Vp8LBitReader bitReader; - public WebPLosslessDecoder(Stream stream) + private readonly int imageDataSize; + + public WebPLosslessDecoder(Stream stream, int imageDataSize) { this.bitReader = new Vp8LBitReader(stream); + this.imageDataSize = imageDataSize; + + // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. + //stream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment. } - public void Decode(Buffer2D pixels, int width, int height, int imageDataSize) + public void Decode(Buffer2D pixels, int width, int height) where TPixel : struct, IPixel { - //ReadTransformations(); + this.ReadTransformations(); + int xsize = 0, ysize = 0; + this.ReadHuffmanCodes(xsize, ysize); + } + + private void ReadHuffmanCodes(int xsize, int ysize) + { + int maxAlphabetSize = 0; + int colorCacheBits = 0; + int numHtreeGroups = 1; + int numHtreeGroupsMax = 1; + + // Read color cache, if present. + bool colorCachePresent = this.bitReader.ReadBit(); + if (colorCachePresent) + { + colorCacheBits = (int)this.bitReader.Read(4); + int colorCacheSize = 1 << colorCacheBits; + if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) + { + WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); + } + } + + // Read the Huffman codes. + // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. + // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. + bool isEntropyImage = this.bitReader.ReadBit(); + if (isEntropyImage) + { + uint huffmanPrecision = this.bitReader.Read(3) + 2; + int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision); + int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); + + // TODO: decode entropy image + return; + } + + // Find maximum alphabet size for the htree group. + for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) + { + int alphabetSize = WebPConstants.kAlphabetSize[j]; + if (j == 0 && colorCacheBits > 0) + { + alphabetSize += 1 << colorCacheBits; + } + + if (maxAlphabetSize < alphabetSize) + { + maxAlphabetSize = alphabetSize; + } + } + + for (int i = 0; i < numHtreeGroupsMax; i++) + { + int size; + int totalSize = 0; + int isTrivialLiteral = 1; + int maxBits = 0; + var codeLengths = new int[maxAlphabetSize]; + for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) + { + int alphabetSize = WebPConstants.kAlphabetSize[j]; + if (j == 0 && colorCacheBits > 0) + { + alphabetSize += 1 << colorCacheBits; + size = this.ReadHuffmanCode(alphabetSize, codeLengths); + if (size is 0) + { + WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + } + } + } + } + } + + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths) + { + bool simpleCode = this.bitReader.ReadBit(); + if (simpleCode) + { + // (i) Simple Code Length Code. + // This variant is used in the special case when only 1 or 2 Huffman code lengths are non - zero, + // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros. + + // Read symbols, codes & code lengths directly. + uint numSymbols = this.bitReader.Read(1) + 1; + uint firstSymbolLenCode = this.bitReader.Read(1); + + // The first code is either 1 bit or 8 bit code. + uint symbol = this.bitReader.Read((firstSymbolLenCode == 0) ? 1 : 8); + codeLengths[symbol] = 1; + + // The second code (if present), is always 8 bit long. + if (numSymbols == 2) + { + symbol = this.bitReader.Read(8); + codeLengths[symbol] = 1; + } + } + else + { + // (ii)Normal Code Length Code: + // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; + // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. + var codeLengthCodeLengths = new int[WebPConstants.NumLengthCodes]; + uint numCodes = this.bitReader.Read(4) + 4; + if (numCodes > WebPConstants.NumCodeLengthCodes) + { + WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); + } + + for (int i = 0; i < numCodes; i++) + { + codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); + } + + // TODO: ReadHuffmanCodeLengths + } + + int size = 0; + // TODO: VP8LBuildHuffmanTable + + return size; + } + + private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize, + int[] sorted) // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + { + // total size root table + 2nd level table + int totalSize = 1 << rootBits; + // current code length + int len; + // symbol index in original or sorted table + int symbol; + // number of codes of each length: + var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; + // offsets in sorted table for each length + var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; + + // Build histogram of code lengths. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) + { + if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) + { + return 0; + } + + ++count[codeLengths[symbol]]; + } + + // Generate offsets into sorted symbol table by code length. + offset[1] = 0; + for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) + { + if (count[len] > (1 << len)) + { + return 0; + } + + offset[len + 1] = offset[len] + count[len]; + } + + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) + { + int symbolCodeLength = codeLengths[symbol]; + if (codeLengths[symbol] > 0) + { + sorted[offset[symbolCodeLength]++] = symbol; + } + } + + // Special case code with only one value. + if (offset[WebPConstants.MaxAllowedCodeLength] is 1) + { + var huffmanCode = new HuffmanCode() + { + BitsUsed = 0, + Value = sorted[0] + }; + + return totalSize; + } + + return 0; } private void ReadTransformations() { // Next bit indicates, if a transformation is present. - bool transformPresent = bitReader.ReadBit(); + bool transformPresent = this.bitReader.ReadBit(); int numberOfTransformsPresent = 0; + var transforms = new List(WebPConstants.MaxNumberOfTransforms); while (transformPresent) { - var transformType = (WebPTransformType)bitReader.Read(2); + var transformType = (WebPTransformType)this.bitReader.Read(2); + transforms.Add(transformType); switch (transformType) { case WebPTransformType.SubtractGreen: @@ -42,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.WebP case WebPTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. - uint colorTableSize = bitReader.Read(8) + 1; + uint colorTableSize = this.bitReader.Read(8) + 1; // TODO: color table should follow here? break; @@ -51,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of prediction data define the block width and height in number of bits. // The number of block columns, block_xsize, is used in indexing two-dimensionally. - uint sizeBits = bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.Read(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; @@ -62,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, // just like the predictor transform: - uint sizeBits = bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.Read(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; break; @@ -70,15 +268,23 @@ namespace SixLabors.ImageSharp.Formats.WebP } numberOfTransformsPresent++; - if (numberOfTransformsPresent == 4) + + transformPresent = this.bitReader.ReadBit(); + if (numberOfTransformsPresent == WebPConstants.MaxNumberOfTransforms && transformPresent) { - break; + WebPThrowHelper.ThrowImageFormatException("The maximum number of transforms was exceeded"); } - - transformPresent = bitReader.ReadBit(); } // TODO: return transformation in an appropriate form. } + + /// + /// Computes sampled size of 'size' when sampling using 'sampling bits'. + /// + private int SubSampleSize(int size, int samplingBits) + { + return (size + (1 << samplingBits) - 1) >> samplingBits; + } } } From e4572a8195be44f1710417d3800c0399270fd61d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 1 Nov 2019 21:20:28 +0100 Subject: [PATCH 037/359] Add ReadHuffmanCodeLengths --- src/ImageSharp/Formats/WebP/WebPConstants.cs | 12 +++ .../Formats/WebP/WebPLosslessDecoder.cs | 97 ++++++++++++++++++- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 7bd1f559b..a2930171d 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -76,6 +76,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int MaxAllowedCodeLength = 15; + public static int DefaultCodeLength = 8; + public static int HuffmanCodesPerMetaCode = 5; public static int NumLiteralCodes = 256; @@ -86,6 +88,16 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int NumCodeLengthCodes = 19; + public static int LengthTableBits = 7; + + public static int kCodeLengthLiterals = 16; + + public static int kCodeLengthRepeatCode = 16; + + public static int[] kCodeLengthExtraBits = { 2, 3, 7 }; + + public static int[] kCodeLengthRepeatOffsets = { 3, 3, 11 }; + public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; public static int[] kAlphabetSize = { diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 7a2c55b37..2b90ee060 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -101,10 +101,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { alphabetSize += 1 << colorCacheBits; size = this.ReadHuffmanCode(alphabetSize, codeLengths); - if (size is 0) + /*if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); - } + }*/ } } } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); } - // TODO: ReadHuffmanCodeLengths + this.ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths); } int size = 0; @@ -160,9 +160,66 @@ namespace SixLabors.ImageSharp.Formats.WebP return size; } - private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize, - int[] sorted) // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + private void ReadHuffmanCodeLengths(int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + { + int maxSymbol; + int symbol = 0; + int prevCodeLen = WebPConstants.DefaultCodeLength; + BuildHuffmanTable(WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + if (this.bitReader.ReadBit()) + { + int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); + maxSymbol = 2 + (int)this.bitReader.Read(lengthNBits); + } + else + { + maxSymbol = numSymbols; + } + + while (symbol < numSymbols) + { + int codeLen; + if (maxSymbol-- == 0) + { + break; + } + + codeLen = int.MaxValue; // TODO: this is wrong + if (codeLen < WebPConstants.kCodeLengthLiterals) + { + codeLengths[symbol++] = codeLen; + if (codeLen != 0) + { + prevCodeLen = codeLen; + } + } + else + { + bool usePrev = codeLen == WebPConstants.kCodeLengthRepeatCode; + int slot = codeLen - WebPConstants.kCodeLengthLiterals; + int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; + int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; + int repeat = (int)(this.bitReader.Read(extraBits) + repeatOffset); + if (symbol + repeat > numSymbols) + { + return; + } + else + { + int length = usePrev ? prevCodeLen : 0; + while (repeat-- > 0) + { + codeLengths[symbol++] = length; + } + } + } + } + } + + private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize) { + // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + var sorted = new int[codeLengthsSize]; // total size root table + 2nd level table int totalSize = 1 << rootBits; // current code length @@ -219,6 +276,36 @@ namespace SixLabors.ImageSharp.Formats.WebP return totalSize; } + int step; // step size to replicate values in current table + int low = -1; // low bits for current root entry + int mask = totalSize - 1; // mask for low bits + int key = 0; // reversed prefix code + int numNodes = 1; // number of Huffman tree nodes + int numOpen = 1; // number of open branches in current tree level + int tableBits = rootBits; // key length of current table + int tableSize = 1 << tableBits; // size of current table + symbol = 0; + // Fill in root table. + for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) + { + numOpen <<= 1; + numNodes += numOpen; + numOpen -= count[len]; + if (numOpen < 0) + { + return 0; + } + + for (; count[len] > 0; --count[len]) + { + var code = new HuffmanCode() + { + BitsUsed = len, + Value = sorted[symbol++] + }; + } + } + return 0; } From a8a39e6d10409a5d091b996bb5662061642a0e16 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 3 Nov 2019 18:21:23 +0100 Subject: [PATCH 038/359] Move huffman code into separate class, introduce HTreeGroup --- src/ImageSharp/Formats/WebP/HTreeGroup.cs | 49 ++++++ src/ImageSharp/Formats/WebP/HuffmanCode.cs | 3 + src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 146 ++++++++++++++++++ .../Formats/WebP/WebPLosslessDecoder.cs | 120 +++----------- 4 files changed, 216 insertions(+), 102 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/HTreeGroup.cs create mode 100644 src/ImageSharp/Formats/WebP/HuffmanUtils.cs diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs new file mode 100644 index 000000000..d3c21e690 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Huffman table group. + /// Includes special handling for the following cases: + /// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN) + /// - is_trivial_code: only 1 code (no bit is read from bitstream) + /// - use_packed_table: few enough literal symbols, so all the bit codes + /// can fit into a small look-up table packed_table[] + /// The common literal base, if applicable, is stored in 'literal_arb'. + /// + internal class HTreeGroup + { + /// + /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys. + /// + public List HTree { get; set; } + + /// + /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). + /// + public bool IsTrivialLiteral { get; set; } + + /// + /// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero. + /// + public int LiteralArb { get; set; } + + /// + /// True if is_trivial_literal with only one code. + /// + public bool IsTrivialCode { get; set; } + + /// + /// use packed table below for short literal code + /// + public bool UsePackedTable { get; set; } + + /// + /// Table mapping input bits to a packed values, or escape case to literal code. + /// + public HuffmanCode PackedTable { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs index a5511335d..b3133786c 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP { + [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] internal class HuffmanCode { /// diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs new file mode 100644 index 000000000..a32dceff4 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal static class HuffmanUtils + { + public static List BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) + { + Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); + Guard.NotNull(codeLengths, nameof(codeLengths)); + Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); + + // TODO: not sure yet howto store the codes properly + var huffmanCodes = new List(); + + // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + var sorted = new int[codeLengthsSize]; + // total size root table + 2nd level table + int totalSize = 1 << rootBits; + // current code length + int len; + // symbol index in original or sorted table + int symbol; + // number of codes of each length: + var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; + // offsets in sorted table for each length + var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; + + // Build histogram of code lengths. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) + { + if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) + { + return huffmanCodes; + } + + ++count[codeLengths[symbol]]; + } + + // Generate offsets into sorted symbol table by code length. + offset[1] = 0; + for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) + { + if (count[len] > (1 << len)) + { + return huffmanCodes; + } + + offset[len + 1] = offset[len] + count[len]; + } + + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) + { + int symbolCodeLength = codeLengths[symbol]; + if (codeLengths[symbol] > 0) + { + sorted[offset[symbolCodeLength]++] = symbol; + } + } + + // Special case code with only one value. + if (offset[WebPConstants.MaxAllowedCodeLength] is 1) + { + var huffmanCode = new HuffmanCode() + { + BitsUsed = 0, + Value = sorted[0] + }; + huffmanCodes.Add(huffmanCode); + + return huffmanCodes; + } + + int step; // step size to replicate values in current table + int low = -1; // low bits for current root entry + int mask = totalSize - 1; // mask for low bits + int key = 0; // reversed prefix code + int numNodes = 1; // number of Huffman tree nodes + int numOpen = 1; // number of open branches in current tree level + int tableBits = rootBits; // key length of current table + int tableSize = 1 << tableBits; // size of current table + symbol = 0; + // Fill in root table. + for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) + { + numOpen <<= 1; + numNodes += numOpen; + numOpen -= count[len]; + if (numOpen < 0) + { + return huffmanCodes; + } + + for (; count[len] > 0; --count[len]) + { + var huffmanCode = new HuffmanCode() + { + BitsUsed = len, + Value = sorted[symbol++] + }; + huffmanCodes.Add(huffmanCode); + ReplicateValue(table, step, tableSize, huffmanCode); + key = GetNextKey(key, len); + } + } + + return huffmanCodes; + } + + /// + /// Stores code in table[0], table[step], table[2*step], ..., table[end]. + /// Assumes that end is an integer multiple of step. + /// + private static void ReplicateValue(HuffmanCode[] table, int step, int end, HuffmanCode code) + { + Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); + + do + { + end -= step; + table[end] = code; + } + while (end > 0); + } + + /// + /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the + /// bit-wise reversal of the len least significant bits of key. + /// + private static int GetNextKey(int key, int len) + { + int step = 1 << (len - 1); + while ((key & step) != 0) + { + step >>= 1; + } + + return step != 0 ? (key & (step - 1)) + step : key; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 2b90ee060..282b64385 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; @@ -87,6 +88,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + // TODO: not sure about the correct tabelSize here. Harcoded for now. + //int tableSize = kTableSize[colorCacheBits]; + int tableSize = 2970; + var table = new HuffmanCode[numHtreeGroups * tableSize]; for (int i = 0; i < numHtreeGroupsMax; i++) { int size; @@ -99,18 +104,22 @@ namespace SixLabors.ImageSharp.Formats.WebP int alphabetSize = WebPConstants.kAlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { - alphabetSize += 1 << colorCacheBits; - size = this.ReadHuffmanCode(alphabetSize, codeLengths); - /*if (size is 0) + if (j == 0 && colorCacheBits > 0) + { + alphabetSize += 1 << colorCacheBits; + } + + size = this.ReadHuffmanCode(alphabetSize, codeLengths, table); + if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); - }*/ + } } } } } - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths) + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, HuffmanCode[] table) { bool simpleCode = this.bitReader.ReadBit(); if (simpleCode) @@ -139,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // (ii)Normal Code Length Code: // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - var codeLengthCodeLengths = new int[WebPConstants.NumLengthCodes]; + var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; uint numCodes = this.bitReader.Read(4) + 4; if (numCodes > WebPConstants.NumCodeLengthCodes) { @@ -151,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); } - this.ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); } int size = 0; @@ -160,12 +169,12 @@ namespace SixLabors.ImageSharp.Formats.WebP return size; } - private void ReadHuffmanCodeLengths(int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; - BuildHuffmanTable(WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); if (this.bitReader.ReadBit()) { int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); @@ -216,99 +225,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize) - { - // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. - var sorted = new int[codeLengthsSize]; - // total size root table + 2nd level table - int totalSize = 1 << rootBits; - // current code length - int len; - // symbol index in original or sorted table - int symbol; - // number of codes of each length: - var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; - // offsets in sorted table for each length - var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; - - // Build histogram of code lengths. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) - { - if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) - { - return 0; - } - - ++count[codeLengths[symbol]]; - } - - // Generate offsets into sorted symbol table by code length. - offset[1] = 0; - for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) - { - if (count[len] > (1 << len)) - { - return 0; - } - - offset[len + 1] = offset[len] + count[len]; - } - - // Sort symbols by length, by symbol order within each length. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) - { - int symbolCodeLength = codeLengths[symbol]; - if (codeLengths[symbol] > 0) - { - sorted[offset[symbolCodeLength]++] = symbol; - } - } - - // Special case code with only one value. - if (offset[WebPConstants.MaxAllowedCodeLength] is 1) - { - var huffmanCode = new HuffmanCode() - { - BitsUsed = 0, - Value = sorted[0] - }; - - return totalSize; - } - - int step; // step size to replicate values in current table - int low = -1; // low bits for current root entry - int mask = totalSize - 1; // mask for low bits - int key = 0; // reversed prefix code - int numNodes = 1; // number of Huffman tree nodes - int numOpen = 1; // number of open branches in current tree level - int tableBits = rootBits; // key length of current table - int tableSize = 1 << tableBits; // size of current table - symbol = 0; - // Fill in root table. - for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) - { - numOpen <<= 1; - numNodes += numOpen; - numOpen -= count[len]; - if (numOpen < 0) - { - return 0; - } - - for (; count[len] > 0; --count[len]) - { - var code = new HuffmanCode() - { - BitsUsed = len, - Value = sorted[symbol++] - }; - } - } - - return 0; - } - private void ReadTransformations() { // Next bit indicates, if a transformation is present. From 887cf62edf62532ff4a45d6874897229a3b3d58e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 4 Nov 2019 19:39:30 +0100 Subject: [PATCH 039/359] Continue with BuildHuffmanTable --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 82 +++++++++++++++---- .../Formats/WebP/WebPLosslessDecoder.cs | 7 +- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index a32dceff4..9e9f1d7a7 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -2,21 +2,17 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP { internal static class HuffmanUtils { - public static List BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) + public static int BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); Guard.NotNull(codeLengths, nameof(codeLengths)); Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); - // TODO: not sure yet howto store the codes properly - var huffmanCodes = new List(); - // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. var sorted = new int[codeLengthsSize]; // total size root table + 2nd level table @@ -35,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) { - return huffmanCodes; + return 0; } ++count[codeLengths[symbol]]; @@ -47,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (count[len] > (1 << len)) { - return huffmanCodes; + return 0; } offset[len + 1] = offset[len] + count[len]; @@ -71,9 +67,8 @@ namespace SixLabors.ImageSharp.Formats.WebP BitsUsed = 0, Value = sorted[0] }; - huffmanCodes.Add(huffmanCode); - - return huffmanCodes; + ReplicateValue(table, 1, totalSize, huffmanCode); + return totalSize; } int step; // step size to replicate values in current table @@ -93,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.WebP numOpen -= count[len]; if (numOpen < 0) { - return huffmanCodes; + return 0; } for (; count[len] > 0; --count[len]) @@ -103,20 +98,77 @@ namespace SixLabors.ImageSharp.Formats.WebP BitsUsed = len, Value = sorted[symbol++] }; - huffmanCodes.Add(huffmanCode); - ReplicateValue(table, step, tableSize, huffmanCode); + ReplicateValue(table.AsSpan(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); } } - return huffmanCodes; + // Fill in 2nd level tables and add pointers to root table. + for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1) + { + numOpen <<= 1; + numNodes += numOpen; + numOpen -= count[len]; + if (numOpen < 0) + { + return 0; + } + + Span tableSpan = table.AsSpan(); + for (; count[len] > 0; --count[len]) + { + if ((key & mask) != low) + { + tableSpan = tableSpan.Slice(tableSize); + tableBits = NextTableBitSize(count, len, rootBits); + tableSize = 1 << tableBits; + totalSize += tableSize; + low = key & mask; + // TODO: fix this + //rootTable[low].bits = (tableBits + rootBits); + //rootTable[low].value = ((table - rootTable) - low); + } + + var huffmanCode = new HuffmanCode + { + BitsUsed = len - rootBits, + Value = sorted[symbol++] + }; + ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode); + key = GetNextKey(key, len); + } + } + + return totalSize; + } + + /// + /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, + /// len is the code length of the next processed symbol. + /// + private static int NextTableBitSize(int[] count, int len, int rootBits) + { + int left = 1 << (len - rootBits); + while (len < WebPConstants.MaxAllowedCodeLength) + { + left -= count[len]; + if (left <= 0) + { + break; + } + + ++len; + left <<= 1; + } + + return len - rootBits; } /// /// Stores code in table[0], table[step], table[2*step], ..., table[end]. /// Assumes that end is an integer multiple of step. /// - private static void ReplicateValue(HuffmanCode[] table, int step, int end, HuffmanCode code) + private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) { Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 282b64385..a24e78463 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -174,7 +174,12 @@ namespace SixLabors.ImageSharp.Formats.WebP int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; - HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + if (size is 0) + { + WebPThrowHelper.ThrowImageFormatException("Error building huffman table"); + } + if (this.bitReader.ReadBit()) { int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); From 05d119346dc609fd31db9387ade13021b0923fb7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 15 Nov 2019 19:55:35 +0100 Subject: [PATCH 040/359] Overhaul bitmap reader to be similar to libwebp bitreader --- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 57 ++++++ src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 179 +++++++++++++----- .../Formats/WebP/WebPDecoderCore.cs | 27 +-- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 + .../Formats/WebP/WebPLosslessDecoder.cs | 42 ++-- .../Formats/WebP/WebPLossyDecoder.cs | 2 +- 6 files changed, 228 insertions(+), 83 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8BitReader.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs new file mode 100644 index 000000000..bdb1d0e03 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8BitReader + { + /// + /// Current value. + /// + private long value; + + /// + /// Current range minus 1. In [127, 254] interval. + /// + private int range; + + /// + /// Number of valid bits left. + /// + private int bits; + + /// + /// The next byte to be read. + /// + private byte buf; + + /// + /// End of read buffer. + /// + private byte bufEnd; + + /// + /// Max packed-read position on buffer. + /// + private byte bufMax; + + /// + /// True if input is exhausted. + /// + private bool eof; + + /// + /// Reads the specified number of bits from read buffer. + /// Flags an error in case end_of_stream or n_bits is more than the allowed limit + /// of VP8L_MAX_NUM_BIT_READ (inclusive). + /// Flags eos_ if this read attempt is going to cross the read buffer. + /// + /// The number of bits to read. + public int ReadBits(int nBits) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index bf21a6282..9bcad284c 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; namespace SixLabors.ImageSharp.Formats.WebP @@ -8,9 +9,35 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// A bit reader for VP8 streams. /// - public class Vp8LBitReader + internal class Vp8LBitReader { - private readonly Stream stream; + /// + /// Maximum number of bits (inclusive) the bit-reader can handle. + /// + private const int VP8L_MAX_NUM_BIT_READ = 24; + + /// + /// Number of bits prefetched (= bit-size of vp8l_val_t). + /// + private const int VP8L_LBITS = 64; + + /// + /// Minimum number of bytes ready after VP8LFillBitWindow. + /// + private const int VP8L_WBITS = 32; + + private uint[] kBitMask = + { + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff + }; + + private readonly byte[] data; /// /// Initializes a new instance of the class. @@ -18,75 +45,137 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The input stream to read from. public Vp8LBitReader(Stream inputStream) { - this.stream = inputStream; - this.Offset = inputStream.Position; - this.Bit = 0; - } + long length = inputStream.Length - inputStream.Position; - private long Offset { get; set; } + using (var ms = new MemoryStream()) + { + inputStream.CopyTo(ms); + this.data = ms.ToArray(); + } - private int Bit { get; set; } + this.len = length; + this.value = 0; + this.bitPos = 0; + this.eos = false; - /// - /// Gets a value indicating whether the offset is inside the inputStream length. - /// - private bool ValidPosition - { - get + if (length > sizeof(long)) { - return this.Offset < this.stream.Length; + length = sizeof(long); } + + ulong currentValue = 0; + for (int i = 0; i < length; ++i) + { + currentValue |= (ulong)this.data[i] << (8 * i); + } + + this.value = currentValue; + this.pos = length; } + /// + /// Pre-fetched bits. + /// + private ulong value; + + /// + /// Buffer length. + /// + private long len; + + /// + /// Byte position in buffer. + /// + private long pos; + + /// + /// Current bit-reading position in value. + /// + private int bitPos; + + /// + /// True if a bit was read past the end of buffer. + /// + private bool eos; + /// /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. /// - /// The number of bits to read (should not exceed 16). + /// The number of bits to read (should not exceed 16). /// A ushort value. - public uint Read(int count) + public uint ReadBits(int nBits) { - uint readValue = 0; - for (int bitPos = 0; bitPos < count; bitPos++) + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + + if (!this.eos && nBits <= VP8L_MAX_NUM_BIT_READ) { - bool bitRead = this.ReadBit(); - if (bitRead) - { - readValue = (uint)(readValue | (1 << bitPos)); - } + ulong val = this.PrefetchBits() & this.kBitMask[nBits]; + int newBits = this.bitPos + nBits; + this.bitPos = newBits; + this.ShiftBytes(); + return (uint)val; + } + else + { + this.SetEndOfStream(); + return 0; } - - return readValue; } - /// - /// Reads one bit. - /// - /// True, if the bit is one, otherwise false. public bool ReadBit() { - if (!this.ValidPosition) + uint bit = this.ReadBits(1); + return bit != 0; + } + + public void AdvanceBitPosition(int bitPosition) + { + this.bitPos += bitPosition; + } + + public ulong PrefetchBits() + { + return this.value >> (this.bitPos & (VP8L_LBITS - 1)); + } + + public void FillBitWindow() + { + if (this.bitPos >= VP8L_WBITS) { - WebPThrowHelper.ThrowImageFormatException("The image inputStream does not contain enough data"); + this.DoFillBitWindow(); } + } - this.stream.Seek(this.Offset, SeekOrigin.Begin); - byte value = (byte)((this.stream.ReadByte() >> this.Bit) & 1); - this.AdvanceBit(); - this.stream.Seek(this.Offset, SeekOrigin.Begin); - - return value == 1; + public void DoFillBitWindow() + { + this.ShiftBytes(); } - /// - /// Advances the inputStream by one Bit. - /// - public void AdvanceBit() + private void ShiftBytes() { - this.Bit = (this.Bit + 1) % 8; - if (this.Bit == 0) + while (this.bitPos >= 8 && this.pos < this.len) + { + this.value >>= 8; + this.value |= (ulong)this.data[this.pos] << (VP8L_LBITS - 8); + ++this.pos; + this.bitPos -= 8; + } + + if (this.IsEndOfStream()) { - this.Offset++; + this.SetEndOfStream(); } } + + private bool IsEndOfStream() + { + return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); + } + + private void SetEndOfStream() + { + this.eos = true; + this.bitPos = 0; // To avoid undefined behaviour with shifts. + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 70cfe6ac7..5d6103543 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(this.currentStream, (int)imageInfo.ImageDataSize); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader, (int)imageInfo.ImageDataSize); losslessDecoder.Decode(pixels, image.Width, image.Height); } else @@ -298,16 +298,16 @@ namespace SixLabors.ImageSharp.Formats.WebP uint dataSize = this.ReadChunkSize(); // One byte signature, should be 0x2f. - byte signature = (byte)this.currentStream.ReadByte(); + var bitReader = new Vp8LBitReader(this.currentStream); + uint signature = bitReader.ReadBits(8); if (signature != WebPConstants.Vp8LMagicByte) { WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } // The first 28 bits of the bitstream specify the width and height of the image. - var bitReader = new Vp8LBitReader(this.currentStream); - uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; + uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; // The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. bool alphaIsUsed = bitReader.ReadBit(); @@ -315,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. // TODO: should we throw here when version number is != 0? - uint version = bitReader.Read(WebPConstants.Vp8LVersionBits); + uint version = bitReader.ReadBits(WebPConstants.Vp8LVersionBits); return new WebPImageInfo() { @@ -323,22 +323,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Height = (int)height, IsLossLess = true, ImageDataSize = dataSize, - Features = features + Features = features, + Vp9LBitReader = bitReader }; } - private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) - where TPixel : struct, IPixel - { - - } - - private void ReadExtended(Buffer2D pixels, int width, int height) - where TPixel : struct, IPixel - { - // TODO: implement decoding - } - private void ParseOptionalChunks(WebPFeatures features) { if (features.ExifProfile == false && features.XmpMetaData == false) diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 651c8d895..f68cc00b1 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -29,5 +29,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The bytes of the image payload. /// public uint ImageDataSize { get; set; } + + // TODO: not sure if the bitreader is in the right place here, but for the sake of simplicity it will stay here for now. + // Will be refactored later. + public Vp8LBitReader Vp9LBitReader { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index a24e78463..aa186739f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -23,9 +23,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly int imageDataSize; - public WebPLosslessDecoder(Stream stream, int imageDataSize) + public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) { - this.bitReader = new Vp8LBitReader(stream); + this.bitReader = bitReader; this.imageDataSize = imageDataSize; // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool colorCachePresent = this.bitReader.ReadBit(); if (colorCachePresent) { - colorCacheBits = (int)this.bitReader.Read(4); + colorCacheBits = (int)this.bitReader.ReadBits(4); int colorCacheSize = 1 << colorCacheBits; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) { @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool isEntropyImage = this.bitReader.ReadBit(); if (isEntropyImage) { - uint huffmanPrecision = this.bitReader.Read(3) + 2; + uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision); int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); @@ -129,17 +129,17 @@ namespace SixLabors.ImageSharp.Formats.WebP // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros. // Read symbols, codes & code lengths directly. - uint numSymbols = this.bitReader.Read(1) + 1; - uint firstSymbolLenCode = this.bitReader.Read(1); + uint numSymbols = this.bitReader.ReadBits(1) + 1; + uint firstSymbolLenCode = this.bitReader.ReadBits(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.Read((firstSymbolLenCode == 0) ? 1 : 8); + uint symbol = this.bitReader.ReadBits((firstSymbolLenCode == 0) ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. if (numSymbols == 2) { - symbol = this.bitReader.Read(8); + symbol = this.bitReader.ReadBits(8); codeLengths[symbol] = 1; } } @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; - uint numCodes = this.bitReader.Read(4) + 4; + uint numCodes = this.bitReader.ReadBits(4) + 4; if (numCodes > WebPConstants.NumCodeLengthCodes) { WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); + codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); @@ -171,6 +171,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { + Span tableSpan = table.AsSpan(); int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; @@ -182,8 +183,8 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.bitReader.ReadBit()) { - int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); - maxSymbol = 2 + (int)this.bitReader.Read(lengthNBits); + int lengthNBits = 2 + (2 * (int)this.bitReader.ReadBits(3)); + maxSymbol = 2 + (int)this.bitReader.ReadBits(lengthNBits); } else { @@ -198,7 +199,12 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - codeLen = int.MaxValue; // TODO: this is wrong + this.bitReader.FillBitWindow(); + ulong prefetchBits = this.bitReader.PrefetchBits(); + ulong idx = prefetchBits & 127; + HuffmanCode huffmanCode = table[idx]; + this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); + codeLen = huffmanCode.Value; if (codeLen < WebPConstants.kCodeLengthLiterals) { codeLengths[symbol++] = codeLen; @@ -213,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int slot = codeLen - WebPConstants.kCodeLengthLiterals; int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; - int repeat = (int)(this.bitReader.Read(extraBits) + repeatOffset); + int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { return; @@ -238,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var transforms = new List(WebPConstants.MaxNumberOfTransforms); while (transformPresent) { - var transformType = (WebPTransformType)this.bitReader.Read(2); + var transformType = (WebPTransformType)this.bitReader.ReadBits(2); transforms.Add(transformType); switch (transformType) { @@ -248,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.WebP case WebPTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. - uint colorTableSize = this.bitReader.Read(8) + 1; + uint colorTableSize = this.bitReader.ReadBits(8) + 1; // TODO: color table should follow here? break; @@ -257,7 +263,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of prediction data define the block width and height in number of bits. // The number of block columns, block_xsize, is used in indexing two-dimensionally. - uint sizeBits = this.bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.ReadBits(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; @@ -268,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, // just like the predictor transform: - uint sizeBits = this.bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.ReadBits(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; break; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f09be2faf..32d38ba33 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool isShowFrame = bitReader.ReadBit(); - uint firstPartitionSize = (bitReader.Read(16) << 3) | bitReader.Read(3); + uint firstPartitionSize = (bitReader.ReadBits(16) << 3) | bitReader.ReadBits(3); } private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version) From b37ca543464c3e463ff748e0ddec254b000288ea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 15 Nov 2019 20:43:09 +0100 Subject: [PATCH 041/359] Use kTableSize array to determine tableSize --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 4 ++ .../Formats/WebP/WebPLosslessDecoder.cs | 45 +++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index 9e9f1d7a7..0e09d004d 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -7,6 +7,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal static class HuffmanUtils { + public const int HuffmanTableBits = 8; + + public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; + public static int BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index aa186739f..88fd3cad0 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -23,6 +23,24 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly int imageDataSize; + private static int FIXED_TABLE_SIZE = 630 * 3 + 410; + + private static int[] kTableSize = + { + FIXED_TABLE_SIZE + 654, + FIXED_TABLE_SIZE + 656, + FIXED_TABLE_SIZE + 658, + FIXED_TABLE_SIZE + 662, + FIXED_TABLE_SIZE + 670, + FIXED_TABLE_SIZE + 686, + FIXED_TABLE_SIZE + 718, + FIXED_TABLE_SIZE + 782, + FIXED_TABLE_SIZE + 912, + FIXED_TABLE_SIZE + 1168, + FIXED_TABLE_SIZE + 1680, + FIXED_TABLE_SIZE + 2704 + }; + public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) { this.bitReader = bitReader; @@ -37,18 +55,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.ReadTransformations(); int xsize = 0, ysize = 0; - this.ReadHuffmanCodes(xsize, ysize); - } - - private void ReadHuffmanCodes(int xsize, int ysize) - { - int maxAlphabetSize = 0; - int colorCacheBits = 0; - int numHtreeGroups = 1; - int numHtreeGroupsMax = 1; // Read color cache, if present. bool colorCachePresent = this.bitReader.ReadBit(); + int colorCacheBits = 0; if (colorCachePresent) { colorCacheBits = (int)this.bitReader.ReadBits(4); @@ -59,6 +69,15 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); + } + + private void ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) + { + int maxAlphabetSize = 0; + int numHtreeGroups = 1; + int numHtreeGroupsMax = 1; + // Read the Huffman codes. // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. @@ -88,9 +107,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // TODO: not sure about the correct tabelSize here. Harcoded for now. - //int tableSize = kTableSize[colorCacheBits]; - int tableSize = 2970; + int tableSize = kTableSize[colorCacheBits]; var table = new HuffmanCode[numHtreeGroups * tableSize]; for (int i = 0; i < numHtreeGroupsMax; i++) { @@ -163,8 +180,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); } - int size = 0; - // TODO: VP8LBuildHuffmanTable + int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); return size; } @@ -222,6 +238,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { + // TODO: not sure, if this should be treated as an error here return; } else From 41eaff0cc2d5926507a17983abb036cfef3a1d4c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 18 Nov 2019 21:36:42 +0100 Subject: [PATCH 042/359] Continue with ReadHuffmanCodes --- src/ImageSharp/Formats/WebP/HTreeGroup.cs | 9 +- src/ImageSharp/Formats/WebP/HuffIndex.cs | 36 ++++++++ src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 10 ++- .../Formats/WebP/WebPLosslessDecoder.cs | 82 ++++++++++++++++--- 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/HuffIndex.cs diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index d3c21e690..c9763b69b 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -16,10 +16,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class HTreeGroup { + public HTreeGroup() + { + HTree = new List(WebPConstants.HuffmanCodesPerMetaCode); + } + /// /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys. /// - public List HTree { get; set; } + public List HTree { get; private set; } /// /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). @@ -29,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero. /// - public int LiteralArb { get; set; } + public uint LiteralArb { get; set; } /// /// True if is_trivial_literal with only one code. diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/HuffIndex.cs new file mode 100644 index 000000000..6d84b86d7 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HuffIndex.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Five Huffman codes are used at each meta code. + /// + public enum HuffIndex : int + { + /// + /// Green + length prefix codes + color cache codes. + /// + Green = 0, + + /// + /// Red. + /// + Red = 1, + + /// + /// Blue. + /// + Blue = 2, + + /// + /// Alpha. + /// + Alpha = 3, + + /// + /// Distance prefix codes. + /// + Dist = 4 + } +} diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index 0e09d004d..a800f7e85 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -9,9 +9,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { public const int HuffmanTableBits = 8; + public const int HuffmanPackedBits = 6; + public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; - public static int BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize) + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); Guard.NotNull(codeLengths, nameof(codeLengths)); @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - ++count[codeLengths[symbol]]; + count[codeLengths[symbol]]++; } // Generate offsets into sorted symbol table by code length. @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.WebP BitsUsed = len, Value = sorted[symbol++] }; - ReplicateValue(table.AsSpan(key), step, tableSize, huffmanCode); + ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); } } @@ -118,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - Span tableSpan = table.AsSpan(); + Span tableSpan = table; for (; count[len] > 0; --count[len]) { if ((key & mask) != low) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 88fd3cad0..ee33af73e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int FIXED_TABLE_SIZE = 630 * 3 + 410; - private static int[] kTableSize = + private static readonly int[] kTableSize = { FIXED_TABLE_SIZE + 654, FIXED_TABLE_SIZE + 656, @@ -41,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.WebP FIXED_TABLE_SIZE + 2704 }; + private static readonly byte[] kLiteralMap = + { + 0, 1, 1, 1, 0 + }; + public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) { this.bitReader = bitReader; @@ -108,12 +114,16 @@ namespace SixLabors.ImageSharp.Formats.WebP } int tableSize = kTableSize[colorCacheBits]; - var table = new HuffmanCode[numHtreeGroups * tableSize]; + var huffmanTables = new HuffmanCode[numHtreeGroups * tableSize]; + var hTreeGroups = new HTreeGroup[numHtreeGroups]; + Span huffmanTable = huffmanTables.AsSpan(); for (int i = 0; i < numHtreeGroupsMax; i++) { + hTreeGroups[i] = new HTreeGroup(); + HTreeGroup hTreeGroup = hTreeGroups[i]; int size; int totalSize = 0; - int isTrivialLiteral = 1; + bool isTrivialLiteral = true; int maxBits = 0; var codeLengths = new int[maxAlphabetSize]; for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) @@ -121,24 +131,72 @@ namespace SixLabors.ImageSharp.Formats.WebP int alphabetSize = WebPConstants.kAlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { - if (j == 0 && colorCacheBits > 0) - { - alphabetSize += 1 << colorCacheBits; - } + alphabetSize += 1 << colorCacheBits; + } + + size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); + if (size is 0) + { + WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + } + hTreeGroup.HTree.Add(huffmanTable.ToArray()); + + if (isTrivialLiteral && kLiteralMap[j] == 1) + { + isTrivialLiteral = huffmanTable[0].BitsUsed == 0; + } + + totalSize += huffmanTable[0].BitsUsed; + huffmanTable = huffmanTable.Slice(size); - size = this.ReadHuffmanCode(alphabetSize, codeLengths, table); - if (size is 0) + if (j <= (int)HuffIndex.Alpha) + { + int localMaxBits = codeLengths[0]; + int k; + for (k = 1; k < alphabetSize; ++k) { - WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); + if (codeLengths[k] > localMaxBits) + { + localMaxBits = codeLengths[k]; + } } + + maxBits += localMaxBits; + } + } + + hTreeGroup.IsTrivialLiteral = isTrivialLiteral; + hTreeGroup.IsTrivialCode = false; + if (isTrivialLiteral) + { + int red = hTreeGroup.HTree[(int)HuffIndex.Red].First().Value; + int blue = hTreeGroup.HTree[(int)HuffIndex.Blue].First().Value; + int green = hTreeGroup.HTree[(int)HuffIndex.Green].First().Value; + int alpha = hTreeGroup.HTree[(int)HuffIndex.Alpha].First().Value; + hTreeGroup.LiteralArb = (uint)((alpha << 24) | (red << 16) | blue); + if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) + { + hTreeGroup.IsTrivialCode = true; + hTreeGroup.LiteralArb |= (uint)green << 8; } } + + hTreeGroup.UsePackedTable = hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; + if (hTreeGroup.UsePackedTable) + { + throw new NotImplementedException("use packed table is not implemented yet"); + } } } - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, HuffmanCode[] table) + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) { bool simpleCode = this.bitReader.ReadBit(); + for (int i = 0; i < alphabetSize; i++) + { + codeLengths[i] = 0; + } + if (simpleCode) { // (i) Simple Code Length Code. @@ -177,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } - this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); From 5fab53d760310de8636a5d4eec8fbc78fd8e97f9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Nov 2019 20:12:55 +0100 Subject: [PATCH 043/359] Setup color cache --- src/ImageSharp/Formats/WebP/ColorCache.cs | 22 +++++++++++++++++++ .../Formats/WebP/WebPLosslessDecoder.cs | 18 ++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/WebP/ColorCache.cs diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs new file mode 100644 index 000000000..eac1721e8 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class ColorCache + { + /// + /// Color entries. + /// + public List Colors { get; set; } + + /// + /// Hash shift: 32 - hashBits. + /// + public uint HashShift { get; set; } + + public uint HashBits { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index ee33af73e..7bf819035 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -65,17 +65,33 @@ namespace SixLabors.ImageSharp.Formats.WebP // Read color cache, if present. bool colorCachePresent = this.bitReader.ReadBit(); int colorCacheBits = 0; + int colorCacheSize = 0; + var colorCache = new ColorCache(); if (colorCachePresent) { colorCacheBits = (int)this.bitReader.ReadBits(4); - int colorCacheSize = 1 << colorCacheBits; + colorCacheSize = 1 << colorCacheBits; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) { WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } + + int hashSize = 1 << colorCacheBits; + colorCache.Colors = new List(hashSize); + colorCache.HashBits = (uint)colorCacheBits; + colorCache.HashShift = (uint)(32 - colorCacheBits); } this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); + + int lastPixel = 0; + int row = lastPixel / width; + int col = lastPixel % width; + int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; + int colorCacheLimit = lenCodeLimit + colorCacheSize; + bool decIsIncremental = false; // TODO: determine correct value for decIsIncremental + int nextSyncRow = decIsIncremental ? row : 1 << 24; + } private void ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) From d45fe26a69ecae00cfb4a41643b758334eba4b7f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 25 Nov 2019 19:39:50 +0100 Subject: [PATCH 044/359] Add additional helper methods for decoding the image, start with decoding the image (still WIP) --- src/ImageSharp/Formats/WebP/ColorCache.cs | 16 + src/ImageSharp/Formats/WebP/HTreeGroup.cs | 15 +- src/ImageSharp/Formats/WebP/HuffIndex.cs | 12 +- src/ImageSharp/Formats/WebP/HuffmanCode.cs | 2 +- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 8 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 11 +- src/ImageSharp/Formats/WebP/Vp8LMetadata.cs | 28 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 6 +- .../Formats/WebP/WebPLosslessDecoder.cs | 389 ++++++++++++++++-- 9 files changed, 417 insertions(+), 70 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8LMetadata.cs diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index eac1721e8..e8e34e878 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -18,5 +18,21 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint HashShift { get; set; } public uint HashBits { get; set; } + + public void Init(int colorCacheBits) + { + + } + + public void Insert() + { + // TODO: implement VP8LColorCacheInsert + } + + public int ColorCacheLookup() + { + // TODO: implement VP8LColorCacheLookup + return 0; + } } } diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index c9763b69b..99d26844c 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -16,15 +16,20 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class HTreeGroup { - public HTreeGroup() + public HTreeGroup(uint packedTableSize) { - HTree = new List(WebPConstants.HuffmanCodesPerMetaCode); + this.HTrees = new List(WebPConstants.HuffmanCodesPerMetaCode); + this.PackedTable = new HuffmanCode[packedTableSize]; + for (int i = 0; i < packedTableSize; i++) + { + this.PackedTable[i] = new HuffmanCode(); + } } /// - /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys. + /// This has a maximum of HuffmanCodesPerMetaCode (5) entry's. /// - public List HTree { get; private set; } + public List HTrees { get; private set; } /// /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). @@ -49,6 +54,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Table mapping input bits to a packed values, or escape case to literal code. /// - public HuffmanCode PackedTable { get; set; } + public HuffmanCode[] PackedTable { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/HuffIndex.cs index 6d84b86d7..7e2b58a8e 100644 --- a/src/ImageSharp/Formats/WebP/HuffIndex.cs +++ b/src/ImageSharp/Formats/WebP/HuffIndex.cs @@ -6,31 +6,31 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Five Huffman codes are used at each meta code. /// - public enum HuffIndex : int + public static class HuffIndex { /// /// Green + length prefix codes + color cache codes. /// - Green = 0, + public const int Green = 0; /// /// Red. /// - Red = 1, + public const int Red = 1; /// /// Blue. /// - Blue = 2, + public const int Blue = 2; /// /// Alpha. /// - Alpha = 3, + public const int Alpha = 3; /// /// Distance prefix codes. /// - Dist = 4 + public const int Dist = 4; } } diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs index b3133786c..b76f41d23 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the symbol value or table offset. /// - public int Value { get; set; } + public uint Value { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index a800f7e85..ea549ef26 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; + public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); @@ -71,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode() { BitsUsed = 0, - Value = sorted[0] + Value = (uint)sorted[0] }; ReplicateValue(table, 1, totalSize, huffmanCode); return totalSize; @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode() { BitsUsed = len, - Value = sorted[symbol++] + Value = (uint)sorted[symbol++] }; ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); key = GetNextKey(key, len); @@ -138,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var huffmanCode = new HuffmanCode { BitsUsed = len - rootBits, - Value = sorted[symbol++] + Value = (uint)sorted[symbol++] }; ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode); key = GetNextKey(key, len); diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 9bcad284c..cdcca61e9 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; namespace SixLabors.ImageSharp.Formats.WebP @@ -151,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ShiftBytes(); } + public bool IsEndOfStream() + { + return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); + } + private void ShiftBytes() { while (this.bitPos >= 8 && this.pos < this.len) @@ -167,11 +171,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private bool IsEndOfStream() - { - return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); - } - private void SetEndOfStream() { this.eos = true; diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs new file mode 100644 index 000000000..0f9595a41 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8LMetadata + { + public int ColorCacheSize { get; set; } + + public ColorCache ColorCache { get; set; } + + public ColorCache SavedColorCache { get; set; } + + public int HuffmanMask { get; set; } + + public int HuffmanSubSampleBits { get; set; } + + public int HuffmanXSize { get; set; } + + public int[] HuffmanImage { get; set; } + + public int NumHTreeGroups { get; set; } + + public HTreeGroup[] HTreeGroups { get; set; } + + public HuffmanCode[] HuffmanTables { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index a2930171d..c54f72073 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -86,11 +86,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int NumDistanceCodes = 40; - public static int NumCodeLengthCodes = 19; - public static int LengthTableBits = 7; - public static int kCodeLengthLiterals = 16; + public static uint kCodeLengthLiterals = 16; public static int kCodeLengthRepeatCode = 16; @@ -98,8 +96,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public static int[] kCodeLengthRepeatOffsets = { 3, 3, 11 }; - public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - public static int[] kAlphabetSize = { NumLiteralCodes + NumLengthCodes, NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 7bf819035..37c6aba55 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using SixLabors.ImageSharp.Memory; @@ -24,25 +23,51 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly int imageDataSize; - private static int FIXED_TABLE_SIZE = 630 * 3 + 410; + private static readonly int BitsSpecialMarker = 0x100; - private static readonly int[] kTableSize = + private static readonly uint PackedNonLiteralCode = 0; + + private static readonly int NumArgbCacheRows = 16; + + private static readonly int FixedTableSize = (630 * 3) + 410; + + private static readonly int[] KTableSize = { - FIXED_TABLE_SIZE + 654, - FIXED_TABLE_SIZE + 656, - FIXED_TABLE_SIZE + 658, - FIXED_TABLE_SIZE + 662, - FIXED_TABLE_SIZE + 670, - FIXED_TABLE_SIZE + 686, - FIXED_TABLE_SIZE + 718, - FIXED_TABLE_SIZE + 782, - FIXED_TABLE_SIZE + 912, - FIXED_TABLE_SIZE + 1168, - FIXED_TABLE_SIZE + 1680, - FIXED_TABLE_SIZE + 2704 + FixedTableSize + 654, + FixedTableSize + 656, + FixedTableSize + 658, + FixedTableSize + 662, + FixedTableSize + 670, + FixedTableSize + 686, + FixedTableSize + 718, + FixedTableSize + 782, + FixedTableSize + 912, + FixedTableSize + 1168, + FixedTableSize + 1680, + FixedTableSize + 2704 }; - private static readonly byte[] kLiteralMap = + public static int NumCodeLengthCodes = 19; + public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + private static readonly int CodeToPlaneCodes = 120; + private static readonly int[] KCodeToPlane = + { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; + + private static readonly byte[] KLiteralMap = { 0, 1, 1, 1, 0 }; @@ -51,9 +76,6 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.bitReader = bitReader; this.imageDataSize = imageDataSize; - - // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - //stream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment. } public void Decode(Buffer2D pixels, int width, int height) @@ -80,10 +102,14 @@ namespace SixLabors.ImageSharp.Formats.WebP colorCache.Colors = new List(hashSize); colorCache.HashBits = (uint)colorCacheBits; colorCache.HashShift = (uint)(32 - colorCacheBits); + colorCache.Init(colorCacheBits); } - this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); - + Vp8LMetadata metadata = this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); + var numBits = 0; // TODO: use huffmanSubsampleBits. + metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; + metadata.ColorCacheSize = colorCacheSize; + int lastPixel = 0; int row = lastPixel / width; int col = lastPixel % width; @@ -91,10 +117,150 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheLimit = lenCodeLimit + colorCacheSize; bool decIsIncremental = false; // TODO: determine correct value for decIsIncremental int nextSyncRow = decIsIncremental ? row : 1 << 24; + int mask = metadata.HuffmanMask; + HTreeGroup[] hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + var pixelData = new byte[width * height * 4]; + + int totalPixels = width * height; + int decodedPixels = 0; + while (decodedPixels < totalPixels) + { + int code = 0; + if ((col & mask) == 0) + { + hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + } + + this.bitReader.FillBitWindow(); + if (hTreeGroup[0].UsePackedTable) + { + code = (int)this.ReadPackedSymbols(hTreeGroup); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + if (code == PackedNonLiteralCode) + { + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + continue; + } + } + else + { + this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + } + + if (this.bitReader.IsEndOfStream()) + { + break; + } + + // Literal + if (code < WebPConstants.NumLiteralCodes) + { + if (hTreeGroup[0].IsTrivialLiteral) + { + long pixel = hTreeGroup[0].LiteralArb | (code << 8); + } + else + { + uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); + this.bitReader.FillBitWindow(); + uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); + uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + int pixelIdx = decodedPixels * 4; + pixelData[pixelIdx] = (byte)alpha; + pixelData[pixelIdx + 1] = (byte)red; + pixelData[pixelIdx + 2] = (byte)code; + pixelData[pixelIdx + 3] = (byte)blue; + } + + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + } + else if (code < lenCodeLimit) + { + // Backward reference is used. + int lengthSym = code - WebPConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance((int)distSymbol); + int dist = this.PlaneCodeToDistance(width, distCode); + if (this.bitReader.IsEndOfStream()) + { + break; + } + + this.CopyBlock32b(pixelData, dist, length); + decodedPixels += length; + col += length; + while (col >= width) + { + col -= width; + ++row; + } + + if ((col & mask) != 0) + { + hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + } + if (colorCache != null) + { + //while (lastCached < src) + //{ + // colorCache.Insert(lastCached); + //} + } + } + else if (code < colorCacheLimit) + { + // Color cache should be used. + int key = code - lenCodeLimit; + /*while (lastCached < src) + { + colorCache.Insert(lastCached); + }*/ + //pixelData = colorCache.Lookup(key); + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + } + else + { + // Error + } + } } - private void ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) + private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels) + { + ++col; + decodedPixels++; + if (col >= width) + { + col = 0; + ++row; + /*if (row <= lastRow && (row % NumArgbCacheRows == 0)) + { + this.ProcessRowFunc(row); + }*/ + + if (colorCache != null) + { + /*while (lastCached < src) + { + VP8LColorCacheInsert(color_cache, *last_cached++); + }*/ + } + } + } + + private Vp8LMetadata ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) { int maxAlphabetSize = 0; int numHtreeGroups = 1; @@ -111,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); // TODO: decode entropy image - return; + return new Vp8LMetadata(); } // Find maximum alphabet size for the htree group. @@ -129,13 +295,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - int tableSize = kTableSize[colorCacheBits]; + int tableSize = KTableSize[colorCacheBits]; var huffmanTables = new HuffmanCode[numHtreeGroups * tableSize]; var hTreeGroups = new HTreeGroup[numHtreeGroups]; Span huffmanTable = huffmanTables.AsSpan(); for (int i = 0; i < numHtreeGroupsMax; i++) { - hTreeGroups[i] = new HTreeGroup(); + hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); HTreeGroup hTreeGroup = hTreeGroups[i]; int size; int totalSize = 0; @@ -155,9 +321,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } - hTreeGroup.HTree.Add(huffmanTable.ToArray()); - if (isTrivialLiteral && kLiteralMap[j] == 1) + hTreeGroup.HTrees.Add(huffmanTable.ToArray()); + + if (isTrivialLiteral && KLiteralMap[j] == 1) { isTrivialLiteral = huffmanTable[0].BitsUsed == 0; } @@ -185,24 +352,34 @@ namespace SixLabors.ImageSharp.Formats.WebP hTreeGroup.IsTrivialCode = false; if (isTrivialLiteral) { - int red = hTreeGroup.HTree[(int)HuffIndex.Red].First().Value; - int blue = hTreeGroup.HTree[(int)HuffIndex.Blue].First().Value; - int green = hTreeGroup.HTree[(int)HuffIndex.Green].First().Value; - int alpha = hTreeGroup.HTree[(int)HuffIndex.Alpha].First().Value; - hTreeGroup.LiteralArb = (uint)((alpha << 24) | (red << 16) | blue); + uint red = hTreeGroup.HTrees[HuffIndex.Red].First().Value; + uint blue = hTreeGroup.HTrees[HuffIndex.Blue].First().Value; + uint green = hTreeGroup.HTrees[HuffIndex.Green].First().Value; + uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha].First().Value; + hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) { hTreeGroup.IsTrivialCode = true; - hTreeGroup.LiteralArb |= (uint)green << 8; + hTreeGroup.LiteralArb |= green << 8; } } - hTreeGroup.UsePackedTable = hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; + hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; if (hTreeGroup.UsePackedTable) { - throw new NotImplementedException("use packed table is not implemented yet"); + this.BuildPackedTable(hTreeGroup); } } + + var metadata = new Vp8LMetadata() + { + // TODO: initialize huffman_image_ + NumHTreeGroups = numHtreeGroups, + HTreeGroups = hTreeGroups, + HuffmanTables = huffmanTables, + }; + + return metadata; } private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) @@ -239,16 +416,16 @@ namespace SixLabors.ImageSharp.Formats.WebP // (ii)Normal Code Length Code: // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; + var codeLengthCodeLengths = new int[NumCodeLengthCodes]; uint numCodes = this.bitReader.ReadBits(4) + 4; - if (numCodes > WebPConstants.NumCodeLengthCodes) + if (numCodes > NumCodeLengthCodes) { WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); } for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); + codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); @@ -265,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; - int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes); + int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Error building huffman table"); @@ -283,7 +460,6 @@ namespace SixLabors.ImageSharp.Formats.WebP while (symbol < numSymbols) { - int codeLen; if (maxSymbol-- == 0) { break; @@ -294,19 +470,19 @@ namespace SixLabors.ImageSharp.Formats.WebP ulong idx = prefetchBits & 127; HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); - codeLen = huffmanCode.Value; + uint codeLen = huffmanCode.Value; if (codeLen < WebPConstants.kCodeLengthLiterals) { - codeLengths[symbol++] = codeLen; + codeLengths[symbol++] = (int)codeLen; if (codeLen != 0) { - prevCodeLen = codeLen; + prevCodeLen = (int)codeLen; } } else { bool usePrev = codeLen == WebPConstants.kCodeLengthRepeatCode; - int slot = codeLen - WebPConstants.kCodeLengthLiterals; + uint slot = codeLen - WebPConstants.kCodeLengthLiterals; int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); @@ -391,5 +567,130 @@ namespace SixLabors.ImageSharp.Formats.WebP { return (size + (1 << samplingBits) - 1) >> samplingBits; } + + /// + /// Decodes the next Huffman code from bit-stream. + /// FillBitWindow(br) needs to be called at minimum every second call + /// to ReadSymbol, in order to pre-fetch enough bits. + /// + private uint ReadSymbol(Span table) + { + ulong val = this.bitReader.PrefetchBits(); + Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); + int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; + if (nBits > 0) + { + this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); + val = this.bitReader.PrefetchBits(); + tableSpan = tableSpan.Slice((int)tableSpan[0].Value); + tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); + } + + this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); + + return tableSpan[0].Value; + } + + private uint ReadPackedSymbols(HTreeGroup[] group) + { + uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); + HuffmanCode code = group[0].PackedTable[val]; + if (code.BitsUsed < BitsSpecialMarker) + { + this.bitReader.AdvanceBitPosition(code.BitsUsed); + // dest = (uint)code.Value; + return PackedNonLiteralCode; + } + + this.bitReader.AdvanceBitPosition(code.BitsUsed - BitsSpecialMarker); + + return code.Value; + } + + private void CopyBlock32b(byte[] dest, int dist, int length) + { + + } + + private int GetCopyDistance(int distanceSymbol) + { + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + this.bitReader.ReadBits(extraBits) + 1); + } + + private int GetCopyLength(int lengthSymbol) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol); + } + + private int PlaneCodeToDistance(int xSize, int planeCode) + { + if (planeCode > CodeToPlaneCodes) + { + return planeCode - CodeToPlaneCodes; + } + + int distCode = KCodeToPlane[planeCode - 1]; + int yOffset = distCode >> 4; + int xOffset = 8 - (distCode & 0xf); + int dist = (yOffset * xSize) + xOffset; + + return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + } + + private void BuildPackedTable(HTreeGroup hTreeGroup) + { + for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) + { + uint bits = code; + HuffmanCode huff = hTreeGroup.PackedTable[bits]; + HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; + if (hCode.Value >= WebPConstants.NumLiteralCodes) + { + huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; + huff.Value = hCode.Value; + } + else + { + huff.BitsUsed = 0; + huff.Value = 0; + bits >>= this.AccumulateHCode(hCode, 8, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); + bits >>= this.AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + } + } + } + + private int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + { + huff.BitsUsed += hCode.BitsUsed; + huff.Value |= hCode.Value << shift; + return hCode.BitsUsed; + } + + private int GetMetaIndex(int[] image, int xSize, int bits, int x, int y) + { + if (bits == 0) + { + return 0; + } + + return image[(xSize * (y >> bits)) + (x >> bits)]; + } + + private HTreeGroup[] GetHtreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + int metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan(metaIndex).ToArray(); + } } } From 99e70dc61d1baecb719ec12cfb73d7169f814663 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 28 Nov 2019 19:36:55 +0100 Subject: [PATCH 045/359] Using color cache, first version of decoding a lossless image --- src/ImageSharp/Formats/WebP/ColorCache.cs | 30 +++-- .../Formats/WebP/WebPLosslessDecoder.cs | 110 ++++++++++++------ 2 files changed, 94 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index e8e34e878..e9b4bc748 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; - namespace SixLabors.ImageSharp.Formats.WebP { internal class ColorCache @@ -10,29 +8,39 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Color entries. /// - public List Colors { get; set; } + public uint[] Colors { get; private set; } /// /// Hash shift: 32 - hashBits. /// - public uint HashShift { get; set; } + public int HashShift { get; private set; } + + public int HashBits { get; private set; } - public uint HashBits { get; set; } + private const uint KHashMul = 0x1e35a7bdu; - public void Init(int colorCacheBits) + public void Init(int hashBits) { + int hashSize = 1 << hashBits; + this.Colors = new uint[hashSize]; + this.HashBits = hashBits; + this.HashShift = 32 - hashBits; + } + public void Insert(uint argb) + { + int key = this.HashPix(argb, this.HashShift); + this.Colors[key] = argb; } - public void Insert() + public uint Lookup(int key) { - // TODO: implement VP8LColorCacheInsert + return this.Colors[key]; } - public int ColorCacheLookup() + private int HashPix(uint argb, int shift) { - // TODO: implement VP8LColorCacheLookup - return 0; + return (int)((argb * KHashMul) >> shift); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 37c6aba55..1cb550950 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -98,10 +99,6 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } - int hashSize = 1 << colorCacheBits; - colorCache.Colors = new List(hashSize); - colorCache.HashBits = (uint)colorCacheBits; - colorCache.HashShift = (uint)(32 - colorCacheBits); colorCache.Init(colorCacheBits); } @@ -109,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var numBits = 0; // TODO: use huffmanSubsampleBits. metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; metadata.ColorCacheSize = colorCacheSize; - + int lastPixel = 0; int row = lastPixel / width; int col = lastPixel % width; @@ -119,10 +116,11 @@ namespace SixLabors.ImageSharp.Formats.WebP int nextSyncRow = decIsIncremental ? row : 1 << 24; int mask = metadata.HuffmanMask; HTreeGroup[] hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); - var pixelData = new byte[width * height * 4]; + var pixelData = new uint[width * height]; int totalPixels = width * height; int decodedPixels = 0; + int lastCached = decodedPixels; while (decodedPixels < totalPixels) { int code = 0; @@ -131,10 +129,17 @@ namespace SixLabors.ImageSharp.Formats.WebP hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); } + if (hTreeGroup[0].IsTrivialCode) + { + pixelData[decodedPixels] = hTreeGroup[0].LiteralArb; + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); + continue; + } + this.bitReader.FillBitWindow(); if (hTreeGroup[0].UsePackedTable) { - code = (int)this.ReadPackedSymbols(hTreeGroup); + code = (int)this.ReadPackedSymbols(hTreeGroup, pixelData, decodedPixels); if (this.bitReader.IsEndOfStream()) { break; @@ -142,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (code == PackedNonLiteralCode) { - this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); continue; } } @@ -161,7 +166,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (hTreeGroup[0].IsTrivialLiteral) { - long pixel = hTreeGroup[0].LiteralArb | (code << 8); + pixelData[decodedPixels] = (uint)(hTreeGroup[0].LiteralArb | (code << 8)); } else { @@ -175,13 +180,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } int pixelIdx = decodedPixels * 4; - pixelData[pixelIdx] = (byte)alpha; - pixelData[pixelIdx + 1] = (byte)red; - pixelData[pixelIdx + 2] = (byte)code; - pixelData[pixelIdx + 3] = (byte)blue; + pixelData[pixelIdx] = + (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); } - this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); } else if (code < lenCodeLimit) { @@ -197,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - this.CopyBlock32b(pixelData, dist, length); + this.CopyBlock(pixelData, decodedPixels, dist, length); decodedPixels += length; col += length; while (col >= width) @@ -213,31 +216,51 @@ namespace SixLabors.ImageSharp.Formats.WebP if (colorCache != null) { - //while (lastCached < src) - //{ - // colorCache.Insert(lastCached); - //} + while (lastCached < decodedPixels) + { + colorCache.Insert(pixelData[lastCached]); + lastCached++; + } } } else if (code < colorCacheLimit) { // Color cache should be used. int key = code - lenCodeLimit; - /*while (lastCached < src) + while (lastCached < decodedPixels) { - colorCache.Insert(lastCached); - }*/ - //pixelData = colorCache.Lookup(key); - this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels); + colorCache.Insert(pixelData[lastCached]); + lastCached++; + } + + pixelData[decodedPixels] = colorCache.Lookup(key); + this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); } else { // Error } } + + TPixel color = default; + for (int y = 0; y < height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + int idx = (y * width) + x; + uint pixel = pixelData[idx]; + uint a = (pixel & 0xFF000000) >> 24; + uint r = (pixel & 0xFF0000) >> 16; + uint g = (pixel & 0xFF00) >> 8; + uint b = pixel & 0xFF; + color.FromRgba32(new Rgba32(r, g, b, a)); + pixelRow[x] = color; + } + } } - private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels) + private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, uint[] pixelData, ref int lastCached) { ++col; decodedPixels++; @@ -252,15 +275,16 @@ namespace SixLabors.ImageSharp.Formats.WebP if (colorCache != null) { - /*while (lastCached < src) + while (lastCached < decodedPixels) { - VP8LColorCacheInsert(color_cache, *last_cached++); - }*/ + colorCache.Insert(pixelData[lastCached]); + lastCached++; + } } } } - private Vp8LMetadata ReadHuffmanCodes(int xsize, int ysize, int colorCacheBits, bool allowRecursion = true) + private Vp8LMetadata ReadHuffmanCodes(int xSize, int ySize, int colorCacheBits, bool allowRecursion = true) { int maxAlphabetSize = 0; int numHtreeGroups = 1; @@ -273,8 +297,8 @@ namespace SixLabors.ImageSharp.Formats.WebP if (isEntropyImage) { uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; - int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision); - int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); + int huffmanXSize = SubSampleSize(xSize, (int)huffmanPrecision); + int huffmanYSize = SubSampleSize(ySize, (int)huffmanPrecision); // TODO: decode entropy image return new Vp8LMetadata(); @@ -591,14 +615,14 @@ namespace SixLabors.ImageSharp.Formats.WebP return tableSpan[0].Value; } - private uint ReadPackedSymbols(HTreeGroup[] group) + private uint ReadPackedSymbols(HTreeGroup[] group, uint[] pixelData, int decodedPixels) { uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); HuffmanCode code = group[0].PackedTable[val]; if (code.BitsUsed < BitsSpecialMarker) { this.bitReader.AdvanceBitPosition(code.BitsUsed); - // dest = (uint)code.Value; + pixelData[decodedPixels] = code.Value; return PackedNonLiteralCode; } @@ -607,9 +631,25 @@ namespace SixLabors.ImageSharp.Formats.WebP return code.Value; } - private void CopyBlock32b(byte[] dest, int dist, int length) + private void CopyBlock(uint[] pixelData, int decodedPixels, int dist, int length) { - + if (dist > length) + { + Span src = pixelData.AsSpan(decodedPixels - dist, length); + Span dest = pixelData.AsSpan(decodedPixels); + src.CopyTo(dest); + } + else + { + int copiedPixels = 0; + while (copiedPixels < length) + { + Span src = pixelData.AsSpan(decodedPixels - dist, dist); + Span dest = pixelData.AsSpan(decodedPixels + copiedPixels); + src.CopyTo(dest); + copiedPixels += dist; + } + } } private int GetCopyDistance(int distanceSymbol) From 00d993a1f3492057b9f676e73196c9adf4ba2e7e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 30 Nov 2019 19:50:34 +0100 Subject: [PATCH 046/359] Refactor lossless decoding to match better original implementation --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 26 +++-- .../Formats/WebP/WebPLosslessDecoder.cs | 105 ++++++++++-------- 2 files changed, 74 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index ea549ef26..b3c125df3 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -23,16 +23,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. var sorted = new int[codeLengthsSize]; - // total size root table + 2nd level table - int totalSize = 1 << rootBits; - // current code length - int len; - // symbol index in original or sorted table - int symbol; - // number of codes of each length: - var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; - // offsets in sorted table for each length - var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; + int totalSize = 1 << rootBits; // total size root table + 2nd level table + int len; // current code length + int symbol; // symbol index in original or sorted table + var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length + var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) @@ -45,6 +40,12 @@ namespace SixLabors.ImageSharp.Formats.WebP count[codeLengths[symbol]]++; } + // Error, all code lengths are zeros. + if (count[0] == codeLengthsSize) + { + return 0; + } + // Generate offsets into sorted symbol table by code length. offset[1] = 0; for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) @@ -88,6 +89,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int tableBits = rootBits; // key length of current table int tableSize = 1 << tableBits; // size of current table symbol = 0; + // Fill in root table. for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) { @@ -132,9 +134,9 @@ namespace SixLabors.ImageSharp.Formats.WebP tableSize = 1 << tableBits; totalSize += tableSize; low = key & mask; + table[low].BitsUsed = tableBits + rootBits; // TODO: fix this - //rootTable[low].bits = (tableBits + rootBits); - //rootTable[low].value = ((table - rootTable) - low); + // table[low].Value = ((table - rootTable) - low); } var huffmanCode = new HuffmanCode diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 1cb550950..1ce5552fe 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -48,8 +47,8 @@ namespace SixLabors.ImageSharp.Formats.WebP FixedTableSize + 2704 }; - public static int NumCodeLengthCodes = 19; - public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + private static readonly int NumCodeLengthCodes = 19; + private static readonly byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; private static readonly int CodeToPlaneCodes = 120; private static readonly int[] KCodeToPlane = @@ -81,9 +80,35 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Decode(Buffer2D pixels, int width, int height) where TPixel : struct, IPixel + { + uint[] pixelData = this.DecodeImageStream(width, height, true); + this.DecodePixelValues(width, height, pixelData, pixels); + } + + private void DecodePixelValues(int width, int height, uint[] pixelData, Buffer2D pixels) + where TPixel : struct, IPixel + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + int idx = (y * width) + x; + uint pixel = pixelData[idx]; + uint a = (pixel & 0xFF000000) >> 24; + uint r = (pixel & 0xFF0000) >> 16; + uint g = (pixel & 0xFF00) >> 8; + uint b = pixel & 0xFF; + color.FromRgba32(new Rgba32(r, g, b, a)); + pixelRow[x] = color; + } + } + } + + private uint[] DecodeImageStream(int xSize, int ySize, bool isLevel0) { this.ReadTransformations(); - int xsize = 0, ysize = 0; // Read color cache, if present. bool colorCachePresent = this.bitReader.ReadBit(); @@ -102,11 +127,17 @@ namespace SixLabors.ImageSharp.Formats.WebP colorCache.Init(colorCacheBits); } - Vp8LMetadata metadata = this.ReadHuffmanCodes(xsize, ysize, colorCacheBits); + Vp8LMetadata metadata = this.ReadHuffmanCodes(xSize, ySize, colorCacheBits, isLevel0); var numBits = 0; // TODO: use huffmanSubsampleBits. metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; metadata.ColorCacheSize = colorCacheSize; + uint[] pixelData = this.DecodeImageData(xSize, ySize, colorCacheSize, metadata, colorCache); + return pixelData; + } + + private uint[] DecodeImageData(int width, int height, int colorCacheSize, Vp8LMetadata metadata, ColorCache colorCache) + { int lastPixel = 0; int row = lastPixel / width; int col = lastPixel % width; @@ -115,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool decIsIncremental = false; // TODO: determine correct value for decIsIncremental int nextSyncRow = decIsIncremental ? row : 1 << 24; int mask = metadata.HuffmanMask; - HTreeGroup[] hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); var pixelData = new uint[width * height]; int totalPixels = width * height; @@ -126,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int code = 0; if ((col & mask) == 0) { - hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); } if (hTreeGroup[0].IsTrivialCode) @@ -180,8 +211,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } int pixelIdx = decodedPixels * 4; - pixelData[pixelIdx] = - (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); + pixelData[pixelIdx] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); } this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); @@ -211,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if ((col & mask) != 0) { - hTreeGroup = this.GetHtreeGroupForPos(metadata, col, row); + hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); } if (colorCache != null) @@ -242,22 +272,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - TPixel color = default; - for (int y = 0; y < height; y++) - { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = 0; x < width; x++) - { - int idx = (y * width) + x; - uint pixel = pixelData[idx]; - uint a = (pixel & 0xFF000000) >> 24; - uint r = (pixel & 0xFF0000) >> 16; - uint g = (pixel & 0xFF00) >> 8; - uint b = pixel & 0xFF; - color.FromRgba32(new Rgba32(r, g, b, a)); - pixelRow[x] = color; - } - } + return pixelData; } private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, uint[] pixelData, ref int lastCached) @@ -284,27 +299,27 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private Vp8LMetadata ReadHuffmanCodes(int xSize, int ySize, int colorCacheBits, bool allowRecursion = true) + private Vp8LMetadata ReadHuffmanCodes(int xSize, int ySize, int colorCacheBits, bool allowRecursion) { int maxAlphabetSize = 0; - int numHtreeGroups = 1; - int numHtreeGroupsMax = 1; + int numHTreeGroups = 1; + int numHTreeGroupsMax = 1; - // Read the Huffman codes. // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. bool isEntropyImage = this.bitReader.ReadBit(); - if (isEntropyImage) + if (allowRecursion && isEntropyImage) { + // Use meta Huffman codes. uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; - int huffmanXSize = SubSampleSize(xSize, (int)huffmanPrecision); - int huffmanYSize = SubSampleSize(ySize, (int)huffmanPrecision); - + int huffmanXSize = this.SubSampleSize(xSize, (int)huffmanPrecision); + int huffmanYSize = this.SubSampleSize(ySize, (int)huffmanPrecision); + int huffmanPixs = huffmanXSize * huffmanYSize; // TODO: decode entropy image return new Vp8LMetadata(); } - // Find maximum alphabet size for the htree group. + // Find maximum alphabet size for the hTree group. for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) { int alphabetSize = WebPConstants.kAlphabetSize[j]; @@ -320,14 +335,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } int tableSize = KTableSize[colorCacheBits]; - var huffmanTables = new HuffmanCode[numHtreeGroups * tableSize]; - var hTreeGroups = new HTreeGroup[numHtreeGroups]; + var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; + var hTreeGroups = new HTreeGroup[numHTreeGroups]; Span huffmanTable = huffmanTables.AsSpan(); - for (int i = 0; i < numHtreeGroupsMax; i++) + for (int i = 0; i < numHTreeGroupsMax; i++) { hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); HTreeGroup hTreeGroup = hTreeGroups[i]; - int size; int totalSize = 0; bool isTrivialLiteral = true; int maxBits = 0; @@ -340,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.WebP alphabetSize += 1 << colorCacheBits; } - size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); + int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); @@ -356,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP totalSize += huffmanTable[0].BitsUsed; huffmanTable = huffmanTable.Slice(size); - if (j <= (int)HuffIndex.Alpha) + if (j <= HuffIndex.Alpha) { int localMaxBits = codeLengths[0]; int k; @@ -398,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var metadata = new Vp8LMetadata() { // TODO: initialize huffman_image_ - NumHTreeGroups = numHtreeGroups, + NumHTreeGroups = numHTreeGroups, HTreeGroups = hTreeGroups, HuffmanTables = huffmanTables, }; @@ -437,7 +451,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - // (ii)Normal Code Length Code: + // (ii) Normal Code Length Code: // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. var codeLengthCodeLengths = new int[NumCodeLengthCodes]; @@ -683,7 +697,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int xOffset = 8 - (distCode & 0xf); int dist = (yOffset * xSize) + xOffset; - return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + // dist < 1 can happen if xsize is very small. + return (dist >= 1) ? dist : 1; } private void BuildPackedTable(HTreeGroup hTreeGroup) @@ -727,7 +742,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return image[(xSize * (y >> bits)) + (x >> bits)]; } - private HTreeGroup[] GetHtreeGroupForPos(Vp8LMetadata metadata, int x, int y) + private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) { int metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); return metadata.HTreeGroups.AsSpan(metaIndex).ToArray(); From d61ce09b022d4e764334add29ec41ecda3683b47 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 1 Dec 2019 20:44:51 +0100 Subject: [PATCH 047/359] Fix some decoding bugs --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 10 ++-- src/ImageSharp/Formats/WebP/Vp8LMetadata.cs | 2 +- .../Formats/WebP/WebPLosslessDecoder.cs | 52 ++++++++++++------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index b3c125df3..5ae3d4e67 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -125,18 +125,22 @@ namespace SixLabors.ImageSharp.Formats.WebP } Span tableSpan = table; + int tablePos = 0; for (; count[len] > 0; --count[len]) { if ((key & mask) != low) { tableSpan = tableSpan.Slice(tableSize); + tablePos += tableSize; tableBits = NextTableBitSize(count, len, rootBits); tableSize = 1 << tableBits; totalSize += tableSize; low = key & mask; - table[low].BitsUsed = tableBits + rootBits; - // TODO: fix this - // table[low].Value = ((table - rootTable) - low); + table[low] = new HuffmanCode + { + BitsUsed = tableBits + rootBits, + Value = (uint)(tablePos - low) + }; } var huffmanCode = new HuffmanCode diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs index 0f9595a41..edc72a822 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int HuffmanXSize { get; set; } - public int[] HuffmanImage { get; set; } + public uint[] HuffmanImage { get; set; } public int NumHTreeGroups { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 1ce5552fe..152efed1f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -108,15 +108,19 @@ namespace SixLabors.ImageSharp.Formats.WebP private uint[] DecodeImageStream(int xSize, int ySize, bool isLevel0) { - this.ReadTransformations(); + if (isLevel0) + { + this.ReadTransformations(); + } // Read color cache, if present. bool colorCachePresent = this.bitReader.ReadBit(); int colorCacheBits = 0; int colorCacheSize = 0; - var colorCache = new ColorCache(); + ColorCache colorCache = null; if (colorCachePresent) { + colorCache = new ColorCache(); colorCacheBits = (int)this.bitReader.ReadBits(4); colorCacheSize = 1 << colorCacheBits; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) @@ -184,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); } if (this.bitReader.IsEndOfStream()) @@ -301,22 +305,35 @@ namespace SixLabors.ImageSharp.Formats.WebP private Vp8LMetadata ReadHuffmanCodes(int xSize, int ySize, int colorCacheBits, bool allowRecursion) { + var metadata = new Vp8LMetadata(); int maxAlphabetSize = 0; int numHTreeGroups = 1; int numHTreeGroupsMax = 1; // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. - bool isEntropyImage = this.bitReader.ReadBit(); - if (allowRecursion && isEntropyImage) + if (allowRecursion && this.bitReader.ReadBit()) { // Use meta Huffman codes. uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; int huffmanXSize = this.SubSampleSize(xSize, (int)huffmanPrecision); int huffmanYSize = this.SubSampleSize(ySize, (int)huffmanPrecision); int huffmanPixs = huffmanXSize * huffmanYSize; - // TODO: decode entropy image - return new Vp8LMetadata(); + uint[] huffmanImage = this.DecodeImageStream(huffmanXSize, huffmanYSize, false); + metadata.HuffmanSubSampleBits = (int)huffmanPrecision; + for (int i = 0; i < huffmanPixs; ++i) + { + // The huffman data is stored in red and green bytes. + uint group = (huffmanImage[i] >> 8) & 0xffff; + huffmanImage[i] = group; + if (group >= numHTreeGroupsMax) + { + numHTreeGroupsMax = (int)group + 1; + } + } + + numHTreeGroups = numHTreeGroupsMax; + metadata.HuffmanImage = huffmanImage; } // Find maximum alphabet size for the hTree group. @@ -409,13 +426,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - var metadata = new Vp8LMetadata() - { - // TODO: initialize huffman_image_ - NumHTreeGroups = numHTreeGroups, - HTreeGroups = hTreeGroups, - HuffmanTables = huffmanTables, - }; + metadata.NumHTreeGroups = numHTreeGroups; + metadata.HTreeGroups = hTreeGroups; + metadata.HuffmanTables = huffmanTables; return metadata; } @@ -476,7 +489,6 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { - Span tableSpan = table.AsSpan(); int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; @@ -498,7 +510,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (symbol < numSymbols) { - if (maxSymbol-- == 0) + if (maxSymbol-- is 0) { break; } @@ -732,9 +744,9 @@ namespace SixLabors.ImageSharp.Formats.WebP return hCode.BitsUsed; } - private int GetMetaIndex(int[] image, int xSize, int bits, int x, int y) + private uint GetMetaIndex(uint[] image, int xSize, int bits, int x, int y) { - if (bits == 0) + if (bits is 0) { return 0; } @@ -744,8 +756,8 @@ namespace SixLabors.ImageSharp.Formats.WebP private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) { - int metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); - return metadata.HTreeGroups.AsSpan(metaIndex).ToArray(); + uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); } } } From 3da0b5cdc95891b7a4101bebd5a9219570373e8d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Dec 2019 21:19:03 +0100 Subject: [PATCH 048/359] Fix issue with creating huffman tables --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 5 +++-- src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index 5ae3d4e67..f079dcddd 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Fill in 2nd level tables and add pointers to root table. + Span tableSpan = table; + int tablePos = 0; for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1) { numOpen <<= 1; @@ -124,8 +126,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - Span tableSpan = table; - int tablePos = 0; for (; count[len] > 0; --count[len]) { if ((key & mask) != low) @@ -136,6 +136,7 @@ namespace SixLabors.ImageSharp.Formats.WebP tableSize = 1 << tableBits; totalSize += tableSize; low = key & mask; + uint v = (uint)(tablePos - low); table[low] = new HuffmanCode { BitsUsed = tableBits + rootBits, diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 152efed1f..b5cc4fb90 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -334,6 +334,8 @@ namespace SixLabors.ImageSharp.Formats.WebP numHTreeGroups = numHTreeGroupsMax; metadata.HuffmanImage = huffmanImage; + metadata.HuffmanXSize = this.SubSampleSize(huffmanXSize, metadata.HuffmanSubSampleBits); + metadata.HuffmanMask = (metadata.HuffmanSubSampleBits == 0) ? ~0 : (1 << metadata.HuffmanSubSampleBits) - 1; } // Find maximum alphabet size for the hTree group. @@ -625,13 +627,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private uint ReadSymbol(Span table) { - ulong val = this.bitReader.PrefetchBits(); + uint val = (uint)this.bitReader.PrefetchBits(); Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; if (nBits > 0) { this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); - val = this.bitReader.PrefetchBits(); + val = (uint)this.bitReader.PrefetchBits(); tableSpan = tableSpan.Slice((int)tableSpan[0].Value); tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); } @@ -659,7 +661,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void CopyBlock(uint[] pixelData, int decodedPixels, int dist, int length) { - if (dist > length) + if (dist >= length) { Span src = pixelData.AsSpan(decodedPixels - dist, length); Span dest = pixelData.AsSpan(decodedPixels); From eaec97657dd47be4bfa15abdbc89dc7ea7a44016 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Dec 2019 21:19:44 +0100 Subject: [PATCH 049/359] Add tests for lossless images without transforms --- .../Formats/WebP/WebPDecoderTests.cs | 19 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 2 ++ 2 files changed, 21 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 3db121bad..d8c0b1794 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -2,6 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System.IO; + +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + using Xunit; // ReSharper disable InconsistentNaming @@ -43,5 +49,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expectedHeight, image.Height); } } + + [Theory] + [WithFile(Lossless.LosslessNoTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.LosslessNoTransform2, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b8598e82e..5492091d1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -382,6 +382,8 @@ namespace SixLabors.ImageSharp.Tests public const string Lossless2 = "WebP/lossless2.webp"; public const string Lossless3 = "WebP/lossless3.webp"; public const string Lossless4 = "WebP/lossless4.webp"; + public const string LosslessNoTransform1 = "WebP/lossless_vec_1_0.webp"; + public const string LosslessNoTransform2 = "WebP/lossless_vec_2_0.webp"; } public static class Lossy From 4ec83f8036a2125d07d204bae259874e75a565e4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Dec 2019 17:18:11 +0100 Subject: [PATCH 050/359] Fix more decoding bugs, lossless_vec_2_0.webp.webp now decodes without error --- src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 21 ++++ .../Formats/WebP/WebPLosslessDecoder.cs | 98 +++++++++++-------- 2 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8LDecoder.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs new file mode 100644 index 000000000..0bc5a0c19 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8LDecoder + { + public Vp8LDecoder(int width, int height) + { + this.Width = width; + this.Height = height; + this.Metadata = new Vp8LMetadata(); + } + + public int Width { get; set; } + + public int Height { get; set; } + + public Vp8LMetadata Metadata { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index b5cc4fb90..ec71627ea 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -81,7 +81,8 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Decode(Buffer2D pixels, int width, int height) where TPixel : struct, IPixel { - uint[] pixelData = this.DecodeImageStream(width, height, true); + var decoder = new Vp8LDecoder(width, height); + uint[] pixelData = this.DecodeImageStream(decoder, width, height, true); this.DecodePixelValues(width, height, pixelData, pixels); } @@ -96,33 +97,44 @@ namespace SixLabors.ImageSharp.Formats.WebP { int idx = (y * width) + x; uint pixel = pixelData[idx]; - uint a = (pixel & 0xFF000000) >> 24; - uint r = (pixel & 0xFF0000) >> 16; - uint g = (pixel & 0xFF00) >> 8; - uint b = pixel & 0xFF; + byte a = (byte)((pixel & 0xFF000000) >> 24); + byte r = (byte)((pixel & 0xFF0000) >> 16); + byte g = (byte)((pixel & 0xFF00) >> 8); + byte b = (byte)(pixel & 0xFF); color.FromRgba32(new Rgba32(r, g, b, a)); pixelRow[x] = color; } } } - private uint[] DecodeImageStream(int xSize, int ySize, bool isLevel0) + private uint[] DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { if (isLevel0) { this.ReadTransformations(); } - // Read color cache, if present. + // Color cache. bool colorCachePresent = this.bitReader.ReadBit(); int colorCacheBits = 0; int colorCacheSize = 0; + if (colorCachePresent) + { + colorCacheBits = (int)this.bitReader.ReadBits(4); + // TODO: error check color cache bits + } + + // Read the Huffman codes (may recurse). + this.ReadHuffmanCodes(decoder, xSize, ySize, colorCacheBits, isLevel0); + decoder.Metadata.ColorCacheSize = colorCacheSize; + + // Finish setting up the color-cache ColorCache colorCache = null; if (colorCachePresent) { colorCache = new ColorCache(); - colorCacheBits = (int)this.bitReader.ReadBits(4); colorCacheSize = 1 << colorCacheBits; + decoder.Metadata.ColorCacheSize = colorCacheSize; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) { WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); @@ -130,27 +142,31 @@ namespace SixLabors.ImageSharp.Formats.WebP colorCache.Init(colorCacheBits); } + else + { + decoder.Metadata.ColorCacheSize = 0; + } - Vp8LMetadata metadata = this.ReadHuffmanCodes(xSize, ySize, colorCacheBits, isLevel0); - var numBits = 0; // TODO: use huffmanSubsampleBits. - metadata.HuffmanMask = (numBits == 0) ? ~0 : (1 << numBits) - 1; - metadata.ColorCacheSize = colorCacheSize; + this.UpdateDecoder(decoder, xSize, ySize); + + uint[] pixelData = this.DecodeImageData(decoder, xSize, ySize, colorCacheSize, colorCache); + if (!isLevel0) + { + decoder.Metadata = new Vp8LMetadata(); + } - uint[] pixelData = this.DecodeImageData(xSize, ySize, colorCacheSize, metadata, colorCache); return pixelData; } - private uint[] DecodeImageData(int width, int height, int colorCacheSize, Vp8LMetadata metadata, ColorCache colorCache) + private uint[] DecodeImageData(Vp8LDecoder decoder, int width, int height, int colorCacheSize, ColorCache colorCache) { int lastPixel = 0; int row = lastPixel / width; int col = lastPixel % width; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; int colorCacheLimit = lenCodeLimit + colorCacheSize; - bool decIsIncremental = false; // TODO: determine correct value for decIsIncremental - int nextSyncRow = decIsIncremental ? row : 1 << 24; - int mask = metadata.HuffmanMask; - HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); + int mask = decoder.Metadata.HuffmanMask; + HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); var pixelData = new uint[width * height]; int totalPixels = width * height; @@ -161,7 +177,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int code = 0; if ((col & mask) == 0) { - hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); + hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); } if (hTreeGroup[0].IsTrivialCode) @@ -214,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - int pixelIdx = decodedPixels * 4; + int pixelIdx = decodedPixels; pixelData[pixelIdx] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); } @@ -245,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if ((col & mask) != 0) { - hTreeGroup = this.GetHTreeGroupForPos(metadata, col, row); + hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); } if (colorCache != null) @@ -287,10 +303,6 @@ namespace SixLabors.ImageSharp.Formats.WebP { col = 0; ++row; - /*if (row <= lastRow && (row % NumArgbCacheRows == 0)) - { - this.ProcessRowFunc(row); - }*/ if (colorCache != null) { @@ -303,9 +315,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private Vp8LMetadata ReadHuffmanCodes(int xSize, int ySize, int colorCacheBits, bool allowRecursion) + private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion) { - var metadata = new Vp8LMetadata(); int maxAlphabetSize = 0; int numHTreeGroups = 1; int numHTreeGroupsMax = 1; @@ -319,8 +330,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int huffmanXSize = this.SubSampleSize(xSize, (int)huffmanPrecision); int huffmanYSize = this.SubSampleSize(ySize, (int)huffmanPrecision); int huffmanPixs = huffmanXSize * huffmanYSize; - uint[] huffmanImage = this.DecodeImageStream(huffmanXSize, huffmanYSize, false); - metadata.HuffmanSubSampleBits = (int)huffmanPrecision; + uint[] huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision; for (int i = 0; i < huffmanPixs; ++i) { // The huffman data is stored in red and green bytes. @@ -333,9 +344,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } numHTreeGroups = numHTreeGroupsMax; - metadata.HuffmanImage = huffmanImage; - metadata.HuffmanXSize = this.SubSampleSize(huffmanXSize, metadata.HuffmanSubSampleBits); - metadata.HuffmanMask = (metadata.HuffmanSubSampleBits == 0) ? ~0 : (1 << metadata.HuffmanSubSampleBits) - 1; + decoder.Metadata.HuffmanImage = huffmanImage; } // Find maximum alphabet size for the hTree group. @@ -373,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.WebP alphabetSize += 1 << colorCacheBits; } - int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); + int size = this.ReadHuffmanCode(decoder, alphabetSize, codeLengths, huffmanTable); if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); @@ -428,14 +437,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - metadata.NumHTreeGroups = numHTreeGroups; - metadata.HTreeGroups = hTreeGroups; - metadata.HuffmanTables = huffmanTables; - - return metadata; + decoder.Metadata.NumHTreeGroups = numHTreeGroups; + decoder.Metadata.HTreeGroups = hTreeGroups; + decoder.Metadata.HuffmanTables = huffmanTables; } - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) + private int ReadHuffmanCode(Vp8LDecoder decoder, int alphabetSize, int[] codeLengths, Span table) { bool simpleCode = this.bitReader.ReadBit(); for (int i = 0; i < alphabetSize; i++) @@ -481,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } - this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(decoder, table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); @@ -489,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return size; } - private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + private void ReadHuffmanCodeLengths(Vp8LDecoder decoder, HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { int maxSymbol; int symbol = 0; @@ -612,6 +619,15 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: return transformation in an appropriate form. } + private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) + { + int numBits = decoder.Metadata.HuffmanSubSampleBits; + decoder.Width = width; + decoder.Height = height; + decoder.Metadata.HuffmanXSize = this.SubSampleSize(width, numBits); + decoder.Metadata.HuffmanMask = (numBits is 0) ? ~0 : (1 << numBits) - 1; + } + /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// From 6a696d8ca9a2d5be045879f2ecbc2a260b7da394 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 8 Dec 2019 13:56:22 +0100 Subject: [PATCH 051/359] Add tests for lossless images with one transform --- .../Formats/WebP/WebPTransformType.cs | 2 +- .../Formats/WebP/WebPDecoderTests.cs | 80 ++++++++++++++++++- tests/ImageSharp.Tests/TestImages.cs | 37 ++++++++- 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPTransformType.cs b/src/ImageSharp/Formats/WebP/WebPTransformType.cs index ed6e37e0a..96b73161c 100644 --- a/src/ImageSharp/Formats/WebP/WebPTransformType.cs +++ b/src/ImageSharp/Formats/WebP/WebPTransformType.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The goal of the color transform is to decorrelate the R, G and B values of each pixel. /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. /// - ColorTransform = 1, + CrossColorTransform = 1, /// /// The subtract green transform subtracts green values from red and blue values of each pixel. diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index d8c0b1794..2a0559de8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -3,7 +3,6 @@ using System.IO; -using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; @@ -51,8 +50,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Theory] - [WithFile(Lossless.LosslessNoTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.LosslessNoTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) where TPixel : struct, IPixel { @@ -62,5 +61,80 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP image.CompareToOriginal(provider, new MagickReferenceDecoder()); } } + + [Theory] + [WithFile(Lossless.GreenTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform6, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Lossless.ColorIndexTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform6, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform7, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform8, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform9, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform10, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform11, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform12, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform13, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform14, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform15, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform16, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform17, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform3, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform4, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform5, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform6, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Lossless.CrossColorTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.CrossColorTransform2, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5492091d1..01dc13245 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -380,10 +380,39 @@ namespace SixLabors.ImageSharp.Tests { public const string Lossless1 = "WebP/lossless1.webp"; public const string Lossless2 = "WebP/lossless2.webp"; - public const string Lossless3 = "WebP/lossless3.webp"; - public const string Lossless4 = "WebP/lossless4.webp"; - public const string LosslessNoTransform1 = "WebP/lossless_vec_1_0.webp"; - public const string LosslessNoTransform2 = "WebP/lossless_vec_2_0.webp"; + public const string NoTransform1 = "WebP/lossless_vec_1_0.webp"; + public const string NoTransform2 = "WebP/lossless_vec_2_0.webp"; + public const string GreenTransform1 = "WebP/lossless1.webp"; + public const string GreenTransform2 = "WebP/lossless2.webp"; + public const string GreenTransform3 = "WebP/lossless3.webp"; + public const string GreenTransform4 = "WebP/lossless_vec_1_4.webp"; + public const string GreenTransform5 = "WebP/lossless_vec_1_7.webp"; + public const string GreenTransform6 = "WebP/lossless_vec_2_4.webp"; + public const string CrossColorTransform1 = "WebP/lossless_vec_1_8.webp"; + public const string CrossColorTransform2 = "WebP/lossless_vec_2_8.webp"; + public const string PredictorTransform1 = "WebP/lossless_vec_1_10.webp"; + public const string PredictorTransform2 = "WebP/lossless_vec_1_10.webp"; + public const string PredictorTransform3 = "WebP/lossless_vec_1_2.webp"; + public const string PredictorTransform4 = "WebP/lossless_vec_2_10.webp"; + public const string PredictorTransform5 = "WebP/lossless_vec_2_2.webp"; + public const string PredictorTransform6 = "WebP/near_lossless_75.webp"; + public const string ColorIndexTransform1 = "WebP/lossless4.webp"; + public const string ColorIndexTransform2 = "WebP/lossless_vec_1_1.webp"; + public const string ColorIndexTransform3 = "WebP/lossless_vec_1_11.webp"; + public const string ColorIndexTransform4 = "WebP/lossless_vec_1_13.webp"; + public const string ColorIndexTransform5 = "WebP/lossless_vec_1_15.webp"; + public const string ColorIndexTransform6 = "WebP/lossless_vec_1_3.webp"; + public const string ColorIndexTransform7 = "WebP/lossless_vec_1_5.webp"; + public const string ColorIndexTransform8 = "WebP/lossless_vec_1_7.webp"; + public const string ColorIndexTransform9 = "WebP/lossless_vec_1_9.webp"; + public const string ColorIndexTransform10 = "WebP/lossless_vec_2_1.webp"; + public const string ColorIndexTransform11 = "WebP/lossless_vec_2_11.webp"; + public const string ColorIndexTransform12 = "WebP/lossless_vec_2_13.webp"; + public const string ColorIndexTransform13 = "WebP/lossless_vec_2_15.webp"; + public const string ColorIndexTransform14 = "WebP/lossless_vec_2_3.webp"; + public const string ColorIndexTransform15 = "WebP/lossless_vec_2_5.webp"; + public const string ColorIndexTransform16 = "WebP/lossless_vec_2_7.webp"; + public const string ColorIndexTransform17 = "WebP/lossless_vec_2_9.webp"; } public static class Lossy From 31a8b9affa49a9bb8c7a7e4169e619b7129cb378 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 8 Dec 2019 17:43:24 +0100 Subject: [PATCH 052/359] SubtractGreen transform works now --- src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 13 +- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 75 ++++++++++++ src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 4 + src/ImageSharp/Formats/WebP/Vp8LTransform.cs | 38 ++++++ ...PTransformType.cs => Vp8LTransformType.cs} | 2 +- .../Formats/WebP/WebPLosslessDecoder.cs | 114 ++++++++++-------- .../Formats/WebP/WebPLossyDecoder.cs | 4 +- .../Formats/WebP/WebPDecoderTests.cs | 5 +- 8 files changed, 197 insertions(+), 58 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/LosslessUtils.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8LTransform.cs rename src/ImageSharp/Formats/WebP/{WebPTransformType.cs => Vp8LTransformType.cs} (97%) diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index f079dcddd..a52ec3984 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -5,6 +5,9 @@ using System; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// Utility functions related to creating the huffman tables. + /// internal static class HuffmanUtils { public const int HuffmanTableBits = 8; @@ -23,11 +26,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. var sorted = new int[codeLengthsSize]; - int totalSize = 1 << rootBits; // total size root table + 2nd level table - int len; // current code length - int symbol; // symbol index in original or sorted table - var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length - var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length + int totalSize = 1 << rootBits; // total size root table + 2nd level table. + int len; // current code length. + int symbol; // symbol index in original or sorted table. + var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs new file mode 100644 index 000000000..2ba749dc3 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -0,0 +1,75 @@ +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Utility functions for the lossless decoder. + /// + internal static class LosslessUtils + { + /// + /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). + /// + /// The pixel data to apply the transformation. + public static void AddGreenToBlueAndRed(uint[] pixelData) + { + for (int i = 0; i < pixelData.Length; i++) + { + uint argb = pixelData[i]; + uint green = (argb >> 8) & 0xff; + uint redBlue = argb & 0x00ff00ffu; + redBlue += (green << 16) | green; + redBlue &= 0x00ff00ffu; + pixelData[i] = (argb & 0xff00ff00u) | redBlue; + } + } + + public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData, int yEnd) + { + int width = transform.XSize; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int safeWidth = width & ~mask; + int remainingWidth = width - safeWidth; + int tilesPerRow = SubSampleSize(width, transform.Bits); + int y = 0; + + /*uint[] predRow = transform.Data + (y >> transform.Bits) * tilesPerRow; + + while (y < yEnd) + { + uint[] pred = predRow; + VP8LMultipliers m = { 0, 0, 0 }; + const uint32_t* const src_safe_end = src + safeWidth; + const uint32_t* const src_end = src + width; + while (src + /// Computes sampled size of 'size' when sampling using 'sampling bits'. + /// + public static int SubSampleSize(int size, int samplingBits) + { + return (size + (1 << samplingBits) - 1) >> samplingBits; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index 0bc5a0c19..ed827fd3c 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; + namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8LDecoder @@ -17,5 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int Height { get; set; } public Vp8LMetadata Metadata { get; set; } + + public List Transforms { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs new file mode 100644 index 000000000..51863da5f --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Data associated with a VP8L transformation to reduce the entropy. + /// + internal class Vp8LTransform + { + public Vp8LTransform(Vp8LTransformType transformType) => this.TransformType = transformType; + + /// + /// Gets or sets the transform type. + /// + public Vp8LTransformType TransformType { get; private set; } + + /// + /// Subsampling bits defining transform window. + /// + public int Bits { get; set; } + + /// + /// Transform window X index. + /// + public int XSize { get; set; } + + /// + /// Transform window Y index. + /// + public int YSize { get; set; } + + /// + /// Transform data. + /// + public int[] Data { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPTransformType.cs b/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs similarity index 97% rename from src/ImageSharp/Formats/WebP/WebPTransformType.cs rename to src/ImageSharp/Formats/WebP/Vp8LTransformType.cs index 96b73161c..7e1be4deb 100644 --- a/src/ImageSharp/Formats/WebP/WebPTransformType.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. /// Transformations can make the final compression more dense. /// - public enum WebPTransformType : uint + public enum Vp8LTransformType : uint { /// /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index ec71627ea..e754ec654 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Decoder for lossless webp images. + /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp /// /// /// The lossless specification can be found here: @@ -83,35 +83,14 @@ namespace SixLabors.ImageSharp.Formats.WebP { var decoder = new Vp8LDecoder(width, height); uint[] pixelData = this.DecodeImageStream(decoder, width, height, true); - this.DecodePixelValues(width, height, pixelData, pixels); - } - - private void DecodePixelValues(int width, int height, uint[] pixelData, Buffer2D pixels) - where TPixel : struct, IPixel - { - TPixel color = default; - for (int y = 0; y < height; y++) - { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = 0; x < width; x++) - { - int idx = (y * width) + x; - uint pixel = pixelData[idx]; - byte a = (byte)((pixel & 0xFF000000) >> 24); - byte r = (byte)((pixel & 0xFF0000) >> 16); - byte g = (byte)((pixel & 0xFF00) >> 8); - byte b = (byte)(pixel & 0xFF); - color.FromRgba32(new Rgba32(r, g, b, a)); - pixelRow[x] = color; - } - } + this.DecodePixelValues(decoder, pixelData, pixels); } private uint[] DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { if (isLevel0) { - this.ReadTransformations(); + this.ReadTransformations(decoder); } // Color cache. @@ -149,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.UpdateDecoder(decoder, xSize, ySize); - uint[] pixelData = this.DecodeImageData(decoder, xSize, ySize, colorCacheSize, colorCache); + uint[] pixelData = this.DecodeImageData(decoder, colorCacheSize, colorCache); if (!isLevel0) { decoder.Metadata = new Vp8LMetadata(); @@ -158,9 +137,35 @@ namespace SixLabors.ImageSharp.Formats.WebP return pixelData; } - private uint[] DecodeImageData(Vp8LDecoder decoder, int width, int height, int colorCacheSize, ColorCache colorCache) + private void DecodePixelValues(Vp8LDecoder decoder, uint[] pixelData, Buffer2D pixels) + where TPixel : struct, IPixel + { + // Apply reverse transformations, if any are present. + this.ApplyInverseTransforms(decoder, pixelData); + + TPixel color = default; + for (int y = 0; y < decoder.Height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + for (int x = 0; x < decoder.Width; x++) + { + int idx = (y * decoder.Width) + x; + uint pixel = pixelData[idx]; + byte a = (byte)((pixel & 0xFF000000) >> 24); + byte r = (byte)((pixel & 0xFF0000) >> 16); + byte g = (byte)((pixel & 0xFF00) >> 8); + byte b = (byte)(pixel & 0xFF); + color.FromRgba32(new Rgba32(r, g, b, a)); + pixelRow[x] = color; + } + } + } + + private uint[] DecodeImageData(Vp8LDecoder decoder, int colorCacheSize, ColorCache colorCache) { int lastPixel = 0; + int width = decoder.Width; + int height = decoder.Height; int row = lastPixel / width; int col = lastPixel % width; int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes; @@ -327,8 +332,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { // Use meta Huffman codes. uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; - int huffmanXSize = this.SubSampleSize(xSize, (int)huffmanPrecision); - int huffmanYSize = this.SubSampleSize(ySize, (int)huffmanPrecision); + int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision); + int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision); int huffmanPixs = huffmanXSize * huffmanYSize; uint[] huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision; @@ -562,22 +567,26 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void ReadTransformations() + /// + /// Reads the transformations, if any are present. + /// + /// Vp8LDecoder where the transformations will be stored. + private void ReadTransformations(Vp8LDecoder decoder) { // Next bit indicates, if a transformation is present. bool transformPresent = this.bitReader.ReadBit(); int numberOfTransformsPresent = 0; - var transforms = new List(WebPConstants.MaxNumberOfTransforms); + decoder.Transforms = new List(WebPConstants.MaxNumberOfTransforms); while (transformPresent) { - var transformType = (WebPTransformType)this.bitReader.ReadBits(2); - transforms.Add(transformType); + var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); + var transform = new Vp8LTransform(transformType); switch (transformType) { - case WebPTransformType.SubtractGreen: + case Vp8LTransformType.SubtractGreen: // There is no data associated with this transform. break; - case WebPTransformType.ColorIndexingTransform: + case Vp8LTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. uint colorTableSize = this.bitReader.ReadBits(8) + 1; @@ -585,7 +594,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: color table should follow here? break; - case WebPTransformType.PredictorTransform: + case Vp8LTransformType.PredictorTransform: { // The first 3 bits of prediction data define the block width and height in number of bits. // The number of block columns, block_xsize, is used in indexing two-dimensionally. @@ -596,7 +605,7 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - case WebPTransformType.ColorTransform: + case Vp8LTransformType.CrossColorTransform: { // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, // just like the predictor transform: @@ -607,16 +616,35 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + decoder.Transforms.Add(transform); numberOfTransformsPresent++; transformPresent = this.bitReader.ReadBit(); if (numberOfTransformsPresent == WebPConstants.MaxNumberOfTransforms && transformPresent) { - WebPThrowHelper.ThrowImageFormatException("The maximum number of transforms was exceeded"); + WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); } } + } - // TODO: return transformation in an appropriate form. + /// + /// Reverses the transformations, if any are present. + /// + /// The decoder holding the transformation infos. + /// The pixel data to apply the transformation. + private void ApplyInverseTransforms(Vp8LDecoder decoder, uint[] pixelData) + { + List transforms = decoder.Transforms; + for (int i = transforms.Count; i > 0; i--) + { + Vp8LTransformType transform = transforms[0].TransformType; + switch (transform) + { + case Vp8LTransformType.SubtractGreen: + LosslessUtils.AddGreenToBlueAndRed(pixelData); + break; + } + } } private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) @@ -624,18 +652,10 @@ namespace SixLabors.ImageSharp.Formats.WebP int numBits = decoder.Metadata.HuffmanSubSampleBits; decoder.Width = width; decoder.Height = height; - decoder.Metadata.HuffmanXSize = this.SubSampleSize(width, numBits); + decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); decoder.Metadata.HuffmanMask = (numBits is 0) ? ~0 : (1 << numBits) - 1; } - /// - /// Computes sampled size of 'size' when sampling using 'sampling bits'. - /// - private int SubSampleSize(int size, int samplingBits) - { - return (size + (1 << samplingBits) - 1) >> samplingBits; - } - /// /// Decodes the next Huffman code from bit-stream. /// FillBitWindow(br) needs to be called at minimum every second call diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 32d38ba33..47acbae70 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Text; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -9,7 +7,7 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - class WebPLossyDecoder + internal sealed class WebPLossyDecoder { private readonly Configuration configuration; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 2a0559de8..87ba6bce4 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -67,8 +67,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform6, PixelTypes.Rgba32)] + // TODO: Figure out whats wrong with those two images + //[WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] + //[WithFile(Lossless.GreenTransform6, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform(TestImageProvider provider) where TPixel : struct, IPixel { From 137107a4208331615aaf31136cef83c6d7ae71ea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 8 Dec 2019 19:00:59 +0100 Subject: [PATCH 053/359] Reading transformation data --- src/ImageSharp/Formats/WebP/Vp8LTransform.cs | 21 ++++++----- .../Formats/WebP/WebPLosslessDecoder.cs | 36 ++++++++----------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs index 51863da5f..b232f4688 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -8,12 +8,17 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8LTransform { - public Vp8LTransform(Vp8LTransformType transformType) => this.TransformType = transformType; + public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) + { + this.TransformType = transformType; + this.XSize = xSize; + this.YSize = ySize; + } /// - /// Gets or sets the transform type. + /// Gets the transform type. /// - public Vp8LTransformType TransformType { get; private set; } + public Vp8LTransformType TransformType { get; } /// /// Subsampling bits defining transform window. @@ -21,18 +26,18 @@ namespace SixLabors.ImageSharp.Formats.WebP public int Bits { get; set; } /// - /// Transform window X index. + /// Gets or sets the transform window X index. /// public int XSize { get; set; } /// - /// Transform window Y index. + /// Gets the transform window Y index. /// - public int YSize { get; set; } + public int YSize { get; } /// - /// Transform data. + /// Gets or sets the transform data. /// - public int[] Data { get; set; } + public uint[] Data { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index e754ec654..7a8588946 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (isLevel0) { - this.ReadTransformations(decoder); + this.ReadTransformations(xSize, ySize, decoder); } // Color cache. @@ -571,7 +571,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Reads the transformations, if any are present. /// /// Vp8LDecoder where the transformations will be stored. - private void ReadTransformations(Vp8LDecoder decoder) + private void ReadTransformations(int xSize, int ySize, Vp8LDecoder decoder) { // Next bit indicates, if a transformation is present. bool transformPresent = this.bitReader.ReadBit(); @@ -580,7 +580,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (transformPresent) { var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); - var transform = new Vp8LTransform(transformType); + var transform = new Vp8LTransform(transformType, xSize, ySize); switch (transformType) { case Vp8LTransformType.SubtractGreen: @@ -589,29 +589,23 @@ namespace SixLabors.ImageSharp.Formats.WebP case Vp8LTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. - uint colorTableSize = this.bitReader.ReadBits(8) + 1; - - // TODO: color table should follow here? + uint numColors = this.bitReader.ReadBits(8) + 1; + int bits = (numColors > 16) ? 0 + : (numColors > 4) ? 1 + : (numColors > 2) ? 2 + : 3; + transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); break; case Vp8LTransformType.PredictorTransform: - { - // The first 3 bits of prediction data define the block width and height in number of bits. - // The number of block columns, block_xsize, is used in indexing two-dimensionally. - uint sizeBits = this.bitReader.ReadBits(3) + 2; - int blockWidth = 1 << (int)sizeBits; - int blockHeight = 1 << (int)sizeBits; - - break; - } - case Vp8LTransformType.CrossColorTransform: { - // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, - // just like the predictor transform: - uint sizeBits = this.bitReader.ReadBits(3) + 2; - int blockWidth = 1 << (int)sizeBits; - int blockHeight = 1 << (int)sizeBits; + transform.Bits = (int)this.bitReader.ReadBits(3) + 2; + transform.Data = this.DecodeImageStream( + decoder, + LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), + LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), + false); break; } } From 2e8109515cb3840af1fe5c888a4ce471b00ec5e7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Dec 2019 21:20:10 +0100 Subject: [PATCH 054/359] Fix an issue reading the transformations and fixed the testimages accordingly --- .../Formats/WebP/WebPLosslessDecoder.cs | 91 ++++++++++--------- .../Formats/WebP/WebPDecoderTests.cs | 19 +--- tests/ImageSharp.Tests/TestImages.cs | 29 ++---- 3 files changed, 53 insertions(+), 86 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 7a8588946..d12325f38 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -88,9 +88,22 @@ namespace SixLabors.ImageSharp.Formats.WebP private uint[] DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { + int numberOfTransformsPresent = 0; if (isLevel0) { - this.ReadTransformations(xSize, ySize, decoder); + decoder.Transforms = new List(WebPConstants.MaxNumberOfTransforms); + + // Next bit indicates, if a transformation is present. + while (this.bitReader.ReadBit()) + { + if (numberOfTransformsPresent > WebPConstants.MaxNumberOfTransforms) + { + WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); + } + + this.ReadTransformation(xSize, ySize, decoder); + numberOfTransformsPresent++; + } } // Color cache. @@ -571,54 +584,42 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Reads the transformations, if any are present. /// /// Vp8LDecoder where the transformations will be stored. - private void ReadTransformations(int xSize, int ySize, Vp8LDecoder decoder) + private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) { - // Next bit indicates, if a transformation is present. - bool transformPresent = this.bitReader.ReadBit(); - int numberOfTransformsPresent = 0; - decoder.Transforms = new List(WebPConstants.MaxNumberOfTransforms); - while (transformPresent) + var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); + var transform = new Vp8LTransform(transformType, xSize, ySize); + switch (transformType) { - var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); - var transform = new Vp8LTransform(transformType, xSize, ySize); - switch (transformType) - { - case Vp8LTransformType.SubtractGreen: - // There is no data associated with this transform. - break; - case Vp8LTransformType.ColorIndexingTransform: - // The transform data contains color table size and the entries in the color table. - // 8 bit value for color table size. - uint numColors = this.bitReader.ReadBits(8) + 1; - int bits = (numColors > 16) ? 0 - : (numColors > 4) ? 1 - : (numColors > 2) ? 2 - : 3; - transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); - break; - - case Vp8LTransformType.PredictorTransform: - case Vp8LTransformType.CrossColorTransform: - { - transform.Bits = (int)this.bitReader.ReadBits(3) + 2; - transform.Data = this.DecodeImageStream( - decoder, - LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), - LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), - false); - break; - } - } - - decoder.Transforms.Add(transform); - numberOfTransformsPresent++; + case Vp8LTransformType.SubtractGreen: + // There is no data associated with this transform. + break; + case Vp8LTransformType.ColorIndexingTransform: + // The transform data contains color table size and the entries in the color table. + // 8 bit value for color table size. + uint numColors = this.bitReader.ReadBits(8) + 1; + int bits = (numColors > 16) ? 0 + : (numColors > 4) ? 1 + : (numColors > 2) ? 2 + : 3; + transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); + transform.Bits = bits; + transform.Data = this.DecodeImageStream(decoder, (int)numColors, 1, false); + break; - transformPresent = this.bitReader.ReadBit(); - if (numberOfTransformsPresent == WebPConstants.MaxNumberOfTransforms && transformPresent) - { - WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded"); - } + case Vp8LTransformType.PredictorTransform: + case Vp8LTransformType.CrossColorTransform: + { + transform.Bits = (int)this.bitReader.ReadBits(3) + 2; + transform.Data = this.DecodeImageStream( + decoder, + LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), + LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), + false); + break; + } } + + decoder.Transforms.Add(transform); } /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 87ba6bce4..538cf29dd 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -67,9 +67,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] - // TODO: Figure out whats wrong with those two images + // TODO: Reference decoder throws here MagickCorruptImageErrorException //[WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] - //[WithFile(Lossless.GreenTransform6, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform(TestImageProvider provider) where TPixel : struct, IPixel { @@ -86,18 +85,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] [WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] [WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform6, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform7, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform8, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform9, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform10, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform11, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform12, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform13, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform14, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform15, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform16, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform17, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) where TPixel : struct, IPixel { @@ -111,10 +98,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] [WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform3, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform4, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform5, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform6, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 01dc13245..e030db2f2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -386,33 +386,16 @@ namespace SixLabors.ImageSharp.Tests public const string GreenTransform2 = "WebP/lossless2.webp"; public const string GreenTransform3 = "WebP/lossless3.webp"; public const string GreenTransform4 = "WebP/lossless_vec_1_4.webp"; - public const string GreenTransform5 = "WebP/lossless_vec_1_7.webp"; - public const string GreenTransform6 = "WebP/lossless_vec_2_4.webp"; + public const string GreenTransform5 = "WebP/lossless_vec_2_4.webp"; public const string CrossColorTransform1 = "WebP/lossless_vec_1_8.webp"; public const string CrossColorTransform2 = "WebP/lossless_vec_2_8.webp"; - public const string PredictorTransform1 = "WebP/lossless_vec_1_10.webp"; - public const string PredictorTransform2 = "WebP/lossless_vec_1_10.webp"; - public const string PredictorTransform3 = "WebP/lossless_vec_1_2.webp"; - public const string PredictorTransform4 = "WebP/lossless_vec_2_10.webp"; - public const string PredictorTransform5 = "WebP/lossless_vec_2_2.webp"; - public const string PredictorTransform6 = "WebP/near_lossless_75.webp"; + public const string PredictorTransform1 = "WebP/lossless_vec_1_2.webp"; + public const string PredictorTransform2 = "WebP/lossless_vec_2_2.webp"; public const string ColorIndexTransform1 = "WebP/lossless4.webp"; public const string ColorIndexTransform2 = "WebP/lossless_vec_1_1.webp"; - public const string ColorIndexTransform3 = "WebP/lossless_vec_1_11.webp"; - public const string ColorIndexTransform4 = "WebP/lossless_vec_1_13.webp"; - public const string ColorIndexTransform5 = "WebP/lossless_vec_1_15.webp"; - public const string ColorIndexTransform6 = "WebP/lossless_vec_1_3.webp"; - public const string ColorIndexTransform7 = "WebP/lossless_vec_1_5.webp"; - public const string ColorIndexTransform8 = "WebP/lossless_vec_1_7.webp"; - public const string ColorIndexTransform9 = "WebP/lossless_vec_1_9.webp"; - public const string ColorIndexTransform10 = "WebP/lossless_vec_2_1.webp"; - public const string ColorIndexTransform11 = "WebP/lossless_vec_2_11.webp"; - public const string ColorIndexTransform12 = "WebP/lossless_vec_2_13.webp"; - public const string ColorIndexTransform13 = "WebP/lossless_vec_2_15.webp"; - public const string ColorIndexTransform14 = "WebP/lossless_vec_2_3.webp"; - public const string ColorIndexTransform15 = "WebP/lossless_vec_2_5.webp"; - public const string ColorIndexTransform16 = "WebP/lossless_vec_2_7.webp"; - public const string ColorIndexTransform17 = "WebP/lossless_vec_2_9.webp"; + public const string ColorIndexTransform3 = "WebP/lossless_vec_1_5.webp"; + public const string ColorIndexTransform4 = "WebP/lossless_vec_2_1.webp"; + public const string ColorIndexTransform5 = "WebP/lossless_vec_2_5.webp"; } public static class Lossy From f907e67ef44242c050a3656c1941e0477d4f28bf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Dec 2019 19:49:18 +0100 Subject: [PATCH 055/359] Additional tests for images with more than one transform --- .../Formats/WebP/WebPDecoderTests.cs | 46 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 24 ++++++++++ 2 files changed, 70 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 538cf29dd..43627b48b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -120,5 +120,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP image.CompareToOriginal(provider, new MagickReferenceDecoder()); } } + + [Theory] + [WithFile(Lossless.TwoTransforms1, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms2, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms3, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms4, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms5, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms6, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms7, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms8, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms9, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms10, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms14, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms15, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms16, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Lossless.ThreeTransforms1, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms2, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms3, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms4, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms8, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new WebPDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e030db2f2..73a4abc78 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -396,6 +396,30 @@ namespace SixLabors.ImageSharp.Tests public const string ColorIndexTransform3 = "WebP/lossless_vec_1_5.webp"; public const string ColorIndexTransform4 = "WebP/lossless_vec_2_1.webp"; public const string ColorIndexTransform5 = "WebP/lossless_vec_2_5.webp"; + public const string TwoTransforms1 = "Webp/lossless_color_transform.webp"; // cross_color, predictor + public const string TwoTransforms2 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor + public const string TwoTransforms3 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green + public const string TwoTransforms4 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color + public const string TwoTransforms5 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor + public const string TwoTransforms6 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor + public const string TwoTransforms7 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor + public const string TwoTransforms8 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color + public const string TwoTransforms9 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color + public const string TwoTransforms10 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color + public const string TwoTransforms11 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color + public const string TwoTransforms12 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor + public const string TwoTransforms13 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor + public const string TwoTransforms14 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor + public const string TwoTransforms15 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor + public const string TwoTransforms16 = "Webp/near_lossless_75.webp"; // predictor, cross_color + public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms2 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms3 = "Webp/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms4 = "Webp/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms5 = "Webp/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms6 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms7 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms8 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color } public static class Lossy From 47926752d3db062ac008f7031ce5307fc28efd9b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Dec 2019 20:58:08 +0100 Subject: [PATCH 056/359] Implemented ColorSpaceInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 78 ++++++++++++++----- .../Formats/WebP/WebPLosslessDecoder.cs | 9 ++- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 2ba749dc3..20e502ef6 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -22,46 +22,65 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData, int yEnd) + public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData) { int width = transform.XSize; + int yEnd = transform.YSize; int tileWidth = 1 << transform.Bits; int mask = tileWidth - 1; int safeWidth = width & ~mask; int remainingWidth = width - safeWidth; int tilesPerRow = SubSampleSize(width, transform.Bits); int y = 0; + uint predRow = transform.Data[(y >> transform.Bits) * tilesPerRow]; - /*uint[] predRow = transform.Data + (y >> transform.Bits) * tilesPerRow; - + int pixelPos = 0; while (y < yEnd) { - uint[] pred = predRow; - VP8LMultipliers m = { 0, 0, 0 }; - const uint32_t* const src_safe_end = src + safeWidth; - const uint32_t* const src_end = src + width; - while (src> 8); + uint red = argb >> 16; + int newRed = (int)(red & 0xff); + int newBlue = (int)argb & 0xff; + newRed += ColorTransformDelta(m.GreenToRed, (sbyte)green); + newRed &= 0xff; + newBlue += ColorTransformDelta(m.GreenToBlue, (sbyte)green); + newBlue += ColorTransformDelta(m.RedToBlue, (sbyte)newRed); + newBlue &= 0xff; + var pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + } } /// @@ -71,5 +90,26 @@ namespace SixLabors.ImageSharp.Formats.WebP { return (size + (1 << samplingBits) - 1) >> samplingBits; } + + private static int ColorTransformDelta(sbyte colorPred, sbyte color) + { + return ((int)colorPred * color) >> 5; + } + + private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) + { + m.GreenToRed = (sbyte)(colorCode & 0xff); + m.GreenToBlue = (sbyte)((colorCode >> 8) & 0xff); + m.RedToBlue = (sbyte)((colorCode >> 16) & 0xff); + } + + internal struct Vp8LMultipliers + { + public sbyte GreenToRed; + + public sbyte GreenToBlue; + + public sbyte RedToBlue; + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index d12325f38..341af8843 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -630,14 +630,17 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ApplyInverseTransforms(Vp8LDecoder decoder, uint[] pixelData) { List transforms = decoder.Transforms; - for (int i = transforms.Count; i > 0; i--) + for (int i = transforms.Count - 1; i >= 0; i--) { - Vp8LTransformType transform = transforms[0].TransformType; - switch (transform) + Vp8LTransformType transformType = transforms[i].TransformType; + switch (transformType) { case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); break; + case Vp8LTransformType.CrossColorTransform: + LosslessUtils.ColorSpaceInverseTransform(transforms[i], pixelData); + break; } } } From c9b3f1fa25cf8f5b43f411524c728ae3c4f67a66 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Dec 2019 16:22:58 +0100 Subject: [PATCH 057/359] Add ColorIndexInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 114 +++++++++++++++++- .../Formats/WebP/WebPLosslessDecoder.cs | 9 +- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 20e502ef6..5eac30f1f 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -1,3 +1,6 @@ +using System; +using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -22,6 +25,57 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + public static void ColorIndexInverseTransform(Vp8LTransform transform, uint[] pixelData) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + int height = transform.YSize; + uint[] colorMap = transform.Data; + int decodedPixels = 0; + if (bitsPerPixel < 8) + { + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; + + // TODO: use memoryAllocator here + var decodedPixelData = new uint[width * height]; + int pixelDataPos = 0; + for (int y = 0; y < height; ++y) + { + uint packedPixels = 0; + for (int x = 0; x < width; ++x) + { + // We need to load fresh 'packed_pixels' once every + // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte + // is a power of 2, so can just use a mask for that, instead of + // decrementing a counter. + if ((x & countMask) is 0) + { + packedPixels = GetARGBIndex(pixelData[pixelDataPos++]); + } + + decodedPixelData[decodedPixels++] = colorMap[packedPixels & bitMask]; + packedPixels >>= bitsPerPixel; + } + } + + decodedPixelData.AsSpan().CopyTo(pixelData); + + return; + } + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + uint colorMapIndex = GetARGBIndex(pixelData[decodedPixels]); + pixelData[decodedPixels] = colorMap[colorMapIndex]; + decodedPixels++; + } + } + } + public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData) { int width = transform.XSize; @@ -78,11 +132,36 @@ namespace SixLabors.ImageSharp.Formats.WebP newBlue += ColorTransformDelta(m.GreenToBlue, (sbyte)green); newBlue += ColorTransformDelta(m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; - var pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + // uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); } } + public static uint[] ExpandColorMap(int numColors, Vp8LTransform transform, uint[] transformData) + { + int finalNumColors = 1 << (8 >> transform.Bits); + + // TODO: use memoryAllocator here + var newColorMap = new uint[finalNumColors]; + newColorMap[0] = transformData[0]; + + Span data = MemoryMarshal.Cast(transformData); + Span newData = MemoryMarshal.Cast(newColorMap); + int i; + for (i = 4; i < 4 * numColors; ++i) + { + // Equivalent to AddPixelEq(), on a byte-basis. + newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); + } + + for (; i < 4 * finalNumColors; ++i) + { + newData[i] = 0; // black tail. + } + + return newColorMap; + } + /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// @@ -91,6 +170,39 @@ namespace SixLabors.ImageSharp.Formats.WebP return (size + (1 << samplingBits) - 1) >> samplingBits; } + /// + /// Sum of each component, mod 256. + /// + private static uint AddPixels(uint a, uint b) + { + uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); + uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } + + /// + /// Difference of each component, mod 256. + /// + private static uint SubPixels(uint a, uint b) + { + uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } + + private static void PredictorAdd1(uint[] pixelData, int numPixels) + { + /*for (int i = 0; i < num_pixels; ++i) + { + pixelData[i] = VP8LAddPixels(in[i], left); + }*/ + } + + private static uint GetARGBIndex(uint idx) + { + return (idx >> 8) & 0xff; + } + private static int ColorTransformDelta(sbyte colorPred, sbyte color) { return ((int)colorPred * color) >> 5; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 341af8843..24104b494 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -603,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.WebP : 3; transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); transform.Bits = bits; - transform.Data = this.DecodeImageStream(decoder, (int)numColors, 1, false); + uint[] colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false); + transform.Data = LosslessUtils.ExpandColorMap((int)numColors, transform, colorMap); break; case Vp8LTransformType.PredictorTransform: @@ -635,12 +636,18 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8LTransformType transformType = transforms[i].TransformType; switch (transformType) { + case Vp8LTransformType.PredictorTransform: + // LosslessUtils.PredictorInverseTransform(transforms[i], pixelData); + break; case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); break; case Vp8LTransformType.CrossColorTransform: LosslessUtils.ColorSpaceInverseTransform(transforms[i], pixelData); break; + case Vp8LTransformType.ColorIndexingTransform: + LosslessUtils.ColorIndexInverseTransform(transforms[i], pixelData); + break; } } } From beca5f535215ed4b08bdfd81601e64f93f1d24d9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Dec 2019 17:43:47 +0100 Subject: [PATCH 058/359] Fix some warnings --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 26 +++++++++++ src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 45 ++++++++++--------- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 7 ++- .../Formats/WebP/WebPLosslessDecoder.cs | 16 +++---- .../Formats/WebP/WebPLossyDecoder.cs | 2 +- src/ImageSharp/Formats/WebP/WebPMetadata.cs | 9 ++-- 7 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 5eac30f1f..d6996dc9a 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Runtime.InteropServices; @@ -132,6 +135,7 @@ namespace SixLabors.ImageSharp.Formats.WebP newBlue += ColorTransformDelta(m.GreenToBlue, (sbyte)green); newBlue += ColorTransformDelta(m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; + // uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); } @@ -162,6 +166,28 @@ namespace SixLabors.ImageSharp.Formats.WebP return newColorMap; } + public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData) + { + int processedPixels = 0; + int yStart = 0; + int width = transform.XSize; + + // PredictorAdd0(in, NULL, 1, out); + PredictorAdd1(pixelData, width - 1); + processedPixels += width; + yStart++; + + int y = yStart; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int tilesPerRow = SubSampleSize(width, transform.Bits); + } + + private static uint Predictor0C(uint left, uint[] top) + { + return WebPConstants.ArgbBlack; + } + /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index cdcca61e9..0bb69311f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private const int VP8L_WBITS = 32; - private uint[] kBitMask = + private readonly uint[] kBitMask = { 0, 0x000001, 0x000003, 0x000007, 0x00000f, @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Buffer length. /// - private long len; + private readonly long len; /// /// Byte position in buffer. diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index c54f72073..6f0c4618c 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -52,54 +52,57 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Signature byte which identifies a VP8L header. /// - public static byte Vp8LMagicByte = 0x2F; + public const byte Vp8LMagicByte = 0x2F; /// /// 3 bits reserved for version. /// - public static int Vp8LVersionBits = 3; + public const int Vp8LVersionBits = 3; /// /// Bits for width and height infos of a VPL8 image. /// - public static int Vp8LImageSizeBits = 14; + public const int Vp8LImageSizeBits = 14; /// /// Maximum number of color cache bits. /// - public static int MaxColorCacheBits = 11; + public const int MaxColorCacheBits = 11; /// /// The maximum number of allowed transforms in a bitstream. /// - public static int MaxNumberOfTransforms = 4; + public const int MaxNumberOfTransforms = 4; - public static int MaxAllowedCodeLength = 15; + public const int MaxAllowedCodeLength = 15; - public static int DefaultCodeLength = 8; + public const int DefaultCodeLength = 8; - public static int HuffmanCodesPerMetaCode = 5; + public const int HuffmanCodesPerMetaCode = 5; - public static int NumLiteralCodes = 256; + public const uint ArgbBlack = 0xff000000; - public static int NumLengthCodes = 24; + public const int NumLiteralCodes = 256; - public static int NumDistanceCodes = 40; + public const int NumLengthCodes = 24; - public static int LengthTableBits = 7; + public const int NumDistanceCodes = 40; - public static uint kCodeLengthLiterals = 16; + public const int LengthTableBits = 7; - public static int kCodeLengthRepeatCode = 16; + public const uint KCodeLengthLiterals = 16; - public static int[] kCodeLengthExtraBits = { 2, 3, 7 }; + public const int KCodeLengthRepeatCode = 16; - public static int[] kCodeLengthRepeatOffsets = { 3, 3, 11 }; + public static readonly int[] KCodeLengthExtraBits = { 2, 3, 7 }; - public static int[] kAlphabetSize = { - NumLiteralCodes + NumLengthCodes, - NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, - NumDistanceCodes - }; + public static readonly int[] KCodeLengthRepeatOffsets = { 3, 3, 11 }; + + public static readonly int[] KAlphabetSize = + { + NumLiteralCodes + NumLengthCodes, + NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, + NumDistanceCodes + }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index f68cc00b1..35e27f721 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int Height { get; set; } /// - /// Gets or sets whether this image uses a lossless compression. + /// Gets or sets a value indicating whether this image uses a lossless compression. /// public bool IsLossLess { get; set; } @@ -26,12 +26,11 @@ namespace SixLabors.ImageSharp.Formats.WebP public WebPFeatures Features { get; set; } /// - /// The bytes of the image payload. + /// Gets or sets the bytes of the image payload. /// public uint ImageDataSize { get; set; } - // TODO: not sure if the bitreader is in the right place here, but for the sake of simplicity it will stay here for now. - // Will be refactored later. + // TODO: not sure if the bitreader is in the right place here, but for the sake of simplicity it will stay here for now. Will be refactored later. public Vp8LBitReader Vp9LBitReader { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 24104b494..21686d552 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -368,7 +368,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Find maximum alphabet size for the hTree group. for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.kAlphabetSize[j]; + int alphabetSize = WebPConstants.KAlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -394,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var codeLengths = new int[maxAlphabetSize]; for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.kAlphabetSize[j]; + int alphabetSize = WebPConstants.KAlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -548,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.WebP HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); uint codeLen = huffmanCode.Value; - if (codeLen < WebPConstants.kCodeLengthLiterals) + if (codeLen < WebPConstants.KCodeLengthLiterals) { codeLengths[symbol++] = (int)codeLen; if (codeLen != 0) @@ -558,10 +558,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - bool usePrev = codeLen == WebPConstants.kCodeLengthRepeatCode; - uint slot = codeLen - WebPConstants.kCodeLengthLiterals; - int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; - int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; + bool usePrev = codeLen == WebPConstants.KCodeLengthRepeatCode; + uint slot = codeLen - WebPConstants.KCodeLengthLiterals; + int extraBits = WebPConstants.KCodeLengthExtraBits[slot]; + int repeatOffset = WebPConstants.KCodeLengthRepeatOffsets[slot]; int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { @@ -637,7 +637,7 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (transformType) { case Vp8LTransformType.PredictorTransform: - // LosslessUtils.PredictorInverseTransform(transforms[i], pixelData); + LosslessUtils.PredictorInverseTransform(transforms[i], pixelData); break; case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 47acbae70..cd8f98678 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } byte version = (byte)((bitReader.ReadBit() ? 2 : 0) | (bitReader.ReadBit() ? 1 : 0)); - (ReconstructionFilter rec, LoopFilter loop) = DecodeVersion(version); + (ReconstructionFilter rec, LoopFilter loop) = this.DecodeVersion(version); bool isShowFrame = bitReader.ReadBit(); diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 69e9a43f4..65b09d5ec 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections; using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP @@ -28,9 +27,6 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Format = other.Format; } - /// - public IDeepCloneable DeepClone() => new WebPMetadata(this); - /// /// The webp format used. Either lossless or lossy. /// @@ -42,8 +38,11 @@ namespace SixLabors.ImageSharp.Formats.WebP public Queue ChunkTypes { get; set; } = new Queue(); /// - /// Indicates, if the webp file contains a animation. + /// Gets or sets a value indicating whether the webp file contains a animation. /// public bool Animated { get; set; } = false; + + /// + public IDeepCloneable DeepClone() => new WebPMetadata(this); } } From 21b7bdb67818977cb3ea879337051c6a5cadcf3e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Dec 2019 18:47:42 +0100 Subject: [PATCH 059/359] Add helper methods for PredictorInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 162 +++++++++++++++++-- 1 file changed, 153 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index d6996dc9a..c2d292395 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = transform.XSize; // PredictorAdd0(in, NULL, 1, out); - PredictorAdd1(pixelData, width - 1); + // PredictorAdd1(pixelData, width - 1); processedPixels += width; yStart++; @@ -188,6 +188,158 @@ namespace SixLabors.ImageSharp.Formats.WebP return WebPConstants.ArgbBlack; } + private static uint Predictor1C(uint left, uint[] top) + { + return left; + } + + private static uint Predictor2C(uint left, uint[] top) + { + return top[0]; + } + + private static uint Predictor3C(uint left, uint[] top) + { + return top[1]; + } + + private static uint Predictor4C(uint left, uint[] top) + { + return top[-1]; + } + + private static uint Predictor5C(uint left, uint[] top) + { + uint pred = Average3(left, top[0], top[1]); + return pred; + } + + private static uint Predictor6C(uint left, uint[] top) + { + uint pred = Average2(left, top[-1]); + return pred; + } + + private static uint Predictor7C(uint left, uint[] top) + { + uint pred = Average2(left, top[0]); + return pred; + } + + private static uint Predictor8C(uint left, uint[] top) + { + uint pred = Average2(top[-1], top[0]); + return pred; + } + + private static uint Predictor9C(uint left, uint[] top) + { + uint pred = Average2(top[0], top[1]); + return pred; + } + + private static uint Predictor10C(uint left, uint[] top) + { + uint pred = Average4(left, top[-1], top[0], top[1]); + return pred; + } + + private static uint Predictor11C(uint left, uint[] top) + { + uint pred = Select(top[0], left, top[-1]); + return pred; + } + + private static uint Predictor12C(uint left, uint[] top) + { + uint pred = ClampedAddSubtractFull(left, top[0], top[-1]); + return pred; + } + + private static uint Predictor13C(uint left, uint[] top) + { + uint pred = ClampedAddSubtractHalf(left, top[0], top[-1]); + return pred; + } + + private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) + { + int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); + int r = AddSubtractComponentFull((c0 >> 16) & 0xff, + (c1 >> 16) & 0xff, + (c2 >> 16) & 0xff); + int g = AddSubtractComponentFull((c0 >> 8) & 0xff, + (c1 >> 8) & 0xff, + (c2 >> 8) & 0xff); + int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); + return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); + } + + private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) + { + uint ave = Average2(c0, c1); + int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24); + int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); + int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); + int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); + return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); + } + + private static int AddSubtractComponentHalf(uint a, uint b) + { + return (int)Clip255(a + ((a - b) / 2)); + } + + private static int AddSubtractComponentFull(uint a, uint b, uint c) + { + return (int)Clip255(a + b - c); + } + + private static uint Clip255(uint a) + { + if (a < 256) + { + return a; + } + + // return 0, when a is a negative integer. + // return 255, when a is positive. + return ~a >> 24; + } + + private static uint Select(uint a, uint b, uint c) + { + int paMinusPb = + Sub3(a >> 24, b >> 24, c >> 24) + + Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + + Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + + Sub3( a & 0xff, b & 0xff, c & 0xff); + return (paMinusPb <= 0) ? a : b; + } + + private static int Sub3(uint a, uint b, uint c) + { + uint pb = b - c; + uint pa = a - c; + return (int)(Math.Abs(pb) - Math.Abs(pa)); + } + + private static uint Average2(uint a0, uint a1) + { + return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); + } + + private static uint Average3(uint a0, uint a1, uint a2) + { + return Average2(Average2(a0, a2), a1); + } + + private static uint Average4(uint a0, uint a1, uint a2, uint a3) + { + return Average2(Average2(a0, a1), Average2(a2, a3)); + } + + /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// @@ -216,14 +368,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } - private static void PredictorAdd1(uint[] pixelData, int numPixels) - { - /*for (int i = 0; i < num_pixels; ++i) - { - pixelData[i] = VP8LAddPixels(in[i], left); - }*/ - } - private static uint GetARGBIndex(uint idx) { return (idx >> 8) & 0xff; From e4834adf1ba3ebdafef791cc1f0f87c4f14af57b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Dec 2019 18:56:33 +0100 Subject: [PATCH 060/359] Add PredictorAdd methods --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 176 +++++++++++++++++-- 1 file changed, 159 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index c2d292395..ff30b2609 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -168,95 +168,237 @@ namespace SixLabors.ImageSharp.Formats.WebP public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData) { + // TODO: use memory allocator instead + var output = new uint[pixelData.Length]; + int processedPixels = 0; int yStart = 0; int width = transform.XSize; - // PredictorAdd0(in, NULL, 1, out); - // PredictorAdd1(pixelData, width - 1); - processedPixels += width; + // First Row follows the L (mode=1) mode. + PredictorAdd0(pixelData, null, processedPixels++, 1, output); + PredictorAdd1(pixelData, null, processedPixels, width - 1, output); + processedPixels += width - 1; yStart++; int y = yStart; + int yEnd = transform.YSize; int tileWidth = 1 << transform.Bits; int mask = tileWidth - 1; int tilesPerRow = SubSampleSize(width, transform.Bits); + + while (y < yEnd) + { + int x = 1; + + // First pixel follows the T (mode=2) mode. + PredictorAdd2(pixelData, out-width, 1, output); + + // .. the rest: + } + } + + // TODO: the pridictor add methods should be generated + private static void PredictorAdd0(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor0(); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd1(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + uint left = output[startIdx - 1]; + for (int i = 0; i < numberOfPixels; ++i) + { + output[i] = left = AddPixels(input[i], left); + } + } + + private static void PredictorAdd2(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor2(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd3(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor3(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd4(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor4(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd5(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor5(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd6(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor6(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd7(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor7(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd8(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor8(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd9(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor9(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd10(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor10(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd11(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor11(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd12(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor12(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } + } + + private static void PredictorAdd13(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + { + for (int x = startIdx; x < numberOfPixels; ++x) + { + uint pred = Predictor13(output[x - 1], upper.AsSpan(x)); + output[x] = AddPixels(input[x], pred); + } } - private static uint Predictor0C(uint left, uint[] top) + private static uint Predictor0() { return WebPConstants.ArgbBlack; } - private static uint Predictor1C(uint left, uint[] top) + private static uint Predictor1(uint left, uint[] top) { return left; } - private static uint Predictor2C(uint left, uint[] top) + private static uint Predictor2(uint left, Span top) { return top[0]; } - private static uint Predictor3C(uint left, uint[] top) + private static uint Predictor3(uint left, Span top) { return top[1]; } - private static uint Predictor4C(uint left, uint[] top) + private static uint Predictor4(uint left, Span top) { return top[-1]; } - private static uint Predictor5C(uint left, uint[] top) + private static uint Predictor5(uint left, Span top) { uint pred = Average3(left, top[0], top[1]); return pred; } - private static uint Predictor6C(uint left, uint[] top) + private static uint Predictor6(uint left, Span top) { uint pred = Average2(left, top[-1]); return pred; } - private static uint Predictor7C(uint left, uint[] top) + private static uint Predictor7(uint left, Span top) { uint pred = Average2(left, top[0]); return pred; } - private static uint Predictor8C(uint left, uint[] top) + private static uint Predictor8(uint left, Span top) { uint pred = Average2(top[-1], top[0]); return pred; } - private static uint Predictor9C(uint left, uint[] top) + private static uint Predictor9(uint left, Span top) { uint pred = Average2(top[0], top[1]); return pred; } - private static uint Predictor10C(uint left, uint[] top) + private static uint Predictor10(uint left, Span top) { uint pred = Average4(left, top[-1], top[0], top[1]); return pred; } - private static uint Predictor11C(uint left, uint[] top) + private static uint Predictor11(uint left, Span top) { uint pred = Select(top[0], left, top[-1]); return pred; } - private static uint Predictor12C(uint left, uint[] top) + private static uint Predictor12(uint left, Span top) { uint pred = ClampedAddSubtractFull(left, top[0], top[-1]); return pred; } - private static uint Predictor13C(uint left, uint[] top) + private static uint Predictor13(uint left, Span top) { uint pred = ClampedAddSubtractHalf(left, top[0], top[-1]); return pred; From 7af5aa052d6f22f8db11623d15900218af63e789 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Dec 2019 19:37:42 +0100 Subject: [PATCH 061/359] Add PredictorInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 234 ++++++++++++------ .../Formats/WebP/WebPLosslessDecoder.cs | 10 +- 2 files changed, 168 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index ff30b2609..981e7983a 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -176,9 +176,9 @@ namespace SixLabors.ImageSharp.Formats.WebP int width = transform.XSize; // First Row follows the L (mode=1) mode. - PredictorAdd0(pixelData, null, processedPixels++, 1, output); - PredictorAdd1(pixelData, null, processedPixels, width - 1, output); - processedPixels += width - 1; + PredictorAdd0(pixelData, processedPixels, 1, output); + PredictorAdd1(pixelData, processedPixels + 1, width - 1, output); + processedPixels += width; yStart++; int y = yStart; @@ -186,20 +186,90 @@ namespace SixLabors.ImageSharp.Formats.WebP int tileWidth = 1 << transform.Bits; int mask = tileWidth - 1; int tilesPerRow = SubSampleSize(width, transform.Bits); - + int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; while (y < yEnd) { + int predictorModeIdx = predictorModeIdxBase; int x = 1; // First pixel follows the T (mode=2) mode. - PredictorAdd2(pixelData, out-width, 1, output); + PredictorAdd2(pixelData, processedPixels, 1, width, output); // .. the rest: + while (x < width) + { + uint predictorMode = (transform.Data[predictorModeIdx++] >> 8) & 0xf; + int xEnd = (x & ~mask) + tileWidth; + if (xEnd > width) + { + xEnd = width; + } + + int startIdx = processedPixels + x; + int numberOfPixels = xEnd - x; + switch (predictorMode) + { + case 0: + PredictorAdd0(pixelData, startIdx, numberOfPixels, output); + break; + case 1: + PredictorAdd1(pixelData, startIdx, numberOfPixels, output); + break; + case 2: + PredictorAdd2(pixelData, startIdx, numberOfPixels, width, output); + break; + case 3: + PredictorAdd3(pixelData, startIdx, numberOfPixels, width, output); + break; + case 4: + PredictorAdd4(pixelData, startIdx, numberOfPixels, width, output); + break; + case 5: + PredictorAdd5(pixelData, startIdx, numberOfPixels, width, output); + break; + case 6: + PredictorAdd6(pixelData, startIdx, numberOfPixels, width, output); + break; + case 7: + PredictorAdd7(pixelData, startIdx, numberOfPixels, width, output); + break; + case 8: + PredictorAdd8(pixelData, startIdx, numberOfPixels, width, output); + break; + case 9: + PredictorAdd9(pixelData, startIdx, numberOfPixels, width, output); + break; + case 10: + PredictorAdd10(pixelData, startIdx, numberOfPixels, width, output); + break; + case 11: + PredictorAdd11(pixelData, startIdx, numberOfPixels, width, output); + break; + case 12: + PredictorAdd12(pixelData, startIdx, numberOfPixels, width, output); + break; + case 13: + PredictorAdd13(pixelData, startIdx, numberOfPixels, width, output); + break; + } + + x = xEnd; + } + + processedPixels += width; + ++y; + if ((y & mask) is 0) + { + // Use the same mask, since tiles are squares. + predictorModeIdxBase += tilesPerRow; + } } + + output.AsSpan().CopyTo(pixelData); } - // TODO: the pridictor add methods should be generated - private static void PredictorAdd0(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + // TODO: the predictor add methods should be generated + private static void PredictorAdd0(uint[] input, int startIdx, int numberOfPixels, uint[] output) { for (int x = startIdx; x < numberOfPixels; ++x) { @@ -208,119 +278,143 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd1(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd1(uint[] input, int startIdx, int numberOfPixels, uint[] output) { uint left = output[startIdx - 1]; - for (int i = 0; i < numberOfPixels; ++i) + for (int x = 0; x < numberOfPixels; ++x) { - output[i] = left = AddPixels(input[i], left); + output[x] = left = AddPixels(input[x], left); } } - private static void PredictorAdd2(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd2(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor2(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor2(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd3(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd3(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor3(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor3(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd4(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd4(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor4(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor4(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd5(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd5(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor5(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor5(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd6(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd6(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor6(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor6(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd7(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd7(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor7(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor7(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd8(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd8(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor8(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor8(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd9(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd9(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor9(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor9(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd10(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd10(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor10(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor10(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd11(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd11(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor11(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor11(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd12(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd12(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor12(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor12(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } - private static void PredictorAdd13(uint[] input, uint[] upper, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd13(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + int offset = 0; + for (int x = startIdx; x < endIdx; ++x) { - uint pred = Predictor13(output[x - 1], upper.AsSpan(x)); + uint pred = Predictor13(output[x - 1], output, startIdx - width + offset++); output[x] = AddPixels(input[x], pred); } } @@ -335,72 +429,72 @@ namespace SixLabors.ImageSharp.Formats.WebP return left; } - private static uint Predictor2(uint left, Span top) + private static uint Predictor2(uint left, uint[] top, int idx) { - return top[0]; + return top[idx]; } - private static uint Predictor3(uint left, Span top) + private static uint Predictor3(uint left, uint[] top, int idx) { - return top[1]; + return top[idx + 1]; } - private static uint Predictor4(uint left, Span top) + private static uint Predictor4(uint left, uint[] top, int idx) { - return top[-1]; + return top[idx - 1]; } - private static uint Predictor5(uint left, Span top) + private static uint Predictor5(uint left, uint[] top, int idx) { - uint pred = Average3(left, top[0], top[1]); + uint pred = Average3(left, top[idx], top[idx + 1]); return pred; } - private static uint Predictor6(uint left, Span top) + private static uint Predictor6(uint left, uint[] top, int idx) { - uint pred = Average2(left, top[-1]); + uint pred = Average2(left, top[idx - 1]); return pred; } - private static uint Predictor7(uint left, Span top) + private static uint Predictor7(uint left, uint[] top, int idx) { - uint pred = Average2(left, top[0]); + uint pred = Average2(left, top[idx]); return pred; } - private static uint Predictor8(uint left, Span top) + private static uint Predictor8(uint left, uint[] top, int idx) { - uint pred = Average2(top[-1], top[0]); + uint pred = Average2(top[idx - 1], top[idx]); return pred; } - private static uint Predictor9(uint left, Span top) + private static uint Predictor9(uint left, uint[] top, int idx) { - uint pred = Average2(top[0], top[1]); + uint pred = Average2(top[idx], top[idx + 1]); return pred; } - private static uint Predictor10(uint left, Span top) + private static uint Predictor10(uint left, uint[] top, int idx) { - uint pred = Average4(left, top[-1], top[0], top[1]); + uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); return pred; } - private static uint Predictor11(uint left, Span top) + private static uint Predictor11(uint left, uint[] top, int idx) { - uint pred = Select(top[0], left, top[-1]); + uint pred = Select(top[idx], left, top[idx - 1]); return pred; } - private static uint Predictor12(uint left, Span top) + private static uint Predictor12(uint left, uint[] top, int idx) { - uint pred = ClampedAddSubtractFull(left, top[0], top[-1]); + uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); return pred; } - private static uint Predictor13(uint left, Span top) + private static uint Predictor13(uint left, uint[] top, int idx) { - uint pred = ClampedAddSubtractHalf(left, top[0], top[-1]); + uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); return pred; } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 21686d552..4caeb18b9 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -710,13 +710,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - int copiedPixels = 0; - while (copiedPixels < length) + Span src = pixelData.AsSpan(decodedPixels - dist); + Span dest = pixelData.AsSpan(decodedPixels); + for (int i = 0; i < length; ++i) { - Span src = pixelData.AsSpan(decodedPixels - dist, dist); - Span dest = pixelData.AsSpan(decodedPixels + copiedPixels); - src.CopyTo(dest); - copiedPixels += dist; + dest[i] = src[i]; } } } From 9e878f89103495f423f4e316d24d33092257437a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 31 Dec 2019 14:28:05 +0100 Subject: [PATCH 062/359] Fix wrong start and end index in PredictorAdd01 --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 981e7983a..982f406be 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // First Row follows the L (mode=1) mode. PredictorAdd0(pixelData, processedPixels, 1, output); - PredictorAdd1(pixelData, processedPixels + 1, width - 1, output); + PredictorAdd1(pixelData, 1, width - 1, output); processedPixels += width; yStart++; @@ -195,7 +195,6 @@ namespace SixLabors.ImageSharp.Formats.WebP // First pixel follows the T (mode=2) mode. PredictorAdd2(pixelData, processedPixels, 1, width, output); - // .. the rest: while (x < width) { uint predictorMode = (transform.Data[predictorModeIdx++] >> 8) & 0xf; @@ -271,7 +270,8 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: the predictor add methods should be generated private static void PredictorAdd0(uint[] input, int startIdx, int numberOfPixels, uint[] output) { - for (int x = startIdx; x < numberOfPixels; ++x) + int endIdx = startIdx + numberOfPixels; + for (int x = startIdx; x < endIdx; ++x) { uint pred = Predictor0(); output[x] = AddPixels(input[x], pred); @@ -280,8 +280,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private static void PredictorAdd1(uint[] input, int startIdx, int numberOfPixels, uint[] output) { + int endIdx = startIdx + numberOfPixels; uint left = output[startIdx - 1]; - for (int x = 0; x < numberOfPixels; ++x) + for (int x = startIdx; x < endIdx; ++x) { output[x] = left = AddPixels(input[x], left); } From 3e9036d357608b61fa177607248d8af7ce8df4e0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 5 Jan 2020 18:20:35 +0100 Subject: [PATCH 063/359] Fix bug in ColorSpaceInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 33 ++++++++++--------- src/ImageSharp/Formats/WebP/Vp8LTransform.cs | 3 ++ .../Formats/WebP/WebPLosslessDecoder.cs | 4 +-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 982f406be..b3dee8b52 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -89,25 +89,27 @@ namespace SixLabors.ImageSharp.Formats.WebP int remainingWidth = width - safeWidth; int tilesPerRow = SubSampleSize(width, transform.Bits); int y = 0; - uint predRow = transform.Data[(y >> transform.Bits) * tilesPerRow]; + int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; int pixelPos = 0; while (y < yEnd) { - uint pred = predRow; + int predRowIdx = predRowIdxStart; Vp8LMultipliers m = default(Vp8LMultipliers); int srcSafeEnd = pixelPos + safeWidth; int srcEnd = pixelPos + width; while (pixelPos < srcSafeEnd) { - ColorCodeToMultipliers(pred++, ref m); + uint colorCode = transform.Data[predRowIdx++]; + ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, tileWidth); pixelPos += tileWidth; } if (pixelPos < srcEnd) { - ColorCodeToMultipliers(pred++, ref m); + uint colorCode = transform.Data[predRowIdx]; + ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, remainingWidth); pixelPos += remainingWidth; } @@ -115,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ++y; if ((y & mask) == 0) { - predRow += (uint)tilesPerRow; + predRowIdxStart += tilesPerRow; } } } @@ -130,13 +132,13 @@ namespace SixLabors.ImageSharp.Formats.WebP uint red = argb >> 16; int newRed = (int)(red & 0xff); int newBlue = (int)argb & 0xff; - newRed += ColorTransformDelta(m.GreenToRed, (sbyte)green); + newRed += ColorTransformDelta((sbyte)m.GreenToRed, (sbyte)green); newRed &= 0xff; - newBlue += ColorTransformDelta(m.GreenToBlue, (sbyte)green); - newBlue += ColorTransformDelta(m.RedToBlue, (sbyte)newRed); + newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, (sbyte)green); + newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; - // uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); } } @@ -612,23 +614,24 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int ColorTransformDelta(sbyte colorPred, sbyte color) { + var delta = ((sbyte)colorPred * color) >> 5; return ((int)colorPred * color) >> 5; } private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) { - m.GreenToRed = (sbyte)(colorCode & 0xff); - m.GreenToBlue = (sbyte)((colorCode >> 8) & 0xff); - m.RedToBlue = (sbyte)((colorCode >> 16) & 0xff); + m.GreenToRed = (byte)(colorCode & 0xff); + m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); + m.RedToBlue = (byte)((colorCode >> 16) & 0xff); } internal struct Vp8LMultipliers { - public sbyte GreenToRed; + public byte GreenToRed; - public sbyte GreenToBlue; + public byte GreenToBlue; - public sbyte RedToBlue; + public byte RedToBlue; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs index b232f4688..580c03dc7 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -1,11 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP { /// /// Data associated with a VP8L transformation to reduce the entropy. /// + [DebuggerDisplay("Transformtype: {TransformType}")] internal class Vp8LTransform { public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 4caeb18b9..8d7de7513 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - // Error + // TODO: throw appropriate error msg } } @@ -750,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int xOffset = 8 - (distCode & 0xf); int dist = (yOffset * xSize) + xOffset; - // dist < 1 can happen if xsize is very small. + // dist < 1 can happen if xSize is very small. return (dist >= 1) ? dist : 1; } From 7b8d841f5c74665136bace872024c1b0b553864b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 6 Jan 2020 13:37:17 +0100 Subject: [PATCH 064/359] Move corrupted images into separate test --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 4 +- .../Formats/WebP/WebPDecoderTests.cs | 34 +++++++++---- tests/ImageSharp.Tests/TestImages.cs | 49 ++++++++++--------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index b3dee8b52..1bd8ec2c0 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -138,7 +138,6 @@ namespace SixLabors.ImageSharp.Formats.WebP newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; - uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); } } @@ -269,7 +268,6 @@ namespace SixLabors.ImageSharp.Formats.WebP output.AsSpan().CopyTo(pixelData); } - // TODO: the predictor add methods should be generated private static void PredictorAdd0(uint[] input, int startIdx, int numberOfPixels, uint[] output) { int endIdx = startIdx + numberOfPixels; @@ -614,7 +612,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private static int ColorTransformDelta(sbyte colorPred, sbyte color) { - var delta = ((sbyte)colorPred * color) >> 5; + int delta = ((sbyte)colorPred * color) >> 5; return ((int)colorPred * color) >> 5; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 43627b48b..825fdcae0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -23,7 +23,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] [InlineData(Lossy.Alpha.LossyAlpha2, 1000, 307, 24)] [InlineData(Animated.Animated1, 400, 400, 24)] - public void Identify_DetectsCorrectDimensions(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) + public void Identify_DetectsCorrectDimensions( + string imagePath, + int expectedWidth, + int expectedHeight, + int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -38,7 +42,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Theory] [InlineData(Lossy.Alpha.LossyAlpha1, 1000, 307, 24)] - public void DecodeLossyImage_Tmp(string imagePath, int expectedWidth, int expectedHeight, int expectedBitsPerPixel) + public void DecodeLossyImage_Tmp( + string imagePath, + int expectedWidth, + int expectedHeight, + int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -67,9 +75,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] - // TODO: Reference decoder throws here MagickCorruptImageErrorException - //[WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform(TestImageProvider provider) + // TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work. + // [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform( + TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new WebPDecoder())) @@ -135,9 +144,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] [WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] [WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms14, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms15, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms16, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) where TPixel : struct, IPixel { @@ -156,7 +162,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms8, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) where TPixel : struct, IPixel { @@ -166,5 +171,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP image.CompareToOriginal(provider, new MagickReferenceDecoder()); } } + + [Theory] + [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] + [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] + [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] + [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] + public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.Throws(() => { using (provider.GetImage(new WebPDecoder())) { } }); + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 73a4abc78..9d58ef3be 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -396,30 +396,33 @@ namespace SixLabors.ImageSharp.Tests public const string ColorIndexTransform3 = "WebP/lossless_vec_1_5.webp"; public const string ColorIndexTransform4 = "WebP/lossless_vec_2_1.webp"; public const string ColorIndexTransform5 = "WebP/lossless_vec_2_5.webp"; - public const string TwoTransforms1 = "Webp/lossless_color_transform.webp"; // cross_color, predictor - public const string TwoTransforms2 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor - public const string TwoTransforms3 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green - public const string TwoTransforms4 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color - public const string TwoTransforms5 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor - public const string TwoTransforms6 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor - public const string TwoTransforms7 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor - public const string TwoTransforms8 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color - public const string TwoTransforms9 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color - public const string TwoTransforms10 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color - public const string TwoTransforms11 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color - public const string TwoTransforms12 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor - public const string TwoTransforms13 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor - public const string TwoTransforms14 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor - public const string TwoTransforms15 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor - public const string TwoTransforms16 = "Webp/near_lossless_75.webp"; // predictor, cross_color + public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor + public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green + public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color + public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor + public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor + public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor + public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color + public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color + public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color + public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color + public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor + public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor + public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms2 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms3 = "Webp/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms4 = "Webp/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms5 = "Webp/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms6 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color - public const string ThreeTransforms7 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color - public const string ThreeTransforms8 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color + public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color + + // Invalid / corrupted images + // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." + public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. + public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. + public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor + public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. } public static class Lossy From 77d18f35750b62ad2894213007b6ede739ad2477 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Jan 2020 18:54:33 +0100 Subject: [PATCH 065/359] Remove none webp images, except for two example images --- tests/Images/Input/WebP/grid.bmp | 3 - tests/Images/Input/WebP/grid.pam | Bin 1091 -> 0 bytes tests/Images/Input/WebP/grid.pgm | 4 - tests/Images/Input/WebP/grid.ppm | Bin 781 -> 0 bytes tests/Images/Input/WebP/grid.tiff | 3 - tests/Images/Input/WebP/libwebp_tests.md5 | 470 ------------------ .../Input/WebP/lossless_color_transform.bmp | 3 - .../Input/WebP/lossless_color_transform.pam | Bin 1048645 -> 0 bytes .../Input/WebP/lossless_color_transform.pgm | 4 - .../Input/WebP/lossless_color_transform.ppm | Bin 786447 -> 0 bytes .../Input/WebP/lossless_color_transform.tiff | 3 - tests/Images/Input/WebP/lossless_vec_list.txt | 6 +- tests/Images/Input/WebP/peak.bmp | 3 - tests/Images/Input/WebP/peak.pam | 8 - tests/Images/Input/WebP/peak.pgm | 4 - tests/Images/Input/WebP/peak.ppm | 4 - tests/Images/Input/WebP/peak.tiff | 3 - tests/Images/Input/WebP/test_cwebp.sh | 97 ---- tests/Images/Input/WebP/test_dwebp.sh | 101 ---- tests/Images/Input/WebP/test_lossless.sh | 82 --- 20 files changed, 2 insertions(+), 796 deletions(-) delete mode 100644 tests/Images/Input/WebP/grid.bmp delete mode 100644 tests/Images/Input/WebP/grid.pam delete mode 100644 tests/Images/Input/WebP/grid.pgm delete mode 100644 tests/Images/Input/WebP/grid.ppm delete mode 100644 tests/Images/Input/WebP/grid.tiff delete mode 100644 tests/Images/Input/WebP/libwebp_tests.md5 delete mode 100644 tests/Images/Input/WebP/lossless_color_transform.bmp delete mode 100644 tests/Images/Input/WebP/lossless_color_transform.pam delete mode 100644 tests/Images/Input/WebP/lossless_color_transform.pgm delete mode 100644 tests/Images/Input/WebP/lossless_color_transform.ppm delete mode 100644 tests/Images/Input/WebP/lossless_color_transform.tiff delete mode 100644 tests/Images/Input/WebP/peak.bmp delete mode 100644 tests/Images/Input/WebP/peak.pam delete mode 100644 tests/Images/Input/WebP/peak.pgm delete mode 100644 tests/Images/Input/WebP/peak.ppm delete mode 100644 tests/Images/Input/WebP/peak.tiff delete mode 100644 tests/Images/Input/WebP/test_cwebp.sh delete mode 100644 tests/Images/Input/WebP/test_dwebp.sh delete mode 100644 tests/Images/Input/WebP/test_lossless.sh diff --git a/tests/Images/Input/WebP/grid.bmp b/tests/Images/Input/WebP/grid.bmp deleted file mode 100644 index a83fbf5ea..000000000 --- a/tests/Images/Input/WebP/grid.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37ba61a7842f1361f6b5ec563fecadda9bde615b784a55ce372d83fa67177fa1 -size 1078 diff --git a/tests/Images/Input/WebP/grid.pam b/tests/Images/Input/WebP/grid.pam deleted file mode 100644 index 2d1271c1dd1c28af8b330abd6f134b2e80263698..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1091 zcmWGA=L+|93Gq-cG~@Dc^>p_L0kK?M1Asy%T)vJGVU9iuMy94*A)x_2A&~*D3PJ8p w@s2(L9*$hDel8v^L0k+B|NsAIU}zwhrbI8uPIB#q=^M45{J0wp|IzdZ027tY?f?J) diff --git a/tests/Images/Input/WebP/grid.pgm b/tests/Images/Input/WebP/grid.pgm deleted file mode 100644 index 0e9373079..000000000 --- a/tests/Images/Input/WebP/grid.pgm +++ /dev/null @@ -1,4 +0,0 @@ -P5 -16 40 -255 -)R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R))R)R)R)R)R)R)R)RR)R)R)R)R)R)R)R)¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯¥¥¥¥¥¥¥¥¯¯¯¯¯¯¯¯ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ \ No newline at end of file diff --git a/tests/Images/Input/WebP/grid.ppm b/tests/Images/Input/WebP/grid.ppm deleted file mode 100644 index 6facbe3fc7dba8956e943cb2280a8224b354df5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 wcmWGA<1#c;Ff`*bGBxF5VEF%^0SJgCNm2|nmUxpPDo4%7A7Z27L*4KJ0JGKsJ^%m! diff --git a/tests/Images/Input/WebP/grid.tiff b/tests/Images/Input/WebP/grid.tiff deleted file mode 100644 index 8c94aee3d..000000000 --- a/tests/Images/Input/WebP/grid.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7db514b70352b0599ccfc7169c3795d935e9a5ec21126eefdcab6d6b8984d12e -size 1234 diff --git a/tests/Images/Input/WebP/libwebp_tests.md5 b/tests/Images/Input/WebP/libwebp_tests.md5 deleted file mode 100644 index 64b398baa..000000000 --- a/tests/Images/Input/WebP/libwebp_tests.md5 +++ /dev/null @@ -1,470 +0,0 @@ -752cf34b353c61f5f741cb70c8265e5c bug3.webp.bmp -e27a4bd5ea7d83bcbcb255fd57fa8921 bug3.webp.pam -f3766e3c21c5ad73c5e04c76f6963961 bug3.webp.pgm -ae5fa25df6e26d5f97e526ac8cf4a2c0 bug3.webp.ppm -a5d93d118527678a3d54506a2852cf3a bug3.webp.tiff -4a2f38e6075d12677f902f6ef2035fcd lossless1.webp.bmp -fd52591b61fc34192d7c337fa024bf12 lossless1.webp.pam -8141a733978e9efeacc668687f8c773b lossless1.webp.pgm -3a1da0ba5657c5f65fec5c84cc9a888a lossless1.webp.ppm -1edba87958a360dbfad2a9def2e31ed4 lossless1.webp.tiff -4a2f38e6075d12677f902f6ef2035fcd lossless2.webp.bmp -fd52591b61fc34192d7c337fa024bf12 lossless2.webp.pam -8141a733978e9efeacc668687f8c773b lossless2.webp.pgm -3a1da0ba5657c5f65fec5c84cc9a888a lossless2.webp.ppm -1edba87958a360dbfad2a9def2e31ed4 lossless2.webp.tiff -4a2f38e6075d12677f902f6ef2035fcd lossless3.webp.bmp -fd52591b61fc34192d7c337fa024bf12 lossless3.webp.pam -8141a733978e9efeacc668687f8c773b lossless3.webp.pgm -3a1da0ba5657c5f65fec5c84cc9a888a lossless3.webp.ppm -1edba87958a360dbfad2a9def2e31ed4 lossless3.webp.tiff -9b62f79cf1f623a3ac12c008c95bf9c2 lossy_alpha1.webp.bmp -c5c77aff5b4015d3416817d12c2c2377 lossy_alpha1.webp.pam -7dfa7e2ee84b6d0d21dd5395c43e58a0 lossy_alpha1.webp.pgm -060930f62b7e79001069c2a1c6387ace lossy_alpha1.webp.ppm -26784e7f5a919c2e5c5b07429c2789f2 lossy_alpha1.webp.tiff -57a73105a2f7259d05594c7d722cfff5 lossy_alpha2.webp.bmp -5a98f393a1dfd2e56c1fdf8f18a028a6 lossy_alpha2.webp.pam -923e7e529bbbc207d82570ea8aace080 lossy_alpha2.webp.pgm -b34c384890fdd1ef19d337cd5cabfb87 lossy_alpha2.webp.ppm -61de03fb7f4321438c2bd97c1776d298 lossy_alpha2.webp.tiff -34e893a765451a4dbb7234ca2e3c0e54 lossy_alpha3.webp.bmp -4ab07c625657aac74fdfa440b7d80661 lossy_alpha3.webp.pam -9be96d161ea68e222c98c79e9e6bfe55 lossy_alpha3.webp.pgm -b8577b69f3e781ef3db275f1689c7a67 lossy_alpha3.webp.ppm -70051b36f2751894047ce61fb81c5077 lossy_alpha3.webp.tiff -8cdc224c1c98fd3d7a2eccef39615fa2 lossy_extreme_probabilities.webp.bmp -48acff24a64848886eb5fbc7f4d9f48f lossy_extreme_probabilities.webp.pam -7b45594189937b3081c5ff296df0748e lossy_extreme_probabilities.webp.pgm -35b296b4847410c55973cd9b26a00c9e lossy_extreme_probabilities.webp.ppm -6a5b5f4663c9420681fed41031b7e367 lossy_extreme_probabilities.webp.tiff -5d0e23758492b9054edbc3468916a25c segment01.webp.bmp -9cdc59716def2771ed44d6e59e60118e segment01.webp.pam -b6fdd7a449ca379d9c73d3af132f708e segment01.webp.pgm -31fe5642d04d90dd7aa5cedd6c761640 segment01.webp.ppm -48563a05febd80370280e23cb48fda92 segment01.webp.tiff -58b61363438effccdddd8b2d48d39cd4 segment02.webp.bmp -53fea7f9739ebc82633b3abb7742b106 segment02.webp.pam -390293f54eae1df3477d7122351f1a72 segment02.webp.pgm -aca97156b5c91251536becec093e4869 segment02.webp.ppm -afccf77585d5cf6f0ea3320dbec4b120 segment02.webp.tiff -49d19c40152a3b0fde7bcd1c91a5b7be segment03.webp.bmp -55150fffd5fe83e00eff2ca2035bb87b segment03.webp.pam -b7bb5c2b5b48d014f75e2f9db9e45718 segment03.webp.pgm -653d32a9016c1ee5b6fec6f4afefadd8 segment03.webp.ppm -e059fdc5de402db01519ffd2b3018c52 segment03.webp.tiff -4f606f42cb00f1c575a23c4cce540157 small_13x1.webp.bmp -48f544271e281d68a2d406b928de1841 small_13x1.webp.pam -5683f2f30a22f9b800915bf4edfd14de small_13x1.webp.pgm -188a9ac1aa2f4a7d256831ae7a5cb682 small_13x1.webp.ppm -3c336cfb8fd451efb7f52b75afd7b643 small_13x1.webp.tiff -d16c13d5bdd9bfdd62c612f68570f302 small_1x1.webp.bmp -d6605e1f351452a8f8c8cbe7fa9218bd small_1x1.webp.pam -a40ac01f9a60ff4473f1a40ef57f6ff5 small_1x1.webp.pgm -d4e7037a5b97e3c82aa4fd343fc068e4 small_1x1.webp.ppm -5f1f089d669b8c3671c28819cbb9e25b small_1x1.webp.tiff -04429ff71bd421664f73be6d0e8dee45 small_1x13.webp.bmp -b99d3b58c1c1f322f570a2c2ad24080f small_1x13.webp.pam -bd0c99456093f5b4ba8d87b2fb964478 small_1x13.webp.pgm -37fb89b8ec87dcfc4c15e258e0d75246 small_1x13.webp.ppm -47aa7b343bcb14315c3491ad59e1ba1d small_1x13.webp.tiff -9a7f2b9bd981ae5899fb4f75f1405f76 small_31x13.webp.bmp -586337da3501d1fae607ef0e2930a1b1 small_31x13.webp.pam -1d71e36e683771fa452110c61b98ea12 small_31x13.webp.pgm -c803f81036d4ea998cf94e3fd9be9a7f small_31x13.webp.ppm -825990e0c570245defdb6dd2d4678226 small_31x13.webp.tiff -a3b449dc60a7e6dd399d6c146c29f38d test-nostrong.webp.bmp -ce12aa49f7e4f2afa0963f18f11dc332 test-nostrong.webp.pam -20e3e0c26b596803c4c0a51c7fc544d2 test-nostrong.webp.pgm -dc97fd4b0ac668f3a0d3925d998c1908 test-nostrong.webp.ppm -2e660e7ddaffcac8f823db3f1d80c5d5 test-nostrong.webp.tiff -34efa50cddbff8575720f270387414c9 test.webp.bmp -3d9213ea387706db93f0b39247d77573 test.webp.pam -e46f3d66c69e8b47a2c9a298ecc516b9 test.webp.pgm -ebdd46e0760b2a4891e6550b37c00660 test.webp.ppm -a956c5897f57ca3432c3eff371e577f5 test.webp.tiff -54ed492d774eeb15339eade270ef0a2c very_short.webp.bmp -2ec8e78a5fef6ab980cff79948eb5d2c very_short.webp.pam -0517d3e5b01a67dde947fb09564473b7 very_short.webp.pgm -17fbb51aa95d17f3c9440c1e6a1411c3 very_short.webp.ppm -936b795d3dd76e7bae65af1c92181baf very_short.webp.tiff -df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-001.webp.bmp -131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-001.webp.pam -83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-001.webp.pgm -d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-001.webp.ppm -c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-001.webp.tiff -80daf19056e45cc74baa01286f30f33a vp80-00-comprehensive-002.webp.bmp -b8b258d3bb66c5918906d6a167f4673d vp80-00-comprehensive-002.webp.pam -3dd031f2cb1906d5fe1a5f6aee4b0461 vp80-00-comprehensive-002.webp.pgm -9cf357fc1a98224436d0a167e04b8041 vp80-00-comprehensive-002.webp.ppm -21440d3544780283097de49e2ffd65b9 vp80-00-comprehensive-002.webp.tiff -6a83b957594e3d5983b4cf605a43171d vp80-00-comprehensive-003.webp.bmp -e889db2f00f3b788673fd76e35a38591 vp80-00-comprehensive-003.webp.pam -0be1ab40b30824ff9d09722c074271ff vp80-00-comprehensive-003.webp.pgm -4fc3367f461a18119ed534843648a06e vp80-00-comprehensive-003.webp.ppm -d5f951a6b267674066cc40179db791ab vp80-00-comprehensive-003.webp.tiff -df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-004.webp.bmp -131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-004.webp.pam -83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-004.webp.pgm -d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-004.webp.ppm -c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-004.webp.tiff -20e26306afdfd6aeafb832a5934c7331 vp80-00-comprehensive-005.webp.bmp -be0a3cba6d4307a211bc516032726162 vp80-00-comprehensive-005.webp.pam -7f43d5472ffb0be617840cb300787547 vp80-00-comprehensive-005.webp.pgm -0f861236782aad77186c872185c5788d vp80-00-comprehensive-005.webp.ppm -4a112bab41e414c85f6e178d5d510480 vp80-00-comprehensive-005.webp.tiff -d4d78d8840debaaa08aee9cc2b82ef26 vp80-00-comprehensive-006.webp.bmp -ec670bbb7dc209ec061b193f5bd85afe vp80-00-comprehensive-006.webp.pam -ef432fcf2599fac6e7e9c2abaa7d7635 vp80-00-comprehensive-006.webp.pgm -77de8cf761dc28c44b9d4630331d1077 vp80-00-comprehensive-006.webp.ppm -56ea1fc09a7bbf749a54bdb94820b7d0 vp80-00-comprehensive-006.webp.tiff -b5805421c3d192b21afa88203db9049c vp80-00-comprehensive-007.webp.bmp -682ea7892cdfa16e32c080558d3aa6d1 vp80-00-comprehensive-007.webp.pam -d19cc392d55bed791967bc0d97fbe89b vp80-00-comprehensive-007.webp.pgm -9d0abc72e3d44e1a92dbe93fe03ec193 vp80-00-comprehensive-007.webp.ppm -40d06ddca14f3bbf8379bce7db616282 vp80-00-comprehensive-007.webp.tiff -f23de86715e12f0a4eea5beac488c028 vp80-00-comprehensive-008.webp.bmp -595e44c414148ccd73d77ef35218dfe6 vp80-00-comprehensive-008.webp.pam -8a3aa03341721dc43d7154f95ceea4ba vp80-00-comprehensive-008.webp.pgm -ea6f107e0489d9b2e9d1c4a2edec37ee vp80-00-comprehensive-008.webp.ppm -fafa9e2293493e68af1149c0d1e895ce vp80-00-comprehensive-008.webp.tiff -a086ecef18cfe6e2a5147e0ed4dd8976 vp80-00-comprehensive-009.webp.bmp -e07f8c0ae66de49c286ce7532122aff8 vp80-00-comprehensive-009.webp.pam -80ee73b2f08a9c14ca1e9f3936b873dc vp80-00-comprehensive-009.webp.pgm -fed589d9874314c66b8627263865dc0d vp80-00-comprehensive-009.webp.ppm -b4a781da320f6052b4cc9626744ca87d vp80-00-comprehensive-009.webp.tiff -625d334a9d0c4a08871065ae97ce52a7 vp80-00-comprehensive-010.webp.bmp -daac194407ea1483c6e91a8d683f4318 vp80-00-comprehensive-010.webp.pam -828eee458e38de2f706426dc3c326138 vp80-00-comprehensive-010.webp.pgm -9eb59d831bec86417b09bfaa075da197 vp80-00-comprehensive-010.webp.ppm -be2bd1b975e1fb6369024f31912df193 vp80-00-comprehensive-010.webp.tiff -df4e11105487115b323550acc3e82ffe vp80-00-comprehensive-011.webp.bmp -131523469da4cc7a964f3e712936b878 vp80-00-comprehensive-011.webp.pam -83915e3ecabea125b02593b44bbe3b56 vp80-00-comprehensive-011.webp.pgm -d8e49c7ad0c52a1ca4b1cf1615da94a8 vp80-00-comprehensive-011.webp.ppm -c5250dae66c1b4e52c59640dc5537728 vp80-00-comprehensive-011.webp.tiff -80fbbd6f508898f7c26b6bd7e0724986 vp80-00-comprehensive-012.webp.bmp -bd864949ce28ad7c3c4478ed06d2eca2 vp80-00-comprehensive-012.webp.pam -2b98514d0699353bb0876e1cd6474226 vp80-00-comprehensive-012.webp.pgm -e19222f69d98ff61eef85f241b8a279f vp80-00-comprehensive-012.webp.ppm -f13560abf907158e95d0c99619f2d3d6 vp80-00-comprehensive-012.webp.tiff -108b1c8742c0bea9509c9bb013093622 vp80-00-comprehensive-013.webp.bmp -d474b510bd58178b95b74b5c1e35bf62 vp80-00-comprehensive-013.webp.pam -5cd2d920340f771d1e535e4b7f632f18 vp80-00-comprehensive-013.webp.pgm -c4f32060e80bf13fd3e87e55d31b49ad vp80-00-comprehensive-013.webp.ppm -6c74f63613eda4e1f35fdeeb82e70616 vp80-00-comprehensive-013.webp.tiff -1067a63f05f52446a2afb9b0a57c7001 vp80-00-comprehensive-014.webp.bmp -bd3ae3b0ff577f36d46fb874c6f3a82d vp80-00-comprehensive-014.webp.pam -39b06e302571acd69cf71c0bb2cf7752 vp80-00-comprehensive-014.webp.pgm -dea00c2e8d6df679d383196c16dab89c vp80-00-comprehensive-014.webp.ppm -a8903a156cb4f6e58137ef0496c8ef2b vp80-00-comprehensive-014.webp.tiff -3f4d1ac502b5310a9ca401f8c2254bdb vp80-00-comprehensive-015.webp.bmp -9041921a26f7de41f1cda79ac355c0d7 vp80-00-comprehensive-015.webp.pam -7df64ec81488aaca964fcf09fa13b017 vp80-00-comprehensive-015.webp.pgm -fdd58f7ef85dec0503915b802c7b8f26 vp80-00-comprehensive-015.webp.ppm -f6f99798c4c75a8b89d59e9ee00acc13 vp80-00-comprehensive-015.webp.tiff -b0271ce129966000ff0fdd618cedf429 vp80-00-comprehensive-016.webp.bmp -691cb65996f8347d696474ff34e714fc vp80-00-comprehensive-016.webp.pam -7f57f6187412786f64752c08f8be1fe8 vp80-00-comprehensive-016.webp.pgm -f40b8a72514c9ca35dd2f6eaf6208cfb vp80-00-comprehensive-016.webp.ppm -3559ae9a914e7f0154654e0d75aa5efc vp80-00-comprehensive-016.webp.tiff -b0271ce129966000ff0fdd618cedf429 vp80-00-comprehensive-017.webp.bmp -691cb65996f8347d696474ff34e714fc vp80-00-comprehensive-017.webp.pam -7f57f6187412786f64752c08f8be1fe8 vp80-00-comprehensive-017.webp.pgm -f40b8a72514c9ca35dd2f6eaf6208cfb vp80-00-comprehensive-017.webp.ppm -3559ae9a914e7f0154654e0d75aa5efc vp80-00-comprehensive-017.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-01-intra-1400.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-01-intra-1400.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-01-intra-1400.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-01-intra-1400.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-01-intra-1400.webp.tiff -faeeb6228af0caf9e2394acf12c9fde9 vp80-01-intra-1411.webp.bmp -a0930ca8ccf3a5f135692104ae6c177c vp80-01-intra-1411.webp.pam -a2fab5648ef79a82cc71c5e6ec81611d vp80-01-intra-1411.webp.pgm -e56a3d6dc156823f63749174d4d1ecad vp80-01-intra-1411.webp.ppm -b4be9fc15957093c586f009621400c07 vp80-01-intra-1411.webp.tiff -10ef2d26d016bfd6f82bb10f3ad5c4de vp80-01-intra-1416.webp.bmp -0c7bfbb9ecff4853b493d2a6dd0b8fb8 vp80-01-intra-1416.webp.pam -cc7dab0840259b9a659db905b8babd14 vp80-01-intra-1416.webp.pgm -6175ed41106971eed7648b2edf63f832 vp80-01-intra-1416.webp.ppm -ceb5352cf316ef4a0df74be7f03ec122 vp80-01-intra-1416.webp.tiff -c63a158d762c02744b8f1cc98a5ea863 vp80-01-intra-1417.webp.bmp -7b43d51e05c850b3a79709f24bb65f90 vp80-01-intra-1417.webp.pam -91dc3f9fa9f2bc09145adfc05c1e659f vp80-01-intra-1417.webp.pgm -02277ee0fb9ec05c960e83d88aafb0e7 vp80-01-intra-1417.webp.ppm -b498e84ebe1ff6cf70b90b6968baed20 vp80-01-intra-1417.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-02-inter-1402.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-02-inter-1402.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-02-inter-1402.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-02-inter-1402.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-02-inter-1402.webp.tiff -faeeb6228af0caf9e2394acf12c9fde9 vp80-02-inter-1412.webp.bmp -a0930ca8ccf3a5f135692104ae6c177c vp80-02-inter-1412.webp.pam -a2fab5648ef79a82cc71c5e6ec81611d vp80-02-inter-1412.webp.pgm -e56a3d6dc156823f63749174d4d1ecad vp80-02-inter-1412.webp.ppm -b4be9fc15957093c586f009621400c07 vp80-02-inter-1412.webp.tiff -3587a8cd220edc08b52514c210aee0f6 vp80-02-inter-1418.webp.bmp -17b4a0e6a7fc7ed6d9694bf0aee061a2 vp80-02-inter-1418.webp.pam -dee12174722df68136de55f03de72905 vp80-02-inter-1418.webp.pgm -e974bc9609c361b80c8a524f2e1342f4 vp80-02-inter-1418.webp.ppm -75063d7e1cda0828d6bb0b94eb4e71e2 vp80-02-inter-1418.webp.tiff -7672a847500f6ebe8d2068e3fd5915fc vp80-02-inter-1424.webp.bmp -a9c677bc3f6886dac2d96e7b4fb1803f vp80-02-inter-1424.webp.pam -63ce6da2644ba3de11b9d50e0449c38f vp80-02-inter-1424.webp.pgm -fb9564457d6d0763f309d0aaa6ec9fe4 vp80-02-inter-1424.webp.ppm -f854fa1993f48ac9ed394089ef121ead vp80-02-inter-1424.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-03-segmentation-1401.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-03-segmentation-1401.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-03-segmentation-1401.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-03-segmentation-1401.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-03-segmentation-1401.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-03-segmentation-1403.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-03-segmentation-1403.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-03-segmentation-1403.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-03-segmentation-1403.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-03-segmentation-1403.webp.tiff -323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1407.webp.bmp -7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1407.webp.pam -686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1407.webp.pgm -b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1407.webp.ppm -f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1407.webp.tiff -323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1408.webp.bmp -7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1408.webp.pam -686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1408.webp.pgm -b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1408.webp.ppm -f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1408.webp.tiff -323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1409.webp.bmp -7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1409.webp.pam -686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1409.webp.pgm -b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1409.webp.ppm -f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1409.webp.tiff -323fa484ab4abd3e74a7daac0d64c096 vp80-03-segmentation-1410.webp.bmp -7dee95c1bf81653921fa42196e4943dc vp80-03-segmentation-1410.webp.pam -686f21f89620f28d4612d2b7daf4b858 vp80-03-segmentation-1410.webp.pgm -b03109ebdefa82517a5bf751d2ffd46d vp80-03-segmentation-1410.webp.ppm -f3ef9bf39a3cc71e3190b13db48cca7d vp80-03-segmentation-1410.webp.tiff -faeeb6228af0caf9e2394acf12c9fde9 vp80-03-segmentation-1413.webp.bmp -a0930ca8ccf3a5f135692104ae6c177c vp80-03-segmentation-1413.webp.pam -a2fab5648ef79a82cc71c5e6ec81611d vp80-03-segmentation-1413.webp.pgm -e56a3d6dc156823f63749174d4d1ecad vp80-03-segmentation-1413.webp.ppm -b4be9fc15957093c586f009621400c07 vp80-03-segmentation-1413.webp.tiff -e406ddb31ed29c7b87caacdac1f0a0dd vp80-03-segmentation-1414.webp.bmp -a3523ae5a8c4b632291590937f10e77d vp80-03-segmentation-1414.webp.pam -0825b61488bc8cd27820d7ebb7fcb2de vp80-03-segmentation-1414.webp.pgm -ae6230c3f0983b4165d1d4c367fa3af6 vp80-03-segmentation-1414.webp.ppm -d8a2356a4c105e20d242ffdbfacab919 vp80-03-segmentation-1414.webp.tiff -e406ddb31ed29c7b87caacdac1f0a0dd vp80-03-segmentation-1415.webp.bmp -a3523ae5a8c4b632291590937f10e77d vp80-03-segmentation-1415.webp.pam -0825b61488bc8cd27820d7ebb7fcb2de vp80-03-segmentation-1415.webp.pgm -ae6230c3f0983b4165d1d4c367fa3af6 vp80-03-segmentation-1415.webp.ppm -d8a2356a4c105e20d242ffdbfacab919 vp80-03-segmentation-1415.webp.tiff -300bb54edf23e74e2f1762a89d81f32f vp80-03-segmentation-1425.webp.bmp -577964dc9d5d867be2c0c0923fb2baa1 vp80-03-segmentation-1425.webp.pam -7cf96b3757244675c2b7402b4135c760 vp80-03-segmentation-1425.webp.pgm -fc302f122658ef20fd0e2712fa5eec3e vp80-03-segmentation-1425.webp.ppm -f2982fbb8a94c5c0cb66e41703e7fec8 vp80-03-segmentation-1425.webp.tiff -e4e3682e0b44a45cbebdf831578f826d vp80-03-segmentation-1426.webp.bmp -d3605f57d5be180a450453a8ba7eacf9 vp80-03-segmentation-1426.webp.pam -1911149733a7c14d7a57efb646a5958a vp80-03-segmentation-1426.webp.pgm -e7b49578d08759bf9ddddc4ef0e31ad2 vp80-03-segmentation-1426.webp.ppm -6bc9c0a37192535a6ff7b6daeb8025a3 vp80-03-segmentation-1426.webp.tiff -6817a8881625061e43c2d8ce3afe75fd vp80-03-segmentation-1427.webp.bmp -01b3895cd497a1e0ff20872488b227cb vp80-03-segmentation-1427.webp.pam -3cb4d32d2163bef67c483e2aa38bec50 vp80-03-segmentation-1427.webp.pgm -c6480d79d6e0f83c865bb80eacb45d85 vp80-03-segmentation-1427.webp.ppm -760dd78471f88ce5044d9bd406e9c5a0 vp80-03-segmentation-1427.webp.tiff -5b910f0f5593483274196c710388e79d vp80-03-segmentation-1432.webp.bmp -6572b0954b3462d308e8d39e81d2a069 vp80-03-segmentation-1432.webp.pam -68a4e3fe38ab9981080d9c5087c10100 vp80-03-segmentation-1432.webp.pgm -8a6a27f16352f2af9bf23c9a1bfb11a5 vp80-03-segmentation-1432.webp.ppm -e99e5dc77d0e511482255a76290fb0b9 vp80-03-segmentation-1432.webp.tiff -55bdbcb76b41493bed87f2b661e811f9 vp80-03-segmentation-1435.webp.bmp -0544e7ee82a8c00dfb7e7ae002656578 vp80-03-segmentation-1435.webp.pam -dd072cb089a6656f82cc0474e88d5057 vp80-03-segmentation-1435.webp.pgm -35d685ecacf6dc3930371932486a11e7 vp80-03-segmentation-1435.webp.ppm -54bce5209886f305cb24a39024344071 vp80-03-segmentation-1435.webp.tiff -bbf5499355c2168984e780613e65227f vp80-03-segmentation-1436.webp.bmp -bfac8c040851686262a369892a849df8 vp80-03-segmentation-1436.webp.pam -728c352f485cdf199cbdc541ad4c3275 vp80-03-segmentation-1436.webp.pgm -f92f6333eb0d20b7ec1bf7c0cba3396b vp80-03-segmentation-1436.webp.ppm -820193c918fda34ec69b9b51a9980de9 vp80-03-segmentation-1436.webp.tiff -fd6fa5a841f263a82d69abe737ce8674 vp80-03-segmentation-1437.webp.bmp -cb30469eefa905410bb4ffdaff0e2e60 vp80-03-segmentation-1437.webp.pam -e70f445cd30eea193704c41989dc1511 vp80-03-segmentation-1437.webp.pgm -9b5cc3e123b2cd04bdfbf68d093bfb74 vp80-03-segmentation-1437.webp.ppm -8a558c55610d7575b891c39f5fe48a8f vp80-03-segmentation-1437.webp.tiff -2bd4b8fda0fb5e6fc749244bba535ace vp80-03-segmentation-1441.webp.bmp -dcf08939b95abbdae4e1113246ec52a4 vp80-03-segmentation-1441.webp.pam -4a2aaf38eef45410280a725d7452bc35 vp80-03-segmentation-1441.webp.pgm -ce0a53e7d8e4bde0de3fd5fe268ce94c vp80-03-segmentation-1441.webp.ppm -1b7265bcd32583451855212318d31186 vp80-03-segmentation-1441.webp.tiff -5068b19e13d158b42dc4fa74df7c7271 vp80-03-segmentation-1442.webp.bmp -b7b7ac6d5e7795222e13712678fc3f7f vp80-03-segmentation-1442.webp.pam -39e48e2454516fb689aff52bf5b4ae65 vp80-03-segmentation-1442.webp.pgm -714d34a4bc636b5396bac041c1775e47 vp80-03-segmentation-1442.webp.ppm -0015813e079438bb0243537202084d5c vp80-03-segmentation-1442.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1404.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1404.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1404.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1404.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1404.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1405.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1405.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1405.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1405.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1405.webp.tiff -b464975d439ef954e85d3d58a5d819be vp80-04-partitions-1406.webp.bmp -0fb8b11ad643c731f1bd6c47af7d2378 vp80-04-partitions-1406.webp.pam -41f95ccf48340524a7db88c81ed26b45 vp80-04-partitions-1406.webp.pgm -21dd2a8fe2958707bc6045bcb88e4c68 vp80-04-partitions-1406.webp.ppm -5af2f5b1f2cdfdc0177b5db911d5b2e2 vp80-04-partitions-1406.webp.tiff -788d879f438e69f48f94575d2a02dfdc vp80-05-sharpness-1428.webp.bmp -c51ee0ed93ed81b4cc58d8d2ddc93afa vp80-05-sharpness-1428.webp.pam -4be708158caebde5d4f181b5574ca38b vp80-05-sharpness-1428.webp.pgm -8675a10c26a5f367ce743eef3e934930 vp80-05-sharpness-1428.webp.ppm -a188cfdc0bdd3a3d40ec9bb88cb11667 vp80-05-sharpness-1428.webp.tiff -5127dd7416cd90349b8b34d5b1f7ce4a vp80-05-sharpness-1429.webp.bmp -a314ffdd4c568c7fa503fbe600150d54 vp80-05-sharpness-1429.webp.pam -94b28172c35b4d8336c644a1bee01f8a vp80-05-sharpness-1429.webp.pgm -828ce5ec185db55202a9d01f7da0899c vp80-05-sharpness-1429.webp.ppm -54988f2aeac058b08e90627b39b7b441 vp80-05-sharpness-1429.webp.tiff -b2c9a7f6c38a92ad59c14a9fe1164678 vp80-05-sharpness-1430.webp.bmp -c03140c24d0bb238f602b2c01f6fbe98 vp80-05-sharpness-1430.webp.pam -4729c407cc7a3335bbff0a534d9f3a9c vp80-05-sharpness-1430.webp.pgm -be138896b80a40d19f0740a58e138500 vp80-05-sharpness-1430.webp.ppm -bb6b19089f93879eba2d4eb30a00867f vp80-05-sharpness-1430.webp.tiff -0def67a2d0e4ed448118fef3b7ace743 vp80-05-sharpness-1431.webp.bmp -7e4b9e153e7e1f2c6cdac993a8b813e4 vp80-05-sharpness-1431.webp.pam -547ffc3bca806ed8ccd5e0a8144711d9 vp80-05-sharpness-1431.webp.pgm -ed4a1efbf16d356da90f42a4905c998a vp80-05-sharpness-1431.webp.ppm -16ad1d77b4951f18252da24760436b27 vp80-05-sharpness-1431.webp.tiff -bbf5499355c2168984e780613e65227f vp80-05-sharpness-1433.webp.bmp -bfac8c040851686262a369892a849df8 vp80-05-sharpness-1433.webp.pam -728c352f485cdf199cbdc541ad4c3275 vp80-05-sharpness-1433.webp.pgm -f92f6333eb0d20b7ec1bf7c0cba3396b vp80-05-sharpness-1433.webp.ppm -820193c918fda34ec69b9b51a9980de9 vp80-05-sharpness-1433.webp.tiff -f1df5772fcbfac53f924ba591dacf90f vp80-05-sharpness-1434.webp.bmp -5a42133ab3abbf4f59f79d3ca1f860e2 vp80-05-sharpness-1434.webp.pam -8490ff50ec57b37e161aaedde0dd8db2 vp80-05-sharpness-1434.webp.pgm -2603f6a7df3ea5534ef04726826f9dd8 vp80-05-sharpness-1434.webp.ppm -82f4a703c24dd3cf7cf4593b5d01d413 vp80-05-sharpness-1434.webp.tiff -cf0cc73d9244d09791e0604bcc280da7 vp80-05-sharpness-1438.webp.bmp -e576fd6f57cbab1b5c96914e10bed9cc vp80-05-sharpness-1438.webp.pam -93c83925208743650db167783fd81542 vp80-05-sharpness-1438.webp.pgm -e6864282b45e7e51bd7d32031b6e5438 vp80-05-sharpness-1438.webp.ppm -dcafcd8155647258b957a1c1c159b49f vp80-05-sharpness-1438.webp.tiff -7534c5260cfa7ee93d9ded1ed6ab271b vp80-05-sharpness-1439.webp.bmp -66fc50052705274987f8efdfa6f5097a vp80-05-sharpness-1439.webp.pam -8e6a07c24d81652c8c8c0dfeec86d4b7 vp80-05-sharpness-1439.webp.pgm -8c1a1d0aa8e4f218fc92a2a677f7dc16 vp80-05-sharpness-1439.webp.ppm -d00ecbfea2f577e0f11c97854c01a278 vp80-05-sharpness-1439.webp.tiff -bbf5499355c2168984e780613e65227f vp80-05-sharpness-1440.webp.bmp -bfac8c040851686262a369892a849df8 vp80-05-sharpness-1440.webp.pam -728c352f485cdf199cbdc541ad4c3275 vp80-05-sharpness-1440.webp.pgm -f92f6333eb0d20b7ec1bf7c0cba3396b vp80-05-sharpness-1440.webp.ppm -820193c918fda34ec69b9b51a9980de9 vp80-05-sharpness-1440.webp.tiff -98683016a53aed12c94c4a18b73b0c74 vp80-05-sharpness-1443.webp.bmp -177119e26f624e1bbb4fcbe747908ecd vp80-05-sharpness-1443.webp.pam -a76c918a727cce42e41a9c72de5f76f1 vp80-05-sharpness-1443.webp.pgm -d6e6cb69d45ee9ef4b6af06e9b38db60 vp80-05-sharpness-1443.webp.ppm -b0bd5ef4ee3da632e829ec840d5dd8a4 vp80-05-sharpness-1443.webp.tiff -5d7f826f8ffb21190258a4a1e5bd7530 bad_palette_index.webp.bmp -75da37897db997f7bf7b86cd0a34eeb0 bad_palette_index.webp.pam -5b1e96464eac7a124232de5732b703a0 bad_palette_index.webp.pgm -8533d3a64063c7120879582766f63551 bad_palette_index.webp.ppm -953e6c351bda4b4b65a6c33e0f7b85f7 bad_palette_index.webp.tiff -94cd8c8c425643962da4bb3183342a5a alpha_filter_1.webp.bmp -a717bf0070e8264f253cebb7113f0555 alpha_filter_1.webp.pam -d6416589945519a945d43da512f75072 alpha_filter_1.webp.pgm -76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_1.webp.ppm -aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_1.webp.tiff -94cd8c8c425643962da4bb3183342a5a alpha_filter_2.webp.bmp -a717bf0070e8264f253cebb7113f0555 alpha_filter_2.webp.pam -d6416589945519a945d43da512f75072 alpha_filter_2.webp.pgm -76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_2.webp.ppm -aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_2.webp.tiff -94cd8c8c425643962da4bb3183342a5a alpha_filter_3.webp.bmp -a717bf0070e8264f253cebb7113f0555 alpha_filter_3.webp.pam -d6416589945519a945d43da512f75072 alpha_filter_3.webp.pgm -76c9aa742d9f3c8fe0cc568b939e688f alpha_filter_3.webp.ppm -aceecb9488e46b9ea91bb83b0cbe6e17 alpha_filter_3.webp.tiff -94cd8c8c425643962da4bb3183342a5a alpha_no_compression.webp.bmp -a717bf0070e8264f253cebb7113f0555 alpha_no_compression.webp.pam -d6416589945519a945d43da512f75072 alpha_no_compression.webp.pgm -76c9aa742d9f3c8fe0cc568b939e688f alpha_no_compression.webp.ppm -aceecb9488e46b9ea91bb83b0cbe6e17 alpha_no_compression.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_0_method_0.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_0_method_0.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_0_method_0.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_0_method_0.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_0_method_0.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_1_method_0.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_1_method_0.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_1_method_0.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_1_method_0.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_1_method_0.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_2_method_0.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_2_method_0.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_2_method_0.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_2_method_0.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_2_method_0.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_3_method_0.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_3_method_0.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_3_method_0.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_3_method_0.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_3_method_0.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_0_method_1.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_0_method_1.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_0_method_1.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_0_method_1.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_0_method_1.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_1_method_1.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_1_method_1.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_1_method_1.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_1_method_1.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_1_method_1.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_2_method_1.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_2_method_1.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_2_method_1.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_2_method_1.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_2_method_1.webp.tiff -1871cf8be60ff362642dd3f4f2f50fae alpha_filter_3_method_1.webp.bmp -33b13fbc45e1e9fe92cd60ba2108f715 alpha_filter_3_method_1.webp.pam -32f4070f2d6d1695f2285ca21f810cc2 alpha_filter_3_method_1.webp.pgm -8eb0e1ce7e3f102cea5dab93f76688f0 alpha_filter_3_method_1.webp.ppm -8a7526bd4ba54e7d989cfd68220876b6 alpha_filter_3_method_1.webp.tiff -d9950a87e5cf2de155fcd31c01475e93 alpha_color_cache.webp.bmp -91cf9afc53048c718ee51644dad6a34f alpha_color_cache.webp.pam -9d8e492f6b7227a74c04456c84e5113a alpha_color_cache.webp.pgm -82b1c0db2dc88c8fc8c109b622cd84d0 alpha_color_cache.webp.ppm -968aacad5d8a930b85698fc41d1a4f1b alpha_color_cache.webp.tiff -311d2535f9a76bd5623e3cd05e39fb89 lossy_q0_f100.webp.bmp -21a9f2a4ccc334498756a4738fa9b262 lossy_q0_f100.webp.pam -a6b90760b4aabf97de791615aca4a7f8 lossy_q0_f100.webp.pgm -3401967fb1d77a298198362ab5591534 lossy_q0_f100.webp.ppm -1c4cbf811d940f2f347c541606a78870 lossy_q0_f100.webp.tiff -b5c041d9a4f47452072ac69eaa6455cd lossless4.webp.bmp -85a73782fe7504bae587af5aea111844 lossless4.webp.pam -147b72dcacdb989714877612e927504b lossless4.webp.pgm -f434b118e30f3146c49db487e1ff2ba5 lossless4.webp.ppm -22e4581c62b8f17f2fc8e9c3e865fdc7 lossless4.webp.tiff -9bb8a5556e6c7cec368eac26210fd4a8 lossy_alpha4.webp.bmp -63945faa35db26000573bff7a02bba2e lossy_alpha4.webp.pam -96507416669c3135a73ced1b4f79d45c lossy_alpha4.webp.pgm -2f761d6794b556840b572d3db93e7bee lossy_alpha4.webp.ppm -70139ffba2b922bc2e93de3aa162d914 lossy_alpha4.webp.tiff -501113e927e73c99e90f874bc635e06d near_lossless_75.webp.bmp -dc04940d59a46f514c00cd7c90393c13 near_lossless_75.webp.pam -ef032f8837e7245def5ab012f7a04c8d near_lossless_75.webp.pgm -a81c1e1c64508cdea757fd2ac8f9d31b near_lossless_75.webp.ppm -a23482cf9c7e4ed2c4e5bc2534455dcb near_lossless_75.webp.tiff -34efa50cddbff8575720f270387414c9 color_cache_bits_11.webp.bmp -3d9213ea387706db93f0b39247d77573 color_cache_bits_11.webp.pam -28a26055225a9b5086c05aaf7b73e3ec color_cache_bits_11.webp.pgm -ebdd46e0760b2a4891e6550b37c00660 color_cache_bits_11.webp.ppm -a956c5897f57ca3432c3eff371e577f5 color_cache_bits_11.webp.tiff -7823bb625c9002171398fa5a190fe326 big_endian_bug_393.webp.bmp -7d41a1e1f15453ee91fc05b0b92ff13b big_endian_bug_393.webp.pam -1700bae9a667cd9478ba2b8a969491df big_endian_bug_393.webp.pgm -f8d4f927b7dc47d52265a7951b6eb891 big_endian_bug_393.webp.ppm -ffc5abfa7d15035bafc4285faece9b9a big_endian_bug_393.webp.tiff diff --git a/tests/Images/Input/WebP/lossless_color_transform.bmp b/tests/Images/Input/WebP/lossless_color_transform.bmp deleted file mode 100644 index e02262eca..000000000 --- a/tests/Images/Input/WebP/lossless_color_transform.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0739131f43940b1159b05e0480456d2c483a1f4ac35f4dcb8ffdf2cbfc2889fa -size 786486 diff --git a/tests/Images/Input/WebP/lossless_color_transform.pam b/tests/Images/Input/WebP/lossless_color_transform.pam deleted file mode 100644 index 57e467942421f6e8e0520af6789f5b4c8d6a7035..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1048645 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�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%0A`U~0r0|5p>P$07adqJWAvH;jY83#;%tI+`w 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ʀWlC{AwU5@ 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ೝN6vis}ZJ)iWL6zJhz0Ae4#am>1j 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 diff --git a/tests/Images/Input/WebP/lossless_color_transform.tiff b/tests/Images/Input/WebP/lossless_color_transform.tiff deleted file mode 100644 index 6efa917d6..000000000 --- a/tests/Images/Input/WebP/lossless_color_transform.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f252a25468c25e56ced9e70b9872c2324b84441eb18d036f6f763210dc565e42 -size 786642 diff --git a/tests/Images/Input/WebP/lossless_vec_list.txt b/tests/Images/Input/WebP/lossless_vec_list.txt index 119169699..d72bb0c71 100644 --- a/tests/Images/Input/WebP/lossless_vec_list.txt +++ b/tests/Images/Input/WebP/lossless_vec_list.txt @@ -1,6 +1,5 @@ List of features used in each test vector. -All the 'lossless_vec_1_*.webp' WebP files should decode to an image comparable to 'grid.pam' or, -equivalently 'grid.png'. +All the 'lossless_vec_1_*.webp' WebP files should decode to an image comparable to equivalently 'grid.png'. This synthetic picture is made of 16x16 grid-alternating pixels with RGBA values equal to blue B=(0,0,255,255) and half-transparent red R=(255,0,0,128), according to the pattern: @@ -22,8 +21,7 @@ BRBRBRBRBRBRBRBR RBRBRBRBRBRBRBRB The 'lossless_vec_2_*.webp' WebP files should decode to an image comparable -to 'peak.pam' or, equivalently 'peak.png'. Their alpha channel is fully -opaque. +to equivalently 'peak.png'. Their alpha channel is fully opaque. Feature list: lossless_vec_?_0.webp: none diff --git a/tests/Images/Input/WebP/peak.bmp b/tests/Images/Input/WebP/peak.bmp deleted file mode 100644 index a03e57d38..000000000 --- a/tests/Images/Input/WebP/peak.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bb45b4f5d7114ca7bf93e5025d91f3558c1925908fb368017ebc5d01a64a00b -size 49206 diff --git a/tests/Images/Input/WebP/peak.pam b/tests/Images/Input/WebP/peak.pam deleted file mode 100644 index 1a4f1dd13..000000000 --- a/tests/Images/Input/WebP/peak.pam +++ /dev/null @@ -1,8 +0,0 @@ -P7 -WIDTH 128 -HEIGHT 128 -DEPTH 4 -MAXVAL 255 -TUPLTYPE RGB_ALPHA -ENDHDR -ÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌâüÿÔâùÿÌâüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÌæùÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔâùÿÌâüÿÔèüÿÔâùÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔéôÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔéôÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔéôÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÔâùÿÌæùÿÌâüÿÔâùÿÌâüÿÌæùÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔéôÿÔèüÿÔèüÿÔèüÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌæùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÔâùÿÌæùÿÌâüÿÌæùÿØâäÿÌæùÿÌâüÿÌæùÿØâäÿÌæùÿÌâüÿÔéôÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÌæùÿÌæùÿÔèüÿÌæùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÌæùÿÔîüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌâüÿÔéôÿÌæùÿÔéôÿÔéôÿÌâüÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÌâüÿÔèüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÌæùÿÔéôÿÌæùÿÔèüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÌæùÿÔâùÿÌæùÿÌâüÿÌÝ÷ÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÔéôÿÌâüÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔéôÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔâùÿÌÛìÿÌâüÿÌÝ÷ÿÔéôÿÔâùÿÔéôÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔðôÿÔèüÿÔèüÿÔéôÿÌæùÿÔèüÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÌæùÿÔâùÿÌâüÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÔâùÿÌÝ÷ÿÄÖìÿ´ÆÙÿ„”§ÿŒ›©ÿŒ›©ÿ´·ÅÿœªÀÿ¬²ÄÿÌÖæÿÔÝîÿÔâùÿÔéôÿÔâùÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÌæùÿÔèüÿÔâùÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔîüÿÔîüÿÜêüÿÔîüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔâùÿÔèüÿÔâùÿÔâùÿÔéôÿÔâùÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÌâüÿÔèüÿÌâüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÜêüÿÔâùÿÔÞüÿÔâùÿÌÝ÷ÿÌÝ÷ÿœªÀÿl~œÿdnŒÿly”ÿly”ÿtyŽÿ„‹žÿ|€œÿ„‹žÿ”•¦ÿ¤ª¿ÿ´·Åÿ¼ÅÖÿÌÛìÿÔâùÿÔèüÿÔéôÿÔèüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔéôÿÔâùÿÔéôÿÌæùÿÔâùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÌâüÿÔéôÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÌæùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÌæùÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌâüÿÌâüÿÔèüÿÔèüÿÔâùÿÔâùÿÔâùÿÜêüÿÔÞüÿ¬¸Ôÿly”ÿ\n„ÿly”ÿkrÿlvŠÿ„‹žÿ„‹žÿŒ“¨ÿ|€œÿŒŒ”ÿ|†ÿ¬²Äÿ¼½Ìÿ”›¨ÿ¼ÌÙÿØâäÿÔèüÿÔâùÿÔéôÿÔèüÿÔèüÿÔéôÿÔâùÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔîüÿÜêüÿÔèüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÜêüÿÔèüÿÜêüÿÔèüÿÔîüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÌâüÿÔéôÿÔâùÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÌæùÿÌâüÿÌâüÿÌâüÿÌâüÿÌâüÿÔâùÿÌâüÿÔâùÿÌâüÿÌâüÿÌâüÿÔâùÿÌæùÿÌæùÿÌâüÿÔâùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÌâüÿÌâüÿÔâùÿÔèüÿ´¾Üÿ|ެÿtœÿTb|ÿ\g€ÿ|‰¤ÿœ¤¼ÿkrÿ„¬ÿ|€œÿ„…–ÿœš­ÿ„…–ÿ„…–ÿ„…–ÿ”›¨ÿ´±¼ÿ¬¸Çÿ”›¨ÿ´ÀÉÿÄÒäÿÔâùÿÜéòÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÜéòÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔâùÿÔéôÿÔèüÿÔèüÿÔâùÿÔéôÿÌæùÿÔâùÿÔéôÿÌâüÿÔéôÿÌæùÿØâäÿÌæùÿÌâüÿÔéôÿÌâüÿÔâùÿÌâüÿÔâùÿÌæùÿÔèüÿÌâüÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔâùÿÌæùÿÔèüÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿÌÝ÷ÿÜêüÿÔèüÿÄÖìÿ|‰¤ÿL\|ÿTg~ÿTb|ÿ\g€ÿdnŒÿlmŒÿ„¬ÿdnŒÿŒ¬ÿtyŽÿ|€œÿ|zœÿtrŽÿŒÿœš­ÿŒ†–ÿ”›¨ÿžž­ÿÄÌ×ÿ¼ÅÖÿœ£°ÿ¬¸ÇÿÔâùÿÜéòÿÜêüÿÔèüÿÔâùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔéôÿÔâùÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÔéôÿÌæùÿÔèüÿÌâüÿÌæùÿÌæùÿÔéôÿÔéôÿÌâüÿÔâùÿÌæùÿÌâüÿÌæùÿÔâùÿÌâüÿÌæùÿÔâùÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÄÖìÿdv”ÿL\|ÿL[tÿL[tÿTVuÿ\g€ÿkrÿ|€œÿtyŽÿtrŽÿ„…¤ÿ„…¤ÿtrŽÿtrŽÿ|€œÿžž­ÿœš­ÿ|€œÿœ™¡ÿ¤ª±ÿ¼ºÄÿŒ“œÿŒ“¨ÿŒ“¨ÿ¬²ÄÿÔÝîÿÜâïÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔèüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÌæùÿÔâùÿÔéôÿÌæùÿÔâùÿÔèüÿÔéôÿÌâüÿÔéôÿÌæùÿÔèüÿÔéôÿÌæùÿÌâüÿÔéôÿÌâüÿÌâüÿÔâùÿÌâüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌâüÿÔèüÿÔèüÿÌâüÿ¼Îçÿ\n„ÿL[tÿL[tÿT]|ÿL[tÿdjÿtyŽÿŒ“¨ÿ„…¤ÿ|€œÿ„…¤ÿ¬ªÌÿ|€œÿ|€œÿtrŽÿ„Œÿœš­ÿžž­ÿ„…–ÿ¬¶¼ÿ¤¤®ÿ|†ÿ¬°¼ÿ|ÿŒÿ”›¨ÿ´¾ÜÿÔÝîÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÔîüÿÜñüÿÔèüÿÔîüÿÜñüÿÔîüÿÜêüÿÔîüÿÜêüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔîüÿÜêüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔâùÿÔèüÿÔéôÿÔâùÿÔèüÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÔéôÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÌæùÿÔâùÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌÝ÷ÿ\n„ÿDVoÿL[tÿL[tÿDNiÿly”ÿžž­ÿ¤¦½ÿ”–´ÿtyŽÿtrŽÿ¤¦½ÿ¼ºÜÿ´±ÆÿŒ¬ÿlm~ÿ|zœÿtw„ÿžž­ÿ¬°¼ÿÌÍÕÿŒ“œÿŒÿžž­ÿœš­ÿ|†ÿŒ“¨ÿ¬°¼ÿÔÝîÿÔÝîÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔîüÿÔèüÿÔîüÿÔîüÿÔèüÿÔîüÿÔîüÿÜêüÿÔèüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÔéôÿÔèüÿÔèüÿÔèüÿÔâùÿÔéôÿÔâùÿÔéôÿÔèüÿÌæùÿÔâùÿÔéôÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔéôÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÌæùÿÔèüÿÌâüÿÌâüÿÔèüÿÔèüÿÔèüÿ¼Îçÿl~œÿLUtÿLUtÿLUtÿDNiÿTVuÿkrÿÄÆÔÿ¤¤®ÿÜâïÿ¼ºÄÿ„…–ÿžž­ÿÌÎäÿÜÞìÿìêøÿ´·Åÿ¤¤®ÿtw„ÿ”•¦ÿ´±¼ÿ¼ºÄÿŒŒ”ÿ””šÿœž¡ÿ¬®³ÿ”›¨ÿ„†‰ÿ¤¤®ÿ¬°¼ÿÔÝîÿÜéòÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔîüÿÜêüÿÜêüÿÜêüÿÜêüÿÜêüÿÜêüÿÜñüÿÔèüÿÜêüÿÔèüÿÜêüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÜñüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔèüÿÔéôÿÔèüÿÔéôÿÔèüÿÔéôÿÌæùÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌâüÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔâùÿÌæùÿÔèüÿÔèüÿÌâüÿÌâüÿ¬ÂÔÿTg~ÿL\|ÿL\|ÿDNtÿLUtÿDNtÿdjÿŒ¬ÿ”•¦ÿ´±ÆÿÜÜÜÿÔÒÖÿ|€œÿ”•¦ÿ¼ºÜÿÌÆÏÿ¬°¼ÿœ™¡ÿŒÿ|zŽÿ´¶ºÿ¤¤®ÿžž­ÿ|ÿœ™¡ÿ¬®³ÿ¬ª´ÿ¼ºÄÿ¤°ºÿžž­ÿžž­ÿ¤¦½ÿÌÛìÿÔâùÿÜêüÿÜñüÿÔèüÿÔîüÿÔèüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔèüÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÔèüÿÜñüÿÔèüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔèüÿÜñüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÌæùÿÌæùÿÔâùÿÌæùÿÔâùÿ¼ÎçÿTb|ÿDVoÿLUtÿT]|ÿLUtÿLSjÿLNiÿkrÿ”•¦ÿ„‹žÿœ£°ÿŒŒ”ÿŒÿlm~ÿ„Œÿ|zŽÿ”•¦ÿlm~ÿlfwÿtw„ÿ„‹žÿÌÍÕÿ„‹”ÿtw„ÿ”Žžÿ¤¤®ÿ¬ª´ÿœ™¡ÿ¬ª´ÿ´·Åÿ”•¦ÿ¤°ºÿ„‹”ÿ¤°ºÿÄÖìÿÔéôÿÔèüÿÜêüÿÔèüÿÔèüÿÔèüÿÜñüÿÜêüÿÔèüÿÜêüÿÜêüÿÔèüÿÜêüÿÜêüÿÔèüÿÔèüÿÜêüÿÔèüÿÔèüÿÜñüÿÔèüÿÔîüÿÜñüÿÔèüÿÔîüÿÜêüÿÔîüÿÔîüÿÜêüÿÔîüÿÔîüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÔèüÿ¤²ÇÿTg~ÿDVoÿLUtÿL\|ÿT]|ÿLUtÿLUtÿDNtÿdfxÿ|€œÿlm~ÿtw„ÿ|~ÿ|~ÿ\^pÿ|€œÿ¤¤®ÿlloÿdjÿdfxÿ\brÿtw„ÿŒÿ„Œÿ„‹žÿtw„ÿdjqÿ¤¤®ÿÄÃÄÿ””šÿŒŒ”ÿ|ÿ””šÿ„‹”ÿœž¡ÿÌÖæÿÜâïÿÜêüÿÜêüÿÜêüÿÔèüÿÔèüÿÜêüÿÔèüÿÜêüÿÔèüÿÜêüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜêüÿÔèüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÔîüÿÜñüÿÜêüÿÜñüÿÔîüÿÜêüÿÔðôÿÔèüÿÜòóÿÔîüÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔèüÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌÛìÿl~œÿL[tÿLUtÿL[tÿL\|ÿLUtÿL[tÿT]|ÿDNiÿT]|ÿtzœÿkrÿ\^pÿlvŠÿdjqÿ„…–ÿdjÿŒŒ”ÿ„‹”ÿtw„ÿ„‹žÿ|ÿlm~ÿdfxÿ\^pÿtw„ÿ„‹žÿdfxÿdalÿ„Œÿ¬²Äÿ¼½Ìÿ¬¶¼ÿÄÌ×ÿ´ÀÉÿ¤¤®ÿ´·Åÿ´·Åÿ¼ÌÙÿÜâïÿÔéôÿÜêüÿÔèüÿÜñüÿÔèüÿÔèüÿÔîüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÔèüÿÜñüÿÜêüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÜêüÿÔðôÿÜêüÿÔèüÿÜñüÿÔèüÿÔðôÿÜêüÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔðôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌæùÿÔèüÿÄÖìÿ\n„ÿL[tÿL[tÿDNiÿT]|ÿL[tÿLUtÿLUtÿLUtÿLNiÿ\g€ÿkrÿT[rÿT[rÿ\Vkÿdjÿtw„ÿtyŽÿlm~ÿ\brÿ\^pÿlrÿ\brÿ\^pÿdjÿdjÿdfxÿlm~ÿdfxÿ|ÿ”•¦ÿ¤¤®ÿ¬ª´ÿ¤¤®ÿ¼¾Âÿ¬°¼ÿ¬¶¼ÿÄÅÌÿÌÔÙÿ¼ºÄÿ¼ÅÖÿÜâïÿÜêüÿÜêüÿÔèüÿÔîüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÜêüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔîüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÔèüÿÔðôÿÔèüÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔéôÿÔéôÿÔéôÿÔðôÿÌæùÿÔéôÿÔéôÿÌæùÿÔéôÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿ´ÆÙÿTg~ÿDVoÿL[tÿDNiÿTg~ÿLUtÿLUtÿT]|ÿLNtÿLUtÿ\b}ÿ\g€ÿTVuÿLUtÿLSjÿLNiÿT]|ÿlm~ÿT]|ÿdfxÿT[rÿT[rÿT[rÿ\brÿT[rÿ\b}ÿlrÿdr|ÿT[rÿTUiÿtrÿ””šÿ¬ª´ÿ”•¦ÿŒŒ”ÿŒŒ”ÿ„‹”ÿ´¶ºÿ´¶ºÿÌÌÌÿÜÞäÿ¼ÂÌÿ¼ÂÌÿÔâùÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÔîüÿÔèüÿÔîüÿÔîüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÜêüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜñüÿÔèüÿÔðôÿÜñüÿÔîüÿÜòóÿÔîüÿÔîüÿÜòóÿÔîüÿÜñüÿÜéòÿÔîüÿÜñüÿÔéôÿÜñüÿÔéôÿÔîüÿÜêüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔéôÿÔèüÿÔðôÿÌæùÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÔéôÿÔéôÿÌæùÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿÌâüÿÔèüÿ´ÆÙÿL[tÿL[tÿLUtÿDVoÿTb|ÿLUtÿLUtÿL\|ÿDNiÿLUtÿT]|ÿT]|ÿT]|ÿT]|ÿLUtÿTVuÿTVuÿ|ÿ\b}ÿTUiÿLUtÿTUiÿLUtÿ\VkÿkrÿTVuÿTUiÿkrÿtyŽÿdbuÿT[rÿ|ÿœš­ÿ”Žžÿ””šÿ|ÿ„‹”ÿ|~ÿ|z{ÿŒŒ”ÿÄÃÄÿÌÍÕÿ¤ª±ÿ¬¸ÇÿÄÔÚÿÔâùÿÜñüÿÜêüÿÔèüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔèüÿÜñüÿÜêüÿÔîüÿÜêüÿÔîüÿÜêüÿÔîüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÔèüÿÜñüÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÜêüÿÔðôÿÔèüÿÔðôÿÔèüÿÔîüÿÔéôÿÔèüÿÔéôÿÔèüÿÔéôÿÔèüÿÔðôÿÌæùÿÔéôÿÔèüÿÔéôÿÔéôÿÔéôÿÔðôÿÌæùÿÔéôÿÌæùÿÔéôÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔèüÿÔâùÿÌâüÿÔèüÿÔèüÿ¬¸Ôÿ\g€ÿL[tÿL[tÿL\|ÿT]|ÿL\|ÿL[tÿL\|ÿL[tÿDNtÿL\|ÿT]|ÿT]|ÿ\^|ÿT]|ÿTVuÿTVuÿ\^|ÿdnŒÿTVuÿLSjÿLNiÿDHaÿDHaÿDHaÿ„‹”ÿkrÿTVuÿ|€œÿ„…–ÿTVuÿdfxÿtw„ÿ¤ª±ÿ„‹”ÿ|ÿtrÿ„…–ÿŒÿ„Œÿ„…–ÿ¤ž¤ÿÄÆÔÿ´·Åÿ¤¤®ÿ¤¤®ÿÄÌ×ÿÜâïÿÜêüÿÜñüÿÜêüÿÔèüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜêüÿÜñüÿÔîüÿÜêüÿÔîüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜòóÿÜñüÿÔðôÿÜñüÿÔðôÿÔîüÿÔîüÿÜñüÿÔèüÿÜñüÿÔéôÿÔîüÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔéôÿÌæùÿÔðôÿÔéôÿÌæùÿÔéôÿÌæùÿÔéôÿÔéôÿÌæùÿÔèüÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿ|ެÿL\|ÿL[tÿLUtÿLUtÿT]|ÿL[tÿL[tÿL[tÿDVoÿL[tÿDVoÿTb|ÿT]|ÿ\b}ÿ\b}ÿdnŒÿ\VkÿT]|ÿT]|ÿ\^pÿTVuÿTVuÿLUtÿLSjÿDHaÿLNiÿ|ÿ\b}ÿ\b}ÿ”›´ÿ„…–ÿT]|ÿTVuÿdr|ÿŒÿ¤¤®ÿ¬°¼ÿtw„ÿŒÿ„…–ÿœš­ÿŒ†–ÿtrÿœ™¡ÿ¼½Ìÿ„Šˆÿ¤¤®ÿ¼ÂÂÿÌÍÕÿÜéòÿÔèüÿÜêüÿÜñüÿÜñüÿÜñüÿÔîüÿÔîüÿÔîüÿÜñüÿÔèüÿÜêüÿÜñüÿÔîüÿÜêüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜòóÿÜñüÿÔîüÿÜñüÿÔîüÿÔéôÿÜñüÿÔðôÿÜñüÿÔéôÿÔîüÿÔðôÿÔèüÿÔîüÿÔéôÿÔîüÿÔèüÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔéôÿÌæùÿÔðôÿÔéôÿÔéôÿÌæùÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿÔâùÿÜêüÿt†¤ÿL[tÿDNiÿL[tÿLUtÿT[rÿT]|ÿL[tÿT]|ÿL[tÿDNiÿL[tÿL\|ÿTb|ÿ\b}ÿkrÿ\b}ÿtrŽÿ|€œÿ\b}ÿTVuÿ\b}ÿT[rÿDJlÿLIXÿLNtÿDHaÿDNPÿ\^|ÿ\^pÿ|ÿœ¤¼ÿtyŽÿT[rÿ\^pÿtw„ÿtw„ÿ„‹žÿŒÿ„ŒÿŒ†–ÿ¤¤®ÿ¬ª¼ÿ¼ºÄÿtw„ÿ|ÿžž­ÿ¤ª±ÿ¬®³ÿ¤ª±ÿ´ÀÉÿÜéòÿÜêüÿÜêüÿÜêüÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜòóÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÔèüÿÔîüÿÜñüÿÔèüÿÔðôÿÜêüÿÔîüÿÔéôÿÜñüÿÔéôÿÔîüÿÔèüÿÔðôÿÔéôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÌæùÿÔðôÿÌæùÿÔâùÿÔèüÿÌâüÿÔèüÿÔèüÿÌæùÿÔèüÿÔèüÿÔèüÿÔâùÿÔâùÿÔèüÿ|ެÿDVoÿL[tÿL[tÿLUtÿLUtÿT[rÿLUtÿT]|ÿT[rÿLUtÿLUtÿT]|ÿT]|ÿT]|ÿ\g€ÿT]|ÿdbuÿtzœÿ„…¤ÿ|zœÿT[rÿ\b}ÿ\^pÿTVuÿLUtÿLSjÿTUiÿLUtÿLSjÿTUiÿ|€œÿœ¤¼ÿlrÿ\b}ÿ\^|ÿtw„ÿTZ]ÿ\^pÿlm~ÿ„Œÿ”›¨ÿ´±Æÿ¼ºÄÿ¬ª´ÿdfxÿtw„ÿŒŒ”ÿŒÿ”›¨ÿœ™¡ÿœ£°ÿÄÌ×ÿÜêüÿÜêüÿÜêüÿÜêüÿÔîüÿÔðôÿÜñüÿÜêüÿÜñüÿÔîüÿÜêüÿÜñüÿÔèüÿÜñüÿÔèüÿÜñüÿÔîüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÔèüÿÜòóÿÔîüÿÔðôÿÔîüÿÔéôÿÜñüÿÔîüÿÔèüÿÔðôÿÔèüÿÔðôÿÔèüÿÔîüÿÔðôÿÔèüÿÔéôÿÔîüÿÔéôÿÔðôÿÌæùÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔéôÿÔèüÿÔèüÿÔâùÿÔèüÿÌæùÿÔèüÿÔèüÿÔâùÿÜêüÿÌÝ÷ÿÌÝ÷ÿly”ÿLUtÿLUtÿDVoÿDNiÿLUtÿLUtÿT]|ÿLUtÿLSjÿLUtÿLUtÿT]|ÿLZdÿT]|ÿT]|ÿdbuÿkrÿlmŒÿlmŒÿ|€œÿlmŒÿ\^|ÿLNiÿLSjÿTVuÿLUtÿ\^pÿLNiÿLSjÿTUiÿT]|ÿdfxÿT[rÿT[rÿlm~ÿT]|ÿ\brÿ\b}ÿdfxÿdfxÿdfxÿ¤ž¤ÿ¤¤®ÿžž­ÿŒÿlm~ÿdfxÿtw„ÿ|ÿŒŒ”ÿŒÿ¤¤®ÿ¤¤®ÿÌÛìÿÜéòÿÔéôÿÜñüÿÜñüÿÜñüÿÔèüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÔéôÿÜñüÿÔèüÿÜñüÿÔèüÿÔîüÿÜòóÿÔèüÿÔîüÿÔîüÿÔéôÿÔèüÿÔéôÿÔîüÿÔéôÿÔéôÿÔéôÿÌæùÿÔéôÿÌæùÿÌâüÿÔèüÿÌæùÿÔâùÿÔèüÿÔâùÿÌæùÿÔèüÿÔâùÿÌÝ÷ÿdv”ÿDNiÿL[tÿDNiÿDVoÿL[tÿT]|ÿT]|ÿLNiÿLSjÿLNtÿT]|ÿ\b}ÿtyŽÿ\^|ÿ\brÿ\b}ÿdnŒÿdfxÿdjÿdfxÿ\b}ÿTUiÿLSjÿT[rÿ\b}ÿ\^|ÿLSjÿTUiÿ\g€ÿTVuÿDNPÿLNiÿLNiÿLSjÿdjÿ|ÿlm~ÿT[rÿlm~ÿ„ŒÿŒÿ„Œÿ„…–ÿ¬ª´ÿžž­ÿ|zŽÿ„…–ÿ„Œÿ|ÿ|zŽÿ”Žžÿ””šÿ¤ª¿ÿ”›¨ÿ¼ÌÙÿÜâïÿÜêüÿÜéòÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÜñüÿÜêüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜòóÿÔîüÿÔîüÿÔîüÿÜñüÿÔîüÿÔðôÿÔîüÿÔèüÿÜòóÿÔîüÿÔîüÿÔèüÿÜòóÿÔîüÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔîüÿÔéôÿÔðôÿÔéôÿÔéôÿÔèüÿÔèüÿÔèüÿÔèüÿÌæùÿÔèüÿÔèüÿÔâùÿÔâùÿ|€œÿDVoÿL[tÿL\|ÿLUtÿL[tÿT]|ÿT]|ÿLUtÿDNiÿLUtÿLUtÿ\^|ÿlmŒÿ|€œÿkrÿTUiÿdfxÿ„…–ÿT]|ÿ\^pÿT[rÿLSjÿ\^pÿ\g€ÿdfxÿtÿtyŽÿLUtÿTVuÿdjÿDHaÿDNiÿLUtÿDNiÿ\^pÿtÿ|ˆœÿT[rÿTUiÿtyŽÿŒ¬ÿ”•¦ÿžž­ÿœ™¡ÿ¬®³ÿŒŒ”ÿtw„ÿ|€œÿ„…–ÿŒÿ|zŽÿ”•¦ÿœ™¡ÿ´±Æÿ”•¦ÿ¤°ºÿÌÖæÿÜéòÿÜêüÿÜñüÿÔéôÿÜñüÿÔîüÿÜñüÿÔèüÿÜñüÿÜñüÿÜêüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÜñüÿÔîüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÔîüÿÜêüÿÔîüÿÜñüÿÔðôÿÔèüÿÔîüÿÜñüÿÔèüÿÔîüÿÔèüÿÜñüÿÔèüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔéôÿÔéôÿÔâùÿÔèüÿÌâüÿÔèüÿÔâùÿÔèüÿÔâùÿÔèüÿ|ެÿDNiÿLUtÿLUtÿLUtÿL[tÿT]|ÿT]|ÿL[tÿDNtÿLNiÿDJlÿLUtÿdfxÿŒ¬ÿ¤ª¿ÿžž­ÿ\^pÿ|€œÿlm~ÿTUiÿTUiÿLSjÿDHaÿLSjÿ\g€ÿlvŠÿT]|ÿT[rÿ\g€ÿLSjÿDNiÿLUtÿLUtÿDNiÿLNiÿDNiÿtyŽÿT]|ÿLNiÿTNdÿtzœÿœš­ÿ„…–ÿ„…–ÿŒÿ¼ºÄÿ””šÿ„Œÿ„‹”ÿ„…–ÿ|y„ÿtrÿŒÿžž­ÿ´±¼ÿ¬°¼ÿ”›¨ÿ¬°¼ÿÌÜâÿÜéòÿÜéòÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔîüÿÜñüÿÔðôÿÜñüÿÜñüÿÔîüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÜñüÿÔîüÿÜòóÿÔîüÿÔîüÿÜòóÿÔîüÿÔéôÿÔîüÿÔèüÿÜñüÿÔèüÿÔðôÿÔèüÿÜòóÿÔîüÿÔðôÿÔîüÿÔéôÿÔîüÿÔéôÿÔîüÿÔéôÿÔèüÿÔðôÿÔèüÿÔèüÿÌæùÿÔâùÿÔèüÿÔâùÿÔâùÿÔèüÿly”ÿLUtÿLUtÿLSjÿLUtÿT]|ÿ\b}ÿTb|ÿLUtÿLNiÿDNtÿDNiÿLSjÿTVuÿdjÿœ¤¼ÿ´·ÅÿŒ¬ÿdjÿlm~ÿ\b}ÿLSjÿLNiÿTUiÿLSjÿT[rÿdbuÿdfxÿDHaÿLUtÿLNiÿDNiÿLUtÿ\b}ÿDHaÿTVuÿLNiÿTVuÿdjÿLSjÿLSjÿT]|ÿ|€œÿ|zŽÿ\^pÿdbuÿdfxÿ”•¦ÿ”ŽžÿŒŒ”ÿŒÿœš­ÿ\^pÿdbuÿŒ†–ÿžž­ÿÄÆÔÿ¼ºÄÿ¤¤®ÿ¤¤®ÿ¤°ºÿÌÔÙÿÜòóÿÜòóÿÜñüÿÜêüÿÜêüÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÜòóÿÔîüÿÜñüÿÜòóÿÔîüÿÜñüÿÔîüÿÜòóÿÔîüÿÔðôÿÜñüÿÔîüÿÜòóÿÔèüÿÔðôÿÜêüÿÔðôÿÔèüÿÔðôÿÔéôÿÔîüÿÔéôÿÔðôÿÔéôÿÔéôÿÔâùÿÔèüÿÔèüÿÔèüÿÔâùÿÔèüÿl~œÿTb|ÿLUtÿLUtÿLUtÿTVuÿT]|ÿT]|ÿTVuÿDNiÿDJlÿLUtÿLUtÿDNiÿ\brÿly”ÿœ¤¼ÿ¬¸Çÿ\fpÿ\b}ÿ\b}ÿT[rÿT[rÿLSjÿLSjÿ\^pÿdr|ÿ\^pÿdnŒÿLIXÿDNiÿLNtÿDNiÿT[rÿlm~ÿ\b}ÿ|€œÿ\^|ÿTVuÿdjÿTVuÿLSjÿdbuÿŒ¬ÿ\^|ÿ\^pÿ|zŽÿ|zŽÿœ™¡ÿŒÿ”Žžÿ”•¦ÿžž­ÿ\Vkÿ\Vkÿlm~ÿžž­ÿ´±¼ÿ¼ºÄÿ¼¾Âÿ¬ª´ÿ¤°ºÿ´·ÅÿÌÜâÿäòüÿÜéòÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜñüÿÜñüÿÔðôÿÜòóÿÔðôÿÔðôÿÜñüÿÔðôÿÜòóÿÔîüÿÜòóÿÔèüÿÔðôÿÔéôÿÔðôÿÔéôÿÔðôÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔèüÿÔâùÿÔèüÿÔâùÿÔâùÿ„¬ÿLUtÿT]|ÿT[rÿLUtÿLUtÿL\|ÿT]|ÿTVuÿDNtÿDNtÿTVuÿLNtÿDHaÿLNiÿLUtÿlvŠÿ´·ÅÿlvŠÿ\^pÿdfxÿ\^|ÿLSjÿTUiÿTUiÿLUtÿdfxÿ\brÿlm~ÿ\b}ÿDJlÿLUtÿLSjÿDNiÿLUtÿT[rÿtw„ÿ¤¦½ÿtw„ÿT]|ÿlm~ÿLNiÿTVuÿkrÿ„…–ÿLNiÿ\^pÿtw„ÿŒ†–ÿ¤¤®ÿŒÿœš­ÿŒ†–ÿtrÿtrÿ\^pÿ\^pÿ„‹žÿ¬°¼ÿ´±¼ÿ´¶ºÿľÌÿœ¢¡ÿ¬¸ÇÿÄÌ×ÿÜéòÿÜéòÿÜñüÿÜêüÿÜñüÿÜêüÿÜñüÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÔðôÿÜ÷üÿÜñüÿÜñüÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜ÷üÿÜñüÿÜñüÿÜ÷üÿÜòóÿÜ÷üÿÔðôÿÜñüÿÜñüÿÜòóÿÔðôÿÜñüÿÜòóÿÔèüÿÜòóÿÔèüÿÔîüÿÜòóÿÔéôÿÔîüÿÔðôÿÜòóÿÔéôÿÔðôÿÜéòÿÔîüÿÔðôÿÔéôÿÔðôÿÔéôÿÔéôÿÔðôÿÔéôÿÔéôÿÔéôÿÔéôÿÔâùÿÜêüÿŒš´ÿT[rÿT[rÿLUtÿT]|ÿLUtÿLUtÿT]|ÿLUtÿLUtÿDNiÿDNtÿTVuÿT]|ÿLSjÿLUtÿDNiÿT[rÿ|€œÿ\b}ÿT]|ÿT[rÿLNiÿ\ÿ\ÿ\ÿ\ÿ\ÿDB\ÿ\ÿ\ÿDJlÿDHaÿ\ÿDB\ÿ\ÿ,2<ÿ45<ÿ,.<ÿ434ÿ,,3ÿ434ÿDGLÿTSLÿLIXÿ45<ÿ,.<ÿ45<ÿIIERRRPRTWRPT_WPRW[_dWLPLRWWWb_WWY_Y[_WLR_bd_W__RY_R_WSWLW___W__kˆ¸ªr€ƒ˜°¿³‘»Ç–†}{{€„–™¥£z|“·ÖÖàÝàÝÝÝÚÝ>BPEIEEIRPTRLLRILLIIWL>BBIBPLPPLWRIW_WRPRW_bWLLILPWR__WWY_WWWRPW_idg[WW_RWYTWPLIP_Wd[W__d˜|kkk€ˆªº¢y£~„{u}€y„•ž£yuz“³ÝÝÝÝÚàÝÝÝEBILPPI>RPLLILRPILLIWPB@B::BEILWWR_WBIRPLLRPEILEIILEBITW_IEBLWLRR_WRWPIBMYPRTYWSLPPSWWPWTS_`^U^[``bbko~e`joŠzrro‡ÃÙÖÙàWTLEE@BEI_dREPCEMLWMLRPT_W_YWYU_\RMLRY`YYY_YY\``_``W\`ddbg`_vLRWLW:BPT_PSP@BJMPLLESWWYRWRSYRSWTRPUYYSSYWQYY_Y[YYP`YUSU[U`\W[RIRYPBEE63@IELLEILPWWPEW_PPTWWPLWLPWWEBCEMMMELPYQWRSMWYTWU\WU_YYPSU`MMYSY_Y`SUUYSMS\\Y_RLILLB:B@33<<@@ELBBEI@BIWY_RRLRWW__PRWLBLPEBBLRPLPLRWLIBT_WLPPRLWRRPRSLIMRYLYWHB>BEJBEEIISPMPJLJLRUYYY[UYSLMISPSMPSYMW[TMU\QUYYYRLPEIWP<::6:6BEEPB<@<<BEP>EPWWBLWW_Y_LILLIEILLE@RLRE@<>ILLLLLLRSMIMWM@LILEUYPHCEPYWRIMEPLRYRPRY\SPSUPMIMPURRYYTMMRRLMSUPTWLBBE<66EIB66MY[WLMIPMMMLJMRWPMLPSMMLLRLLRYTRQURMMQRWWWLBIIC:6EL@IBBE6>IEELPMLIMELCMEBBEEEURE>@IMR_PEBESWYTRLJMPLMIELMLRSIMRPLMRRMRR\RMMPQILLMW_[WBEIEJ<@@3>::666@RWLIELPSYYRILWLLIBEBBLELLIELE<:BCLELEE@EB>B>@>>@BBEPUP>@IML`RE:>LY[dRMEEEJEEBEPMIR@JRMEPURLLMWMLIMLEEMQRWWW@BEEC6B>@LE<>3:3663:<@EEPLLIEB<>>BB>CE@BLSM<>EIL_WIBMRY[_WSEECEEB@JPSMUIMPMPUWULPLRTMMMMEELMWYWL:3<:336>TWI<6:><@>>>>>>PMI>>@ELYULEPQTW_[SIC@JC>CEHIISMLMPSLPRRMLMPLLIEEEIMRWTLBB>616><@EE:61311136@:BEIB@B>B:><>><<<<@PSM>C@<@EIEIMIBEQPLRSWTILPMIME@EMLWRRWRIE<63>><61111<>:EIIEBLLRWLWTPRLI@EPRRRPIELTTL>>6<<@BBB@@<:<::@>::::BMQE><@JEMYMLIRLPYYRMLB<>>>C>HLPHEELMMIMUMPMMEEEE@EE@TP_RLE>:66E<6><3111136>BEE@@EELIELPW@EEBBILTWLLLLIPRRE<6>>E@88:>@>88<8>>JEJWMIEQPLWYWLRH><8>:EILMIBELLEELWRMEPMECBEEEEWLWWIL<<6:E<6::1111316BEEB<<>86:>>>:<86>EUJ<<@MCISMEPPLMYYRP\^@>>>>>HIEB@CIJEIMTRPIMEC>>JMJMRWWRPI<61:<>::888F:>8<68>EBHWPCEEIPYWWL_bI<8>C>IJME>C@IEEMPPMMMPE>:6@EPPIE<66@6@BB>:68>6688868>SSSE8>C@IMJBICBEW_PP_YSE<86CEEE>>EJEEBIMMPPMML>I:331111133BEITRB@BEEEE@66>>>6B><:83888686861@RPYQ<>C>MMEEMB>IRRPRWYYSB<6>C>>><>ECB>EMPMMILIB@EMMEBEW[RI<16ES>6633313>1PMLI>:B@@BTRB>@IEEB>:8><<6@>@J688383836:6@JMMB@:<6>E@>><>>CEECEEEMMPME@BEILIEBPWPLB16MW<<>:8113C3@EY@6:66@C><6<6::>88811836636BMPUUJ:6>EE>BEE>BLWYTRYRP::6CJ>:>>>>>@>@CILMPL>>BELLMŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹ŠŒŠŒŒ‹ŒŒ‹ŒttttttttttttuuuuuuuvvvwwuuuuwwwwvvvvvvvvvvvvvvvvvvvvvvuuuuuvuuvuŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹Œ‹‹‹Œ‹‹‹Œ‹‹ŠŒŒ‰Œ‰ŒuuuuuuuuututuuuuuuuvuutuuuuvwwwvvvvvvvvvvvvvvvvvuvvvvvuuuuvuvuvuŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹ŒŒ‹‹‹‹ŒŠ‹Œ‹Š‹Š‹ŒutttttttuuuuvvuvuuuuvuuuuuuuvwwwvvvvvvvvvvvvvwvvvuuuuvvuuuuuuutvŽŽŽŒŽŒŒŒ‹‹ŒŒŒŒŒŒŒŒ‹Œ‹‹Œ‹‹Œ‹Œ‹‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹Œ‹uttttuutvuutvvuvuvwvvwvvvvuuwwwwvvvvvvvvvvvvvvvvvvvuvvvuuuuuuuuuŽŽŽŽŒŒŽ‰Š‰‰‹‹ŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŒŒ‹‹‹‹‹‹‹‹‹Œtttuuuvuuvuvuwwwvxxxz||{xvwuwvvvvvvvvvvvvuvvvvuvvvuvvvvvwwxwuuuuŽŽŽŒŽŒ‰ˆ‡‡†ŒŠ‹ŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŒŠ‹‹‹ŠŒ‹‹‹‹‹Œ‹Š‰Œttuuuuuvuuuuuuxxyx{{}~}}{xxwvwvvwvwvvvvvuuuuvuvvvwvvwvvvvwwwuuvuŒŒŽŽŠ‰††‰‰ŠŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠ‹‹‹Š‹Œ‹Œ‹‹ŒŠŒŠ‹vuuuuvvuvuvvuwwwz||}~€~}{zywvvvvwvvvvvvvvvvvvvuwvwvvvvvvwvwutuuŽŒŽ‰ŒŒŒˆ‡„‡‡Š‰ŒŒŒŒ‹‹‹‹‹‹‹‹‹Š‹‹‹ŠŠŠ‹‹‹Š‹ŒŠŒŠ‹‹‹‹‹‹‹uvuvvuuuuuuuvvxy}}~~~~~~|{vvvvwvvvvvvvvuvvuvuvvwvvvvvvwwuwuuuu‡ƒ‰‰…‡…†ƒ……ˆŠ‹ŒŒ‹Š‹Š‹ŒŠ‹ŠŠŠŠŠŠŠŠ‰Š‹‹ŠŠ‰ŠŠŠŠ‰‹‹‹Šuuuuuuvvuuuvvyz}~€~}xwvvwxxwwwvwvvvvvvuuvwvvvvuvvvvvuuuuŒŽ‹ˆ„‰†ˆˆ…‡„ƒ†…‡‰‹ŒŠ‹Š‹Œ‹Š‹‹ŠŠŠŠ‰‰ŠŠŠ‰Š‰‰Š‰‰‰‰‰Š‰ŠŠuuuuuuuuuvwwxz{}}~€}€~}{xxvwwwxvvvvvvuuuuuuwvvvuuvuvuvvuvuuŒŽŽŠ‡ˆ‡ˆ‰ˆ‰††„……‡‰‹Œ‹‹Š‹‹ŠŠ‹ŠŠŠ‰‰Š‰ŠŠŠŠ‰Š‰Š‰‰Šˆ‰ŠˆŠuuuuuuuuuwxzyz{{}~~}}~~~€}}~|ywvvvvwwwvvvvuuuuuvwvvuvuvuuvvvtvuŒŽŽŽ‹ŒŒ‹Œ‰Š†……ƒ‚„ˆ‹‹‹‹‹‹‹ŠŠŠŠ‹ŠˆˆŠ‰‰Š‰Š‰‰‰ŠŠŠŠŠ‰Š‰Šuuuuuuuuwxxyy{z{}|||}}|}€€~€~zwwvvvvvwwvuvvuuuuwvvvvuuvvvuuvtvuŒŒŒŒŒŽŽŽŒ‰Š††‡‡††ƒ„‰Š‰ŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆ‰‰Š‰Š‰ŠŠˆŠ‰Šwvvvwwvwyyxwxy{|||||}|}|}~}xwvuvvvwuuuvuuvvvvutvuutuuvuvuuuŒŒŒŒŒŽŽŽŽŽ‹Œ‹ŠŠŒ…ˆ††††……ˆŠŠ‰ŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆ‰ˆŠ‰Š‰‰Š‰Š‰ˆŠ‰vvuvwwwyz{zzyz|}}|}}|~|}~~€€~}zyvvwvwvuuvuvvuuvvuutvvuvtvvtvuuŒŒŒŽŽŽ‹‹ŒŒŒ‹‰Œ‹ŠŠ‡†…ˆ‡††‡ˆˆŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‰ˆˆŠ‰ˆ‹‰Š‰‰‰‰‰‰vwvvxxzxz{{{{|}}}||}}}|}}€€{ywvvwvvvvuvvvvvvvvvuuuvuvuuuuuvŒŒŒŽŽŽ‰‰‹‹‹‹ŒŒŽŽ‹‹Š†ƒˆ‡ˆ†‡†ˆˆ‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‰ˆ‰Š‰‹Š‰‰‰ŠŠ‰wvvwyyyzy{|}~~}|{{{{{{|{~€~€}{yvvvvvuuuuvvvvvvvuuuvuvvuuuvvvŒŒŒŽŽŽŒŠ‹‹ŒŠ‰Š‹ŒŒŠˆ††ˆˆ†„……†Š‰ˆˆˆˆˆˆˆˆˆ‡ˆˆˆ‡ˆˆ‡‡‡‡‰ˆˆˆ‡ˆvwxwz{{z{z{{|||}|}|z}}||}~€€€€€}zxxwvvvvvvvvvuvvvvuvutuuvvuuuvŠŒŽŽ‘‹‹ŠŒ‰ŒŽŽŠ‰Œ‹‹‡‡†‡ˆ‡ƒƒ††‰ˆˆˆ‡‡ˆˆ‡‡‡ˆ‡‡‡ˆ‡ˆ‡‡ˆ†ˆˆ‡‡ˆˆwxz{{y{y||z|}|~|}||z}~|}}~€€‚€~€€|{wwvvuuvvuuuvuvvvvvuuuuuwuvvv‡ŠŒŽŒ‹Œ‹‰Š†‹ŒŠ‹‰ŠŠ‰‡…‡ˆˆ†ƒ……ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡‡‡‡‡ˆˆ‡‡‡ˆˆ‡ˆ‡‡ˆzz{{|z{{{|{{|}}}||{{~~~~~~€€€€~{zwvvuuuuuuuuvuvvvvvvuvvuvuuv‡†ˆŠ‹ŽŽŠŽŒ‹Š‰ŠŒŒ‰‡ˆ†Š‰†‡‡ˆˆ†ƒ„„‡‰‡ˆˆˆˆˆˆ‡‡‡‡ˆ‡‡ˆˆˆ‡‡‡ˆ‡ˆˆˆ‡ˆ{}~}|{{z|{{z}{}|}}{{€€~~€€~yxvvvvvvuuuuvuvvvvuvuvuvvuvvƒ„„„…ˆ‰ŒŽŽŒŒ‰ŠŒŽŒˆ‡ˆ‡†…‡‡†‡„ƒ„„†‡ˆˆ‡‡‡††††††…††‡ˆˆˆˆ‡‡†ˆ‡‡‡~~~~}}||{{||{|{{{~~~~}€€€€|yxvuvvvuuuuutuuvvvvvvuustuu‚„‚‚‡‹ŒŒŒŽŒŠŠ‹ŒŠ‰‰‰ˆ†‡…‡††………‚‡‡‡‡‡‡‡††††††††…‡‡ˆ‡‡ˆ‡ˆˆ‡‡‡€~€€~}~~~{{|{{z{{|}~~~~~€€€€€~|xvvvwvuuuuuuuuvvvvvvuvttuu‚‚‚ЉŒŒŽŒŽŠ‹ŒŒŠ‡†‰†‡†‡…†…„„ƒ…„††‡‡‡†††††††……†‡ˆ‡ˆˆ‡‡‡‡‡ˆ‡~~€}}}|{|z{{{{{|~€~~~~€€€€~yuwvvvuuuuuuttuvvvvvuuuutt‚‚€‚‚‚„†‰ŒŒŒŒŒŒ‹‰‰‡ˆ‡††…‡…†„…„ƒƒ„††‡††††††‡††††‡‡†ˆ‡ˆ‡ˆ‡‡ˆ‡€€€~~}|||{|{{|{|}|}~~~~€€€€€€€~zwvvuuvuuuuvuuvuuvuvvvttttƒ‚ƒ†…††‹Ž‹†…†‡‡‡„ƒ„„†…‚ƒ‚„‚†„†‡……†…„„…………†„†…††‡†‡†††€€€~~{{z{{{{{{|~€€€€€€€|zvuvvvwwwuuuuutuuvvvvtuttƒ‚ƒ‚…†††…ŠŽŽŒ‡‡‡‡‡…„ƒ„ƒ†„„‚ƒ‚‚ƒ…‡……†…†……………………†…†‡††……††€€€€€~}|z{z{zzz{|~}€€€€€€€}wwvuvvvvuuuuvtvuuvuvtust‚ƒƒ„„„†„†‰ŒŽŒˆ†‡‡††…ƒ………„ƒ‚‚‚ƒ„„††……„……„………†……„………†………††€€€€~}}z{zzzzz{|~€€€€€€€€€€zwwvwvuwuvuuuuttuuvuutstƒƒ‚‚ƒ„†……„††ŽŽŽŽŠŠ…†‡†ˆ…‚ƒƒ……‚‚‚„„†‡………………………………††„„……„…††€€€€~~}{z{{{{{{|}€€~€€€€€~{xuvvvuuuuvuuvuvvuvtuts‚‚ƒ‚‚…‰‡‡…‰‹Œ‰ˆ†‡†…„„„ƒƒƒ‚‚‚ƒ‚…††„„…„ƒ„ƒ„„…„„„„„„„………†€€€€}€~}}||{{{|||~~€€€€€€}zwvvwtuvuvuvuuutuuuvut‚‚‚‚…ˆˆ†…†‰ŒŽŽŽŒ‰……„……„‚ƒƒƒ‚‚ƒ‚„ƒ…†…„…„ƒ„„ƒƒ…„„„„„„„………†€€€€~~~€}}{{|{{{}}€€€€€€€{yuvuuuvuvuuuuttuuvuut‚‚‚‚„†ˆ†…‡‡‰ŽŽŽŽŒŽŒŒ‰…†…††ƒ‚„„‚‚‚‚‚‚‚€…„ˆ„…ƒƒ„ƒƒ‚ƒ„…„„„„„ƒ……†…€~~~~}{{{|{||}}€€€€€€€€€~}|vtuuuuvvuvtuttttvuuu‚‚€‚‚…ˆˆ‰‡†„Š‹ŽŽŠ‡…‡……ƒƒ‚„‚ƒ‚ƒ‚ƒƒ‚„„…‡…ƒƒƒƒƒƒ„…„„„„„„ƒ………†€€€€~~€|}|{{{|{}}~€€€€€€€€€€€}xutvuvvuvvutttttuuuu€„†Šˆ†„„‡ˆŒŒŒŒ‹„„…‡†ƒ„ƒ…‚‚‚ƒƒ‚‚ƒƒ……„ƒƒƒ‚‚‚ƒ„ƒƒƒƒƒ„…………€€€€€~€€€~zz||}}|~€€€€€€€€€€{wustvuvvuuutttuvvuu€€‚…‡†ˆ…ƒƒƒ‰‹ŒŽŒŒŒŒˆ…†„…‡ƒ„‚‚ƒ‚‚‚ƒ………ƒƒ‚‚‚‚ƒ‚ƒƒƒƒƒƒ„„……€€€€€€€€€€€€|{}}|}|~€€€€€€€€€€€}zwsuvvvutuuttuuuuuu€ƒƒ„‚‚ƒ„†‹‹Ž‹ŒŒ‰‡†„„†…„ƒ‚‚‚ƒ„…„ƒ‚‚‚‚‚ƒƒƒƒƒƒƒƒ„„……€€€€€€€€€~}|}}|||€€€€€€€€€€€€€€}|wuuuuvuvuuuuuuuuuu~~~~€€‚ƒƒ‰‹Š‹Œ‡†…‡…ƒ„…†ƒ‚€‚ƒ„„„„‚‚‚‚‚ƒƒƒ‚ƒ„„„„„……€€€€€€€€}||}}~~€€€€€€€€~{xvuuvuuvvvuuuvvvuv~~~~€~€€€€€‚ˆŠ‡ŠŒŠ†‡…„„…†…‚‚€‚‚ƒƒ…„‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ„„„„€€€€€€€€€~~}}}€€€€€€€€€€~ywuvvuwwwwwwvvvvvv€~~~~~€~~~€‚ƒ‚„‰Š…†„„††ƒ„‚‚‚‚‚ƒ„…‚„‚‚‚‚‚‚‚‚ƒ‚ƒƒƒ„„„€€€€€€€€€€€€~}~€€€€€€€€€€€€€~ywuuvvwwwwwwxwxvvv€~~~~~~~~€€€ƒ„‡…‚‚„„……‚‚‚‚€‚„ƒ„‚‚‚‚‚‚‚‚‚„ƒƒƒ„„€€€€€€€€€€~€€€€€€€~€€€€€€€€~{vvuvuwwwwwwwxvwwv€~~~~~~€~€€€€‚‚‚€ƒ„……€‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚‚‚ƒ„ƒ„„„€€€€€€€€€€~~€€€€€€€€€€€€€€€~zxvvvvwwwwwwwwwwvw~~~~~~~~~~~€‚ƒ……ƒ‚‚€ƒ‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}|xwwwwwwxyxyyxyxw~~~~~~~~~~~~~~~€€€€„„‚ƒ‚€‚‚ƒ‚‚‚‚‚‚ƒƒƒƒ„ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~yxyyxwxywxyxxywx~~~~~~~~~~~~~~~~~~€~‚ƒ‚‚€‚€€€€‚‚‚„…‰ŒŠˆ„„ƒ„ƒƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}{|{{xxyxywxxyxyy~~~~~~~~~~~~~~~~~~~~~~~~€€‚‚‚‚‚€‚ƒ‚…ŠŽŒ‰‹ˆ…„ƒƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}yyxyyxyxyyxy~}~~~~~}~~}~~}~~~~~~~€~€‚€‚†ˆ‹‹ŠŠ‹Š…„ƒƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}}|}|zyvwzyyy~€~~~~~€~~~}~~~~~~~~~~~~~€€€‚„‡‰ŽŽˆ‰‹‰ƒ‚ƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}}~}zyzwyzyy€€€€€‚€€~~~~~~~}~~~~‚‚~~~€ƒˆ‹Ž‹Š†Š‰ˆ…ƒƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~}}}|{yzzyzy€€€~€€~~~~‚‚ƒƒ€€‚„‡ˆŠ‰‹ŠŠŠ‰…ƒƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}~}}|{{yyyyy€€€€€€~€€€€~~€~~~~~€~‚„‚ƒ€€‚‚…‡‹Šˆ‹Žˆ„ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€~~}{{{zx€€€~~€€‚~€€~~ƒ‚ƒ€€€‚€‚€‚„…†‹ˆˆ‹‡„ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€|}|}|{yy~€€€~~€~‚€~€€€€€€€€‚€€€‚€~‚€ƒ‚‚„…ƒ††„„…„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~||{{~€€~€~€€‚€€~€€€€€‚€€€‚€ƒ€€ƒƒ‚€€‚€„ƒƒ…„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€~~}|€~~€~€€€€~~€€€‚‚‚‚€€‚ƒ€€€€‚ƒ€‚‚‚€‚‡ƒ€‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}}€€€€~~~~€‚€€~~‚€~€~€€„‚‚€€€€€ƒ€‚ƒ€‚„„ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~€ƒ€€~~~€€€€~~€€‚‚€ƒ€€€€‚‚ƒ€‚‚‚ƒƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~€€‚‚€~€~€€€€~€€ƒ‚‚‚€‚€‚‚‚‚ƒƒ‚ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~}€~€‚€~€~€€€€‚€€€€ƒ‚ƒ€€€€€ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~~€‚ƒ€‚€€€€‚€€€‚ƒ‚€‚ƒ€ƒ‚‚‚‚€€€‚‚€€€€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~~}„‚ƒ„„ƒ€€€‚€€€€€ƒ‚€€‚‚ƒ‚‚€ƒ‚‚‚‚‚‚€€€€‚‚‚€€€€€€~~€€€€€€€€€€€€€€€€€€€€€€€€~~~€‚„‚‚„„…ƒ€€€~€€€ƒ‚€‚‚‚ƒ‚‚‚€‚‚ƒ„ƒƒ€‚‚€€€‚‚€€~~€€€€€€€~€€€€€€€€€€€€€€€€~~~€‚ƒ‚‚‚……ƒ‚~€€€ƒ‚‚„…‚…„ƒ‚ƒ‚ƒ‚€‚‚„ƒ‚ƒ€ƒ‚‚‚€€€€€~€€€€€€€€€€€€€~~~€€€€€€€€€€€€€€~~‚‚‚„…„…€€€€€ƒ‚ƒ……‡„ƒƒ„ƒ‚ƒ‚€€‚„„ƒ‚„‚€‚ƒ‚‚€€€~€€€€€€€€€€~~~~€€€€€€€€€€€€€€€€€€€€}‚ƒ‚ƒ……„ƒ€‚‚‚‚€‚‚€‚€ƒƒƒ‚‚„†„†…„ƒ„„‚‚‚‚‚„ƒƒ‚ƒ‚‚€‚‚‚€€€€€~~€€€€€€€€€€€€€€€€€~€€€€€€€€€€€€€€€€€~~‚ƒƒƒ„ƒ‚‚ƒ‚ƒ‚€‚ƒ‚ƒ‚‚ƒ†††„ƒ‚ƒ‚„‚‚‚~€ƒƒƒ‚ƒƒ‚‚‚‚€€€€€€€€€€€€€€€€€€€€~~~€€~€€€€€€€€~€€€€€ \ No newline at end of file diff --git a/tests/Images/Input/WebP/peak.ppm b/tests/Images/Input/WebP/peak.ppm deleted file mode 100644 index c1ddfca70..000000000 --- a/tests/Images/Input/WebP/peak.ppm +++ /dev/null @@ -1,4 +0,0 @@ -P6 -128 128 -255 -ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÌæùÔâùÌâüÔâùÌâüÔâùÔèüÔâùÔèüÔâùÌæùÔèüÌæùÔèüÌâüÔèüÌæùÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÌæùÔèüÌæùÔéôÌæùÔèüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌâüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔâùÌâüÔâùÌæùÔâùÌæùÔâùÌâüÔèüÌæùÔâùÌæùÔâùÌâüÔâùÌâüÔèüÔâùÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔéôÔèüÌæùÔéôÌâüÔéôÌæùÔéôÌâüÔéôÌâüÌæùÔâùÌæùÌâüÔéôÌâüÌæùÔâùÌæùÌâüÔéôÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌÝ÷ÌâüÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÔâùÌæùÌâüÔâùÌâüÌæùÔèüÌæùÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔéôÔèüÔèüÔèüÌæùÔèüÌæùÔéôÌâüÔéôÌæùÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌâüÔèüÌâüÌæùÔâùÌæùÌæùÌæùÔâùÌæùÌâüÔèüÌæùÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÔèüÔâùÌæùÔâùÔèüÌâüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÔéôÔèüÔèüÔèüÔéôÔèüÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌæùÔâùÌæùÌâüÌæùØâäÌæùÌâüÌæùØâäÌæùÌâüÔéôÌâüÌÝ÷ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÔâùÌâüÔâùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌæùÔèüÌâüÌæùÌæùÔèüÌæùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÌæùÔîüÔèüÔéôÔèüÔèüÔéôÌæùÔèüÌâüÔéôÌæùÔéôÔéôÌâüÌæùÔéôÌæùÔéôÌâüÌæùÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÌæùÌâüÌæùÌâüÔâùÌæùÔâùÌæùÌâüÔèüÌâüÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèüÔèüÔèüÌâüÔèüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÌæùÔéôÌæùÔèüÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔéôÌâüÔéôÌæùÌæùÔâùÌæùÌâüÌÝ÷ÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔèüÌâüÔâùÌâüÔâùÌâüÌâüÌæùÔâùÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÌâüÔâùÌæùÔâùÌæùÔèüÌæùÔèüÔâùÔèüÔéôÌâüÔâùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔèüÔèüÜñüÔèüÜêüÔèüÔîüÔèüÔéôÔèüÔèüÔéôÔèüÔîüÔèüÔèüÔèüÔéôÔèüÔèüÔèüÔéôÌâüÔéôÌæùÔéôÌâüÔéôÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌâüÌæùÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÔâùÌâüÌâüÌæùÌâüÌæùÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔâùÌâüÔâùÌâüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔâùÌÛìÌâüÌÝ÷ÔéôÔâùÔéôÔèüÔâùÔèüÔâùÔèüÌâüÔèüÌâüÔèüÔâùÔèüÔâùÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÔðôÔèüÔèüÔéôÌæùÔèüÌæùÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔéôÌâüÔéôÌæùÌâüÔéôÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÔâùÌæùÔâùÌæùÔèüÌâüÔèüÔâùÔèüÌâüÔèüÔâùÔèüÌæùÔâùÌâüÌâüÔâùÌâüÔèüÌâüÔèüÔâùÔâùÌÝ÷ÄÖì´ÆÙ„”§Œ›©Œ›©´·ÅœªÀ¬²ÄÌÖæÔÝîÔâùÔéôÔâùÔèüÔèüÔâùÔèüÔèüÌæùÔèüÔâùÔèüÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÜêüÔîüÜêüÔèüÜñüÔèüÔîüÜêüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔîüÔîüÜêüÔîüÔîüÜêüÔèüÜñüÔèüÔîüÜêüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÔèüÔèüÔéôÔâùÔèüÔâùÔâùÔéôÔâùÌæùÔéôÌâüÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÌâüÔâùÌâüÌâüÌâüÔâùÌâüÌâüÔèüÌâüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÌâüÔèüÔâùÔèüÔâùÜêüÔâùÔÞüÔâùÌÝ÷ÌÝ÷œªÀl~œdnŒly”ly”tyŽ„‹ž|€œ„‹ž”•¦¤ª¿´·Å¼ÅÖÌÛìÔâùÔèüÔéôÔèüÔèüÌâüÔèüÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÜêüÔîüÔèüÔîüÔèüÜêüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔèüÜñüÔèüÔîüÔèüÜêüÔîüÔèüÔèüÔèüÔèüÔéôÔèüÔèüÔèüÔèüÔâùÔèüÔéôÔâùÔéôÌæùÔâùÔéôÌâüÔéôÌâüÔéôÌâüÌâüÔéôÌâüÌâüÌâüÌâüÌâüÌæùÌâüÌæùÔâùÌæùÌâüÌæùÔâùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÌâüÌâüÔèüÔèüÔâùÔâùÔâùÜêüÔÞü¬¸Ôly”\n„ly”krlvŠ„‹ž„‹žŒ“¨|€œŒŒ”|†¬²Ä¼½Ì”›¨¼ÌÙØâäÔèüÔâùÔéôÔèüÔèüÔéôÔâùÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔîüÜêüÔèüÔîüÜêüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÜêüÔîüÜêüÔèüÜêüÔèüÔîüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔéôÔèüÔéôÌâüÔéôÔâùÌæùÔâùÔèüÔâùÌæùÔéôÌæùÔéôÌâüÔéôÌâüÌæùÌâüÌâüÌâüÌâüÌâüÔâùÌâüÔâùÌâüÌâüÌâüÔâùÌæùÌæùÌâüÔâùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèüÔèüÌâüÌâüÔâùÔèü´¾Ü|ެtœTb|\g€|‰¤œ¤¼kr„¬|€œ„…–œš­„…–„…–„…–”›¨´±¼¬¸Ç”›¨´ÀÉÄÒäÔâùÜéòÔâùÔèüÔâùÔèüÔèüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔèüÔîüÔèüÔèüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÜêüÔèüÜéòÔèüÔèüÔéôÔèüÔèüÔèüÔâùÔéôÔèüÔèüÔâùÔéôÌæùÔâùÔéôÌâüÔéôÌæùØâäÌæùÌâüÔéôÌâüÔâùÌâüÔâùÌæùÔèüÌâüÌæùÔèüÌâüÔèüÌâüÔèüÔâùÌæùÔèüÌæùÔâùÌæùÔèüÌâüÔèüÌâüÔèüÔèüÌÝ÷ÜêüÔèüÄÖì|‰¤L\|Tg~Tb|\g€dnŒlmŒ„¬dnŒŒ¬tyŽ|€œ|zœtrŽŒœš­Œ†–”›¨žž­ÄÌ×¼ÅÖœ£°¬¸ÇÔâùÜéòÜêüÔèüÔâùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔîüÜêüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÜñüÔîüÜñüÔîüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔéôÔâùÔèüÔâùÔèüÔèüÔâùÔéôÌæùÔèüÌâüÌæùÌæùÔéôÔéôÌâüÔâùÌæùÌâüÌæùÔâùÌâüÌæùÔâùÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÌâüÔèüÄÖìdv”L\|L[tL[tTVu\g€kr|€œtyŽtrŽ„…¤„…¤trŽtrŽ|€œžž­œš­|€œœ™¡¤ª±¼ºÄŒ“œŒ“¨Œ“¨¬²ÄÔÝîÜâïÜêüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÔîüÜêüÔèüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜêüÔîüÔèüÔèüÔéôÔèüÔèüÔèüÌæùÔâùÔéôÌæùÔâùÔèüÔéôÌâüÔéôÌæùÔèüÔéôÌæùÌâüÔéôÌâüÌâüÔâùÌâüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÌâüÔèüÔèüÌâü¼Îç\n„L[tL[tT]|L[tdjtyŽŒ“¨„…¤|€œ„…¤¬ªÌ|€œ|€œtrŽ„Œœš­žž­„…–¬¶¼¤¤®|†¬°¼|Œ”›¨´¾ÜÔÝîÜêüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔîüÔîüÜñüÔèüÔîüÜñüÔîüÜêüÔîüÜêüÔèüÔîüÔèüÜñüÔèüÜêüÔèüÔèüÔèüÔîüÜêüÔèüÔèüÔéôÔèüÔèüÔâùÔèüÔéôÔâùÔèüÌâüÔéôÌâüÔéôÌâüÔéôÔéôÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÌæùÔâùÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌÝ÷\n„DVoL[tL[tDNily”žž­¤¦½”–´tyŽtrޤ¦½¼ºÜ´±ÆŒ¬lm~|zœtw„žž­¬°¼ÌÍÕŒ“œŒžž­œš­|†Œ“¨¬°¼ÔÝîÔÝîÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÜêüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔèüÔîüÔèüÔèüÔèüÔèüÔîüÔèüÔîüÔîüÔèüÔîüÔîüÔèüÔîüÔîüÜêüÔèüÔîüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÔîüÔèüÔèüÔéôÔèüÔèüÔèüÔâùÔéôÔâùÔéôÔèüÌæùÔâùÔéôÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔéôÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÌæùÔèüÌâüÌâüÔèüÔèüÔèü¼Îçl~œLUtLUtLUtDNiTVukrÄÆÔ¤¤®ÜâZĄ…–žž­ÌÎäÜÞììêø´·Å¤¤®tw„”•¦´±¼¼ºÄŒŒ”””šœž¡¬®³”›¨„†‰¤¤®¬°¼ÔÝîÜéòÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔîüÜêüÜêüÜêüÜêüÜêüÜêüÜñüÔèüÜêüÔèüÜêüÔîüÜêüÜñüÔèüÜñüÜñüÔèüÜñüÜñüÔèüÜñüÜñüÔèüÜñüÔèüÜñüÔîüÔèüÜñüÔèüÜñüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÜñüÔéôÔèüÔðôÔèüÔéôÔèüÔèüÔéôÔèüÔéôÔèüÔéôÌæùÔéôÌâüÔéôÌâüÔéôÌâüÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔâùÌæùÔâùÌæùÔâùÌæùÔèüÔèüÌâüÌâü¬ÂÔTg~L\|L\|DNtLUtDNtdjŒ¬”•¦´±ÆÜÜÜÔÒÖ|€œ”•¦¼ºÜÌÆÏ¬°¼œ™¡Œ|zŽ´¶º¤¤®žž­|œ™¡¬®³¬ª´¼ºÄ¤°ºžž­žž­¤¦½ÌÛìÔâùÜêüÜñüÔèüÔîüÔèüÔèüÜêüÔèüÜñüÔèüÜêüÔèüÜêüÔèüÜêüÔîüÔèüÔèüÜñüÔèüÔèüÜêüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÔîüÜñüÔèüÜñüÜêüÔîüÔèüÜñüÔîüÜñüÔîüÔèüÔèüÔðôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÔéôÌæùÔéôÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌæùÔéôÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÌæùÌæùÔâùÌæùÔâù¼ÎçTb|DVoLUtT]|LUtLSjLNikr”•¦„‹žœ£°ŒŒ”Œlm~„Œ|zŽ”•¦lm~lfwtw„„‹žÌÍÕ„‹”tw„”Žž¤¤®¬ª´œ™¡¬ª´´·Å”•¦¤°º„‹”¤°ºÄÖìÔéôÔèüÜêüÔèüÔèüÔèüÜñüÜêüÔèüÜêüÜêüÔèüÜêüÜêüÔèüÔèüÜêüÔèüÔèüÜñüÔèüÔîüÜñüÔèüÔîüÜêüÔîüÔîüÜêüÔîüÔîüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔéôÔîüÔéôÔéôÔéôÔéôÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌæùÔéôÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÔèü¤²ÇTg~DVoLUtL\|T]|LUtLUtDNtdfx|€œlm~tw„|~|~\^p|€œ¤¤®llodjdfx\brtw„Œ„Œ„‹žtw„djq¤¤®ÄÃÄ””šŒŒ”|””š„‹”œž¡ÌÖæÜâïÜêüÜêüÜêüÔèüÔèüÜêüÔèüÜêüÔèüÜêüÜñüÔèüÜêüÔîüÔèüÔîüÔèüÜñüÔèüÔèüÜêüÔèüÔîüÜñüÔîüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔîüÔîüÔîüÜñüÜêüÜñüÔîüÜêüÔðôÔèüÜòóÔîüÔèüÔðôÔèüÔéôÔèüÔðôÔèüÔéôÔéôÔèüÔðôÌæùÔéôÌæùÔéôÔéôÌæùÔéôÔéôÔéôÌæùÔéôÌæùÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌÛìl~œL[tLUtL[tL\|LUtL[tT]|DNiT]|tzœkr\^plvŠdjq„…–djŒŒ”„‹”tw„„‹ž|lm~dfx\^ptw„„‹ždfxdal„Œ¬²Ä¼½Ì¬¶¼ÄÌ×´Àɤ¤®´·Å´·Å¼ÌÙÜâïÔéôÜêüÔèüÜñüÔèüÔèüÔîüÔèüÔîüÔèüÜñüÔèüÜêüÔîüÜêüÔèüÜñüÜêüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔèüÜñüÜêüÔðôÜêüÔèüÜñüÔèüÔðôÜêüÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔðôÔèüÔéôÔîüÔéôÔðôÌæùÔéôÔéôÌæùÔéôÔéôÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌæùÔèüÄÖì\n„L[tL[tDNiT]|L[tLUtLUtLUtLNi\g€krT[rT[r\Vkdjtw„tyŽlm~\br\^plr\br\^pdjdjdfxlm~dfx|”•¦¤¤®¬ª´¤¤®¼¾Â¬°¼¬¶¼ÄÅÌÌÔÙ¼ºÄ¼ÅÖÜâïÜêüÜêüÔèüÔîüÔèüÜêüÔèüÜñüÔèüÜêüÔîüÔèüÜêüÔèüÜñüÔèüÜñüÔèüÜêüÔîüÔèüÜñüÔîüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔèüÔèüÜñüÔèüÔðôÔèüÔîüÔéôÔîüÔéôÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔéôÔéôÔéôÔðôÌæùÔéôÔéôÌæùÔéôÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèü´ÆÙTg~DVoL[tDNiTg~LUtLUtT]|LNtLUt\b}\g€TVuLUtLSjLNiT]|lm~T]|dfxT[rT[rT[r\brT[r\b}lrdr|T[rTUitr””š¬ª´”•¦ŒŒ”ŒŒ”„‹”´¶º´¶ºÌÌÌÜÞä¼Â̼ÂÌÔâùÜêüÔèüÜêüÔîüÔèüÔîüÔèüÔîüÔîüÜêüÔîüÔèüÜñüÔèüÜêüÔîüÜêüÜñüÔèüÜñüÔîüÜñüÔîüÔèüÜñüÔèüÔðôÜñüÔîüÜòóÔîüÔîüÜòóÔîüÜñüÜéòÔîüÜñüÔéôÜñüÔéôÔîüÜêüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔðôÔèüÔéôÔèüÔðôÌæùÔðôÌæùÔéôÌæùÔéôÔéôÔéôÔéôÌæùÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèüÌâüÔèü´ÆÙL[tL[tLUtDVoTb|LUtLUtL\|DNiLUtT]|T]|T]|T]|LUtTVuTVu|\b}TUiLUtTUiLUt\VkkrTVuTUikrtyŽdbuT[r|œš­”Žž””š|„‹”|~|z{ŒŒ”ÄÃÄÌÍÕ¤ª±¬¸ÇÄÔÚÔâùÜñüÜêüÔèüÜêüÔîüÔèüÜñüÔèüÔîüÔèüÜñüÔèüÔèüÜñüÜêüÔîüÜêüÔîüÜêüÔîüÔèüÜñüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÔîüÔèüÜñüÜñüÔèüÜñüÔèüÔîüÜêüÔðôÔèüÔðôÔèüÔîüÔéôÔèüÔéôÔèüÔéôÔèüÔðôÌæùÔéôÔèüÔéôÔéôÔéôÔðôÌæùÔéôÌæùÔéôÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔèüÔâùÌâüÔèüÔèü¬¸Ô\g€L[tL[tL\|T]|L\|L[tL\|L[tDNtL\|T]|T]|\^|T]|TVuTVu\^|dnŒTVuLSjLNiDHaDHaDHa„‹”krTVu|€œ„…–TVudfxtw„¤ª±„‹”|tr„…–Œ„Œ„…–¤ž¤ÄÆÔ´·Å¤¤®¤¤®ÄÌ×ÜâïÜêüÜñüÜêüÔèüÔîüÜñüÔîüÜñüÔîüÜêüÜñüÔîüÜêüÔîüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÜñüÜñüÔîüÜñüÜñüÜñüÜñüÜñüÔîüÜòóÜñüÔðôÜñüÔðôÔîüÔîüÜñüÔèüÜñüÔéôÔîüÔîüÔðôÔîüÔéôÔîüÔéôÔîüÔéôÔéôÌæùÔðôÔéôÌæùÔéôÌæùÔéôÔéôÌæùÔèüÔâùÔèüÌâüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔâùÔèü|ެL\|L[tLUtLUtT]|L[tL[tL[tDVoL[tDVoTb|T]|\b}\b}dnŒ\VkT]|T]|\^pTVuTVuLUtLSjDHaLNi|\b}\b}”›´„…–T]|TVudr|Œ¤¤®¬°¼tw„Œ„…–œš­Œ†–trœ™¡¼½Ì„Šˆ¤¤®¼ÂÂÌÍÕÜéòÔèüÜêüÜñüÜñüÜñüÔîüÔîüÔîüÜñüÔèüÜêüÜñüÔîüÜêüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÔîüÜñüÔîüÜñüÔîüÜñüÔîüÜòóÜñüÔîüÜñüÔîüÔéôÜñüÔðôÜñüÔéôÔîüÔðôÔèüÔîüÔéôÔîüÔèüÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔéôÌæùÔðôÔéôÔéôÌæùÔèüÌæùÔèüÔèüÔèüÔèüÔèüÔèüÔèüÔâùÔèüÔâùÜêüt†¤L[tDNiL[tLUtT[rT]|L[tT]|L[tDNiL[tL\|Tb|\b}kr\b}trŽ|€œ\b}TVu\b}T[rDJlLIXLNtDHaDNP\^|\^p|œ¤¼tyŽT[r\^ptw„tw„„‹žŒ„ŒŒ†–¤¤®¬ª¼¼ºÄtw„|žž­¤ª±¬®³¤ª±´ÀÉÜéòÜêüÜêüÜêüÜñüÔîüÜñüÔèüÜñüÔèüÜñüÔîüÜêüÜñüÔèüÜñüÔîüÜñüÔîüÔîüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜòóÜñüÔðôÜñüÔîüÜñüÔèüÔîüÜñüÔèüÔðôÜêüÔîüÔéôÜñüÔéôÔîüÔèüÔðôÔéôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÌæùÔðôÌæùÔâùÔèüÌâüÔèüÔèüÌæùÔèüÔèüÔèüÔâùÔâùÔèü|ެDVoL[tL[tLUtLUtT[rLUtT]|T[rLUtLUtT]|T]|T]|\g€T]|dbutzœ„…¤|zœT[r\b}\^pTVuLUtLSjTUiLUtLSjTUi|€œœ¤¼lr\b}\^|tw„TZ]\^plm~„Œ”›¨´±Æ¼ºÄ¬ª´dfxtw„ŒŒ”Œ”›¨œ™¡œ£°ÄÌ×ÜêüÜêüÜêüÜêüÔîüÔðôÜñüÜêüÜñüÔîüÜêüÜñüÔèüÜñüÔèüÜñüÔîüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÔîüÔèüÜòóÔîüÔðôÔîüÔéôÜñüÔîüÔèüÔðôÔèüÔðôÔèüÔîüÔðôÔèüÔéôÔîüÔéôÔðôÌæùÔéôÔðôÔéôÔéôÔéôÔéôÔèüÔèüÔâùÔèüÌæùÔèüÔèüÔâùÜêüÌÝ÷ÌÝ÷ly”LUtLUtDVoDNiLUtLUtT]|LUtLSjLUtLUtT]|LZdT]|T]|dbukrlmŒlmŒ|€œlmŒ\^|LNiLSjTVuLUt\^pLNiLSjTUiT]|dfxT[rT[rlm~T]|\br\b}dfxdfxdfx¤ž¤¤¤®žž­Œlm~dfxtw„|ŒŒ”Œ¤¤®¤¤®ÌÛìÜéòÔéôÜñüÜñüÜñüÔèüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÜñüÔðôÜñüÔîüÜñüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÜñüÔðôÜñüÜñüÜòóÜñüÔîüÜñüÜñüÔîüÜñüÔîüÔéôÜñüÔèüÜñüÔèüÔîüÜòóÔèüÔîüÔîüÔéôÔèüÔéôÔîüÔéôÔéôÔéôÌæùÔéôÌæùÌâüÔèüÌæùÔâùÔèüÔâùÌæùÔèüÔâùÌÝ÷dv”DNiL[tDNiDVoL[tT]|T]|LNiLSjLNtT]|\b}tyŽ\^|\br\b}dnŒdfxdjdfx\b}TUiLSjT[r\b}\^|LSjTUi\g€TVuDNPLNiLNiLSjdj|lm~T[rlm~„ŒŒ„Œ„…–¬ª´žž­|zŽ„…–„Œ||zŽ”Žž””š¤ª¿”›¨¼ÌÙÜâïÜêüÜéòÜñüÔîüÜñüÜñüÜñüÜñüÜêüÜñüÜñüÔîüÜñüÜñüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÜñüÜñüÜñüÜñüÜñüÔîüÜñüÔîüÜñüÜòóÔîüÔîüÔîüÜñüÔîüÔðôÔîüÔèüÜòóÔîüÔîüÔèüÜòóÔîüÔîüÔðôÔîüÔéôÔîüÔîüÔéôÔðôÔéôÔéôÔèüÔèüÔèüÔèüÌæùÔèüÔèüÔâùÔâù|€œDVoL[tL\|LUtL[tT]|T]|LUtDNiLUtLUt\^|lmŒ|€œkrTUidfx„…–T]|\^pT[rLSj\^p\g€dfxttyŽLUtTVudjDHaDNiLUtDNi\^pt|ˆœT[rTUityŽŒ¬”•¦žž­œ™¡¬®³ŒŒ”tw„|€œ„…–Œ|zŽ”•¦œ™¡´±Æ”•¦¤°ºÌÖæÜéòÜêüÜñüÔéôÜñüÔîüÜñüÔèüÜñüÜñüÜêüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜñüÜñüÔîüÜñüÜñüÔðôÜñüÜñüÜñüÔðôÜñüÔðôÜñüÜñüÜñüÔðôÜñüÔîüÜñüÔîüÜñüÔîüÔîüÜêüÔîüÜñüÔðôÔèüÔîüÜñüÔèüÔîüÔèüÜñüÔèüÔéôÔîüÔéôÔèüÔðôÔèüÔéôÔéôÔâùÔèüÌâüÔèüÔâùÔèüÔâùÔèü|ެDNiLUtLUtLUtL[tT]|T]|L[tDNtLNiDJlLUtdfxŒ¬¤ª¿žž­\^p|€œlm~TUiTUiLSjDHaLSj\g€lvŠT]|T[r\g€LSjDNiLUtLUtDNiLNiDNityŽT]|LNiTNdtzœœš­„…–„…–Œ¼ºÄ””š„Œ„‹”„…–|y„trŒžž­´±¼¬°¼”›¨¬°¼ÌÜâÜéòÜéòÜñüÜñüÜñüÜñüÜñüÜñüÔîüÜñüÔîüÜñüÔîüÜñüÔðôÜñüÜñüÔîüÜñüÔðôÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜòóÜñüÔîüÜòóÔîüÔîüÜòóÔîüÔéôÔîüÔèüÜñüÔèüÔðôÔèüÜòóÔîüÔðôÔîüÔéôÔîüÔéôÔîüÔéôÔèüÔðôÔèüÔèüÌæùÔâùÔèüÔâùÔâùÔèüly”LUtLUtLSjLUtT]|\b}Tb|LUtLNiDNtDNiLSjTVudjœ¤¼´·ÅŒ¬djlm~\b}LSjLNiTUiLSjT[rdbudfxDHaLUtLNiDNiLUt\b}DHaTVuLNiTVudjLSjLSjT]||€œ|zŽ\^pdbudfx”•¦”ŽžŒŒ”Œœš­\^pdbuŒ†–žž­ÄÆÔ¼ºÄ¤¤®¤¤®¤°ºÌÔÙÜòóÜòóÜñüÜêüÜêüÜñüÜêüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜñüÜñüÜñüÔðôÜñüÜñüÜòóÔîüÜñüÜòóÔîüÜñüÔîüÜòóÔîüÔðôÜñüÔîüÜòóÔèüÔðôÜêüÔðôÔèüÔðôÔéôÔîüÔéôÔðôÔéôÔéôÔâùÔèüÔèüÔèüÔâùÔèül~œTb|LUtLUtLUtTVuT]|T]|TVuDNiDJlLUtLUtDNi\brly”œ¤¼¬¸Ç\fp\b}\b}T[rT[rLSjLSj\^pdr|\^pdnŒLIXDNiLNtDNiT[rlm~\b}|€œ\^|TVudjTVuLSjdbuŒ¬\^|\^p|zŽ|zŽœ™¡Œ”Žž”•¦žž­\Vk\Vklm~žž­´±¼¼ºÄ¼¾Â¬ª´¤°º´·ÅÌÜâäòüÜéòÜñüÜêüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜñüÜñüÔðôÜñüÜñüÔðôÜñüÜñüÔðôÜòóÔðôÔðôÜñüÔðôÜòóÔîüÜòóÔèüÔðôÔéôÔðôÔéôÔðôÔéôÔðôÔéôÔéôÔéôÔèüÔâùÔèüÔâùÔâù„¬LUtT]|T[rLUtLUtL\|T]|TVuDNtDNtTVuLNtDHaLNiLUtlvŠ´·ÅlvŠ\^pdfx\^|LSjTUiTUiLUtdfx\brlm~\b}DJlLUtLSjDNiLUtT[rtw„¤¦½tw„T]|lm~LNiTVukr„…–LNi\^ptw„Œ†–¤¤®Œœš­Œ†–trtr\^p\^p„‹ž¬°¼´±¼´¶ºÄ¾Ìœ¢¡¬¸ÇÄÌ×ÜéòÜéòÜñüÜêüÜñüÜêüÜñüÜñüÜñüÜñüÜ÷üÜñüÜ÷üÔðôÜ÷üÜñüÜñüÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜ÷üÜñüÜñüÜ÷üÜòóÜ÷üÔðôÜñüÜñüÜòóÔðôÜñüÜòóÔèüÜòóÔèüÔîüÜòóÔéôÔîüÔðôÜòóÔéôÔðôÜéòÔîüÔðôÔéôÔðôÔéôÔéôÔðôÔéôÔéôÔéôÔéôÔâùÜêüŒš´T[rT[rLUtT]|LUtLUtT]|LUtLUtDNiDNtTVuT]|LSjLUtDNiT[r|€œ\b}T]|T[rLNi\\\\\DB\\\DJlDHa\DB\\,2<45<,.<434,,3434DGLTSLLIX45<,.<45< - -Options: - --exec= - --md5exec= - --loop= - --nocheck - --mt - --noalpha - --lossless - --extra_args= -EOT - exit 1 -} - -run() { - # simple means for a batch speed test - ${executable} $file -} - -check() { - # test the optimized vs. unoptimized versions. this is a bit - # fragile, but good enough for optimization testing. - md5=$({ ${executable} -o - $file || echo "fail1"; } | ${md5exec}) - md5_noasm=$( { ${executable} -noasm -o - $file || echo "fail2"; } | ${md5exec}) - - printf "$file:\t" - if [ "$md5" = "$md5_noasm" ]; then - printf "OK\n" - else - printf "FAILED\n" - exit 1 - fi -} - -check="true" -noalpha="" -lossless="" -mt="" -md5exec="md5sum" -extra_args="" - -n=1 -for opt; do - optval=${opt#*=} - case ${opt} in - --exec=*) executable="${optval}";; - --md5exec=*) md5exec="${optval}";; - --loop=*) n="${optval}";; - --mt) mt="-mt";; - --lossless) lossless="-lossless";; - --noalpha) noalpha="-noalpha";; - --nocheck) check="";; - --extra_args=*) extra_args="${optval}";; - -*) usage;; - *) break;; - esac - shift -done - -[ $# -gt 0 ] || usage -[ "$n" -gt 0 ] || usage - -executable=${executable:-cwebp} -${executable} 2>/dev/null | grep -q Usage || usage -executable="${executable} -quiet ${mt} ${lossless} ${noalpha} ${extra_args}" -set +e - -if [ "$check" = "true" ]; then - TEST=check -else - TEST=run -fi - -N=$n -while [ $n -gt 0 ]; do - for file; do - $TEST - done - n=$((n - 1)) - printf "DONE (%d of %d)\n" $(($N - $n)) $N -done diff --git a/tests/Images/Input/WebP/test_dwebp.sh b/tests/Images/Input/WebP/test_dwebp.sh deleted file mode 100644 index 245d1da26..000000000 --- a/tests/Images/Input/WebP/test_dwebp.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/sh -## -## test_dwebp.sh -## -## Author: John Koleszar -## -## Simple test driver for validating (via md5 sum) the output of the libwebp -## dwebp example utility. -## -## This file distributed under the same terms as libwebp. See the libwebp -## COPYING file for more information. -## - -self=$0 - -usage() { - cat < (must support '-c') - --mt - --extra_args= - --formats=format_list (default: $formats) - --dump-md5s -EOT - exit 1 -} - -# Decode $1 and verify against md5s. -check() { - local f="$1" - shift - # Decode the file to the requested formats. - for fmt in $formats; do - eval ${executable} ${mt} -${fmt} ${extra_args} "$@" \ - -o "${f}.${fmt}" "$f" ${devnull} - done - - if [ "$dump_md5s" = "true" ]; then - for fmt in $formats; do - (cd $(dirname $f); ${md5exec} "${f##*/}.${fmt}") - done - else - for fmt in $formats; do - # Check the md5sums - grep ${f##*/}.${fmt} "$tests" | (cd $(dirname $f); ${md5exec} -c -) \ - || exit 1 - done - fi - - # Clean up. - for fmt in $formats; do - rm -f "${f}.${fmt}" - done -} - -# PPM (RGB), PAM (RGBA), PGM (YUV), BMP (BGRA/BGR), TIFF (rgbA/RGB) -formats="bmp pam pgm ppm tiff" -mt="" -md5exec="md5sum" -devnull="> /dev/null 2>&1" -dump_md5s="false" -for opt; do - optval=${opt#*=} - case ${opt} in - --exec=*) executable="${optval}";; - --md5exec=*) md5exec="${optval}";; - --formats=*) formats="${optval}";; - --dump-md5s) dump_md5s="true";; - --mt) mt="-mt";; - --extra_args=*) extra_args="${optval}";; - -v) devnull="";; - -*) usage;; - *) [ -z "$tests" ] || usage; tests="$opt";; - esac -done - -# Validate test file -if [ -z "$tests" ]; then - [ -f "$(dirname $self)/libwebp_tests.md5" ] && tests="$(dirname $self)/libwebp_tests.md5" -fi -[ -f "$tests" ] || usage - -# Validate test executable -executable=${executable:-dwebp} -${executable} 2>/dev/null | grep -q Usage || usage - -test_dir=$(dirname ${tests}) -for f in $(grep -o '[[:alnum:]_-]*\.webp' "$tests" | uniq); do - f="${test_dir}/${f}" - check "$f" - - if [ "$dump_md5s" = "false" ]; then - # Decode again, without optimization this time - check "$f" -noasm - fi -done - -echo "DONE" diff --git a/tests/Images/Input/WebP/test_lossless.sh b/tests/Images/Input/WebP/test_lossless.sh deleted file mode 100644 index 347ccbd54..000000000 --- a/tests/Images/Input/WebP/test_lossless.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/sh -## -## test_lossless.sh -## -## Simple test to validate decoding of lossless test vectors using -## the dwebp example utility. -## -## This file distributed under the same terms as libwebp. See the libwebp -## COPYING file for more information. -## -set -e - -self=$0 -usage() { - cat < - --formats=format_list (default: $formats) -EOT - exit 1 -} - -# Decode $1 as a pam and compare to $2. Additional parameters are passed to the -# executable. -check() { - local infile="$1" - local reffile="$2" - local outfile="$infile.${reffile##*.}" - shift 2 - printf "${outfile##*/}: " - eval ${executable} "$infile" $extra_args -o "$outfile" "$@" ${devnull} - cmp "$outfile" "$reffile" - echo "OK" - - rm -f "$outfile" -} - -# PPM (RGB), PAM (RGBA), PGM (YUV), BMP (BGRA/BGR), TIFF (rgbA/RGB) -formats="ppm pam pgm bmp tiff" -devnull="> /dev/null 2>&1" -for opt; do - optval=${opt#*=} - case ${opt} in - --exec=*) executable="${optval}";; - --extra_args=*) extra_args="${optval}";; - --formats=*) formats="${optval}";; - -v) devnull="";; - *) usage;; - esac -done -test_file_dir=$(dirname $self) - -executable=${executable:-dwebp} -${executable} 2>/dev/null | grep -q Usage || usage - -vectors="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" -for i in $vectors; do - for fmt in $formats; do - file="$test_file_dir/lossless_vec_1_$i.webp" - check "$file" "$test_file_dir/grid.$fmt" -$fmt - check "$file" "$test_file_dir/grid.$fmt" -$fmt -noasm - done -done - -for i in $vectors; do - for fmt in $formats; do - file="$test_file_dir/lossless_vec_2_$i.webp" - check "$file" "$test_file_dir/peak.$fmt" -$fmt - check "$file" "$test_file_dir/peak.$fmt" -$fmt -noasm - done -done - -for fmt in $formats; do - file="$test_file_dir/lossless_color_transform.webp" - check "$file" "$test_file_dir/lossless_color_transform.$fmt" -$fmt - check "$file" "$test_file_dir/lossless_color_transform.$fmt" -$fmt -noasm -done - -echo "ALL TESTS OK" From 9fd131d16007879b345a908ad3c65e77c366deaa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 9 Jan 2020 16:18:33 +0100 Subject: [PATCH 066/359] Fix bug in PredictorInverseTransform --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 60 +++++++++---------- .../Formats/WebP/WebPDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/WebP/bike_lossless.webp | 3 + 4 files changed, 35 insertions(+), 30 deletions(-) create mode 100644 tests/Images/Input/WebP/bike_lossless.webp diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 1bd8ec2c0..e047f18d3 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // decrementing a counter. if ((x & countMask) is 0) { - packedPixels = GetARGBIndex(pixelData[pixelDataPos++]); + packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } decodedPixelData[decodedPixels++] = colorMap[packedPixels & bitMask]; @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int x = 0; x < width; ++x) { - uint colorMapIndex = GetARGBIndex(pixelData[decodedPixels]); + uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); pixelData[decodedPixels] = colorMap[colorMapIndex]; decodedPixels++; } @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } ++y; - if ((y & mask) == 0) + if ((y & mask) is 0) { predRowIdxStart += tilesPerRow; } @@ -501,35 +501,37 @@ namespace SixLabors.ImageSharp.Formats.WebP private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { - int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); - int r = AddSubtractComponentFull((c0 >> 16) & 0xff, - (c1 >> 16) & 0xff, - (c2 >> 16) & 0xff); - int g = AddSubtractComponentFull((c0 >> 8) & 0xff, - (c1 >> 8) & 0xff, - (c2 >> 8) & 0xff); - int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); + int a = AddSubtractComponentFull((int)(c0 >> 24), (int)(c1 >> 24), (int)(c2 >> 24)); + int r = AddSubtractComponentFull( + (int)((c0 >> 16) & 0xff), + (int)((c1 >> 16) & 0xff), + (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentFull( + (int)((c0 >> 8) & 0xff), + (int)((c1 >> 8) & 0xff), + (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); } private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) { uint ave = Average2(c0, c1); - int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24); - int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); - int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); - int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); + int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); + int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); } - private static int AddSubtractComponentHalf(uint a, uint b) + private static int AddSubtractComponentHalf(int a, int b) { - return (int)Clip255(a + ((a - b) / 2)); + return (int)Clip255((uint)(a + ((a - b) / 2))); } - private static int AddSubtractComponentFull(uint a, uint b, uint c) + private static int AddSubtractComponentFull(int a, int b, int c) { - return (int)Clip255(a + b - c); + return (int)Clip255((uint)(a + b - c)); } private static uint Clip255(uint a) @@ -539,26 +541,24 @@ namespace SixLabors.ImageSharp.Formats.WebP return a; } - // return 0, when a is a negative integer. - // return 255, when a is positive. return ~a >> 24; } private static uint Select(uint a, uint b, uint c) { int paMinusPb = - Sub3(a >> 24, b >> 24, c >> 24) + - Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + - Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + - Sub3( a & 0xff, b & 0xff, c & 0xff); + Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) + + Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + + Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + + Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); return (paMinusPb <= 0) ? a : b; } - private static int Sub3(uint a, uint b, uint c) + private static int Sub3(int a, int b, int c) { - uint pb = b - c; - uint pa = a - c; - return (int)(Math.Abs(pb) - Math.Abs(pa)); + int pb = b - c; + int pa = a - c; + return Math.Abs(pb) - Math.Abs(pa); } private static uint Average2(uint a0, uint a1) @@ -605,7 +605,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } - private static uint GetARGBIndex(uint idx) + private static uint GetArgbIndex(uint idx) { return (idx >> 8) & 0xff; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 825fdcae0..d31343ef3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -162,6 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms8, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 9d58ef3be..aa057c6b8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -416,6 +416,7 @@ namespace SixLabors.ImageSharp.Tests public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; // color_indexing, predictor, cross_color public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; // substract_green, predictor, cross_color public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; // color_indexing, predictor, cross_color + public const string ThreeTransforms8 = "Webp/bike_lossless.webp"; // substract_green, predictor, cross_color // Invalid / corrupted images // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." diff --git a/tests/Images/Input/WebP/bike_lossless.webp b/tests/Images/Input/WebP/bike_lossless.webp new file mode 100644 index 000000000..a311c5af1 --- /dev/null +++ b/tests/Images/Input/WebP/bike_lossless.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a552b43d45c77ece0ab4331f054fb183725420748656d47a49c5b672e42f4f9 +size 61782 From 0d1d657bf832197dc5868e0aa914aa5bc2a25943 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 10 Jan 2020 16:46:09 +0100 Subject: [PATCH 067/359] Fix warnings, add additional comments --- src/ImageSharp/Formats/WebP/ColorCache.cs | 11 ++-- src/ImageSharp/Formats/WebP/HTreeGroup.cs | 24 +++---- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 7 +- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 66 +++++++++---------- src/ImageSharp/Formats/WebP/Vp8LTransform.cs | 2 +- .../Formats/WebP/WebPDecoderCore.cs | 26 ++++++-- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 10 +-- .../Formats/WebP/WebPLosslessDecoder.cs | 32 ++++++--- .../Formats/WebP/WebPLossyDecoder.cs | 3 + src/ImageSharp/Formats/WebP/WebPMetadata.cs | 4 +- 10 files changed, 109 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index e9b4bc748..1fc47180f 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -5,20 +5,23 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal class ColorCache { + private const uint KHashMul = 0x1e35a7bdu; + ///

- /// Color entries. + /// Gets the color entries. /// public uint[] Colors { get; private set; } /// - /// Hash shift: 32 - hashBits. + /// Gets the hash shift: 32 - hashBits. /// public int HashShift { get; private set; } + /// + /// Gets the hash bits. + /// public int HashBits { get; private set; } - private const uint KHashMul = 0x1e35a7bdu; - public void Init(int hashBits) { int hashSize = 1 << hashBits; diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs index 99d26844c..c311601bb 100644 --- a/src/ImageSharp/Formats/WebP/HTreeGroup.cs +++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs @@ -8,11 +8,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Huffman table group. /// Includes special handling for the following cases: - /// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN) - /// - is_trivial_code: only 1 code (no bit is read from bitstream) - /// - use_packed_table: few enough literal symbols, so all the bit codes - /// can fit into a small look-up table packed_table[] - /// The common literal base, if applicable, is stored in 'literal_arb'. + /// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN) + /// - IsTrivialCode: only 1 code (no bit is read from the bitstream) + /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] + /// The common literal base, if applicable, is stored in 'LiteralArb'. /// internal class HTreeGroup { @@ -27,32 +26,33 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// This has a maximum of HuffmanCodesPerMetaCode (5) entry's. + /// Gets the Huffman trees. This has a maximum of HuffmanCodesPerMetaCode (5) entry's. /// - public List HTrees { get; private set; } + public List HTrees { get; } /// - /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code). + /// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code). /// public bool IsTrivialLiteral { get; set; } /// - /// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero. + /// Gets or sets a the literal argb value of the pixel. + /// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero. /// public uint LiteralArb { get; set; } /// - /// True if is_trivial_literal with only one code. + /// Gets or sets a value indicating whether there is only one code. /// public bool IsTrivialCode { get; set; } /// - /// use packed table below for short literal code + /// Gets or sets a value indicating whether to use packed table below for short literal code. /// public bool UsePackedTable { get; set; } /// - /// Table mapping input bits to a packed values, or escape case to literal code. + /// Gets or sets table mapping input bits to packed values, or escape case to literal code. /// public HuffmanCode[] PackedTable { get; set; } } diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index e047f18d3..f2a754a92 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.WebP newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; - pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; } } @@ -511,7 +511,7 @@ namespace SixLabors.ImageSharp.Formats.WebP (int)((c1 >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); - return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; } private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) @@ -521,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); - return (uint)(((uint)a << 24) | (r << 16) | (g << 8) | b); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; } private static int AddSubtractComponentHalf(int a, int b) @@ -576,7 +576,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return Average2(Average2(a0, a1), Average2(a2, a3)); } - /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 0bb69311f..180f3cb5b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -13,17 +13,17 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Maximum number of bits (inclusive) the bit-reader can handle. /// - private const int VP8L_MAX_NUM_BIT_READ = 24; + private const int Vp8LMaxNumBitRead = 24; /// /// Number of bits prefetched (= bit-size of vp8l_val_t). /// - private const int VP8L_LBITS = 64; + private const int Vp8LLbits = 64; /// /// Minimum number of bytes ready after VP8LFillBitWindow. /// - private const int VP8L_WBITS = 32; + private const int Vp8LWbits = 32; private readonly uint[] kBitMask = { @@ -38,6 +38,31 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly byte[] data; + /// + /// Pre-fetched bits. + /// + private ulong value; + + /// + /// Buffer length. + /// + private readonly long len; + + /// + /// Byte position in buffer. + /// + private long pos; + + /// + /// Current bit-reading position in value. + /// + private int bitPos; + + /// + /// True if a bit was read past the end of buffer. + /// + private bool eos; + /// /// Initializes a new instance of the class. /// @@ -72,31 +97,6 @@ namespace SixLabors.ImageSharp.Formats.WebP this.pos = length; } - /// - /// Pre-fetched bits. - /// - private ulong value; - - /// - /// Buffer length. - /// - private readonly long len; - - /// - /// Byte position in buffer. - /// - private long pos; - - /// - /// Current bit-reading position in value. - /// - private int bitPos; - - /// - /// True if a bit was read past the end of buffer. - /// - private bool eos; - /// /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. /// @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - if (!this.eos && nBits <= VP8L_MAX_NUM_BIT_READ) + if (!this.eos && nBits <= Vp8LMaxNumBitRead) { ulong val = this.PrefetchBits() & this.kBitMask[nBits]; int newBits = this.bitPos + nBits; @@ -134,12 +134,12 @@ namespace SixLabors.ImageSharp.Formats.WebP public ulong PrefetchBits() { - return this.value >> (this.bitPos & (VP8L_LBITS - 1)); + return this.value >> (this.bitPos & (Vp8LLbits - 1)); } public void FillBitWindow() { - if (this.bitPos >= VP8L_WBITS) + if (this.bitPos >= Vp8LWbits) { this.DoFillBitWindow(); } @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool IsEndOfStream() { - return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); + return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); } private void ShiftBytes() @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; - this.value |= (ulong)this.data[this.pos] << (VP8L_LBITS - 8); + this.value |= (ulong)this.data[this.pos] << (Vp8LLbits - 8); ++this.pos; this.bitPos -= 8; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs index 580c03dc7..78874554a 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8LTransformType TransformType { get; } /// - /// Subsampling bits defining transform window. + /// Gets or sets the subsampling bits defining the transform window. /// public int Bits { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 5d6103543..0e5e79994 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -64,6 +64,12 @@ namespace SixLabors.ImageSharp.Formats.WebP this.options = options; } + /// + /// Decodes the image from the specified and sets the data to the image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. public Image Decode(Stream stream) where TPixel : struct, IPixel { @@ -80,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader, (int)imageInfo.ImageDataSize); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader); losslessDecoder.Decode(pixels, image.Width, image.Height); } else @@ -114,6 +120,10 @@ namespace SixLabors.ImageSharp.Formats.WebP return new ImageInfo(new PixelTypeInfo(bitsPerPixel), imageInfo.Width, imageInfo.Height, this.metadata); } + /// + /// Reads and skips over the image header. + /// + /// The chunk size in bytes. private uint ReadImageHeader() { // Skip FourCC header, we already know its a RIFF file at this point. @@ -133,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private WebPImageInfo ReadVp8Info() { this.metadata = new ImageMetadata(); - this.webpMetadata = metadata.GetFormatMetadata(WebPFormat.Instance); + this.webpMetadata = this.metadata.GetFormatMetadata(WebPFormat.Instance); WebPChunkType chunkType = this.ReadChunkType(); @@ -328,9 +338,13 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + /// + /// Parses optional metadata chunks. + /// + /// The webp features. private void ParseOptionalChunks(WebPFeatures features) { - if (features.ExifProfile == false && features.XmpMetaData == false) + if (features.ExifProfile is false && features.XmpMetaData is false) { return; } @@ -354,7 +368,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private WebPChunkType ReadChunkType() { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) + if (this.currentStream.Read(this.buffer, 0, 4) is 4) { var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); this.webpMetadata.ChunkTypes.Enqueue(chunkType); @@ -371,10 +385,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The chunk size in bytes. private uint ReadChunkSize() { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) + if (this.currentStream.Read(this.buffer, 0, 4) is 4) { uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; + return (chunkSize % 2 is 0) ? chunkSize : chunkSize + 1; } throw new ImageFormatException("Invalid WebP data."); diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 653c8fa67..6ae4f1e9d 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -9,27 +9,27 @@ namespace SixLabors.ImageSharp.Formats.WebP public class WebPFeatures { /// - /// Gets or sets whether this image has a ICC Profile. + /// Gets or sets a value indicating whether this image has a ICC Profile. /// public bool IccProfile { get; set; } /// - /// Gets or sets whether this image has a alpha channel. + /// Gets or sets a value indicating whether this image has a alpha channel. /// public bool Alpha { get; set; } /// - /// Gets or sets whether this image has a EXIF Profile. + /// Gets or sets a value indicating whether this image has a EXIF Profile. /// public bool ExifProfile { get; set; } /// - /// Gets or sets whether this image has XMP Metadata. + /// Gets or sets a value indicating whether this image has XMP Metadata. /// public bool XmpMetaData { get; set; } /// - /// Gets or sets whether this image is a animation. + /// Gets or sets a value indicating whether this image is a animation. /// public bool Animation { get; set; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 8d7de7513..887ed8691 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -21,8 +21,6 @@ namespace SixLabors.ImageSharp.Formats.WebP { private readonly Vp8LBitReader bitReader; - private readonly int imageDataSize; - private static readonly int BitsSpecialMarker = 0x100; private static readonly uint PackedNonLiteralCode = 0; @@ -72,12 +70,22 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 1, 1, 0 }; - public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) + /// + /// Initializes a new instance of the class. + /// + /// Bitreader to read from the stream. + public WebPLosslessDecoder(Vp8LBitReader bitReader) { this.bitReader = bitReader; - this.imageDataSize = imageDataSize; } + /// + /// Decodes the image from the stream using the bitreader. + /// + /// The pixel format. + /// The pixel buffer to store the decoded data. + /// The width of the image. + /// The height of the image. public void Decode(Buffer2D pixels, int width, int height) where TPixel : struct, IPixel { @@ -113,7 +121,11 @@ namespace SixLabors.ImageSharp.Formats.WebP if (colorCachePresent) { colorCacheBits = (int)this.bitReader.ReadBits(4); - // TODO: error check color cache bits + bool coloCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits; + if (!coloCacheBitsIsValid) + { + WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); + } } // Read the Huffman codes (may recurse). @@ -192,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int lastCached = decodedPixels; while (decodedPixels < totalPixels) { - int code = 0; + int code; if ((col & mask) == 0) { hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); @@ -235,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { if (hTreeGroup[0].IsTrivialLiteral) { - pixelData[decodedPixels] = (uint)(hTreeGroup[0].LiteralArb | (code << 8)); + pixelData[decodedPixels] = hTreeGroup[0].LiteralArb | ((uint)code << 8); } else { @@ -306,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - // TODO: throw appropriate error msg + WebPThrowHelper.ThrowImageFormatException("Webp parsing error"); } } @@ -483,7 +495,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. - if (numSymbols == 2) + if (numSymbols is 2) { symbol = this.bitReader.ReadBits(8); codeLengths[symbol] = 1; @@ -583,6 +595,8 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Reads the transformations, if any are present. /// + /// The width of the image. + /// The height of the image. /// Vp8LDecoder where the transformations will be stored. private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index cd8f98678..906f11efd 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.IO; diff --git a/src/ImageSharp/Formats/WebP/WebPMetadata.cs b/src/ImageSharp/Formats/WebP/WebPMetadata.cs index 65b09d5ec..4788d2b0b 100644 --- a/src/ImageSharp/Formats/WebP/WebPMetadata.cs +++ b/src/ImageSharp/Formats/WebP/WebPMetadata.cs @@ -28,12 +28,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// The webp format used. Either lossless or lossy. + /// Gets or sets the webp format used. Either lossless or lossy. /// public WebPFormatType Format { get; set; } /// - /// All found chunk types ordered by appearance. + /// Gets or sets all found chunk types ordered by appearance. /// public Queue ChunkTypes { get; set; } = new Queue(); From 864aaa52a2efa8ed5657a8d21a13cfde0cdb7487 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 10 Jan 2020 18:51:56 +0100 Subject: [PATCH 068/359] Use memory allocator --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 62 +++++++++---------- src/ImageSharp/Formats/WebP/Vp8LMetadata.cs | 2 - .../Formats/WebP/WebPDecoderCore.cs | 14 ++++- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 6 +- .../Formats/WebP/WebPLosslessDecoder.cs | 38 ++++++++---- 5 files changed, 70 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index f2a754a92..4ca97b371 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -41,7 +41,6 @@ namespace SixLabors.ImageSharp.Formats.WebP int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; - // TODO: use memoryAllocator here var decodedPixelData = new uint[width * height]; int pixelDataPos = 0; for (int y = 0; y < height; ++y) @@ -167,11 +166,8 @@ namespace SixLabors.ImageSharp.Formats.WebP return newColorMap; } - public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData) + public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData, Span output) { - // TODO: use memory allocator instead - var output = new uint[pixelData.Length]; - int processedPixels = 0; int yStart = 0; int width = transform.XSize; @@ -265,10 +261,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - output.AsSpan().CopyTo(pixelData); + output.CopyTo(pixelData); } - private static void PredictorAdd0(uint[] input, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; for (int x = startIdx; x < endIdx; ++x) @@ -278,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd1(uint[] input, int startIdx, int numberOfPixels, uint[] output) + private static void PredictorAdd1(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; uint left = output[startIdx - 1]; @@ -288,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd2(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd2(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -299,7 +295,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd3(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd3(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -310,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd4(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd4(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -321,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd5(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd5(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -332,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd6(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd6(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -343,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd7(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd7(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -354,7 +350,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd8(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd8(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -365,7 +361,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd9(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd9(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -376,7 +372,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd10(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd10(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -387,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd11(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd11(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -398,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd12(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd12(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -409,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static void PredictorAdd13(uint[] input, int startIdx, int numberOfPixels, int width, uint[] output) + private static void PredictorAdd13(Span input, int startIdx, int numberOfPixels, int width, Span output) { int endIdx = startIdx + numberOfPixels; int offset = 0; @@ -425,75 +421,75 @@ namespace SixLabors.ImageSharp.Formats.WebP return WebPConstants.ArgbBlack; } - private static uint Predictor1(uint left, uint[] top) + private static uint Predictor1(uint left, Span top) { return left; } - private static uint Predictor2(uint left, uint[] top, int idx) + private static uint Predictor2(uint left, Span top, int idx) { return top[idx]; } - private static uint Predictor3(uint left, uint[] top, int idx) + private static uint Predictor3(uint left, Span top, int idx) { return top[idx + 1]; } - private static uint Predictor4(uint left, uint[] top, int idx) + private static uint Predictor4(uint left, Span top, int idx) { return top[idx - 1]; } - private static uint Predictor5(uint left, uint[] top, int idx) + private static uint Predictor5(uint left, Span top, int idx) { uint pred = Average3(left, top[idx], top[idx + 1]); return pred; } - private static uint Predictor6(uint left, uint[] top, int idx) + private static uint Predictor6(uint left, Span top, int idx) { uint pred = Average2(left, top[idx - 1]); return pred; } - private static uint Predictor7(uint left, uint[] top, int idx) + private static uint Predictor7(uint left, Span top, int idx) { uint pred = Average2(left, top[idx]); return pred; } - private static uint Predictor8(uint left, uint[] top, int idx) + private static uint Predictor8(uint left, Span top, int idx) { uint pred = Average2(top[idx - 1], top[idx]); return pred; } - private static uint Predictor9(uint left, uint[] top, int idx) + private static uint Predictor9(uint left, Span top, int idx) { uint pred = Average2(top[idx], top[idx + 1]); return pred; } - private static uint Predictor10(uint left, uint[] top, int idx) + private static uint Predictor10(uint left, Span top, int idx) { uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); return pred; } - private static uint Predictor11(uint left, uint[] top, int idx) + private static uint Predictor11(uint left, Span top, int idx) { uint pred = Select(top[idx], left, top[idx - 1]); return pred; } - private static uint Predictor12(uint left, uint[] top, int idx) + private static uint Predictor12(uint left, Span top, int idx) { uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); return pred; } - private static uint Predictor13(uint left, uint[] top, int idx) + private static uint Predictor13(uint left, Span top, int idx) { uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); return pred; diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs index edc72a822..25ecea6b8 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -9,8 +9,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public ColorCache ColorCache { get; set; } - public ColorCache SavedColorCache { get; set; } - public int HuffmanMask { get; set; } public int HuffmanSubSampleBits { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 0e5e79994..667212f90 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader, this.memoryAllocator); losslessDecoder.Decode(pixels, image.Width, image.Height); } else @@ -256,6 +256,11 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo(); } + /// + /// Reads the header of a lossy webp image. + /// + /// Webp features. + /// Information about this webp image. private WebPImageInfo ReadVp8Header(WebPFeatures features = null) { this.webpMetadata.Format = WebPFormatType.Lossy; @@ -300,6 +305,11 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + /// + /// Reads the header of lossless webp image. + /// + /// Webp image features. + /// Information about this image. private WebPImageInfo ReadVp8LHeader(WebPFeatures features = null) { this.webpMetadata.Format = WebPFormatType.Lossless; @@ -319,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; - // The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. + // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. bool alphaIsUsed = bitReader.ReadBit(); // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 35e27f721..cf692289a 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -30,7 +30,9 @@ namespace SixLabors.ImageSharp.Formats.WebP ///

+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⪚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 diff --git a/tests/Images/Input/WebP/lossless_color_transform.pgm b/tests/Images/Input/WebP/lossless_color_transform.pgm deleted file mode 100644 index 663f633ba..000000000 --- a/tests/Images/Input/WebP/lossless_color_transform.pgm +++ /dev/null @@ -1,4 +0,0 @@ -P5 -512 768 -255 -vvvuuuuttttssssrrrrrqqqppppoooooonnnmmmmlllllkkjjjjiiiiiiihhhggggffffeeeeeddcccbbbbbbaaaa````____^^^^]]]]\\\\\[\[[ZZZZYYYXXXXWWWWWVVVVUUUUTTTTTSSSSSRQRQQPPPPPPPOONNNNNNMMLLLLKKKKKJJJJJJIIHHHHHGGGFFFFFEEEEDDDDDCCCBBBBAA@@A@@@????>>>>>====<<<<;;::::::9999888877776666554454444333222221110100///.....----,,,,+++++*+*)*))))((('''&&&%%%%%%$%$$######""""!!!! wvvvuuuutttttsssssrrqqqqpppoooooooonnnmmmmllllkkjkjijiiiiiihhhgggffffeeededddccccbbbbbbaaa```_`___^^]^]]]\\\\\\\[[[Z[ZZYYYYXXXXXWWWVVVVUUUUTTTTTTSRSRQQQQQPPPPPPPOOOOONMMMMLLLLLKKKKJJJJIIIIHHHHGHGGFFFFFEEEEDDDDCCCCCBBBAAA@@A@@@???>>>>>=====<<<;;;;::::9988888887777666555455443322222221111000/////....----,,,++++++***)))())(('''&&&&%%%%%%$$$######"""!!!! wvvvvuuuuuuutsstsrrrrrqqqqppppoooonnnnnmmmmlllkkkjkjjjiiiiiihhhhgggfffefeeeddddccccbbbbaaaaa``_____^^^^]]]]]\\\[\[[[[[ZZYYYYXXXWWWWWVVVVUVVUTTTTTSSSRRRRRQQQPPPPPOOOOONNNMMMMLLLLKKKKKJJJIIIIIIHHHHGGGFFFEEEEDDDDDCCCBBBBAAAAA@@@@???>>>>>>===<<<<<;;;::::999988888877766666555544343332222211110000////./.-----,,+,+++++***)*)))(((''&&'&&%%%%%%$$$$$###""""!!! vwvvvvuuuuutttttsssrrrrqqqpppppoooonnnnmmmmmllllljkjjjjiiiiiihhhgggfgfeffeededddccccbbbbaaaa`a```____^^]]]]]\\\\\[[[[[ZYYZYYXYXXXXWWVVVVVVVUUUTTTTSSSRRRRRQQQQPPPPOOOONNNMMMMLMLLLLKKJJJJJJIIIHIHHHGGGGFFFFFEEDDDDDDDCCCBBBAAAA@@@@@??>?>>>=====<<<;;;;:::::9999888877776666555554433332322221111000/////...----,,,,,++++*****))))(((('''&&&&%%%%%$$$#$###"""""!!! wwvvvvvuuuuuttttsssrrrrrqqqpppppoooonnnnmmmlmllkklkkjjjiiiiiiihhhgggfgffefeedddddcccbbbbbbaaaa`````__^^^]]]]]\\\\\\[[[ZZZYYYYYXXXXXWWVVVVVUUUTTUTTSTSSSSRRRQQQQPPPPOOONONNNMMMMMLLLKKKKJJJJJIIIIHHHHHGGFFFFEFEEEDDDDDCCCBBBBBAAAA@@@@???>>>>>>>==<<<;;;;;:::::999888887776665655544444333222222111000//////..-.---,,,+++++****)*))((((('''&&&&&%%%$%%$$$###"""!"!!!! wwwwvvvuuuuuuuttssssrrrrqqqqqpppoooooonnnmmmmlllkkkkkjjjjiiiiiiihhgggfffffefeeddddccbbbbbbbaaa````____^^^^]]]]\\\\\[[[[[ZZZYZYYXXXXWWWVVVVVVUUUTTTTTTSSSRRQQQQQQPPPPOOONONNNNMMMLLLLKKKKJJJJIJIIIHHHHGGGGGFFEEEDDDDDDDCCCBBBBAAAA@@@@@????>>>>>===<<<<<;::::::99998888777766666555544443332222211100000///....-----,,,,++++*+***))))(('('''&&&&%%%%%$%$$$####"""!!! ! xwwwwvvvvuuuuutttsssssrrrrrqqqqpopooooonnnmmmmmlllllkjkjjiiiiiihhhhgggfffffeedeedddcccbbbbbbaaa`a````__^^^^]]]]]\\\\[[[[ZZZZYYYYXXXXWWWWVVVVVVUUTUTSTTSSRRRRRQQQQPPPPPOOOONNMNMMMLLLLKKKJJJJJJIIIIIHHHGHGFFFFEEEEEDDDDCCCCCBBBAAA@@@@@????>>>>>=>==<<<<;;;;::::99988888877776666555444433332222221110110///.....---,,,,++++++*+*)))()(((('''&&&&%%%%%%$$$$##"#"""!!!! xxwwwwvvvvvuuuututtssssrrrqqqqqqoppoooooonnnmmmmlllllkkjjjjiiiiiihhhgggggfefeeeedddcccccbbbbaaaa````_____^^]^]]]\\\\\[[[ZZZZZYYYYXXXXWWWWVVVVUVUUTTTSTSSSSRRRQRQQPPPPPOOOONNNNMMMLLLLLKKKKKJJJJIIIIHHHGGHGFFFFFEEEEDDDDDCCCBBBBBAAA@@@@????>>>>>===<<<<<;;;;::::999988888777766665555543433322222211010000////....---,,,,+++++*****)())(('(''&&&&%%%%%$$$$$##"#""!"!! yxwwwwvvvvvvuuuutttttssrrrrqqqpqppppooooonnnmmmmlllllkkjjjjjjiiiiiihhghgggffeeeeeddddcccbbbbbbaaaa````___^^^^^]]]\\\\\\[[[[ZZZZYYYXXXWWWWWVVVVUUUUTUTTTSSSSRRRQQQQQPPPPOOOONNNNMMMMMLLLLKKKKJJJJJIIIHHHHHGGGFFFFEEEEDDDDDDCCCBBBAAAAAA@@@???>>>>>>==<=<<<<;;::::999988888887766665555454444332222221110000///./...-----,,,+++++****)))()((('''&'&&%%%%%%$$$#####"""!!!!! yxxwwwwvwvvvuuuuuuttssssssrrrqqqqpppooooooonnnmmmlmlklkkkkjjijiiiihhhhhgggggffeeeedddddccbbbbbaaaaaa`_`____^^^^]]]\\\\\[[[[[ZZYZYYYXXXXWWWWVVVVUUUUUTTTSTSSSRRRQQQPPPPPPPOOOONNNNMMMLLLLKKKKJJJJJIIIHHHHGHGFGFFFFEEEEDDDDDCCCCBBBBAAA@@@@@???>>>>>====<<<;<;;;:::::99988887777776665554444433332222121110000//./...----,,,,+++++*****)))(((''''&&&&&%%%%$%$$$####"""!!!!  yxxxxwwwvwvvvvuuuuuttsssssrrrqrqqppppoooooonnnnnmmlllllkkkjjjjiiiiihhhhhgggffffeedddddddcccbbbbbaaa```_``___^^^]]]]\\\\\\\[[Z[ZZYYYYXXXWXWWWVVVVVUUUUTTTTSSSRRRRQQQQQPPPPOOOONNNNMMMMMLLKLKKKJJJJJIJIHHHHHHGFGFFFFEEEEDDDDDCCCBBCBAAA@@@@@????>>>>>====<<<<<<:;;:9:99988888877777666555554443332222221111000////....----,,,,,+++++****)))((((''''&&&&%%%%%$$$$####""""!!! yxxxxxxwwwwwvvuuuuutttsssssrrrrrqqqqpopoooonnnnnmmmllllklkkjjjjiiiiihhhhggggfgffeeeedddcdcccbbbbbaaaa````___^^^^]]]]]\\\\\[[[ZZZZYYYYYXXWXWWWVVVVVUUUTTTTSTTSSSRRRQQQQPPPPOOOOONNNNMMMMLLLLKKKJJJJJJIIIHHHHGGGGFFFEEEEEDDDDCCCCCBBBBAAA@A@@@???>>>>>====<<<<;;;;:::9999998888777666655545444433332222211110000////....---,,,++++++****))())((''''&'&&%%%%%%$$$$###"#"!!!! ! yyyyyxxxwwvvvvuuuuuutttsssssrrrqrqqpppppoooononnnnmmmlllklkjjjjjjiiiihhhhhgggfffefeeeddddcccbbbbbbaaaa````___^^^]]]]]]\\\\[[[[[ZZZZZYYXXXXWWWWVVVVVUUUUTTSTSSSRRRRRRQQQPPPPPOOONNNNMMMMLLLKKLKKJJJJJIIIHHHHGGGFFFFFFFEEEDDDDDCCCCBBBBBAAA@@@@??>>>>>>>===<<<<<;;;:::9999988888877766666554444443332222221111000///...----,,,,+++++++***))))(((((''&&&&&%%%%%$$$######""!"!!!  zyyyyxxxxxwwwvvvuuuuuutttsssrsrrrqqqqpppoooooonnnmmmmmllllkkkkjjjjiiiiiihhgggggfffeeeedddddccbbbbbbaaaa```_____^^^^]]]\\\\\\[[[[[ZYYYYYXXXXXWWWVVVVVUUUUUTTSSSSSRRRQRQPQPPPPPOOOONNNNMMMMLLLKKKKKJJJJIIIIIHHHGGGFFFFFFEEEEDDDDCCCBCBBBAAA@@@@@????>>>>>====<<<<;;::;:9:9998888887777665555544433322222212110000////...-.--,,,,+++++*+**)*))(((('''''&&&%%%%%$$$$$####""!!!!! zzyyyyxxxxwwwwvvvvuuuuuttttsssrrrrqqqqqpppoooooonnnmmmlmllklkkkjjjiiiiihhhhgggggffffeeeeddccccbbbbbbbaaa```_____^^]^^]]]\\\\\[[[[ZZZYYYYXXXXXWWWWVVVVVUUUUTTSSSSRRRRQQQPQPPPPPOOONNNNNNMMMLLLLKKKKJJJJIIIIHHHHHHGGFFFFEEEEDDDDDCCCBBBBBAAAA@@@????>>>>>>====<<<;;;;:::::9988888878777666655544333333222212210000000.....-----,,,++++*++***)))(((''''&&&&%%%%%%%$$$###"#"""!!! zzzyyyyyxxwwwwwvvvuuuuutttttsssrrrrrqqqqppppooooonnmmmmmlmlkkkjjjjjiiiiiihhhhggggfffeeededddcccbbbbbabaaaa`_`___^^^^^]]]]\\\\[[[[ZZZZYYYYYXXWXWWVWVVVVUUUTTTTSSSSSRRRRQQQPPPPPOOOOONNNNMMMMLKLLKKJKJJJJIIIIHHHGHHGFFFFFEEEEDDDDDCCCBBBBABAAA@@@@???>>>>>>====<<;;;;:;:::9989988887777666565555443433322222111110000////..---,,,,,+++++*+*))))((((''''&'&&%%%%%$%$$####""""""!! zzzzzyyxyxxxwwwvwvvvuuuutttttsssrrrqrqqqqppooooooonnnmmmlllllkkjjkjjjiiiiihhhhgggfffeffeeeddddccbbbbbaaaa`a````____^^^]]]]\\\\[[[[[ZZZZYYYYYXXXWWWVVVVVVUUUTTTTSSSRRRRQRQQPPPPPPOOONNNNMMMMLMLLLKKKKJJJJIIIIHHHHGGGGGFFEEEEEDDDDDDCCCBBBBAAA@@@?@????>>>>>===<<<<<<;;;:::9998988887777766666554443333322222211110000///....----,,,++++++**)*)))))(''''''&%&%%%%$$$$$###""""!!! {zzzzyyyyxxxxxwvvvvvvuuuututttssssrrrqqqqpppoooooooonnnmmmllkllkkkkjjjiiiiiihhggggffffeeeddeddcccccbbbbbaaaa```____^^^]]]]]\\\\\[[[[ZZYYYYXYYXXXWWVVVVVVVUUTTTTTTSSSSRRRQQQQPPPPOOOONNNNNMNMMMLLKKKKJJJJJJIIIHHHGHGGGGGEFFEEEEDDDDCCCCCBBBAA@@A@@????>>>>>>====<=<<;;;:;:::99988888887666665554444333322222221100000/////..-----,,,,+++++*+*))))((((('''&&&%%%%%$$$$$###"#"""!!!! {{zzzzyyyyxxxwwwwwvvvuuuuutttttssssrrrrqqpqqpopooooonnnmmmllllklkkjjjjiiiiiihhhgggggffeefeeeddddccbbbbbbbaaa`a`____^^^^^]]\]\\\\\\[[[[ZZYYYXYXXXXWWWVVVVVUUUUUTTTTTSSRRRRQQQQPPPPPOOOONNNNMMMLLLLKKKKKJJJJJIIIIHHHHGGGGGFFFEEEEDDDDCCCBBCBBAAA@@@@@@???>>>>>====<<<;;;;;::::99998888777766656555454333333222211101000//./..-----,,,,++++++*****))(((('''''&&&%%%%%%$$$#####""!!!! ! {{{zzzyyyyyxxxxxwvwvvvuuuuuutttsssssrrrrqqqqppoooooonnnnmmmlmllkkkkkjjjiiiiiiihhhggfffffeeeedddddcccbbbbbbbaaa```____^^^]]]]]\\\\\[[[[ZZZYZYYYXXXWWWWVVVVVVVVUTTTTSSSSRRRRRQQPPPPPPOOOONONMMMMLLLLKKKKJJJJJIIIIIHHHGGGGGFFFEEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>====<=<<<;;::::::99888888877666665555544433322222211110000//../...----,,,++++++****)))(((''''''&&&%%%%%$$$$$##""""!!!!!  {{{{{zzzzyyxxxxwwwvwvvvuuuuuutttttsrrrrrqqqqpppoooooonnnnmmmlllllkkkkkjjiiiiiihhhghggffffeeededdddcccbbbbbaaaaa```_`__^^^^^]]\\\\\\\[[Z[[ZZZYYXYXXXXWVWVVVVVUUTTTTTSSSSSRRRQQQPPPPPPOOONNONNNMMMLLLKKKKKJJJJJIIIIHHHHHGGGGFFFEEEDDDDDDCCBBBBAAAAA@@@@????>>>>>====<<<<;;;;:::9:998888887877766555455444333222222111000/0//...------,,+,+++++***)))))((((''&&&&&%%%%%$$$$####"""!!!! ! {{{{{zzzzyyyyxxwxwxwwvvvuuuuuuutttssssrrrrqqqpqppoooooonnnnnmmmllklkkjjjjiiiiiihhhhgggggffffededdddcccbbbbbbaa`a````___^^^^^]]]\\\\\[[[[[ZZYYYYYXXXWWWWWVVVVVVUUUTTSSSSSSRRQQQQQPPPPPPOOONNNNMNLMLLLKKKKKJJJJJIIIIHHHHHGGFFFFFEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>>=====<<;<;;:;:::9999888878777666655554433333222221111000/0////...---,,,+,+++++****))())((('''&&%%%%%%%$$$#$###""""!!!! |{{{{{{zzzyyyyyxxxwwwwvvvvuuuuutttstsssrrrrrqqqppppoooonnnnnnmlmllllkkjjjjjiiiihhhhhgggffffefeedddddccccbbbbaaa`a``______^^]^]]]]\\\\[[[[ZZZZYZYXXXXXWWWVVVVVUUUUUUTTSSSSRSQQRQQPPPPPPOOOONNNNMMMMLLLLKKJKJJJJIJIIIHHHGGGGGGFFFEEEEDDDDDCCCBBBBAAAA@@@@???>>>>>>====<<<<;;;:::::999888887777766666555444333332222211110000////..-----,,,+++++*****))(((((('''&&&&%%%%%%$$$$###"#"""!! ! ||{{{{{zzzzyzyyxxxxwwwwvvvvuuuuutttstssrrrqqqqqqpppooooonnnnnmmmmlllkkkkkjjjiiiihihhhgggfgffefeeeddddcccbbbbbbaa`a``_`___^^^^]]]]\\\\[[[[[ZZZYYYYXXXXXWWWWVVVVVUUUUTTTTSSSRSRRQQQQQPPPPPOOONNNNNMMMLLLLLKJKJJJJJIJIIIHHHGGGGFFFFEEEEDDDDDCCBBCBBBAAA@@@@@????>>>>>===<<<<;;::;:::9998888877777665555455443332222221110000///////.----,,,,,+++++***)))))((('''''&&&%%%%%$$$$###"""""!!!! ||{|{{{{{zzyyyyyyxxxwwwwvvvvvuuuttttssssrrrqrqqpqqpppoooonnnnnmmmlllkkkkkkjjjiiiiihhhhghgfffffeeededdccccbbbbbbaaa`````___^^^^]]]]\\\\\[[[[[ZZZZYYYXXXXWWWWVVVVUVUUTTTSTTSSSRRRRQQQPPPPPOOOONNNNNMMMLLLKKKKJJJJJIIIIIHHHHHGGFGFFFEEEEDDDDDCCCCBBBAAA@@@@@????>>>>>>====<<;;;;::::99988888877776666665554433333222221111000////......---,,,++++++****))))((((''''&&%%%%%%$$$$####"""!!!! }||||{{{{{zzzyyyxxxxxwwwwwvvvuuuuuuttttssssrrqrqqqpppooooonnnnmmmmlllllkjkjjjjiiiiihhhgggggffffefeedddccccbbbbbbba`aa`____^^^^^]]]]\\\\\\[[[[ZZZYYYYXXXXWWWWVVVVVUUUUTTTSSSSRRRQQQQQQPPPPOOOOONNNNMMMLLLLKKKKJJJJIJIIHHHHGGGGGFFFFEEEDDDDDDCCCBBBBBAA@@@@??????>>>>=>==<<<;;;;;:::9:9988888777767666555544433332222211111000////...-.-,-,,,,++++*****))))(((''''&&&&%%%%%%$$$$####""!"!! }|||||{{{{{zzzzyyyxxxxwwwwvvvvuuuuutttsssssrrrqrqqqqpppooooonnnnnmlmlllkkkkjjjjjiiiihhhhggggffffeeeedddcccccbbbbbaaa````_____^]]]]]]\\\\\[\[[ZZZZYYYYXXXXWWWWVVVVUUUUUUTTTSSSRRRRQQPQPPPPPPOOONNNMNMMLLLLKKKKKJJJJJJIIIHHHHGGGFFFFEEEEDDDDDCCCCBBBBAAAAA@@@????>>>>>>====<<<;;;;;::999998888887776666555544433333222211101000////....---,-,,,+++++******))(((('''&&&&&%%%%%$$#$##"""!""!!! }}|||||{{{{{zzzzyyyyxxwwxwwwvvvuuuuuttttsssrsrrqqqqqppppoooonnnnnmmmlllllkkkjjjiiiiiihhhghggggffefeedddddccccbbbbbaaaa```_`__^^^^^]]]\\\\\\[[[Z[ZZYYYYXXXXWWWWVVVVVVUUUTTTTTSSRRRRQRQQPPPPPOOOONNNNMMMMMLLLLKKJKJJJJIIIIHHHGGGGFFFFFEEEDDDDDDDCCCCBBAAAAAA@??????>>>>====<<<<<;;:::::999988888777776655555544433232222111110000//.....-----,+,+++++****)))((((('''&&&&&%%%%%%$$$##"""""!! ~}}|||||{{{{{zzzzzyxxxxwwwwvvvvvvuuuuutttsssrrrrqqqqqppppooooononmmmmlllkkkjjkjjjiiiiihhhggggfgfffeeeedddcccccbbbbaabaa````____^]^]]]]\\\\\[[[[ZZZYYZXYXXXXWWWVVVVVVUUUTTTTSSSSSSRRRQQQPPPPPPOOOONNMMNMMMLLLKKKKJJJJIIIIIHHHHGGGGFFFFEEEEDDDDDCCCCBBBAAAA@@@@@???>>>>>====<<<<<;;;;::9:9988888877776666655544343322222221110000/////...---,,,+,+++++****)))(((('(''&&&&&%%%%%$$#####""""!!!!! ~}}}|}||{{{{{{zzzzyyyyyxxwwwwwvvuvuuuututtsssssrrqrqqqppppooooonnnnmmmlllllkkkjjjiiiiiihhhghggggffeeeeeddcddcccbbbbbaaaa````___^^^^^]]]\\\\\\[[[ZZZZYYYYYXXXXWWWVVVVVVUUUTTTTSSSRRRRRQQQPPPPPOOOONNNNNNMMLLLLLKKJJJJJJIIIIHHHHHHGGFFEEEEEDDDDDCDCCCBBBAAAAA@@@????>>>>>=====<<<<;;:::::99998888877767666555544433332222221110000////.....---,,,++++++*+*)*))(((((''&&&&&&%%%%%%$$#$###""!"!!! ~}}}}||||{{{{{{zzzzyyyyxxxwxwwwvvvuuuuuuttttsssrrrqqqqpqpppoooonnnnmmmmmlllkkkkkjjjjiiiihihhghggfffefeededdcccccbbbbbaaaa```_`__^^^]]]]]]\\\\\[[[ZZZZYZYXXXXXWWWVWVVVUVUUTTTTTSSSSRRRQQQQQPPPPPOOOONNNMMMMLLLLKKKKKJJJJJIIHHHHHHGFGFFFFEEEDDDDDCCDCCBBBBAAA@@@@????>>>>>====<<<<<<;;:::9999888888777766665555444333322222111010000///....---,-,,,++++++**)*)))(((((''&&&&%%%%%%$$######""""!!!! ~}}}}}}|||{{{{{{{zzyyyyxxxxxwwwwvvvvuuuuuttsssssrrrrqqpqqpooooooonnnnmmlmllklkkkkjjijiiiihhhhgggfgffffeeeddddcccbbbbbbbaaaa````__^_^^]]]]\\\\\[[[[Z[ZZZYYYYXXXWWWWWVVVVVUUUUTTSSSSSSRRRQQQQPPPPPPOOONNNMNMMLLLLKKKKJJJJJIIIIHHHHGGFGFFFFEEEEDDDDCCCCCBBBBAAA@@@@?????>>>>====<<<<;;:;:::999999888877777665654544433323222211110000/0////...---,,,,++++******))(((((('''&&&%%%%%$$$$#####"""!!!! ~~}~}}}}|||{{{{{zzzyyyyxxxxxwwvvvvvuuuuuutttssssrrrrqqqqqpppooooonnnmmnmmlllkkkkkjjjiiiiihhhhhggffgfeeeeeddddcccbbbbbbaaa``````___^^^^]]]\\\\\\[[[[[[YZYYYYXXXXWWWVVVVVUVUTTTTTSSSSSRRQQQQQPPPPOOOOONNNNNMMMLLLKKKKJJJJJIIIHIHHHGGGFFFFEEEEEDDDDDCCCBBBBAAAAA@@@?????>>>>====<<<;<<;:::::999998888877776666545444333332222211100000//.....----,,,+++++++*****)))(''('&''&&&%%%%%$$$####""""!! ! ~~~~~}}|||||{{{{{zzzzzyyyxxwxwwvwvvvvuuuuuttttssssrrrrqqqpppppoooonnnmmmmmllllkkkkkjjiiiiihhhhhggfggffffeeddddccccbbbbbaaa`a````___^^^]]]]]\\\\\\[[[[ZYZZYYXXXXXWWWVVVVVVUUTTTTTSTSRRRRRQQQQPPPPOOOONNNMMMMMLMLLLKKJKJJJJJIIIHHHGHGGGFFEEEEEDDDDDDCCCBBBBBBAAA@@@@???>>>>>>===<<<<;;;::::::9988888877777666555544443332222221111000////....----,,,,++++*****)))))(((''''&&&%%%%%$$$$####""""!!! ~~~~}}}}||||{{{{{zzzzzyyyxxxwwwwwvvvuuuuuttttsssssrrrrqqppppoooooonnnnmmmmlllllkjkkjjiiiiiihhhhgggfgfffeeeeddccccccbbbbaaaa`````__^^^^^]]]]\\\\\[[[[ZZYYYYYXXXWXWWWVVVVVUUUUTUTTSSSSRRQRQQQQPPPPPOOONNNNMMMMLLLLLKKKJJJJJIIIIIHHHHHGFFFFFFEEDDDDDDCCCBBBBAAAA@@@@?????>>>>==>==<<;<;;:::::99999888877776666655454343332322212111000///./....---,,,,,++++*****)))))('(''&&&&%%%%%%$$$$##"""""""!  €~~~~~}}}||||{{{{{zzzzyyyyxxxxwwwwvvuvuuuutttstsssrrrrqqqqpppoooooonnnmnmmmlllkkkkkjjjijiiihihhhgggggfeffededcdccccbbbbbaaaa````____^^^^]]\]\\\\[[[[ZZZYZYYXYXXXXWWWWVVVVVUUUTTTSTSSSRRRRQQPQPPPPPOOOOONNNMMMMLLLLKKJKJJJJJIIIIHHHGGGGGFFFEEEEDDDDDDCCCCBBAAAAA@@@???>>>>>>=====<<<;;;:;:::99998888877767666555444443333222211111000/////...---,,,,,++++++**)*))))((''''''&%%%%%%%$$$$##"#"""!!! €~~~}}}}}||||{{{{{zzzyzyyxyxwwxwwwwvvuuuuuuttttsssrrrqqqqqpppppoooonnnnmmmlmlllkkkkkjjijiiiihhhhgggfgfffeedddddcccccbbbaaaa`a``____^^^]]]]]]\\\\\\[[Z[ZYYZYYXXXXWWWWVVVVVUUUTTTTTTTSSRRRRQQQQPPPPOOOOONNNNMMMLMLLLKKKJJJJJJJIIHHHHGGGFFFFEFEEEDDDDDCCCBBBBBAAAA@@@@@??>>>>>>>==<<<;<;;;;::::9989888877776666555444333332222211111000///./....----,,,+++++***))))))(((''''&&&&%%%%%%$$$###"#"""!!! €€~~~~}}}}|||{{{{zzzzzyyxxxxxwwwwwvvvvuuuuttttstsssrrrrqqqpppooooooonnnnmmmlllkkkkjkjjjiiiiiihhhhggfffeeeeededdcccbbbbbbaaaaa```___^^^^]]]]]\\\\\[[[[ZZZZYYYYYXXWWWWVVVVVVUVUTTTSTSSSRRRRQQQPPPPPPOOOONNNMNMMMLLLLKKKKJJJJIIIIHIHHGHGGGFFFFEEDDDDDDCCCBBBBBAAA@A@@????>>>>>=====<<<;<;;::::99998888887776665655544434333222221111000/0////..-.--,,+,+++++++***))))((''''&&&&%%%%%%$$$$####""""!!!  €€€€~~~}}}|||||{{{{{zzzzzyyyxxxxwwwwvvvuuuuuutttssssrrrrqqqpqpppoooooonnnmmmmmllkkkkkjjjjjiiiihhhgghgfgffefeededddcbcbbbbbaaa``````__^_^^^]]]]\\\\\\[[ZZZZZZYYYXXXWWWWVVVVVVVUUTTTTSTSSSRRQQQQPPPPPPOOOONNMMNMMLLLLLKKKKJJJJIIIIIIHHGHFGGFFEFEDEDDDDDDCCCBBBAAA@A@@@@@??>>>>>>====<<<;;;;;;::99989888887776765555554434323222221110010/0///....----,,,++++++***))))(((''('&'&&&%%%%%%$$####"#"""!!!!! €€~~~}}}}||||{{{{{zzzyyyyyxxxxwwwwvvvvuuuuuttttsssrrrrrqqppppopoooonnnmmmmlmlkllkkkjjjjiiiiihhhggggggfffeeeedddcccbcbbbbbaa````____^^^^^]]]\\\\\[\[[[[ZZYYYYXXXXXWWWVVVVVVUUUTTTTTSSRRRRRQQQQPPPPPPOOONNNMNMMLLLLLKKKKJJJJJIIIHIHGGGGGFFFEFEEEEDDDDCCCBCBBBAA@@@@@@@???>>>>>===<<<<<;;;:::9:99988888777767655555544433332222211110000////....----,,,,++++****)))))(('(''&'&&&&%%%%%$$$####""""!! ! €€€€~~~}}}}||||{{{{{zzzzzyyyxxxwwwwwwvvuuuuuuttttssrrrrrrqqqqpooooooooonnmmmlllllkkkkjjjiiiiiihhhhgggffffffeeddcdcccccbbbbbaa````_`___^^]]]]]\\\\\[[[[[[ZZYYYYXXXXXWWWVVVVVVUUUUTTSSTSSSRRQRQQQPPPPPOOOONNNNNMMMLLLLKKKJJJJJJIIIHHHHHGGGGFFFFEEDEDDDDCCCCBBBBAAA@A@@?@???>>>>>>===<<<<;;;:::::9988888887776666655554444333222221111000//////..-----,,,++++++****))))('(('''&&&&%%%%%$$$#####"""""!!!  €€€~~~~}~}}}||||{{{{{zzzyyyxxxwxwwwvvvvvuuuuuuttstssrrrqrqqqqpoppoooonnnnmmmmlllkkkkkjjjjjiiiiihhhgggfgfeeeeeeddcccccbbbbbaaaaa``_____^^^^]]]]\\\\\\[[ZZZZYYYYYXXXWWWWVVVVVVUUUUTTTSSSRSRRRQQQQPPPPPPOONNNNMMMMLMLLLKKJKJJJJJIIIIHHHHGGFFFFFEEEEDDDDDCCCCCBBBAAAAA@@@@???>>>>>=====<<<;;:;:::9:8998888777776665555444443332222221101000////...----,,,,++++++***))))(((((''''&&&%%%%%$$$#####""!!!!!!! €€€€~~}~}}|}||{{{{{{zzzzyyyxxxxxxwvwvvvvuuuuuutttssssrrrrqqqqppopooooonnnnmmmmllkkkkkjjjiiiiiiihhhghgggfffeeedddddcccbbbbbbaaaa````____^^^^]]\\\\\\[[[ZZZZYZZYXXXXWWWWWVVVVVUUUTUTTSSSRRRRQQQQPPPPPPPOOONNNNMNMMLLLKKKKKJJJJJIIIHHHHHGGGGFFFFFEEEDDDDDCCCCBBBAAA@A@@@?????>>>>====<<<;<;;;;::99898888877776666565444444333222222111100//////.-.----,,,,+++++***))))((((('''&&&&&%%%%$$$$$##"#""!"!!!! €€€€~~}}}}|||||{{{{{zzzzyyyyxxwwwwwwwvvvuuuuutttssssrrrqrrqpqppppoooonnnnnmmmmlllllkjjjjjiiiiiihhhggggffffeeeeddddcccbbbbbaaaaa````___^^^^^]]]\\\\\[[[[ZZZZZYYYXXXXWWWWVVVVVUUUTTTTTSSSRRRRQQQQPPPPPPOONNNNMMMMMLLLLKKKKJJJJJIIIHIHHGGGGFFFEEEEEDDDDDDCCCBBBBAAAA@@@@@???>>>>>=>===<;<;;;;:::::999888877776666555555443332222221111000/0///...-.---,,,+++++++***))(()(''('''&&&%%%%%%$$#$$#""""!!!! €€€€€~~~~~~}}}}||||{{{{z{zzyyyxxyxxwwwwvvuuuuuuuutttssssrrrrrqqpppppooooonnnmmmlllllkkkkkjjiiiiiiihhhggggffeeeeeeddcdcccbbbbbaaaa`````__^^^^]^]]\\\\\\\[[[[ZZYZYYYYXXWWWWWVVVVVUUUUTTTTSSSSSRRRQQPPPPPPOOONOONNMMMMMLKLKKKKJJJJJIIIIIHHHGGGGGFFEEEEDDDDDCCCCBBBBBA@A@@@@????>>>>>===<<<<<;;;;;::9999988887777766665544444333222222111100000/./...-----,,++++++***)*))(((('('''&&&&%%%%%%%$######"""!!!!! ‚€€€€~~~~~~}}}|||{{{{{{zzzzyyyxxwxwwwwvvvvuuuuuutttsssrsrqqqqqpppppooooonnnnmmmmlllkljkkjjijiiiihhhhggggfffeeeeeedddccccbbbbbaaa`````___^_^^]]]]\\\\\\[[[[ZZYZYYYYXXXWWWVVVVVVUUUTTTSTTSSRRRRQQQQQPPPPOOOONNNMNMMMLLLLKKKKJJJJIIIIIHHHHHGGGFFFFEEEDDDDDDCCCCBBBAAAA@@?@@????>>>>====<<<<;;::::::99888888777776666555554433332222211111000////....----,,,,++++*+**)))()(((('''&&&&&%%%%%$$$#####""""!!! ‚€€€€~~~}~}}}||||{{{{{zzzyzyxxxwxwwwvvvvvvuuuututsssssrrrqqqqpppopoooooonnmmmmmlllklkjjjjjiiiiiihhhhgggffffeeeeddddccbbbbbbbaaaaa```____^^^]]]\\\\\\[[[[[ZZZZYYXXXXXWWVWVVVVVVUUTTTTTSSSSRRRQQQQPPPPPPOOONNNNMNMMLLLLLKKKJJJJJJIIHHHHHGGGFGFFFEEEEDDDDDCCCBBBABAAA@@@@???>>>>>>====<<<<;;;::::::99988888777676655554543333232222211100000//./..-----,,,,++++*****))))((('(''&&&&%%%%%$%$####"#"""!!!!! ‚‚‚€€€~~~}}}}}||||{{{{zzzzyyyyyxxxxwwwvvvvuuuuuuttttssssrrrqqqqppppoooonnnnnmmmmmlllkkjkjijiiiiihhhhgggggffffeeeddcdcccbbbbbbaaa``````___^^^]]]]\\\\\[[[[[ZZZZYYXYXXWWWWWWVVVVVUUTTTTSSSRRRRRRQQQQPPPPOOONONNNNMMMMLLLKKKJJJJJJJIIIHHHGGGGGGFEFFEEEDDDDCCCCCBBBAAAA@@@?????>>>>>====<<<<;;;:::9::9998888877776665554454343332222211111000////....---,,,,,++++*+**))))((('(''&'&%&%%%%%%$$#$##"#""""!!! ‚‚‚€€€~~~~}}}}||||{{{{{zzzyzyyxxxxwwwwwvuvvuuuuttttsssrsrrrrqqqpppoooooononnmmmllllkkkkjjijiiiiiihhghgggfgfefeeedddccccbbbbbbbaa`a`____^^^^^]]]\\\\\\[\[[[[ZZYYYYYXXXXWVVVVVVUUUUTTTTSSSSRRRRRQQQPPPPPPOOOOONNNMMMMLLLKKKKJJJJJIIIIHHHGGGGFGFFEEEEDDDDDDDCCCCBBBBA@A@@@@@??>>>>>>===<<<<;;;;;::::998888887876666655554444332322221111000000///..-----,,,+++++*****)))()(('''&&&%&%%%%%%$$$$$##"""""!! ! ‚‚‚‚€€€~~~}}}}}||||{{{{{zzzyzyyyxxwwwwwwvvvvuuuuutttsssssrrqqqqqqppooooonnnnmnmmmlllklkkjjjjiiiiihhhhggggfgffeeedddddcccbbbbbbaaaaa``_`_^^^^^]]]]\\\\\[[[[ZZZYYYYXYXXXXWWWVVVVUVUUTTTTTTSRSSRRQQQPQPPPPPOOOONNNMMMLMLLKKKKKJJJJJJIIHIHHHGGGFFFFFEEEEDDDDDCCCCBBAAAA@@@@@@????>>>>>===<<<<;;;;;:99999988887777776665545544433332222111100000////....---,,,,++++******))(((('('''&&&%%%%%$$$$$##"#"""!!!! ƒƒ‚‚‚‚‚€€€€~~~~}}}|||||{{{{{zzzyyyyxxxwxwwwvvvvuuuuutttttssrrrrrqqqqppppooooonnnnmmmmlllkkkkjjjjiiiiiihhhggggfffeeeedddddcccbbbbbbbba```_`__^^^^^]]]]]\\\\[[[[[ZZZZYYYYXXXXWWWVVVVVVUUTTTTSSSSSRRRRRQPQPPPPOOOONNNNNMMMLLLLKKKKKJJJJIIIIIHHHHGGFGFFFFEEDDDDDCCCCCCBBBBAAA@@@?@??>>>>>>===<<<<;;;:::::99998888877766666655554433323222221111000////....----,,,,+++++*+**))))(('(''''&&&&%%%%$$$$$##"#""""!!!! ƒƒƒ‚‚‚‚€€€~~~}}}}|||{{{{{zzzzyyyyyxxxxwwwvvvvvuuuutttsstssrrrrqqqpqpppoooooonnnnmlmllllkkkjjjjiiiiiihhhhggggfffeeededdccccbbbbbbbaa````_____^^^^^]]\\\\\\[[[[^ciou|€ƒ……ƒ€{tnf`ZVVUUUUTTTTSSSSRRRRQQQQPPPPPOOONNNNNMMMMLLLLKKKJJJJJIJIIHHHGGGGGGFFFEEEEDDDDDCCCBBBBABAAA@@@?????>>>>=====<<<<<:::::::99988888777766665554544433332222121110000////..-.---,,,+++++****))))))(('''&&&&&&%%%%$$$$######""!!!! „ƒƒƒ‚‚‚‚€€€€€~~~~}}|}|||{{{{(=ZuvuuuuuuuuttUTqqppppiicccb]]]]\\.Bc‚………………………}m/SSRRRRRRQQPOOONNNNMMF1$%0;GJJIIIHHHGHGGGFFFFFEEDDDDDDDCCBBBBAAAAA@@@????>>>>>>===<<<<<<;;:::::9988888887766665555444433333222221110000///./...---,,,,,,++++******))((((''''&&&%%%%%%$$$#$####""""!!! ƒƒƒƒ‚ƒ‚‚‚€€€~~~~~}}}}|||{{{Tvvvuuuuutt00qqqqppjjcccb]^]]]a\…………………„„„MTSSSRRRRQPPPOONNNH'JJIIIIHHHGGGFFGFFFEEDDDDDDDCCBCBBBAAA@@@@@????>>>>====<<<;<;;;:::::9998888877777665655544333333322221111000/////..--.--,,,++++++*****)(()((('''&&&&&%%%%$%$##$###"""!!! „„ƒƒƒƒƒ‚‚‚€€€€€~~}~}}}|||||{{9vvvuuuuukiqqqppjjcccc^^`l‚‡N……………………„+TTSSSSRRRPPPPOOOFJJJIIHIHHHHGGGFFFEEEEEDDDDCCCCCCBBBAAA@@@@@???>>>>>>===<<<<;;;;:::999998888877766666555454433333222221211000///.//..--,--,,,+++++****))))((((''''&&&%%%%%%$$$###"##""!!!! „„„ƒƒƒƒ‚‚‚‚€€€€~~}~}}}||{|{CvvuuuuuHGqrqqpjjcddcdv‡ˆ‡‡o……………………PTSSSSRRRPPPPOONJJJIIIIHHHHHGFGGFFFFEEDDDDDDCCCCBBBBAAA@@@@@????>>>>>===<<<<;;;;:::::9998888887776665555554433332222211111100/////..-----,,,,+++++*****))((((((''''&&%%%%%%%$$####""""!!!!! ……„„„ƒ‚ƒ‚‚‚‚‚€€€€~~~}}}|}|||ivvvuuu""rrqqqkkddddˆˆˆˆ‡‡?……………………9uZTSSRRRPPPPPO7JJJJJIIIHHHHGGGGFFFFEEEEDDDDDCDCCBBBAAAA@@@?@????>>>>>===<<<<<;;:;::::9998888888776666654554433332222221111000///.....---,-,,,,+++++***)))))(((''''&&&&%%%%$$$$$####""""!!!! ………„„ƒƒƒ‚‚‚‚€€€~~~~}}}||||{zzzyviP!Avvvuua^rqqqqpppppooonmlllllkkkkjjjiiiiiiggfffffeedddcbbbaaaaa`d{ˆˆˆˆˆˆ‡‡‡†††€oK ……………………nƒsXSSSSQPPPPO!/AJLJF;/KJJJJIIIIIHHHHGGGFFFFEEEEDDDDDCCCBBBBAAAA@A@@????>>>>>>====<<<;;;;;::9999998888777676655554544433322222211100000///...-.---,,,,,+++++****))))(((''''&&&&%%%%%%$$#$####"""!!!! ……„„„„ƒ„ƒƒƒ‚‚‚€€~~~}}}}|||zzzzzyyyv-%vvvvv: 9rrqrqqqppoooommmmlllkkkkkkjijiiiigggfffefeedddbbbbbaabu‰‰‰ˆˆˆˆˆˆˆ‡‡†††††[……………………&2ƒƒƒkTSSQQPPPP?>>>>=====<<<;;;:::::999988888777666665554444333332222211111000////...-.-,-,,,++++++****))))((''''&&&&&%%%%%$$$$###"#"""!! …………„…„„„ƒƒƒ‚‚‚‚€€€~~~~}|}|{{zzzzyyygwwvvsFEorrrrqqppppoonnmmlmllllkkjjjijiiighggfffefeeddcbbbbak‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡†††……………………djƒƒƒƒ_SQQQPPPLNNNMMMLLLLLLKKKJJJJJIIIIHHHHGGGFFFFEEEEEDDDDCCCCBBBBABAAA@@@????>>>>>>==<<<<;;;;;:::998988888787766666545544433333222221110000////...----,,,,+++++****)*)))((('''&&&&&%%%%%$$$$###""""""!! †…………„„„ƒƒƒ‚‚‚‚‚€€€€~~~~~~}|}{{{zzzzzyvwwvwSkjQsrrrqqqpppopnnmmmmmllllkkkjjjjiigggggfgfffedebbbbcx‰‰‰‰‰‰‰‰‰ˆˆˆˆ‡‡‡††††€……………………„/-„ƒƒƒƒoQQQPPP7ONNNMMMLMLLLKKKJJJJJJIIIHHHHHGGGGGFEEEEEEDDDDCCCCBBBAAAA@@@@@????>>>>>>==<<<<;;;;:;::999998888787766666655544333332222211110000/////..-----,,+++++++*****))(((((''''&%&%%%%%$$$$$####""""!!! †…†……„…„„„ƒƒƒƒ‚‚‚€€€€~~}~~}}{{{{zzzzyfwwwv,.uu-+srrrrqqqqqppnnnmmmmllklkkkkjjiiihhggggfffeeeecbci‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆ‡‡‡‡‡††Z;……………………„md„ƒƒƒƒRRQQQP!%8FNNMMMMLLLLKKJKJJJJJIIIHHHHHGGFGGFEFEEEDDDDDCCCCCBBBAAAAAA@@????>>>>>====<<<<;;;;::99998888888777766655554543333332222111110000//......---,,,+++++****)))()(((('''&&&&%%%%%$$$$$###"""""!!! ††…………„…„„ƒƒƒƒ‚ƒ‚‚‚€€€€€€~~~~~}}|{{{{zzzw-%wxwkVuuTgrrrrqqqqppponnmnmmmmlllkkkjjjjjhhggggfffffee‰‰‰‰‰ˆˆ‡‡‡‡pJk………………………„7'ƒƒƒƒƒRRQQQP8%1?KLLLKKKKKKJJJJJIIIIHHHHGGFGFFFFEEEDDDDDDCCCCBBBBAAAA@@@@??>>>>>>====<<<<;;;;:::99999888888777766555545444333322221111100////.....---,,,,++++++****)))))((''''&&&%%%%%%%%$$$####"!"!!!! ††††……„„…„ƒ„„ƒƒ‚‚‚‚€€€€~~~~||{{{wkP!CxwxEtuusDsrrrrqqqqpponnnmnnnmlmllkkkjjjiiihhhggffffef‰‰‰‰‰‰ˆD††………………………t^ƒ„ƒƒRRQQQPP (BLLLKKKJKJJJJIJIIIHIHGHGGGGFFFEEEEDDDDDCCCCBBBABA@@@@@????>>>>>====<<<<;;;;:::999998888777766665555544443332222221110000///....-----,,,++++++***)))))((('''&'&&&%%%%%%$$$$##"#""""! ! ‡†††††………„„„„ƒƒƒƒƒ‚‚‚€€€€~~~ixxx=vuuu<ssrsrrqqqqqooonnnnmmmmllklkkkjjiihhhggggffff‰‰‰‰‰‰ˆM††……………………………A"‚„ƒƒSRRRRQQM"+KLLLKKKKJJJJIJIIIHHHGGGFGFFFEEEEEDDDDDDCCCBBBBAAA@@@@@???>>>>=====<<<;;;;;;:::9998888877777666555544433333322222111100////...-.----,,,++++++**)*))))((('''&&&&&%%%%%%%$$$###""""!!! ‡‡†††…………„„…„„ƒƒƒƒƒ‚‚‚€€~~Dyxx^dvvvub\sssrrrrqqpooooonnnmmmmllllkkjjiiihhhghggffe‰‰‰‰‰‰ˆo††††…………………………{X„„„TRSRQQQQQ;%MLLKKKKKJJJJJIIIHHHHGGGGGFFFFEEEEDDDDCCCBCBBBBAAAA@@@??>?>>>>>>==<<=<;;;;:;:9::9988888877776665554544333323222221111000////....----,,,,+++++*****)))((('(''&&&%&%%%%$$$$###"##""!!! ‡‡‡†††††……„„„„„ƒƒƒƒƒ‚‚‚€€€€:yyyx7%wvvvvu$5ssssrrrrrqoooonnnnnmmmmlllklkkiiihhhhhggggf‰‰‰‰‰‰‰h††††…………………………J€„„xTRRRRRQQQQF4%>>>>====<<<<;;;::::::9998888787766665654444343333222211110100////./...---,,,,++++****)))(((((''''&&&&&%%%%%$$$#$#"#""""!!!  ‡‡‡‡†††††…………„„„ƒƒƒƒƒ‚‚€€€€VzyyytKvwvvuvJotssrrrrqqooooonnnnnmmmmlllkkkiiiihhhhggggf‹‹Š‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰u††††††…………………Q„„ƒvSSRRQQQQPPPPPMB5$$MMLLKKKKKJJJJJIIIHIHHHGGGGFFFFEEEDDDDDCDCCCCBBAAAA@@@?@??>?>>>>====<<<;<;;::::::989888878776666555544444333222222111000/////...-.---,,,++++++***))))(((('''&'&&&%%%%%%$$$$###"""!!!!! ˆ‡‡‡‡‡††……………„„„„„ƒƒ‚‚‚‚€€€€€(>\zzzzzyPMsssssrrrqppoooonnnnmnmmlllkkljiiiiiihghggg‹‹‹‹‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆ…x[&0†††††††…………………T~„ƒƒqSSSRQRQQQPPPPPPOJMMMLLLKKKJJJJJJIIIIHHHHHGGGGFFEEEEEDDDDDCDCBBBBBAA@A@@@@????>>>>===<<=<;;;;;::::99998888877766655555544433332222211110000///...-.----,,,,++++******)))((('''&&&&%&%%%%%$$$####""""!!!! ˆ‡‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚€€€€~}}|||{|{{{{{zzzz((ttsssrsrrpoooooonnnnnnmmmlllkiiiiiihhhgggg‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆ…2j††††††…………………‚ K„ƒƒƒkSSRRQRQQQPPPPPOOEMMMMLLLKKKJJJJJJIIIIHHHHGGFFGFFFFEDEDDDDCCCCBCBABAAA@@@@@??>?>>>>>====<<<;;;:;:::99989888877776666555444433332222222111100////...-.--,,,,,+++++*****)()((((('&'&&&%%%%%$$$$$####"""!!!!! ˆˆˆ‡‡‡‡†††††………„„ƒ„„ƒƒƒ‚‚‚‚‚€€€€€~~}}}||||{{{{{zzjettssssrrqpppoooooonnmmmmmllljjiiiiiihhhgg‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆ4‡†††††††………………^|ƒƒƒƒcS ARRQQQPQPPPPPLNMMMLLLLLKKKKJJJJIIIIHHHHGGGGFFFFEEEEDDDDDCCCCCBBAAAAA@@?????>>>>>====<<<<<;:;:::::99988888877666656555444333322222111100000//./...---,-,,,+++++***)*))(((((''''&&&%%%%%%$$$$#####"!!!!! ˆˆˆˆ‡‡‡‡‡†††…………„„„„„ƒƒƒ‚‚‚‚€€€€~~~}}}|||{{{{{{zB@tttttssrqpppoooooonnnnmmmlmljjjiiiiiihhhm‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰Š‰‰‰‰ˆˆˆˆˆˆSv‡††††††…………………(FƒƒƒƒƒZ9NRQQQPPPPP4NNMMMLLLLLKKKKJJJJIIIIIHHHGGGFFFFFEEEEEDDDDCCCBBBBBBAAAA@@@???>>>>>>====<<<;<;:;:::99998888887776665555444444322222221111100////.....----,,,+++++***)*))(((((''''&&%%%%%%%%$$#####""""!!!!  ˆˆˆˆˆ‡‡‡‡†‡†††………„„„„ƒƒƒƒƒ‚‚‚€€~~~}}}}|||{{{{{ztttsssssqqppppooooooonmmmmmlkjjjiiiiihhi‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆ‚B‡‡†††††…………………gƒƒƒƒƒ‚%4BJNPK?('NMMNMMLLLLKKKKJJJJJIIIHHHHGGGGFGFFFFEEDDDDDDDCCBBBBBBAA@@@@????>>>>>>>==<<<;;;;;::::99998888877766766555444433332222212110000/0//...---,,,,,++++++****))))((('''''&&&%%%%%%%$$#####"""!!! ˆ‰ˆˆˆˆ‡‡‡‡‡†††††………„„ƒ„ƒƒƒ‚‚‚‚€€~~~}}}|}|||{{{\jyyxxwxwwvwhXuttstssqqqqppoooooonnnmmmllkkjjjjiiiih}ŒŠŠŠ‰Š‰‰‰‰ˆˆˆˆˆF€‡‡††††……………………1„ƒƒƒƒƒ=NNNNNMMLLLLKKKKKJJJJIIIIHHHGHGGGGFFFEEEEDDDDCCCCCBBBAAAAAA@@@???>>>>>>===<=<<<;;:;;:::99988888777766565555544433332222211100000//./.-.----,,,,,++++*****)((()(''''''&&%%%%%%$$$#####""!!"!! ‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„„„ƒƒ‚‚‚‚}~}}}|||||{{3*zyyxyxwxwwww(2uutttttqqqpppoooooononnnnmmkkjjjjiiiiqŒŒŠŠŠŠ‰‰‰‰‰ˆ‰ˆˆˆyQ‡‡‡†††……………………o„„ƒƒƒƒ"OONNNNMMMMLLLKKKKJJJJJIIIIIIHHGGGFGFFFEEEEEDDDDCCCBBBBBAA@A@@??@???>>>>=====<<<;<;;::::99888888877776666665444443332222222111000////.....---,,,+++++++***))))((((''&''&&%%%%%$%$#$$###""""!!! ‰‰‰‰‰ˆˆ‡‡‡‡‡†††††………„…„„„ƒƒƒ‚‚‚‚~~~}~}}}||||tRyzyxxxxwwwwwPnuutttsrrqqqppppoooooonnnmnkkjjjjjiijŒŒŒŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆ8†‡†‡††………………………:„„„„„ƒLOONONMMMMMLLLLLKKKKJJJJIIIIIHHHHGGGGFFFFEEEEDDDDCCCCBBBBAAAAA@@@???>>>>>>====<<<;;;;;::::99888888777766666544443343322222221110100///....---,,,,,+++++****)))((((''''&&&&&%%%%%$$$$###"#""!!!!! ŠŠ‰‰ˆˆˆˆ‡‡‡‡‡‡†††…………„„„„„ƒƒƒ‚‚‚‚€~~}~}}}}|||MvzyzyyxxxxwxwrJuuutttrrrqqpppppoooooonnmnkkkkjjjji{ŒŒŒ‹ŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆm_‡‡‡††………………………w„„„„„ƒgB0NPOOOONNNNMMMMLLLLKKKKJJJJJIIIIHHHGGGGGFFFFEEEEDDDDCCCBBBBBAAA@@@@?@???>>>>=====<<;;;;;:::::999888887777666665545443333322222211100000///....----,,,+++++****)*)(((((''&&&&&%%%%%%$$$$$##"#"!!"!! ! ŠŠŠ‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„„„„„ƒƒƒƒ‚‚‚€~~~~~}}|}|%9zzzzyyyyxyxwww8#uuuuttrrqqqqpppppooooonnnnllkkkjjjnŒŒŒŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆ**‡‡‡‡†††……………………D„„„„„„ƒƒƒO;-$+>>>>>==<=<<;<;;;::::99888888877767666555544433322222211101000////..-----,,,,++++**+*))))((((((''''&&%%%%%%$$$$###"""!!!!!! ‹Š‰‰‰ˆˆ‰ˆˆˆ‡‡‡‡‡††††…………„„„ƒƒƒƒƒ‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyxxxxxwwwvvvvuuuuuuttssssrrrrrrqqpppooooooonnnmmmmmllllkkkk‚ŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†‡††††††……………………………………………„„„„„„„ƒƒtSSSSRSRRRQQQQPPPPPOOONONNNMMMMMLKKLKJJJJJJJJIIHHHHGGGGFFFEEEEEDDDDDDCCCBBBBAAAA@@@@????>>>>=====<<<;;;:;::::999988888777766655554444333322222111010000////...---,,+,++++++***)))))(((('''&&&&%%%%%$$$$$##"""""!!! ! ‹ŠŠ‰‰‰ˆ‰ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒƒ‚‚‚‚€€€€€~~}}}}}}|||{{{{{zzzzyyyyxxxxwwwwvvvvuuuuutttsssssrrrrqqqppppooooonnnnmmmmlllllkkpŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††………………………………………………„„„„„ƒƒ„[STSSRRRRRRQQQPPPPPOOOONNNNMMMMLLLKKKKKJJJJJIIIIHHHGHGGFFFFEEEEDDDDDCCCCCBBAAAA@@@@@????>>>>>===<<<<;;;:;::::99898888877767656555444433332222211111000///./...---,,,+,+++++***)*)))((((''&&&&&%%%%$$$$$####""""!!!! ‹Š‹Š‰Š‰ˆ‰ˆˆˆ‡‡‡‡‡‡††††……………„„ƒƒƒƒƒ‚‚‚‚€€€~~~}}}}}|||{{{{{{zzyzyyxxxwwxwwwvvvuuuuuttttsssssrrqqqqqpqopooooonnnnmmmmlllllkƒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††…†……………………………………„…„„„„„„„uTSSTSSSRRQQQQQPPPPOPPOONNNNNMMMMLLKLKKJJJJJJIIIIHHGHGGGFGFFFEEDDDDDDDCCCCBBBAAA@@@@@??>>>>>>>=====<<;;;;::::999988888877766666554544433332222111111000////...----,,,,,++++****)))))(((''''&&&&&%%%%%$$$$##""""!!!!! ‹‹‹ŠŠŠŠ‰‰‰ˆˆˆ‡‡‡‡‡‡††††……………„„„ƒƒƒƒ‚‚€€€€~~~}}}|||||{{{{{{zzzyyyyxxxxwwwwvvvvuuuuuuttstssssrrrqqqpppppoooonnnnmmmlllllpŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††………………………………………………„„„„„„„ZTTSSSSRRRRQQQQQPPPPPOOOONNNMMMMLLKLKKKKJJJJIIIIIHHHHGGGGFFFEEEEDDDDDDCCCCBBBAAAA@@@??????>>>>=====<<<;;;;::::9998888877777666555544443333222221111100/00//...----,,,,,+++++***)))))(((''''&&&&%%%%%$$$$####""""!! ! Œ‹‹‹‹ŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡‡‡‡†††………„„„„„ƒƒƒƒ‚‚‚‚€€€€~~~~}}||}||{|{{{{{zzzzyyyyxxxxwwwvvvuuuuuuttttsssssrrqqqqpppooooooonnnnmmmlll€ŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††……………………………………………„„„„„„ƒpTTTTTSSSRRRRQQQPPPPOPOONNNNNMMMMLLLKLKKKJJJJJIIIHHHHHGGGFFFEEEEDEDDDDCCCBBBBBBAA@@@@@??>?>>>>====<=<<<;;;;;:::99988888877766666555444343332222212111000///....-----,,,+++++**+*)))()(((('''&&&&%%%%%%$$$$###"""""!! ! Œ‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††…†……„„„„ƒ„ƒ‚‚‚‚‚‚€€€€~~~~~}~}}}|||{{{{{zzzzyzyyxxxxwwwvwvvvuuuutttttssssrrqrqqqpppopoooonnnmmmmmmnŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…†……………………………………………„„„„„„VTTTTSSSRSRQQQQQPPPPPOOONONNMMMMMLLLLKKKJJJJJJJIIHHHHGGGFGFFFFEEEDDDDDDCCBBBBBBAAAA@@@@???>>>>>====<<<<<<;;::9::9988888877777665655454443332222211101000////....----,,,+++++****))))((((('''&&&%%%%%%%$$$$####""""!! Œ‹‹‹ŠŠ‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~~}}}|||{{{{{z{zzzyyyxxxwwwwvvvvvuuuuuttttsssssrrrqqqqpppooooononnnmmmxŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††…………………………………………„„„„„„eUUTSTSSSSRRRQQQQPPPPPPOOOONNMMMMMLLLKKKKJJJJJJIIIIHHHGGGGGGFEEEEDEDDDDCCCCBBBBAAAAA@@?@???>>>>>====<<<<;;;:::::9989888887776666565545443322222221121100////.....----,,,+++++*****))))(((('''&&&%&%%%%$$$$###"#"""!!! !  ŒŒŒ‹‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡††††…†……„„ƒ„„ƒƒƒ‚‚‚‚€€€€~~~~~}}}}|||{{{{{{zzzyyyyyxwxxwwwvvvvvuuuuuttstsssrrrrqqqqpppoooooonnmnmmŠŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††……………………………………………„„„„~UUUTTSSSSSRRRRQQQQPPPPOOOONNNMMMMMMLLLKKKKJJJJJJIIHHHHGHGGFFFFEEEEEDDDDDCCCBBBBBAAAA@@@@???>>>>=>===<<<;;;;:::::99988888777766665555544333332222211100000////.....--,-,,,++++****)*)))(((('''&&&&%%%%%%%$$$###""""!!!! ŒŒŒ‹‹‹‹‹‹ŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡‡†††…………„„„„ƒƒƒƒ‚‚‚€€€~~~~}}}||||{{{{{{zzyyyyyyxxxxwwvvvvuvuuuutttttsssrrrrqqqpppppooooonnnnqŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡†‡†††††…†………………………………………„„„„„ZUUTTTTSSSRRRRQQQQPPPPPPOONNNNNNMMMMLLLKKKKJJJJJIIIHIHHGGGFGFFFFEEEDDDDDCCCCBCBBAAA@@@@@????>>>>>>==<<<<<;;;;:::9:99988888877676665555444433332222111110000///...----,,,,++++++***)))))((((''''&&&%%%%%$$$#$###"#"!"!! ! ŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰‰ˆˆ‡‡‡‡‡‡††††…†………„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|||{{{{{zzzzyyzyyxxxxwwvvvvvvuuuuutttssssrrqqqqqpppppooooonnnzŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††……………………………………………„„„gUUUTTTSTSSSRRRRQQQQPPPPPOOONNNNMMMMMLLLKKKKJJJJJIIIIHHHGGGFGGFFFEEEDDDDDDCCCBBBBBAAA@@@@@???>>>>>>===<<<<;;;::::999899888878766665555444433333222222110000///./...----,,,,++++++***))))((''''&&&&&&%%%%%%$$####""""!!! ŒŒŒ‹Œ‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††…………„„„„ƒ„ƒƒƒ‚‚€€€~~}~}}||||{|{{{{{zzzyyyxxxxxxwwwwvvuuuuuuttttsssssrrqqqqqqpppooooonn‰ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡‡†††††††…………………………………………„„}VUUUTTTTSSSSRRQRRQQQPPPPPOOOONNMNMMMLLLLLKKKJJJJJIIIIHIHGGGGGFFFEEEEDDDDDCCCCCBBBAAAA@A@@????>>>>>===<<<<<<;;;:;:9:99988887777666555554544333332222111100000//./....--,,,++++++*****)))(((((('''&%&%%%%%$$$$####""!!!! ! ŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡††††………„„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|||{|{{{{{{zzzyyxyxxxwwwwwvvvuuuuuuttsssssrrrqqqqqppppooooopŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††………………………………………„„„XUUUTTTTTSTSSRRRQQQPPPPPPPOONNNNMMMMMMLLLLKKKKJJJJIIIHIHHHGGGGGFFFEEDDDDDDDDCCBBBBAAA@@@@?@???>>>>>>==<<=<;<;;:::::99998888877766665554444434332222222110000////...-----,,,++++++****))((((('''''&&%&%%%%%$$$###""#"""!!! ŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒ‚‚‚‚‚€€€€€€~~~~}}}}||||{{{{{{zzzyyyxxxxxxwwwwvvvuuuuutttstsssrrrqrqqppppooooovŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡†††††…†……………………………………………aVVUUTTTTTSSSRRRRRQQQPPPPPPOOONNNNMMMMMLLLLKKKJJJJJJIIHIHHHHGGFFFFFEEEDDDDDCCCCCCBBAAA@@A@@?????>>>>>==<=<<<;;::;::999988888877777665554544443332222211110000////...------,,++++++****)))((((('''&'&&%%%%%%$$$$###"""""!!!! ŒŒŒ‹Œ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††………………„„„ƒ‚ƒƒ‚‚‚€€€€€~~~}~}}}|||{{{{{{zzzyyyxyxxwwwwvvvvvuuuuuttttsssrrrrrqqqqqppooooŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††††…………………………………………mVVUUUUUTTTSSSRSRRRQQQPPPPPOOOOONNNMMMLLLLKLKKKJJJJJIIIIHHHHGGGGGFFFEEEEDDDDDCCCBCBBABAAA@@@@????>>>>>==<<<<;;;;;::::99998888777776666554544443323222221110000////...-----,,,++++++*****)))(((('''&&&&%%%%%$$$$#####""!!!!! ŽŒŒŒ‹Œ‹‹ŠŠŠ‰‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚‚‚€€€€€~~}}}}}|}||{{{{{{zzzyyyyxxxxwwwwvvvvvuuuuuttttsssrrqrqqqqqpopooŠŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…†………………………………………}VVVUUUTTTTTTSSSRRRQQQQQPPPPPOOOONNNNMMMMLLLLKKKJJJJJJIIHHHHHGGGGGFEFEEEEEDDDDCCCBBBBAAAA@@@@?????>>>>====<<<<<;;:;:::99999888877777666554444433332222222111000/0/./....---,,,,+++++****))))((((''''&&&&%%%%$$$$$###"#""""!! ŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„ƒƒ„ƒƒƒ‚‚‚€€€€€~~~}~}}}||||{{{{{zzzyzyyxxxxwwwwwvvvuuuuuttutsssssrqrrqqqpppopŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††………………………………………WVVVUUUUTTTTSSRSRRQRQQQPPPPPOOOOONNNNNMMLLLLKKKKKJJJJIIIIHHHHGGGGFFFFFEEEDDDDDCCCBBBBAAAA@A@@@@???>>>>====<<<;<;;;::::999988888777666665555444433322222111110000///....--,-,,,,+++++*+*))))(()((''''&&&%%%%%$$$$$$#""#"""!!!! ŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††……………„„„„„ƒƒ‚‚‚€€~~~~}}}|||{{{{{{{zzzzyyxyxwwxwvvvvvvuuuuttttssssrrrrqqpqqpprŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††……………………………………[VVVVVUUUUTTSSSSSRRRRQQQQPPPPOOOONNNNMMMMMLLLKKKKJJJJJIIIIIHHHGGGFFGFFEEEEDDDDDDCCCCBBBAAA@@@@???>?>>>>====<<<<;<;;:::::99988888877766655555443433222222211110000////...---,,,,+,++++****))))((((''''&&&&%%%%%$$$$####""""!!! ŽŽŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††††……„„„„ƒƒƒƒ‚‚‚‚‚€€€€€~~~~}}}}}}||{{{{{{zzzzyyyxxxxxwwwvvvvvuuuuttttssssrrrqqqqpppvŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡ˆ‡‡‡‡‡†‡††††††††………………………………`WVVVVUVUUUTTSTSSSRRRQQQQQPPPPPPOOONNNNMMMLLLLKKKKKJJJJIIIIHHHHHGGFGFFFEEEEDDDDDCCCBCBBBAAA@@@?@??>?>>>>>====<<<;;;:;::9:99988888877776665555544433322222211101000///....-----,,,++++++**)*)))(((('''&&&&&%%%%%%$$$$##"""!!!!!! ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡†‡†††…………„„„„ƒƒƒ‚‚‚‚€€€€€€~~}}}}||||{{{{{{zzzzzzyyxxwwxwwvvvvvuuuuttttssssrrrrqqqqp{ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††…………………………………gWVVVVVUVVUTTTSTSSRRRRRQQQPPPPPPOOOONNNNMMMLLLLLKKJJJJJJIIIIHHHGGHGGFFFFFEEDDDDDDDCCCCBBABAA@@@@@???>>>>>=====<<<<;;;;:::999998888777766666655444333232222221101000/////...---,,,,++++++***))))((('('''&&&&%%%%%%$$$$###"""!!!!! ŽŽŽŒŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒƒƒ‚‚‚€€€€~~~}}}}}||||{{{{{zzzzzyyyxxxwwwwwvvvvuuuuutttssssssrrqqqq€ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡†††††††††…………………………nWWVVVVVVVUUTTTTTSSSRRRQQQQPQPPPPOOOOOONNNMMLLLLLKKKJJJJJJIIIHHHGHGGGFFFFEEEEDDDDDCCCCBBBBAAA@@@@@????BHOXajt|ƒˆŽŒˆ‚zsh^UKC=:888887767666665554444332222212110000/0//./...--,-,,+,+++++***)))(((((''''&&&&%%%%%$$$#$###"""!!!!!! ŽŽŽŽŒŒ‹‹Œ‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„„ƒ‚ƒ‚‚€€€€~~~}}||||||{{{{{zzzyzyyyyxxwwwwvvvuvuuuuutttttsssrrrrqq…ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††…………………………uWWVVVVVVUUUTTTTSTSSSSRRQQQQQPPPPPOOOONNNMMMMMMLLLKKKKJJJJIJIIIHHHHGFGGFFFFEEDDDDDDDCCCBBBBAAA@ABKYkŽŽŽŽŽŽ}fRC:88766765555554444323222221111000/////...-----,,,+++++****)*)()((('''&'&&&%%%%%$$$$#####"""!!!! ŽŽŽŽŽŒŒŒŒŒ‹‹ŠŠŠŠ‰Š‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„ƒƒƒƒ‚ƒ‚‚‚€€€€€~~~}}}}||||{{{{{zzzyyyyxxxxxxwwvvvvvuuuuuttttssssrrrrq‰ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡†††††††……………………{WWWWWVVVVVUUUTTTTTTSSRRRRQQQPPPPPPOOONONNNMMMMLLKLKKKJJJJJJJIIIHHHGHGGFFFFFEEEDDDDDCCCCBBBBERi†‘‘‘‘‘‘‘ŽŽŽŽŽŽbJ:76666555544444332222221111000/0///...-----,,+++++++***)))))(('('''&&&&%%%%%$$$$#$"#"""!!!!! ŽŽŽŽŒŒ‹‹‹‹Š‹Š‰Š‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„„…„ƒ„ƒƒƒƒ‚‚‚€€€€~~~~}}}}||||{{{{{zzzyyyyyxxxwxwwvvvvvuuuuutttssssrsrrrŒ‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡††‡†††††††…………………€XWXWWVVVVVVUUUTTTTSSSSRRRQRQQQPPPPPPOOONNNMNMMMLLLKKKKKJJJJIJIIHHHGGGGGGGFFEEEEEDDDDCCCCLc„’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ~ZB76555555444333222222211100000///...-----,,,+++++****)))(()(('''&'&&&%%%%$$$$$###"""""!! !! ŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆ‡‡‡‡‡††††……………„„ƒƒƒƒƒƒ‚‚‚€€€€~~~~}}|}||{{{{{{{zzyyyyyxxxxwwwvvvvuuuuutttttsssrrrq‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡†‡††††††……………………ƒXXWWWWWVVVVVVUUTTTSSSSRSRRRQQPPPPPPPOOONONNNMMMMMLKKKKKKJJJJIJIIIHHHHGGGGFFFEEEDDDDDDNj’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽ`B6655444444333322222211000/0///...----,,,,,++++****)))(()(((''''&&&%%%%%%$%$$###"""!!!!! ŽŽŽŒŒŒ‹Œ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††…………„…„„„„ƒƒƒ‚‚‚€€€~~~~~~}}}|||{{{{{{zzzyyyyxxxwxwwwwvvvuuuuutttsstssrr‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡†††††……………………XXXXWWWVVVVVUUUUTTTTSTSSRRRRQQQQPPPPPPOONNNNNMMMMLLLKLKKKJJJJJIJIHIHHGHGGGFFFFEEEDJe““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŠY<5554444333222222111110000/.//.----,-,,,++++++***)))())(('''''&&&%%%%$$%$#$###"""!"!!!  ŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡‡‡†…†………„„„„ƒƒ‚‚‚‚‚€€€€~~}~}}|||||{{{{{{zzzyyyyxxxxxwwwvvvvvuuuuuuttsssrr‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡‡††††††…………………XXXXXXWVVVVVVVUUUTTTSSSSSRSRRRQPQQPPPPOOONNNNNMNMMMLLKKKJKJJJJIIIIHHHHGGGGFFFFEFU|““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒrG65544333323222221111000/////..----,,,,,++++****))))(((('''&'&&&%%%%%$$$$#$###"!!!!!! ‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuuttttssss‘‘‘‘‘‘ŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††………ƒYYYXXWWWWWVVVVVUUUUTTSTSSSRRRQQQQPQPPPPPOOOONNNMMMMMLKLKKKKJJJJJIIIIHHGGGGGFFH`‘““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒ‰R7554433332222211110100////..-..---,,,,+++++****))())((('''&&&&%%%%%$$$$$###"""!!!!! ‘‘‘ŽŽŽŒŒŒ‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆ‡‡‡‡††††………………„„ƒƒƒƒƒ‚‚‚€€€€€~~~~~}}}}}|||{{{{{{zzzyyyyyxxwwwwvvvvuuuuuuutttsss‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††………€YYYXXXXWWVWVVVVVVUUTTTTTSSSRRRRRQQQPPPPPPOOONNNMNNMLLLLLKKKKJJJJJIIIIHHHHHGJf”““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒY74444323322221111000/////..-..---,,,+++++*****))))((''''''&&&%%%%%%$$$#$###""!!!!! ‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††††………„„ƒ„„ƒƒ‚‚‚‚€€€€€~~~}}}}||||{{{{{zzzzzyyyyxxxwwwvvvvvuuuuuttttssŠ‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††……|YZXXXXXWWWWWVVVVVUUUUTTTTSSSRRRRQQQQQPPPPOOOOONNMNMMMLLLKKKKKJJJJJIIIIHHHJg”””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒX64443333222221100100/////..-----,,+,+++++***)*)))((((('&&&&&%%%%%%%$#$####""!"! ! ’‘‘‘‘ŽŽŽŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††…………„„…„„„ƒƒ‚‚‚‚€€€~~}}}}}|||||{{{{{zzzyyyyyyxxwwwwvvvvuuuuutttss†‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…vZYYYXXXXXWWWVVVVVVVUUUUTTTTSRRRRRRQQQPPPPPPOOOONNNMMMMLLLLKKKKKJJJJJIIHIa”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒQ53433232222111110000///.....---,,,,+++++***)))))((('''''&&&%%%%%%$$$$$##""""!!!!! ’’’‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡†††…………„…„„ƒƒƒ‚ƒ‚‚‚‚€€~~}~}}}|||||{{{{zzzzzzyyxxxxxwwvwvvvuuuuututt‚‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††oZYYYYYXXXXXWWVVVVVVVUUTTTSSTSSSRRRRQQQPPPPPPOOONNNNMMMMLMLLLKKKJJJJJIIYŽ••””””””““““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒ…G4333322222211100000///...-.---,,,,++++++*)*)))(((((''''&&&%%%%%$$$######"""!!! ’’’‘’‘‘ŽŽŽŒŒ‹Œ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††…†…………„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}||||{{{{{zzzzyyyyxxxxwwwwwvvvuuuuuutt~‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††iZZYYZYYXXXXWWWVVVVVVUUUUTTTTTSSSRRQQQQQPPPPPPOOOOONMMNMMLMLLKKJKJJJJOz•••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒl;33332222221111000////...-----,,,++++++*****))(((((('''&&&%%%%%$$$$$$#""""""!!! “’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹Š‹Š‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††……………„„„„ƒƒƒƒƒ‚‚‚€€€~~~}}}}|||||{{{{zzzzyyyyyxxxxwwwvvvvvuuuuttz‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††d[ZZZZZYXXXXWWWWWVVVVVUVUUTTTSSSSRRRRRQQQQPPPPOOOONNNMNMMMMLLLLKKKKJd•••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒR33332222221110000////....----,,+++++++****))))((((''''&&&&%%%%%$$$$$##"""""!!!! “’’“’’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡††††…†………„„„„ƒƒƒƒ‚‚‚€€€€~~~}~}}}||||{{{{{{zzyyyyxxxwxwwwwvvuvuuuuuv‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡†††_[[ZZYYZYYXXXXXWWWVVVVVUUUTTTTTSSSRRRRRQQQPPPPPPOOOONNNMMMMMLLLKKKR„••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒw=33332222211100000////...----,,,,+++++***))))()((((''&&&&&%%%%%$$$$$####""!!! ! “““’’’’‘‘ŽŽŽŽŽŒŒŒ‹‹‹Š‹ŠŠ‰Š‰‰‰ˆˆˆˆˆ‡‡‡‡‡††…………„„„„ƒƒƒƒƒƒ‚‚‚‚€€€~~~~}}}}||||{{{{{zzzyyyyyxxxwwwvwvvvuuuuuu‘’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†‡†\[[ZZZZZYYXYXXXWWWWVVVVVUUUTTTTTSSSSRRRQQQQPPPPPPOOOOONNMNMMMLLLKc•–••••••••••””””””““““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹P333222221111100000///..-.----,,+++++++**)*))))('''''&&&%&%%%%$$$$$####""""!! ! “““’’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††…………„„„„„ƒƒƒƒ‚‚‚€€~~}}}}|}||{{{{{{zzzzzyyyxxxxwwwwvvvuuuuu‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†€\\[[Z[ZZZYYXYXXXWWWVVVVVVVUUTTTTTTSSSRRRRQQQPPPPPOOOOOONNMMMMMLOz–––•••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒk6333222221111000///./....----,,,++++++****)())((('''''&&&%%%%%$$$$###"""!!!! ! ““““’’’’’’‘‘ŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††……†………„„„„ƒƒƒƒ‚‚‚€€€€€~~}}}}||||{{{{{zzzzzyyyyxxxwwwwwvvuuuuuƒ’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡q\[[[[ZZZZYZYXYXXXWWWWVVVVVUUUUTTTTTSSRRRRQQQQQPPPPPOOONNNNMMMMW––––––••••••••••”””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹A332222221110000////.....--,,,,,+++++****))))(((('''''&&%%%%%$$$$#####""""!!!! ”“““““’’’’‘‘‘ŽŽŽŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††††…………„„ƒ„„ƒƒ‚‚‚€€€~~~~}}}}||||{{{{{zzzyyyyyyxwxwwvvvvvuuu{’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡f\\\[[[[ZZZZYYYYXXWWWWWVVVVVUUUUUUTTSSSRSRRQRQQPPPPPOOOOONNNMMc—––––––•–••••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹N3332222222110000///./..----,,,,,+++++***))((((('''''&&&%%%%%%%$$$$###""""! ! ”“““““’’’‘‘‘‘‘ŽŽŽŒŒ‹‹Œ‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒ‚ƒ‚‚‚‚€€€€~~~~}}}}|||{{{{{{zzzyyyyyxxxwxwwvvvvvuw’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡_\\\[[[[ZZZYYYYYYXXXWWWWVVVVVUUUUTTTTSSSSRRRRRQPPPPPPPOOONNNNr———–––––––––••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹^333222221111000////.....----,,++++++***)*)))((((('''&&&&%%%%%$$$#$##""""!!!! ””“““““’’’’’’‘‘ŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„ƒƒƒƒƒƒ‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyyxyxxxwwwwwwvvuuŽ’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰‰ˆˆˆ‡‡††………~WVVVWVWWXXXYYYYYXXYXXWWWWVVVVVUUUUTTTSSSSSSRQQQQQPPPPPOOOONP————–––––––––•••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹o5332222221110000/0///....,,-,,++++++*+**)))(((('('''&&&&%%%%$$$$$$###"""!!!!!  ””””““““’’’’’’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„ƒƒƒƒ‚‚‚‚€€€~~~~~~}}|}||{|{{{zzzzzyyyyxxwxwwwvvvuu’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‡‡†…„„„„„„„„„iWWWVVVVVVVVVUUUUVWWXWXXWWWWVVVVUUUUTUTTSSSSSRRQQQPPPPPPOOOT‹——————––––––––•••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹|833222221111100/////...-----,,,+++++****))))(((('''''&&&%%%%$%$$$$#"#""""!!! ”•”””““““’“’’’’‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡†††…†………„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}}}|}|||{{{{{z{zzyyyyxxxwwwwvwvvx’’’’‘’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠ‰ˆ‡………………………„„„„„„„\WWWWWWVVVVVVUUUUUUUUUUVVWWVVVVVVVUUTTTTSTTSSSRRQQQQQPPPPOV–˜˜——————–––––––––•••••••••••””””””““““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‰;32222221111100//////..-----,,,,++++*+**)))()((('''&&&%%%%%%%%$$$####""!"!!! ••”””“““““’’’’’’‘‘‘ŽŽŽŒŒ‹‹‹‹‹Š‹ŠŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡‡††††……………„„„ƒƒƒ‚‚‚‚‚€€€€~~~}}}}}|||{{{{{zzzyzyyxxxxxwwwwvvu’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹Š‰ˆ……………………………………„„„„„WWWWWWWVVVVVVUUUUUUUUUUUTTTVVVVVVVUUUUUTTTTTSSRSRQQQQQPPPX˜˜˜˜˜˜———————––––––•–••••••••••””””””””“““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹=3322222121100000///...-----,,,++++++****)))))(('''''&&&%%%%%$$$$$###""""!!!!! ••••””“““““““’’’‘‘‘‘ŽŽŽŒŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††††………„„„„ƒƒƒƒ‚‚‚€€€~}}~}}}}|||{{{{{zzzzyyyyxxxwwwwvvv’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹Š‰‡………………………………………………„„„„gWWWWWWWVVVVVVVVUUUUUUUUUUUUTTTTUVVVUUUUTTTTTSSSRRRRQQQQPZ˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹>332222211101000//./....--,,,,,+++++***)))))((((('''&&&&%%%%$%%$#$##"#"""!!!!  –•••”””“““““““’’’‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††††……„„…„ƒƒƒ‚ƒ‚‚‚€€€~~~~}}|}|{|{{{{{zzzzyyyyxxxxwwwwvx’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‰‡†††††……………………………………………„„„ZWWWWWWWWVWVVVVVUVUUUUUUUUUTTTTTTTTUUUUUUTTTTSSSSSRRRQQQX™™˜˜˜˜˜˜˜————————––––––•••••••••••••”””””“”“““““““““““’’’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹=3322222111100000///....----,,,+++++****)))(((((''''&&&&&%%%$%$$####""""""!!! ––•••””””“““““’’’’‘‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆ‰ˆˆ‡‡‡‡‡†‡†††………„…„„„„‚ƒƒ‚‚‚‚€€€€€~~}}}|}||{{{{{{zzzyyyyxyyxxwwwww‡’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ˆ††††††††………………………………………………„rWWWWWWWWWWVWVVVVVUUUUUUUUUUUTTTTTTTSSTUUUUUTTTSSSSSRRRQX™™™˜™˜˜˜˜˜————————––––––––••••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹;33222222110100000//...-.---,,,,+++++***)))))(('('''&&&&%%%%%$$$$#####"""!!! ––••••”””“““““““’’’‘’‘‘ŽŽŽŽŒŒŒŒ‹‹Š‹ŠŠŠ‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††……………„„„„ƒ‚ƒ‚‚‚‚€€€~~}}}}|||||{{{{z{zyxxvutsrqppppu‘‘‘‘’‘’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŠ‡†††††††††††……………………………………………„]WWWWWWWWWWWWWVVVVVVVUUUUUUUUUUUTTTTTTTSSUUUUUTTSSSSRRRV—™™™™™˜˜˜˜˜˜˜—————————––––•––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒ‹‹‰83332222211010000//.....---,,,,,++++++***)))((((''''&&&&%%%%%%$$#####"""!!!!!! —–––••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹Š‹ŠŠ‰‰‰ˆ‰ˆˆˆ‡‡‡‡‡‡††……………„„„ƒƒƒƒƒ‚‚‚‚€€€~~~~}}}}|||||{{zwurqqqqqqqqpppppp†‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹Š‡†††††††††††††††………………………………………xXXWWWWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTSSSSTUUTTSTSSRRU™™™™™™™˜˜˜˜˜˜—˜——————–––––––••••••••••••”””””””““““““““““““’’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹|5322222221111000////.....--,,,,,+++++*****)))((('(&''&&%%%%%%%$$#$###"""""!!!  –––•••••”””““““““’’’’‘‘‘‘ŽŽŽŒŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††…†…………„„„ƒƒƒƒ‚‚‚‚€€€€~~~~~}~}}}}{xtrrrqqqqqqqqqqqpppppt‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒ‰‡‡‡‡‡†††††††††††††………………………………………_XXXWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUTTTTTTTTSSSSTTTTSTTSR‚™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒ‹n4332222212100000///.....---,-,++++++*+**)))()('('''&'&&%&%%%%$$$#####"""!"!! ! ——––••••••”””“““““’’’‘’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡†††……………„„„„„ƒ‚ƒƒ‚‚‚€€€~~~~~}|xssrrrrrrqqqqqqqqqqpppppp†‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŠ‡‡‡‡‡‡††††††††††††††††………………………………vXXXXXWWWWWWWWWWWWWVVVVVVUUUUUUUUUUTTTTTTTTTSSSSSTTTTSSv™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––•••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹_3332222221110000////....----,,,,+++++****))))(((('''&&&&&%%%%%$$#$####"""!!! ! ——–––––•••”””““““““’’’’‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰ˆ‰ˆˆˆˆ‡‡‡‡†‡††………„„„„„ƒƒƒƒ‚‚‚‚€€€€€€~}yssssrrrrrrrqqqqqqqqqqqqppppt‘‘‘‘ŽŽŽŽŽŽŽŠ‡‡‡‡‡‡‡‡‡†††††††††††††††………………………………]XXXXWXWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUUTTTTTTSSSSSSSTTTi™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••””•””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒ‹‹N3323222221101000///....-----,,+,+++++****)))))(('''&&&&%&%%%%$%$$$##""""!!!!! ˜—–——––••••””””“““““’’’‘’’‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††……………„„„ƒƒƒƒ‚‚‚‚€€}wstssssssrrrrrrqqqqqqqqqqqqpppp€‘‘‘ŽŽŽŽŽŽŒ‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††††…†………………………nXXXXXXXXWWWWWWWWWWWWVVVVVVUVVUUUUUUUUTTTTTTTTSSSSSSSS^™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•–••••••••••””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒ‹A33332222211110000///....----,,,,+++++*+**))))(((('''&&&&%%%%%%%$$$####""!!!! ˜————––•–••••””““““““’’’‘’‘‘‘‘ŽŽŽŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆˆ‡‡‡‡‡††…†……„„„„„ƒƒƒ‚‚‚‚€€|utttssssssrsrrrrrrqqqqqqqqqqqqpppr‘‘ŽŽŽŽŽŽŽŠ‡‡‡‡‡‡‡‡‡‡‡‡‡†‡††††††††††††††………………………ZXXXXXXXXWWWWWWWWWWWWWVVVVVVVVUUUUUUUUUUTUTTTTSSSSSSSU™šš™™™™™™™™™™™™™™˜˜˜˜˜˜—————————––––––•–••••••••••””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹63333222211110000///.//.-.--,,,,++++++****)*))((((''''&&&%%%%%%%$$$##""""!"!!! ˜˜————––•–••””””““““““’’’‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡†††……………„„„„„ƒƒƒƒ‚‚‚‚}uttttttstssssssrrrrrrrqqqqqqqqqqqpppx‘‘ŽŽŽŽŽŽ‹‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††††††††††††††………………cXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVVUUUUUUUUUUUUTTTTSTTSSS™™™šš™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒk3333222222111000////....-----,,,+++++****)))))(((''''&&&&&%%%%%%$$$####"""!!!! ™˜—˜——––––•–•”•”””“““““’’“’‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒ‚‚‚‚‚vuuttttttttssssssrrrrrrrrqqqqqqqqqqqpppƒ‘ŽŽˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡††††††††††††††…………sYYYXXXXXXXXXWWWWWWWWWWWWWWVVVVVUUUUUUUUUUUUTUTTTTTTSSj™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•••••••••••””””””””“““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒP3333222222111000/////....---,-,,,+++++****))))(((('''&&&&&%%%%%%$$$###""""!"!! ˜˜˜˜————––––••”•”””“““““““’’’‘‘‘ŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆˆˆ‡ˆ‡‡‡‡††††…………„…„„ƒƒƒƒ‚‚‚yuuuuutttttttsssssssrrrrrrrrqqqqqqqqqqqpprŽŒ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††††………[YYXYXXXXXXXXXWWWWWWWWWWWWWWVVVVVVUVUUUUUUUUUUTTTTTTSZ™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒ=43332222211110000/////....---,,,,+++++****))))((((''''&&&%%%%%%$%$$$#""""""!!!! ™˜˜˜˜˜——–––•–•••”””“”“““““’’’‘‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒ‚}uuuuuuuuttttttstssssssrrrrrrrqqqqqqqqqqqqppu‘ŽŠˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††††††††……`YYYYXXXXXXXXXXXXWWWWWWWWWWWWWVVVVVUUUUUUUUUUUUUUTTTTT‰™™™™™™™™™™š™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————––––––––••••••••••”•””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒw333323222221110000////./..----,,,,,+++++***)))(((((('''&&&%%%%%%$$$######""!!!!! ™™˜˜˜———–——––••••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŽŒŒ‹Œ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††…†……„„„„„ƒ‚xuuuuuuuuuuutttttttssssssrsrrrrrqqqqqqqqqqqqpp{Žˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††††††††hYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVUVUUUUUUUUUTTTTlšš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜—˜——————–––––––•–••••••••”•”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒS4433322222211110000///......---,,,+++++*****)))((((''''&&&%%%%%%$$$$####""""!!  ™™™™˜˜————–—–––••••”””““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††††………„…„ƒvuuuuuuuuuuuutttttttssssssssrrrrrqrqqqqqqqqqqqpp€Œˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††††††††††pZZYYYYYXYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUTTYšššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•–•••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒ;4433332222211100100////....---,,,,++++*****)))(()((''''&&&%%%%%$$$$###"""""!!!! ™™™™˜˜˜˜————––––••••”””“““““’’’’’‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††††…„…ƒ{vvvuuuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqqqqqpppp„ŽŠˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††††††vZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVWVVVVVUUUUUUUUUUTTšššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————–––––••••••••••••”•”””””””““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒm54333332222211110000////....--,-,+,,++++****))))((('(''''&&&%%%%%$$$$#$##""!"!!! ™™™™™™˜˜——˜——–––•–•••”””“““““’“’’‘‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹Š‹ŠŠ‰‰‰ˆ‰ˆˆˆ‡‡‡‡‡†††………‚vvvvvvvuvuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqpqqqˆ‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††z[ZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVUUUUUUUUUUTdššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–—––––––••••••••••”•””””””“““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒG44443322222221100000///./...---,,,,,+++++++**)))(((('''&'&&&%%%%%%$$#$#"""""!!! š™™™™™™˜˜˜————–––••••”””””““““““’’’’‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††…‚vvvvvvvvvuuuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqqqpq‰Žˆˆˆˆˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††|\ZZZZZZZYYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVUUUUUUUUUU•ššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜————————––––––••••••••••”””””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒ…54444333222212111000////.....----,,,++++++***))))(((''''''&&%%%%%%$$$#$###"""""!! šš™™™™˜˜˜˜˜————–––––••””””““““““’’’‘’‘‘‘‘ŽŽŽŽŽŒŒŒŒŒ‹‹Š‹Š‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††vvvvvvvvvvvuuuuuuuuuututtttttsssssssrrrrrrrrrqqqqqqqqqqpqˆŽ‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††{\[[ZZZZZZZYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVUUUUUUUlššššššššššš™™™™™™™™™™š™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒR444433332222211100000////...----,,,,++++++**)))))((((('''&&&&%%%%%$$$#####"""!"!!! ššš™™™™™˜˜˜—————–––––•••””””““““’’’’’’‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠ‰Š‰‰ˆ‰ˆˆˆˆ‡‡‡‡wvvvvvvvvvvvvuvuuuuuuuuttttttttssssssrssrrrrrrrqqqqqqqqqpqq„‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†v\[[[ZZZZZZZZYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVUUUUUWššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜——˜—————––––––––••••••••••”””””””“““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒ75543333322222111111000////.-.---,,,+++++++***)*))()(('''&&&&&%%%%%%$$$####"""!"!! ›šš™š™™™™™˜˜˜˜˜———––•••••””””“““““’’’‘’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡~wwwvvvvvvvvvvvuuuuuuuuuuuuttttttsssssssssrrrrrrrrqqqqqqqqqqpp€‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡q[[[[[[ZZZZZZZYZYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUVUUUqššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–—–––––••••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒY544444333222221111100/0///.....--,,,,++++++**)*)))(((''''''&&%%%%%%%$$$######"""!!! ›šššš™™™™™˜˜˜˜—————–––•••”””””“““““’’’‘’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡wwwwwvvvvvvvvvvvvuuuuuuuuuututtttttsssssssssrrrrqqrqqqqqqqqqpqp{‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡j\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUUWšššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—————————–––––––•••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒ855443333332222211101000///...--,-,,,,,+++++*****))()('('''&&&%&%%%%%$$$$####"""!!!! ››šššš™™™™™™˜˜˜—˜———–––••••”””“““““““’’’‘‘‘ŽŽŽŽŽŒŒ‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆ€wwwwwwwvvvvvvvvvvvvvuuuuuuuuuuttttttttsssssssrrrrrrrqqqqqqqqqqqqpu‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†b\\\\[[[[[[[[ZZZZZZYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVUUqšššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––••••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒY5444443333222221111100////.....--,,,,,+++++++**)*)((((('''''&&&%%%%%%$$$###""""""!! ››š›ššš™™™™™˜™˜˜˜˜—––––––••””””““““““’“’‘‘’‘ŽŽŽŒŒŒŒ‹‹‹Š‹ŠŠŠŠ‰‰‰ˆxwxwwwwwvvvvvvvvvvvvvvvuuuuuuuuuuttttttttsssssrrrrrrrqqqqqqqqqqqppprƒŽ‹‹‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡u]\\\\\[[[[[[[[ZZZZZZYYYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVWššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—————–––––––•–••••••••••”•””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒ755544433323222211101000////...-.---,,,,+++++****)))(((((''''&&&&%%%%$$$$$####""!!!! œœ›››ššš™™™™™™˜˜˜˜˜——––––•–•••”””“““““’’’’‘‘ŽŽŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ƒxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuututtttttsssssssrrrrrrqrqqqqqqqqqqpppxŽ‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡f\\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVmšššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽS555444443332222211110000////....---,-,,++++++****)))((((('''&&&&%%%%%%$$$####""""!!!!! œ›››››ššš™™™™™˜˜˜˜————––––•••”•”””“““““’’’‘‘’‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠ‰‰…xxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuutttttttttsssssrsrrrrrrrqqqqqqqqqqpppr€Ž‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡q^\\\\\\\\\\[\[[[[[ZZZZZZZZYZYYYYYYXXXXXXXXXXWWWWWWWWWWWWWVVVV˜šššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––––•••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ‰75544543433332222111110000///...-----,,,,++++++***))))((('''''&&&%%%%%%%$#$###""""!!!!! œœ››››ššššš™™™™™™˜˜˜˜—–—–––••”••””””““““’“’’‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠˆxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqpqpppt†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡ya]]]]]\\\\\\\\\[[[[[[[ZZZZZZYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWWVVdšššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽH655444443333222222111100/0///....----,,,+++++******))))((('''&&&&%%%%%%$$$$####""!!!!! œœœœ››››ššš™™™™™™˜˜˜˜———–—––•••••”””“““““’’’’’‘‘‘ŽŽŽŒ‹Œ‹‹‹Š‰yxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttssssssssrrrrrrrqqqqqqqqqqpppppu†ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡{d]]]]]]]\\\\\\\\[[\[[[[Z[ZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWWV†šššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••–••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽs66554444343333222211110000////..-.-----,,,+++++*+**))))(((''''&&&&%%%%%$$$$$###"""""!!!! œœœœœ›››››ššš™™™™˜˜˜˜˜————–––••••””””““““““’’‘‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹yxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttsssssssrrrrrrqqqqqqqqqqqqppppps‚‹ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡ˆ‡vc]]]]]]]]\\\\\\\\\\\\[[[[[[[Z[ZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWW\›ššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜————————–––––––––••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ<655555444333332222111100000///....----,,,+++++++****)))(((('''&&&&%%%%%%$$#$##"#""""!! œœœœœ››››ššš™™™™™™˜˜˜——————––••••”””“”““““’“’‘‘‘‘‘‘ŽŽŽŒŒŒ‹‹…yyxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvuuuuuuuuuututtttttsstssssrsrrrrrrrrqqqqqqqqqpppppppru‡ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆƒl`]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYXXXXXXXXXXXXXWWWWWWWWWWs›››šššššššššššššššššššššš™™™™™™™™™™™™š™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••”””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽZ665555544343332222222110000/0//.....---,,++++++******)))))(((''&&&&&%%%%%$$$$####""""!!! œœœ›››ššššš™™™™™™˜˜˜˜—–——––––•”•””“”“““““’’‘‘‘‘‘ŽŽŽŒŒŒŠyyyyyxxxxxxxxwwxwwwwwvvvvvvvvvvvvvuvuuuuuuuuuutttttttsssssssrsrrrrrqqqqqqqqqqqpppppplggkv†Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆoc^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYYXXXXXXXXXXXXWWWWWWWWW™››šššššššššššššššššššššššš™š™™™™™™™™™š™™™™™™™™™™™™™™˜˜˜˜˜—˜˜——————–––––––•••••••••••””””””””“““““““““““’“’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŠ66655554444333222222111100000/////..----,,+++++++****)))(((('('''&&%&%%%%%$$$###""#"""!!! œœœ›››šššš™™™™™™˜˜˜˜———––––•••”””””““““““’’’’‘‘‘‘ŽŽŽŒ}yyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttttsssssssrrrrrrqqqqqqqqqqqppppnggggggioy†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ˆˆˆˆˆ‚sha_^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWW`››››š›šššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••”•””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽB7656554544433332222211110010////./.----,,,+,,+++++***)))(()((('''&&&&%%%%%$$$#$##"""""!!! žžœœœœ››››ššš™™™™™™™˜˜˜˜———–—––•–””””””“““““’’’’‘‘‘‘‘‘ŽŽŽŽ…zyyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuutttttttsssssrsrrrrrqrqqqqqqqqqqqppggggggggggfgimsy~ƒ‡‰‹‹‰‡ƒ~vnhc`_____^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[\[[[[[ZZZZZZZZYZYYYYYYXXXXXXXXXXWWWWWWWx›››››››ššššššššššššššššššššššššš™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––••••••••••”””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽa776655544444433333222211110000//./....--,-,,++++++*****))(((((''''&&&&%%%%%$$$####""""!"!!žžœœœ›››››šš™š™™™™™˜˜˜˜———–—–––••”””””““““““’’’‘‘‘‘ŽŽŽ‹zyyyyyyyyxxxxxxxxxxwxwwwwwwvvvvvvvvvvvvvuuuuuuuuututtttttttsssssssrrrrrqrqqqqqqqqqqqhggggggggggggfffffffffffffff```________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZYYYYYYXYXXXXXXXXXXWWWWWW››››››››ššššššššššššššššššššššššš™™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••••”””””“””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ76766555455443432322222111000000//....---,,,,,++++++***))))(('(''''&&&&%%%%%$$$###"##"!""!žžžžœœœœœ››šššš™™™™™™™˜˜˜———–––––•••””””“““““’’’’’‘‘‘ŽŽŽŽŽzzzyyyyyyyyyxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuuuuttttttssssssssssrrrrrrrqqqqqqqqmhgggggggggggggffffffffffffffd```______^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZYZYYYYXYXXXXXXXXXXXXWW`››››››››š›ššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽB776665665544434332222221110000/////...---,,,,,++++**+***)))(((''&''&&&&%%%%%$$$$#####""!!Ÿžžžžœœ›œœ››ššššš™™™™˜˜˜˜˜˜———––––••••””””“““““’’’’‘‘‘‘‘ŽŽŽŽŠzzzzyzyyyyyyxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqqqohhgggggggggggggfgfffffffffffffa````______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYXYXXXXXXXXXWXWt›››››››››››ššššššššššššššššššššššš™š™™™™™™™š™™™™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ[777766665544443333332222211000/0///.....---,,,,,++++*****))))((('''&&&&&%%%%%%$$$###"#""!ŸŸžžžœœœœœ›››šššš™™™™™™™˜˜˜————–––•–••”””””““““““’’’‘‘‘ŽŽŽŽ}zzzzzzyyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqrghhhgggggggggggggffffffffffffffe```_________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXXW››››››››››š›ššššššššššššššššššššššššš™™™™™™™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––––••••••••••””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ77777666655554443323222222211000/////....----,,+++++++***)))()((('''&&&&&%%%%%$%$$$##""""ŸŸŸžžžœœœœ›››š›ššš™™™™™™™˜˜———–––––••••”””“““““““’’’‘‘‘މ{zzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuttttttttssssssrrrrrrrqqqqqlhhhhgggggggggggggggfffffffffffffb```_`______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXX[››››››››››››››ššššššššššššššššššššššš™š™™™™™™š™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽ;87776666555554443332222221111000////....----,,,,++++++****)))((((('''&&&%%%%%$$$$$$###""Ÿ ŸžŸžžœœœœ››š›šš™™™™™™˜˜˜˜———–—––•••••”””““““““’’‘‘‘’‘Ž|{{zzzzzzyzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuututttttssssssssrrrrrrrrqqphhhhhhhghggggggggggggfffffffffffff``````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZYYYYYYYYYXXXXXXXXf››››››››››››››šššššššššššššššššššššššššš™™™™™™š™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––••••••••••””””””““““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽJ877776665555544443323222211111000/////...----,,,,+++++****))))((((''''&&&&%%%%%%$$$###"" ŸŸŸžžžžœœœœœ››››šššš™™™™™˜˜˜˜˜————––•–••”””””“““““’“’‘‘‘‘‘‰{{{zzzzzzzzzyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvvuuuuuuuuuuutttttssssssssrsrrrrrqrihhhhhhhhggggggggggggggffffffffffffb`````________^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXyœ›››››››››››››››šššššššššššššššššššššššš™™™™™™™š™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽb8887777666555544444332322211110010/0/......--,,,,,+++++*****))())('(''&&&&&%%%%%$%$##### Ÿ ŸŸžžžžœœœ›œ›››ššššš™™™™™™˜—————––––••””””””“““““’“’’‘‘‘‘}{{{{zzzzzzzzzzyyyyyyyxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuuuuutttttttttsssssssrrrrrrohhhhhhhhhgggggggggggggggfffffffffffe``````________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZYZYYYYYYYXXXXXX’œœ›››››››››››››››šššššššššššššššššššššššš™™™™™™šš™™™™™™™™™™™™™™™™˜˜˜˜˜˜——˜——————––––––•••••••••••••””””””“““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ‚88878777665555545433332222222111000/0//.....----,,,,+++++**)*))))((((''&&&&&&%%%%$$$####  Ÿ ŸŸŸžžžœœœœœ›››ššš™™™™™™˜˜˜˜————–––––••”•”””““““’’’’’‘‘‘‘Œ{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuuuuttttttttssssssssrrrrhhhhhhhhhhhhggggggggggggggfffffffffffa`````_`______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYXXXXXZœœœœ››››››››››››››ššššššššššššššššššššššššššš™™™™š™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————––––––––••••••••••••””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ;88877777666655544433333222221111000////...-.----,,++++++****))))((((''''&&&&%%%%%$$$$##    ŸŸŸŸžžžœœœœ››ššššš™™™™™™˜˜˜—————–––•••••”””“““““““’’‘‘‘‘‘‚{{{{{{{{zzzzzzzzyyyyyxyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttstsssssrrrrmhhhhhhhhhhhhhgggggggggggggfffffffffffd```````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXaœœœœœœœ›››››››››››š›šššššššššššššššššššššššš™™™™™™šš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••••”””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽD8888777766666554444443333222211110000//./...------,,+++++****)))))(((''''&&&%&%%%%%$$$#     ŸŸŸŸžžžžœœœœœ››š›ššš™™™™™˜˜˜˜————––•–••••””””“““““’’’’‘‘‘Ž||{{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuutttttttssssssssrqhhhhhhhhhhhhhhhghggggggggggggffffffffff```````_`______^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\[[\[[[[[ZZZZZZZZYYYYYYXYXXmœœœœœœ››››››››››››››ššššššššššššššššššššššššš™™™™™ššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽS98888877766666555544443332222211101000//./....----,,,,+++++***)*)))(('''''&&&&%%%%%$$$$      ŸŸŸžžžžžžœœœœœ›››š™š™™™™™™˜˜˜˜———–—–––••””””“““““““’’’‘‘ˆ||||{|{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuutttttttssssssrlhhhhhhhhhhhhhhhgggggggggggggggfffffffffc`````````_______^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYXX|œœœœœœœœœ›››››››››››š›ššššššššššššššššššššššššš™™™™ššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––•••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽf9988888777666665554444332222222111110000///....---,,,+,+++++***)*)((((((''&'&&&%%%%%$$$¡¡     ŸŸŸŸžžžœœœ›››ššššš™™™™™˜˜˜˜˜———–—––•••””””””““““’’’‘|||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuutttttttssssssqhhhhhhhhhhhhhhhhhghggggggggggggffffffffff`````````______^_^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[\[[[[[[ZZZZZZZYYYYYYYYœœœœœœœ›››››››››››››››››ššššššššššššššššššššššššš™™šššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•–•–••••••••••””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽ~99988888777767665545444333322222111110000///....----,,,+++++******))((('(''&'&&%%%%%%%$¡¡¡     ŸŸŸžŸžžžœœœœœ›››ššš™™™™™˜˜˜˜˜———–––––••••””””““““’’’Ž|||||||{{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttstsssskhhhhhhhhhhhhhhhhhhhgggggggggggggfffffffffa`````````_______^^^^^^^^^]]]]]]]]]]]\]\\\\\\\\\[\[[[[[ZZZZZZZZYYYYYYZœœœœœœœœœœ››››››››››››››šššššššššššššššššššššššššš™™ššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜—————–—–––––––••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ:999888887776666665545434432322222111100////.....----,,,++++++*+**)))(((((''''&&&&%%%%%¡¡¡¡     ŸŸŸŸžžžžœœœ››››šššš™™™™™™˜˜˜————––––••••”””“““““““’‡|||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvuuuuuuuuuuuuuttttttssrhhhhhhhhhhhhhhhhhhhhhgggggggggggggggffffffd```````````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZZZZZYYYYY\œœœœœœœœœœœœ›››››››››››››››ššššššššššššššššššššššš™™™ššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————–––––––•••••••••••”””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ>:9998888877767666655444343332222221111000///....-.---,,,,++++****)))))((((''''&&&%%%%%¡¡¡¡¡     Ÿ ŸŸŸžžžžœœœœ›››››ššš™™™™™™˜˜˜˜——–—––••–••””””“““““’||||||||||{{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuututtttttsslhhhhhhhhhhhhhhhhhhhhhggggggggggggggfgffffffaa````````_`______^_^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[Z[ZZZZZYZYY`œœœœœœœœœœœœ››››››››››››››››ššššššššššššššššššššššš™™šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•––•••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽC:999888887777666666544544333222222111110000/////...---,,,,++++++**)*)))(('(''''&&&%%%%¢¡¡¡¡      ŸŸŸžžžžžžœœœœ››››šššš™™™™˜˜˜˜—————–––•••••””””“““||||||||||{{{{{{{{zzzzzzzyyyyyyyyyxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuvuuuuuuuttttttttrhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggfgfffffaa``````````_________^^^^^^^]]]]]]]]]]]]]\\\\\\\\[[[[[[[[Z[ZZZZZZYYYgœœœœœœœœœœœœœœ››››››››››››››ššššššššššššššššššššššššš™šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––•––••••••••••””””””““““““““““““’“’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽL::999988887777667665554444433332222211100000///...-.--,,,,,++++**+***)))()((''''&&&%%%¢¢¢¡¡¡¡     ŸŸŸŸžžžžœœœœ›››ššššš™™™™˜™˜——˜———––––•••”””””““Š||||||||||||{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuvuuuuuuuuuttttttnhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggfffffca```````````_________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[ZZZZZZZZYYnœœœœœœœœœœœœœœœ›››››››››››››››šššššššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜—————–––––––•–••••••••••””””””“”““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽU:9:999988888877666665554444332332222221110000////...----,,,,+++++***)*))(((('('&'&&&&%¢£¢¢¡¡¡ ¡     ŸŸžŸžžœœœœœœ›››šššš™™™™˜˜˜˜————–—––––••””””““|||||||||||||{{{{{{{{{zzzzzyyyyyyyxxxxxxxxxxxxxwwwwwvwvvvvvvvvvvvvvuuuuuuuuuuuttsiihhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggffffeaaaa`````````_________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZZZwœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššššššššš™šššššššš™™™™™™™™™™™™™™˜™˜˜˜˜˜˜˜——————––––––––•••••••••••””””””””“““““““““““““’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽ`::::99898888887776666655444443332222221110000///./...--,-,,,,+++++****)))()((('''&&&&%£¢¢¢¢¡¡¡       ŸŸŸžžžžœœœ›œ›››šš™š™™™™™™˜˜˜˜————–••••••””““||||||||||||||{{{{{{{{z{zzzzzzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuttoiihhhihhhhhhhhhhhhhhhhhhhhhghggggggggggggggfffaaaa```````````_______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZ~œœœœœœœœœœœœœœœœœœ›››››››››››š›ššššššššššššššššššššššššš›ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————––––––••••••••••••””””””“”““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽi;:::::99988888777766666554444333222222221100000///.....----,,+++++++**)*)))(((((''&&&%££¢£¢¢¡¡¡¡     ŸŸŸŸŸžžžœœ››››ššššš™™™™™˜˜˜————––––••••”””|||||||||||||||||{{{{{{zzzzzzzzyyyyyyyxyxxxxxxxxwwwwwwwvwvvvvvvvvvvvuuuuuuuuuuuutiiiiihihhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggfgfbaa`a``````````_`______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZ†œœœœœœœœœœœœœœœœ›œœ››››››››››››››ššššššššššššššššššššššš›ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘s;;::::999998888777767665554444433322222221110000/////...--,,,,,++++++***)*)(((('''''&&¤£££¢¢¢¢¡¡      ŸŸŸžžžžžœœœ›››››š™™™™™™™˜˜˜—————–––•–••””Œ}|||||||||||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuriiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggfcaaaa`a```````````____^__^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZœœœœœœœœœœœœœœœœœœ›››››››››››››››››ššššššššššššššššššššš›››šššššš™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–––––––––••••••••”•””””””“““““““““““““’“’’’’’‘‘‘‘‘‘‘‘‘‘‘‘{;;;::::99898888877777666555454433332222221111000/0////..-----,,,+++++******)()((('''&'¤¤££¢£¢¢¢¡¡¡¡     ŸŸŸžžžžžœœœœ››››šš™™™™™™˜˜˜˜————––––•••”†}|}|||||||||||||||{|{{{{{{{zzzzzzyyyyyyyxxxxxxxxxxxxwwwwwvwvvvvvvvvvvvvvvuuuuuuumiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggfeaaaaa````````````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZ“œœœœœœœœœœœœœœœœœ›œ›››››››››››››ššššššššššššššššššššššš›››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––••••••••••••”””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ƒ<;;;::::99988888878766666555545444322322221111000////./..----,,,,++++*++*)))))((((''''¤££££££¢¢¢¡¡¡     ŸŸŸŸŸžžžžœœ›››››ššš™™™™™™˜˜˜˜˜——–—––––••~}}}|||||||||||||||{{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuutiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggbaaaaaa```````````_______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZ˜œœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššššššššš›››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ˆ<<<;;;::::99888888777666655555443333322222221110000///.....---,,,,+++++****))))(((((''¤¤£££££¢¢¢¡¡¡¡       ŸŸžžžžžœœœœœœ›››šššš™™™™™˜™˜˜˜—–––––•••}}}}|}||||||||||||||||{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwxwwwwwwwvvvvvvvvvvvvuuuuuuqiiiiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhggggggggggggggbaaaaaaa````````````_______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZšœœœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššššš››š›ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘‘<<;;;;;:::9:9988888777777656555543343322222211110000//./....--,-,,,+++++****)))))(((('¥¤¤¤££££¢¢¢¡¡¡ ¡    ŸŸŸŸŸžžžžœœœ›››››š™š™™™™˜™™˜—˜———–—••“}}}}}}|||||||||||||||||{{{{{{{{zzzzzzyyyyyyxyxxxxxxxxxxwwwwwwvwvvvvvvvvvvvvuuuuuliiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggbbaaaaaaaaa`````````_______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššššššššššššš››››š›šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————––––––••••••••••••””””””””“““““““““““’“’’’’’‘‘‘‘‘‘‘‘‘‘‘Ž<<<<;;;;::::9998888887776765655544443333322221111000///./....---,,,,,+++++***)))((((((¥¤¥¤¤££££¢¢¡¢¡¡¡¡     ŸŸŸžžžžœœœœœ›››šššš™™™™™™˜˜˜˜———–––‘}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzzyyyyyyyxxxxxxxxxxwwwwwwvwvvvvvvvvvvvvvuuuiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggggggggcbbbaaaaaaa``````````_______^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[œœœœœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššš››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘Ž==<<;;;;;:::9:9988888877766655555444443332222211100000//.//...---,,,++++++****))))((((¥¥¥¤¤¤¤££££¢¢¡¡¡¡       ŸŸžžžžžœœœœ››››šššš™™™™™˜˜˜˜——–––—Ž~~}}}}}}||||||||||||||||{{{{{{{z{zzzzzzzyyyyyyyxxxxxxxxxxwxwwwwvwvvvvvvvvvvvuvuriiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhggggggggggcbbabaaaaaa``````````_______^_^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\[[[[[[›œœœœœœœœœœœœœœœœœœ›››››››››››››››šššššššššššššššššš›››››››šššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜——————–––––––––••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘Ž===<<<;;;::::99998888887767666555544443333222221111100//////.-.----,,,,+++++*****)()((¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡     ŸŸŸŸŸžžžœœ›››š›šš™™™™™™˜™˜˜˜————Œ~~~}}}}}|}|||||||||||||||{{{{{{{z{zzzzzyzyyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvvvvumiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggdbbbbbaaaaaaaa``````````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[˜œœœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššššš››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–—–––––––••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‰===<<<;;;;;::::99998888877766665555544343332222211111000///.....--,,,,,,++++++****))((¥¦¥¥¤¤¤¤¤£££¢¢¢¢¢¡ ¡     ŸŸŸžžžžœœœœ››››šššš™™™™™™˜˜˜———–ˆ~~~~}}}}}|||||||||||||||||{{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvuiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggggebbbbaaaaaaaa```````````________^^^^^^^^]]]]]]]]]]]\\]\\\\\\\\[[[[”œœœœœœœœœœœœœœœœ›››››››››››››››š›šššššššššššššššš››››››››šššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–—––––––••••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘„====<<<<;;;:::::9999888887777666555544443333222221111000////./...----,,,++++++****))))¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¡¡¡¡¡     ŸŸŸžžžžžœœœœœ›››››šššš™™™™™˜˜˜˜——…~~~~~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzyyyyyyxyxxxxxxxxxxwwwwwwwwvvvvvvvvvvsiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhghggggggebbbbbbaaaaaaaa``````````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[œœœœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššš›››››››ššššššš™šš™™™™™™™™™™™™™™˜™˜˜˜˜˜—˜——————––––––––••••••••••••””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘}>===<<<<<;;;:::::99988888877777666555544433322222221110000/0//....---,,,,+++++**+**))(¦¦¥¥¥¥¤¤¤¤£¤£¢¢¢¢¡¡¡¡¡      ŸŸŸŸžžžœœœœœ›››šššš™™™™™˜˜˜˜˜˜ƒ~~~~~~}}}}}}}|||||||||||||||{{{{{{{zzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwvvvvvvvvvpiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhgggggggfbbbbbbbaaaaaaa````````````______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[†œœœœœœœœœœœœœœœœ›œ››››››››››››››ššššššššššššššš›œ›››››››ššššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–•••••••••”””””””””“““““““““““’“’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘t>==>=<<<<<;;;;::::99988888777776665654444443322222222111000///.....---,,,,+++++****)))¦¦¦¦¥¥¤¤¤¤£¤££¢£¢¢¢¡¡¡      ŸŸŸŸžžžžžœœœ›››››šššš™™™™˜™˜˜—~~~~}~~}}}}}}}||||||||||||||||{{{{{{{{zzzzzyyyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvjiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggbbbbbbbbaaaaaaaa```````````_______^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[œœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššœœœ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘k>>>=====<<<<;;:::::999898888877666665555443433322222211001000//./..-.---,,,+++++++**))¦¦¦¦¥¥¥¥¤¤¤¤£¤£¢£¢¢¢¡¡¡      ŸŸŸŸžžžžœœœœ›››››ššš™™™™™™˜˜˜€~~~~~~~}}}}}}||||||||||||||||||{{{{{{{zzzzzzzyyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvviiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhgggggcbcbbbbbbaaaaaaaa`````````_`______^_^^^^^^^]]]]]]]]]]]\]\\\\\\\\\xœœœœœœœœœœœœœœœœœœœ›››››››››››››š›šššššššššššššœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––•––•••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘b>>>>>====<<;;;;;;::99999888887776666655455443333222222111110000//.....---,,,,+++++****§¦¦¦¦¦¥¥¥¥¤¤££¤£¢¢¢¢¢¢¡¡       ŸŸŸžžžœœœœœœ›››šš™™™™™™˜˜˜~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwvvvvvvtiiiiiiiiiiiiiiiiiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhhggggcccccbbbbbaaaaaaa```````````________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\qœœœœœœœœœœœœœœœœ›œ››››››››››››››ššššššššššššš›œœ›œ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘X>>>>>>====<<;;;:;:::99999888887776666555554443333222222111000/////.....--,,,,++++++***§¦¦¦¦¦¥¥¥¥¥¤¤£¤££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžœœœœ››››šššš™™™™™™˜~~~~~~~~}}}}}}}}|||||||||||||||{|{{{{{{{{zzzzyzzyyyyyyxxxxxxxxxxxwwwwwwwvvvvpiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggccccbbbbbbbaaaaaaaa``````````______^_^^^^^^^^]]]]]]]]]]]]\\\\\\\\jžœœœœœœœœœœœœœœœœœœœœ››››››››››››ššššššššššššš›œœœœ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••••”””””“”““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘Q?>>>>>>==<<<<<;<;;;::999988888877766666554444443332222211111100///./....----,,,++++++*§§§¦¦¦¦¥¥¥¥¤¥¤¤££££¢£¢¢¡¡¡ ¡    ŸŸŸŸžžžžœœœ››››ššššš™™™™™€~~~~~}}~}}}}}||||||||||||||||||{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwvvliiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhggcccccbbbbbbbaaaaaaaa```````````_______^^^^^^^]]]]]]]]]]]]]]\\\\\\cžžœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššš›œœœœœ›››››››šššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––•••••••••••••”””””””““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘I???>>>>====<<<;;;;::::9999888888877776655555444433322222211110000////....---,,,+++++*+¨§§§¦¦¦¦¥¦¥¥¥¥¤¤£££¢¢¢¢¡¢¡¡ ¡    ŸŸŸŸŸžžžžœœœ›››››šššš™™™™‚~~~~~~}}}}}}}}|||||||||||||||{{|{{{{{{zzzzzzyyyyyyyxyxxxxxxxxxxxwwwwwwwwiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhggccccccbbbbbbbbaaaaaa`a``````````_______^^^^^^^]]]]]]]]]]]]\\\\\\\_žžžžœœœœœœœœœœœœœœœœœ›››››››››››››››››ššššššššššœœœœœœ›œ›››››ššššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—˜——————––––––••••••••••••””””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘D@???>>>>>===<<<<;;;;:::::99888888877767655555444333322222211101000///....----,,,,+++++§§§§¦¦¦¦¦¥¥¥¥¥¤¤¤¤£££¢¢¢¡¢¡¡¡¡    ŸŸŸŸŸžžžœœœ››››ššššš™™™„~~~~~~~}}}}}}}}}|||||||||||||||{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxxxwwwwwwwuiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgccccccccbbbbbbbaaaaaa````````````_______^^^^^^]]]]]]]]]]]]]\\\\\\\žžžžœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜——————––––––•–••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘A????>>>>>=====<<;;;:;;::::999888888776765655544443333322221111000000//.....---,,,,++++¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤£££¢¢¢¢¡¡¡       ŸŸŸŸžžžžœœœœœ›››š›šš™™™‡€~~~~~~~~}}}}}}|||||||||||||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxxxwwwwwtiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhfcccccccccbbbbbbaaaaaaaa``````````_______^_^^^^^^]]]]]]]]]]]]\\\\\\žžžžœœœœœœœœœœœœœœœœœœ››œ›››››››››››››ššššššššœœœœœœœ›œ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••”•””””””“““““““““““““’’’’’’’’‘‘‘‘‚@@???>>>>>>====<<<<;;;;;::::99998888877767665554443433332222211100000////...----,,,,+++¨¨¨¨§§§¦¦¦¦¥¥¥¥¤¤¤¤££££££¢¢¡¡¡¡     ŸŸŸŸžžžžœœœ›››ššššš™Š€~~~~~~}}}}}}}}||||||||||||||||{{{{{{{zzzzzzzyzyyyyyyxxxxxxxxxxxxwwwwqjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhfccccccccccbbbbbbbaaaaaaaa``````````______^^^^^^^^^]]]]]]]]]]]]\\\\žžžžžœœœœœœœœœœœœœœœœœœ›œ›››››››››››››š›šššššš›œœœœœœœœœœ›››››š›ššššššš™™™™™™™™™™™™™™™˜™˜˜˜˜˜—˜——————––––––––••••••••••””””””””““““““““““““’’’’’’’’‘‘‘k@@@?????>>>>====<<<;;;:;::::99988888878767666555544434332222211211000///./...----,,,,++©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¡¡¡¡¡     ŸŸŸŸŸžžžžœœœ›››››šš™Ž€~~~~~~~}~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzyyyyyyxxxxxxxxxxxxxwwwnjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhheccccccccccccbbbbbbaaaaaaaa``````````_____^_^^^^^^]^]]]]]]]]]]]]\\\ožžžžžžœœœœœœœœœœœœœœœœœ›œ›››››››››››››ššššššš›œœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————––––––••••••••••••”””””””“““““““““““““’’’’’’‘‘‘YAA@@@??>?>>>>=====<<<<;;;;;::99989888887776666555554443332222221110000////....-----,,,+©¨¨¨¨§§§¦¦¦¦¦¦¥¦¥¤¥¤¤¤££££¢¢¡¢¡¡¡¡     ŸŸŸŸžžžœœ››š›ššš‘€€€~~~~~~}~}}}}}||||||||||||||||||{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxxwwkjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhheccccccccccccbbbbbbaabaaaaa```````````_______^^^^^^^]]]]]]]]]]]]\\\ežžžžžžžžœœœœœœœœœœœœœœœœ››››››››››››››››››šššš›œœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–––––––••–••••••••••”””””””““““““““““““’’’’’’’‘‘LAA@@@????>>>>>>===<<<;;;;;;::9999888888877666665555444433332222211110000///./..----,,,+©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¤¤£££££¢¢¢¢¡¡¡     Ÿ ŸŸžžžžžœœœœ›››››š•€€€~~~~~~~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxwxjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhdcccccccccccccbbbbbbbabaaaaa``````````_`_______^^^^^^]]]]]]]]]]]]]]]žžžžžžžžžœœœœœœœœœœœœœœœœœœœœ››››››››››››šššššššœœœœœœœœœœœœœ›››››››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””““““““““““““““’’’’’’’CA@A@@@@@???>>>>====<<<<<;;;;;::99998888877776666555544444332322221110100/0///....----,,©©©©©¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¢¡¡¡     Ÿ ŸŸžžžžœœœœ›››š˜€€€€€~~~~~~~~}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxwjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhdccccccccccccbbbbbbbbbaaaaaaa````````````_______^^^^^^^]]]]]]]]]]]]\”žžžžžžžžœœœœœœœœœœœœœœœœœ›œ››››››››››››››ššššœœœœœœœœœœœ›››››››ššššššš™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜———————––––––••••••••••••””””””“““““““““““““’’’’’’†AAA@A@@@@????>>>>====<<<<;;;;;:::::9988888777766666555444433322222211110000///./..--,--,©©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡    Ÿ ŸŸžžžžœœœ›œ›››™€€€€€€€~~~~~~~}}}}}}}}||||||||||||||||{{{{{{{z{zzzzzzzyyyyyyyxxxxxxxxxvjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhdccccccccccccccbbbbbbbbaaaaaaa````````````_____^^^^^^^^]]]]]]]]]]]]]|žžžžžžžžžžœœœœœœœœœœœœœœœœœ›››››››››››››››šššœœœœœœœœœœœœ›››››š›ššššš™šš™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••””””””””“““““““““““’’’’’iBBAA@@@@@@????>>>>=>===<<<<;;;:::9:99888888877776665554454433332222211110000//./...-----ªª©©©©¨¨§¨§§§¦¦¦¦¥¦¥¥¥¤¤¤¤£££¢¢¢¢¡¡¡¡¡     ŸŸŸžŸžžžœœœ››››€€€€€€~~~~~~}}}}}}}}}|||||||||||||||{{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxujjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhdccccccccccccccccbbbbbbbaaaaaaa````X`````_________^^^^^^^]]]]]]]]]]]jžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››››››››šššœœœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••••••”””””“”“““““““““““’’’’SBBAAAAA@@@@???>>>>>>===<=<<;;;;;;:::99998888877766666555444443323222222110000///./.-----ªª©©©©©¨¨¨§§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡¡     ŸŸŸžžžžœ›œ›››Š€€€€€~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxsjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhfcccccccccccccccccccbbbbbaaaaaaaaaa`````````_______^^^^^^^^]]]]]]]]]]`žžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››››››š›šœœœœœœœœœœœœœ››››››šššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————–––––––•••••••••”••”””””””““““““““““““’’FCCBBAAA@@@@@????>>>>>>===<<<<;;;;:::99998888877777766665454444332222211111100////....---«ªª©©©©¨¨¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¡¡¡¡¡     ŸŸŸŸžžžœœœœ›’€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{zzzzzzzyzyyyyyyxxxxxxrjjjjjjjjjjijiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhfdcccccccccccccccccbbbbbbbbaaaaaaaa``````````________^^^^^^^]]]]]]]]]]’žžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ››››››››››››››šœœœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————–––––––••••••••••”•”””””””““““““““““““…CCBBBBBBAA@@@@????>>>>>=====<;;;;:::::99998888888776666655544443332222222111100/0///....-ªªªª©©©¨©©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¡¢¡¡      ŸŸŸžŸžžžžœœ›œ—€€€€€€€€€~~~~~~~}}}}}}|}||||||||||||||||{{{{{{{zzzzzzyzyyyyyyyxxxxxqjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhedddccccccccccccccccbbbbbbbabaaaaaaaa`````````_________^^^^^^]]]]]]]]]wžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ››››››››››››››œœœœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—˜—————–—––––––•••••••••••”””””””““““““““““““dCCBBBBABAAA@@@@????>>>>=====<<<;;;;;;:99999888887877666555544443333222222111010000//./..-«««ªª©ª©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¥¤¤££££¢¢¢¡¢¡¡¡     ŸŸŸŸŸžžžœœœ›š€€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{{{{{{zzzzzzzyzyyyyyyxxxxxojjjjjjjjjjjjijiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhddddddccccccccccccccccbbbbbbbbaaaaa`a`````````_________^^^^^^^]]]]]]]]ežžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ››››››››››››œœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–••••••••••””””””””““““““““““NDCCBCBBBAAAA@@@???>?>>>>>=====<<<;;;:::::999888888777666665545444333322222121100000/....-«««ªªªªª©©¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¤¤£¤£££¢¢¢¡¢¡¡      ŸŸŸŸŸžžžžœœœœ†€€€€€€€€€€€~~~~~~}}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzyyyyyyxxxxnjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhgddddddcdccccccccccccccbbbbbbbbaaaaaaaa`````````_`_______^^^^^^]]]]]]]]]žžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››››žžœœœœœœœœœœ››››››š››ššššš™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜———————––––––•••••••••••”•””””””““““““““““DDCCCCCBBAAAAA@A@?@???>>>>>=====<<<;;;;::::99988888877767666555544433323222211111000////..««««ªªª©©©©©©¨¨§§¦§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¢¡¡      Ÿ ŸŸžžžžœœœ‘€€€€€€€€€~~~~~~~}~}}}}}||}|||||||||||||||{{{{{{{zzzzzzzyyyyyyxyxxmjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhfddddddddcccccccccccccccccbbbbbbaaaaaa`````````````_______^^^^^^]^]]]]]]}žžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœ›œ›››››››››››žžœœœœœœœœœœœœ›››››š››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–—––––––––••••••••••”””””””““““““““jDDDCCCCCBBBAAAA@@@@@????>>>>=====<<;<;;::::999988888887777666555544433323222221110000///..¬««««ªªª©©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤£¤£££¢¢¢¢¡¡¡      ŸŸŸŸŸžžžœœ˜€€€€€€€€€€€~~~~~~~}}}}}}}|||||||||||||||||{{{{{{{zzzzzzyyyyyyyyxxljjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhfeedddddddcccccccccccccccbcbbbbbbbaaaaaaaa````````_``______^^^^^^^^]]]]]fžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››››››žžžœœœœœœœœœœ››œ››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜————————–––––––••••••••••••”””””””“““““““ODDDDDCCCBBBBAAAA@A@?????>>>>>====<<<<;;;;;:::9999888887777666665554443333222222111100/////¬«««««ª«ªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¥¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡     ŸŸŸŸžžžœœ€€€€€€€€€€~~~~~~~}}}}}}}||||||||||||||||{{{{{{{{{zzzzzyyyyyyyykjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhheeeeddddddcccccccccccccccccbcbbbbabaaaaaa````````X```______^_^^^^^^^]]]]]œžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››žžžžœœœœœœœœœœœ››››››ššššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜——————–––––––––•••••••••••””””””””““““‘EDDDDDCCCCBCBBAAAAA@@@???>?>>>>>===<<<<;;;;:::::998888877776666655554444333222221111000/0//¬¬¬¬«««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžŽ€€€€€€€€€€~~~~~~~~}}}}}}}|||||||||||||||{|{{{{{{zzzzzzzyyyyyyykjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihihgeeededddddddccccccccccccccccbcbbbbbbbaaaaaaa``````````_______^^^^^^^^]]]]xžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœ››››››››››žžžžžœœœœœœœœœœœ›œ››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––—–––––•••••••••••””””””””“““fEDEDDDDDCCCBBBBBAAA@@@@???>?>>>>====<<<<;;;:::::99888888877777666555544443333222221111100//¬¬¬¬«««ªªªªª©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£¤£££¢¢¢¡¡¡      ŸŸŸŸŸžžž˜€€€€€€€€€€€~~~~~~~~~}}}}}}}|||||||||||||||{{{{{{{zzzzzzzyyyyyyykkjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihfeeeeeeddddddddcccccccccccccccbcbbbbbaaaaaaaa```````````________^^^^^^]]]]cžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ›››››››žžžžžžœœœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–––––––••••••••••••””””””““““KFEEEDDDDDDCCCBBBAAAA@A@@@????>>>>>===<<<<;;;;;;:::99888888777666665555544433332222111100000¬¬¬¬¬¬««ªªªªª©©©©¨¨¨¨§§¦¦¦¦¦¦¦¦¥¥¥¤¤¤¤££££¢¢¡¡¢¡¡      ŸŸŸžžžžžœ€€€€€€€€€€€€€~~~~~}~}}}}}}|}|||||||||||||||||{{{{{{zzzzzzyzyyykkkjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifeeeeeeddddddddccccccccccccccccbbbbbbbbbaaaaa`a``````````_______^^^^^^^]]]]‹žžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››žžžžžžœœœœœœœœœœ›œ››››››››šššš™šš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––••••••••••••””””””””}FFEEEEDDDDCCCCCBBABAA@A@@@????>>>>>====<<<<;;;;:;:999988888887776666554554433333222222100000¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨§§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤££¢£¢¢¢¡¡¡¡      ŸŸŸŸžžž‘€€€€€€€€€€€€~~~~~~~~}}}}}}}|||||||||||||||{{{{{{{{{zzzzzzyzyykkkkjjjjjjjjjjjjjjjihgeca_^]\ZZZZZ[\^_abdeghiiiiiiigfeeeeeeeddddddddcccccccccccccccccbbbbbabaaaaaaa``````````________^^^^^^^]]]kžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››žŸžžžžžžœœœœœœœœœœ›œ››››››ššššššššš™™™™™™™™™™™™™™™˜™˜˜˜˜˜———————–––––––––••••••••••””””””“WFFFEEEDDDDDDCCCBBBBBAAAAA@@?????>>>>====<<<<;;:;:::::998888888776766655554444333322221211000­¬¬¬¬¬¬¬««ªªªª©ª©©©¨¨¨§¨§§§¦¦¦¦¥¥¥¥¤¤¤£¤££££¢¢¢¢¡¡       ŸŸŸžžžžž›€€€€€€€€€€~~~~~~~~~}}}}}}||||||||||||||||{|{{{{{{zzzzzzzyylkkkkjjjjjjjjihda][[[[ZZZZZZZZZZZZZZZZZZZZZZZ\`cfhifffeeeeeeeedddddddccccccccccccccccccbbbbbaaaaaaa`a``````````______^_^^^^^^^]^œžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ››œŸžžžžžžžžœœœœœœœœœœœœ››››››š›ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””’HFFFFEEEDDDDDCDCCCCBBBAAA@A@?@??>>>>>>>>===<<<;;;;::::999898888777667655554444333222222211110­­¬­¬¬¬¬¬¬««ªªªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¡¡¡       ŸŸžŸŸžž‰€€€€€€€€€€€€€~~~~~}}}}}}}}}}||||||||||||||{|{{{{{{z{zzzzyzymkkkjkjjjiea[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZSR\cfeeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaaaa``````````_`______^^^^^^]^]sžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›œ›œŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––••••••••••””””aGFFFFFEEEEDDDDCCCCCBBABBAA@@@@@????>>>>>>==<<<;;;;::::999998888877776665655544343332222221111®­­¬¬¬¬¬¬««««ªªªªª©©¨¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¤¤£¤££¢£¢¢¢¡¡¡¡     ŸŸŸŸžžžž˜€€€€€€€€€€~~~~~}}}}}}}|}||||||||||||||||{{{{{{zzzzzzzyokkkkjfa[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZHDDDDQ\ceeeeeddddddddccccccccccccccccbbbbbbbaaaaaaaa``````````________^^^^^^^]_žžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›œœŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••”•””IGFGFFFFFEEEDDDDDCCCCBBBAAAA@@@@?????>>>>=>=<=<<<<;;;;:::9989888888876666555544443332222222211®®­­­­¬¬¬¬¬¬««ªªªªª©©©¨¨¨§¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡¡     ŸŸŸŸžžž„‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||||||||||{{{{{{{z{zzzzzpkie^[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZWDDDDDDDDJYbeeeedddddddcccccccccccccccbbcbbbbbaaaaaaa`aa``````````______^^^^^^^^xžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœœŸŸŸŸŸžžžžžžžžœœœœœœœœœ››››››››››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–•••••••”••gHHHGFFFFEEEEDEDDDDDCCBBBBBAAAAA@@?@???>>>>====<=<<;;;;:;:9999988888777676666554444333322222211®­­­¬­¬¬¬¬¬«««««ªªª©ª©©¨¨¨¨§§§§¦¦¦¦¥¦¥¥¤¤¤¤£¤£££¢¢¢¢¡¡¡¡    ŸŸŸŸŸžž–€€€€€€€€€€€~~~~~~~}}}}}}||||||||||||||||{{{{{{{{{zzzxk][[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZJDDDDDDDDDDDIZceeeddddddcccccccccccccccbcbbbbbaaaaaaaa````````````_______^^^^^^^`žžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœŸŸŸŸŸŸŸžžžžžžœœœœœœœœœœœ››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••KHHHGGFGFFFFEEEDDDDDCCCCCBBAAAAA@@?@???>>>>>>===<<<;;;;;;::::9989888877777666654454443332222221®®®­®­­¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££££¢¡¢¡¡¡     ŸŸŸŸŸžž‚‚‚€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||||||||||{|{{{{{zzuia^[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYEDDDDDDDDDDDDDDO_dddddddddcccccccccccccccbcbbbbbabaaaaa`````````````_______^^^^^^xžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••••••••••hHHHHGGGGFFFEFEEEDDDDDCDCCBBBBAAAAA@@@????>>>>>===<<<<;;;;:;::9999988887777766655555444333322222®®®®®­­­­¬¬¬¬«««««ªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤£££¢¢¢¢¢¢¢¡ ¡     ŸŸŸžž–‚‚€€€€€€€€€€~~~~~~~~}}}}}}||||||||||||||||{{{{{yqbbaa_[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZJDDDDDDDDDDDDDDDDDDYcdddddddccccccccccccccccbbbbbbbbaaaaa```````````_`_______^^^^^`žžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœžŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››š›šššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––•••••••KHHHHHHHGFFFFFFEEDDDDDDCCCCBBBAAAAA@@@?????>>>>>====<<<<;;;:::::99998888877766655555544333323222¯®®®®®­­­¬¬¬¬¬¬««««ªªªªª©©©¨¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¢¡¡     ŸŸŸŸžž…‚‚€€€€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{ynbbbaaa`[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZVDDDDDDDDDDDDDDDDDDDDCTadddddccccccccccccccccbbbbbbbbbaaaaaa`a`````````________^^^^^sžžžžžžŸŸ ¡£¤¦§¨©©ªªªª©©¨¦¥£¢¡ŸžœœœœœœœœœœœœœžŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––••••••cIIIIHHHHGGGGFFFEEEEEDDDDCCCCBBBAAAAA@@?@??>?>>>>>====<<<;;;;:::::9998888888776766665444433333322¯¯¯®®®®­­­­¬¬¬¬«¬«««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¢¢¡       ŸŸŸŸ™‚‚€€€€€€€€€€€~~~~~~~~}}}}}}|||||||||||||||ylbbbbbbba`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZHDDDDDDDDDDDDDDDDDDDDDCDQadddcccccccccccccccccbbbbbbbbaaaaaaa``````````_______^_^^^^_𡣦©ªªªªªªªªªªªªªªªªªªªªªªªªªªªª©¥¡ŸžœœœœœœœœœœœœœŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœœ››››››š›ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–—––––•••••JJIIIHHHHGHGGGFFEEEEDDDDDDDCCBBBBBBAAAA@@@????>>>>>====<<<<;;;;;:::999988887777666565554444332322¯¯¯¯¯®®­®­­­¬¬¬¬¬««««ªª©ª©©©¨¨¨¨§§§§§¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¢¡¡¡       ŸŸž‹‚‚‚‚€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||||||ymbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZSDDDDDDDDDDDDDDDDDDDDDDCCCCQadddcccccccccccccccccbbbbbbabaaaaaa````````````______`djsz…«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª§¢Ÿœœœœœœœœœœ ŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœ›œ›œ›››››››ššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——˜—————–––––––•–•ZJJJIIIIHHHGHGGFFFFEEEEEDDDDCCCCBBBBAAAAA@@@????>>>>=>===<<<;;;;;::::99988888887766666554544333332°°¯¯®®®®®­­­­¬¬¬¬«««««ªªªª©©©©¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¢¢¡¡      ŸŸŸœ‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}|||||||||{obbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZGEDDDDDDDDDDDDDDDDDDDDDDDDDCCTcdcccccccccccccccccbcbbbbbbbaaaaa`a`````````_`_`dlwzzzzzzšªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¨£Ÿœœœœœœ ŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––{JJJJIIIIHIHGGGGGGFFEEEEEDDDDDDCCCBBBBAAA@@@@@?????>>>>>==<=<<<;;:;;:::9998888877776666655554434333°°¯¯®®®®­­®­­­¬¬¬¬«««««ªª©ª©©©©¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¤¤¤£¤£££¢¢¢¡¡¡¡¡     ŸŸŸ”‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}||||||sbbbbbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZMEEDEDDDDDDDDDDDDDDDDDDDDDDCDCCCZddcccccccccccccccccbbbbbbabaaaaa`aa```````bhr{zzzzzzzzz~««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¥ œœŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžœœœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––PKJJJJJIIIHHIGHGGGGFFFEEEEDDDDDDCCCBBBBBBAA@@@@@???>>>>>>==<<<<<;;;;::::999988888877666565544543433°°°°¯¯¯¯®®®­­­­¬¬¬¬¬«««««ªªª©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤££¢£¢¢¡¡¡¡¡    ŸŸŸž‡‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}|||xfbbbbbbbbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZVEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCH_dddccccccccccccccccbbbbbbaaaaaaaaa```aiu{{z{zzzzzzzzzz‹«««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª§¡¡ŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜————————––––fKJKJJJJJJIIHHHGGGGGGFFEFEEDEDDDDCCCCBBBBAB@@@@@@????>>>>>=====<<<;;;;::::98998888877766665555444443±°°¯¯¯¯®¯®®®­¬­­¬¬¬¬««««ª«ªªª©©¨¨¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¤¤¤¤¤£££¢£¡¢¡¡ ¡    ŸŸŸœ‚‚‚‚‚€€€€€€€€€€€€~~~~~~}}}}}}}||obbbbbbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZGEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCSccccccccccccccccccbbbbbbbaaaaaa``afr{{{{{{{{zzzzzzzzzz ««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª««¨£ŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››š›šššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––…LLKKKJJJJJJIIIHHHHGGGFFFFFEEEEDDDDCCCCBCBABAAAA@@?@???>>>>>>===<<<<<;:;;:::9998888888777766555544443°°°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨§§¦§¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¡¡¡¡      ŸŸ•‚‚‚‚‚€€€€€€€€€€€~~~~~~~}}}}}wbbbbbbbbbbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZLEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDDDDCCCCC\dcccccccccccccccccbbbbbbaaaaacn{{{{{{{{{{{{zzzzzzzzz«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«««««¥¡ŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜————————–—SLLKKKJJJJJJJIIIIHHHHGGFFFFEEEEEDDDDDCCCCBBBBAAA@@@@@????>>>>>===<=<;;;;::::9:99888888777766665554544±°°°°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««ª«ªªª©©©©¨¨¨§§§¦§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¢¡¡      ŸŸŒ‚‚‚‚‚€€€€€€€€€~~~~~~~}}}}ocbbbbbbbbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZSEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDDDCDCCCCRcccccccccccccccccbcbbbbbbbfs{{{{{{{{{{{{{{zzzzzzzzzzŠ««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«««««««¨¢ŸŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ›››››››››ššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜—————dLLLKKKKKJJJJJIJIIIHHHGHGGGFEFFEDDDDDDDDCCBCBAAAAA@@@@@???>>>>>>===<<<<;;:;::::99998888877766666554454±±±±°°°¯°®®¯®®®®­­¬¬¬¬¬«««««ªªªª©©©¨¨¨¨¨§§¦§¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¢¡ ¡     ŸŸ‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}zdcccccbbbbbbbbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZYFEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCE_cccccccccccccccccbbbbbhy{{{{{{{{{{{{{{{{{zzzzzzzzzz™««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬««««««««ª¢ŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœœœœœ›››››ššššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————{MLMMLLLKKKKJJJJIIIIHHHGHHGGFFFFEEEEDDDDDCCCCBBBABA@@A@@????>>>>>=====<<<;;;:::9::998888887777666555444²±±°°°°°¯¯®¯¯®®®­­­¬¬¬¬¬¬«««ªªªª©©©©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤¤£¢£¢¢¢¡¡¡¡¡    Ÿœ‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~tcccccccbbbbbbbbbbbbbbbbbb\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZHEEEEEEEEEEEDDEDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCYcccccccccccccccccbbi{||||{{{{{{{{{{{{{{{{zzzzzzzzz|«««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª««««««««««««£ŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———–QNMMLMLLKKKJJJJJJIIIIHHHGGGGFFFFFEEDDDDDDDCCCBBBBAAA@@@@@@???>>>>>=>=<<<<<<;;;;:::999888887777666665555²±²±±±±°°°¯¯¯®®®®­­­¬¬¬¬¬«««««ªªª©©©©¨©¨¨¨¨§§§¦¦¦¦¥¥¥¥¥¤¥¤¤££¢£¢¢¢¡¡¡¡     Ÿ™‚‚‚‚‚‚€€€€€€€€€€€~~~~mccccccccccbbbbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[KFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCPcccccccccccccccci{|||||{{{{{{{{{{{{{{{{{{zzzzzzzzz‚«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬«««««««««««««£ŸŸŸŸŸŸŸžŸžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——YNMNMLMMLLLKKKJJJJJIJIIHIHHGGGFFFFEEEDEDDDDCCCCCCBBBAAA@@@????>>>>>>====<<<<<;;:::::99998888787777666554²²²±±±°°°°¯¯¯®®®®­­­­­¬¬¬¬¬««««ª«ªª©©¨©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤¤££££¢¢¡¡¡      Ÿ•‚‚‚‚‚‚€€€€€€€€€€€~|fccccccccccccbbbbbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[MFFEEEEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDCCDCCCCCCG`cccccccccccchy|||||||{|{{{{{{{{{{{{{{{{{{zzzzzzzz‰««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬«««««««««««««ª¢ŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ›››››››››ššššš™š™™™™™™™™™™™™™™™˜™˜˜˜˜˜dNNMNMMMLLLLKKKKKJJJJIIIIHHHHHGGFGFEFEEEEDDDDDCCCCBBBAAAAA@@@?????>>>>====<<<<;;;;;:::9999888888777766555²²²²±±±°°°°°¯¯®®®®®­­­­¬¬¬¬¬¬««««ªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡     ‘‚‚‚‚‚‚‚€€€€€€€€€€€€€yccccccccccccccbbbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[OFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCC]cccccccccft|||||||||||||{{{{{{{{{{{{{{{{zzzzzzzzz’««««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬«¬««««««««««««««§ ŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜rONONNMNMLMLLLKKKKKJJJJIJIIHHHHHGFFFFFFEEEEDDDDDCCCCBBBAAAA@@@@@????>>>>=====<<<<<;;;:::999888888787776666²²²²²±±±±°°°¯¯¯¯¯®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¥¤¤££££¢¢¢¢¢¡¡¡      ‚‚‚‚‚€€€€€€€€€€€wddccccccccccccccbbbbbbbbbbbbbbb\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCZccccccdo}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzz›«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬««««««««««««««««¥ŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™˜˜˜€OOONNNNNMMMMLLKKKKKKJJJJJIIIHHHHGGGGFFFFEEEDDDDDCCCCCBBBBBAA@A@@????>>>>>=====<<<;;;;::::99998888877776665²²²²²±±±±°±°°°¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªª©©©©©©¨¨¨¨§¦§¦¦¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¢¡¡¡     Ÿ‚‚‚‚‚‚‚€€€€€€€€€€€€sddddcccccccccccccccbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCVcccci}}}|||||||||||||||{{{{{{{{{{{{{{{{zzzzzzzzz{¢«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬«««««««««««««««««¢ŸŸŸŸŸžžžžžžžžžœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™ŒROOOOONNNNMMMMLLLKKKKJJJJJIIIIHHHHHGGGGFFFEEEEDDDDDCCCCCBABAA@@@@@@????>>>>==>=<<<<<;;::::::999888887777666³³²²²²²²±±±°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢£¢¢¡¡¡¡      ‚‚‚‚‚‚‚‚€€€€€€€€€€€€qdddddcccccccccccccccbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCSces}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzz}©««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸŸžžžžžžžžœœœœœœœœ›œœ›››››››šššššš™™™™™™™™™™™™™™™–UPPOOOOONNNMMMLMLLLKKKKKJJJJIIIIHHHHHHGGFFFFFEEEDDDDDCCCCBBBBAAA@@@@@????>>>>>>==<=<<<;;;;::9:99988888777676³³²²²²²²±±±±°°¯¯¯¯¯®®®®®­­­¬¬¬¬¬¬«««ªªª©©©©¨©©¨¨¨§§§¦¦¦¦¦¦¥¥¤¤¤¤¤££¢¢£¢¡¡¡¡¡¡     ’‚‚‚‚‚€€€€€€€pdddddddcccccccccccccccccbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[OFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCU|}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{z{zzzzzz«««««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬¬««««««««««««««««««¢ŸŸŸŸžžžžžžžžœœœœœœœœœœœ›››››››šššššš™š™™™™™™™™™™™™™XPPPPPOOONNNNNMMMMLLKKKKJKJJJJIIIIHHHHGGGGFFFEEEEDEDDDDDCCBCBBBAAAA@@@@@???>>>>====<<<<<;;;;:::99989888877776³³³³²²²²±²±±±°°°¯°¯®®®®®­­­­¬¬¬¬¬¬¬««ªªªªª©©©¨¨¨§¨§¦¦¦¦¦¦¥¦¥¥¥¥¤¤£¤£££¢¢¢¢¡¡¡¡     •‚‚‚‚‚‚€€€€€oddddddddddccccccccccccccbbbbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[[[[[[MFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCDJNa|}}}}}}}|}||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzz€«««««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššš™™™™™™™™™™™™YQQPPPPPPOONONNNMMMLLLLLKKKKJJJJIJIIHHHHGGFFFFFFEEEEDDDDDDCCCBBBABAAA@@@@???>>>>>>===<=<<<;;;;:::9998988887777´³³³²²²²²²±±±°°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬¬««ªªªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¢¢¡¡¡¡    Ÿ˜‚‚‚‚‚‚‚€€€€odddddddddddddcccccccccccccbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[KFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDCCCENNNNa|}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{zzzzzzzz€«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ª¡ŸŸŸžžžžžžžœœœœœœœœœœ›››››››››šššššš™™™™™™™™™™[QQQQPPPPPPOONNNNNMMMLLLLKKKKKJJJJJJIIHHHGGGGFFFFFEEEEEDDDDDCCBCBBBBAAAA@@@????>>>>=====<<;;;;;;:::999998888777´´³³³³²²²²²±±±±°°¯¯¯¯¯®®®®®­­­­¬¬¬¬¬«««ªªªª©ª©©©¨¨§§§§¦§¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¡¡¡¡ ¡     ›ƒ‚‚‚‚‚‚€€€pdddddddddddddcdcccccccccccccbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[ZJFGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCGNNNNNNb}}}}}}}}|}||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzz€«««««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªª­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««£ŸŸŸžžžžžžžžœœœœœœœœ›œ›››››››ššššššš™™™™™™™ZRRQQQPQPPPPOOONNNMNMNLMLLLLKKKKJJJJIIIIIIIHHHGGGFFFEEEEDDDDDDCCCBBBBAAA@A@@@????>>>>=>===<<<;;;::::::9989888887´´³³³³³²²²²±±±±°°°¯°°¯¯¯®®®®­­­­¬¬¬¬«««««ªª©©©©©¨¨¨§¨§§¦§¦¦¦¦¦¥¥¤¤¤¤¤¤££¢¢¢¢¡¢¡¡¡     ž‚‚‚‚‚rdddddddddddddddccccccccccccccccbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[SGGGFGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDJNNNNNNNNe}}}}}}}}|||||||||||||||||{{{{{{{{{{{{{{{zzzzzzzz©««««ª«ªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸŸžžžžžžžžœœœœœœœœœœœ›››››››ššššššš™š™™™—YRRRRQQQQPPPPPPOONNNNNNNMMLLLLKKKKJJJJIIIIIIHHGGGGGFFEFEEEDDDDDDCCCBBBBAA@@A@@@????>>>>>====<<<;<;;;::9:998888887µµ´´³³³³²²²²²±±±±±°°¯¯¯¯¯®®®®­­¬­¬¬¬¬«««««ªªªª©©©¨¨¨¨¨§¦¦§¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡      Ÿ–‚‚‚‚‚‚udddddddddddddddddccccccccccccccccbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[NGGGGGGFFFFFFFFFFFFFFFFFFEFEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDEMNNNNNNNNNNj}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzz}¢««««««ªªªªªªªªªªªªªªªªªªªªªªª¬­¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««© ŸŸžžžžžžžœœœœœœœœœ››››››››šššššššš™™ŽXSRRRRRQQQQPPPPPOOOONNNNMMMMMLLKKKKJJJJJJIIIIIHGHGGGFGFFFEEEEDDDDCCCCCBBABAA@@@@@@???>>>>=>==<<<<;;;;::::::9998888´µ´´´³³³³³²²²²²±±°±°°°°¯®¯®®®®­­­¬¬¬¬¬«««««ª«ª©©©¨©©¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£¢£¢¢¢¡¡¡¡      Ÿœ‡‚‚‚‚‚xeedddddddddddddddddddcccccccccccccbbbbbbbbb[[[[[[[[[[[[[[[[[[[[WIGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDFNNNNNNNNNNNNNn}}}}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzz{›«««ª««ªªªªªªªªªªªªªªªªªªªªªª­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¡ŸŸžžžžžžžžœœœœœœœœœœ›››››››šššššššƒVSSSRRRRRRQQQPPPPPOOOONNNNMMMLLLLKLKKKJJJJIIIIHHHHGGGGGFFFEEEEEDDDDDCCCBBBABAAA@@@@?????>>>======<<<;;;:::::9998888µµ´´´´´³³³³²²²²²±±±°°°¯¯¯¯¯®®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡      Ÿž”‚‚‚‚{eeeeddddddddddddddddddcccccccccccccbcbbbbbbb^[[[[[[[[[[[[[[[[[[NGGGGGGGGGGGGFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEDEDDDDDDDDDDDDDDDGONNNNNNNNNNNNNNs}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{z{zzzzzzz’««««««ªªªªªªªªªªªªªªªªªªªª«­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¢ŸŸŸžžžžžžœœœœœœœœœœœ››››››››šššššvTTTTSSSRRRRQQQPPPPPPPOOONNNNMMMMLLLLKKKKJJJJJIIIHHHHHGGGGGFFEEEEDDDDDCCCCCBBAAAAA@@@@????>>>>>===<<<<<;;;;::9:999888µµ´´´´´³³³³²²²²²²±±±°°°°¯¯¯®®®®®®­­¬¬¬¬¬¬«««ªªªªª©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤£¤£££¢¢¡¡¡¡      ŸŸœ‹‚‚eeeeeeddddddddddddddddddccccccccccccccbcbbbbba[[[[[[[[[[[[[[[[TIGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEEDDDDDDDDDDDDDDIOONNNNNNNNNNNNNNNx}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzzz‰«««««ªªªªªªªªªªªªªªªªªªªª­­­­¬­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««£ŸŸžžžžžžžžœœœœœœœœœœœ›››››››šššjTUTTTTTSSRRRQRQQQPPPPOOOOOONNMMNMMLLLLKKKJJJJJJIIIHHHHHHGFGFFEEEEEEDDDDDCCCBBBABAAA@@@@????>>>>>>==<<<<;;;;:;::::9998¶¶µ´´´´´³³³³³²²²²±²±°°°°°¯¯¯®¯®®®­­­­¬¬¬¬¬¬«««ªªªª©©¨©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£¤££¢¢¢¡¡¡       ŸŸŸ™…€geeeeeededddddddddddddddddccccccccccccccbbbbbbb[[[[[[[[[[[[[[YLHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDIOOOONNNNNNNNNNNNNNS{}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{{zzzzzzzz‚««««««ªªªªªªªªªªªªªªªªªª­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¥ŸŸŸŸžžžžžžœœœœœœœœœœœ››››››››š_UUUTTUTSSSSRRRRRQQPQPPPPPPOOONNNMNMMMLLLLKKKJJJJJIJIIHHHHGGFFFFFEEEEEDDDDDCCCCBBBAAA@@@@@@????>>>>====<<<<<;;;::::9898¶¶µµµ´´´´³³³³²²²²²²±±°°°°°¯¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªª©©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡¡¡    ŸŸŸŸteeeeeeeeeedddddddddddddddddcccccccccccccccbbbbb`[[[[[[[[[[[ZMHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDEJOOOOOOONNNNNNNNNNNNN`}}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzz|™««««ª«ªªªªªªªªªªªªªªª¬­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¦ŸŸŸžžžžžžžœœœœœœœœœœœ›››››XVVUUUUTTTSTSRSRRRQQQQPPPPPPOONNNNNNMMMLLLLKKKKKJJJIIIIHIHHHGGGGFFFEEEEDDDDDDDCBCBBBBBA@@@@@@???>>>>======<<<;;:;:::9999¶¶µµµ´´´´´´³³³²²²²²±±±°°°°¯°¯®®¯®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨¨¨¨¨§§§§¦¦¦¦¦¥¦¥¥¥¤¤¤¤££¢¢¢¢¡¢¡¡       Ÿ‡]^`deeeeeeeddddddddddddddddddcdccccccccccccbbbbbb[[[[[[[[[YMHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDEKOOOOOOOONNNNNNNNNNNNNNl}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{zzzzzzzzzŠ«««ª«ªªªªªªªªªªªªªªª­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¦ŸŸŸžžžžžžžœœœœœœœœœ›››››mVVVVVUUUUUTTSSSSSRRQQQQPQPPPPOOOONNNNNMMMLLLKLKKJJJJJJIIIIIHHHGGFFFFFFEEEEDDDDCCCCCBBBBAAA@@?@???>?>>>>>==<==<<;;;;;::::9¶¶¶µµµ´´´´´³³³²³²²²²±±±°±°°°¯¯¯®®®®®®­­­¬¬¬¬«¬«««ªªªª©©©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤££££¢¢¢¢¡¡¡      •]]]]]_ceeeeeedddddddddddddddddddccccccccccccbcbbbb^[[[[[[ULHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDEKOOOOOOOOOOONNNNNNNNNNNNNv}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzz «««ªªªªªªªªªªªªªª¬­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««§ŸŸŸžžžžžžžœœœœœœœœœœ›‹^VWVVVVVVUUUTTTTSSRSRRRRQQQPPPPPPOONONNMMNMMLLLLLKKKJJJJJIIIHHHHHGGFGFFFFEEEEDDDDCCCCBBBBBAA@A@@@@??>>>>>>====<<<;;;:;:::::·¶¶¶¶µµ´µ´´´´³³²²²²²²²±±±°±°°¯¯¯¯®¯®­®­­¬¬¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¢¡¡¡   ža^]]]]]]^`eeeeedddddddddddddddddddcccccccccccccbbbbb[[[[PIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDEKOOOOOOOOOOOOOONNNNNNNNNNNQ{}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzzzz‹«««ªªªªªªªªªªªªª­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸžžžžžžžžœœœœœœœœpWWWVVVVVVVUUUTTTTSSSRSSRQRQQQPPPPPOOOONNNNMNMMLLKLLKKKJJJJJIIIHHHHGHGGFFFFFEEEEDDDDDCCCBBBBAAAAA@@?@????>>>>====<<<<<;;;;::9··¶¶¶¶µµµ´´´´³³³³³²²²²²±±±°°°°°¯¯®®¯®­­­­¬¬¬¬¬¬«««««ªª©©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¤¥¤¤£££¢¢¢¢¡¢¡¡¡  }^^^^]]]]]]]^`eeedddddddddddddddddcdcccccccccccccbbbb^QKHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEDDJOOOOOOOOOOOOOOONNNNNNNNNNNNd}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzz~š«««ªªªªªªªªªª«­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸŸžžžžžžœœœœœ„]XXXXWWWVVVVVVUUTTTTTTSSRSRRQQQQQPPPPPOONONNNNMMMMMLLKKKKJJJJJIIIIHHHHGGGGFFFFEEEDDDDDDCCCCBBBAAA@@@@@????>>>>>====<<<<<;;;:::··¶·¶¶¶µµ´µ´´´³³³³³²²²²²±±±°°°¯¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªª©ªª©¨¨¨¨¨¨§§¦§¦¦¦¦¥¥¥¥¥¤¤¤££££££¢¢¢¡¡¡’^^^^^^^]]]]]]]]^`bdddddddddddddddddccccccccccccccca\YWHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEJOOOOOOOOOOOOOOOOOONNNNNNNNNNNs}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{zz{zzzzzzzzz…§««ªªªªªªªªª­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««¥ŸŸžžžžžžžžœœœ—gYXYXXXXWWWVVVVVVUUUUTTTTSSRRRRRQQQPPPPPPOOONNNNNMMLLMLKLKKKJJJJIIIIIHHHHHGGFFFFFEEEDDDDDDCCCCBBBAAAA@@@@@???>>>>>====<=<<<;;;::·····¶¶¶µµµ´´´³´³³³²²²²²±±±±±°°°°¯¯®¯®®®®­­­¬¬¬¬¬««««ªªª©©ª©¨¨¨¨¨§§§¦¦¦¦¦¦¥¦¥¤¤¤¤¤£££££¢¢¡¡Ÿ__^^^^^^]]]]]]]]]]]]^_adddddddddddddddccccccccb_\ZYYYYXMHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEIOOOOOOOOOOOOOOOOOOONONNNNNNNNNN{}}}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzzzzŠªªªªªªªªª«­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««£ŸŸŸžžžžžžœœpZZYYYYYXWWWWWVVVVVVUVTTUTTTSSSSRQRQQPQPPPPOOONNNNMMMMMLLKLKKKKKJJJJIIIHHHHHGGGGGFEEEEEDDDDDDDCCCBBBBBA@@@@@????>>>>>>===<<<<;;:::·¸··¶¶¶¶µµµµ´´´´³´³³³²²²²±²±±±°°°°¯¯¯¯®®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¡¢~_^^^^^^^^^]]]]]]]]]]]\]\\\]^`abbcddcccba`^]\[ZYYYYYYYYXVHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEFEEEEEEEEHOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNd}}}}}}}}}}}}}}|||||||||||{|{|{{{{{{{{{{{{{{{{{zzzzzzzzzz{Žªªªªªªª­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¢ŸŸžžžžžžžžv\ZZZZYYXYXWXWWWVVVVVVVUUTTTTSSSRSSRRRQQQQPPPPPOOONNNNNMMLMLLLLKKKJJJJJIIIIHHHGGGGFFFFEFEEEDDDDDCCCBCBBAAAAA@@@????>>>>>======<;;<;:¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±°°°°¯¯¯¯®®®®­­­¬¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤£££££¢¢¢–____^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZZZYYYYYYYYOHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEGOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNu}}}}}}}}}}}}}||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzzzzzzz{Ž«ªªª¬­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¡ŸŸŸžžžžžžžžv^[[ZZZZZYYYXXXXWWWWVVVVVVUUUTTTTSSSSRRRRQQQPPPPPOOOOONNNNMMMLLLLLKKKJJJJJIIIIHHHGGGGGFFFFEEEEDDDDDCCCCBBBBBAA@@@@@???>>>>>>===<=<<;;;¸¸¸···¶·¶¶µµµµµ´´´³´³³³²²²²²±±±±°°°°¯¯®®¯®®®­­­¬¬¬¬¬¬««««ªª©©©©©©©¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¡h______^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYYYYYWHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEGOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNU|}}}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzzzzzzzz{Š©ª­­­­­­­­­­­­­­­­­­­­¬­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««« ŸŸžžžžžžžœr^\\[[[Z[ZZYYYYYXXXXWWWVVVVVUUUUTTTSSSRSRRRRRQQQPPPPPOOOONNNNMMMMLLLLKKJJJJJJIIIIHHHHGGFGGFFEEEEDDDDDDCCCCCBBBAAA@@@@@????>>>>>===<<<<<;¸¸¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±°°°¯¯¯¯®¯®®®­­­­¬¬¬¬¬«««««ª©©©©©©¨¨§§§§§¦¦¦¦¥¥¥¥¥¤¥¤¤£££Œ________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYYZYYYYYQHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEENOOOOOOOOOOOOOOOOOOOOOOOOONONNNNNNNNNm}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzz‡ ­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬«¬««««««««««««««««««©ŸŸŸŸžžž‹k]\\\\\[[[Z[ZZYYYXYXXXWWWVWVVVVUUUUTTTTTSSRRRRRQQQQPPPPOOOOONNNNMMMMLLLLKKKJJJJJIIIIHHHHGGGGFFFFFEEDDDDDDCCCCBBBBAAA@@@@????>>>>>====<<<<;¸¸¸¸¸···¶¶¶¶¶µµµµ´´´³³³³³²²²²²±±±°±°°°¯¯¯®®®®­­­­¬¬¬¬¬¬««ªªªª©ª©©©©©¨§§§¦¦¦¦¦¦¦¥¥¥¥¤¤£¤£ _________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZYYYYYXHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEKOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNN{}}}}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{z{zzzzzzzzzzzzz€€„“¬­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸœxc]]]\\\\\\\[[[ZZZZZYYYXWXWWWWWVVVVUUUUTTTSSSSSSRRRRQQQPPPPPOOOONNMNMMMLMLLLKKJJJJJJJIIIHHHHHGGFFFFEFEEEEDDDCCCCCBBBABAAAA@@????>>>>>>====<<<¹¸¸¸¸¸····¶¶¶µµµ´µ´´´´´³³²²²²²²±±±°°°°¯¯¯®®¯®®­­­­­¬¬¬¬¬««««ªªª©©©¨¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤£‚___________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZYYYYYYSHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFIOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNf}}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{zzzzzzzzzzzzzz~€€€€‡–­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««£~h^^^]]]]]\\\\[\[[[ZZYZZYYYXXWXWWWWVVVVVUUUTTTTTSSSSRRRQQPQPPPPPPOONONNNMMMLLLLLKKJJJJJJJIIIIHHHHGGGFFFFEEEEDDDDDCCCBBBBBAAA@@@?????>>>>>>===<=<¹¹¸¸¸¸¸¸¸··¶¶¶¶µµµ´´´³´³³³²²²²²²±±±°±°°°¯¯¯¯¯®®®­­¬¬¬¬¬¬««««ªªªª©©©¨©¨¨§§§§¦§¦¦¦¦¥¥¥¥¤¤ž`___________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[\\[[[[[ZZZZZZZZYYYYYKHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFGOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONONNNNNNNy}}}}}}}}}}}}}|}}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzzz{€€€€†’¥­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««£{d__^^^^]]]]\\\\\[[[[[ZYZYYYXYXXWWWVVVVVVUUUUUTTTSSSSRRRRQQQQPPPPPOOOONNNMNMMLLLLKKKKJJJJJIIIIHHHGHGGGGFFFEEEEDDDDDCCCBBBBAAAA@@@@@???>?>>>====<<¹¹¹¹¸¸¸¸····¶¶¶µµµµ´´´´´´³³²³²²²±±±±°°°°¯¯¯¯®®®®­­­­­¬¬¬¬¬«««ªªªª©ª©©©©¨§¨§§§§¦¦¦¦¥¥¥¥¥|```__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[\[[[[ZZZZZZZZYYYYYWIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGFGFFFFFFFFFFFFFFOOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNb}}}}}}}}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzz€€€€€€€€‚‰–§­­­­­­­­­­­­¬­¬¬¬¬¬¬¬¬¬¬¬¬«¬««««««««¤“…}{{{{z`__^^^^]]]\\\\\\\[[[[ZZZZYYYXXXWWWWVVVVVVUUUUTTSTSSSSRRRRQQQPPPPPOOOONNNMMMMMLLLLKKKKJJJJJIIIHHHHHGGFFFFFFEEEDDDDDCCCCCBBAAAAA@@@?????>>>>=====¹¹¹¹¹¸¸¸¸····¶¶¶¶µµµ´´´´´³³²³³²²²²±±±±°°°°¯¯¯®®®®®­­­­¬¬¬¬¬«««ªªªª©ª©©¨©¨¨¨§§§¦¦¦¦¦¥¥¥ž````___________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYYQIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFKPPOPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNx}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzz~€€€€€€€€€€…˜¤­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««£–‹‚}{{{{{{{{{o__^^^^]^]\]\\\\\\[[[[ZZYYYYYXXXWWWWWVVVVUUUUTTTTSTSRSRRQRQQQQPPPPOOONNONMNMMMLLLKKKKJJJJJJIIIHIHHHGGFFFFFFEEDDDDDDCCCCBBBAAAA@@@@@????>>>>=>==º¹¹¹¹¹¸¸¸¸¸···¶¶¶¶µµµ´´µ³´´³³³²²²²²±±±±°°°¯¯¯®®®®­®­­­¬¬¬¬¬«««««ªª©ª©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥}````___________^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZYZYYYIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFHPPPPOPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNb}}}}}}}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzz{‚€€€€€€€€€€€„‰“˜¢¦©«¬¬«©¦¡—’Œ‡‚|||||{{{{{{{{{{{g___^^^^^]]\\\\\\\[[[ZZZYYYYXYXXXWWWWVVVVUVVUTTTTTSSRRSRQRQQPPPPPPOOOONNNNMNMMLLLLKKKKJJJJJJIIHHHHHGGGFFFFEEEEEDDDDCDCCBBBBBAA@@@@@????>>>>>==ºº¹¹¹¹¹¸¸¸¸¸···¶¶¶µ¶µµµ´´´³´³³³²²²²²±±±°°°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬«««ªªªªª©©¨©¨¨¨§§§§§¦¦¦¦ a```````__________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZZZYYVIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFGPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNy}}}}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzz‚‚€€€€€€€€€€€€~~~~~~}}}}}}}}|||||||{{{{{{{{{{a____^^^]]]]\\\\\[[[[[ZZZYYYXXXXWWWWWVVVVVUUUTTTTSTSSRRRRRQQQPPPPPPOOOONNMNMMMLLLLKKKKJJJJIIIIHHHHHHGGFGFFFEEEDDDDDDCCCBBBAAAAA@@@?????>>>>>=»ºº¹¹¹¹¹¸¸¸¸·····¶¶¶¶µµµ´´´´³³³³³²²²±²±±±°°°¯¯¯¯¯¯®®®®­¬¬¬¬¬¬¬¬«««ªªª©©©¨©©¨¨¨§§¦§¦¦¦‚a````````___________^^^^^^]]]]]]]]]]]]]]\\\\\\\\\[\[[[[[[ZZZZZZZYYYYQIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFLPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNe}}}}}}}}}}}}}}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{z~‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}||}|||||{|{{{{{{{{p`___^^^^]]]\\\\\\[[[[[ZZYZYYXYXXWWWWVVVVVUUUUTTTTTSSSSSRRQQQPPPPPPOOOONNNMMMMMMLLLKKKKJJJJJIIIIHHGHGGGGFFFFEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>>»ºº¹¹¹¹¹¸¸¸¸¸·····¶¶¶µ¶µ´´´´´³³³³³²²²²±±±±°°°°°¯¯®®®®®­­­¬¬¬¬¬«««««ªª©ª©©¨¨¨¨¨¨§§§¦¦£aaa```````____________^^^^^^^^]]]]]]]]]]\\\\\\\\\\\[[[[[[[[[ZZZZZZZYYYKIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFHPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNN{}}}}}}}}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{{|‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{{{{{{{{f`___^^^]^]]\\\\\\[[[ZZZZZZYYYXXXXWWWVVVVVUUUUTTTTTSSSSSRQRQQQQPPPPOOOONNNNMMMLLLLLKKKKJJJJJIIIIHHHGGGGGGFFEEEEEDDDDCCCCBBBAAAAAA@@@????>>>>»»»ºº¹¹¹¹¹¸¸¸¸¸····¶¶¶µ¶µµµ´´´³³³³²²²²²²±±°°°°°°¯¯¯¯®®®­­¬¬¬¬¬¬¬««««ªªª©©©©©¨¨¨¨§¦¦¦aaaaa```````__________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[ZZZZZZZYYWIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFGPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNl}}}}}}}}}}}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||{{{{{{{{{a`____^^^]]]]\\\\\\[[[[ZZYZYYYXXXXXWWVVVVVVUUUTTTTSSSRSRRRQQQQQPPPPOOOONNNNNMMMMLLLKKKJJJJJJIIIIIHHGGGGGFFFEEEEDDDDDDCCCCBBBAAA@@@@@?????>>»»»ºººº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶µµµ´µ´´³³³²²²²²²±±±±°±°°¯¯¯¯®®®®®­¬¬¬¬¬¬¬««««««ªª©©©¨¨¨¨¨§§¦baaaa```````_`___________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZYYYUIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFKPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNO}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{€ƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~}}}}}}}||||||||{{{{{{{{m___^__^^^^]]]\\\\\[[[[[ZZZYYYYXXXXWWVVVVVVVVUUUTTTSSSSRSRRQRQPPPPPPPOOONNNNNMMMLLLKKKKJJJJJJIIIIIHHGHGGGFFFFEEEEDDDDCCCCBCBABAAAAA@@?????>»»»ººº¹º¹¹¸¸¸¸¸¸¸····¶¶¶¶µµ´´´´´³³³³³²²²²²±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©¨©¨¨¨¨§™aaaaaaaa```````___________^^^^^^]]]]]]]]]]]]]\]\\\\\\\\\[[[[[[Z[ZZZZZZZZYQIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGFGPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNs}}}}}}}}}}}}}}}}}}}}|}|||||||||||||{{{{{{{{{{{{ƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}}}}}}}|||||||{{{{{{{c``____^^^]]]\]\\\\[[[Z[ZZZZYYYYXXWWWWWVVVVVVUUUUTTSSSSRRRRRQQQPPPPPOPOONNNNNMMLLLLLKKKKJJJJJIIIHIHHHGGGFFFFEEEEEDDDDCCCCCBBBAA@@@A@?@???>¼»»»ººººº¹¹¹¹¸¸¸¸¸···¶¶·¶¶¶µµµ´´´³³³³²²²²²±±±±±°°°¯¯¯®¯®®®®­­¬¬¬¬¬¬«««ªªªªª©©¨¨¨¨¨¨ybaaaaaa`a``````___________^^^^^^]^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYYMIIIIHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGGGGGGGMPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONN^}}}}}}}}}}}}}}}}}}}}|||||||||||||||{|{{{{{{{{{}ƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}}}|}|||||{{{{{{{{s``_`___^^^^]]]\\\\\[[[[ZZZZZYYYXXXXXWWWVVVVVVUUUTTTTSSSSRRRQQQPPPPPPOPOONONNMMMLMLLLKKKKKJJJJIIIIHHHHHGGFFFEFEEDEDDDDDCCCCCBBAAA@A@@@????¼¼¼¼»»»ºººº¹¹¸¸¸¸¸¸····¶¶¶µ¶µµµ´´´³³³³³²²²²²±±±°°°°¯¯¯®¯®®®­­­­¬¬¬¬¬«««ªªªª©ª©©©©¨£bbbaaaaaaa`````````__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGHPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNz}}}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}~}}}}|||||||{{{{{{{{e``____^^^^^]]]]\\\\[[[[ZZZZYYYYXXXXWWWWVVVVVUUUTTTTSTSSRSRQQQQQPPPPPOOOOONNNMMMLLLLLKKKKJJJJJIIIHIHGHHGGGFFEFEEEEDDDDDCCCCCBAAAAA@@@@@??¼¼¼»»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶¶µµ´´µ´´³³³³³²²²²²±±±±°°°¯¯¯¯®®®­­­­­­¬¬¬¬««««««ªª©©©¨¨bbbbaaaaaaa````````___________^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZWIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGOPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONm}}}}}}}}}}}}}}}}}}}}}|}||||||||||||||{{{{{{{‚ƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~}}}}}}}||||||{{{{{{x```_`__^^^^]]]]]\\\\[\[[ZZZZYYYXXXXWWWWWVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOONONNNMMMMMLLLKKKJJJJJJIIIIIHHHHGGFFFFFFEEEDDDDCDCCBBBAAAAAA@@@@?¼¼¼¼»¼»º»ºº¹¹¹¹¹¸¸¸¸¸·····¶¶¶¶µµ´´´´´³³³²²²²²²±±±°°°°°¯¯¯®®®®®­­¬¬¬¬¬¬«««ªªªªª©©©¨mbbbbbaaaaaaa````````__________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYVIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGIPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOW}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{‚„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}}}}|||||||{{{{{{h`````___^^^]]]\\\\\\\[[[[ZZYYZYYYXXXWWWWWVVVVVUUUTTTSSSSRRRRQQQQQPPPPPOOONONNNMMMMLLLKKKKJJJJJIIIIIHHHHGFFGFFEFEEEDDDDDCCCBBBABBAA@@@@@½¼¼¼¼»¼»»ºººº¹¹¹¸¹¸¸¸¸····¶¶¶¶µµµµ´´´³´³³³²²²²²²±±±°°°¯¯¯¯®®®®®­­­¬¬¬¬¬«««««ªª©ª©£cbbbbbbaaaaaaa````````__________^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\[[[[[Z[ZZZZZZZYUIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOy}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||{|{{{„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~}}}}}}}|||||||{{{{za````___^^^^]]]]]\\\\[[\[[ZZYYYYYXXXXWWWVVVVVVUUUUUTTSSSSSRRRRQQQPPPPPPOONOOONMMMLMLLLKKKJJJJJJJIIIHHHHHGFGFFFFEEEDDDDDDCCCCCBABAAAA@@@½½½¼¼¼»¼»ºº»ºº¹¹¹¸¸¸¸¸¸¸···¶¶¶¶µµµµ´´´´³³³²²²²²±±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªªªccbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[ZZZZZZZZSIIIIHIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGJPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOm~}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||{||„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~}}}}}}}|}||||{{{{{{ia````____^^]]]]]]\\\\[\[[[ZZYYZYXXXXWWWWWVVVVVVUUTTTTTSSSSRRRQRQQQPPPPPOOOONNNNMMMLLLLLKKKJJJJJJJIIHHHHGGGFFFFEEEEEDDDDDCCCCBBBBBAAA@@¾½½¼¼¼¼¼»»º»ºººº¹¹¹¸¸¸¸¸¸···¶¶¶µ¶µµ´´´´´³³³³²²²²±±±±°°°°°¯¯¯®®®®®­­­¬¬¬¬¬«««««ªªªoccbbbbbbaaaaaaa`a```````__________^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYYSIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX~~}}}}}}}}}}}}}}}}}}}}}}}}|}|||||||||||„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||{{{{{aa````_`_^^]^^]]]]\\\\\[[[Z[ZZYZYYYXWXWWWWVVVVVVUUTTTTTSTSSRRRRQQQQPPPPPOOOONNNMMMMMLLLLKKKKJJJJJIIHIHHHGHGGFFFFEEEEDDDDDDCCCBCBBBA@A@½½½½¼¼¼¼¼»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶µµµµ´´´´´³³³³²²²²±±±±±°°°°¯¯¯®®®®®­­­¬¬¬¬¬¬«««ªª¥ccccbbbbbbbaaaaaaaa```````_`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZYZRIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGHGJPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO{~}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||……„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{{haa````___^_^^^]]]]\\\\\[[[[ZZZZZYYYXXWWWWWVVVVVUVUUUTTTSSSRRRRRQQQQPPPPOOOONNNNNMMMMMLLKKKKJJJJJJIIIIHHHHGGGGFFFFEEDDDDDDCCCCCBBAAAA@¾½½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶¶µµ´´´´´´³³²²²²²±±±±°°°°¯°¯¯¯¯®®­­­­¬¬¬¬¬¬««««—ccccbbbbbbbaaaaaaa`a``````__`_________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[[ZZZZZYYRIIIIHIHHHHHHHHHHHHHHHHHHHHHHHGGOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOq~~~}}}}}}}}}}}}}}}}}}}}}}}}|||||||||…………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{xaaaa``_`_^^^^]]]]]\\\\\[[[[[ZZZZZYYYXXXWWWWVVVVVVUUUTTTSSSRSSRRRQQPQPPPPPOOOONNNNNMMLLLLLKKJJJJJJJJIIIHHHHHGGFFFFFEEEDDDDCCCCCBBBBBAA¾¾½½½½¼¼¼»»»»»»ººº¹¹¹¹¹¸¸¸¸¸··¶¶¶¶¶¶µµµµ´´´´³³³²²²²²±±±°°°°¯°¯¯®®®®®­­­­¬¬¬¬«««ª~ccccccbbbbbbabaaaaaa`````````__________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYSIIIIIHHHHHHHHHHHHHHHHHHHHHHHHIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOa~~~~}}}}}}}}}}}}}}}}}}}}}}}}|||||||‚………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}}}}}||||||{||{gaaa````____^]^^]]]]\\\\\[[ZZZZZZYYYYXXWWWWVWVVVVUUUUTTTTSSSSSRRRQQQQPPPPPOOOOONNMMMMMLLKLKKKJJJJJJIIIIHHHHGGGGFFFEEEEDDDDDCCCCBBABAA¾¾¾½½½½¼¼¼¼¼¼»»ºººº¹¹¹¹¸¸¸¸¸·····¶¶¶¶µ´´´´´´³³²³²²²²²²±±±±°°¯°¯¯¯®®®­®­­­¬¬¬¬¬«ªccccccccbbbbbbbbaaaaa`a``````____________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZYTIIIIHIHHHHHHHHHHHHHHHHHHHHHHMPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOOOOOO}~~~~}}}}}}}}}}}}}}}}}}}}}}}||||||‚…………………„…„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{saaaa`````___^^]^]]]]\\\\[[[[[ZZYYZYYYYXXXWWVWVVVVUUUUTTTTSSSSRRQRRQQPPPPPPOOOOONNMMMMMLLLLKKKKJJJJJIIIIHHHHGGGGFFFEEEEDDDDDCCCCCBBAB¾¾¾¾¾½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸¸····¶¶¶¶µµ´´´´´³³³³²²²²²±±±±°°°°°¯¯¯®®®®­­­¬¬¬¬¬«£cccccccccbbbbbbbaaaaaaa```````_`__________^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYUIIIIIHHHHHHHHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOy~~~~~~}}}}}}}}}}}}}}}}}}}}}}||||ƒ††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}}}|||||||daaaaa``_`__^^^^]]]]\\\\\\[[[ZZZZZYYXXXXXXXWWVVVVVUUUUTTTTTTSRSRRRQQQQQPPPPOOOOONNMMNMMLLLLKKKKJJJJJIIIHHHHGHGGGFFFFEEEEDDDDCCCCCBBB¿¾¾¾¾¾½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´´´´´³³³²²²²±±±±±°±°¯¯°¯¯®®®­­­­­¬¬¬¬•cccccccccccbbbbbbaaaaaaaa```````____________^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZZZZZZVIIIIIHHHHHHHHHHHHHHHHHHHHKPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOo~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}|ƒ†††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~~}}}}}|}}||||||nbbaa`a```____^^^^]]]\\\\\\[[[Z[ZZZZYYYXXXXWWWWVVVVVUUUTTTTSSSRRRRRQQQQPPPPPOOOOONMNMMMLLLLKKKKKJJJJIIIIIHHHGGGGFFFFEEEEEDDDDCCCBBCB¾¿¾¾¾¾¾½½½¼¼¼¼¼¼»»ºººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶µµµµ´µ´³³³³³²²²²²±±±°°°°¯¯¯®¯®®®­­­­­¬¬¬€cccccccccccbbbbbbbaaaaaaaa```````___________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[ZZ[ZZZZZYXJIIIHHHHHHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOa~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}…††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}|}||||||{bbbbaaa```_____^^]^]\\]\\\\[[[[Z[YYYYXXYXXXWWWWVVVVUUUUUUTTSSSSSRRRRQQQQPPPPOOOOONNNMMMLMLLLLKKKJJJJJIIIHHHHGHGGGFFFFEEEDDDDDCCCCCB¿¾¾¾¾¾¾¾¾½½½¼¼¼»»»»»ººº¹¹¹¹¸¸¸¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±±±°°¯¯¯¯®®®­­­­¬¬¬eccccccccccccbbbbbbbbaaaaaaa```````____________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZYZYNIIIIHHHHHHHHHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOO~~~~~~~}~}}}}}}}}}}}}}}}}}}}…††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}}||||{ibbbaaa```_`___^^^]]]]]\\\\\[[[[ZZZZYYYXXXXXWWWWVVVVVUUUUTTSTTSSSRRRQQQPPPPPPOOONNONMMNMMLLLKKKKKJJJJJIIIIHHHHGGGGFFEEEEDEDDDDDCCBCÀ¿¿¿¾¾¾¾¾½½½¼¼½¼»¼»»»ºº¹¹¹¹¹¹¸¸¸¸·¸··¶¶¶¶µµµµµ´´´´³³³²²²²±²±±°°°°¯°¯¯®¯®®®®­­­©cccccccccccccccbbbbbbaaaaaaaa`````````_________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZYRIIIIHHHHHHHHHHHHHHHHMPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOO{~~~~~~~~}}}}}}}}}}}}}}}}}}‚†††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~}}}}}}||||||tbbbaaaa```__`___^^^]]]\\\\\\[[[[[ZZYYYYYXXXXXWWWVVVVUUUUTTTTSSSSSRRRRQQQQPPPPOOONNNNNMMMLMLLLKKKKJJJJIIIIIHHHGGGGFFFEEEEEEDDDDCCCBÀ¿¿¿¿¿¾¾¾¾½½½¼¼¼¼¼»»»ºººº¹¹¹¹¹¸¸¸¸·····¶¶¶µ¶µ´´´´´³³³³²²²²²±²±±°°°°¯¯®¯®®®­­­¬¡ccccccccccccccccbbbbbbbaaaaaaaa```````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZZYZUIIIIHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOv~~~~~~~~~}}}}}}}}}}}}}}}}„††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}||||||dbbbaaaa`a```___^_^^]]]]\\\\\\[[[ZZZZYYYXYXXXWWWWVVVVUUUUTTTTTSSSRRRQRQQPPPPPPOPOONNNNNMMMLLLLKKKKJJJJJIIIHHHHGGGGFGFFEEEDEDDDDDCCÀÀ¿¿¿¿¿¾¾¾¾½½½½¼¼¼»»»»»ºººº¹¹¹¹¸¸¸¸¸··¶·¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±°°°°¯¯¯¯®®®®­”cccccccccccccccccbbbbbbabaaaaa``a`````____________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYXMIIIHHHHHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOm~~~~~~~~~}}}}}}}}}}}}}}€†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}||||kbbbbaaaaa````____^^^^]\]]\\\\\\[ZZZZZYYYXYXXXWWWVVVVVUVUUTUTTTSSSRRRRQQQQQPPPPOOOONONNMMMMLLLLKKKKJJJJJIJIHHHHHGHGGGFFFEEEEDDDDDCÀÀÀ¿¿¿¿¾¾¾¾¾¾½½¼½¼¼¼¼¼»»ºººº¹¹¸¹¸¸¸¸····¶¶¶¶¶µµµ´´´´´³³³³²²²²±±±±°°°°¯¯¯®¯®®®­ƒdcdccccccccccccccbbbbbbbbbaaaaa`a``````____________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[ZZZZZZZYZYSIIIHHHHHHHHHHHHOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOc~~~~~~~~~}~}}}}}}}}}}}ƒ‡‡†‡††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~}}}}}}}}}|||vbbbbbbaaaaa```____^^^]]]]\\\\\\[[ZZZZYZZYXXXXXWWWVVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOOONNNNMMMMMMLLKKKKJJJJJIIIHIHHHGGGFFFFEEEDDDDDDCÀÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»ººº¹º¹¹¹¸¸¸¸¸···¶¶¶¶µµµµ´´´³´³³³³²²²²±±±±°°°°¯°¯®®®®®pdddccccccccccccccccbbbbbbabaaaaaaa`````__`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[Z[ZZZZZZYYYWKIHHHHHHHHHHHIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOW~~~~~~~~~~~~}}}}}}}}†‡‡‡‡††††††††††††††††……………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}}||ebbbbbbaaaa`````___^^^]]]]\\\\\\[[[ZZZZYYYYXXWXWWWWVVVVVVUUUTTTTSSSSRRRQQQQPPPPPOOOONNNNNMMMMLLKLKKJJJJJJIIIIHHHHGGGGFFFFEEEEDDDDÁÁÀÀ¿À¿¿¿¿¾¾¾¾¾½½½½¼¼¼»»»»ºººº¹¹¸¸¸¸¸¸¸···¶·¶¶¶µµµµ´´´´³³³²²²²²²±±±°°°¯¯¯¯®®®­dddddccccccccccccccccbbbbbbbaaaaaa`````````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYSIIHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOO}~~~~~~~~~~}~}}}}}}ƒˆ‡‡‡‡‡‡‡†††††††††††††††…†……………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}|kbbbbbbaabaaa```___^^^^]]]]]\\\\\[[[Z[ZYZZYYXXXWWWWWVVVVVVVUUTTTTSSSSRRRQQQQPPPPPPOOOONNNNMMMLLLLLKKKKJJJJJIIIHHHGGGGFFFFEEEEEDDDÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼»»ººººº¹¹¹¹¸¸¸¸¸·¸·¶¶¶¶µµµ´´´´³³³³²³²²²²±±±±°°°°°¯¯¯¯«ddddddccccccccccccccccbbbbbbbaaaaaaa`````````__________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYWMIHIIHHHHHNPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOO|~~~~~~~~~~~~~}}}‡ˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~~}}}}}}||uccbbbbbbaaaaa``_`__^^^^^]]\\\\\\[\[[ZZZZZYYYXXXWXWWWVVVVVUUUTTTSTTSSRRRRQQQQPPPPPOOOONNNNMMMMMLLLLKKKJJJJJIJIIHHHGHGGFGFEFEEEEDDÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½¾½¼¼¼¼¼»»»»ºº¹¹¹¹¹¸¸¸¸¸···¶·¶¶µµµ´µ´´³´´³³³²²²²±²±±±°°°¯¯¯®¥ddddddcdccccccccccccccbbcbbbbbbaaaaaaa````````__________^^^^^^]^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYYUIIIHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOx~~~~~~~~~~~~~~}†ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}||dccbbbbbbaaa`a```___^^^^]]]]]\\\\\[[[[ZZZZYYYYXXXWWWWVVVVVVUUUUTTTSSSSRRRQQQQPPPPPOPOONNNNNMMMMLLLKKKKJJJJJIIIIIIHHHGGFFFFFEEEDDÂÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»¼»»º»ººº¹¹¸¸¸¸¸¸···¶¶¶¶¶µµµ´µ´´´³³³²²²²²±±±±±°°°¯¯¯Ÿdddddddccccccccccccccccbbbbbbbbaaaaaaaa```````___________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[Z[[ZZZZZYZYYYYSHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOt~~~~~~~~~~~~~…ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~}}}}}}|hccccbbbbaaaaaa```____^^]^]]]\\\\\[[[[ZZZZZYYYXXXXWWWVVVVVVVUUUTTTTTSSRSRRRQQQPPPPPOOOOOONNNMMLMLLLKKKJJJJJJJIIIHHHGHGGGFFFFEEEEÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼¼»»ºººººº¹¹¸¹¸¸¸¸···¶¶¶¶¶µµ´µ´´´´³³³³²²²²±±±±±°°°°¯™eedddddddccccccccccccccccbbbbbbabaaaaaaaa``````__________^^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[ZZZZZZZYZYYYYXQIHHLPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOp~~~~~~~~~~~„ˆ‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}~}}}}}pdccccbbbbbaaaa````__^^^^^]]]\]\\\\\[[[ZZZZYYYYXXXWWWWWVVVVUUUUTUTTSSSSSRRRQQQQQPPPPPOOOONNNNMMMLLLLKKKKJJJJJJIIHIHHHHGFFFFFEEEEÂÂÁÁÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼»»»º»ºººº¹¹¹¸¸¸¸¸···¶¶¶¶µ¶µ´´´´´³³³³³²²²²±±±±±±°°¯eeeddddddcdcccccccccccccccbbbbbbbaaaaaa````````_`__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZ[ZZZZYZYYYYYXQHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOPOOOOOOOOOOOOj~~~~~~~~~„ˆ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}zddccccbbbbbaaaa```____^^^^]]]]]\\\\[[[[[[ZZYZXXXXXXXWWWVVVVVUUUUTTTSSSSSRRQRQQQQPPPPPOOOONNNNMMMLLLLLKKKJJJJJIIIIHHHHGGGFFFFFEEÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼»»»»ºººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶µµµ´´´´³³³³³²²²²±±±±°°°°ˆeeeeeddddddcccccccccccccccccbbbbbabaaaaaa```````___________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[ZZZZZZYYYYYYYYXUPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOf~~~~~~~…‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~}}}}}fddccccbbbbbaaaa````___^^^^^]]]\\\\\[[[[ZZZZZYYYXXXXXWWVVVVVVUUUUTTTSTSSSSRRRRQQPPPPPPOOONNNNMMMMLLLLKKKJKJJJJJIIIHIHHGGGFFGFFEÂÃÂÂÁÁÁÁÁÀÀ¿À¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸···¶¶¶¶¶µµµ´´´´´³³³³²²²²²²±±±°°€eeeedddddddddccccccccccccccccbbbbbbaaaaaaaa```````____________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZYZYYYYXXXVRPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOO`~~~~€‡‰ŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††††……………„„……„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}jddcccccbbbbbbaaa``_`____^^]^^]]]\\\\\[[[ZZZZZYYYXXXWXWWWWVVVVVUUUTTTTSSSRRRRQQQQQPPPPPPOOONNNNMMMLLLLLKKKJJJJJJIIHHHHHGGGGFFFEÃÃÂÂÂÁÁÁÁÁÁÀ¿À¿¿¿¿¾¾¾¾½½½½½¼¼¼¼¼»»ººººº¹¹¸¹¸¸¸¸···¶¶¶¶¶¶µµµµ´´´³³³³²²²²²±±±±°yeeeeedddddddddcccccccccccccccbbbbbbbbaaaaaa```````_`__________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[\[[[[[[ZZZZZZZYYYYYYXXWWWTPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOO[ƒ‰ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡†‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~}}}}odddddcccbbbbbaaaaa```____^^^]]]]]\\\\\[[[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTSSSSSRRQRQQPPPPPPOOONNNNNMMMLLLLKKKKJJJJJJIIIIHHHHGGGFFFÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½½¼¼¼»»»ººº¹¹¹¹¸¹¸¸¸¸···¶¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±reeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZZZYYYXXXXXWWXVRPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOYˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}wedddccccbbbbbbbaaaa````___^^^^]]]]\\\\\\[[[ZZZZYYYYXXXWWWWVVVVVUUUTTTTTTSSRRRRQQQQPPPPPPPOONONNMNMMLMLLLKKKKJJJJJIIIIIHHHGGGFFÃÃÃÃÂÂÂÂÂÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»¼»ººººº¹¹¹¸¹¸¸¸¸¸··¶¶¶¶µµµµµ´´´´³³³²²²²²²±±±mfeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaaa```````____________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZZZYXXXXXWWWWWXURPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOQSTZ‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††…………………„„„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}eeddddcccccbbbbbaaa``_```_^^^^]]]]]\\\\\\[[[ZZZZYYYXXXXWWWWWVVVVUUUUTTTTSSSSRRRRRQQPPPPPPOOOONNNNMMMMLLLKKKJJJJJJJIIIHHHHHGGFFÄÄÃÃÂÂÂÁÂÂÁÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½¼¼¼»¼¼»º»ºººº¹¹¹¸¸¸¸¸¸···¶¶¶¶¶µµ´´´´³³³³³²²²²²²±jfefeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaa`a`````____________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[[[ZZZZZZYZXXXXXXWWWWWWWWVTPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPRTUUUUW‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~}~geedddddcccbbbbbbaaa````____^^^^]]]]\\\\[\[[ZZZYYYYYXXXXWWWWVVVVVVVUUUTTTTSSRRRRRQQQQPPPPOOOOOONNNMMMMLLLLKKJJJJJJJIIIHHHGGHGGÄÄÃÃÃÂÂÂÂÂÂÁÁÁÀÀ¿ÀÀ¿¿¾¾¾¾¾¾½½½½¼¼¼»»»»»ººº¹¹¹¸¸¸¸¸¸····¶¶·¶µµµ´´´´´´³³²²²²²±±gffeeeeeeeeddddddddcccccccccccccccbbbbbbbabaaaaa`a`````_`___________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZYXXXXXXXXWWWWWWWWWWUTQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPRSUUUUUUUUV‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…†…………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~~keedddddcccbbbbbbbaaa````_____^^^]]]]\\\\[[[[[[ZZYYYYYXXXXWWWWVVVVUUUUTTTSSSSSSRRQRQQQPPPPPOOOONNNNMMMLMLLKLKKKJJJJJIIIIHHHHGGÄÄÄÃÄÄÃÂÂÂÁÂÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ººººº¹¹¸¸¸¸¸¸¸··¶¶¶¶µ¶´µ´µ³´³³³²²²²²²ffffefeeeeeeedddddcdcccccccccccccccbbbbbbbaaaaaaaa``````_`_________^_^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYXXXXXXXXWWWWWWWWWWWWWWVUSQPPPPPPPPPPPPPPPPPPPPPPPPPPPPRTUUVVVVVVUUUUUV‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒ‚ƒ‚ƒ‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~offeeedddcccbbbbbbbaaaa````____^^^]]]]\\\\\[[[[ZZZZYYYYYXWXWWWWVVVVUUUUTTTTSSSSSRRRRQQPPPPPPOOOONNMNMMMMLLLLKKKJJJJJJIIIHHHHHGÄÄÄÃÄÃÃÃÂÂÂÂÁÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼»»»»ººº¹¹¹¹¹¸¸¸¸····¶·¶¶¶µµµµ´´´³³³³³²²²hffffeeeeeeedddddddddccccccccccccccccbbbbbbabaaaaaa```````__________^_^^^^^^^]]]]]]]]]]]]\]\\\\\\\\[[[[[[[[[ZZZZYXXXXXXXXXWWWWWWWWWWWWWWWWWWWVUUTSSRQQQPPPPPQRRSTTUVVVVVVVVVVVVVVVVUUUVŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~uffeeededdccccbbbbbaaa``a```___^^^^^]]]\\\\\[[[[[ZZYYYYYYXXWWWVWVVVVUVUUTUTTSSSSSRRRQQQQPPPPOOOOONONNMMMMLLKKKKKJJJJJIIIIIHHHHÄÄÄÄÄÃÃÃÂÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½½¼¼¼¼»»º»ºº¹º¹¹¹¸¸¸¸¸¸···¶·¶¶¶µµ´´´´´³³³²²²²jffffffeeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaa`````````__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[ZZZZZYXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUXŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††††……………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~{fefeeedddccccccbbbbbbaa`````____^^]]]]\\\\\\\\[ZZZYZZYYXXXXWWWWWVVVVVVUUTTTTTSSSRRRRQQQPPPPPPOOOONNNMMMMLMLLLKKJKJJJJJIIIHIHHÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶µµµ´´´´´³³³²³²mffffffeeeeeeedddddddddcccccccccccccccbbbbbbbaaaaaaaaa`````__`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVUUUZŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆ‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~gffeeeededddccbcbbbbabaaa`````__^^^^]]]\\\\\\[[[[[ZYZZYYYYXXWWWWVVVVVVUUUUUTTTSSSRRRRRQQPPPPPPPOOONNNNMMMMLLLLKKKJJJJJIJIIIHHÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»»ººººº¹¹¹¹¸¸¸¸·····¶¶¶µµµ´´´´´´³³³³sffffffffeeeeeeeedddddddcccccccccccccccccbbbbbbbaaaaa````````_`_________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWWVVVVVVVVVVVVVVVUU^ŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~hffffeeeeddcdcccbbbbbaaaa```______^^^]]\]]\\\\[[[[[ZZZYYYXXXXXWWWWVVVVVUUUUTTTSSSSSRRRQQQPPPPPPPPOONNNMMMMMLLLKKKJJJJJJJIIIHHÅÅÄÄÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÁÀ¿¿¿¿¿¾¾¾¾¾¾½½¼¼¼¼»»»»ººººº¹¹¹¹¸¸¸¸¸··¶·¶¶¶¶µµµ´´´´´³³³zfffffffeeefeeeeeddddddddcccccccccccccccbbbbbbbbaaaaaaa```````___________^^^^^^^]^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWVVVVVVVVVVVVVVVVUcŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~jgfffeeeeedddcccccbbbbabaaaa``___^^^]]^]]\\\\\\\\[[ZZZYZYYYXXXXWWWWVVVVVUUTUUTTTSSSSRRRQQQQPPPPPOOONNNNMNMMMLLLLKKKKJJJJIJIIIÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»»»»»ºº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶¶µµµ´´´´³³³‚ffffffffeeeeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaa`a```````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[YXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVViŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††…………………„……„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~lgfffffeeeddddcccccbbbbbaaaa````____^^^^]]]\\\\\[[[Z[[ZZZYYXXXXXWWWVVVVVVUUUUTTSTTSSRRRRRQQQQPPPPPOOONNNNMMMMMLLKLKKJJJJJIIIIÆÅÅÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÀÁ¿À¿¿¿¿¾¾¾¾½½½½½½¼¼¼¼»»»ººº¹º¹¹¸¸¸¸¸·¸···¶¶µ¶¶µµ´´´´´³Šfffffffffeeeeeeeeedddddddddccccccccccccccbbbbbbbbbbaaaa`````````____________^^^^^^]]]]]]]]]]]]\\]\\\\\\\\[\[[[ZYXYYXXXXXXXXXXXWXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWVVVVVVVVVVVVVVVoŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡†‡†††††††††††††††…………………„„„„„„„ƒ„„ƒƒƒƒƒƒƒƒ‚‚ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€nhgggffffedeeddccccbbbbbbaaa``````___^^]]]]\\\\\\[\[[[ZZZYYYYYXXXWWWWVVVVUUUUUUTTTSSSRRRRRQQQPPPPPPOOONNNNMMMLLLLLLKKKJJJJJIIÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÂÂÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´³´“ffffffffffffeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa````````____________^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[\[[ZYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVvŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€qgggfgfffeeeeeddcccccbbbbbaaa`````____^^^]]]]\\\\\[[[[[ZZZZYYXYXXWWWWWVVVVUUUUUTTTTTSSSRRRRQQQPPPPPOOOONNNMMMMLLLLKLKKKJJJJJIÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÃÂÂÁÂÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»»ºº¹º¹¹¹¸¸¸¸····¶¶¶¶µ¶µ´µ´´´œggggffffffffffeeeeeededdddddcccccccccccccccbbbbbbbbaaaaaaa````````_________^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[ZYYYYYXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVV|ŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€shhggfgffffeeedddddcccbbbbbaaaaa````____^^^]]]]\\\\[[[[Z[ZYZYYYYXXWXWWVVVVVVUUUUUTTTTSSSRRRQQQQQPPPPOOOOONNNNMMMMLLLKKKKKJJJIÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸······¶µ¶µµµ´´´¤gggffffffffffeeeeeeeeeeddddddcccccccccccccccbbbbbbbbaaaaaaaaa`````____________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[ZYYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡†‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€€vhhhggggffefeeeeddcccccbbbbbaaa`````___^^^^^]]]\\\\\\[[[[ZZZYYYXXXXXXWWWVVVVVUUUUTTTSSSSRRRRRRQQQPPPPPPOOONNMMNMMLLLLLKKKKJJJÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼»»»»ºººººº¹¹¸¸¸¸¸····¶¶¶¶µ¶µµ´´«gggfgfffffffffeeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaaa````````___________^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[ZYYYYYYXXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV†ŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…†……………„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€€xhhhhggfgffeeeeeedddccccbbbbbabaa````___^^^^^]]]]\\\\[[[[[ZZZYZYYXXXXWWWVVVVVVUUUUUTTTTSSRRRRRQQQQPPPPOOOONOONNMMMLLLLKKKKKJJÇÇÇÇÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÃÂÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼¼¼»»»ºººº¹¹¸¹¸¸¸¸·¸·¶¶¶¶¶µµµ´±ggggfggfffffffffeeeeeeedddddddccccccccccccccccbbbbbbbbbbaaaaaa```````__________^_^^^^^^^]]]]]]]]]]]\\\\\\\\\\\YYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV‹ŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€ziihhggggffffffedddcddcccbbbbabaaaa``_`____^^]]]]]\\\\\[[[Z[ZZZYYYYXXXXWWWVVVVVUUUTTTTTTSSSRRRQQQQPPPPPPOONOONNMMMLLLLLLKKJJJÈÇÆÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼»¼»»»ºº¹º¹¹¹¸¸¸¸¸···¶¶¶¶µµµµ´gggggfgfffffffffffeeeeeedddddddddcccccccccccccccbbbbbbbbaaaaaa````````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\YYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVŒŒŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†‡††††††††††††††………………………„„„„„ƒƒ„ƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€|iihhhghggffffefeddddddcccbbbbbaaaa```_`__^^^^^]]]]\\\\\[[[Z[ZZZZYXXXXWWWWWWVVVVUUUUTTTTTSSSSRRQRQPQPPPPPOOOONNNNMMMMLLKKKKKKÈÇÇÇÇÇÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÂÁÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»»ºº¹º¹¹¹¸¸¸¸¸¸¸···¶¶¶¶µµµtggggggffffffffffffeeeeeededddddddcccccccccccccccbbbbbbbaaaaaaa```````__`________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\YYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVV_ŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹ŠŠ‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††……………………„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€~iiihhhghgggfffffeeedddccccbbbbbaaaa`a```__^^^^^]]]\\\\\[[\[[ZZYZZYXXXXXWWWWVVVVVVUUUTTTSTSSRRRRRQQQQPPPPPOOOONNNNNMLLLLLLKKKÈÈÈÈÇÇÆÆÅÆÆÅÄÅÄÄÄÄÄÄÃÃÃÃÂÁÁÁÁÁÀÀÀ¿À¿¿¿¾¾¾¾¾½¾½½¼¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸···¶¶¶¶¶µµˆhggggggfgfffffffffeeeeeeededdddddccccccccccccccccbbbbbbbaaaaaaaa``````_____________^^^^^]^]]]]]]]]]]]\\\\\\\\ZYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVmŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…†……………„„„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~iiihihhhhggffffeeeedddccccbbbbbabaaa````___^^^^^]]]\\\\\[[[[ZZZZZZYYYXXXWWWWVVVVVUUUTTTTTSSSSRRRRQQQQPPPPOOOONNNMNMMMMLLLLKKÈÈÇÈÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÄÃÄÃÃÂÂÂÂÂÁÁÀÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ºº¹¹¹¹¹¹¸¸¸¸····¶¶¶µµšhgggggggfffffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaa``````__`________^^^^^^^]^]]]]]]]]]]]]]\\\\\YYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVzŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†‡†††††††††††††††…………………„……„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€iiiiihhhggggffffeeeeddddccccbbbbbaaaaa``___^^_^^^^]]]\\\\[\[[ZZZZZYYYYYXXWWWWVVVVVUVUUTTTTSTSRRRRRQQQPPPPPOOOONNNNNNMMMLLKKKÈÈÈÈÇÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»º»ºººº¹¹¹¹¸¸¸¸¸¸···¶¶¶©hhggggggggfffffffffffeeeeeeeddddddddccccccccccccccbbbbbbbbaaaaaaaa````````__________^^^^^^^^]]]]]]]]]]]]]\\\\YYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVV„ŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€€€jiiiiihhhggggggffeeeddddddcccbbbbbabaa`````___^^^^]]]\\\\\\[[[[ZZZZZYXYXXXWWWWVVVVVUUUUTTTTTSSRRRRQQQQPPPPPOOOOONNNNMMMLLLLKÉÉÉÈÇÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÂÂÂÂÁÁÀÁÁÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»ººººº¹¹¸¹¸¸¸¸¸···¶¶¶²hhhhhgggggggfffffffffeeeeeededddddddcccccccccccccccccbbbbbbaaaaaaaaa```````_________^^^^^^^^]]]]]]]]]]]]\]\\\ZYYYYYYYYYYYYYYXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVŠŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€jijiiiiihhhhggffffeeeeeddcdcccbbbbbaaaaa`_`____^^]]]]]]\\\\[\[[[ZZZYZYYXXXXWWWWWVVVVUUUUTTTTTTSSSRRRQQQPPPPPOPOONNNNMMMMMLLLÉÉÈÈÈÈÈÇÇÇÆÇÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÁÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»»»ºº¹¹¹¹¹¸¸¸¸¸¸···¶¶¶ihhhgggggggggfffffffffeeeeeeddedddddddcccccccccccccccbbbbbbbaaaaaaa`````````_________^_^^^^^^^]]]]]]]]]]]]]\\ZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€~jjjiiiiihhhhggggfffeeeededdcccbbbbbbbaaa`a`_``__^^^^]]\]\\\\\[[[ZZZYZYYYXXXXWWWWWVVVVVUUTTUTTSTSSRRRRRQQQQPPPPOOONNNNNMMMMLLÉÉÉÈÈÈÈÇÈÇÇÇÆÆÆÆÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿À¿¿¿¾¾¾¾¾½½½½¼½¼»»»»»»ºº¹¹¹¹¸¸¸¸¸···¶¶¶‡hhhghggggggfgfffffffffeeeeeeeeedddddddccccccccccccccccbbbbbbaaaaaaaa```````____________^^^^^^^]]]]]]]]]]]\\\ZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVlŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡††‡††††††††††††††††………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€}jjjjiiiiihhhghggggfeeeededdcccccbbbbbabaaa```____^^^]]]]]\\\\\\[[ZZZYYYYYXXXXWWWWWVVVVVVUTTTTTTSSSSRRRRQPPPPPPPPOOONNNMNMMMMÉÉÉÉÉÈÈÇÈÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÁÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½¾½½¼¼»»¼»º»ººººº¹¹¸¸¸¸¸¸···¶žhhhhhggggggggfgffffffffefeeeeeeddddddcccccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^]]]]]]]]]]]]]]ZZYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW|ŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€{kkjjjiiiiihhhhggggffffeeedddddcccbbbbbbaaa````____^^]^^]]]]\\\[[[[[Z[ZYYYYYXXXWWWWWVVVVVVUUTTTTTSSSRRRRRQQPQPPPPOOONNNNMMMLMÊÊÉÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¾¿¾¾¾¾½½½½¼¼¼»¼»»ºººº¹¹¹¹¸¸¸¸¸¸···®hhhhhhgggggggfggffffffffeeeeeeeeedddddcdccccccccccccccccbbbbbbbaaaaaaaa``````__________^^^^^^^^^]]]]]]]]]]]]ZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW‰ŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€zkkjjjjiiiihiihhggggfffffeeeddcdccccbbbbaaaa````____^^^^]]]\\\\\\[[[ZZ[ZYZYXYXXXWWWWWVVVVUUUTUTTSTSSRRRRRQQQQPPPPPOOONNONNNMMÊÊÊÊÉÉÈÉÈÈÈÇÇÇÇÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÃÃÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¿¾¾¾¾½½½½¼¼¼¼»»»»»º¹º¹¹¸¹¸¸¸¸¸··¶hhhhhhhgggggggggffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa``````______________^^^^^^^]]]]]]]]]][YZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡†‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€xkkkkjjijiiihhhhhhgggfffeeeeddddcccccbbbbbaaaa````____^^^]]]\\\\\\\[[[[[ZZYYXYXXWWWWVWVVVVUUUUTTTTTSSSRSRQQQQQQPPPPOOOONNNMNNÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½½¼¼»¼»»»»ººº¹¹¹¹¸¸¸¸¸··‡ihhhhhhhggggggggffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa``````_`__________^^^^^^^^^]]]]]]]]][ZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWlŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€vlkjkkjjjiiiiihhhghhgggffeeededdcdcbcbbbbbbaaaa````____^^^^]]\\\\\\\[[[ZZYZYYYXXXWXWWWVVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOOONNNMMÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÆÆÇÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÂÁÀÀÀ¿ÀÀ¿¿¿¾¾¾¾¾½½½¼½¼¼¼¼»»ººººº¹¹¹¹¸¸¸¸¸¸¢iihhhhhhghgggggffffffffffffeeeeeeedddddddcdcccccccccccccccbbbbbbbaaaaaaa````````_________^^^^^^^^^]]]]]]]]][ZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€tllkkjkjjjiiiiiihhggggggffeeeeddddcccbbbbbbaaaaa````___^_^]]]]]\\\\\\[[[[ZYYZZYXXXXXWWWWVVVVVUUUTTTTSSSRSSRRQQQQQPPPPOOONNNNNÊÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾½½½½½¼¼¼¼»»»»ººº¹º¹¹¹¸¸¸¸¸³iihhhhhhhggggggggffffffffffefeeeeededddddddcccccccccccccccccbbbbbbaaaaaaa`a```````___________^^^^^^]]]]]]]]\ZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW‹ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€rlllkkkjjjiiiiiiihhhggggffefeedddddcccbbbbbbbaaaaa`_`__^^^^^]]]]\\\\\[[[[ZZZYYZYXYXXXWWWWVVVVUUUUUTTTSTSSSSRRRQQQQPPPPPOOONNNËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÈÇÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼½¼¼¼»»»ººººº¹¹¸¸¸¸¸¸wiiiihhhhhggggggggfffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaa`a``````___________^^^^^^^^^]]]]]]\ZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWaŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€plllkkkkjjjjiiiiihhhhgggfgffffeedddddcccbbbbbaaaa````_`___^^]^]]]\\\\\\[[[ZZZZZYYYXXXWWWWWVVVVVUUUUUTTSTSRSRRRRQQQQPPPPOPPONNËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÀÀÀÀÀ¿¿¿¾¿¾¾¾¾½½½½½¼¼¼¼»»»»ºº¹¹¹¹¸¸¸¸¸›iiiihhhhhhgggggggfggffffffffefeeeeeeedddddddcccccccccccccccbbbbbbbbbaaaaaa```````_`_________^^^^^^^^]]]]]]]ZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWW{ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€ollllkkkjkjjjiiiiiihhgggggfffeeedeedccccbbbbbbbaaaaa``_`___^^^]]]]\\\\\[\[Z[ZZZYYYYYXXXWWWVVVVVUUUUUTTTSSSSRRRRRQQQPPPPPPOOOOËËËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÇÇÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÁÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»»»»»ººº¹¹¹¸¸¸¸±iiiiihhhhhhhgggggggffffffffffffeeeeeeedddddddccccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]ZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWŠŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚nllmlklkkkjjjjiiiiiihhhgggggfffeeeeedddccccbbbbbbaaa`````__^^^]^]]]\\\\\[[[[[ZZZYYYYYXXWWWWWVVVVUUUUUTTTTTSSRSRQRQQQQPPPPPPOOÌËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÆÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»»»»ºººº¹¹¹¹¸¸¸viiiiihhhhhhgghggggggfffffffffefeeeeeeeedddddddccccccccccccccbbbbbbbbbaaaaaa`````````___________^^^^^^]]]]]ZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWW`ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚nmmlmllllkkjjjiiiiiihhhhgggfgffeeeeeeddddcccbbbbbaaaa```_`_^^^^^^]]]\\\\\\[[[[ZZYZZYYYXXXWWWVVVVVVUUUUTTTSTSSSSRRQQQQQPPPPOPOÌÌÌËËÊÊÊÊÊÉÉÉÉÈÉÈÈÈÈÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÃÂÁÁÁÁÀÁÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸žiiiiiiihhhhhhggggggggffffffffeefeeeeeeddddddddccccccccccccccccbbbbbbbaaaaaaa`a`````_`_________^^^^^^^^]]]]ZZZZZZZZYZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWW|ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚nmmmmllklkkkjjjjjiiiihhhhhgggfffeeeededdcccccbbbbbaaa`````___^_^^]^]]\\\\\\[[[ZZZZYYYYXXXXXWWWVVVVVUUUUTTTTSSSSRRRRRQQPPPPPOOÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÂÃÂÂÁÂÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼»»»»»»ºººº¹¹¹¸´iiiiiihhhhhhhhhggggggfgfffffffffeeeeeeeddddddddcccccccccccccccbbbbbbbbaaaaaaaaa`````____________^^^^^^^]]]\ZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWŒŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…………………„…„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚znmmmmlllllkkkjjjjjiiiiihhhhggfgfffefeedddccccbbbbbaaaa`a`_______^^]]]]]\\\\\\[[Z[ZZYZYYXXXXXWWWWVVVVUUUUTTSSSSSSRRRQQQQQPPPPPÍÌÌËËËËËËÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÆÆÆÆÆÆÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»¼»»»ººº¹¹¹¸¸„iiiiiihhhhhhhhhgggggggfffffffffffeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaaaa``````____________^^^^^^]]\ZZZZZZZZZYZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWjŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡†††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚vnnnmmmllllllkkkjjjjiiiiihhhhhggggffeeeeedcddcccbbbbaaaaa``_`____^^]^]]]\\\\\\\[[[ZZZYYYYYXXXXWWWVVVVVUVUTTTTTTTSRRRRRQQQQPPPPÍÍÌÌËËËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÈÇÇÇÆÆÅÆÆÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÂÁÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»º»ºººº¹¹¹¨iiiiiiihihhhhhhgggggggffffffffffffeeeeeeedddddddcccccccccccccccccbbbbbbabaaaaaa``````_`___________^^^^^^]]ZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWW„ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††††………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚rnnnmmmmmmllklkkkjjjjiiiiihhhhgggfffffeedddddccccbbbbbaaaa````_____^^^^]]\\\\\\[[[Z[ZZZYYYXXXXWWWVWVVVVVUUUTTTSTSSSRRRQQQPQPPPÍÌÍÍÌÌËËËÊÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½¼¼¼¼¼»»»»»ºº¹¹¹¸iiiiiiiihhhhhhhhhggggggfgffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbbaaaaaa`````````_________^^^^^^^^]ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡††††††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚poonnnmnmmlllklkkjjjjiiiiiihhhggggfgfefeededdddccbbbbbbaaaa`````__^^^^^]]]\\\\\\\[Z[ZZZYYYYYXXXXWWWVVVVVVUUUTTTTSSRRSRQQQQQQPPÎÍÍÍÍÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÆÆÅÆÆÅÅÅÅÄÄÄÃÄÃÃÃÂÂÂÁÂÁÁÁÁÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼¼¼»»»ºº¹¹¹¹›iiiiiiiihhhhhhhhggggggggfffffffffffeeeeeeeedddddddcccccccccccccccbbbbbbbabaaaaa`````````___________^^^^^^ZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWW{ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚ooonnnnnmmmllkkkkkkjjiiiiiihihhhhggfffffeeedddccccbbbbbbaaa```______^^^^]]]\\\\\\[[[[ZZZYYYXYXXXXWWWWVVVVUUUUTTTSTSSSSRRQRQQQPÎÍÍÌÌÍÌÌÌËËËÊÊÊÊÊÊÊÉÉÈÉÈÇÈÇÇÇÆÆÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÂÁÁÁÁÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»»ººº¹¹µiiiiiiiiiiihhhhhghggggggffffffffffefeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaaa```````_____________^^^^^[ZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXWWWWWWWWWWWWWWŽ‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††…††……………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚}oooononnmmmlmllkkkkkjjjjiiiiihhhhgggfgffffeeddddccccbbbbbaaaaa``_____^^^^]]]]\\\\\[[[[ZZYYYYXYXXXWWWWVVVVVVVUUTTTTSSSSSRRRQQQPÎÎÎÍÍÍÌÌÌËËËÊËÊÊÊÊÉÉÉÈÉÈÈÇÈÇÇÆÇÆÆÆÆÅÄÅÄÄÄÄÄÃÄÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ººº¹¹iiiiiiiiihhhhhhhgggggggggfffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaaaa``````___________^^^^^\ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWs‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚xooooonnnnnmmmmllkkkkkjjjiiiiihhhhgggggfffffeeeddddccbcbbbbbaaaa``_____^^^]]]]\\\\\[[[[[ZZZZYYYYXXXXWWVWVVVVVVUUTTTTSSSSRRRRRQQÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÊÉÉÉÈÈÈÇÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÂÁÂÁÁÁÀ¿¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼¼»»ººººº³iiiiiiiiiihhhhhhhhhggggggggffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaaa````````__________^^^^][[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXWWWWWWWWWWWŒ‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚spooooonnnnmmmmllllkkkkjjjiiiiiiihhhggggfffeeeedddddcccbbbbbaaa````______^^]^]]]\\\\\[[[Z[ZZYYYXYXXXWWWWVVVVVVUUUTTTTSSSRSRRRQQÎÎÎÍÍÍÍÍÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¾¾¾¾¾¾½½½½½¼¼¼¼¼»º»ººº¹‹iiiiiiiiiiiihhhhhhghgggggfffffffffffeeeeeeeeeddddddcccccccccccccccccbbbbbbaaaaaaa`a`````____________^^^^[[Z[ZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXYXXXXXXXXXXXXXWWWWWWWWWWp‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒƒqpoooooonnnnmmmlllllkkkkjjjjiiiiiihhggggggffeeededddccccbbbbbaaaa````___^^^^^]]]\\\\\\[[[ZZZZYZYYYXXXXWWWWVVVVVUUUTUTSSSSSSRRRQÏÏÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÃÂÃÂÁÁÁÁÁÀÀ¿ÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»ºº¹²iiiiiiiiiiiihhhhhhhgggggggfgfffffffffeeeeeeedddddddddcccccccccccccccccbbbbbaabaaaaa````````___________^^[[[[ZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXWWWWWWWWWŒ‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ€ppppoooooonnmmmmmlmllkkkkjjjjiiiiiihhhggggfffeeeeeddddcccbbbbbbaaa````____^^^]]]]\\\\\\\[[ZZZZZZYYYXXXWWWWWVVVVUUUTUTTTTSSRRRRRÏÏÎÎÎÍÎÍÌÍÍÌÌÌËËËËÊÊÊÊÊÉÉÉÈÈÈÇÈÈÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÃÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»»ººº‹iiiiiiiiiiiihhhhhhhgggggggggfffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaa``````_``_________^^\[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXWXXWWWWWWp’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒyqqpoppoooonnnnmmmlmllllkkkjjjiiiiiihhhhgggfggfefeeeecdccccbbbbbbbaaaa````_^^^^^^]]\\\\\\\[[[ZZZZYZYXXXXXWWWVVVVVUUUUTUTTTSSSSRRÏÏÏÎÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÄÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½½½½½¼¼¼»¼»»»»º³iiiiiiiiiiiiihhhhhhghggggggfgfffffffffeeeeeeededddddddcccccccccccccccbbbbbbbbaaaaaa`a```````___________^[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWW’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡††††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒtqqqppppoooononnnmmmmllklkkjjjjijiiiiihhggggggfeeeeeddddccccbbbbbaaaa````___^^^^]]]]]\\\\\\[[[ZZZZZYXYYWWXWWWVVVVVVUUUTTTTSTSSSRÐÏÏÏÏÎÎÎÍÍÍÍÍÌÌËÌËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÆÇÆÆÆÅÆÅÄÄÄÄÄÄÃÄÃÃÂÂÂÁÂÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼»»»»ºº“jiiiiiiiiiiiihhhhhhhggggggggggffffffffeeeeeeeedddddddccccccccccccccccbbbbbbbaaaaaaa`````````__________^[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWu’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡††††††††††††††††………………„„…„„„„„ƒƒƒƒƒƒƒrqqqqpppooooonnnnnmmlmllllkkkkjjjiiiiihhhhggggfffefeeeddcdcccbbbbbaaaaaa```___^^^]]]]]\\\\\[[[[ZZZZZYYYXXXXWWWVVVVVVVUUTTTTTTSRSÐÐÏÏÏÎÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÁÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼¼»»ºº¶jjiiiiiiiiiiihihhhhhhhgggggggffffffffffefeeeeeeddddddddcccccccccccccccbbbbbbbaaaaaaaa```````___________\[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWW’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒ~rqqqqqppopooooonnnnmmmllllkkkjkjjiiiiiihhhhhhggfffeeeeddddcccccbbbbbaaaa``____^_^^^^]]]\\\\\\[[[ZZZYYYYYXXXWXWWWVVVVVUUUTTTTTTSSÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌËËËËËÊÊÊÊÊÊÉÉÈÈÈÈÈÈÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾½½½½½¼¼¼¼»¼»»» jijiiiiiiiiiiiihhhhhhggggggggffffffffffefeeeeeedddddddcdccccccccccccccccbbbbbbaaaaaaaa``````__________^[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXX~’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„ƒƒƒwrrrrqqpqpppooooonnnmmmmllllkkkjjjjjiiiiihhhgggggfffeeeeddddcccccbbbbaaaa`````__^_^^^]]]]\\\\\[[[[[ZZZZYYYXXXWWWWWVVVVVVUUUTTTSTSÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌËËËËËÊÊÊÊÉÊÉÉÉÈÈÇÈÈÇÆÇÆÆÅÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼»»»ººtjjiiiiiiiiiiiihhhhhhhggggggggffffffffffeeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaa``````````________[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXX_’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒtsrrrqqqqppoooooonnnnmmmmllllkkkkjjjjiiiiihhhhhggggfeefeededddccbbbbbbbaaa`a``_`___^^^^]]]]\\\\[[[[[ZZZYYYYYXXXWWWWVVVVVVUUUTTSTTÐÐÐÐÐÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼»»»º»¬jjiiiiiiiiiiiiihhhhhhhggggggggffffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbaaaaaaa`````````________[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXˆ’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…†………………„„„„„„„„€ssrrrrqqqpppppoooonnnnmmmmlllllkkkjjjjiiiiihhhhhgggfffeeeeeddddccbbbbbbaaaa````____^^^^^]]\\\\\\[[[[[ZZZYYYYXXXXWWWWVVVVUUUUTTTTTÐÐÐÐÐÏÏÏÏÎÎÎÍÎÍÍÌÌÌÌÌÌËËÊËÊÊÊÉÉÊÉÈÉÈÈÈÈÇÇÇÇÆÅÆÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»»jjjiiiiiiiiiiiiihhhhhhgggggggggfffffffffefeeeeeeeddddddcdcccccccccccccccbbbbbbabaaaaa`a``````________][[[[[[[[Z[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXs’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡ˆ‰‹”›¢©°´²¯¬¨¥ œ˜”Œ‡………„…„„„„„ƒxtsssrrrqqqqppppoooonnnnmmmmlmllklkkjjjjiiiiiihhhgggfgfffeeeeddddccccbbbbbaba`a````_^^^^^]]]]\\\\\\[[[ZZZYYYYYXXXXXWWWVVVVVUUUTTTTÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÇÈÈÇÇÇÆÆÆÅÆÅÄÅÄÄÄÄÃÃÃÃÂÃÂÂÂÁÁÀÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»¸jjjjjiiiiiiiiiiiihhhhhhhggggggfgfffffffffeeeeeeeedddddddcdccccccccccccccbcbbbbbbaaaaaa`aa`````________[[[[[[[[[[[ZZZZZZZZZZZZYZZYYYYYYYYYYYYYYYYYXXXXXXXX‘’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‰ˆˆˆˆˆ‰‰‹‹ŒŒ•𡦭³¶³°®«¨¥£¡žš˜–“Œˆ…„„„„„„tsssrrrqqqqqqpppooooonnnnnmmmmllllkkkkjjjjiiiihhhhhggggfffeeeedddccccbbbbbbaaa``````___^^^^]]]\\\\\[[[[ZZZZZYYYXXXXWWWWVVVVVUUUUTTÑÐÑÐÐÐÐÐÏÏÏÏÎÎÍÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÃÂÁÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»¼»©jjjjiiiiiiiiiiiihhhhhhhhhgggggfffffffffffefeeeeededddddddccccccccccccccccbcbbbbbabaaaaa````````______\[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXX†’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆŠ‹ŒŒŒŒŒŒ•𠦬²¶³°®«©¦£¡Ÿš™–”’ŽŒ‹‰†…„„tttsssrrrrqqqqqpppooooonnnnmmmllllklkkjjjjjiiiiihhhgggggffefeedddcdcccbbbbbbabaa````____^^^^]]]]\\\\\[[Z[[ZZYYYYYXXXXWWWWVVVVUUUTTÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÀÀÁÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»jjjjiiiiiiiiiiiihhhhhhhhgggggggggfffffffffeeeeeeeddda\XTPKHDB?<:98778:;=@DHLPV\bbaaaaa``````````____^[[[[[[[[[[Z[ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXs““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‹‹”𠦬±¶³°®«©¦¤¡Ÿš™—•“ŽŒ‹Š‰‡†…„ztttssssrrrqrqqqqpppoooonnnnnmmmmmlkllkkjjjjiiiiiihhhhgggfffffeeedddddccbbbbbbaaaa``_`__^_^^^]]]\\\\\\[[[[[[ZZYZYXXXXXXWWVVVVVVVUUTÒÒÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÍÎÍÍÍÌÌÌËËËÊËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼»»»¹njjjijiiiiiiiiiiiihhhhhhghggggggggfffffffffeeeb^YTOIGEDCA??><;:98866665555555566=DKS[aaa````````_____[[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXX[’“““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰Š‰Š‹Ž”™Ÿ¥«±¶´±®«©¦¤¡Ÿ›™–•“‘‹Š‰‡†…„ƒzywttsssssrrqrqqqppopooooonnnmnmmmlllkkkkkjjjjiiiihhhhgggfgffeeeeeddddcccbbbbbbaaaa```_____^^^^]]]\\\\\\[[[[[ZYYYYXYXXXWWWWWVVVVUUUÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÍÎÍÍÍÌÌÌÌËËËÊËÊÊÊÊÊÊÉÉÉÈÈÇÇÇÆÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÁÂÁÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½½½¼»»¼¯jjjjjjiiiiiiiiiiiihhhhhhhghgggggfffffffffc_ZUQPNLJHFEDA@?>=;:99876665555555656678889<;:99776655555555656778999:;<=>GPY````__`__[[[[[[[[[[[[[Z[ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYY““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠ‹“™ž¤ª°¶´²¯¬©¨¤¢ ›š˜–“‘Œ‹‰ˆ‡†…ƒ‚zyxvutsrrssrrrrrrqqppppopooooonnnmmmllllkkkkkjjjjiiiihihhgggfgffffeeddddcccccbbbbbaaaa````_____^^^]]]\\\\\[[\[ZZZYZYYXYXWXWWWWVVVVVUÓÒÑÒÑÑÑÑÐÐÐÐÏÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÂÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½¼¼¼¼»¼‰jjjjjiiiiiiiiiiiihhhhhhhhhggggggfeca^\ZWVTRPNLJIFEDB@?>=<;:9877655555555566678899:;<=>?@AAFOX````__\[[[[[[[[[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYp””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŒŽ’˜ž¤ª¯¶µ²¯­ª¨¥¢ žœš˜–”’ŒŠŠ‰‡†…„‚yxwutsrpnlossrrrrqqqqqppoooooooonnnmmmmllkkkkkjjjiiiiihhihgggfffffeeeeeddddcbcbbbbbaaaa```______]^]]]]\\\\\\[[[[Z[ZZYYYXXXXWWWWVVVVVÓÓÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÌÌÌÌÌÌËËËÊÊÊÊÊÉÊÉÈÈÉÈÈÈÇÇÇÇÆÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÂÁÁÀÀÀÀ¿À¿¿¾¾¾¾¾¾½½½¼¼¼¼¼¹rjjjjjiiiiiiiiiiiiihhhhhhhgggijifdb_][YWUSQOMKIHFECA@?><;:99877655555555666778999;<<=>?@ACDEFHQY`__^[[[[[[[[[[[[[[[ZZZZZZZZZZZZZYYYYYYYYYYYYYY_“”””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Œ’˜£©¯µµ³¯­ª¨¥£ žœš˜–”’ŽŒ‹Š‰‡†…„‚zxwvusrpnliggnsssrrqrqqqpppooooonnnnmmmmlllkkkkjjjjjjiiihhhhggggggffefeeedddccccbbbbbbaaa```_`___^^]^^]]]\\\\\[[[[ZZYYYYXXXXXWWWVVVVVÓÓÒÒÑÑÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÉÈÈÇÈÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÀÀ¿À¿¿¾¾¾¾¾½½½½½¼¼¼»³jjjjjjjiiiiiiiiiiiihhhhhhjmnljhfca^]ZXVTRPNLKHFEDB@?>=<;:9877666555555666788999:;<=>?@ABDEFHHJKNV\_\[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYŽ””””“““““““’’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŽŽŽ’—£¨¯µµ³°®«¨¦£¡Ÿ›˜–”“ދЉ‡†…„ƒ‚€xwvutrqoljgeb`eosrrqrqqqppppoooooonnnmmmmmmlllkkjjjjjiiiihhhhhgggffffeeededdddccccbbbbbaaa```_____^^^^]]]\\\\\[[\[[ZZZZZYYYXXXXWWWVVVÓÓÓÓÒÒÑÑÑÐÑÑÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÍÍÌËÌËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÈÈÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÁÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾½½½½½¼¼¼¼ªjjjjjjjiiiiiiiiiiiihhh„ rpnkigdb`][YWTSQOMKJHFECA@?><;:9987766555555566688899:;<=>?@@ACDEFHIKLMNPUe[[[[[[[[[[[[[[[ZZZZZZZZZZZZZYZZYYYYYYYYYˆ•””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹ŒŽŽŽŽŽ‘—¢¨®´¶³°®«©¦£¡Ÿ›˜—”“ŒŠ‰ˆ†…„ƒ‚{xvusrqomjhfc`^[]jrrrrqqqqqppoooooonnnnnmmmmlllkkkkjjjiiiiiiihhhgggfffeffeddddddcccbbbbbbaaa`````____^^]]]]]\\\\\[[[ZZZZZZYYYXXXWWWWVVÔÓÓÓÓÒÒÒÒÑÑÐÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËÊÊÊÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÂÁÁÁÁÀ¿¿À¿¿¾¾¾¾¾¾½½½¼¼¼¼»¢jjjjjjiiiiiiiiiiiio©°tqomjheca^]ZXVTRPNLKIGEDB@??=<;:988776555556556678899::;<=>?@ABDEFHIJKLNOQppme][[[[[[[[[[[[[[ZZZZZZZZZZZZZYYYYYYYYY‚••”””””“”““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽ‘–œ¢¨®´¶³±®¬©§¤¡Ÿ›™—•“‘Œ‹‰ˆ‡†„ƒ‚€xwvtsromjhfca_\]_biprrrqqqppppppoooononnmmmlmllklkkjjjijiiiiiihhhggggfffeeeeeddddccccbbbbbaaaaa``_`__^^^]]]]\\\\\\[[[[ZZZZZYYYXXXWWWWVÓÔÓÓÓÒÒÒÑÑÑÑÑÑÐÐÐÏÐÐÏÎÎÎÎÍÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÉÉÉÈÈÈÇÇÇÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾¾½½½½¼¼¼¼»™jjjjjjjiiiiiiiis’°°±°spnkigdb`]\YWURQOMKJHFECB@?>=;::987776555555666778999:;<=>?@ABCDEFHJKLMNQQpppnnf^[[[[[[[[[[[[[ZZZZZZZZZZZZZZZYYYYY|•••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽŽŽ–›¡§­³·´±¯¬©§¤¡Ÿž›™—•“‘ŒŠ‰ˆ‡……„‚€zwvtsrpnkifda_\]`bdgipsrrqqqqqpppoooooonnmnmmmlmlkkkkkjjjjiiiihhhhhhgggffefeeededddccccbbbbbaaa```_`___^^^^]]]\\\\\\[[[[[ZZZYYYXYXXXWWWÔÔÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÇÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½½¼¼¼»“jjjjjjiiiiiiu“°°°±±tqoljhfda^]ZXVTRPNLKIGEDBA??=<;:998777656556666678899::<==>?@ACDEFHIJKMNOQppppnooog^[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYw•••••””””””””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽ–›¡§­³·´²¯¬©§¤¢ ž›š—•“’ŒŠŠˆ‡†…ƒ‚wwutrqnkigdb`]^`begjknrrrqqqqqpppppoooonnnmnmmmmlllklkkjjjiiiiiihhhhgggfffeefeeedddccccbbbbbaaaa````____^^^^]]]\\\\\\[[[[[ZZZYYYXXXXXWWÔÔÔÔÔÓÓÒÓÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËËÊÊÊÊÊÊÊÉÉÈÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼jjjjjjjjiq’¯°°°°°±rpnkigdb`]\YWUSQOMKJHFECB@?>=;;:987766665556666788999:;<=>?@ABCEFGIJKLNOQRpppppnooopg][[[[[[[[[[ZZZZZZZZZZZZZZZZYr•••••••”•””””””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ•› ¦¬²·µ²¯­ª¨¥¢ žœš˜–“’ŽŒ‹Š‰‡†…„‚€yvutsqnljgeb`]]`bdgikmpsusrrrqqqppppooooooonnmmmmllllkkkjjjjjjiiiihhhggggfffffeeedddddccccbbbbabaaa```___^_^^]]]]]\\\\\\[[Z[ZZZZYYYXXXWWÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÏÎÎÍÍÍÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼»‹jjjjjjjŒ­¯¯°°°°°tqpmjhfda_]ZXVTRPNLKIGEDBA@?=<;:998876666666566778899:;<=>??AACDEFGIJKMNPQppppppoooopppf[[[[[[[[[[ZZZZZZZZZZZZZZZq••••••••”””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ”𠦬²·µ²°­ª¨¦¢ žœš˜–”’ŽŒ‹Š‰‡†…„ƒ}wvusqomjhec`^]_begilnpsuwyurrrqqqppppoooooonnnnmmmmlkllkkkjjjjiiiiihhhhgggggfffeeeedddccccbbbbbbaaa````____^^^]]]]]\\\\[\[[ZZZZYYYYYXXXWÕÔÔÔÔÓÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌÌËËÊÊÊÊÊÊÊÊÉÈÈÈÈÇÈÇÇÇÇÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÂÁÁÁÀÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼»»‹jjjj„¥­®¯°°±±±±rpnljgeb_]\YWUSQONKJGFECB@?>=<;:988776666666667778999:<<=>?@ABCEFGIJKLNOPRqqqpqpqooppppqoc[[[[[[[[[[ZZZZZZZZZZZZr•–••••••••”•””””””””““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ”™Ÿ¥«±·¶³°­ª¨¦£¡Ÿš˜–”’ŽŒŠ‰ˆ‡…„ƒ‚~xvttromkhfca^]`bdfiknpsuwz}yrrrqqqqpppoooooonnnmmmllmllkkkkkjjjiiiiiihhhhgggffffeeeedddcccbbbbbbaaaaaa``___^^^^^]]]]\\\\\\[[[ZZZZYYYYYXXÕÕÕÔÔÓÔÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÌÌÌËËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÃÄÄÃÃÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾¾½½¼½¼¼»jwš­­®¯°°°±±±uqpmjhfda_][XVTRPOLKIGEDBA@?><;:998877666666677778999:;<=>?@ABCDFFHJKLMNPQqqqpqqqqpopppqqrrl`[[[[[[[[ZZZZZZZZZZZs•––•••••••••””•””””””“““““““’’’’’’’’’’’’’’’’‘’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ“™Ÿ¤ª±¶¶³°®«©¦£¢Ÿž›™—”“ŒŠ‰ˆ‡†„ƒ‚~{vutrpnkhfca_]_bdgiknpsuxz}†Š~rrrqqpppppooooonnnnnmmmllllkkkkkjjjjiiiihhhghgggfffeeededddcccccbbbbaaaa```______^]^]]]]\\\\\[[[Z[ZZZZYYYXXÕÕÕÔÕÔÓÔÓÓÓÓÓÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÍÌÌÌËÌËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÀÁÀÀÀÀ¿¿¾¾¾¾¾¾½¾½½¼½¼¼¨«¬­®®°¯°±±±±spnkjgdb`][YWUSQOMKJHFECB@?>=<;:99877776666666778899:;;<=>?@ACDEFGIJKMNPQRqqqqqqqqqopppqqrrssh[[[[[[[[ZZZZZZZZZw––––•••••••••••””•”””””“““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ“™ž¤ª°¶¶´±®¬©¦¤¢Ÿž›™—•“‘Œ‹‰ˆ‡†…ƒ‚~~wutspnkigdb_]`begikmpsuwz}…Š“ƒrrrrqqqppppooooonnnnmmmlllllkkjkjjiiiiiihihhhgggfffffeedddddccccbbbbbbaaaa``_`___^^^^]]]\\\\\\[[[[ZZYZYXYXÖÕÕÕÕÕÔÔÔÓÓÓÓÓÒÑÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÌÍÌÌËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÆÆÅÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼ÀÊÏÀ­­®¯°°°±±±trpmjhfdb_]ZXVTRPNLKIGEDCA@?=<;:99877776666666778899::;==>?@ABCDFGHIKLMOPRqqqqqqqqqqqppqqrrrsstpa[[[[[[[[ZZZZZZ}––––––––•••••••••””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ’˜ž¤ª°¶¶´±®¬©¦¤¢Ÿžœš—•“‘Œ‹Š‰‡†…ƒ‚‚€~zutsqnligeb`]`bdgilnpruwz}…‰Ž’—š„rrqqqqqppppoooonnnnmmmmmlllklkjkjjjjiiiiihhggggfgffffeeeeddcccccbbbbbaaa````___^_^^^^]]]]\\\\\[[[[ZZZYYYYÖÕÖÕÕÔÔÔÔÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÎÍÌÌÌÌÌËËËËÊÊÊÊÉÉÉÉÈÈÈÇÈÇÇÇÆÇÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÀÀÀÀÀÀ¿¿¿¿¾¾¾¾½½½ÄÍÏÐÐÆ®®°°±±±±±spnligdc`^\ZWUSQPMKJHFEDBA?>=<;:99887766666666778899:;;==??@ABDEFGIKLMNOQRqqqqqqqqqqrpqqqrrssttuui[[[[[[[Z[ZZZ„—–—–––––––••••••••••””””””””““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ’—£©¯µ¶´±¯¬©§¥¢ žœš˜–”’ŽŒ‹‰‰ˆ†…„‚‚€~|vusqnmjhec`]_adgiknprtwz|…ŠŽ“–š™˜‚rrrqqpqppppooooonnnnmmmmlllklkkjjjjiiiiiihhhgggggffeeeeeddddcccbbbbbbaaaa```___^_^^]^]]]]\\\\[[[[ZZZZZYYÖÖÕÖÖÕÔÕÔÔÓÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÍÍÍÍÌÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÇÆÆÅÅÅÅÄÄÄÄÄÃÃÃÃÂÃÂÂÂÂÁÁÀÀÀÀÀ¿¿¾¾¾¾¾¾ÀÉÍÎÏÐÑÒ˯°°±±±±tqomkifdb_][YVTRQOLKIGEDCA@?=<<:99988766666677778899::;<=>?@ABCDEGHJKLNOPRqqqqqqqqrrrrrqqrrrsttuvvwq`[[[[[[ZZZ‹————–––––––•••••••••••””””””””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽ’—£©®´·´²¯¬©§¥¢¡žš˜–”’ŽŒŠ‰ˆ††„ƒ‚€~|xusromkhfc`^`befikmpsuwz|€…‰Ž’–šš™˜—rrqqqqqppppooooonnnmmmmlllllkkkjjjjiiiiihhhhggggffffeeeeeddcccccbbbbaaba`````____^^^^]]]]\\\\\[[[ZZZYZYÖÖÖÕÕÕÕÕÕÔÔÔÓÓÒÓÒÒÑÒÑÑÐÑÐÐÐÐÏÏÐÏÏÏÎÍÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÊÉÉÈÈÈÈÇÇÆÆÇÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¾¾¾¾ÂËÌÍÏÐÑÒÓÔд±±±²±spnljgec`^\ZWUTQPMKJHGECB@?>=<;:9988776666667777889::;;=>??@ACDEFHIKLMNPQRqqqqrqqrrrrrsrqrrssttuvwwxyg[[[[[[`‘—————–––––––––•••••••••”””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ‘—¢¨®´·µ²°­ª¨¥£¡ž›˜—”’ŽŒŠ‰ˆ††„ƒ‚€~}yutromkhfca^`bdfiknpruwy|€…‰’–›š™™—––{rrqqqpqqpppoooonnnnnmmmllllllkkjjjjiiiiihhhhhggggffeeeedddddccccbbbbaaaa`a``___^^^^^^]]]\\\\\\\[[[[ZZZ×Ö×ÖÖÕÕÕÕÔÔÓÔÓÓÒÓÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÍÌÌÌËËËËËÊÊÊÊÉÉÉÈÈÈÈÈÇÈÇÇÆÆÆÆÅÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¿ÅËÌÍÏÐÑÑÒÔÔÔÔ¾±²±trpmkhfdb_]ZXWURQNLKIGFDCA@?><;:99988777766677788899:;<==>?@BCCEFGHJKLNOQRqqqqqqqqrrrrsssrrrsttuvvwxyyym[[[[r–˜———————–—–––––––••••••••”””””””””““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽ–œ¢¨®³·µ³°­ª¨¦£¡Ÿ›™—•’ŽŒŠŠˆ‡†…ƒ‚€}|vtrpnkifdb__bdfikmpruwy{€…‰’•šš™˜——–•’wrrrqqqqppppooooonnnnnnmmllklkkjjjjjiiiiihhhgggggfgffefedddddccccbbbbbaaaaa``____^^^^]]]\]\\\\[\[[ZZZZ×××ÖÖÖÕÕÕÔÔÔÔÔÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÎÏÎÎÍÎÍÍÌÌÌÌÌËËËÊÊÊÊÊÊÉÉÈÈÈÈÇÇÈÇÇÆÆÅÅÅÅÅÅÄÄÄÄÃÄÃÃÃÃÂÂÁÁÁÁÁÁÀÀ¿¿ÀÆÊËÍÏÏÐÑÒÓÔÔÕÕÖɲ²sqnljgdc`^\ZWVTQPMLJHGECB@??=<;:9988777777777788899::;<=>??@BCDEFHJKKMOPQSqqqqrrrqrrrsstttrssttuuwwxyyz{t`[‚˜˜˜————————–––––––••••••••••”””””””””““““““’“’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽ–›¡§­³¸µ³°­«©¦£¡Ÿ›™—•“‘ŽŒŠ‰‰ˆ†…ƒ‚€~|wusqnkigeb__bdfiknprtwy{€„ˆ‘–šš™™˜—––•”Œrrrqrqqpppopoooooonnnmmmlllllkkkjjjijiiiiihhhggggffffeeeddddccccbbbbbbaaa`````_____^^]]]]]\\\\\\[[[ZZ×××ÖÖÖÖÖÕÕÕÔÔÔÓÓÓÓÓÒÒÑÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÍÌÌËËËËËÊÊÊÊÉÉÊÉÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÁÀÀÀÂÈÊËÌÎÏÐÑÑÒÓÔÕÕÖÖÖÑvrpmjieda_][XVTRQNMKIGFECB@?>=<::998877777777778899::;<<>>?@ABDEFGHJKLNPQRrrqqqqrrrrrsssttutsstuuvwxyyy{{|{‘˜˜˜˜˜—˜——————––––––•–••••••••••”””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘•›¡§­²¸¶³°®¬¨¦£¢Ÿ›™—•“‘Œ‹‰‰‡†…„ƒ€~}yutqoljgec`_bdfhknpruwy|„ˆŒ‘•™šš™˜—–––•”“…rrrrrqqpqppoooooonnnnmnmmllllkkkkjjjiiiiihhhhhggggffffeededdcccbbbbbbbaaaaa````__^^^^]]]]\\\\\[[[[ZZ×××××ÖÖÖÕÕÕÔÕÔÔÓÓÓÓÒÒÒÑÒÒÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÌÍÌÌÌËËËÊÊÊÊÊÊÉÊÉÈÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÃÂÃÂÂÁÁÁÁÀÃÈÊÊÌÍÏÐÑÑÒÓÔÕÕÖÕÖÖÖ—oljhec`^\ZWVTQPMLJIGEDBA??=<;:9998777777777778999:;;<=>?@ABCEEGHIKKNOPRSrqqqqrrrrsrssttuuutttuvvwxyyz{|‰˜š™˜˜˜˜˜˜——————––––––––•–••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘•› ¦¬²¸¶³±¯¬©¦¤¢ ž›™—•“‘‹Š‰ˆ†…„ƒ€~|{vtqomjgec`_bdfhkmpruwy|„ˆŒ‘•ššš™™˜—––”””“’rrrqqqqqpppoooooonnnmmmmmlllkkkkkkjjiiiiihhhhhgggfffeeeeeedddccccbbbbbbaa```_`__^_^^^^]]]]\\\\\[[[[××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÓÓÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÎÍÍÍÍÌÌËËËËËÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÃÇÉÊÌÍÏÏÐÑÒÓÓÔÕÕÖÖÖÖ™–”Šjifdb`][XWUSQOMKJHFECB@?>=<::998877777777788899:;;;=>??AACDEFGIKKMNPQRrrrrrrrrrrsrssuuuvvvtuvvwwyyzz|’™™š›š˜˜˜˜˜————————–––––––––•••••••••••”””””””“““““’’’’’’’’’’’’’’’’‘’‘‘‘‘‘‘‘”𠦬²¸·´±¯¬©§¤¢ žœš˜–”‘‹Š‰ˆ‡…„ƒ‚~|{wurpmkhfca_bdghkmpruwy{„ˆŒ‘•™›š™™˜——–•””“’‘wsrrrqqqpppppoooooonnnmmmlmllkkkkkjjjjiiiiihhhhgggfffffeeedddcccccbbbbbaaa`````____^^^^]]]]\\\\\\[[Ø×××××ÖÖÖÖÕÕÕÕÔÔÔÔÔÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÁÄÇÈÊËÌÎÏÐÑÒÓÓÕÕÖÖÖÖÖÖ—•’xeca^\ZWVSRPMLJIGEDCA??>=;:9998877777777888899:;<<>??@ACDDFGHJKLMOPRSrrrrrrrrrssstttuuvvvvuvvwxyyzˆ—˜™š››œ›˜˜˜˜˜˜———————––––––•••••••••••••””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘“šŸ¥«±¸·´²¯­ª§¤¢ žœš˜–”’ދЉˆ‡†…ƒ‚}|xurpnkhfda`bdfiknpruwy|ƒ‡Œ•™››š™™˜—–•””“’’‘ˆssrrrqqqqqppopoooonnnmmmmmmllkkkkjkjjjiiiihhhhgggfgfffeeeeddddccccbbbbbaaaa````___^_^^]]]]]\\\\\[[ØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÓÓÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÏÐÏÏÏÏÎÎÍÍÍÍÍÌÌÌËËÊËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÄÇÈÉÊÌÍÏÐÑÒÒÓÔÕÖÖÖÖÖ×™–”’Š‚f_][XWURQOMKIHFECA@?>=<;:998887777777788999::;<=>??@ACEEGGIKKMOPQRrrrrrrrrrrsssttuuvvvwwvvwxyy~’—˜™™š›œžœ˜˜˜˜˜—˜——————––––––––••••••••••••””””“”“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘”™Ÿ¥«±¶·µ²¯­ª¨¥£¡Ÿœš˜–”’ŽŒŠŠ‰‡†…„‚€}|yuspnkigda_bdfikmprtvy|ƒˆŒ”™››š™˜˜—––•””“’’‘srsrrrqqpqpppooooonnnnnmmllllkkkkjjjjjiiiiihhhhgggfffffeeeddddcccbbbbbbaaaaa```___^^^^^]]]]\\\\[[ØØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÔÓÓÓÒÒÒÒÒÑÑÑÑÐÐÐÐÏÏÏÏÏÎÎÎÎÍÍÍÍÌÌÌËÌËËËÊÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÅÅÅÅÅÅÄÄÄÄÄÃÃÂÃÄÅÇÉÊÌÍÎÏÐÑÒÓÓÕÖÖÖ×××ט–“ŽŒ‰‡„w\ZXVSQPNLJHGEDCA@?>=;::99887777777788899::;<<=??@ACDEFGIJKLNPPQSrrrrrrssssssttuuvvvwwwxwwxy–—˜˜™š››žŸŸž˜˜˜˜˜˜——————–—––––––•–•••••••••””””””””““““““““’’’’’’’’’’’’’’‘’‘‘‘‘‘‘‘“˜ž¥ª°¶·µ²°­«¨¥£¡Ÿ›˜–”’ŽŒŠ‰ˆ‡†…„ƒ€}|{vsqoligeb`bdfhkmprtwy|~ƒ‡Œ”™œ››š™˜˜—––•”“’‘‘Žvsrrrrqqqqpppppooooonnmmmmmlllklkjkjjjiiiiiiihhhggggffefeeeeddcdcccbbbbbaaaaa````____^^^]]]]\\\\[ÙØØØØ×××××ÖÖÖÕÕÕÔÕÔÔÓÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÍÎÍÍÍÌÌÌÌËËÊÊÊÊÊÊÊÊÉÈÈÉÈÈÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÃÃÃÅÇÈÉËÌÎÏÐÑÒÓÔÕÕÖÖÖ×××™—”‘Šˆ†„}kWUSQOMKJHFECB@??=<;:999887777777788999:;<<=>?@ABCDEGHIKKMOPQRrrrrrrrrsssssttuvvvwwwxyyx†”–——˜™™š›œžŸ  ž˜˜˜˜˜————————––––––––••••••••••””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘“˜¤ª¯µ¸µ²°­«¨¦£¡Ÿ›™—”’ދЉ‡†…„ƒ‚€~}{vsroljhec`bdfiknprtwy|‚‡‹”˜››š™™——––•”““’‘Ž„ssrrrrqqqqpppppoooonnnmmmmmmllklkkkkjjjiiiiiihhhggggfffffeeddddcccbbbbbbaaa`a````___^^^^^]]\\\\\ÙÙØØ×Ø×××××ÖÖÖÕÕÕÔÔÔÔÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÌÍÌÌËËËËËÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÆÆÅÅÄÄÄÄÄÄÄÃÄÆÇÉÊÌÍÎÐÐÑÓÓÔÕÖ×××××ט•“‘ŽŒŠ‡…‚€~|wcRPNLKIGEDCB@?><<;:999877777777889999;<<=>??ABBDEFHIJKMNOQRTrqrrrrrsssstttuuvvvwxxxy‡–••——˜™™š››žŸŸ ¡¢Ÿ˜˜˜˜˜˜———————–––––––––••••••••••””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘’˜£©¯µ¸¶³±®«©¦£¡Ÿ›™—•“‘ދЉ‡††„ƒ‚€~}{wtronjhecaadfiknprtwy|~‚‡‹”˜œ››™™˜——––””“’‘‘ŽŒ{ssrsrrrrqqqppppoooonnnnmmmmllllkkkkjjjiiiiiihhhgggggffffeeeedddcccccbbbbaaaa````____^^^^]]]\\\\ÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÏÏÏÎÎÍÎÍÍÌÌÌËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÃÄÅÇÈÊÌÍÎÏÐÑÒÓÔÕÕÖÖ××××™—”’‹ˆ‡„}{ywq^NKJHFECB@??=<;;:99887777778888999:;<==??@ABDEEGHIKLNOPQRqrrrrrrrsssttttuvvvwwwx†•™™™–—˜˜™™››œžŸ  ¡£¤ ˜˜˜˜˜˜—˜—————–––––––––•••••••••••””””””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘—£©¯´¹¶´±®¬©¦¤¢ ž›™—•”‘ދЉˆ‡…„ƒ‚~}|xtrpnkhfcabdfikmprtvy|~‚†‹”˜œ››š™™˜—–••”“’‘‘ŽŒŒ†sssrrrrrrqpqppooooooonnnnmnmmllkllkkjjjiiiiiihhhgggggffffeededddccccbbbbbaaaa```_`___^^^^]]]]\\ÙÙÙÙØØØ××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÓÒÒÒÑÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÅÅÅÅÄÄÃÃÅÆÈÊËÌÎÏÐÑÒÓÔÕÖÖ×××××ט–“‘ŽŒŠ‡…ƒ~|zxutrm\IGEDCA@?><<;:99988777777888999:;;<=>?@ABCDEFGIJKMNPQRSrrrrrrrrsssttuuuvvvwx‡•˜™™š›š—˜™™š›œœŸŸ ¡££¥¥¢˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““’“’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘—œ¢¨®´¹·³±¯¬©§¤¢ žœš—•”‘ދЉˆ‡†„ƒ‚€~}|xuspnligdbbdfhknortwy{~‚†‹“˜œœ››šš™˜—––•”““’‘ŽŒ‹‹}sssrrrrqrqqqppppooooonnnmmmmllllkkkkjjjjiiiiiihhhgggfffeeeeeeddddcccbbbbbbaaa````____^^^^]]]]\ÙÙÙÙÙØØØ×Ø××××ÖÖÖÕÕÕÕÔÔÔÓÔÓÓÓÒÓÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÎÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÉÈÈÇÇÆÇÆÆÆÆÅÅÅÃÃÄÆÇÉÊÌÍÎÐÑÒÓÔÕÕÖÖ×××××™—•“‹‰†„~{zwutqonj]EDC@@?=<;;:99887777778889999:;<=>??@ABDEFGIJKLNOQRSqrrrrsssssssttuuvvvw‹–˜™™šš››œš™™™›œœžžŸ ¡¢£¤¥¦§¡˜˜˜˜˜˜—˜—————––––––––•••••••••••””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘–œ¢§®´¹·´±¯­ª§¥£ žœš˜–”’ŽŒ‹‰ˆ‡†…ƒ‚~}|yvtpnligdbbdfhknprtwy|~‚†‹“—œœ›šš™˜——––”““’’‘ŽŽŒŒ‹Š†tssssrrrqqqqqpppoooooonnnmmmmmmlklkkjjjjijiiiihhhhhggfffeeeeeeddddcccbbbbbbba````_____^^^^]]]]ÚÚÚÙÙÙØ×Ø×××××ÖÖÖÖÕÖÕÕÕÔÔÔÓÔÓÓÓÒÑÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÏÎÎÍÍÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÅÆÂÂÄÅÇÉÊËÍÎÏÐÒÒÓÕÕÖÖ××רכ˜–”‘ŒŠˆ…ƒ~|zxvurpomkih_M@?>=;;:99988777787888999:;<<=>?@ABCDEFHIKLMNPQRrrrrrrsrsssttttuuv€–˜˜˜™™š››œž›™š›œžŸ ¡¡¢£¤¦¦§¨¢˜˜˜˜˜˜————————–––––––••••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘–›¡§­³¹·´±¯­ª¨¥£¡ž›˜—”’Œ‹Š‰‡†…„‚€~|yvtroljhebbdfhkmprtwy|~†ŠŽ“˜œœ››š™˜˜—––•”“’’‘Ž‹‹ŠŠ‰|tssssrrrrqqpqqpooooooonnnmmmmmlllkkkjkjjjiiiiiihhhggggffffeeeedddcccbcbbbbaaaa````_`___^^]]]]ÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÖÕÔÕÕÔÔÓÔÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÐÏÏÏÎÎÍÍÍÌÍÌÌÌÌËËËËÊÊÊÊÊÊÉÉÈÉÈÈÇÇÇÇÆÆÆÆÃÂÃÄÆÈÊÊÌÍÏÐÑÒÓÕÕÖÖ××××Ø×š˜•“ŽŒ‰‡…‚€~{zwusqpnljigfdaVC=;;:9988877778888999:;;<=>??AACDEFGIJKLNOQRSrrrrrrrssssstuuzŠ”–——˜˜™™š››œœœžŸœ›œžŸ ¡¢£¤¥¦§§ª¬¢˜˜˜˜˜˜———————–––––––––••••••••••”””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘•›¡§¬²¹¸µ²°­ª¨¥£¡Ÿ›˜—•’‘ŽŽŒ‹Š‰‡†…ƒ‚‚€~}zvtromjhecadfhkmprtwy{~†ŠŽ“—›œœ›šš™˜——–•”““’‘‘ŽŽŒŒ‹Š‰ˆ…ttsssrrrrrqqqqppppoooononnnmmmllllklkkjjjjiiiiiihhhggggfffffeeeeddddccbbbbbbaaaa````___^^^^]]ÛÚÚÚÙÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÔÔÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËÊÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÆÆÄÁÂÄÆÇÉÊÌÍÏÐÐÒÓÔÕÖÖ××Ø×ØØ›™–”‘‹ˆ…ƒ|{yvurpomljhgfdba`]RA:9988877777889999:;<<=>?@ABCEFGIJKLMOPQRrrrrrsrssstttz‰’•–––—˜˜™™™š›œœžŸŸ žŸ ¡¡££¥¦¦§¨«®± ˜˜˜˜˜˜———————–––––––––•••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘•› ¦¬²¸¸µ³°­ª¨¦£¢Ÿ›™—”“‘ދЉˆ†…„‚‚~}zwuromkhfdadfhkmprtwy|~…ŠŽ’—›žœœ›š™˜——––•”““’‘‘ŽŒ‹Š‰ˆˆ‡zttsssrrrrqqqqqqpooooooonnnmmmmmmllkkkkjjjiiiiiiiihhggggffeeeeedddcdccccbbbbbaaaa`````__^^^]]ÛÚÚÚÚÙÙÙÙØØØØØ××××ÖÖÖÖÖÕÕÔÔÔÔÓÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÆÀÁÄÅÇÈÊËÍÎÏÐÒÒÓÔÖ××××ØØØØš˜•“Œ‰‡…‚€}|ywusronmkihfecba`_^][RF888877788999:::;<=>??@ACDEFGIKKMNOQRTrrrrsrssss€Œ’”••––——˜˜™™šš››œŸŸ  ¡ŸžŸŸ ¡¢£¤¥¦¦¨ª¬¯²²œ˜˜˜˜˜˜—————————––––––•••••••••••”””””””“““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘”𠥬±·¸¶³±®«©¦£¢Ÿ›™—•“‘ŽŒŠ‰ˆ‡†„ƒ‚€~}{wuspnkifdadfijnortvy{~…‰Ž’—›žœœ›šš™——–••”““’‘‘ŽŒŒ‹‹‰‰ˆˆ‡‚tttsssrrrrrqqqqqppooooonnnnmnmmmllkllkjkjjjjiiiihhhggggggffeeeeedddccccbbbbbbaaa`````_____^^ÛÛÚÚÚÚÚÙÙÙØØ×Ø××××××ÖÖÖÕÕÕÕÔÔÔÔÓÓÒÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÁÁÃÄÆÇÉËÌÎÏÐÑÒÓÔÕÖ××××ØØØ›™—”’‹ˆ†ƒ}{ywusqomljigedcba`_^]\\\[WPF878899999:;<=>>@@ABCEFGHJKLMOQQSrrrrrrs€‰‘“”•”••–––——˜™™™š››œœžžŸ  ¡¢£ Ÿ ¡¢¢¤¥¦¦§©«®±³¶±˜˜˜˜˜˜˜˜———————––––––•–•••••••••”•””””””““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘”™Ÿ¥«±·¸¶´±®¬©¦¤¢ ž›š—•”‘ŽŒŠŠˆ‡†„„‚€}zwvsqnlifebcfiknortvx{~…‰Ž’—›žœœ››š˜˜——•••”“’‘‘ŽŽŒŒ‹Š‰ˆˆ‡†…xttsssssrrrqqqqqqpppooooononmmmlmlllkkkkkjjiiiiiihhhhhgggggfeeeededddccccbbbbbaaa`````___^^^ÛÛÚÛÚÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÖÕÕÔÔÔÔÔÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÈÇÂÁÂÄÅÇÈÊÌÍÏÐÐÒÓÔÕÖ×××רØÙØš˜–“‘ŽŒŠ‡…ƒ~|zxvtrpnmkihgedbba_^^]\\\[[[[[YUOG>99:;<=>>?@ABCDEFHIJKMNOQRSrxˆŽ’““””””•••–––——˜™™™šš›œžž  ¡¡££¤¢ ¡¢¤¥¥¦§¨ª­¯²´·¹­˜˜˜˜˜˜˜˜———————––––––••••••••••••”””””””““““““““’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘“šŸ¤«°¶¹·´±®«ª¦¤¢ ž›š˜•”‘ŽŒ‹Šˆ‡†…„‚€~{wvsroligebdfikmortvy{~€…‰‘—›žž››š™™˜—–••”““’‘‘ŽŒŒ‹Š‰‰ˆˆ†……~utttssssrrrqqqpqqppoooooonnnmnmmllllkkkkkjjiiiiiihhhhggggffffeeeeeddcccccbbbbaaaaaa`_`____^ÜÛÛÚÛÚÚÙÙÙÙØØØØ××××××ÖÖÖÕÕÕÕÕÕÔÓÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌËËËËËÊÊÊÊÉÉÉÉÈÈÄÀÁÄÅÇÈÊËÌÎÐÐÑÓÓÕÖ×××רØÙÙ›™—”’‹ˆ†„‚}{ywusqonljigfdcba`_^]]\\\\[[[[[[[\\\[ZWUQNKIFEDDEFIKPTY]chlq“““““””””””••••––——˜˜™™™šš›œžžŸ ¡¡¢£¤¤¥¤¢¢¤¥¦¦¨©«®±³¶¸º¼¨˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””””““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘“˜ž¤ª±¶¹·´±¯¬ª§¥¢¡žœš˜–”’Ž‹Šˆ‡†…„ƒ‚€~{xvtromjhecdfhkmprtwy|~€„‰’–›Ÿžžœœ›š™™˜—–•••”“’’‘ŽŒ‹ŠŠ‰ˆ‡††…„ƒututtstsrsrrrrqqqpppooooonnnnmnmmllllkkkkjjjjiiiiihhhhggggfgffeeedddccccccbbbbaba```````__^ÜÛÛÛÛÛÚÚÚÙÚÙÙØØØ×××××××ÖÖÕÖÕÕÕÔÔÔÓÓÓÓÓÓÒÑÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌËËËËËÊÊÊÊÊÉÉÉÆÀÁÃÄÆÈÉÊÌÎÏÐÑÒÓÔÕÖ××ØØØÙÙÙš˜–“‘ŽŒ‰‡…ƒ~|zxvtrpomkjhgedcba`_]]]\\\[[[[\\\\\\]]^^_`aabceefhijllmopqstu””“““””””””••••—–——˜˜™™šš›œžŸ  ¡¢£¤¥¥¦§¦£¤¦¦§¨«¬¯²µ·¹»½¿ ˜˜˜˜˜˜˜———————–––––––––•••••••••••””””””“““““““’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘“˜ž£©°µ¹·´²¯­ª§¥£¡Ÿš˜–”’‹Š‰ˆ†…„ƒ‚€~{xwtrpmkhfcdfhkmprtvy|~€„‰‘–šŸžžœ›š™™™˜—–••”““’‘ŽŽŒ‹‹Š‰‰ˆ‡‡†…„ƒyuuuttsssssrrrqqqqpppopooonnnnmmmlmllllkkkjjiiiiiiihhhhghgggfffeedeeddddcccbbbbaaaa`a`_`___ÜÜÛÛÛÛÛÚÚÚÚÙÙÙÙØØØØ×××××ÖÖÖÖÕÕÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÍÍÍÍÌÌÌÌËËËÊËÊÊÊÊÉÉÉÁÀÂÄÅÇÉÊËÍÎÐÐÒÓÔÕÖ××ØØØÙØÙœ™—”’‹‰†„‚~{zwusqonljihfedba`__^]\\\\\[[[\[\\\\]^^__aabcdefhhiklmnpqrtu”””“”””””””•••––——˜˜˜™™™šš›œžŸ  ¡¢¢¤¤¥¦§§¨¨¥¦¦§©¬®²³¶¸»¼¾À¸˜˜˜˜˜˜˜˜˜˜——————––––––––••••••••••””””””“””“““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘’˜£©¯µº¸µ²°­ª¨¥£¡Ÿ›™—”’Ž‹‹‰ˆ†…„ƒ‚€~{yxuspmkhfdcfhkmortwy{~€„‰‘•šžŸžœœšš™™˜——–•”““’‘ŽŽŒŒ‹‹Š‰ˆˆ††…„„ƒ~uututtsssrrrrrqqqqppppoooooonnnmmmmlllkkkkjjjjiiiiiihhhhgggfgfeeeeeeddcccccbbbbbaaaaa``___ÜÜÜÛÛÛÛÚÚÚÚÙÚÙÙÙØØØ×××××ÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÒÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÊÄ¿ÁÃÅÆÈÉÊÌÍÐÐÑÓÔÕÕÖ×ØØØÙÙÙÙ›˜–“‘Šˆ…ƒ}zxvtrponljigedcba`_^^]\\\\\\\\\\\\\]^^__aabcdeefhijkmnopqstu”””””””•”••••––———˜˜™™šš›››žŸŸ ¡¡¢£¤¥¥¦§¨©ªª§§¨«­°²µ·º»¾¿Á˜˜˜˜˜˜————————–—––––••••••••••••”””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘’˜£©®´»¸µ²°®«¨¦£¡Ÿ›™—•“‘ŽŒ‹Šˆ‡†„ƒ‚~{zxusqnligddfhjmortwy{~€ƒˆ‘–šžŸž›››š™˜˜—––”““’‘ŽŽŒ‹ŠŠŠ‰ˆ‡‡……„ƒ‚uuuuuttssssrrrrqqqqqpppooooonnnnnmlmmllkkkkkjjjjiiiihhhhggggfffeefedddddccccbbbbaaaa`a```_ÝÜÜÜÜÛÛÛÛÛÚÚÙÙÙØØØØ×××××××ÖÖÖÕÕÕÔÔÔÔÓÔÓÓÓÓÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÌÌÌËÌËËËÊÊÊÊÆ¿ÁÂÄÅÇÉÊÌÍÏÐÑÒÓÔÖÖ×ØØØÙÙÙÙ™˜”“‹‰‡„‚€}{zxutqpnlkihgedcb`__^]]\\\\\\\\\\\\]]^__`abbcdefhiiklmopqrtu•”””””””•••••–––—˜˜˜˜™™š››œœžŸŸ ¡¡¢£¤¥¥¦§¨¨ª«¬¬ª©¬¯²´¶¸»½¾ÀÁÃĤ˜˜˜˜˜˜˜˜—————————––––––•–••••••••””””””””“”““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘–¢©®´º¸µ³°®«¨¦£¢Ÿœ™—•“‘ŽŒŠŠˆ‡†…ƒ‚€~{zxutqomigdcfikmortvy|~€ƒˆŒ‘•™Ÿžžœ››š™˜——–••”“’‘‘ŽŽŒŒ‹‹‰‰ˆ‡††…„ƒ‚‚yuuututttsssrrrrrqqpppppooooonnnnmmmmlllklkkkkjjjiiiiihhhggggfffeeeeeddddccccbbbbaaaaa````ÝÝÜÜÜÜÛÛÛÚÛÚÚÚÙÙÙØÙØØ×××××ÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÓÒÒÒÑÑÐÑÑÐÐÐÐÏÏÏÏÏÎÎÍÎÍÍÍÌÌÌÌËËËËÊÊÊÁÀÂÃÅÇÈÊËÌÎÐÑÒÓÔÕÖרØÙÙÙÙÙÙ›™—”‘Ї†ƒ|{xvusqonljigfddba``_^^]]\\\\\\\\\\]]^__`abbcdefgiiklmnoqrtuv”•””””””••••––—–—˜˜™™™š››œœžžŸ  ¡¢¢£¥¦¦§¨¨©ª«¬¬­®®°²µ¸º»¾¿ÁÃÄĽ˜˜˜˜˜˜˜˜˜———————–––––––•––•••••••••””””””””““““““““’’’’’‘‘‘‘‘‘‘‘‘‘‘—›¢¨­´¹¸µ³°®¬©¦¤¢ žœ™—•“‘ŽŽŒŠŠˆ‡†…„ƒ€}{zxvtqomjhecfhjmoqtvy{}€ƒ‡Œ‘•™žŸŸžœ››šš™˜—––•”“’’‘‘ŽŒŒ‹‹Š‰ˆˆ‡†…„„ƒ‚‚|uuuuutttstssrrrrrqqqqppoooooonnnmmmmmlllkkkkjjjjjiiiiihhhgggggffffeeedddcccccbbbbbaaaa```ÝÝÝÝÜÜÜÛÛÛÚÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÕÕÕÔÔÔÓÔÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÐÏÏÏÎÎÎÍÍÍÍÌÌÌËËËËÊÿÁÃÄÆÇÊÊÌÎÏÐÑÓÔÕÖÖרØÙÙÙÙÚš—•“Ž‹‰‡…‚€~|zxvtrpomkjhgedbba`_^^]]\\\\\\\\\\]^^__``abcdefghijkmnoprsuv•””•””•”••••–––——˜˜˜™™™š››œžžŸ  ¡¢¢£¤¥¦¦§¨©ª«¬¬­¯²³±´·¸»½¿ÀÁÃÄÅŰ˜˜˜˜˜˜˜˜˜˜————————–––––•••••••••••”••””””””“““““““’’’’‘‘‘‘‘‘‘‘‘‘‘–›¡§­³¹¹¶´±®¬©§¤¢ žœš˜–”‘Ž‹Šˆ‡†…„ƒ‚€}{zywuromjhfdfhjmortvy{~€ƒˆŒ•™žŸžžžœ››š™˜——–••““’‘‘ŽŽŒ‹‹‹‰‰ˆ‡††…„ƒƒ‚€~uuuuutttttssrrrrrqqqqppoooooooonnnmmmlllllkkkkjijjiiiiihhhgggggfffeeeeddcdccccbbbbabaaa``ÝÝÝÝÜÜÜÜÜÛÛÚÚÚÚÙÚÙØÙØØØ×××××ÖÖÖÖÕÖÕÕÔÔÔÔÔÓÓÒÓÒÒÒÑÑÐÑÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌÌÌËËÈ¿ÀÃÄÆÇÉÊÌÎÎÐÑÒÔÔÖÖרÙÙÙÚÚÚÚ›™–”‘Šˆ†„}{yvusqonlkigfedcb``__^]]]]\\\\\\]]]^^__`abccdeghijklmopqstuv••••”••••••–––——˜˜™™™šš›œœžŸŸ  ¢¢£¤¥¦¦§¨©ªª¬¬­®°³¶¸¸¸º¼¾ÀÁÃÄÄÅÆÆ–˜˜˜˜˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““’“’’’’’‘‘‘‘‘‘‘–›¡§¬²¸¹¶´±¯¬ª§¤¢ Ÿœš˜–”’ŽŒ‹Š‰ˆ†…„ƒ‚~{{yxurpnkhfdfijmortwy{}€ƒˆ‹”™ Ÿžžœ››š™™˜——–•”“’’‘ŽŒ‹‹Š‰‰ˆ‡‡†…„ƒƒ‚€€wuuuuuutttssssrrrrqqqqppppooooonnnnmmmmllllkkkjjjjjiiiiihhhgggggffefeeeeddccccbcbbbbbaa`aÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙØØØ×Ø××××ÖÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÓÒÒÑÑÑÑÑÐÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÌÌÌÌÌËÁÀÁÄÅÆÈÊËÌÏÐÑÒÓÕÖÖרØÙÙÚÚÙÚš˜–“ŽŒ‰‡…‚€~|{xvurqomljigfdcba``_^^]]\\\\\\\]]]]^__`abccdefgiijlmooqrsuv•••••••••––––———˜˜™™™šš›œœžŸŸ  ¡£££¥¦¦§§¨©««¬­®°²´·¹¼½»½¿ÁÂÄÄÅÅÆÆŒ˜˜˜˜˜˜˜˜˜˜———————–—––––––•–•••••••••””””””””““““““““’’’’’‘‘‘‘‘•› ¦¬²¸¹·µ²¯¬ª¨¥£¡Ÿš™–”’Ž‹ŠŠˆ‡…„ƒ‚€~|{zxuspnlifdfikmoqtvy{~€ƒ‡Œ”™ ŸŸžœ›š™™˜——–•””“’‘‘ŽŒŒ‹‰‰ˆˆ‡††„„ƒ‚‚€yvvuuuuttttstsssrrrrqqqqppppooooonnnmmmlllllkkkjjjjiiiiihhhhghhggfffeeeedddccccccbbbbaaaaÞÞÝÝÝÝÝÜÜÜÛÜÛÛÚÚÚÚÚÙÙØØØØØ××××Ö×ÖÖÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÐÏÎÎÎÎÎÍÍÍÍÌÌËÅ¿ÁÃÄÆÈÉËÌÍÏÐÒÒÔÕÖ×רÙÙÚÚÚÚÚ›™—”’‹ˆ†„‚€}{ywusqpnmkihgfdbba``_^^]]\\\]\\]]]]^^_`abbcdefghijlmnoprstuw–••••••••–––————˜™™™™šš›œžŸŸ  ¡¡£¤¤¥¦¦§¨©ª«¬¬­®±³¶¸»¾¾ÁÀÀÁÃÄÅÅÆÆˆ†˜˜˜˜˜˜˜˜˜˜˜——————–—––––––•••••••••••”””””””““““““““’’’’’’‘‘‘”𠥫²¸¹·µ²°­ª¨¥£¡Ÿ›™—”’Ž‹‹Šˆ‡†„ƒ‚~|{{xvsqnligeeikmoqtvy{}€‚‡‹”™œ  ŸŸžœ››š™˜——–•””“’’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„„ƒ€~zvvvuuuuttttssssrrrqrqqqppppooooonnnmnmmllllkkkkkjjjjiiiihihhhggfgfffeeeedddccccccbbbbbaaÞÞÞÝÝÝÝÜÜÜÜÛÛÛÚÚÚÙÙÙÙØÙØØØ×××××ÖÖÖÖÕÕÕÕÔÔÔÓÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÎÎÍÍÍÍÌ¿ÀÂÄÅÇÉÊÌÍÏÐÑÒÓÔÖ×רØÙÚÚÚÚÚš˜–“‘ŽŒŠ‡…ƒ~|zxvusqomljigfdccba``_^]]\\\\]]]]]]^__`abbcddeghijklnopqssuv••••–••••––—–——˜˜˜™™šš››œœžžŸ  ¡¡¢£¤¥¦¦§¨©ª«¬­­®°²µ·º¼¾ÁÂÄÅÂÄÄÅÆÆÆ‡……˜˜˜˜˜˜˜˜˜˜˜—˜——————––––––––••••••••••””””””””““““““’’’’’’’‘”™Ÿ¥«±·º¸µ²°­«¨¦¤¡Ÿ›™—•“ŽŽŒ‹Šˆ‡†…„}|{yvtqoljgefhkmorsvy{}€‚†‹”˜œ¡  Ÿžœ›š™˜———••”““’‘‘ŽŒ‹Š‰‰ˆˆ‡†…„„ƒ‚€~}{wvvvuuuuuutttssssrrqrqqqqppopoooonnnnnnmlllklkkkkjjjiiiiiiihhgggggfffeeeeeddddcccbbbbbaaÞÞÞÝÝÝÝÜÝÜÜÛÛÛÛÚÚÚÙÚÙÙÙÙØØØ×××××××ÖÖÕÖÕÕÕÔÔÓÓÔÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÎÎÎÎÍÍÍÍÍÄÀÂÃÄÆÈÉËÌÎÐÐÒÓÔÕÖרØÙÚÚÚÛÛÚœ™—•’‹ˆ†„‚€}|ywvtqpnmliigedcbaa`_^^]]]\\\]]]]]^^__`abbcdeggiiklmnoprsuvw–•––••–––––—–—˜˜˜˜™™š›››žŸ  ¡¡¢£¤¥¦§§¨¨ªª«¬­®¯²³¶¹»¾¿ÂÄÅÇÈÇÅÆÆÆ‰†ƒ‹˜˜˜˜˜˜˜˜˜˜˜—˜———————––––––••–••••••••””””””””“““““’’’’’’’”™ž¤ª±·»¸µ³±®«¨¦£¢ œš—–“‘ŽŒ‹Š‰‡†…ƒ€}|{ywtqpmjhffhjmoqtwy{}€‚†‹”˜œ¡  Ÿžœœœšš™˜——•••”“’’ŽŽŒ‹‹Š‰ˆˆ‡†……„ƒ‚‚€~~}|vvvvvuuuuuutttsssrrrrqqqqqppppoooonnnnmmmmlllkkkkjjjiiiiiihihhggggggfeeeeeddddccccbbbbbaÞÞÞÞÞÝÝÝÜÝÜÜÜÛÛÛÚÚÚÚÚÚÙÙÙØØØ×××××ÖÖÖÖÕÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÊ¿ÀÂÄÆÇÉÊÌÎÏÐÑÓÔÕÖ×רÙÚÚÛÛÛÚ›˜–“‘Ї†ƒ~}{ywusqonljihgedbbaa__^]]]]\\\]^]^^^__`abbcdefghijlmnopqstuw–––––––––––———˜˜˜˜™ššš››œžŸ  ¡¡¢¢¤¤¥¦§¨¨©««¬­­®°³µ¸º¼¾ÁÃÄÆÈÉÊÉÈÆÆ‡…‚€}•˜˜˜˜˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””””””“““’’’’’’“™ž¤ª°¶»¸¶³±®¬©¦¤¢ žœš˜–”‘ŽŒ‹Š‰‡‡…ƒ‚€~|{yxurpmkhefhjmortwy{}€‚†Š“˜œ¡¡ Ÿžžœ›šš™™——––•”“’’‘ŒŒ‹Š‰‰ˆ‡††……ƒƒ‚€€~}}xvvvvvuuuuuutttstsssrrrqqqqpppooooooonnnmmmmllllkkkjkjjjiiiiiihhhgggfgffeeeedddddcccbbbbbßßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÙÙÚÙØØØØØ××××××ÖÖÕÕÕÕÔÔÔÓÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÃÀÂÃÅÇÉÊËÍÎÐÑÒÔÕÖÖרØÙÚÛÛÛÛÛœš—•’‹‰‡„‚€}|zxvtrpomkjigeecbba``_^]]]]]]]^^^^___`abbbdefghijklnooqrtuvx–––––––––––—˜˜˜˜™™™šš››œœžŸŸ  ¡¢¢¤¥¥¦¦§¨©ª«¬¬®¯¯²´·¹¼¾ÀÂÄÅÇÈÊÊËËʉ†ƒ~{†˜˜˜˜˜˜˜˜˜˜˜˜˜———————–––––––––••••••••••””””””””““’’’’’’˜ž¤©¯µ»¸¶³±®¬©§¥¢ žœš˜–”’ŽŒ‹‹‰‡†…ƒ‚€~}|zxurpnkiffhjmoqtvy{}†Š“˜œ ¡¡Ÿžžœ›šš™˜—––•”““’‘‘ŽŒ‹Š‰‰ˆˆ‡†……„ƒ‚‚€~~}xtuwwvvuuvuuuuutttsssrrrqrrqqqppppoooononnnmmmlllklkkkkjiiiiiiihhhhgggggffeeeeeddcdcccbbbbßßßÞÞÝÝÝÝÝÝÝÜÜÛÛÛÛÛÚÚÚÙÚÙÙØÙØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÏÐÏÏÎÎÎÊ¿ÁÂÄÆÈÊËÌÎÏÑÑÓÔÕÖרØÙÚÚÛÛÛÛž›™–“‘Šˆ†ƒ}{ywusqonljihfedcbaa___^^^]]]]]^]^___``abbcdefhiijlmnoqrtuuw–––––––——————˜˜˜™™™šš››œœžŸŸ  ¡¢££¥¥¦¦§¨©ª«¬¬­®¯±³¶¸»½¿ÁÃÅÇÈÉÊËËÌŒ‰‚€}{x“˜˜˜˜˜˜˜˜˜˜˜˜˜——————––––––––––••••••••••””””””””“’’’’’˜£©¯µ»¹¶³±®¬©§¥£¡žœš˜–”’Ž‹‹‰‰†…ƒ‚}|zxusqnligfhjmortvy{~€‚†ŠŽ“—œŸ¡¡ Ÿžœ›šš™™——––•”’’’‘ŽŽŒ‹‹Š‰‰ˆ‡‡†…„ƒƒ‚€€~}ytoqwwwvvvvuuuuutttssssssrrqqqqpppppoooononnmmmlllllkkkjjjjjiiiiihhhhghgggfeeeedeedcccccbbbßßÞÞÞÞÞÝÝÝÝÝÜÜÜÜÛÜÛÛÚÚÚÚÙÙÙÙØØ×Ø×××××ÖÖÖÖÕÕÕÕÔÔÔÔÔÓÓÒÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÎÄÁÂÄÅÇÉÊËÍÏÐÑÓÔÕÖרÙÙÚÚÛÛÛÛÛš˜”“‘ŽŒ‰‡…‚€~|zxvtsponkjigffdcbb`___^^]]]]]]^^___`aabbcdefghijlmnopqrtuwx––—–—––——–——˜˜™˜™™™š››œœœžŸŸ  ¡¡¢£¤¥¦¦§¨©©ª¬¬­®®°²´·¹¼¿ÀÃÄÅÈÉÊËËÌËŽ‹ˆ…‚|yw‚˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“’’’’—¢¨®´»º·´±®¬ª§¥£¡Ÿ›˜–”’‘ŽŒ‹Šˆ††„‚€~|{yvtqnljgfhjmortwy{}‚†ŠŽ“—› ¡¡ ŸŸžœ››š™™˜——–•”““’‘ŽŒ‹Š‰‰ˆ‡‡††„ƒƒ‚€~zupknwwwwvvvvuuuuututssssrrsrqrqqpqpppoooonnnnmmmmlllllkkjkjjjiiiiihhhhhggggffffeeeedddccccbßßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙØØØØ××××××ÖÖÖÕÕÕÔÔÔÓÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏË¿ÂÄÄÆÈÊËÍÎÐÑÒÔÕÖ×רÙÙÚÛÛÛÜÜž›™–”’‹ˆ…„}{ywutrpolkjigedcbba`__^^^]]]]]^^^___`abbcdefghijklmopqrtuvx———–—————————˜˜™™™™š››œžžŸŸ ¡¡¢££¥¥¦§§¨©ª«¬¬®¯¯±´¶¸»½ÀÁÄÅÇÉÊÊËËÌŠ‡…‚{vs’˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————–––––––••••••••••”””””””“’’’—¢¨®´ºº·µ²¯­ª§¥£¡Ÿ›™—•“‘ŽŒ‹‰ˆ‡…„ƒ€~|{yvtromjgfhkmortvx{}‚…ŠŽ“—›Ÿ¢¡ Ÿžžžœœ›šš™˜——–•”““’‘ŽŒ‹‹Š‰ˆ‡‡††…„ƒ‚‚€~{uqkgixwwwwwvvvuuuuutttstssssrrqqqqpqppooooonnnnnmmmlmllkkkkjjjiiiiiihhhhgggggfffeeeeddddccccàßßßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÚÚÚÙÙÙÙÙØØ×Ø××××ÖÖÖÖÖÕÕÕÔÔÓÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÄÁÃÄÆÇÊÊÌÎÏÐÒÓÕÕ×רÙÙÚÛÛÜÜÜÜœš˜•“Ž‰‡…ƒ|zxvusqomlkihgedcbba`__^^^^^^^^^___``aabccdffghiklmnoqrsuvwy———————————˜˜™™™™™š›››œœŸŸŸ  ¡¢£¤¤¥¦¦§¨©ª«¬¬­®¯°³µ¸º½¾ÁÃÅÇÈÉÊËÌÌÌŽ‹‰†ƒ{yvƒ˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————–—–––––––•••••••••••””””’’’–œ¡§®´ºº·µ²°­ª¨¦£¡Ÿ›™—•“‘ŽŽŒ‹Šˆ‡†„ƒ‚€~|{zwtromjhfhjmoqtvx{}€‚…‰Ž’–šŸ¢¡ ŸŸžžœ››š™˜˜—–•”““’’‘Ž‹‹‹Š‰ˆ‡‡……„ƒƒ‚€~|vrlgbdwwwwwvvvvvuuuuuttttssssrrrrqqqqppppoooooonnmmmmmlllllkjkjjjiiiiihhhhggggfffeeeeeeddccccàààßßßÞßÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÙÙÙÚÙØØØØ×××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÒÓÒÑÑÑÑÑÐÐÐÐÐÐÎÀÂÃÅÇÈÊËÎÏÐÑÓÔÕÖרÙÚÚÚÛÛÜÜÜž›™–”’‹ˆ†„€~{zwutrpomkjihfedcbaa`__^^^^]^^^^__``aabccdefghijkmnoprrtuwx———–———————˜˜˜™™™™šš››œžŸŸ  ¡¢¢£¤¥¦¦§¨©ªª«¬­­¯°±´·¹¼¾ÀÂÄÆÇÉÊÊÌÌÌŠ‡…‚€}{xusžž˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜——————–––––––•–•••••••••”•”””“’–›¡§­³¹»¸µ³°­«©¦¤¢ œ™˜•“’‘ŽŒ‹Š‰‡†…ƒ‚€}|zxuspmkifhjmortvy{}€‚…‰’—›Ÿ¢¢¡ ŸŸžœœ››™™˜——–”””“’‘Œ‹ŠŠ‰ˆˆ‡†…„ƒƒ‚€|wrmhc^cxxxxwwwvvvvuuuuuutttstssrrrrqqqqqppooooonnnnmmmmmllllkkjjjjjiiiiihhhhgggggfffefeddddcccàààààßÞÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÛÚÙÙÙÙØØØØØ×××××ÖÖÖÖÕÕÕÔÔÔÔÓÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÆÁÃÄÆÈÉËÌÎÐÑÒÓÕÖ×רÙÚÛÛÛÜÜÜÜš˜•“‘ŽŒŠ‡…ƒ|{yvusqonlkihgfdcbba``___^^^^^^^___`aabbcdefghijklnooqrtuvwy———————˜˜˜˜˜˜™™™™ššš›œœžžŸ  ¡¢££¤¥¥¦¦¨¨ªª«¬­­®¯±³µ¸»½¿ÁÃÅÇÈÊËËÌÌÌŽŒ‰‡ƒ~|ywuq‰££ œ˜˜˜˜˜˜˜˜˜˜˜˜————————–––––––••••••••••••””’–›¡¦¬³¸»¹¶³±®«©¦¤¢ œš˜•”’‘ދЉ‡†…„‚€}|zxuspnlighjmoqtvx{}€‚…‰Ž’–šŸ£¢¡  Ÿžžœ››™™˜˜—–•””“’‘‘ŽŒŒ‹Š‰‰‡‡†…„„ƒƒ‚€€}xsmic_^axxxxwwwwvvvvvuuuuttttssssrrrrrqqqppopoooooonnnnmmmmllklkjkjijiiiiiihhhhggggffefeeeedddcáàààààßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÛÛÛÚÚÚÚÙÚÙÙÙØØØ×××××ÖÖÖÖÕÕÕÔÔÔÔÓÔÓÓÓÒÒÑÑÑÑÑÐÐÐÐÂÃÄÅÇÉËÌÍÏÐÑÓÔÖ×רÙÚÛÛÛÛÜÜÜž›™—”“‹‰†„‚€~{zxutqqomljihfedcbba``_^_^^^^^^__``aabbcdeeggiiklmnoprstuwx—˜—˜———˜˜˜˜˜˜˜™™™šš››œžžŸ  ¡¡¢£¤¤¥¦¦¨¨ªª«¬¬­®¯°²µ·º¼¾ÁÃÄÆÇÉÊËÌÌÌ‹‡…ƒ€}{xvsqt¤£££¢Ÿš˜˜˜˜˜˜˜˜˜˜———————––––––––•••••••••••””•› ¦¬²¸»¸¶³±®«©§¥£ žœš˜–”’‘ŽŒ‹‰ˆ‡…„ƒ‚€}}{xvsqnljghjmoqtvx{}‚„‰‘–šž£¢¡¡ ŸŸžœœ›š™˜˜——•””“’’‘‘ŽŒŒ‹‹‰ˆˆ‡††…„ƒƒ‚€€~ysoje`^^_xxxxxwwwvwvvuvuuuutttttsssrrrrqqqqppppooooonnnmnmmmlllkkkjjjjjiiiiihhhhggggfffffeeedddcáááàààßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙÙØØ×Ø×××××ÖÖÖÖÕÖÕÕÔÔÔÔÓÓÒÓÒÒÒÒÑÑÐÐÐÉÂÄÅÆÈÊËÍÏÐÑÓÔÕ×רÙÚÛÛÛÜÜÜÝÝš™–“‘ŒŠ‡…ƒ|{xwusqpnmkihgfedbbaa``_^^^^_^___```abbccdefghijllnopqstuvx—˜——˜˜—˜˜˜˜˜˜™™™ššš›››žžŸ   ¡¢£¤¤¥¦¦§¨©ª«¬¬­®¯¯±³¶¹»¾¿ÂÃÅÇÉÊËËÍÍÍŽŒ‰‡„|zvurpnŽ£¤££¢¢¡Ÿ›˜˜˜˜˜˜˜˜˜———————–––––––•–•••••••••”𠦬±¸¼¸¶´²®¬©§¤£ Ÿš˜—”’‘ŽŒ‹Šˆ‡†„ƒ‚€~}{yvtroljhhkmoqtvx{}€‚…‰‘–šž££¡¡  Ÿžœœ››š™˜˜—–•””“’‘ŽŽŒ‹‹Š‰ˆˆ‡†……„ƒ‚‚€~ytoje`____yxxxxwwwwwvvvvuuuuuttttsssrsrrqqrqpqpppooooonnnnmmlllllkkkkjjjjjiiiihhhggggfffffeeeeddcâááààààßßßÞÞÞÞÞÝÝÝÝÝÜÜÜÛÛÛÚÛÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÔÔÔÔÔÓÓÒÒÒÑÑÑÑÐÄÃÄÆÈÉËÌÎÏÐÒÓÕÖ×רÙÛÛÜÜÜÜÝÝŸœ™—•“Ž‹‰‡…‚€}|zxvurqonljihgfeccba````___^_^___`aabbbcdefghijklnopqrtuvwx———˜˜—˜˜˜˜˜™™™™ššš››œœžžŸŸ  ¡¢¢¤¤¥¥¦§¨©©««¬­®®°°²µ·º½¿ÂÃÅÇÈÊÊÌÌÍÍ‹ˆ…ƒ€}{xvtqomx¤¤££¢¢¢¢¢¡ ™˜˜˜˜˜˜——————––––––––•••••••••••Ÿ¥«±·¼¹¶´²®¬ª§¥£¡Ÿ›š—•“’‘ŽŒ‹Š‰ˆ†…„‚€}|yvurpmjhhkmoqtvx{}€‚„ˆŒ‘•šž¢£¢¡  Ÿžžœ››š™˜˜—–••”“’‘‘Œ‹Š‰ˆˆ‡†……„ƒ‚‚€€~zupkfa_____zyyyxxxwwwwvvvvuuuuuttttssssrrrrqqqpppppooooonnnnmmmmlllkkkkjjjjjiiiihhhhggggfffffeededâáááàààààßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÔÔÔÔÓÔÓÓÒÒÒÒÒÑÑÎÂÄÅÇÉÊÌÍÏÐÒÔÔÕ×רÙÚÛÛÜÜÜÜÝ ›˜–”‘‹ˆ†ƒ}{ywusrpomliihfedcbba```_________`aabbbcdefghiikkmnoqrsuuwx˜˜˜˜˜˜˜˜˜˜˜™™™™™šš››œœžž   ¡¡¢£¤¥¥¦§¨¨©ª«¬¬®¯¯°²³·¸¼¾ÀÂÄÆÇÉÊËÌÍÍÍŒ‰‡…|zwurpnli˜¤££££££¢¡¢¡¡¡Ÿœš˜˜———————–––––––•––••••••••¤«°¶½º·´²¯¬ª§¦¤¢ žœš˜–“’ދЉˆ‡…„ƒ‚€}|ywurpnkhhjloqtvx{}‚„ˆŒ‘•™ž££¢¡¡  žžžœœ›š™˜˜—–••””“’‘ŽŽŒ‹ŠŠ‰ˆ‡††……„ƒ‚‚€€{vqlga______yyyyxxxxwxwwwvvvuuuuutttttssssrrrqqqqqppooooonnnnnmmmlmllklkkkjjjjiiiihhhhgggggfffeeeeeâááááààààßßßßÞÞÝÞÝÝÝÝÝÜÜÜÛÛÛÛÛÛÚÚÚÙÙÙØØØ××××××ÖÖÖÕÕÕÕÕÕÔÔÓÓÓÓÒÒÒÒÒÑÈÃÅÆÈÊËÍÎÐÑÒÔÕÖ×ÙÙÚÛÛÜÜÝÝÝÝŸœš—•“ŽŒ‰‡„‚€~|{xvtsqonljihgfedcbaa```________``aabbccdegghijlmnopqstuwxy˜˜˜˜˜˜™˜˜˜™™™™™šš››œœœžžŸŸ  ¡¢¢¤¤¥¦¦§¨©ª«¬¬­¯¯°°³¶¸»½¿ÁÄÅÇÉÊËÌÌÍÍŽ‹ˆ†ƒ€~{yvtqomki¤£¤££¢¢¢¢¢¡¡¡¡¡  Ÿžœš˜——————–––––••–•••••••ª°¶»º¸µ²°®«©§¥£ žœš˜–““‘ŽŒ‹‰ˆ‡…„ƒ‚~|zwuspnkihjmortvxz}‚…‡Œ‘•™ž¢£¢¢¡¡ Ÿžžœ›šš™˜——–••““’‘‘ŽŽŒ‹ŠŠ‰‰ˆ††……„ƒ‚‚€€|wrlhb_______zyyyyyxxxxxwwvvvvuuuuuuttttssssrrqqqqqpppppoooonnnmmmmmlllkkkkkjjjiiiiiihhhghgggffeeeeeâáâááááààßàßÞßÞÞÝÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÙÙÙÙÙØØ×Ø××××ÖÖÖÖÕÕÕÕÔÔÔÓÓÓÓÓÒÒÒÒÄÄÆÈÊÊÌÎÏÑÒÔÕÖרÙÚÚÛÜÜÝÝÝÝ ›™–”‘‹ˆ†„~{yxutrponljihgfdccbba```_______```bbbccdefghiiklmnoprtuvwy˜˜˜—˜˜˜˜˜™™™™™™šš›››œœžžŸŸ ¡¡¢££¤¥¦¦§¨©ªª¬¬¬®¯¯±²´·¹¼¾ÀÃÄÇÈÉËÌÌÍÍÎŒŠ‡…}zwurpnlihk¤¤¤¤£££¢¢¢¢¡¡¡¡     Ÿ žžžœœ›š™˜——––•–––––––¯¶½¼¹¸´²¯­©¨¥£¡Ÿ›™–”“’ŽŒ‹Šˆ‡†„ƒ‚€~}zxvsqnkihjmnqtvx{}‚„ˆŒ‘”™ž¢¤£¢¢¡ Ÿžžœ››š™™——––•”“’’‘‘ŽŽŒŠŠŠˆˆ‡††…„ƒƒ‚€|xrmhc_______`zzyyyyyxxxxwwwvwvvvuuuutttttsssrrrqrrqppppoooooonnnmnmmmmlllkkkjjjjjiiiiihhhhgggfffeefeââââááàáààßàßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÜÛÛÚÚÚÚÚÙÙØÙØØ×××××××ÖÖÖÖÖÕÕÕÔÔÔÓÔÓÒÓÒÌÄÅÇÈÊËÍÏÐÑÓÔÖ×רÚÚÛÜÜÝÝÝÝÝ œš˜•“‘ŽŒ‰‡…ƒ~}{yvusqonmkiihgedcbbaa````____```aabbbcdefghiiklmnoqrsuuwyz˜˜˜˜˜˜™™™™™™™™ššš›œœœžŸŸŸ  ¡¢££¤¥¦¦§§©©ª«¬¬­¯°°±³¶¸»¾ÀÁÄÅÈÉÊËÌÍÍ΋ˆ†ƒ~{ywtromkigd¤¤¤¤££££¢¢¢¢¡¡        ŸŸžŸžžœœœœ››››šš´½½º·µ²°­ª¨¦¤¡Ÿ›™—”“’‘ދЉˆ†…„‚€~{yvsqoljhjmoqtvx{}€‚„‡‹•™¢¤£¢¢¡  Ÿžœœ›šš™˜——–•””“’‘ŽŽŒ‹ŠŠ‰ˆ‡‡††„„ƒƒ‚€}xsnid`_______azzzzyyxyxxwxwwwwvvvuuuuuutttstsssrrqqqqpqpopooooonnnnmmmllllllkkjjjjjiiiihhhhhggfgfffefââââááááààààßßßßÞÞÞÞÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÙÚÙÙÙØØØØØ×××××ÖÖÖÕÕÕÕÔÔÔÔÓÓÓÓÓÈÅÆÈÊËÍÎÐÑÒÔÕÖרÙÚÛÛÜÝÝÝÝÝ ž›™—”’‹ˆ†„‚€~|zwutsponljihgeddcbbba```````_``aabbbcdeeghhijkmnopqrtuvxy™™˜˜˜˜˜™™™™™™™ššš››œœžŸŸ  ¡¢¢£¤¥¦¦¦§©©ª«¬¬­®¯°±²µ¸º¼¿ÁÃÅÇÉÊËÌÍÍÎΊ‡…‚€}zxusqoljhedz¤¤¤¤¤¤££££¢¢¢¡¡ ¡    ŸŸŸŸŸžžžžœœœ››š›š·½º¸µ³°­ª¨¦¤¢ žœ™—•“’‘Œ‹Š‰ˆ‡…„ƒ~|ywtromjhjmnqtvx{}‚„‡Œ”™¡¤££¢¡  Ÿžžœ›š™™˜——–•””“’’‘ŽŽŒ‹‹Š‰ˆˆ‡††…„„ƒ‚€~ytoie`_`______d{zzzyyyyxyxxxxwwvwvvuuuuuutttstssrrrqrqqppppooooooonnnnmmmlllllkkjjjjjiiiihhhhgggggfffeãâââââááàààààßßßÞßÞÞÞÝÝÝÝÜÝÜÜÜÜÛÛÚÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÕÕÔÕÕÔÔÓÓÓÒÄÆÇÉËÌÍÏÑÒÓÕÖרÙÚÛÛÜÝÝÝÝÝÝŸœš˜•“‘ŽŒŠ‡…ƒ}{ywutqonmkjigfedccbbaaa````_```aaabcccdefghijklmooqrtuvwyz˜™˜˜™˜™™™™™™™ššš››œœžŸŸ   ¢¢£¤¥¥¦¦§¨©ª«¬¬­®®¯±²´¶¹»¾ÀÃÄÆÈÊÊÌÍÍÎÎ‘Ž‹‰†ƒ~|ywtronkigecd¥¥¤¤¤¤£££££¢¢¢¡¡¡      ŸŸŸŸžŸžžœœœœ›››š¶»¸¶´±®«¨§¤¢ žœš˜•“’‘Œ‹‰ˆ‡…„„‚€~|ywtrpmkhjloqtvxz}€‚„‡‹”˜¡¥¤££¢¡ ŸŸœ››š™™———–•”““’‘ŽŽŽŒŒ‹ŠŠ‰ˆ‡‡†…„„ƒƒ€~ztoje`````_____e{{zzzyyyyyyxwxwwwvvvvuuuuuttttttsssrrqqqqqqpppoooooonnmnnmllllkkkkkkjjiiiiiihhhhggggfffããââââááááàààßàßßßßÞÞÞÝÝÝÝÝÜÜÜÛÛÛÛÚÛÚÚÙÙÙÙÙØØØ××××××ÖÖÖÕÕÕÕÕÔÔÔÓÓÎÅÆÈÊËÍÏÐÑÓÔÖ××ÙÚÚÛÛÝÝÝÝÝÝ¡žœ™—”’‹‰†„‚€~{zxvurqonmkiigfedccbba`````````aabbbccdefghhjklmnopqstuwxy™™™™™™™™™™™™šššš››œœžžŸ   ¡¢££¤¥¦¦§¨©ªª«¬­®®°±²²µ¸º½¿ÂÄÅÇÉÊËÍÎÎÎΊˆ…‚€~{xvsqomjifeba¥¤¤¤¤¤¤£¢£¢¢¢¢¡¡¡¡     ŸŸŸŸžžžžžœœœœ›››±¹¶´±®¬©§¥£¡žš˜–”’‘Œ‹Šˆˆ†…„‚€~}zwurpnkijmoqsvx{}‚„†‹”˜¡¥¤¤£¢¡  Ÿžžœ›››™™˜——–•””“’’‘ŽŒ‹‹Š‰ˆˆ‡††…„ƒƒ‚€zupkfa``````____h{{{zzzzyyyxxxxwxwwwvvvvuuuuuuttsssssrrqrqqqqpppooooonnnnmmmmllllkkkkkjjiiiiiihhhhgggfgfããããâââáááááààààßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÙÙÙØØØ×××××××ÖÖÖÕÕÕÕÕÔÔÔÓÊÆÈÉËÌÎÐÑÒÔÕ×רÚÚÛÜÜÝÝÝÝÞÞŸš™–“‘ŽŠˆ…ƒ}{ywutrpomljihffedcbbaa```````a`aabbccdeefhhijklnoorstuvxyz™™™™™™™™™ššššš›››œžžŸ  ¡¡¢££¤¤¦¦§§¨ªª«¬¬­®¯°±²´¶¹¼¾ÀÂÅÆÉÊËÌÍÎÎÎ‘ŽŒ‰†„|yvusonligedb`y¥¥¥¥¤¤£¤£££¢¢¢¡¡¡¡¡     ŸŸŸžŸžžžœœœœœ›¬·´±¯¬©§¦£¡Ÿš™—”“’ŽŒŒŠ‰ˆ‡…„‚€~}zxuspolijmoqsvx{}€„‡‹“˜œ¡¥¤¤£¢¡  Ÿžžœ››š™˜˜—–••”““’‘ŽŒŒ‹Š‰‰ˆ‡†……„ƒƒ‚€{vqlfb``````__`_`l{{{{zzzzyyyyyxxxwwwwvvvuuuuututtstsssrrrrqqqqpppooooonnnnnmmmllkllkjjjjjjiiiiihhhghgggfãããããâââáááááààßàßßßÞÞÞÞÝÝÝÝÝÜÝÜÜÛÛÛÛÚÚÚÙÚÙØØØØ×Ø××××ÖÖÖÖÖÕÕÕÕÕÔÔÆÇÉÊÌÍÏÐÒÓÕÖרÙÚÛÛÜÝÝÝÞÞÞ¡Ÿœ™—”’Ž‹‰‡…‚€~|zxvusqonlkjigffddcbbaaa````aaaabbbccdefghhijklmooqrsuuwxz™™™™™™™™™™™ššš››››œžŸ   ¡¡££¤¤¦¦¦¨¨©ª«¬¬­®¯°±²³¶¸»¾¿ÂÄÆÈÉÊËÌÎÎÏÏŠ‡…‚€~{yvtqomjigeba_c¥¥¥¤¤¥¤¤¤£££¢¢¢¡¡¡ ¡¡    ŸŸŸžžžžžžœœœ›¨µ²¯­ª¨¦¤¢Ÿš™—•““‘ŽŒŠ‰ˆ‡…„ƒ}{yvsqoljjloqsvxz}‚„†Š“˜œ¡¥¥¤¤¢¢¡ ŸŸžœœ›š™™˜—–••”““’‘‘Œ‹ŠŠ‰ˆˆ‡†…„„ƒ‚€{vqlgb`a````````__p{{{{{zzzzyyyxyyxwwwwwvvvuuuuututttssssrrrqrqqqpppooooonnnnnmmmmllkklkjjjjjiiiihhhhhhgggãããããââââááááàààßàßßÞßÞÞÝÞÝÝÝÝÝÜÜÛÛÛÛÛÛÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÕÕÕÕÔÐÆÈÊËÍÏÐÑÓÔÖרØÚÛÜÜÝÝÞÞÞÞÞ ›˜–“‘ŒŠˆ†„}{zwvtrqonljihgfeddcbbaa````aaaaabbbcdeefghiiklmnoqrstuwxz{™™™™™™™™™šššš›››œœžžŸŸ  ¡¡¢£¤¥¥¦§¨¨©©«¬¬­®®°°²²µ·¹¼¾ÁÃÅÇÉÊËÌÎÎÏÏ‘Œ‰‡ƒ|zwuspnljhfdb`_\”¦¥¥¥¤¤¤££££££¢¡¢¡¡¡¡      ŸŸŸŸžžžœœœ£²°­«©¦¤¢ ž›š—•”“‘Œ‹‰ˆ‡†„ƒ‚~{ywtromjjloqsvx{}‚„‡Š“˜œ ¥¥¥¤£¢¡  Ÿžœ››š™˜——–•””“’‘‘ŽŒ‹‹Š‰‰ˆ‡‡†„„ƒƒ|wrmhcaaa````````__t|{{{{{zzzyyyyyxxxwxwwwwvvuuuuuuuttsssssrrrqrqqppppooooonnnnmmmmlllkkkkkjjjjiiiiihhhhgggääããããâââââáááàààßßàßßßÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÚÙØÙØØØØ×××××ÖÖÖÖÕÖÕÕÍÈÉÊÌÎÐÑÒÔÕ×רÙÚÛÜÝÝÞÞÞÞÞ¡Ÿœ™˜•“Ž‹‰‡…‚|{yvusqpnmkjihgfedcbbaaa`a`a`aabbbbcddeffhiijllnopqrtuvxyz™™™™™™™™™™šš›››››œœžžŸŸ  ¡¢¢¢¤¤¥¦§§¨©©«¬¬¬®®¯°±²³¶¹»¾ÀÂÄÆÈÊËÌÍÎÏÎÏ‹ˆ…ƒ€}{yvtromkigeba_]\€¦¦¥¥¥¤¤¤¤££££¢¢¢¢¢¡¡ ¡      ŸžžžžœœœŸ±­«©§¥¢ ž›š˜–”“’ŽŒ‹Š‰ˆ†…ƒ‚~|ywuromkjlortvxz}€‚„†Š“—œ ¥¥¥¤£¢¡¡  žžœœ›š™˜˜—–••”““‘‘ŽŒ‹ŠŠ‰ˆ‡‡†„„„ƒ‚}xsnicaaaaaaa``````_x|{{{{{{zzzzyyyxyxxwwwwvvvvuuuuututtssssrrrrrrqqpqpoooooonnnnmmmmllllkkkkkjjjjiiiihhhhghäääããããââââáááàààààßßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÚÛÚÚÚÙÙÙØØØØ××××××ÖÖÖÖÕÕÕÊÈÊÌÍÏÐÑÓÕÖרÙÚÛÜÜÝÝÞÞßÞÞ ž›™–”‘‹ˆ†„€}{ywvtrponlkjigffedcbbbaaaaa`ababbbbddeefgiijklnopqrsuvwxz{šš™™™™™™ššš›››œœœœžžŸŸ   ¡¢££¤¥¦¦§¨¨©ª«¬¬­®¯°±²²µ¸º¼¿ÁÄÆÈÊÊÌÌÎÏÏÏ’Œ‰‡„|zwuspnljgfdba_]\l¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡      ŸŸŸŸžžžœ­«©§¥¢ Ÿœš˜–”“’ŽŒŠ‰ˆ†…„ƒ€|zwuspnkjloqtvx{}€‚„‡Š“—œ ¤¦¥¤£¢¢¡  Ÿžœ›š™™˜—–••””“’‘‘ŒŒŠŠ‰ˆ‡‡†……„ƒ‚‚~xtnidaaaaaaaa```````}||||{{{{zzzzyyyxxxxxwwwwwvvvuuuuutttttssrrrrrqqqppppooooooonnnmmmlllkkkkkjjjiiiiiihihhgääääãããããââââââáààààßßßÞÞÞÞÝÝÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙØÙØØØ××××ÖÖÖÖÖÖÔÈÊÊÍÎÐÑÒÔÕרÙÚÛÜÜÝÝÝÞÞßÞ¡Ÿš—•“ŽŒ‰‡…‚}{ywusrpomljiigfedcbbbaaaaaaabbbbbbcdeffgiiijlmnopqstuwxy{šššššš™šššš›››œœœœžŸŸŸ ¡¡¡££¤¥¦¦§¨©©ªª¬¬­®¯°±²²³·¹¼¾ÀÃÄÇÉÊËÌÎÏÏÏЋˆ†ƒ~{yvuqomkigecb`^\[Z£¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¢¡¡¡     ŸŸŸŸžžžžœœ¨©§¥£¡Ÿ›˜—•“’‘ŽŒŠ‰ˆ†…„ƒ‚€}zxvspnkjmnqsvxz}‚„†‰Ž’—›Ÿ¤¦¥¤¤¢¢¡¡ Ÿžžœ›šš™™˜—––””“’’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„ƒ‚‚ytojeabaaaaaaa``````f}}||||{{{{{zzzyyyxyxxwwwwwvvvvvuuuuutttssssrrqrqqqqppopooooonnnnmmmmmlklkkjkjijiiiiihhhhåäääããããããââââáááàààßßßßßßÞÞÞÝÝÝÝÝÜÝÜÜÛÛÛÛÚÚÙÚÚÙÙØØØØØ×××××ÖÖÖÖÑÉÊÌÎÏÐÒÔÕÖØØÙÚÛÜÝÝÝÞÞßßß ž›™–”‘‹ˆ‡„‚€~|zxvusqonmkiihgeedccbbaaaaaaabbbbccdeffghiijkmnopqstuvxyz{ššššššššššš››œœœœžŸŸ   ¡¢¢£¤¤¥¦¦§¨©ªª¬¬­®¯°°²²³¶¸»½¿ÁÄÆÈÊÊÌÍÎÏÐÏ’ŒŠ‡…€|{xusqnljhgdba_]\[Y¦¦¦¦¥¥¤¤¤¤¤¤££¢¢¢¢¡¡¡¡       ŸŸŸŸžžžžžœ¤¨¦¤¢Ÿ›™—•”“ŽŒ‹‰ˆ‡†„„‚€}{xvsqoljloqsvx{}‚„‡‰Ž’–› ¤¦¥¥¤£¢¡¡ ŸŸž››š™™˜—––””““’‘ŽŽŒ‹‹Š‰ˆ‡††…„„ƒ‚zupkfbabaaaaaaa``````l}}}|||{{{{{{zzyyyyyxxxxwwwwwvvvuuuuutttttssssrrqqqqqqpppoooonnnmnmmmllllkkkkkjjjiiiiihhhååääãäããããããââááááàààààßßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÚÚÚÚÚÙÚØÙØØØ××××××ÖÖÖÏÊÌÍÏÐÒÒÔÖרÙÚÛÜÝÝÝÞÞßßß¡ œš˜•“‘ŽŒ‰‡†ƒ}{ywutrpomlkiihffdccbbbbaaaaaabbbccddefgghijkmmnpqrsuvwxz{ššššššššššš›œ›œœžžŸŸ   ¡£££¤¥¦¦§¨©©ª«¬¬­®¯°±²³´·¹¼¾ÁÄÅÇÉËÌÍÎÏÐÏÐŽ‹ˆ†ƒ~|ywtronkigecb`^]\ZY~¦¦¦¦¦¥¥¥¤¤¤¤££££¢£¢¢¡¡ ¡      ŸŸŸžžžž¡¦¤¢ žœ™—•”“’ŽŒ‹Š‰‡†…„‚€~{yvtqomjloqtvx{}‚„†‰Ž’–›Ÿ¤¦¦¥¤¤¢¢¡¡ Ÿžžœ››™™˜——–•”““’’‘ŽŒ‹‹‰‰ˆˆ‡†……„ƒƒ€{vqlfbbbbbbaaaaaa`a```s}}}}||||{{{{{zzzyyyyyxxxwwwwwvvuuuuuututtssssrrrrrqpppppoooooonnnmnmmmllllkkkjkjjiiiiihhåååääãããããããâââáááááàààßßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙÙÙÙØØ×××××××ÖÍÊÌÎÐÑÒÔÕרÙÚÛÜÝÝÝÞÞßßßß¡žœ™—”’‹‰‡„‚€~|zywusrpnmljihggedcbbbbbbbaabbbbbccdefgghiiklmnoqrstuvxy{|ššššššš›š›š›œœœœœžžŸŸ   ¢¢££¤¥¦¦§¨¨©ª«¬¬­®¯°±²²³¶¸»¾ÀÂÄÆÈÊÌÍÍÏÐÐÐ’Š‡…‚€}zxvtqoljhgdbb_^\[ZYm§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¡¡¡¡     ŸŸŸŸŸžžžž¤¢¡žœš˜–•“’ŽŒŠ‰ˆ†…„ƒ€~|ywtromjlnqsux{}€‚„†‰Ž’—›Ÿ£§¦¥¤¤£¢¢¡ ŸŸžœ›šš™™˜—––•”““‘‘ŽŽŒ‹Š‰‰ˆ‡††…„ƒƒ€|wrlgbbbbbbaaaaaaaa````z~}}}||||{{{{{zzzzyyyyyxxxwxwwvvuvuuuuuttttssssrrrqqqqqppooooooonnnnmmmlllllkkkjjjjjiiiihæåååääääããããââââââáááààààßßßÞÞÞÞÞÝÝÝÝÜÜÜÜÛÛÛÛÛÚÚÙÚÙÙØØØØ×××××××ËÌÎÏÐÒÓÕ××ÙÚÚÜÝÝÝÝÞßßßߢ ›˜–“‘ŽŠ‡†ƒ}{ywutrqonlkjihgfddccbbbbbbabbbbbcdddffghiijkmnopqrtuvxyz{š›ššššš›šš›››œœžžŸ    ¡¢££¤¤¦¦¦§¨©ª«¬¬­®®°±±²³µ¸¹½¿ÁÄÅÈÊËÌÍÎÐÐÐÐ‘ŽŒˆ†ƒ~{ywuronlihecb`_]\[YX\§¦¦¦¦¦¦¥¥¥¥¥¤¤££££££¢¢¡¡¡ ¡    ŸŸŸžŸžžž¢ žœš˜–•”’‘ŽŒ‹Šˆ‡…„ƒ‚|ywtspmkmoqsuxz}„†‰Ž‘–›Ÿ£§¦¥¥¤£¢¢¡ ŸŸžœ››™˜˜——–•”““’‘‘ŽŽŒ‹‹Š‰ˆ‡‡†…„„ƒ‚|wsmhcbbcbbbbaaaaaaaaa`e~~}}}}|||||{{{{{zzzzzyyyxxxwwwwvvvvuuuuuttttssssrrrqqqqpqpopooooonnnnmmmllllkkkjkjjjjiiiiååååååäääãããããâââáâááàààààßßÞÞÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÚÛÚÚÚÚÙÙÙÙØØØ××××ÕËÍÎÐÒÓÔÖרÙÚÛÝÝÝÞÞßßààß žœ™—”’‹‰‡…ƒ€~|{yvusrpomlkiihfeddccbbbbbbbbbbccddeefgghijklnooqrsuuvxz{|›š››šš›››š›œœœœžžŸŸ   ¡¡££¤¤¥¦¦§¨©ªª«¬­®¯¯±±²³³¶¹¼¾ÀÂÅÆÉÊÌÍÎÏÐÐÐ’‹ˆ…‚€~zxvtqomjifeca_^\[ZYXW𧦦¦¦¦¥¥¥¥¥¤¤¤¤££££¢¢¡¢¡¡ ¡     ŸŸŸžžžžŸŸ›™—•”’‘‘ŽŽŒ‹Š‰ˆ†…ƒ‚}zxuspmklnqsuxz}€‚„†ˆ‘–šž£§¦¦¥¤££¢¡  Ÿžœ›šš™˜——––””“’’‘ŽŒŒ‹Š‰‰ˆ‡‡†„„ƒ‚|xsnhcccbbbbbaaaaaaaaaaal~~}}}}}||||{{{{{zzzyyyyyyxxwwwwwvvvuuuuuttttssssrrrqrqqqqpopooooonnnnmmmmmllkkkkjjjjiiiiææåååäåääããããããâââáâááààààßßßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÛÛÚÛÚÚÚÚÙÙÙÙØØ×Ø××ÒÌÎÐÐÓÔÕרÙÚÛÛÝÝÞßÞßßà࢟›™–“‘Šˆ†ƒ‚}{zxvtsqonmkjihgfedcccbbbbbbbbbccdddeffghijkkmnopqstuwxyz{››››››››››››œžžžŸ  ¡¡¢¢£¤¤¥¦¦§¨¨ªª«¬¬­®¯°±²²´µ¸º½¿ÂÄÆÈÊËÍÍÏÏÐÐÐ‘ŽŒ‰†„~|zwuspnlihfdb`_]\[ZXWVŒ§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¡¡¡¡     ŸŸŸŸžžžžžž›™—–•“‘‘ŽŒŠ‰ˆ†…ƒ‚}zxusqnlloqsuxz}ƒ†‰‘•šž£§§¦¥¤¤££¢¡ ŸŸžœ››š™™˜—–••””“’‘Ž‹‹ŠŠ‰ˆ‡‡†…„„‚}xtoidcccbbbbbbababaaaaaau~~~}}}}|||{{{{{{zzzzzyyyyxxwwwwwwvvuuuuuutttttsssrrrqqqqqppppooooonnnnmmmlllllkkjjjjiiiiææåæåååääääããããããâââááááààààßßßßÞÞÞÞÝÝÝÝÝÜÜÛÛÛÛÛÛÚÚÚÚÙÙÙØØØØ××ÑÍÏÐÑÓÔ×רÚÚÛÝÝÞÞßßßßà࡟™˜•“Ž‹‰‡…ƒ|{ywutrponlkiihffeddcbbbbbbbbbbccdeeeghhiiklmnoprrtuvwyz{}››››››››››œœœžŸŸŸ  ¡¡¢£¤¤¥¦¦§¨©ªª«¬¬­®¯°±²²³´·¹¼¾ÁÃÅÇÉÊÌÎÏÏÐÐÐ’Šˆ…ƒ€}{yvtronkigeca`^]\[YXVV}§§§¦¦¦¦¦¦¥¥¥¥¤¤£££££¢£¢¢¡¡¡¡     ŸŸŸžžžžžœ™˜–•“’‘ŽŒ‹‰ˆ‡…„‚‚€~{yvtqollnqsvxz}ƒ†ˆ‘•šž¢§§¦¦¤¤££¢¡¡ Ÿžœ››š™˜—––••”“’‘‘ŽŽŒ‹‹Š‰ˆ‡‡†…„„ƒytojeccccccbbbbbaaaaaaaab~~~}~~}}}|||{{{{{zzzzyyyyyxxwwwwwvvvvvuuuuttttsssssrrqrqqqpppooooonnnnnnmmlmlllkkkjkjjjiæææææååååääããããããââââáááàáàààßßÞÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙÙØØØØÐÎÐÑÓÔÖרÙÚÛÜÝÞÞßßßààߢ ž›™–“’‹ˆ†„€~{zxvusqpomljiigffedcccbbbbbbbbccddeefghiijklmooqrstvwxz{|››››››››››œœœœžžŸŸ   ¡¡¡££¤¥¥¦§§¨©ª««¬­®®¯°²²³³¶¸»½¿ÂÄÆÉÊËÍÎÏÏÐГ‘Œ‰‡„|zxuspnljhfdba_^\[ZYWWVo§§§¦¦¦¦¦¦¦¥¥¥¤¥¤¤££££¢£¢¢¢¡¡¡¡     ŸŸŸžžž›˜–•”“‘ŽŒ‹‰ˆ‡……ƒ‚€~{ywtqpmlnqsuxz}„†ˆŒ‘•šž¢¦¨§¦¦¤¤£¢¡  Ÿžœ››š™˜˜—–••”“’’‘ŽŒŒ‹Š‰‰ˆ‡‡†……„zupjfccccccbcbbbbbbbbaaaak€~~}}~}}}}||{{{{{zzzzzyyyxxxxxwwwvvvvuuuuuuttttssssrrrrqqqqppoooooonnnmmnmmllkkkkkjjjjjççæææååååääääãããããââââááááàààßßßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÜÛÛÛÚÚÚÙÙÙÙÙØØ×ÏÐÑÒÔÕ××ÙÚÜÜÝÝÞßßààààà¡Ÿš˜•“Ž‹Š‡…ƒ|{zwutrponlkjihgfedddcbbbbbbcbccddeefghiijklmnopqstuvxy{|}›››››œ›œœœœœœžžžžŸ   ¡¡¢¢£¤¥¥¦¦§¨©ª««¬­®®¯°±²³³´·º½¾ÂÄÅÇÊËÌÎÏÐÐÐÑ“Šˆ…ƒ€~{yvtromkigfcb`_]\[YXWVVb¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¢¡¡¡      ŸŸŸŸžœ—•”“‘ŽŒ‹Š‰‡†…ƒ‚~|zwuromlnqsvx{}„†ˆŒ”𢧧§¦¦¥¤¤¢¡¡ ŸŸžœœ›šš™˜—––•””“’‘ŽŒŒ‹ŠŠ‰‰ˆ†††„„€zuqkfddcccccccbbbbbbbbaabbu~~~~~}}}|||{{{{{{zzzzyzyxxxxwwwwwvvuvuuuuttttttsssrrrqqqpppppooooooonnmmmmllkllkkjjjjçæææååååååääääããããããâââááááàààßßßÞÞÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÚÚÚÚÚÙÙÙÙØØØÏÐÒÓÕÖ×ÙÙÛÜÝÝÞßààààà࣠ž›™—”’‹‰‡„‚€~|zxvusrpomlkjhhgfeedcccbbbcbccccdeefghhiijlmnopqstuvwyz{|››››››œ›œœœœžžžŸ Ÿ ¡¡¢¢£¤¤¥¦¦¨¨©©ª«¬­­®¯°±²³³´¶¹¼¾ÀÃÄÇÈÊÌÎÏÏÐÐÑ”’Œ‰‡„|zxusqoljhfdba_^\\ZYXWVVV¨¨¨§§§§¦¦¦¦¥¥¥¥¥¥¤££££¢££¢¢¡¡¡ ¡    ŸŸŸŸžŸ–”“’ŽŒŠ‰ˆ‡…„ƒ|zwuspmlnqsvxz}„†‰Œ•™¢¦¨§¦¦¥¤¤¢¢¡  Ÿžœ›šš™˜˜—–••”“’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„€{vqlgdddcdcccccccbbbbbaaaac€€€~~~}~}}}||||{{{{{zzzyyyyyyxxwwwvwvvvuuuuuttttttsrrrrrqqqqqpppooooonnnnmmmmlllkkkkjjjçççæçææåååååääããããããâââââááááààßßßßßßÞÞÞÝÝÝÝÝÝÜÜÛÛÛÛÛÚÚÙÚÚÙÙØ×ÐÑÓÔÖרÙÛÜÝÝÞßßàààààà¡Ÿš˜•“‘ŽŒŠ‡†ƒ}{ywvtsqonmkjihggfedddcccbbccccddefffghiijklnnpqrstuwxy{|}››››œœœ›œœœœžžžŸ    ¡¡¢££¥¥¦¦¦¨¨©ª«¬¬­®¯°°±²³´µ¸º½¿ÁÄÆÈÊËÍÎÏÐÐÑÑ“‹ˆ…ƒ~{yvtronkihfdba_]\[ZXXWVVUž¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤££¢¢¢¡¢¡¡¡¡     ŸŸŸž™“’‘ŽŒŠ‰ˆ††„ƒ}{xvspnloqsvxz}„†ˆŒ”™¢¦¨§§¦¥¥¤£¢¡¡ Ÿžžœœ›š™™˜—––•”“’’‘ŽŽŒŒ‹‹‰‰‰‡††…„|wrmhddddddcccccccbbbbbbabbn€€€€~~~}~}|}|||{{{{{{{zzzyyxxyxxwxwvwvvvuuuuutttttssssrrrqqqqqpppooooonnnnmmmmlmlllkkjjçççæææææåååäåääãäãããããââáááááààààßßßÞßÞÝÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÙÚÙÙÙÖÐÒÔÕרÙÚÜÝÝÝßßàààááᣠžœ™—”’‹‰‡…‚}|{ywusrqonlkjihgffedddccccccccddeefgghiijkmmnopqstuwxy{{|›œœœ››œœœœžžŸŸŸ   ¡¡¢£¤¤¥¦¦§¨¨©ª««¬¬­®°±±²³³´·¹¼¾ÀÃÅÈÉËÌÎÏÐÐÑÑ”’‰‡…‚|zxusqomkigecb`^]\[YXWVVVU’©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¢¡¡       ŸŸŸœ’‘ŽŒ‹‰ˆ‡†…ƒ‚€}{xvtqolnqsux{}ƒ†‰Œ”™¡¦¨¨§¦¦¥¤£¢¡¡  Ÿžœ›šš™˜——–••““’‘‘Œ‹ŠŠ‰‰ˆ‡†…„‚}xrniddddddddcccccccbbbbbbbaz€€€€€~~~~}}}}||||{{{{zzzzyyzyxyxxxwwwvvvuvuuuututtsssrrrrrqqqqpppoooooonnnnmmmmlllllkkjèèçççæææææåååääääãããããâââááááááàßßßßßßßÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÛÚÚÙÙÙÕÒÓÕÖרÚÚÜÝÞÞßàààáááá¡ š˜–“‘ŽŒŠˆ†ƒ€}{zwvusqpnmlkihhgfeeeccccccccccddeffghhiijlmnopqrtuvwxz{|~œœœœ›œœžžžŸŸ   ¡¡¢££¤¤¥¦§§¨©ªª«¬­­®¯°±²²³´µ¸»½ÀÂÄÇÈÊÌÍÏÐÐÑÑÑ“‘Ž‹‰†ƒ~|zwurpnlihfdba_]\[ZYXWVVVU‰¨©¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡      ŸŸŸ•ŽŒ‹Š‰‡†…ƒ‚€}|ywtromnqsuxz}ƒ†ˆ‹”˜¡¦©¨¨¦¥¥¥¤£¢¡¡ Ÿžžœ››š™˜˜—––”““’’‘ŽŒŒ‹ŠŠ‰ˆ‡†……‚}ysnjdeddddddddcddccccccbbbbi€€€€~~~}}|}|||{{{{{zzzzyyyyxyxxwwwvvvvvuuuuuttttsssrrrqqrqpppppooooonnnmmmmmlllkkkkèèçççççæææåæååääääãããããããââáááàáààààßÞßßÞÝÞÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÚÙÙÕÒÔÖרÙÚÜÝÝÞßßàààááᣠž›š—•’Œ‰‡…ƒ~|{ywutrqonmkjihggfeeddcccccccdddeffgghijjllnooqrstvvxy{|}œœœœœœœœžžžŸŸ   ¡¡¢££¤¤¥¦¦§¨©ªª«¬­­®¯°±²²³´µ·¹¼¿ÁÄÆÈÊËÌÎÐÐÑÑÑ”’ŒŠ‡…‚€}{xvtqomkigecb`_]\[YXWWVVVU€©©¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢¢¢¢¡¡¡¡     ŸŸŸ™ŽŒŠ‰‡†…ƒƒ~|ywtrpmnpsuxz}ƒ†‰‹“˜¡¥©¨¨¦¦¥¥¤£¢¡¡ Ÿžžœœ›š™™˜—–••”“’’‘ŽŽŒ‹‹Š‰ˆ‡††…ƒ~ytojeeeedddddddcccccccccbbbbv€€€€€€~~~~~}}}|||{|{{{{z{zzzyyyyxxxwwwwvvvvvuuuuttttsssssrrqqqqqqppppoooonnnnmmmmmmlklkèèèçççççææåæååååäääãããããââââáááááààßàßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÚÚÚÚÚÕÓÕÖ×ÙÚÛÝÝÞÞààááááᥢ ›™–“’‹ˆ†„‚~|zxvusqoomlkiihggfedcdcccdcddddeefgghiijklmnopqstuvxy{{}œœœœœœœžžžŸ     ¡¢££¤¤¤¦¦§§©©ªª«¬­®¯°°±²³´µ¶¸»¾ÀÂÅÇÉÊÍÍÏÐÑÑÒÑ“Ž‹‰‡ƒ~|zwuspnljhfdba`^]\ZYXWWVVUUx©¨©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¢¡¡¡¡     ŸŸ‘ŽŒ‹‰ˆ‡†„ƒ~|zxurpnnqsuxz}ƒ†‰‹”˜œ¡¥©©§§§¦¥¤££¢¡ ŸŸžœ›šš™˜——–•””““‘ŽŒ‹‹Š‰ˆˆ‡†…„zupkeeeeedeedddddddccccccbbbg‚€€€€€~~~~}}}|}||{|{{{{zzzzzyyyxxxwwwwwvvvvuuuutttttsssrrrrrqqqpppppooooonnnmmmmmlllkéèèèçççææçæææåååääääãããããâââââáááàààààßßßÞÞÞÝÝÝÝÝÝÝÜÜÜÜÛÛÚÚÚÚÕÔÖרÚÛÜÝÝÞßààááááᣡŸœ™—•“ŽŒ‰‡…ƒ}{ywutrqonmljiihggeeddcccddddddeeegghhijkllnoprrsuvwyz{|~œœœœžžžžŸŸ   ¡¡¢¢£¤¤¥¥¦§¨¨ªª««¬­®®¯°±²²´µµ¸º¼¿ÁÄÆÈÊÌÎÏÐÐÑÒÒ•’Ї…‚€}{xvtqomjihedb`_^\[ZYXWVVUUUp©©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¥¤¤££££¢¢¢¢¢¡¡¡¡    Ÿ—Œ‹‰ˆ‡†„ƒ‚}zxuspnnqsvxz}„†ˆ‹“˜œ¡¥©©¨§¦¦¥¤££¢¡  ŸŸœ››š™˜——–••”““’‘ŽŒŒ‹Š‰‰ˆ‡‡†„€{vqkfeeeeeeedddddddddccccccccu‚‚‚€€€~~~~}}}|||{{{{{zzzzyyyyyxxwwwwvwvvuuuuuttttttssrrrrrqqqqpppoooooonnnnmmmlllkééèèèçèççççæææåååääääãããããââââáááàààààààßßÞÞÞÞÞÝÝÝÝÜÜÜÜÛÛÛÛÚÚÕÕרÚÚÜÝÝÞßßáááááᥢ ›™–”’‹ˆ‡„‚€~|zyvutrponlkjiiggfedddcdccddddeeegghhiijklnooqrstvwxz{|}œœžžžžŸŸŸŸ   ¡¢¢£¤¤¥¥¦¦§¨©ª««¬¬­®¯¯±²²³´µ¶¹¼¾ÁÃÅÇÊËÍÎÐÐÑÑÒÒ”‘ŽŒ‰‡ƒ~|zwuspoljifecb`^]\[YXXWVVVUUjªª©©©©©¨§¨§§§§¦¦¦¦¦¥¥¥¤¤¤£££££¢¢¡¡¡¡¡     ŸŸŽŠ‰‡†…ƒƒ}zxvsqnnqsuxz}„†ˆ‹“˜› ¥©©¨§§¦¥¥¤£¢¢¡ ŸŸžœ›š™™˜—––•””“’‘ŽŽŒ‹‹‰‰ˆˆ‡†…€|wrmgeeeeeeeeeeddddddddccccccgƒ‚‚‚‚€€€~~}}}}}}||{{{{{{{zzyyyyyxxxxwwwwvvvvuuuuutttssssrsrrrqqqpppoooooonnnnmmmllllééèèèèèèççççæææåååääääãããããââââááááàààààßßßßÞÞÞÝÝÝÝÝÝÜÜÛÜÛÛÛÚÖÖØÙÚÜÜÝÞßàààáâââ⣡Ÿš—•“ŽŒŠ‡†ƒ}{zwvtsqoonljjihggfeeddddcddddeeffgghiikklmooprstuvxyz{}~œžžžžžŸŸ   ¡¡¡¢¢¤¤¤¥¦¦§¨¨©ª«¬¬®®¯°°²²³´´µ¸º½¿ÂÄÇÉÊÍÍÏÐÑÑÒÒ–“Šˆ…ƒ€~{ywtromlihfdba`^\[ZYXXWVVUUUeªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£¤£££¢¢¡¡¡¡      –Šˆ†…„ƒ€~{yvtqonqruxz}ƒ†ˆ‹Ž“—œ ¤©©©¨§§¦¥¤£¢¢¡  Ÿžžœ›š™˜˜—–•””“’’‘ŽŽŒ‹‹Š‰‰ˆ‡†…|wrmheeeeeeeeeeeddddddddccccccvƒ‚‚‚‚‚€€€€€~~}~}}}|||{|{{{{{zzzyyyyxxxwxwwwvvvuuuuuuttttssssrrrqqqqppppooooonnnmnmmllléééèèèèçççççæææææååäääääããããããâââáááàààßàßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛ×רÚÛÜÝÞßàßáááââ⥢¡ž›™—“’‹‰‡…‚€~|{ywutrponmkjiihgfeeeeddcddedeeffgghiijklmnopqrtuvwxz{|~œžžžŸŸŸ    ¡¡¢££¤¥¥¦¦§§¨©ª«¬¬­®¯¯°²²³³µ¶·¹¼¾ÁÄÅÈÊËÍÎÐÐÑÒÒÒ”‘ŽŒ‰‡„|zxusqoljigecb`^]\[ZYXWVVVVUU`ªªª©©©©©¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¢¡¡¡¡¡     ‡†„ƒ€~|zwtroopsuxz|„†ˆŠŽ’—› ¤©©©¨§§¦¥¥¤£¢¡¡  Ÿž››š™™˜—–••”““’‘‘ŽŒŒ‹Š‰‰ˆ‡††‚}wsnheeeeeeeeeedeedddddddddccci„ƒ‚ƒ‚‚‚‚€€€~~~~~}}|||{|{{{{zzzzyyyyxxxxwwwwwvvvuuuuuutttsssssrqqqqqpqppooooononnmmmmléééééèèèèçççççææææåååäääããããããâââáááááàààßàßßÞÞÞÝÞÝÝÝÝÜÜÜÜÜÛÛרÙÛÛÝÝÞßàááâââã⤡Ÿœš˜•“‘ŽŠˆ†„€}{zxvusqpomlkjiihgfeeeddddddeeffffghiiiklmmopqrttuwxy{|}žžžžžžŸŸŸŸ   ¡¡¢¢£¤¤¥¦¦§§¨©©«¬¬¬®¯¯°±²³´´µ¶¸»¾¿ÃÄÇÉÊÌÎÐÑÑÒÒÓ•“Šˆ…ƒ€~{yvtrpnlihfdba_^\[[ZXXWVVVVUU\«ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢£¢¡¡¡¡¡    ˜†…„|ywtrpnpsvxz|ƒ†‰‹Ž’—› ¤¨ª©¨¨§¦¦¥¤¤£¡¢¡ ŸŸœ›š™™˜——–•””“’‘‘ŽŒ‹‹Š‰ˆˆ‡†ƒ~xsojfeeeeeeeeeeeeeddddddddddccy„ƒƒƒƒƒ‚‚‚€€€~~}}}}||||{{{{{{zzyyyyyxyxwwwwvwvvvuuuuutttsssssrrrrqqqppppoooooonnnnmmmêéééééèèèèèççææææåæåååäääãããããâââââááááààààßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÜ×ÙÚÛÜÝßßààáââââ⥣ ž›™—”’‹‰‡…ƒ|{ywutrpoomkjiihgffeeeedddddeeeffghiijjklnnopqstuvxy{||~žžžžžžžžŸŸŸ   ¡¡¡¢£££¤¥¥¦¦¨¨©©ª¬¬¬­¯¯°±²²³´µ¶·º¼¿ÂÄÆÈÊÌÎÏÐÑÒÒÒÓ”’Ї…‚}{xutqomkigedba_]\\ZYYWWVVVVUUY«ªªªª©©ª¨©¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¥¤¤££££¢¢¢¡¡¡¡     Ž„|{xurpnpsuwz|~„†ˆŠ’—›Ÿ¤¨ªª©¨§¦¦¥¥¤£¢¢¡ ŸŸœœœ›š™™˜—–••”““’‘ŽŒ‹Š‰‰ˆ‡†ƒytojfffeeeeeeeeeeedededddddddcn„„ƒ„ƒƒ‚‚‚‚‚€€€€~~~~}}}}|}||{{{{{zzzzyyyyxxxwwwwvwvvvvuuuutttttssrrrrrqqqqpppooooonnnnnnmêêééééééèèçççççææææåååäääääãããããââââááàààààààßßßÞÞÝÝÝÝÝÝÝÜÜÜÜØÙÛÜÝÝßààáââââã㥢 š˜•“‘ŽŒŠˆ†„€}|zxvusrponlkjiigggfeeeddddeeeffgghiijjklnnopqrtuuwxz{|~žžžžžžžžžžžŸŸŸ   ¡¡¢¢£¤¤¥¥¦¦§¨©ªª«¬¬­®¯¯±±²³´µµ·¸»¾ÀÃÅÇÉËÌÎÐÑÒÒÓÓ–“Ž‹ˆ†ƒ~|ywurpnljifeba`_]\[ZYXWVVVVVVVW««««ªª©ª©©©¨¨¨§§§¦§¦¦¦¦¥¦¥¥¤¤¤¤£¤£££¢¡¢¢¡¡¡   ›‚€|{xutqnpsuxz|ƒ…‰Š’—šŸ£¨«ª©©¨§¦¦¥¤£¢¢¡¡ŸŸžœœ›šš™˜—––•”““’‘ŽŽŒŒ‹ŠŠ‰ˆ‡‡„zupkfffefeeeeeeeeeeeeeddddddddd…„„„ƒƒ‚ƒƒ‚‚‚€€€€€€~~~~}}}}|||{{{{{zzzyyyyxxxxwwwwwvvvvuuuuuttttsssssrrqrqqqppppoooononnnnêêêêééééèèèèçççæææææååååääääãããããâââââáàààààßßßßÞÞÞÞÝÝÝÝÝÜÝÜÜÙÚÜÝÞßààááââãã㥣 Ÿ›™—”’Ž‹‰‡…ƒ}{ywvurqoomlkjihhgfffeeeeeeeeffggghiijkllmooqqstuwxy{|}~žžžžžžžžžŸžžŸ     ¡¡¢¢££¤¥¦¦¦§§¨©ª«¬¬­®®¯°±²³´´µ·¸º½¿ÂÄÆÈÊÌÎÏÐÒÒÓÓÓ”’‰‡„€}{xvtqomkigfdba`^\\[ZYXWWVVVVVVV««««ªªªª©©©©©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡¡¡  ‘}{xvtqopruxz|~ƒ…ˆŠ’–šŸ£§«ª©¨¨§¦¦¥¤¤£¢¢¡ Ÿžœœ›š™™˜——–••”“’‘ŽŽŒ‹‹Š‰‰ˆ‡…€{vpkfffffffeeeeeeeeeeeeddddddddv……„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}}|||{|{{{{{zzzzyyyyxxwxwwwvvvvuuuuutttsssssrrrqqqqqppppooooonnnmêêêéééééééèèçççççææææåååäåäääãããããâââááááàààààßßßßÞÞÝÝÝÝÝÝÝÜÜÚÛÝÝÞßàáááãããã㤢 ›™–“‘‹ˆ†„‚€}|{yvusrqonmlkjihggffeeeeeeeeeffgghiijklmmoopqrtuvxyz{|}€ŸžžžžžžžžžŸŸŸ    ¡¡¢¢££¤¤¥¦¦§¨¨©ªª«¬­­®¯°±²²³´µ¶·¹¼¾ÁÃÆÈÊËÍÏÐÑÒÒÓÓ–“‘Ž‹‰†„~{zwurpnljhfecb`_]\[ZYXWWVVVVVVVV¬¬«««ªªªªª©©©¨¨§¨¨§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡¡   …yvtroqsuxz|ƒ†ˆŠ’–šŸ£¨«ªª©©¨§¦¦¤¤£¢¡  Ÿžœœ›šš™˜—––•”“’‘‘ŽŽ‹‹ŠŠ‰ˆ‡…€{vqlgffffffefeeeeeeeeeeeeedddddm………………„ƒ„ƒƒ‚‚‚‚‚€€€€~~~~~}}}}||{{{{{{zzzyyyyxywxwwwwwvvvuuuuutttstssssrrrrqpqppoooooooonnêëêêêéééééèèèèçççççæææååäåäääãããããâââââááááàààßßßÞÞÞÞÞÝÝÝÝÝÜÝÛÝÝÞßàááââããã㦣¡Ÿœ™—•“ŽŒ‰‡…ƒ}{zwvusqponlkjiihggffeeeeeeeeeggghhijkllmnopqrsuvwxz{|}žžžžžžžžžŸŸŸŸ    ¡¡¢¢£££¤¥¥¦¦§¨©ª««¬¬­®¯°±²²³´µ¶·¸»½ÀÂÄÇÉËÌÏÐÑÑÓÓÔÔ”’Ї…‚€}{xvtqomkigfdba`^]\[ZYXWWWVVVVVVW¬¬««««««©©©©©¨©¨¨§¨§§§¦¦¦¦¦¥¥¥¤¤¤¤££££££¢¢¡¡¡   šxuroqsuxz|„…ˆ‹‘•šž£§¬ªªª©¨§¦¦¥¤¤¢¢¡ ŸŸžœ›šš™˜——–•””“’‘‘ŽŒ‹ŠŠ‰ˆˆ†|wrmhgggfffffeeefeeeeeeeeeeddddd€†………„„…„„ƒƒƒƒ‚‚‚‚€€~~~~~}}}}}|||{{{{{{zzzyyyyxxxxwwwwvvvuuuuuuttttstsssrrrrqqqqppooooonnnëëëêêêêéééééèèçççççççææåååäåäääããããããâââáááààààßßßßßÞÞÞÝÝÝÝÝÜÛÝÝÞßàááâãããã㥢 ›˜–“‘‹ˆ†„‚€~|{ywvtsqpnmlkjihhhffffeeeefeegghhhijjklmnooqrstuwxy{|}~€ŸžžžžŸŸžŸŸŸŸ    ¡¡¡¢££¤¤¥¥¦¦§¨¨ªª««¬­®¯°±²²²´µµ¶¸º¼¿ÁÄÆÉÊÌÎÐÐÒÓÔÓÔ–”‘Ž‹‰†„|zwusqomjigecba_^\\ZYYXWWWVVVVVVX¬¬¬««««ªªª©©©©¨©¨¨¨§§§¦¦¦¦¦¥¦¥¥¤¥¤¤££££¢¢¢¡¡¡¡¡  sppruwz|~ƒ†ˆŠ‘–šž£§««ªª¨¨¨§¦¥¤££¢¡¡ žžžœ››š™˜˜—–••”“’’‘ŽŒŒ‹Š‰‰ˆ‡‚}xsmhgggggfffffeefeeeeeeeeeeeedey‡†††…………„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}}}}|||{{{{{{zzzzyyyxxxwwwwwvvvvvuuuuttttsssssrrrqqqqpppoooooooëëëëêêêêééééèéèèççççææææåååäääääãããããââââââááààààßßßßÞÞÝÝÝÝÝÝÝÝÞßááââãããã㦤  œš˜•“ŽŒŠ‡†ƒ}|zxvtsrponmkkiihhggfffeeefeffgghhiijkkmnooprrtuvwyz{|~žžŸŸŸžŸŸŸŸŸŸ    ¡¡¡¡¢£¤¤¥¥¦¦§§©©ª««¬­®®¯°±²²³µµ¶·¸¼¾ÁÃÅÇÊËÍÏÐÑÒÓÔÔÔ•’Ї…‚€}{yvtronlihfdbb_^]\[ZYYWWWVVVVVVV[¬¬¬¬««««ªªª©ª©©©¨¨¨§§§§¦¦¦¦¦¦¦¥¥¤¤¤¤¤££££¢¢¡¡¡¡¡¡ „psuwz|~ƒ†ˆŠ•šž¢§«¬«ª©¨¨§¦¥¥¤££¢¡ Ÿžžžœ››š™™˜—–••”““’‘ŽŒ‹‹‰‰ˆ‡ƒ}xtnjhgggggfgffffffeeeeeeeeeeeeer‡†††††………„…„„„ƒƒƒ‚‚‚‚€€€~~~}}|||||{{{{{zzzzzyyyxxxxxwwwwvvvvuuuututtssssrrrqrqqqqpppoooooëëëëêëêêéééééééèèèççççææææååäåääããããããââââáááàààààßßßßÞÞÞÝÝÝÝÝÞßàáââãããäã㥢 ž›™—”’‹ˆ‡„‚€~}{yxutrqpomlkjiihhggffffffffffghhiijkllnoopqrttvwxz{|}€žžŸŸŸŸŸŸŸŸŸ    ¡¡¢¢¢£¤¤¤¥¦¦§§¨©ª««¬­®®¯°±²²³´µ¶·¸º½¿ÂÄÆÉÊÌÎÐÑÒÓÔÔÔ–”‘ŽŒ‰‡„|zxusqomkigedba_^\\ZZXXXWWWVVVVVW^¬¬¬¬¬¬««««ªªª©©©¨¨¨¨§¨§¦§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¢¡¡  }uwz|ƒ†ˆŠ‘•™ž¢¦«¬«ªª¨¨§§¦¥¤£¢¢¡ ŸŸŸœ››š™˜—––•””““‘‘ŽŽŒŒ‹Š‰ˆˆƒ~ytojghggggggfffffffefeeeeeeeeeem‡‡‡‡††††……„……„„„ƒƒƒ‚‚‚‚€€€~~~~}}}}|||||{{{{{zzzzyyxyxxxxwwwwvvvvuuuuuttttssrrrrqrqqqppppooooëëëëëëêêêêééééèéèèèèçççæææåååäåääääãããããââáááááàáàßßßÞßÞÞÞÝÝÝÝßààââãããäã㦤¡Ÿœš˜–“‘ŽŒŠˆ…ƒ‚~|zxwutrqoomljjiihhggffffeffffghhiiijklmnopqrstuvxy{{}~€ŸŸŸŸŸŸŸŸŸŸ    ¡¡¡¡¢££¤¤¥¥¥¦§§¨¨©««¬¬­®¯°±²²³³µ¶¶·¹»¾ÁÃÆÇÊËÎÏÐÑÒÓÔÕÔ•“‹ˆ…ƒ€~{yvtrpnljhgdcb`_]\[ZZYXXWWWWWWWWWb­­¬¬¬¬«¬«««ªª©ª©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¢¡¡¡¡¡š{z|~ƒ…ˆŠ•™¢¦«¬¬«ª©¨¨§¦¥¤¤£¢¡  ŸŸžœœ›š™˜——––•”““’‘ŽŒŒ‹ŠŠ‰ˆ„zupkhggggghgggfgfgffffffeeeeeeehƒ‡‡‡‡††††……………„„„„ƒƒƒ‚‚‚‚€€~~~~}}}}||||{{{{z{zzyzyyxxxwwxwwvvvvvuuuuuttttssssrrrqqqqqpppoooëëëëëëêêêêéééééèèèèçççççæææåæåååäääãããããâãââáááàààààßßßÞßÞÞÝÝÞàááââãããää䥣 ›™—”’‹‰‡…ƒ}{ywvtsqponmkkjiihggfffffffgggghiiijklmmoopqstuvwxz{|~ŸŸŸŸŸ ŸŸ Ÿ    ¡¡¡¢¢££¤¥¥¥¦¦§¨©©ª«¬¬­®¯¯°²²³³´µ··¸º½ÀÂÄÇÉËÍÏÐÑÒÒÔÕÕ—”‘ŽŒ‰‡„}zxusqomkihfdba_^]\[ZZYXXWWWWWWWWWg­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤£££¢¢¢¡¢¢¡¡ —|„†ˆ‹Œ•˜¢¦ª¬¬«ª©©¨§¦¥¤¤££¡¡  Ÿžœœ›šš˜˜——–•”““’‘ŽŒ‹ŠŠ‰ˆ„€{uqlghhghggggggggfffffffeeeeeeeeˆ‡‡‡‡‡‡††††………„„„ƒ„ƒƒƒ‚‚‚‚€€€~~~~}}}|||{{|{{{{zzzzzyyyyxxwwwwwvvvuuuuutttstssrssrrrqqqppppooëëëëëëëêêêêêééééèèèèçççæçæææåååååäääããããããâââáááàáàààßßßÞßÞÝÝÞàáâãããããä䦤¡Ÿš˜•“‘Šˆ†„‚€~|zywutspponlkjiihhggggffffgggghhiijjllmoopqsttvwxz{|}~€ŸŸŸŸŸŸŸ Ÿ      ¡¡¢¢£££¤¥¦¦¦§¨©©ª««¬­­®¯°±²²³´µ¶·¸¹¼¾ÁÄÆÈÊÌÎÏÑÒÓÓÔÔÕ•“‹ˆ…‚~|ywuspnljifecb`_^\[[ZYYXWWWWWWWWWWm­­¬¬¬¬¬¬¬««««ªªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤£££¢¢£¢¢¡¡¡  ”„…ˆŠŒ”˜¡¦ª­¬«ªª¨¨¨¦¦¥¤¤£¢¡¡ Ÿžžœ›š™˜——–•””“’‘‘ŽŒ‹‹Š‰‰†{vqlhhhhgghgggggggfffffffeeeeeee|ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}~~}}}|||{{{{{zzzzzzyyyxxwwwwvvvvvvuuuuutttsssssrrqrqqqpppppëëëëëëëëëêêêééééééèèèççççæçæåæååååääääããããâãââááááàààßßßÞßÞÞÞßáââããããää䦣 žœ™—”“Ž‹‰‡…ƒ}{zxvusrponmkkjiihhhggggfggggghiiijjlmmnopqqstuwwyz||~ ŸŸŸŸ        ¡¡¢¢¢£££¤¥¥¦¦§¨¨©©ª¬¬¬­¯¯°±²²³´µ¶·¸¸»¾ÀÃÅÇÊËÎÏÐÑÓÔÔÕÕ—”’Œ‰‡„‚}{xvtqomlihfdca`^]\\[ZYYXXWWWWWWWXWu®­­­­¬¬¬¬««««ªªªªª©¨©©¨¨¨¨§§¦§¦¦¦¦¥¦¥¥¤¤¤¤¤££¢¢¢¢¢¡¡¡ •†ˆŠ”˜œ¢¦ª¬¬«ªª©¨§§¦¥¥¤£¢¡¡¡Ÿžžœ›š™™˜—––””““’‘‘ŽŒ‹Š‰‰†|wrmhhhhhhhggghghggggfgfffffefeezˆˆˆˆˆˆ‡‡‡‡††††…………„…„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}|||{{{{{zzzyzyyxxxxwwwwvvvvvuuuuttttttsssrrrrqqqpppoëëëëëëëëëëëêêêééééèèèèèççççææææååäåääããããããââââáááááààààßßßÞÞàáâãããäãää§¥¢ š˜–“’Šˆ†…€~|{ywutsqponmkjjiihhhggggggggghhiijjklmnnoprrtuuwyz{|~  Ÿ ŸŸŸ       ¡¡¢¢££¤¤¥¥¦¦§§¨©©ª«¬­­®¯°±±²³´´µ··¸º¼¿ÂÄÇÈÊÌÎÐÑÒÓÔÕÕÕ–“ŽŠˆ†ƒ~|ywusqoljigedba_^]\[ZZYYXXWWXWWWWXX|­­­­¬¬¬¬¬¬«««ªªªªªª©©©¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡¡–ŠŒ”˜¡¥ª®¬««ª©©¨§¦¥¥¤££¢¡¡ Ÿžœœ›š™™˜——–••”“’‘‘ŽŒ‹‹Š‰‡‚|xrnhihihhhghggghggggggfffffffeezЉ‰‰ˆˆˆ‡‡‡‡‡‡‡†††………„„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}~}}}||||{{{{{zzzzzyyxyxxxwwwwvvvvuuuutttttsssrrrrqqqpqppëëëëëëëëëëëêêêééééééèèèççççæææåææååååääãããããââââááááààààßßßßÞàâãããääää䦣 Ÿœ™—•“ŽŒ‰‡…ƒ}{zxvutrppomlkjjiihhggggggggghhiijjkkmmnopqrsuuwxy{|}€ ŸŸ          ¡¡¢£¢£¤¤¥¦¦¦§§¨©©ª«¬¬­®®¯°±²³³µµ¶·¸¸¼¾ÀÃÅÈÉÌÎÏÐÒÓÔÔÕÕ—”’Ї„‚€}{yvtronkihfecba_^\\[ZZYXXXXXXXWXXXX„®®®®­­­¬¬¬¬««««ªªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¡¡¡¡¡˜“˜œ ¥©®­¬«ª©©¨¨§¦¥¥¤£¢¡¡ Ÿžœœœ›š™˜——–••”“’’‘Ž‹ŠŠ‰‡ƒ}ysniiiihhhhghghggghgggggfffffffzЉ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…†……„„„„„ƒƒƒ‚‚‚‚‚€€€~~~~}}}|}||||{{{{zzzyyyyxxxxxxwwvvvuvuuuuttttsssssrrrqrqqqpëëëëëëëëëëëëêêêêééééèèèèèççççææææååååäääããããããâââáááàáàààßßßÞßâããäääää§¥¢ ž›™–“’‹ˆ‡…‚€|{zwvtsrponmlkjiiihggggggggghhhiijkkmnnopqrsuuwxyz{}~€           ¡¡¡¡¢¢£££¤¥¥¦¦§§¨¨©ª««¬­­®¯°±²²´´µ¶·¸¸»½ÀÂÄÇÉËÍÏÐÑÓÔÔÕÕ™–“Ž‹‰†ƒ|zwusqomjigfdba`^]\[[ZZYYXXXXXXXXXXYŽ®®®­­­­­¬¬¬¬««««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤££££¢£¢¢¡¢¡  ›—œ¡¥©®­¬¬ªªª¨¨§§¦¥¤££¢¡  Ÿžœœ›š™™˜——–•”““’‘‘ŽŒ‹ŠŠ‰ƒ~ytojiiiihhhhhhhgggggggggggffgff|ŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡††††…†……„……ƒ„ƒƒƒ‚‚‚‚€€€€~~~}}}}||||{{{{{zzzzyyyxxxxxxwwwvvvvuuuutttttsssrrrqrqqqpëëëëëëëëëëëëëêêêêéééééèèèççççæçæåæååäääääãããããâââââááááààßàßßàããäääåä䦤¡Ÿœš—•“ŽŒŠ‡†ƒ‚€}|zxwutrqponllkjiihhgggggghghhiiijjklmnooqqstuvwyz{|}€         ¡ ¡¡¡¢¢£££¤¥¥¦¦¦§¨¨©ª««¬­­®¯°±²²³´´µ¶·¸¹¼¾ÁÄÆÈÊÌÎÐÑÒÓÔÕÕÖ—”’Ї…‚€}{yvurpnljhfecba_]\\[[ZYYXXXXXXXXXYYY˜¯¯®®­­­¬­¬¬¬¬««««ªªªª©©©©¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¡¡¡  Ÿ ¥©®­­¬««ª©¨§¦¦¥¤££¢¢  Ÿžœ›šš™˜—–––•”“’‘‘ŽŽ‹‹Š‰„zupkjiiiiiihhhhhgghgggggggggfff~‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyzyyxxxxwwwvvvvuuuuuuttttssssrrrqqqqëëëëëëëëëëëëëëëêêêéééééèèèèçèççææææåååääääããããããââáááááàààßßßàãäääååå§¥£ ž›™–”’‹ˆ‡…‚}{ywvusrqonmlkjjihhhghgghhhhhiiijjklmnnopqrsuvvxz{|}~€         ¡¡¡¡¡¢¢¢£¤¤¤¥¥¦¦§¨¨¨ªª«¬¬­®®¯±±²³´µ¶¶·¸¹»¾ÀÃÄÇÉËÍÏÐÒÓÔÕÕÖ™–“‘Ž‹‰†„|zwusqomkigfdba`^]\\[[ZYXXXXXXXYYYYYY¤¯¯¯®­®­­­­¬¬¬¬¬«««ªªª©©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¢¢¡¡¡¡¡¨­®­¬«ªª©¨§¦¦¦¥¤£¢¡¡ ŸŸžœœ›šš™———–•”“’’‘ŽŒ‹‹Š…€{uqkjjijiiiihhhhhhhhghgghhggggj‚Œ‹‹ŠŠ‹ŠŠŠ‰ˆ‰‰ˆˆ‡‡‡‡‡‡†††…………„…„„„ƒƒ‚ƒƒ‚‚€€€€~~~}}}}}|||{{{{{{zzzzzyyyyxxxxwwvwvvuuuuuutttstssrrrrqqqëëëëëëëëëëëëëëëëêêêéééééèèèçççççææææååååäääãããããããââááááàààßßßãääååå妤¡Ÿ™˜•“‘ŽŒŠˆ†„‚€~|{ywutsqponmlkjjiihhggggghhiiiijjkllnoooqrstuvxyz{}~‚         ¡¡¡¢¢¡¢£¤¤¤¥¦¦¦§¨¨©©ª«¬¬­®®¯°±²²´µµ¶·¸¹¹¼¿ÂÄÆÉÊÍÎÐÑÓÔÕÕÖÖ˜•’Ї…‚€}{yvurpnljigecba_^]\\[[YYXXXXXYYXYYZY[¯¯¯¯¯®®­®­­¬¬¬¬¬«¬««««ªª©©©©©¨¨¨§¨§¦¦¦¦¦¦¦¥¥¥¥¤¤¤£££¢£¢¢¢¡¡¡¡¢ª­¬««ªª©¨§¦¦¥¤££¢¡ Ÿžžœœ›š™™˜——–•”““’‘ŽŒ‹‹Š…|vqljjijjiiiiihhhhhhghgghgggggp‡ŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡‡‡†††………………„„ƒƒƒƒ‚‚‚‚€€€€€~~~~}}}|||||{{{{{zzzyzyyyxxwxwwwvvvvvuuuuutttttssrrrrrqëëëëëëëëëëëëëëëëëêêéêééééèèèèèççææææåæååääääãããããããââáááààààßàãåååå娦¢ ž›™—”’‹‰‡…ƒ}{zxvutrqonmllkiiiihhhhhhhhhiiijjkllmnooqqstuvwxz{|}‚¡        ¡¡¡¡¢¢¢££¤¥¥¥¦¦§¨¨¨©ªª«¬¬®®¯°±²²³´µ¶·¸¸¹»¾ÀÃÅÈÊÌÎÏÑÒÔÔÕÖÖ™–“‘Œ‰‡…|zxvsqomlihgdcb`_^]\\ZZYYYXXXXYYYYYYZh°°¯¯¯¯®®®­­­­¬¬¬¬¬¬««««ªª©©©©¨¨¨¨§§§§§¦¦¦¦¥¦¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡¡ §¬«ª©©¨§¦¦¥¤¤£¢¡  Ÿžžœœ›š™˜˜—––””“’’‘ŽŒŒ‹Š†|wrmjjjjijiiiiiiiihhhghhggggggvŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒ‚ƒ‚‚‚‚€€€€€~~~}}}}|||{{{{{{zzzyyzyxxxwxwwwvwvvvuuuuutttssssssrqqëëëëëëëëëëëëëëëëëëêêêéééééèèèçççææææææååååäääããããããââââáááàààßãåååå妤¢ š˜–“‘Ž‹ˆ†„‚€~|{ywvtsqponmmkjjiiihhhhhhhihiiijkllmnoopqstuvwxz{{}‚¡¡¡     ¡¡¢¢¢¢£££¤¤¥¥¦¦§§¨©©©««¬¬­®¯°°²²³´µ¶¶·¸¹º½¿ÂÄÇÉÊÍÏÑÒÓÔÕÖÖÖ˜•’‹ˆ†ƒ~|ywuspomkihedba_^]\\[ZZYYYXXXYYYYYZZZu°¯¯¯¯¯¯®®­­­­­¬¬¬¬¬«««ªªªªªª©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¡¡¡¡¡¡ ¤ªª©©¨§¦¥¥¤£¢¢   Ÿžœœšš™˜˜—––””““’‘ŽŒŒ‹‹‡‚}wsnjjjjjjjijiiiihhhhhhhghgggh~ŒŒŒŒ‹‹ŠŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡††††………………„„ƒƒƒ‚‚‚‚‚€€€~~~}}}}}||{{{{{{zzzzzzyyxxxxwwwwwvvvvuuuuttttsssrrrqëëëëëëëëëëëëëëëëëëëêêêéééééèèèççççççæåæååååääääããããâãâââáááàààâååææ¨¦£ žœ™—”“ŽŒ‰‡…ƒ}|zxwutsqoonmlkjiiihhhhhhhhiiiijjklmnnopqrstuvxx{{}~‚¡¡  ¡ ¡¡¡¡¡¢¢¢¢££¤¥¥¥¦¦¦§¨¨©©ª¬¬¬­®¯°°±²²³´µ¶·¸¸º¼¾ÁÃÆÈÊÌÎÐÒÓÔÕÖÖ×™–”’ŽŒ‰‡„}zxvtqonlihfecb`_^]\\[ZZYYYXYXYYYZYZZ[ƒ°°°°¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¥¤£¤£££¢¢¢¢¡¡¡  ¢§¨¨§¦¥¥¤££¢¡¡ Ÿžœ›š™™˜——–•”““’‘‘ŽŒŒ‹‡‚}xsnjjjjjjjjjiiiiiiihhhhhhhggrˆŒŒ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡†‡‡†…†………„…„„ƒƒƒƒ‚‚‚€€€~~~~~}}}||||{{{{{zzzyyyyxxxxwwwwvvvvuuuuutttsssrssrëëëëëëëëëëëëëëëëëëëêêêêééééééèèèèççæçæåæåååäääääãããããâââáááááàâæææ©¦¤¢ š˜–“’‹ˆ†„‚€}{ywvusrpoommlkjiiihhhhhhhiiiijjkllmnooqrstuvwxz{|~€¡¡ ¡¡¡¡¡¡¡¡¢¢¢£££¤¤¥¥¥¦§§¨¨©©ª«¬¬­®¯°°±²²´´µ¶··¸¹º¾ÀÂÅÇÊËÍÏÑÒÓÕÖÖ×ט•“‹ˆ†ƒ~|ywusqomkihedba`_^]\[ZZZYYYYYYYZZYZZZ[“°°°°¯¯¯®®®®®­­­­¬¬¬¬¬««««ªªª©©©¨©¨¨¨¨¨¦§§¦¦¦¦¦¦¥¥¥¥¤¤£¤£¢£¢¢¢¢¡¡ ¡ £§§¦¥¤££¢¡¡  žžœ››š™˜——––””““‘‘‘ŽŒŒ‹ˆƒ~ytokkjkjjjjjjjjjiiihihhhhhhh}ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠ‰‰Š‰‰ˆˆˆˆˆ‡‡‡‡††††………„„„„ƒ„ƒƒ‚‚‚‚€€€€~~~~}}}}}||||{{{{z{zzzyyyxxxwxwwwvvvuuuuuttttssssrrëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèçèççæææåæååäåäääãããããâââáááàááæææ¨¦£¡žœ™—•“‘ŽŒŠ‡…ƒ€~|zywutsqponmlkjjiiiihihhihiiijjklmmnnopqstuuwxy{|}€‚¡¡¡¡¡¡¡¡¡¡¢¢¢£££¤¤¤¥¦¦¦¦¨¨©ªª«¬¬­­®¯¯°±²³´´¶··¸¹º¼¾ÁÄÆÈËÌÏÐÒÓÔÖÖ×Ö™—”’‰‡…‚€}{yvtronljifedba`_]\\\[ZYYYYYYXYZZZZ[[[£±°°°¯°¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¢¡¡¡¡  ¢¤¥¥¤£¢¢¡  Ÿžœœ›š™™˜—––•””“’‘ŽŽŒ‹‰„zuokkkkkjjjjjjjjijiiiiihhhhuŠŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†‡†††……„…„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}}}}||}|||{{{{{zzzyyyxxxxxxwwwvvvvuuuuututsssssëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèççççæææææåååååäääããããããâââááááææ©§¤¢ š™—“’‹‰†…ƒ}{zxvutrqoonmkkjjjiiiiiihiiiijjkklmnnopqrstuvxyz|}~‚¡¢¡¡¡¡¡¡¡¡¢¢¢£££¤¤¤¥¦¦¦¦§¨¨©ªª«¬¬­®¯°±±²³³´¶¶·¸¸º»¾ÀÃÅÇÊÌÎÐÑÓÔÕÖ××ט–“‘Ž‹ˆ†ƒ|zwusqomkigfdca`_^]\\[ZZZZYYYZYYZZ[Z[[a±±±°°°°¯¯¯¯®®®­­­­­¬¬¬¬««««ª«ªª©©©©¨¨¨¨§§§¦§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¡¡    ¡¤¤£¢¡  Ÿžœ››™™˜—––••”“’‘‘ŽŒŒŠ…€{vpkkkkkkkjjjjjjjijiiiihhhpƒŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡‡†††…†……„…„„„ƒ‚ƒ‚‚‚‚€€€€€~}~}}}|}|||{{{{zzzzyyyxxxxwwwwwvvvvuuuuuttttsssëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèèèçèçççææææåååäääãäãããããâââââááä稦£¡Ÿœ™˜•“‘ŽŒŠˆ†„‚€~|{ywuusqponnlkkjiiiiiiiiiiijikkllmmnopqrstuvwyy{|}€ƒ¡¡¡¢¡¡¡¡¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ªª«¬¬­®®°°±²³´´µ¶·¸¸¹»¼¿ÁÄÆÉËÍÏÐÒÓÕÖ××ך—”’Ї…‚€}{yvtspnljigedba`_^]\\[[ZZZYZYZYZZ[[[[[s±±±±°°°¯°¯¯¯®®®­­­­­¬¬¬¬¬¬«««ªªªª©ª©¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤¤£££¢¢¢¡¡¡     ¡¢¢¡ Ÿžžœœ›š™™˜—–••”““’‘‘ŽŒŠ†€{vqmkkkkkkkkjjjjjjjjiiiiin€‘ŽŽŽŽŒŒŒŒ‹Œ‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††††…………„„ƒ„ƒƒ‚ƒ‚‚€€€€~~~}}}}}|||{{{{{zzzyyyyxxxxwwwvvvvvuuuuuuttsssëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççççææåæååååäääãããããââââááã©§¥¢ ž›™—”’Ž‹‰‡…ƒ~{zxwutrqponmllkjjiiiiiiiiiijjkkllmnooprrtuvwxyz|}~€‚¢¡¡¢¢¢¡¢¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ª««¬¬­®¯¯±±²²´´µµ·¸¸¹º¼¾ÀÄÅÈÊÌÎÐÑÓÔÕÖ×××™–“‘Ž‹‰†„~|zwutqomkihfecba_^]]\[[ZZZZZZZYZZ[[[[\\†²±±±±±°°°°¯¯¯®®®®­­­¬¬¬¬¬¬¬««ª«ªªª©©©¨¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡  ¡       Ÿžžœ›šš™˜—–••”““’‘‘‘ŽŒ‹†|wrmkkkkkkkkkkjjjjjjjjiio€‘‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††…†……„…„„„„ƒƒ‚‚‚‚€€€€~~~}~}}|}|||{{{{zzzzzyyyxyxxwwwwwvvvvuuuututtsëëëëëëëëëëëëëëëëëëëëëëëëëêêééééééèèèèèççææææææåååäääããããããâââáá㨦¤¡Ÿœš˜•“‘ŒŠˆ†„‚€~}{zxvusrqponmkkjiiiiiiiiiiijjkkklmnoopqrsuuvxyz{}}€ƒ¢¢¢¢¡¢¢¢¢¢¢£££¤¤¤¥¥¥¦§§¨¨©ª«««¬­­®¯°±²²³´µ¶¶¸¸¹º»½¿ÂÄÇÉÌÍÐÑÒÔÕÖ×××™˜”“Šˆ…ƒ€~{yvurpolkigedba`_^]\\[[ZZZZZZZZZZ[[[\\\š²²²±±±±±°°°¯¯®®®®®­­¬¬¬¬¬¬¬«¬«««ªªª©©¨¨©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤£¤£££¢¢¢¡¡¡       ŸŸ››š™˜——––””“’’‘ŽŒŒ‡‚}xsmkkkkkkkkkkkkjjjjjjjsƒ’’‘’‘ŽŽŽŽŽŒŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††……………„„„„„ƒƒ‚‚‚€€€€€€~~~~~}}||}||{{{{{{zzzzyyyxxxxxwwwwvvvuuuuuttttëëëëëëëëëëëëëëëëëëëëëëëëêëëêééééééèèèçèççæææææåååååäääããããããââáߨ¥¢ ž›™—”’Œ‰‡…ƒ€~|zxwutsqponmmlkjjiiiiiiiiijjkkkllnoopqrrtuvwxz{|}€‚¢¢¢¢¢¢¢¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ªª««¬¬­®¯°°²²²´´µ¶·¸¸¹º¼¾ÁÄÆÉÊÍÏÐÒÓÕÖ××××™–“ŽŒ‰†„|zxvtqomljhgecba`_^]\\[[ZZZZZZZZZZ[[\\\\¯²²²±±±±°°°°¯¯¯¯¯®®®®­­¬¬¬¬¬¬««««ªªªª©©©©¨¨¨§§§§§¦¦¦¦¥¥¥¥¤¥¤££££££¢¢¢¡¡¡¡¡    Ÿ ž››šš˜˜—––•”““’‘‘ŽŽŽŒˆ‚}xsnlkkkkkkkkkkkkkjjjlzŠ““’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆ‡ˆˆ‡‡‡‡††††………„„„„„„ƒƒ‚‚‚‚‚€€€€€~~}}}}}|||{{{{{zz{zzyyyxxxxxwwwwvvvvuuuuuttëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèçççæææææåååäääãããããããâââ´¤¡Ÿš˜•“‘Šˆ†„‚}{zxvutrqponmllkjjjiiiiiiiijkklllmnopqrstuuwwy{{}~€‚„¢¢¢¢¢£¢¢££££££¤¤¥¥¦¦¦§¨¨©©«««¬¬­®¯¯°±²²³µµ¶·¸¸¹º»¾ÀÂÅÇÊÌÎÐÑÓÕÖÖ××ך˜•’‹ˆ†ƒ€~{ywusqomkigfdcba_^^\\\[[[[ZZZZZZ[[\\\\\q³²²²²±±±°°°°°°¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©©©¨¨¨§¨§§§¦¦¦¦¥¥¥¥¤¤¤¤¤££££¢¢¢¡¡ ¡¡      ŸŸœ›™˜——–•””“’’‘ŽŽŒˆƒ~ytollkkkkkkkkkkkkklx…““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡†††…………„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|}||{{{{{{zzzzyyyxyxwxwwvwwvvvuuuutuëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèççççæçææåååååäääãããããâââÄ£ ž›™—”“Œ‰‡†ƒ‚€~|{ywvusrqoonmllkjjjjiiiijjjjkklmnnoppqsstuvwyz{}~‚‚£¢¢¢¢¢£¢£££££¤¤¥¥¥¦¦¦§§¨¨©ª««¬¬­®¯¯°°²²³´µ¶·¸¸¹º»¼¾ÁÄÆÉËÍÏÐÒÔÕÖ××ØØ™–”‘ŽŒ‰‡„|{xvtrpnljhgedbb`_^]]\[[[[[ZZ[Z[[[[\\\]]ˆ³³²²²²²±±±°°°°°¯¯¯¯®®®®­­­¬¬¬¬¬«¬««ªªªªª©©©¨¨¨¨¨§¦§¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¡¡¡¡      ŸŸŸŸžœ™—–”““’‘‘ŽŒ‰„ztpllllllkkkkkkkq|‡“””””““““““’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚‚‚€€€€~~~~~}}}}}|||{{{{{{zzyyyyyyxxwwwwvvvvvuuuutëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèçççççææææååäåäãããããããââÓ¡ š˜–“‘‹ˆ‡„‚}{yxwutsqqonmmlkkjjjjijjijjjklllmmoopqrstuvwxz{|~€‚„£¢¢¢¢¢£¢££££¤¤¥¥¥¥¦¦¦¨¨¨©ª««¬¬¬­®¯°°²²³´µ¶¶·¸¹º»»¾ÁÃÅÈÊÌÎÐÑÓÕÖ×ØØØš˜•’‹ˆ…‚~|ywusqomkihfdcba`_^]\\\[[[[[[[[[[[\\\\]] ³³²²²²²±±±±°°°°°¯¯¯¯®®®­­­¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨¨§§¦¦¦¦¦¥¦¥¥¤¤¤¤¤££££¢¢¢¢¡¡      ŸŸŸŸžŸžžœš˜•“‘Š…{upllllllkkllt~†•••”””””“““““’“’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡†‡††……………„„„ƒƒƒ‚ƒ‚‚‚€€€€€~~~~~}|}}|||{{{{{zzzzyyyyxxxwwwwvvvvvuuuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéêéééééèèèççççæææååååäåäãäãããããã⦞œ™—•“ŽŒŠ‡†ƒ€~|{ywvutsqoonmmlkkjjjjiijjjjkkllmnnopqrstuvwxyz{}‚„£¢££££¢££££¤¤¤¤¥¥¦¦¦§§¨¨©©ª«¬¬¬­®¯°°±²³³´¶¶·¸¹¹º»½¿ÁÄÇÉËÍÐÑÓÔÖ××ØØØ™–”‘Œ‰‡…‚€}{xvtrpnljigedcb``^]]\\[[[[[[[[[[[\\\\]]e³³³³³²²²²±±±±°°°°¯¯¯¯¯®®­®­­­¬¬¬¬¬¬««ª«ªª©ª©©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤£¤££££¢¢¢¢¡¡¡    ŸŸŸŸŸžžžžš˜—•’ˆ‚|vrmnqsv{…Š‘————––•••••”””“““““’’’’’‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡ˆ‡‡‡‡‡†††………………„ƒ„ƒƒƒƒ‚‚‚€€€~~~}}}|||||{{{{{{zzzyyyyxxwxwwwwvvvvuuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççççææææåååääääããããã⸛™–“‘‹‰‡…ƒ}|zywutsrqonnnlkkkjjjjjjjjkjkllmnnooqqssuuwxxz{|~€ƒ„£¢£££¢££¤¤¤¤¤¤¥¥¦¦¦¦§¨¨©©ª«¬¬­­®®°°±²³³´´¶··¸¹º»¼¾ÁÄÆÈËÍÏÐÒÔÕÖ××ØØš™•“‹ˆ†ƒ~|zxusqomkihgecba`_^]]\\[[[[[[[[[[\\\]]]]´´³³³²²²²²²±±±±±°°°¯¯®®®®®­­­­¬¬¬¬¬¬¬««ªªªª©©©¨¨©¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¢¢¡¡¡     ŸŸŸŸžžžžœœœœ››››šš™™™™™™˜˜˜˜˜—————––––••””””““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆˆ‡‡‡‡‡††††…………„„„ƒ„ƒƒƒ‚‚‚€€€~~~~}}}||||{{{{zzzzzyyyxxxwxwwwvwuvvuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééèèèèçèççæææææååååäääãããããÌœ™˜•“‘ŽŒŠˆ†„‚€~}{yxwussqpoonmllkkjjjjjijjkkllmmnopqqrstuvxxy{|}€‚„£££¢£¢££¤¤¤¤¤¤¥¥¦¦¦§§¨¨©ªªª«¬¬­­®¯°±²²³´´¶·¸¸¹º»¼¾¿ÃÅÇÊÌÎÐÒÓÕÖ××ØØØš—”’ŽŠ‡…‚}{ywuspomkihfdcba`_]]\\\\\[\[[[[\\\\]]]^^™´³³³³³³²²²²²±±±±±°¯¯¯¯¯®®®®­­¬­¬¬¬¬¬«««ªª«ªªª©©©¨¨¨§¨§§¦¦¦¦¦¦¦¥¥¤¥¤¤£££££¢¢¢¢¡¡¡¡    ŸŸŸŸžžžžžžœœœ›››››ššš™™™™™™˜˜˜˜————–––••••••”””“““““’’’’’‘‘‘ŽŽŽŽŒŒŒ‹‹Š‹ŠŠŠŠ‰‰ˆˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒƒƒ‚‚‚‚€€~~~}}}}}|{{{{{{{zzzzyyyyxxxwwwwwvvvuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèçççææçæååæååääääããããâ™–”’‹‰‡…ƒ~|zywvusrqoonmmllkkjjkjjjkkklkmmnoopqqstuvwxyz|}~€‚ƒ…££££££££¤¤¤¥¤¥¥¦¦¦§§§¨©©ª««¬¬­­®¯°°²²³´´µ¶·¸¸¹º»¼¿ÂÄÇÉËÍÏÑÓÔÖ×רÙÙ›˜–“Ž‹‰†ƒ~}zwusqonkjigedcaa_^^]]\\\\\\\\\\\\\\]]^^a´µ´´´³³³³²²²²²±±±±°°°¯¯¯¯¯®®®®­­¬¬¬¬¬¬¬«««««ªª©©©¨¨¨¨¨§§§§§¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¡¢¡¡¡       ŸŸžŸžžœœœœ›››ššššš™™™™™˜™˜˜——–—––––••••””””““““““’’’’‘‘‘‘ŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††††………„…„„ƒƒƒƒƒ‚‚‚€€€€~~~}~}}||||{{{{{zzzyyyyxxxxwwwwwvvvuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëëêêêééééèéèèèçççææææåååååääãäãã㳘•“‘ŽŠˆ†„‚}{zxvutsqpoonmllkkkkjjjkkkkkllmnnoopqssuuwxyz{|~€ƒ„££££££££¤¤¤¤¥¥¥¦¦¦§§§¨¨©ªªª«¬­­®¯°±±²³³´µ¶·¸¸¹º»¼½ÀÃÅÈÊÌÏÐÒÓÕÖרØÙÙš˜”’Ї…‚€~{ywusqomjihfecba`__]]]\\\\\\[\\\\\\]]^^^~µµ´´´´³³³³³²²²±²±±±±±°°¯¯¯®®®®­­­¬¬¬¬¬¬¬««««ªªª©ª©©©¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¢¡¡¡     ŸŸŸžžžžžœœœœœœ››››ššš™™™™˜™˜˜˜——–—–––––••••””““““““’’’’‘‘‘ŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚€€€€~~~}}}}}|||||{{{{{zzzzyyyyxxxxwwwvvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççææææææååååäääãããË—”’ދЇ†ƒ€~{{ywvutrqpoonmmlllkkkkkkkkkllmnnooqqrrtuvxyy{|}~€‚„…¤¤£££¤£¤¤¤¥¥¥¥¥¦¦§§§¨¨©©ª«¬¬¬­®®¯°±²²³´µ¶·¸¸¹º»¼¼¿ÁÄÇÉËÎÐÑÓÕÖ×ØØØÙ›˜–“ŽŒˆ‡„}zxvtqonljigfdcb`__^^]\\\\\\\\\\\\\\]^^^_šµµµ´´´³³³³³³²²²²±±±±°°°¯°¯¯¯®®®­­­¬¬¬¬¬¬«««««ªª©©ª©©©©¨¨§§§§§¦¦¦¦¦¥¥¥¥¤¤£¤££¢¢¢¢¡¡¡¡¡      ŸŸžžžžžœœœœœ›››šššš™™™™™˜˜˜—————–––••••”•”””““““““’’‘’‘‘‘ŽŽŽŽŒŒŒ‹‹Œ‹ŠŠŠŠŠ‰‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„…„„ƒƒƒƒƒ‚‚‚‚€€€€~}}}}|}||{{{{{{zzzzyyyxyxxwxwwwvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèçççæææææååååäääãã㜓‘Œ‹‰‡…ƒ}{zywvtsqqpoonmmlkkkkkkkkkklllmnoopqrstuvwxyz{}~€ƒ„£¤£¤££¤¤¤¤¥¥¥¥¦¦¦¦¦§¨¨©©ª««¬¬­®®¯°±²²³´µµ¶·¸¹º»»¼¾ÀÃÅÈËÌÏÐÒÔÕרØÙØÙ𗕒Ї…ƒ€}{ywusqomkihfedbb`_^^]]]\\\\\\\\\\\]]]^__g¶µµµ´´´´´³³³³³²²²±±±±°°°°¯¯¯¯¯®®®­­­­¬¬¬¬¬¬¬«««ªªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡     Ÿ ŸŸžžžžžžœœœœœ›››››šš™š™™™™™˜˜———–——––•–•••””””““““’’’’’’‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚‚€€€€€~~}}}}}|||{|{{{{{zzzzyyyxxxxwwwvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééèèèèèççççæææååååäääããã·“ŽŒŠ‡†„€~|{zxvutsrqoonnmmlllkkkklkkllmmnoopqqrtuuwxyz{}~€ƒ„…¤¤¤¤¤¤¤¤¤¥¥¥¦¥¦¦¦§§¨¨©©ªª¬¬¬­­®¯°°²²²³µµ¶·¸¸¹º»¼½¿ÂÄÇÊÌÎÐÑÓÕÖרÙÙÙ›™–“‘ŽŒ‰‡„}{xvtrpnljigfecba`_^]]]\\\\\\\\\\]]]]^^_`†¶¶¶µµ´´´³´³³³³²²²²²±±±±°°°°¯¯¯®®®®®­­¬¬¬¬¬¬«¬««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¦¥¥¤¤¤¤££££¢¢¢¢¢¡¡¡     ŸŸŸŸžžžžžœœœœ››š›šššš™™™™˜˜˜————–––––••••””””““““’’’’‘’‘‘‘ŽŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰ˆ‰‰ˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒ‚‚‚‚€€€~~~~~}}}}|||{{{{{{{zzyyyyxxxwxwwvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèéèèçççææææåæåääääääÓ‘‹‰‡…ƒ}|zxwvusrqpoonmmlklkkkkkllllmmnooppqrstuvwyz{|}~€‚„…¤¤¤¤¤¤¤¤¥¤¥¥¥¦¦¦¦¦§¨¨©©ª««¬¬­­®®°±±²²³´µ¶·¸¸¹º»¼½¾ÁÄÆÉËÍÐÑÓÕÖרÙÙÚÙš˜•“Šˆ…ƒ~|zwusqomljhgedbba`_^^]]\\\\\\\\\\]]]^^__`¥¶¶¶¶µµ´´´´´´³³³³²²²²²±±°±°°°°¯¯®¯®®®­­­¬¬¬¬¬«¬««ªªªª©©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡    ŸŸ ŸŸžŸžžžœœœœ›››šššš™™™™™™˜˜˜—————–––•••••”””“““““’’’‘‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒƒ‚‚‚‚€€€€~~}}}}}|||{{{{{z{zzyyyyxxxxwwwëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééèèèèçççæææåååååååäã㤎ŒŠˆ†„‚~}{zxwutsrppoonmllkkkkllllllmmnooopqrstuvwxy{{}~‚ƒ„†¤¤¤¤¤¤¥¤¥¤¥¦¦¦¦¦§§¨¨©©ªª«¬¬¬­®¯¯°±²³³´µ¶¶·¸¹º»¼½½ÀÃÅÈÊÌÏÑÒÔÖרÙÙÚÚœ™–“‘ދЇ„‚€}{yvtrpnmkigfecbb``^^]]\]\\\\\\\]]]]^^_``u···¶¶µµµ´µ´´³´³³²²²²²²±±±±°°°¯°¯¯¯®®®­­­­­¬¬¬¬¬««««ªªª©©©¨©©¨§§¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡      ŸŸŸŸžžžœœ›œ››››ššš™™™™™˜˜˜˜—————––•••••”””“““““““’’‘‘‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰Š‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚€€€~~}}}}}}|||{{{{{zzzzyyyxyxxxxwëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèéèèçççæææææåååäääãÄ‹ˆ‡…ƒ€~|{ywvutrrqooommlllkkklllllmmnnoopqrstuuwwyz{|}€ƒ„†¤¥¤¥¥¥¤¤¤¥¥¦¦¦¦¦¦§§¨©©ªª«¬¬¬­®¯¯°±²²²³µ¶¶·¸¸¹º¼¼½¿ÁÄÆÉËÎÐÒÓÕÖרÙÚÙš˜•“‹ˆ…ƒ|zwusqomljhgfdcba``_^]\\\\\\]\]]]]]^^^_``–···¶¶¶µµµ´´µ´´³³³³³²²²²²±±°°±°¯¯¯¯¯®®®®­­­­¬¬¬¬««««ªªªª©©©©¨©¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¥¤¤¤£££¢¢¢¢¡¡¡¡¡     ŸŸŸŸžžžœœœ›››››šššš™™™™˜˜˜˜————––––••••”””””““““’’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡‡‡†…†…………„„„„ƒƒƒƒ‚‚‚€€€€~~~~}}}}||||{{{{zzzyyzyxxxxwxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèçççççææææååååää䕊ˆ†…‚}{zxwvusrqpoonmmmlllkkllllmmmnnopqrrstuvwxz{|}€‚ƒ…†¥¥¥¥¥¥¥¥¥¥¥¦¦¦¦¦§§¨¨©ªª«¬¬¬­­¯¯°±±²³³´µ¶·¸¹ºº»¼½¾ÁÃÅÈÊÌÏÑÒÔÖרÙÙÚÚ›™–”‘Œ‰‡…‚€}{yvusqomkihgecbba``_^]]\\\]\]\]]]]^^^_``i·····¶¶µµµµµµ´´´³³³³³²²²²±±±°°°°°°¯¯¯¯®®®­­­¬¬¬¬¬¬¬«««ªª©©©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¢¢¡¡ ¡    ŸŸŸŸŸžžœœœœœ››š›šš™š™™™™˜˜˜—————––––••••””””““““““’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„ƒƒ‚ƒ‚‚‚‚€€€~~~~~}|}}|||{{{{{zzzzyyyyxxxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèçèççççææååååää䶉‡…„€~}{yxvutsrqpoomnmlllllllllmmnnooppqrttuvwxy{{}~‚ƒ…†¥¥¥¥¥¥¥¥¥¥¥¦¦¦§¦§¨¨¨¨ªªª«¬¬­­®¯°°±²³³´µ¶·¸¸¹º»¼½½¿ÂÄÇÊËÎÐÒÓÕ××ÙÚÚÚ›˜•’‹ˆ†ƒ~|zxvtronljihfdcbba`_^]]]]\]]]]]]]]^^^^``a‹¸·····¶¶¶µµµµ´´´´³³³³³²²²²²±±±±±°°¯°¯¯®®®­­­­¬¬¬¬¬¬¬«««ªªªª©©©©©¨¨¨¨§§¦¦¦¦¦¥¥¥¥¤¥¤¤££££££¢¢¢¡¡¡      ŸŸŸžŸžžžœœœ›››››šššš™™™™˜˜˜˜————–––––••”•””“““““’’’’’‘‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†‡†††………„„„„ƒƒƒƒ‚‚‚€€€~~~}}~}|}}|{|{{{{{zzzzyyyxxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççææææææåååå܈‡„ƒ}|zywvutrrpponmmllllllllmmmmnnoppqrrtuuwxy{{|}€ƒ„…‡¦¥¥¥¥¥¥¦¥¦¦¦¦¦§§¨¨¨©ªª««¬¬¬­®¯¯°±±²³´µµ··¸¸º»»¼¾¾ÁÃÆÈÊÍÏÑÓÔÖרÙÚÛÚœ™—”’ŒŠ‡…‚€~{ywusqomlihgedcbaa__^]]]]]]]]]]^^^^___aaa¯¸¸¸····¶¶¶¶µµµµ´´´´³³³²²²²²±±±±±°°¯°¯®¯¯®®­®­¬¬¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤£££¢¢¢¢¢¢¡¡¡      ŸŸŸŸžžžœœœœœ›››š›ššš™™™™™˜™˜—˜˜—–—––•••”””””“““““’’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡†††…††…„…„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèçççæææææåååä䮆ƒ‚€}{zxwutsrqpoonnmmmllllllmmmnnoopqrrtuuvxxy{|}€‚„…‡¥¥¥¥¥¥¥¥¦¦¦¦¦¦§§¨¨¨©©ªªª«¬­­­®°°±²²³´´µ¶·¸¹º»»¼½¾ÀÂÄÇÊÌÎÐÒÔÖרÙÚÚÚž›˜–“‹ˆ†„|zxvtqonmkihfecbba`_^^^]]]]]]]]^^^^____`b†¸¸¸¸¸¸··¶·¶¶¶¶µµµ´´´´³³³³²²²²±±±±°°°°¯¯¯¯¯®®®­­­¬¬¬¬¬¬«««««ªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤£££££¢¢¢¡¡¡¡¡    ŸŸŸŸŸžžžœœœœœ›››››šš™™™™™™™˜˜˜˜———–––––•••””””“““““’’’’’’‘‘ŽŽŽŒŒŒŒŒ‹‹‹‹Š‰Š‰‰ˆˆˆˆ‡‡‡‡‡‡††††………„…„„„„ƒƒƒ‚‚€€€~~~}}}}}|||{{{{{{zzzzzyxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêééééèèèèèçççææææååååÖ…ƒ~|zyxvutsrqpoonmmmmlllmlmmnnooooqrrstuvwxyz{|~€ƒ„…‡¥¥¥¥¦¥¦¥¦¦¦¦¦§§§¨¨©©ªª««¬¬­®®¯°±²²²´µµ¶·¸¸¹º¼¼½¾¾ÁÄÇÉËÎÐÒÓÕרÙÚÚÛÛš—”’Ї…‚€~{zwusqonkjigfdcbaa___^]]]]]]]^^^^^^__``ab«¸¸¸¸¸······¶µµµµµ´´´´´´³³²²²²²²²±±°±°°¯¯¯¯¯®®­­­­¬¬¬¬¬¬««««ªªª©©©©¨©¨¨§§§¦§¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢£¢¢¢¡¡¡¡    ŸŸŸŸžžžžœœœœ››šššš™š™™™™™˜˜˜˜˜———–––•••••””””““““’’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰‰ˆˆ‡‡‡‡‡‡†††…†…„…„„„„ƒƒƒ‚‚‚€€€€€~~~~}}}}}|||{{{{{zzzzyyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççççæææååå媂€}{zywuutsrqooonnmmmmmmlmmnnnoopqqrstuvvxyz{|}~€‚……‡¥¥¦¥¦¦¥¦¦¦¦¦¦¦§§¨¨©©ªª«¬¬¬­­®¯°±±²²³´µ¶¶¸¸¹¹»¼¼½¾ÀÃÅÇÊÌÏÑÓÔÖרÙÚÛÛž›™–“‘Œ‰‡„‚}{xvtrpomkihfedcbb`__^^^]]]]]]^^^____``ab„¹¹¹¸¸¸¸¸····¶¶¶µµµ´´´´´´³³³³²²²²±±±±°°°¯°¯¯¯¯®®­­­¬­¬¬¬¬¬«««ªªªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤£¤£££¢£¢¢¡¡¡¡     ŸŸŸŸŸžžžœœœœœœ››››šššš™™™™˜˜˜˜——˜——––••••”•”””“““““’“’’‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††††……„„„„ƒƒƒƒƒ‚‚‚€€€€~~~}}}}}|||{{{{{{{zzyyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééèèèçèççææææååæåÕ€~{{yxwutsrqpooonmmmmmlmmmmnoooopqrrsuuvxyz{|}~€‚ƒ…†¦¦¥¥¦¦¦¦¦¦¦¦¦¦§¨¨¨©©©ª««¬¬­­®¯¯°±²²³´µµ¶·¸¹ºº¼¼½¾¿ÂÄÇÉËÎÐÒÔÖרÙÚÛÛÛš˜”’Šˆ…ƒ~|zwusqonljigfedbb```_^^]]]]^]]^^____``abb¬º¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´´´³³³³²²²²²±±±±±°°¯¯¯¯¯®®®®­­­¬¬¬¬¬«««ª«ªª©©©¨©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢£¢¡¢¡¡¡      ŸŸŸžžžžžœœœ››››ššš™š™™™™˜˜˜˜————–—–––•••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}|}|||{{{{{{zzzyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèççççææææååå«}{zywvutsrqpoonnnmmmmmmmmnoooopqrrstuvwxyz{}~ƒ„†‡¦¦¦¦¦¦¦¦¦¦§¦¦§¨¨¨¨©©ª««¬¬¬®®®¯°±²²³³´¶¶·¸¸¹º»¼½¾¾ÀÄÅÈÊÍÏÑÓÕÖ×ÙÚÛÛÛžœ™–“‘ŽŒ‰‡„‚}{yvuspomkjigedcbaa___^^^^^^^^^____``aaab‡º¹¹¹¹¹¹¸¸¸¸···¶¶¶¶¶¶µµµ´´´´´³³²²²²²²±±±°°°°¯¯¯¯®®­®®­­¬¬¬¬¬¬««««ªªª©©ª©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢£¢¢¡¡¡¡       ŸŸŸžžžœœ››››››šš™™™™™™˜˜˜˜——–—–––•••••””“““““““’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„ƒƒƒƒƒ‚‚‚€€~~~}}}}}|||{{{{{zzzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèççççææææåÚ|{zxwuusrqpooonnmnnmmmmnmooooppqrstuvwxyz{|}€‚„…‡¦¦¦¦¦¦¦¦¦¦¦§¦§§¨¨©©©ªª«¬¬­­®¯¯°°±²²³´µ¶·¸¸¹º»¼½¾¾ÀÂÅÇÊÌÎÐÒÕÖרÚÚÛÜÜš˜•“‹ˆ…ƒ~|zxvtqpnljihgedcba``___^^^^^^^^____``aabb°ºººº¹¹¹¸¸¸¸¸····¶¶¶µµµµ´µ´³´³³³²²²²²²±±±±°°¯¯°¯¯®®®®­­­­¬¬¬¬¬«¬««ªªª©©©¨©¨¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤£¤£££¢¢¢¡¡¡¡      ŸŸŸžžžžœœ›œ››››ššš™™™™˜™˜˜˜————–––••••””””“““““’’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡†‡†††…………„„„„ƒƒƒ‚‚‚€€€€}~}}}|}|||{{{{{zzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèççççæææææå°|{ywvuusrqpooonnnmnmmmmnnoooppqrstuuvwxz{{}~€‚ƒ…†‡¦¦¦¦¦¦¦¦¦§§§§¨¨©©©©ªª«¬¬¬­­¯¯°°²²²³´µ¶¶·¸¹ºº¼½½¾¿ÁÄÆÉËÎÐÑÔÖרÙÚÛÜÜžœ™–“’Œ‰‡„‚€}{ywurqomkjhgfedbba``__^^^_^^^^^___``aaabººººº¹¹¹¸¸¸¸¸¸····¶¶¶¶¶µ´´µ´´³³³²³²²²²²±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¡¡¡¡¡     ŸŸŸŸžžžœœœœœœ›››šš™š™™™™™™˜˜˜————–––•–•••”””““““““’’’‘’‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒ‚‚‚‚‚‚€€€~~~}~}}}||||{{{{zzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèçççççæææåã‡zywvutrqqpoooonnnnmnnnnnooppqrsttuvwxy{{}~€ƒ„†‡¦¦¦¦¦¦¦¦§¦§§§§¨¨©©ªªª«¬¬¬­®®¯¯°±²²³´´µ¶¸¸¹ºº¼¼½¾¾ÀÃÅÇÊÌÏÑÒÕרÙÚÚÛÜÜš˜•“‹ˆ†ƒ|zxvtqpnlkihgedcbaa``__^^^^^^____```aabbm»»»»ººº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶µµ´µ´´´³³³³³²²²²²±±±±°°°°¯¯®¯®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¦¥¥¥¤¤¤£££££¢¢¢¢¡¡¡¡     ŸŸŸŸŸžžœœœœ››š›šššš™™™™™™˜˜˜˜——–––––••••””“”“““““’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„…„„ƒƒƒ‚ƒ‚‚€~~~}}}|}}|||{{{{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèçççççææææ½ywwuttsrqpooonnnnnnnnnnooppqqrstuvwxyz{|~€ƒƒ…†‡¦¦¦¦¦¦¦§§§§§§¨¨¨©ªª«««¬¬¬­®¯°°±±²³³´µ··¸¸¹º»¼½¾¾¿ÂÄÇÉËÎÐÒÔÖ×ÙÚÛÛÜÜŸœ™—”‘Œ‰‡…‚€}{ywusqonljigfedbba```___^_^^____```aaabb™»¼»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´´´´´³³³²²²²±±±±±°°°°¯¯¯®®®®­­­­¬¬¬¬¬««««ªªª©©©©¨¨¨¨¨§§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžžžœœ››››šššš™™™™™˜˜˜˜˜————––•–•””””””“““““’’’‘‘‘ŽŽŽŽŒŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆ‡‡‡‡‡‡†††…………„„„„„ƒƒƒƒ‚‚‚‚€€€€~~~~}}||||{{{{{zëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèèçççæçæææ—wvutsrqqpooonnnnnnnnnooopqrrstuuwwxy{|}~€‚ƒ…†‡§¦¦¦¦¦¦¦§§§§¨¨¨¨©ªªªª¬¬¬­­®¯¯°±²²³³´µ¶··¸¹º»¼¼¾¾¿ÀÃÆÈËÍÏÑÓÕרÙÚÛÛÜÜžš˜–“Ž‹‰†„|{xvtrpomkihgfdcbba``____^_______``aaabb{¼¼»»»»ººººº¹¹¹¸¸¸¸¸·····¶¶¶¶µµµ´´´´³³³²²²²²²±±±±°°°°°¯¯®®®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡      ŸŸŸŸžžžžœœœœ›››šššš™™™™™™˜˜˜˜————––•–•••”””””“““““’’’’’‘‘ŽŽŽŽŽŽŒŒŒŒ‹‹‹‹‹Š‰Š‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…………„…„„„ƒƒ‚‚‚‚‚‚€€€€€~~~}}}}|||{{{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêééééèèèèçèçççççæÐwvussrqppooooonnnnnoooppqqrstuuuwxy{{}~ƒ„…‡ˆ¦§¦¦¦§¦¦§¨§¨¨¨¨¨©ªª««¬¬­­®¯¯°±±²³´´µ¶¶¸¸¸ºº»¼½¾¿ÀÂÄÇÊÌÎÐÒÔÖ×ÙÚÛÜÜÝŸœš—”’Ї…‚€~{zwvsqonljihfedcbaa```___________`aaabbbª¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´µ³³´³³³³²²²²±±±±±°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªªª©©©¨¨¨¨¨§§¦§¦¦¦¦¦¥¦¥¥¥¤¤¤¤£¢£¢¢¢¡¢¡¡     ŸŸŸŸžŸžžžœœœœ››››ššššš™™™™˜˜˜—˜˜—––––•••••”””““““““’’’’‘‘ŽŽŽŽŽŒŒŒŒŒ‹‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††……„„„„ƒƒ„ƒƒ‚‚‚€€€~~~}}}}}||||{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèèèççççææææ«vtssrqpoooooonooooooopqqrsstuvwxyz{|}€ƒ„…‡ˆ§§§§¦§§¦§§¨¨¨¨¨©©ªª««¬¬­­®®¯°±±²²³´´µ¶·¸¸¹»¼¼½¾¾¿ÁÄÆÈËÎÐÑÔÕ×ÙÚÚÛÜÜÝž›˜–“‘Ž‹ˆ†„}{yvtrqomkjigfedbbaa``___`_____`_`aaabbb½½¼¼¼¼¼»»»ºººº¹¹¹¹¸¸¸¸·····¶¶¶µµµ´´´´³´³³³²²²²²²±±±±°°¯¯¯¯¯¯®®­­­¬­¬¬¬¬««««ªªª©ª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤¤££¢££¢¢¡¡¡¡ ¡    ŸŸŸŸžžžžœœœœ›œ››››ššš™™™™™˜˜˜˜————––•–•••”””””“““““’’’’’‘‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹ŠŠ‹ŠŠ‰‰‰ˆˆˆˆˆˆ‡‡‡‡†††…†……„…„„„ƒƒƒ‚‚‚‚€€€€~~}~}}}||||{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççççæææŠtsrrqpoooooonooooooppqqrstuuvwyy{|}~€‚ƒ„…‡ˆ§§§§§§§§§¨¨¨©¨©©ªªª«¬¬¬­®®®°±±²²³´´µ¶·¸¸¹º»¼¼¾¾¿ÀÂÅÈÊÌÏÐÓÕ××ÙÚÛÜÜÝ œš—•’Šˆ…‚€~|zxusronlkihgfdcbbaa````__`___````abbbbt½½½¼¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´´´³³³³²²²²²²±±±°°°°¯¯®®®®­®­­­¬¬¬¬¬¬«««ªªªªª©©©¨¨¨¨§§§¦§¦¦¦¦¥¥¥¤¤¤¤¤££¢£¢¢¢¡¡¡¡¡¡    ŸŸŸŸžžžœœœœ››››ššš™™™™™˜˜˜˜————––––••••”””“““““’’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††…………„„„ƒƒƒƒƒ‚‚‚‚€€€€~~~}}}}|}||||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééééèèèèçççææÉssqqpppoooooooooooppqqrssuuvwxyz|}~€‚……‡ˆ§§§§§§§§§§¨¨¨©©©©ª««¬¬¬¬­¯¯¯°±±²³´´µ¶·¸¸¹ºº»¼¾¾¿ÀÁÄÆÉËÍÐÒÔÖ×ÙÚÛÜÝÝÝž›˜–“‘ŽŒ‰‡„‚€}{ywtspomljihgedcbbaa``````___``a`abbbbc§¾½½½½¼¼¼¼»»»ºººººº¹¹¸¸¸¸¸¸···¶¶¶¶¶µµ´´´´³³³³²²²²²²±±±±±°°°°¯¯®®®­­­­¬­¬¬¬¬¬¬«««ªªª©©©¨¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¥¤£££££¢£¢¢¡¡¡      ŸŸŸŸžžžœœœœœ›››šššš™™™™™˜˜˜—˜———––––••••”””“““““““’’’’‘‘‘ŽŽŽŽŒŒŒŒ‹Œ‹‹‹ŠŠ‰Š‰‰‰‰ˆˆˆˆ‡‡‡‡†††…………„…„„„ƒƒƒ‚‚‚‚‚€€€€€~~~~}~}}}||||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêêéééééèèççççççæªsqqppoooooooooooppqqrsttuvwxzz{|~€‚„…†ˆ‰¨§§§§§§§¨¨¨¨©©©©ªª«¬¬¬¬­®®¯°±²²²³´µ¶··¸¹¹º»¼½¾¿¿ÀÃÅÈÊÍÏÑÔÕרÚÛÜÝÝÝ š—•’Šˆ…ƒ~|zxutrpomkihgeddbbbba``````_````aaabbbc޾¾¾½½½¼¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸·¸··¶¶¶¶µµµµ´´´³´³³²²²²²²±±±±±°°¯°¯¯¯®®­­®­¬¬¬¬¬¬¬«¬«ª«ªª©©©©¨¨¨§§§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡       ŸŸŸŸžžžœœœœ››š›ššš™™™™™™™˜—˜——–––––•••••”””““““““’’’‘‘ŽŽŽŽŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒ‚ƒ‚‚‚€€€~~~~~}}}||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêééééèèèèççççæærqpoooooooooooopqqrsstuvwxyz{|}~ƒ…†‡ˆ¨¨¨§§§¨§§¨¨©©©©ªªª«¬¬¬¬­®®¯°°±²²³´´µ¶·¸¸¹º»¼½¾¾¿ÀÁÄÇÊÌÎÐÓÔרÙÚÜÜÝÝÝž›™—“‘ŽŒ‰‡„‚€~{ywusqonljihfedcbbba``````````a`aabbbby¾¾¾¾¾½¾½½¼¼¼¼»»º»ºººº¹¸¸¹¸¸¸¸¸···¶¶¶¶µ¶µ´µ´´´´³³³²²²²²²±±±°°°°¯°®®®®®®­­­­¬¬¬¬¬¬«««ªªª©©©©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡      ŸŸžžžžžœœœœœœ››šššš™™™™™˜˜˜˜—————–•–••••”””““““““’’’‘‘’‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡†‡††………„„„„„„ƒ‚‚‚‚‚€€€€~~~}}}}}}|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‡‰Š‰‡…‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|sjdadjs|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„‰“””””ˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~veHBBBEi€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŠ€€€€€€€€ƒ“”””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~gNa€€€€€€€€xEBBBa€€€€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰””Š€€…ŠŠ‡€€€”””Š€€€€Š‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~fRBBa€€qabl€€€YBBBa€€€€dd~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡’””””Š€€Š”””ˆ€€Š”””Š€€…€‚“’†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lJBBBBa€€aBBBg€€`BBBa€€r€|FJm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€””””””Š€€Š”””ˆ€€”””Š€€Š†€Š””ˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~XBBBBBBa€€aBBBh€€ZBBBa€€ap€bBBh€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””Š€€…ŠŠ‡€€‚“”””Š€€Š‘€“”Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBa€€qabl€€yFBBBa€€aO€}HBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””Š€€€€€€€…’””””Š€€Š”‡€‰”Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBa€€€€€€€rJBBBBa€€aBk€eBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ŠŠŠŠŠŠ”””Š€€€€€€€Ž””””Š€€Š”’’Š€€‰„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€aaaaabBBBa€€€€€€€WBBBBa€€aBK~Ja€€et€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””Š€€Š”’ˆ€€‚“”””Š€€Š””‰€ˆŠ€€Š’ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBBBBBBBa€€aCJj€€|FBBBa€€aBBf€ha€€aHy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””Š€€Š”””„€€‹”””Š€€Š””“ˆ€€Š”‘€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€BBBBBBBBBa€€aBBCv€€_BBBa€€aBCH}k€€aBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ŠŠŠŠŠŠ””Š€€Š”””€€‚”””Š€€Š”””Š€€€€Š””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€X€€€aaaaaaRBBa€€aBBBZ€€yCBBa€€aBBBa€€€€aBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡”€€€€€€€€€Š””Š€€Š””””‚€€Ž””Š€€Š”””“‚€€€Š””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€mB€€€€€€€€€aBBa€€aBBBD{€€WBBa€€aBBBE{€€€aBBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’”€€€€€€€€€Š””Š€€Š””””Š€€…””Š€€Š””””Œ€€€Š””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~JB€€€€€€€€€aBBa€€aBBBBa€€sBBa€€aBBBB\€€€aBBFZ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“”””””””””””””””””””””””””””””””””””””””””””””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~GBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBG~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””””””””””””””””‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€gBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBg€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘”””””””””””””””””””””””””””””””””””””””””””””””€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€NBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ”””””””””””””””””””””””””””””””””””””””””””””””””ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBCBBBBBBBBBBBCBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž”””””””””””””””””””””””””””””””””””””””””””””””””Ž€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€VBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€””””””””””””””””””””””””””””””””””””””””””””””””””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€EBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBE€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”””””””””””””””””””””””””””””””””””””””””””””””””””‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|BCBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBCBBBBBBB|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…”””””””””””””””””””””””””””””””””””””””””””””””””””„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~xqjebbejqx~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBCs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ„…………„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡”””””””””””””””””””””””””””””””””””””””””””””””””””‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|k_TEBBBBBBBBBBBBET_k|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€jBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„†‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡†„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””””‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€v_NBBBBBBBBBBBBBBBBBBBBBBN_v€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚†ˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ†‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}eLBBBBBBBBBBBBBBBBBBBBBBBBBBBBLd}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€aBBBBBBBBBCBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€y\CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBC\y€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ”””””””””””””””””””””””””””””””””””””””””””””””””””‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{ZBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB[{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€jBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…”””””””””””””””””””””””””””””””””””””””””””””””””””…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sBBBBBBBBBCBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”””””””””””””””””””””””””””””””””””””””””””””””””””‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sLBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBLs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|BBBBBBBBBBBBBCBBBBBBBBBBBBBBCBBBBBBBBBBBBCBBBBBBBBB|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””””””””””””””””””””””””””””””””””””””””””””€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€bBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€EBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBCBBBBBBBE€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž”””””””””””””””””””””””””””””””””””””””””””””””””Ž€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{PBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBO|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€VBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š””””””””””””””””””””””””””””””””””””””””””””””“’‘‚vvwy{~€€€€€€€€€€€€€€€€€€€€€€vJBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBJu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCEGJMyŸž›•ˆƒ€€€€€€€€€€€€€€€€€€€€€€‚‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ””””””””””””””””””””””””””””””””””””””””’‹‹‹‹‹pkkkkkkklqux~€€€€€€€€€€€€€€€€oEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBEo€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCHPTZ^^^^^²¾¾¾¾¾¾¾»­¢˜†€€€€€€€€€€€€€€€€ƒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘””””””””””””””””””””””””””””””””””””Ž‹‹‹‹‹‹‹‹‹…kkkkkkkkkkkkknt{€€€€€€€€€€€€mBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBEOV^^^^^^^^^q¾¾¾¾¾¾¾¾¾¾¾¾¾¸£‘€€€€€€€€€€€„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””‘‹‹‹‹‹‹‹‹‹‹‹‹xkkkkkkkkkkkkkkkkmt|€€€€€€€€mBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€gBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCNW^^^^^^^^^^^^—¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¤Œ€€€€€€€€„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘–šŸ¤¨©¨¥¢ žœ—•”””””””””””””””””””””””“Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‰lkkkkkkkkkkkkkkkkkkkpy€€€€€rCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCr€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡•œ¡¨®²´°^TQOLECBBBBBBBBBBBBBBBBBBBBBBBER]^^^^^^^^^^^^^e»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²—€€€€‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚š§©©©©©©©©©¨¦¦§§§¦ ›•””””””””””””””””””“‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹zkkkkkkkkkkkkkkkkkkkkkknx€€yGBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBHy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”¡±´´´´´´´´´‹YYZYYXQKCBBBBBBBBBBBBBBBBBBFT^^^^^^^^^^^^^^^^’¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶˜€‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰š©©©©©©©©©©©©©©§¦¦¦§§§§¦ ˜”””””””””””””””‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‡lkkkkkkkkkkkkkkkkkkkkkkkkoxMBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ¡´´´´´´´´´´´´´²eYYYYYYYYQHBBBBBBBBBBBBBBDR^^^^^^^^^^^^^^^^^j¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾´“‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¡©©©©©©©©©©©©©©©©©§¦¦§§§§§¦¦£š”””””””””””‘Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ukkkkkkkkkkkkkkkkkkkkkkkkkkJBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´©iYYYYZYYYY[dffb^^^^^^^^^^^^^^^^^n²¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾µ¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨§§¦§§§§§¤£££¢—‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹€mkkkkkkkkkkkkkkkkkkkkkkkkkkkkA9999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBK€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´±yYYYZYZY[dffffb^^^^^^^^^^^^^^^€º¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾ª¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§§¦¦§§¤££££££•‹‹‹‹‹‹‹‹‹‹‹‹‡vkkkkkkkkkkkkkkkkkkkkkkkkkkkkk[999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBl€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´—eYYYZZdffffffa^^^^^^^^^^^^jŸ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨§¦¦£££££££££‘‹‹‹‹‹‹‹‹‹‰zlkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkF9999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBS€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´²‹`YZdffffffff`^^^^^^^^^e“»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¬¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨£¢££££££££¡‹‹‹‹‹‹…xlklkkkkkkkklkkkkkkkkkkkkkkkkkkkke999999999999999999@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´±vfffffffffe_^^^^^^q—»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¥  ¡¢£££££££œ‹‹ŠzokkkkkkkkkkkkklkkkkkkkkkkkkkkkkkkkkQ999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÀÐÞ‡jffffffd^^b}‘²¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾±¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£ŒŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§       ¡¡¡¢¡¡„nkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk@9999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBJ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ºÐÐÐÐÐÏȺ« › «°·¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾©¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡             œnkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkke999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÌÐÐÐÐÐÐÐÐÐÐÐÐÐÏ¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦•ŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©£               ˆkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkT9999989999999999999999ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÆÐÐÐÐÐÐÐÐÏÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾³¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŽŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                 okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkJ9999999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´»ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¢                 ‡kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;99999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µËÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                  œmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkf999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¿ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÏ¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                   kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk[9999999999999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBk€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐО¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¥                    kkkkkkkkkkkkkkkkkkkkkkkkkkklkkkkkkR9999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€£´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÁÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐʾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¤‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                     mkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkN99999999999999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐп¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾°¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                      {kkkkkkkkkkkkkkkklkkkkkkkkkkkkkkkkI99999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBU€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¿ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐþ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾­¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¢ Ÿ                    ˆkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkC999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ËÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾«¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦“ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ÿ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                       “kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk>999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§       Ÿ                 vkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk:9999999999999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´»ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¤Ÿ                        }kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk>99999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBH€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÃÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐľ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¨¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                         ‚kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkC99999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÌÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÆ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾«¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©                          …kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkI99999999999999999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBU€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¶ÐÐÐÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐǾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦šŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                          …kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkN99999999999999998999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´½ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐǾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾°¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¤                          ‚kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkR999999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÃÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÆ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©£                          }kkkkkkkkkkkkkkkkkkkkkkkkkkkkkklk[999999999999999999999999999999ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBk€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÇÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐľ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                          vkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkf999999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÍÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©                           okkkkkklkkkkkkkkkkkkkkkkkkkkkkkkkk;99999999999999999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦™ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨                           lkkkkkkkkkkkkkkkkikkkkkkkkkkkkkkkkI99999999999899999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBW€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´·ÐÐÐÐÐÐÐÐÐÐÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾Å¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦›ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ÿ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                          “kkkkkklkkkkkkkkkkkkkkkkkkkkkkkkkkkT99999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”´´´´´´´´´´´´´´´´´´´´´´´´´³°¯¯¯¯¯°¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¡’’’’’’’’’’’’’’¢º¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½©vhhhx€€€€€€€€€€€€€†‘¦˜ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ž©©©©©©©©©©©©©©©©©©©©©©­¾ÅÅÅÅÅÅÅÅÂÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁ½µµµµµµµµµµµ´´µµµ³ŠlkkkkkkkkkkkkkkkkdZPNNNNNNK.+++++++++++++,,,,,,,++,6>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBF{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦´´´´´´´´´´´´´´´´´´´´´´´±¯¯¯¯¯¯¯¯º¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼±’’’’’’’’’’’’’’’’”«¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾ª‹mhhhhhhj~€€€€€€€€€€€€‚…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„¦©©©©©©©©©©©©©©©©©©©©µÅÅÅÅÅÅÅÅÅÅÂÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÀ¶µµµµµµµµµµµµµ´´´µµ§vkkkkkkkkkkkkj^RNNNNNNNNNNB,,+++++++++++,,,,,,,+++/02:ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…¯´´´´´´´´´´´´´´´´´´´´²°¯¯¯¯¯¯¯¯¯·¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¸–’’’’’’’’’’’’’’’’’’›¸¾¾¾¾¾¾¾¾¾¾¾¾º˜thhhhhhhhhhq€€€€€€€€€€€€tsv€‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©¬¿ÅÅÅÅÅÅÅÅÅÅÅÃÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÀ¸µµµµµµµµµµµµµµ´´´µµµ´‡kkkkkkkkki[NNNNNNNNNNNNNN7,,,+++++++++++,,,,,,,,000018ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBW€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´°¯¯¯¯¯¯¯¯¯¯¯µ¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼ž’’’’’’’’’’’’’’’’’’’’“®¾¾¾¾¾¾¾¾¾¸Žkhhhhhhhhhhhhhx€€€€€€€€€€€€€sssst}‰ŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©°ÂÅÅÅÅÅÅÅÅÅÅÅÅÄÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁºµµµµµµµµµµµµµµµµ´´´µµµµšmkkkkkj[NNNNNNNNNNNNNNNNM2,+,+++++++++++,,,,,,-00000008ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•´´´´´´´´´´´´´´´´³°¯¯¯¯¯¯¯¯¯¯¯¯²¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼£’’’’’’’’’’’’’’’’’’’’’’’¢½¾¾¾¾¾ºihhhhhhhhhhhhhhhi{€€€€€€€€€€€€{sssssss~‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•©©©©©©©©©©©©©©´ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÁÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁ»µµµµµµµµµµµµµµµµµµµµ´µµµµ¦qkkk`PNNNNNNNNNNNNNNNNNNJ/,,++++++++++++,,,,,.000000001;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBGy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´²¯°¯¯¯¯¯¯¯¯¯¯¯¯°¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼§’’’’’’’’’’’’’’’’’’’’’’’’’›»¾¾¾œohhhhhhhhhhhhhhhhhhk}€€€€€€€€€€€yssssssssuŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©·ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÂÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁ»µµµµµµµµµµµµµµµµµµµµµµ´µµµµªufTNNNNNNNNNNNNNNNNNNNNNF,,+++++++++++++,,,,/00000000004?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBCr€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›´´´´´´´´´´´´²¯¯°¯¯¯¯¯¯¯¯¯¯¯¯¯¸¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¨’’’’’’’’’’’’’’’’’’’’’’’’’’’™¹°yhhhhhhhhhhhhhhhhhhhhhm€€€€€€€€€€€€ussssssssssx†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“©©©©©©©©©©¸ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀÀÀÁÁÁÁÁÁÁÁºµµµµµµµµµµµµµµµµµµµµµµ´µ´´µµµ¦\NNNNNNNNNNNNNNNNNNNNNNND,,,++++++++++++,,,0000000000000:BBBBBBBBBBBBBBBBBBBBBBBBBBBBn€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜´´´´´´´´´´²¯¯¯°¯°¯¯¯¯¯¯°¯¯¯¯µ¼¼¼¼¼¼¼¼¼¼¼¼¼¼¥’’’’’’’’’’’’’’’’’’’’’’’’’’’’’„ihhhhhhhhhhhhhhhhhhhhhhho€€€€€€€€€€€€~sssssssssssss€ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©¸ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÀÀÀÀÀÀÁÁÁÁÁÁÁ¹µµµµµµµµµµµµµµµµµµµµµµµµµ´´µ´ª§Ÿ[NNNNNNNNNNNMNNNNNNNNNNNC,,,++++++++++++,.000000000000004ABBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’´´´´´´´´²¯¯¯¯¯¯¯¯¯¯¯¯¯°¯¯¯¯±¼¼¼¼¼¼¼¼¼¼¼¼¼¡’’’’’’’’’’’’’’’’’’’’’’’’’’’’tkkihhhhhhhhhhhhhhhhhhhhhhho€€€€€€€€€€€€€zssssssssssssssxˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡¢©©©©©·ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÂÀÀÀÀÀÀÀÁÁÁ¿·µµµµµµµµµµµµµµµµµµµµµµµµµµµ´±©§§§WNNNNNNNNNNNNNNNNNNNNNNND-,,++++++++++++/0000000000000002>BBBBBBBBBBBBBBBBBBBBBBEo€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¬´´´´´²¯¯¯¯¯¯¯°°¯¯¯¯¯¯°¯¯¯¯¹¼¼¼¼¼¼¼¼¼¼µ™’’’’’’’’’’’’’’’’’’’’’’’’’’’’‰okkkkihhhhhhhhhhhhhhhhhhhhhhhn~€€€€€€€€€€€€wsssssssssssssssu…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—©©©´ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀÀÀÀÀÁ»µµµµµµµµµµµµµµµµµµµµµµµµµµµµµ°§§§§§§˜SNNNMNNNNNNNNNNNNNNNNNNNH0,,++++++++++,000000000000000000;BBBBBBBBBBBBBBBBBBBBJu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚´´´²¯¯°¯¯¯¯¯¯¯°°¯¯¯¯¯°¯¯¯µ¼¼¼¼¼¼¼¼¼©“’’’’’’’’’’’’’’’’’’’’’’’’’’’’„kkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhl|€€€€€€€€€€ssssssssssssssssss‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ °ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÁÀÀÀÀÀÀ¾·µµµµµµµµµµµµµµµµµµµµµµµµµµµµµ¯§§§§§§§§‘ONNNNNNNNNNMNNNNNNNNNNNNK3,,+++++++++.00000000000000000009BBBBBBBBBBBBBBBBBBP|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š¨³¯¯¯°¯¯¯¯¯¯¯¯°°¯¯¯¯¯°¯¯°¼¼¼¼¼¼¼²š’’’’’’’’’’’’’’’’’’’’’’’’’’’’’€kkkkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhjz€€€€€€€€€zsssssssssssssssssssŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†»ÄÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀ¾¹µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ®§§§¨§§§§§§zNNNNNNNNNNNNNNNNNNNNNNNNN=,,++++++++0000000000000000000008BBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¤¯¯¯°¯¯¯¯¯¯¯¯°¯¯¯¯¯¯¯¯¯·¼¼¼¼´Ÿ’’’’’’’’’‘’’’’’’’’’’’’’’’’’’’’}kkkkkkkkkkjhhhhhhhhhhhhhhhhhhhhhhhhht€€€€€€€tssssssssssssssssssss}ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€°¾¾ÀÃÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÀÀ¼¸µµµµµ´µµµµµµµµµµµµµµµµµµµµµµµµµ­§§§§§§§§§§§§gNNNNNNNNNNNNMNNNNNNNNNNNNG2,++++++-00000000000000000000008BBBBBBBBBBBBBLs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€xvvƒš¯¯°°¯¯¯¯¯¯¯¯¯¯¯¯¯°°°²¼¼­œ’’’’’’’’’’’’’‘’’’’’’’’’’’’’’’’’|kkkkkkkkkkkkihhhhhhhhhhhhhhhhhhhhhhhhhm|€€€€€€|ssssssssssssssssssssss}ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›¾¾¾¾¾¾ÁÃÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅü¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ®§§§§§§§¨§§§§§SNNNNNNNNNNNNNNNNNNNNNNNNNM>-+++++/000000000000000000000009BBBBBBBBBBCc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|vvvvvyŒš­°¯¯¯¯¯¯¯°¯¯¯¯¯¯¬š›”’’’’’’’’’’’’’’’’’’’’’’’’’‘’’’’’’~kkkkkkkkkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhhhis€€€€€vsssssssssssssssssssssss~ŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¾¾¾¾¾¾¾¾¾¾¿ÁÂÃÄÄÅÅÅÄÄÃÂÁ¿¾¾½µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ¯§§§§§§§§§§¨§§§§ˆNNNNNNNNNNNNNNNNNNNNNNNNNNNK9+++-0000000000000000000000000:BBBBBBBB[{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vvvvvvvvvw~•𢩭¯®©¢š•~wv|’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’‚kkkkkkkkkkkkkkkjhhhhhhhhhhhhhhhhhhhhhhhhhhhjw€€€|sssssssssssssssssssssssss€ŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¯¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¸µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ°§§§§§§§§§§§¨¨§§§§eNNNNNNNNNNNNNNNNNNNNNNNNNNNNJ9,/0000000000000000000000000096@O`lmkkiihgb\UNP€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾º´¯ª¦£ žœœŸ¡¤¨¬±·½¾¾¾¾¾¾¾´¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬ŒWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVPJEDDFfxaOPTX[_behkmnpvƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vvvvvvvvvvvvvvvvvvvvvvvwxyy{z{{{{{{{{{zyxxvvvvvvvvi_______________________XLLLLLLLLLMLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLKC;5556>N^lmlkiihgfeeddk~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¸¯¦Ÿœœœœœœœœœœœœœœœœœœœ£ª³¼¾¾¾¼¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬žZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWUMFEEEDEaxdPPSW[^adgkmnprƒ‡‰†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|vvvvvvvvvvvvvvvvvvwyz{{{{|{|{{{|{{{{{{{||{zyxwvvvs______________________\MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLJ@655555=L]kmlkjihgffeddcwzy|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¨¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶ª œœœœœœœœœœœœœœœœœœœœœœœœœœœ¥°»¾±¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬ªeWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVMFEEEEEE\yfQOSW[^adgjlnprx‡‰‹‹‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zvvvvvvvvvvvvvvxz{{{{||{|{{{||{|||{{|{|||{|{|{zywve_____________________OLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ?65555555555;IZimlkjihggfeddccboxxwvvtux{}‚…‰Ž’”“’‘ŽŽŒ‹Š‰ˆ‡††…„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}qnnnn^WWWWWWWWWWWWWWWWWWWWWWWWWHEEEUykTORVZ]adgjlnoqsx†ŠŒ“–šš–’ŽŠ†‚zslhhiiiiijjjkkkkklllllmmmp€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|niiiiVLLLLLLLLLLLLMLLLLLLLLLLLL9555:HXhmlkjihgffeddccgvyxwvuuuxz}‚„‰’”“’‘ŽŒ‹Š‰ˆ‡‡†…„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yonnkXWWWWWWWWWWWWWWWWWWWWWWWVEEEQwmVNRVZ]`cfilnoqt€‰ŠŒ’–𛗒І‚{tlhhiiiiijjjjjkkklllllmmmmn€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wkiifMLLLLLLLLLLLLLLLLLLLLLLLK6558FVgmlkjihhgfeddcepyyxwwuuuwz|„ˆ’”“’‘ŽŽŒ‹‹‰‰ˆ‡†…„ƒ‚€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vnnaWWWWWWWWWWWWWWWWWWWWWWWTEEMtpYNRUY]`cfikmoq{†ˆŠŒŽ’•™›—“‹‡ƒ|umiiiiiiijjjjjkkkkllllmmmmno}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€tiiYLLLLLLLLLLLLLLLLLLLLLLLH558DTenmkjiihgfedddnyyyxxwvtuwz|„ˆŒ‘”“’’ŽŒ‹Š‰‰ˆ‡†…„ƒ‚‚€~y~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vlXWWWWWWWWWWWWWWWWWWWWWWREJqs[NQUY\`bfikmoyƒ†‰ŠŒŽ’•™›˜“‹‡ƒ}unihihiiijjjjjkkllllllmmmmmoq|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~sgMLLLLLLLLLLLLLLLLLLLLLLE57CSdmlkjiihgfeeenyzzyxwwvutwy|~ƒ‡Œ‘““’’ŽŒ‹Š‰ˆˆ‡†…„ƒ‚‚€zsz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€p]WWWWWWWWWWWWWWWWWWWWWPInt]OQUX\_beikoy‚„†ˆŠŒŽ’•˜›˜“‹‡ƒ~voihhiiiijijjjkkkklllmmmmmmoqr{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lSLLLLLLLLLLLLLLLLLLLLLC6AQbmmlkiihgfegqzzzzxxxwvutvy{~ƒ‡‹”““’‘ŽŒ‹‹Š‰ˆ‡†……„ƒ‚€ztmw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€whYWWWWWWWWWWWWWWWWWWRjw_NPTX\_beir{ƒ…†ˆŠŒŽ‘•˜›˜”Œˆ„~vpihhiiiiiijjjkkklklllmmmmmoqrsz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€taOLLLLLLLLLLLLLLLLLLC@O`lmlkihhggnw{{zzyyxxwvuuvy{~ƒ†Š”“’’‘ŽŒ‹‹Š‰ˆ‡†……„ƒ‚€{tmjv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vj^WWWWWWWWWWWWWWWewbOPTX\_dmu}‚„†ˆ‰‹‘”˜›™•‘Œˆ„wpihhiiiijjjjjkkkkklllmmmmnnprssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sdULLLLLLLLLLLLLLLGN^lmlkjilqx||{{zzzyxxwvutvy{~€ƒ†Š““’’‘ŽŒŒ‹Š‰ˆ‡†……ƒƒ‚€€|unjiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tkf\WWWWWWWWWWfdOPV^dkrwz}€ƒ„†‡‰‹”—›™•‘‰„€xqjhhiiiiijjjkkkkklllllmmmmnprsssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}qe^SLLLLLLLLLLN]lmosuy~~}||{{{zzyyxwvutvx{}€‚…Š“”“’‘ŽŒ‹Š‰ˆ‡††…„ƒ‚€|uojiiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}yuqnllnqra`eimpswz}~€‚„†‡‰‹“—›™–‘‰…€yrjhhiiiiijjjkjkkklllllmmmmnprssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|wrmiggimp~ƒƒ‚€~}}||{{zzyyxwvutux{}‚…ŠŽ“““’‘ŽŒ‹Š‰ˆ‡††„„ƒ‚€}vpjiiiv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€taeimpsvy|€‚„†‡‰‹“–šš–’Ž‰†zrkhhiiiiijjjjkkklkllllmmmmnprsssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚„„‚€€}}||{{zzyyxwvuuux{}€‚…‰Ž’““’‘ŽŽŒ‹Š‰ˆˆ‡†…„ƒ‚€}wpjjiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€weilpsvy|€‚„…‡‰ŠŒ“–šš–’ŽŠ†‚{slhhhiiiijjjjkkkkkklllmmmmnprssssss|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ‚€~~}||{{zzyyxwwuuuwz}„‰’”“’‘Œ‹Š‰‰‡‡†…„ƒ‚‚€€}xqkiiiiiy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{hlorvy|~€‚ƒ…‡‰ŠŒ’–™›—“ŽŠ†‚{tlhhiiiiijjjjkkkkkllllmmmmnpqsssssss~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚€~}||{{zzyyxxwvuuwz|‚„ˆ‘““’’‘ŽŒ‹‹‰‰‡‡†…„ƒ‚‚€€~xrkjjiiji|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lorvx{~€‚ƒ…‡ˆŠŒŽ’–™›—“‹‡ƒ|umihiiiiijjjjkkkkkllllmmmmnoqssssssss€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚€€~}||{{zzyyyxwvuuwz|„ˆŒ‘”“’‘ŽŒ‹ŠŠ‰ˆ‡†…„ƒ‚‚€~yrlijjiijj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€rrux{}€ƒ…‡ˆŠŒŽ’•™›˜“‹‡ƒ}vnihiiiiijjjjjkkkkklllmmmmnoqssssssssv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}|||{zzyyxxwvutwy|~ƒ‡Œ”“’‘ŽŒ‹Š‰‰ˆ‡†…„„ƒ‚€zslijiiiiim€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wtx{}ƒ„†ˆŠŒŽ‘•˜›˜”Œ‡ƒ~voihiiiiijjjjjkkkklkllmmmmmoqrssssssssx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}||{{zzyyyxwvttwy|~ƒ‡‹”“’’ŽŒ‹ŠŠ‰ˆ‡†…„ƒƒ‚€ztmiiiiiijir€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|w{}ƒ…†ˆŠ‹‘”˜›˜”Œˆ„~wpihiiiiijjjjkkkkkklllmmmmmoqrsssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~||{{{zyyxxwvutvy{~ƒ†‹”““’‘ŽŒ‹‹Š‰ˆ‡†…„„ƒ‚€{tniiiijiiijx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€z}ƒ„†ˆ‰Œ”˜›™•‘ˆ„xpihhiiiijjjjkkkkkllllmmmmmoprssssssssss€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}||{{zzyyyxwvutux{~€ƒ†Š”“’‘‘Ž‹‹Š‰ˆ‡†…„„ƒ‚€{uniiiiiijjij~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~‚„†ˆ‰‹”—𙕑‰…€xqjhhiiijijjjjjkkllllllmmmmoprssssssssssv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~|{{{zzyxxwvutux{~€ƒ†ŠŽ“““’‘ŽŽŒ‹Š‰ˆ‡†……„ƒ‚€|vojiiiiiiiiio€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚„†‡‰‹“—𙕑‰…€yqjhhiiiijjjjjjkkkllllllmmmnprsssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|{{zzyyxwvuuvx{}€‚…ŠŽ“““’‘ŽŒŒ‹Š‰ˆˆ‡†„„ƒ‚€€}vpjiiiiiiiijjw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ†‡‰‹“–šš–‘ŽŠ†zrkhhhiiiijjjjkkkklllllmmmnnprssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|{zyyyxwvutuxz}‚…‰Ž’”“’‘Œ‹Š‰ˆˆ‡†…„ƒ‚€€}wpjiiiiiiiiiik€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‡‰Š“–™š–’ŽŠ†zslhihiiijjjjjjkkkkllllmmmmnpqssssssssssssy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}zzyyxwwutuwz}‚„‰’”“’‘ŽŒ‹Š‰‰ˆ‡†…„ƒ‚€~xqkiiiiijiiiiis€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡‰ŠŒ“–™›—“ŽŠ†‚{tlhhhiiiijjjjkkkkllllmlmmmnpqsssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zyyywwvuuwz|‚„ˆ‘”“’‘ŽŽŒ‹‹‰ˆˆ‡†…„ƒ‚€~xrkjiiiiiiiiiij~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŒŽ’•™›—“‹‡‚|tmihhiiiiijjjjkkkkllllmmmmnoqsssssssssssssy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|yxxwvuuwy|„ˆŒ‘““’‘‘Œ‹‹‰‰ˆ‡†…„ƒƒ‚€yrliiiiiiiiiiiit€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‹Ž’•™›˜“‹‡ƒ}unihiiiiiijjjkkkkkllllmmmmnoqrsssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yxvvutwy|~„‡Œ”“’‘ŽŽŒ‹Š‰‰ˆ‡†…„ƒƒ‚€zsliiiijiijiiiik~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†‘•˜›˜”Œˆƒ~voihhiiiijjjjkjkkkllllmmmmmoqssssssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}wvutvy{~ƒ‡‹”“’‘‘ŽŒ‹Š‰ˆˆ‡†…„ƒƒ‚€zsmiiiiiiijijiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜›˜”Œˆ„~woihhiiiijjjjkkkkklllmmmmmnopssssssssssssssw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yutvy{~ƒ†‹”“’’‘ŽŽŒ‹‹Š‰ˆ‡††„ƒƒ‚€{uniiiiiiiijiijjp€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ˜™•‘Œˆ„wpihhhiiiijjjjkkkklllllmmmmoprssssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€uvx{~€ƒ†Š“”’‘‘ŽŽŒ‹‹Š‰ˆ‡†……„ƒ€|unjiiiiiiiiiijil~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†•‘‰…€xqjhhhiiiijjjjkkkkkllllmmmmoprssssssssssssst}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~x{}€‚…Š“““’‘ŽŒ‹‹Š‰ˆ‡‡……„ƒ‚€€|vojjiiiiiiiiijij{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡‰…€yrkhhhiiijjjjjjkkklllllmmmmnprssssssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}€‚…‰Ž“”“’‘ŽŽŒ‹Š‰ˆ‡‡†…„ƒ‚}vpjijiiiiiiiiiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…†yrkhhiiiiijjjjjkkkkllllmmmmnpqssssssssssssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‰Ž’”“’‘ŽŽŒ‹Š‰ˆ‡‡†…„ƒ‚€€}wpjjiijjiiiiiiiiv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zslhhhiiiijjjjkkkkkllllmmmmnprssssssssssssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚‰’”“’‘ŽŽŒ‹ŠŠˆˆ‡†…„ƒ‚€€}xqjiijiijjiiiiiiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{mhhiiiiijjjjkkkkklllmmmmmnoqssssssssssssst{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘”“’‘ŽŽŒ‹Š‰ˆˆ‡†…„ƒ‚€~xqkiiiijijiiiiijw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{kiiiiiijjjkkkkkllllmlmmmoqsssssssssssssu}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘’‘ŽŒ‹ŠŠ‰ˆ‡†…„ƒƒ€~yrkiiijijiiijiil{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~piiijjjjkkkklkllllmmmnoqrssssssssssssw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ŽŒ‹ŠŠˆˆ‡†…„ƒƒ‚€zsliiiijjijjiijp~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wkijjjjkkkkllllmmmmmoqsssssssssssst{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŽŽŒ‹‹‰‰ˆ‡†…„„ƒ‚€ztmiiiiiiijijjkw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tkjjkkkkllllmmmmnoqsssssssssssty€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŒ‹‹Š‰ˆ‡†…„„ƒ‚€{uniiiiiiiijikt~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tlkkklllllmmmnoprsssssssssty€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†‰‰ˆ‡†…„ƒƒ‚€|uojjiiiiiiikt~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€xplllllmmmmnprssssssssv{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ††……ƒƒ‚€€|vojiijjiiiow€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~ytpmmmmnprsssssux|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒƒ‚€}vojijijnrx~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}{yxyzz{|~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}yvvwy|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ \ No newline at end of file diff --git a/tests/Images/Input/WebP/lossless_color_transform.ppm b/tests/Images/Input/WebP/lossless_color_transform.ppm deleted file mode 100644 index 4607dab20ed859ce8f6ac62843823af42df64979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 786447 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

public uint ImageDataSize { get; set; } - // TODO: not sure if the bitreader is in the right place here, but for the sake of simplicity it will stay here for now. Will be refactored later. - public Vp8LBitReader Vp9LBitReader { get; set; } + /// + /// Gets or sets Vp8L bitreader. Will be null if its not lossless image. + /// + public Vp8LBitReader Vp9LBitReader { get; set; } = null; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 887ed8691..d878a0d2b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { @@ -70,13 +72,20 @@ namespace SixLabors.ImageSharp.Formats.WebP 0, 1, 1, 1, 0 }; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + /// /// Initializes a new instance of the class. /// /// Bitreader to read from the stream. - public WebPLosslessDecoder(Vp8LBitReader bitReader) + /// Used for allocating memory during processing operations. + public WebPLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator) { this.bitReader = bitReader; + this.memoryAllocator = memoryAllocator; } /// @@ -197,6 +206,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); + // TODO: use memory allocator var pixelData = new uint[width * height]; int totalPixels = width * height; @@ -412,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.WebP alphabetSize += 1 << colorCacheBits; } - int size = this.ReadHuffmanCode(decoder, alphabetSize, codeLengths, huffmanTable); + int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); if (size is 0) { WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); @@ -472,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.WebP decoder.Metadata.HuffmanTables = huffmanTables; } - private int ReadHuffmanCode(Vp8LDecoder decoder, int alphabetSize, int[] codeLengths, Span table) + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) { bool simpleCode = this.bitReader.ReadBit(); for (int i = 0; i < alphabetSize; i++) @@ -491,7 +501,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint firstSymbolLenCode = this.bitReader.ReadBits(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadBits((firstSymbolLenCode == 0) ? 1 : 8); + uint symbol = this.bitReader.ReadBits((firstSymbolLenCode is 0) ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. @@ -518,7 +528,7 @@ namespace SixLabors.ImageSharp.Formats.WebP codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } - this.ReadHuffmanCodeLengths(decoder, table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); @@ -526,7 +536,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return size; } - private void ReadHuffmanCodeLengths(Vp8LDecoder decoder, HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { int maxSymbol; int symbol = 0; @@ -580,13 +590,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: not sure, if this should be treated as an error here return; } - else + + int length = usePrev ? prevCodeLen : 0; + while (repeat-- > 0) { - int length = usePrev ? prevCodeLen : 0; - while (repeat-- > 0) - { - codeLengths[symbol++] = length; - } + codeLengths[symbol++] = length; } } } @@ -651,7 +659,11 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (transformType) { case Vp8LTransformType.PredictorTransform: - LosslessUtils.PredictorInverseTransform(transforms[i], pixelData); + using (IMemoryOwner output = this.memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) + { + LosslessUtils.PredictorInverseTransform(transforms[i], pixelData, output.GetSpan()); + } + break; case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); From 84f55736c7ab5e81e431abca452b86450f488597 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 Jan 2020 19:36:24 +0100 Subject: [PATCH 069/359] Use memory allocator where possible in lossless decoder --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 37 +++++----- src/ImageSharp/Formats/WebP/Vp8LMetadata.cs | 4 +- src/ImageSharp/Formats/WebP/Vp8LTransform.cs | 3 +- .../Formats/WebP/WebPDecoderCore.cs | 2 +- .../Formats/WebP/WebPLosslessDecoder.cs | 70 +++++++++++-------- 5 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 4ca97b371..748031860 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -4,6 +4,8 @@ using System; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// /// The pixel data to apply the transformation. - public static void AddGreenToBlueAndRed(uint[] pixelData) + public static void AddGreenToBlueAndRed(Span pixelData) { for (int i = 0; i < pixelData.Length; i++) { @@ -28,12 +30,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void ColorIndexInverseTransform(Vp8LTransform transform, uint[] pixelData) + public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) { int bitsPerPixel = 8 >> transform.Bits; int width = transform.XSize; int height = transform.YSize; - uint[] colorMap = transform.Data; + Span colorMap = transform.Data.GetSpan(); int decodedPixels = 0; if (bitsPerPixel < 8) { @@ -57,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } - decodedPixelData[decodedPixels++] = colorMap[packedPixels & bitMask]; + decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; packedPixels >>= bitsPerPixel; } } @@ -72,13 +74,13 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int x = 0; x < width; ++x) { uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); - pixelData[decodedPixels] = colorMap[colorMapIndex]; + pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; decodedPixels++; } } } - public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData) + public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) { int width = transform.XSize; int yEnd = transform.YSize; @@ -89,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int tilesPerRow = SubSampleSize(width, transform.Bits); int y = 0; int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; + Span transformData = transform.Data.GetSpan(); int pixelPos = 0; while (y < yEnd) @@ -99,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int srcEnd = pixelPos + width; while (pixelPos < srcSafeEnd) { - uint colorCode = transform.Data[predRowIdx++]; + uint colorCode = transformData[predRowIdx++]; ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, tileWidth); pixelPos += tileWidth; @@ -107,7 +110,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (pixelPos < srcEnd) { - uint colorCode = transform.Data[predRowIdx]; + uint colorCode = transformData[predRowIdx]; ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, remainingWidth); pixelPos += remainingWidth; @@ -121,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void TransformColorInverse(Vp8LMultipliers m, uint[] pixelData, int start, int numPixels) + public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels) { int end = start + numPixels; for (int i = start; i < end; i++) @@ -141,14 +144,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static uint[] ExpandColorMap(int numColors, Vp8LTransform transform, uint[] transformData) + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) { - int finalNumColors = 1 << (8 >> transform.Bits); - - // TODO: use memoryAllocator here - var newColorMap = new uint[finalNumColors]; newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); Span newData = MemoryMarshal.Cast(newColorMap); int i; @@ -158,19 +156,18 @@ namespace SixLabors.ImageSharp.Formats.WebP newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); } - for (; i < 4 * finalNumColors; ++i) + for (; i < 4 * newColorMap.Length; ++i) { newData[i] = 0; // black tail. } - - return newColorMap; } - public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData, Span output) + public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) { int processedPixels = 0; int yStart = 0; int width = transform.XSize; + Span transformData = transform.Data.GetSpan(); // First Row follows the L (mode=1) mode. PredictorAdd0(pixelData, processedPixels, 1, output); @@ -194,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (x < width) { - uint predictorMode = (transform.Data[predictorModeIdx++] >> 8) & 0xf; + uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; int xEnd = (x & ~mask) + tileWidth; if (xEnd > width) { diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs index 25ecea6b8..737def7f4 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Buffers; + namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8LMetadata @@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int HuffmanXSize { get; set; } - public uint[] HuffmanImage { get; set; } + public IMemoryOwner HuffmanImage { get; set; } public int NumHTreeGroups { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs index 78874554a..ae62123d3 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Buffers; using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP @@ -41,6 +42,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the transform data. /// - public uint[] Data { get; set; } + public IMemoryOwner Data { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 667212f90..b12ed0e72 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reads the header of lossless webp image. + /// Reads the header of a lossless webp image. /// /// Webp image features. /// Information about this image. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index d878a0d2b..ecdaf606c 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -99,11 +99,19 @@ namespace SixLabors.ImageSharp.Formats.WebP where TPixel : struct, IPixel { var decoder = new Vp8LDecoder(width, height); - uint[] pixelData = this.DecodeImageStream(decoder, width, height, true); - this.DecodePixelValues(decoder, pixelData, pixels); + IMemoryOwner pixelData = this.DecodeImageStream(decoder, width, height, true); + this.DecodePixelValues(decoder, pixelData.GetSpan(), pixels); + + // Free up allocated memory. + pixelData.Dispose(); + foreach (Vp8LTransform transform in decoder.Transforms) + { + transform.Data?.Dispose(); + } + decoder.Metadata?.HuffmanImage?.Dispose(); } - private uint[] DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) + private IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { int numberOfTransformsPresent = 0; if (isLevel0) @@ -162,7 +170,8 @@ namespace SixLabors.ImageSharp.Formats.WebP this.UpdateDecoder(decoder, xSize, ySize); - uint[] pixelData = this.DecodeImageData(decoder, colorCacheSize, colorCache); + IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); + this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache); if (!isLevel0) { decoder.Metadata = new Vp8LMetadata(); @@ -171,7 +180,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return pixelData; } - private void DecodePixelValues(Vp8LDecoder decoder, uint[] pixelData, Buffer2D pixels) + private void DecodePixelValues(Vp8LDecoder decoder, Span pixelData, Buffer2D pixels) where TPixel : struct, IPixel { // Apply reverse transformations, if any are present. @@ -195,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private uint[] DecodeImageData(Vp8LDecoder decoder, int colorCacheSize, ColorCache colorCache) + private void DecodeImageData(Vp8LDecoder decoder, Span pixelData, int colorCacheSize, ColorCache colorCache) { int lastPixel = 0; int width = decoder.Width; @@ -206,8 +215,6 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); - // TODO: use memory allocator - var pixelData = new uint[width * height]; int totalPixels = width * height; int decodedPixels = 0; @@ -331,11 +338,9 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Webp parsing error"); } } - - return pixelData; } - private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, uint[] pixelData, ref int lastCached) + private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) { ++col; decodedPixels++; @@ -369,14 +374,15 @@ namespace SixLabors.ImageSharp.Formats.WebP uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision); - int huffmanPixs = huffmanXSize * huffmanYSize; - uint[] huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + int huffmanPixels = huffmanXSize * huffmanYSize; + IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + Span huffmanImageSpan = huffmanImage.GetSpan(); decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision; - for (int i = 0; i < huffmanPixs; ++i) + for (int i = 0; i < huffmanPixels; ++i) { // The huffman data is stored in red and green bytes. - uint group = (huffmanImage[i] >> 8) & 0xffff; - huffmanImage[i] = group; + uint group = (huffmanImageSpan[i] >> 8) & 0xffff; + huffmanImageSpan[i] = group; if (group >= numHTreeGroupsMax) { numHTreeGroupsMax = (int)group + 1; @@ -625,19 +631,26 @@ namespace SixLabors.ImageSharp.Formats.WebP : 3; transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); transform.Bits = bits; - uint[] colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false); - transform.Data = LosslessUtils.ExpandColorMap((int)numColors, transform, colorMap); + using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) + { + int finalNumColors = 1 << (8 >> transform.Bits); + IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean); + LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); + transform.Data = newColorMap; + } + break; case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: { transform.Bits = (int)this.bitReader.ReadBits(3) + 2; - transform.Data = this.DecodeImageStream( + IMemoryOwner transformData = this.DecodeImageStream( decoder, LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), false); + transform.Data = transformData; break; } } @@ -650,7 +663,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. - private void ApplyInverseTransforms(Vp8LDecoder decoder, uint[] pixelData) + private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData) { List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) @@ -710,7 +723,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return tableSpan[0].Value; } - private uint ReadPackedSymbols(HTreeGroup[] group, uint[] pixelData, int decodedPixels) + private uint ReadPackedSymbols(HTreeGroup[] group, Span pixelData, int decodedPixels) { uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); HuffmanCode code = group[0].PackedTable[val]; @@ -726,18 +739,18 @@ namespace SixLabors.ImageSharp.Formats.WebP return code.Value; } - private void CopyBlock(uint[] pixelData, int decodedPixels, int dist, int length) + private void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { if (dist >= length) { - Span src = pixelData.AsSpan(decodedPixels - dist, length); - Span dest = pixelData.AsSpan(decodedPixels); + Span src = pixelData.Slice(decodedPixels - dist, length); + Span dest = pixelData.Slice(decodedPixels); src.CopyTo(dest); } else { - Span src = pixelData.AsSpan(decodedPixels - dist); - Span dest = pixelData.AsSpan(decodedPixels); + Span src = pixelData.Slice(decodedPixels - dist); + Span dest = pixelData.Slice(decodedPixels); for (int i = 0; i < length; ++i) { dest[i] = src[i]; @@ -811,14 +824,15 @@ namespace SixLabors.ImageSharp.Formats.WebP return hCode.BitsUsed; } - private uint GetMetaIndex(uint[] image, int xSize, int bits, int x, int y) + private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) { if (bits is 0) { return 0; } - return image[(xSize * (y >> bits)) + (x >> bits)]; + Span huffmanImageSpan = huffmanImage.GetSpan(); + return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) From b2b9a280857150f9b84fcdeb0707532d6b63b931 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Jan 2020 19:13:06 +0100 Subject: [PATCH 070/359] Add additional comments --- src/ImageSharp/Formats/WebP/ColorCache.cs | 11 ++++ src/ImageSharp/Formats/WebP/HuffmanCode.cs | 3 + src/ImageSharp/Formats/WebP/HuffmanUtils.cs | 2 +- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 66 +++++++++++++------ .../Formats/WebP/WebPDecoderCore.cs | 17 +++-- .../Formats/WebP/WebPLosslessDecoder.cs | 13 ++-- 6 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index 1fc47180f..d5579cbf1 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -3,6 +3,9 @@ namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. + /// internal class ColorCache { private const uint KHashMul = 0x1e35a7bdu; @@ -22,6 +25,10 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public int HashBits { get; private set; } + /// + /// Initializes a new color cache. + /// + /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. public void Init(int hashBits) { int hashSize = 1 << hashBits; @@ -30,6 +37,10 @@ namespace SixLabors.ImageSharp.Formats.WebP this.HashShift = 32 - hashBits; } + /// + /// Inserts a new color into the cache. + /// + /// The color to insert. public void Insert(uint argb) { int key = this.HashPix(argb, this.HashShift); diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs index b76f41d23..ac6c5bec4 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -5,6 +5,9 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. + /// [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] internal class HuffmanCode { diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index a52ec3984..3cab68dd1 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Guard.NotNull(codeLengths, nameof(codeLengths)); Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); - // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. var sorted = new int[codeLengthsSize]; int totalSize = 1 << rootBits; // total size root table + 2nd level table. int len; // current code length. diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 748031860..a0b11121d 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -30,6 +30,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. + /// This will reverse the color index transform. + /// + /// The transform data contains color table size and the entries in the color table. + /// The pixel data to apply the reverse transform on. public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) { int bitsPerPixel = 8 >> transform.Bits; @@ -80,6 +86,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + /// The transform data. + /// The pixel data to apply the inverse transform on. public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) { int width = transform.XSize; @@ -124,6 +136,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Reverses the color space transform. + /// + /// The color transform element. + /// The pixel data to apply the inverse transform on. + /// The start index of reverse transform. + /// The number of pixels to apply the transform. public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels) { int end = start + numPixels; @@ -144,24 +163,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) - { - newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); - Span newData = MemoryMarshal.Cast(newColorMap); - int i; - for (i = 4; i < 4 * numColors; ++i) - { - // Equivalent to AddPixelEq(), on a byte-basis. - newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); - } - - for (; i < 4 * newColorMap.Length; ++i) - { - newData[i] = 0; // black tail. - } - } - + /// + /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. + /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded. + /// The prediction mode determines the type of prediction to use. We divide the image into squares and all the pixels in a square use same prediction mode. + /// + /// The transform data. + /// The pixel data to apply the inverse transform. + /// The resulting pixel data with the reversed transformation data. public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) { int processedPixels = 0; @@ -198,6 +207,8 @@ namespace SixLabors.ImageSharp.Formats.WebP xEnd = width; } + // There are 14 different prediction modes. + // In each prediction mode, the current pixel value is predicted from one or more neighboring pixels whose values are already known. int startIdx = processedPixels + x; int numberOfPixels = xEnd - x; switch (predictorMode) @@ -602,9 +613,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return (idx >> 8) & 0xff; } + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) + { + newColorMap[0] = transformData[0]; + Span data = MemoryMarshal.Cast(transformData); + Span newData = MemoryMarshal.Cast(newColorMap); + int i; + for (i = 4; i < 4 * numColors; ++i) + { + // Equivalent to AddPixelEq(), on a byte-basis. + newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); + } + + for (; i < 4 * newColorMap.Length; ++i) + { + newData[i] = 0; // black tail. + } + } + private static int ColorTransformDelta(sbyte colorPred, sbyte color) { - int delta = ((sbyte)colorPred * color) >> 5; return ((int)colorPred * color) >> 5; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index b12ed0e72..39d7ecdfc 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -162,13 +162,20 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo(); } + /// + /// Reads an the extended webp file header. An extended file header consists of: + /// - A 'VP8X' chunk with information about features used in the file. + /// - An optional 'ICCP' chunk with color profile. + /// - An optional 'ANIM' chunk with animation control data. + /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. + /// + /// Information about this webp image. private WebPImageInfo ReadVp8XHeader() { uint chunkSize = this.ReadChunkSize(); - // This byte contains information about the image features used. - // The first two bit should and the last bit should be 0. - // TODO: should an exception be thrown if its not the case, or just ignore it? + // The first byte contains information about the image features used. + // The first two bit of it are reserved and should be 0. TODO: should an exception be thrown if its not the case, or just ignore it? byte imageFeatures = (byte)this.currentStream.ReadByte(); // If bit 3 is set, a ICC Profile Chunk should be present. @@ -349,7 +356,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Parses optional metadata chunks. + /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). + /// If there are more such chunks, readers MAY ignore all except the first one. + /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. /// /// The webp features. private void ParseOptionalChunks(WebPFeatures features) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index ecdaf606c..64bbf5cc1 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -108,6 +108,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { transform.Data?.Dispose(); } + decoder.Metadata?.HuffmanImage?.Dispose(); } @@ -644,12 +645,11 @@ namespace SixLabors.ImageSharp.Formats.WebP case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: { + // The first 3 bits of prediction data define the block width and height in number of bits. transform.Bits = (int)this.bitReader.ReadBits(3) + 2; - IMemoryOwner transformData = this.DecodeImageStream( - decoder, - LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), - LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), - false); + int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); + int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); + IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); transform.Data = transformData; break; } @@ -659,7 +659,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reverses the transformations, if any are present. + /// A WebP lossless image can go through four different types of transformation before being entropy encoded. + /// This will reverses the transformations, if any are present. /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. From 7b535461f59b197b120ef69b62e7abd01bf01e94 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Jan 2020 15:51:25 +0100 Subject: [PATCH 071/359] Add parsing of EXIF chunk --- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 46 ++++++++++++--- .../Formats/WebP/WebPDecoderCore.cs | 56 +++++++++++-------- .../Formats/WebP/WebPLossyDecoder.cs | 5 +- tests/Images/Input/WebP/exif.webp | 3 + tests/Images/Input/WebP/exif_lossless.webp | 3 + 5 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 tests/Images/Input/WebP/exif.webp create mode 100644 tests/Images/Input/WebP/exif_lossless.webp 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..35e454b96 --- /dev/null +++ b/tests/Images/Input/WebP/exif.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b +size 40765 diff --git a/tests/Images/Input/WebP/exif_lossless.webp b/tests/Images/Input/WebP/exif_lossless.webp new file mode 100644 index 000000000..3990bd8c6 --- /dev/null +++ b/tests/Images/Input/WebP/exif_lossless.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30734501a0b1953c392762d6ea11652400efec3f891f0da37749e3674e15b6a0 +size 183280 From ee00861062a36e8721ced7fec2e8d9a86a1589a2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Jan 2020 17:29:42 +0100 Subject: [PATCH 072/359] Add parsing of ICCP chunk --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 18 +++++++++++++++--- tests/Images/Input/WebP/lossless_iccp.webp | 3 +++ tests/Images/Input/WebP/lossy_iccp.webp | 3 +++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/Images/Input/WebP/lossless_iccp.webp create mode 100644 tests/Images/Input/WebP/lossy_iccp.webp diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 104138c96..927ad84f7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -8,6 +8,7 @@ using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -208,13 +209,22 @@ namespace SixLabors.ImageSharp.Formats.WebP this.buffer[3] = 0; int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; - // Optional chunks ALPH, ICCP and ANIM can follow here. Ignoring them for now. + // Optional chunks ICCP, ALPH and ANIM can follow here. WebPChunkType chunkType; if (isIccPresent) { chunkType = this.ReadChunkType(); - uint iccpChunkSize = this.ReadChunkSize(); - this.currentStream.Skip((int)iccpChunkSize); + if (chunkType is WebPChunkType.Iccp) + { + uint iccpChunkSize = this.ReadChunkSize(); + var iccpData = new byte[iccpChunkSize]; + this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); + var profile = new IccProfile(iccpData); + if (profile.CheckIsValid()) + { + this.Metadata.IccProfile = profile; + } + } } if (isAnimationPresent) @@ -236,6 +246,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { chunkType = this.ReadChunkType(); uint alphaChunkSize = this.ReadChunkSize(); + + // ALPH chunks will be skipped for now. this.currentStream.Skip((int)alphaChunkSize); } diff --git a/tests/Images/Input/WebP/lossless_iccp.webp b/tests/Images/Input/WebP/lossless_iccp.webp new file mode 100644 index 000000000..a99d2686f --- /dev/null +++ b/tests/Images/Input/WebP/lossless_iccp.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:312aea5ac9557bdfa78ec95bab5c3446a97c980317f46c96a20a4b7837d0ae37 +size 68355 diff --git a/tests/Images/Input/WebP/lossy_iccp.webp b/tests/Images/Input/WebP/lossy_iccp.webp new file mode 100644 index 000000000..a87580edf --- /dev/null +++ b/tests/Images/Input/WebP/lossy_iccp.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46e70fecb9cfc72243dfad58b68baa58c14f12660f8d2c88c30d5e050856b059 +size 25241 From 3a89c2a8dc270914ae47fd14936c6a4b142a6819 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 14 Jan 2020 13:56:28 +0100 Subject: [PATCH 073/359] Fix parsing VP8 header --- src/ImageSharp/Formats/WebP/WebPChunkType.cs | 2 +- .../Formats/WebP/WebPDecoderCore.cs | 41 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs index 9af98a394..a03f6bfb1 100644 --- a/src/ImageSharp/Formats/WebP/WebPChunkType.cs +++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public enum WebPChunkType : uint { /// - /// Header signaling the use of VP8 video format. + /// Header signaling the use of VP8 format. /// Vp8 = 0x56503820U, diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 927ad84f7..c72216344 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -287,8 +287,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.webpMetadata.Format = WebPFormatType.Lossy; // VP8 data size. - this.currentStream.Read(this.buffer, 0, 3); - this.buffer[3] = 0; + this.currentStream.Read(this.buffer, 0, 4); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); // https://tools.ietf.org/html/rfc6386#page-30 @@ -298,10 +297,30 @@ namespace SixLabors.ImageSharp.Formats.WebP // - 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; + uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); + bool isKeyFrame = (frameTag & 0x1) is 0; + if (!isKeyFrame) + { + WebPThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); + } + + uint version = (frameTag >> 1) & 0x7; + if (version > 3) + { + WebPThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); + } + + bool showFrame = ((frameTag >> 4) & 0x1) is 1; + if (!showFrame) + { + WebPThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); + } + + uint partitionLength = frameTag >> 5; + if (partitionLength > dataSize) + { + WebPThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); + } // Check for VP8 magic bytes. this.currentStream.Read(this.buffer, 0, 4); @@ -311,10 +330,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } this.currentStream.Read(this.buffer, 0, 4); - - // TODO: Get horizontal and vertical scale int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; + if (width is 0 || height is 0) + { + WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); + } return new WebPImageInfo() { @@ -350,6 +371,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // The first 28 bits of the bitstream specify the width and height of the image. uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; + if (width is 0 || height is 0) + { + WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); + } // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. bool alphaIsUsed = bitReader.ReadBit(); From 309f1f83c6dcd6976cfa208473fb6ffce3add986 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 14 Jan 2020 14:03:02 +0100 Subject: [PATCH 074/359] Throw exception, if a transform is present more than once --- src/ImageSharp/Formats/WebP/LosslessUtils.cs | 3 ++- src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index a0b11121d..600338e8e 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -164,9 +164,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// + /// This will reverse the predictor transform. /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded. - /// The prediction mode determines the type of prediction to use. We divide the image into squares and all the pixels in a square use same prediction mode. + /// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode. /// /// The transform data. /// The pixel data to apply the inverse transform. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 64bbf5cc1..d4c1e7569 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -617,6 +617,13 @@ namespace SixLabors.ImageSharp.Formats.WebP { var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); var transform = new Vp8LTransform(transformType, xSize, ySize); + + // Each transform is allowed to be used only once. + if (decoder.Transforms.Any(t => t.TransformType == transform.TransformType)) + { + WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); + } + switch (transformType) { case Vp8LTransformType.SubtractGreen: From 87dbbe60cf5b0eaee3dfe4084eff748231dbdbfe Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 Jan 2020 12:39:30 +0100 Subject: [PATCH 075/359] Add bits per pixel in webp image info --- .../Formats/WebP/WebPBitsPerPixel.cs | 21 +++++++++++++++++++ .../Formats/WebP/WebPDecoderCore.cs | 8 +++---- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 5 +++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs new file mode 100644 index 000000000..57bc16a66 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Enumerates the available bits per pixel the webp image uses. + /// + public enum WebPBitsPerPixel : short + { + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 24, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present). + /// + Pixel32 = 32 + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index c72216344..214b385e4 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -118,9 +118,7 @@ namespace SixLabors.ImageSharp.Formats.WebP 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); + return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), imageInfo.Width, imageInfo.Height, this.Metadata); } /// @@ -341,9 +339,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { Width = width, Height = height, + BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, IsLossLess = false, ImageDataSize = dataSize, - Features = features + Features = features, }; } @@ -388,6 +387,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { Width = (int)width, Height = (int)height, + BitsPerPixel = WebPBitsPerPixel.Pixel32, IsLossLess = true, ImageDataSize = imageDataSize, Features = features, diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index cf692289a..14d2c0ff7 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public int Height { get; set; } + /// + /// Gets or sets the bits per pixel. + /// + public WebPBitsPerPixel BitsPerPixel { get; set; } + /// /// Gets or sets a value indicating whether this image uses a lossless compression. /// From e244ff9013ddd7501e8bca3cc5f1e2249b753e06 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Tue, 14 Jan 2020 23:16:36 +0100 Subject: [PATCH 076/359] Lossless WebP: avoid duplicate if by moving the negated condition to the else case --- src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index d4c1e7569..839fe5b68 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -131,6 +131,10 @@ namespace SixLabors.ImageSharp.Formats.WebP numberOfTransformsPresent++; } } + else + { + decoder.Metadata = new Vp8LMetadata(); + } // Color cache. bool colorCachePresent = this.bitReader.ReadBit(); @@ -173,10 +177,6 @@ namespace SixLabors.ImageSharp.Formats.WebP IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache); - if (!isLevel0) - { - decoder.Metadata = new Vp8LMetadata(); - } return pixelData; } From 2ec0fa7162aaefc9e0c3fd64e0437894894b3625 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Tue, 14 Jan 2020 23:17:36 +0100 Subject: [PATCH 077/359] LosslessWebP: use logical connections between fields to make it less error prone --- src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 839fe5b68..3b0a06e5b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -47,10 +47,9 @@ namespace SixLabors.ImageSharp.Formats.WebP FixedTableSize + 2704 }; - private static readonly int NumCodeLengthCodes = 19; private static readonly byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + private static readonly int NumCodeLengthCodes = KCodeLengthCodeOrder.Length; - private static readonly int CodeToPlaneCodes = 120; private static readonly int[] KCodeToPlane = { 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, @@ -67,6 +66,8 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 }; + private static readonly int CodeToPlaneCodes = KCodeToPlane.Length; + private static readonly byte[] KLiteralMap = { 0, 1, 1, 1, 0 From 2fb000ae6e5ca0b387eab5ecda078f9fe91b7db9 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Wed, 15 Jan 2020 18:14:31 +0100 Subject: [PATCH 078/359] move some methods from LosslessDecoder to base class Implementing WebPLossyDecoder.DecodeAlphaData these methods are called from there containing the same code than in the lossless decoder, so we move them to an abstract base class. (not sure if it's a good idea to move the BitReader itself as well, so passing it as a parameter for now) --- .../Formats/WebP/WebPDecoderBase.cs | 86 +++++++++++++++++++ .../Formats/WebP/WebPLosslessDecoder.cs | 76 +--------------- .../Formats/WebP/WebPLossyDecoder.cs | 2 +- 3 files changed, 90 insertions(+), 74 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/WebPDecoderBase.cs diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs new file mode 100644 index 000000000..607dbc1c3 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs @@ -0,0 +1,86 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + abstract class WebPDecoderBase + { + protected HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); + } + + private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) + { + if (bits is 0) + { + return 0; + } + + Span huffmanImageSpan = huffmanImage.GetSpan(); + return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; + } + + // TODO: copied from WebPLosslessDecoder + protected int GetCopyDistance(int distanceSymbol, Vp8LBitReader bitReader) + { + if (distanceSymbol < 4) + { + return distanceSymbol + 1; + } + + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; + + return (int)(offset + bitReader.ReadBits(extraBits) + 1); + } + + // TODO: copied from WebPLosslessDecoder + protected int GetCopyLength(int lengthSymbol, Vp8LBitReader bitReader) + { + // Length and distance prefixes are encoded the same way. + return this.GetCopyDistance(lengthSymbol, bitReader); + } + + // TODO: copied from LosslessDecoder + protected static readonly int[] KCodeToPlane = + { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; + + protected static readonly int CodeToPlaneCodes = KCodeToPlane.Length; + + protected int PlaneCodeToDistance(int xSize, int planeCode) + { + if (planeCode > CodeToPlaneCodes) + { + return planeCode - CodeToPlaneCodes; + } + + int distCode = KCodeToPlane[planeCode - 1]; + int yOffset = distCode >> 4; + int xOffset = 8 - (distCode & 0xf); + int dist = (yOffset * xSize) + xOffset; + + // dist < 1 can happen if xSize is very small. + return (dist >= 1) ? dist : 1; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 3b0a06e5b..851ac213b 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The lossless specification can be found here: /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification /// - internal sealed class WebPLosslessDecoder + internal sealed class WebPLosslessDecoder : WebPDecoderBase { private readonly Vp8LBitReader bitReader; @@ -50,24 +50,6 @@ namespace SixLabors.ImageSharp.Formats.WebP private static readonly byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; private static readonly int NumCodeLengthCodes = KCodeLengthCodeOrder.Length; - private static readonly int[] KCodeToPlane = - { - 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, - 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, - 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, - 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, - 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, - 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, - 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, - 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, - 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, - 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, - 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, - 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 - }; - - private static readonly int CodeToPlaneCodes = KCodeToPlane.Length; - private static readonly byte[] KLiteralMap = { 0, 1, 1, 1, 0 @@ -289,10 +271,10 @@ namespace SixLabors.ImageSharp.Formats.WebP { // Backward reference is used. int lengthSym = code - WebPConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); + int length = this.GetCopyLength(lengthSym, this.bitReader); uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance((int)distSymbol); + int distCode = this.GetCopyDistance((int)distSymbol, this.bitReader); int dist = this.PlaneCodeToDistance(width, distCode); if (this.bitReader.IsEndOfStream()) { @@ -767,41 +749,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private int GetCopyDistance(int distanceSymbol) - { - if (distanceSymbol < 4) - { - return distanceSymbol + 1; - } - - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; - - return (int)(offset + this.bitReader.ReadBits(extraBits) + 1); - } - - private int GetCopyLength(int lengthSymbol) - { - // Length and distance prefixes are encoded the same way. - return this.GetCopyDistance(lengthSymbol); - } - - private int PlaneCodeToDistance(int xSize, int planeCode) - { - if (planeCode > CodeToPlaneCodes) - { - return planeCode - CodeToPlaneCodes; - } - - int distCode = KCodeToPlane[planeCode - 1]; - int yOffset = distCode >> 4; - int xOffset = 8 - (distCode & 0xf); - int dist = (yOffset * xSize) + xOffset; - - // dist < 1 can happen if xSize is very small. - return (dist >= 1) ? dist : 1; - } - private void BuildPackedTable(HTreeGroup hTreeGroup) { for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) @@ -832,22 +779,5 @@ namespace SixLabors.ImageSharp.Formats.WebP huff.Value |= hCode.Value << shift; return hCode.BitsUsed; } - - private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) - { - if (bits is 0) - { - return 0; - } - - Span huffmanImageSpan = huffmanImage.GetSpan(); - return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; - } - - private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) - { - uint metaIndex = this.GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); - return metadata.HTreeGroups.AsSpan((int)metaIndex).ToArray(); - } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 484f5071f..c1293ff49 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -10,7 +10,7 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - internal sealed class WebPLossyDecoder + internal sealed class WebPLossyDecoder : WebPDecoderBase { private readonly Configuration configuration; From 87c4a7af0574f8415f4ae7b09d308113afb4a5f5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 16 Jan 2020 17:05:19 +0100 Subject: [PATCH 079/359] Add data classes for decoding VP8 images --- src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 31 +++++++++++++ src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs | 26 +++++++++++ src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 15 +++++++ src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs | 21 +++++++++ .../Formats/WebP/Vp8MacroBlockData.cs | 44 +++++++++++++++++++ .../Formats/WebP/Vp8PictureHeader.cs | 38 ++++++++++++++++ src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 24 ++++++++++ .../Formats/WebP/Vp8SegmentHeader.cs | 33 ++++++++++++++ 8 files changed, 232 insertions(+) create mode 100644 src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs new file mode 100644 index 000000000..0cfaf1e04 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Filter information. + /// + internal class Vp8FilterInfo + { + /// + /// Gets or sets the filter limit in [3..189], or 0 if no filtering. + /// + public sbyte Limit { get; set; } + + /// + /// Gets or sets the inner limit in [1..63]. + /// + public sbyte InnerLevel { get; set; } + + /// + /// Gets or sets a value indicating whether to do inner filtering. + /// + public bool InnerFiltering { get; set; } + + /// + /// Gets or sets the high edge variance threshold in [0..2]. + /// + public sbyte HighEdgeVarianceThreshold { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs new file mode 100644 index 000000000..c7bfa67f6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Vp8 frame header information. + /// + internal class Vp8FrameHeader + { + /// + /// Gets or sets a value indicating whether this is a key frame. + /// + public bool KeyFrame { get; set; } + + /// + /// Gets or sets Vp8 profile [0..3]. + /// + public sbyte Profile { get; set; } + + /// + /// Gets or sets the partition length. + /// + public uint PartitionLength { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index ed827fd3c..fa4a570ec 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// Holds information for decoding a lossless image. + /// internal class Vp8LDecoder { public Vp8LDecoder(int width, int height) @@ -14,12 +17,24 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Metadata = new Vp8LMetadata(); } + /// + /// Gets or sets the width of the image to decode. + /// public int Width { get; set; } + /// + /// Gets or sets the height of the image to decode. + /// public int Height { get; set; } + /// + /// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image. + /// public Vp8LMetadata Metadata { get; set; } + /// + /// Gets or sets the transformations which needs to be reversed. + /// public List Transforms { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs new file mode 100644 index 000000000..b867893f5 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Info about a macro block. + /// + internal class Vp8MacroBlock + { + /// + /// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma). + /// + public uint NoneZeroAcDcCoeffs { get; set; } + + /// + /// Gets or sets non-zero DC coeff (1bit). + /// + public uint NoneZeroDcCoeffs { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs new file mode 100644 index 000000000..35d8803af --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Data needed to reconstruct a macroblock. + /// + internal class Vp8MacroBlockData + { + /// + /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4. + /// + public short Coeffs { get; set; } + + /// + /// Gets or sets a value indicating whether its intra4x4. + /// + public bool IsI4x4 { get; set; } + + /// + /// Gets or sets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. + /// + public byte Modes { get; set; } + + /// + /// Gets or sets the chroma prediction mode. + /// + public byte UvMode { get; set; } + + public uint NonZeroY { get; set; } + + public uint NonZeroUv { get; set; } + + /// + /// Gets or sets the local dithering strength (deduced from NonZero_*). + /// + public byte Dither { get; set; } + + public byte Skip { get; set; } + + public byte Segment { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs new file mode 100644 index 000000000..f9f0fd37d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8PictureHeader + { + /// + /// Gets or sets the width of the image. + /// + public uint Width { get; set; } + + /// + /// Gets or sets the Height of the image. + /// + public uint Height { get; set; } + + /// + /// Gets or sets the horizontal scale. + /// + public sbyte XScale { get; set; } + + /// + /// Gets or sets the vertical scale. + /// + public sbyte YScale { get; set; } + + /// + /// Gets or sets the colorspace. 0 = YCbCr. + /// + public sbyte ColorSpace { get; set; } + + /// + /// Gets or sets the clamp type. + /// + public sbyte ClampType { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs new file mode 100644 index 000000000..aaca80285 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8QuantMatrix + { + public int[] Y1Mat { get; set; } + + public int[] Y2Mat { get; set; } + + public int[] UVMat { get; set; } + + /// + /// Gets or sets the U/V quantizer value. + /// + public int UvQuant { get; set; } + + /// + /// Gets or sets the dithering amplitude (0 = off, max=255). + /// + public int Dither { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs new file mode 100644 index 000000000..993b74484 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Segment features. + /// + internal class Vp8SegmentHeader + { + public bool UseSegment { get; set; } + + /// + /// Gets or sets a value indicating whether to update the segment map or not. + /// + public bool UpdateMap { get; set; } + + /// + /// Gets or sets the absolute or delta values for quantizer and filter. + /// + public int AbsoluteOrDelta { get; set; } + + /// + /// Gets or sets quantization changes. + /// + public byte[] Quantizer { get; set; } + + /// + /// Gets or sets the filter strength for segments. + /// + public byte[] FilterStrength { get; set; } + } +} From 7fa2589230e6493185c4489a8943935a52fdbd0a Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Thu, 16 Jan 2020 19:54:54 +0100 Subject: [PATCH 080/359] code cleanup --- src/ImageSharp/Formats/WebP/WebPDecoderBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs index 607dbc1c3..ecbf68a39 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Buffers; From 6f37d5c873cfd1c8f067fc8006c598f48f74d734 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 17 Jan 2020 12:17:59 +0100 Subject: [PATCH 081/359] Introduce bitreader base class --- src/ImageSharp/Formats/WebP/BitReaderBase.cs | 63 ++++++++++++++++++ src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 40 +++++++++-- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 66 +++++-------------- .../Formats/WebP/WebPDecoderBase.cs | 2 +- .../Formats/WebP/WebPDecoderCore.cs | 12 ++-- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 7 +- .../Formats/WebP/WebPLosslessDecoder.cs | 28 ++++---- .../Formats/WebP/WebPLossyDecoder.cs | 36 +++------- 8 files changed, 147 insertions(+), 107 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/BitReaderBase.cs diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs new file mode 100644 index 000000000..a07c68ed9 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs @@ -0,0 +1,63 @@ +// 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 +{ + /// + /// Base class for VP8 and VP8L bitreader. + /// + internal abstract class BitReaderBase + { + /// + /// Gets raw encoded image data. + /// + protected byte[] Data { get; private set; } + + /// + /// Reads a single bit from the stream. + /// + /// True if the bit read was 1, false otherwise. + public abstract bool ReadBit(); + + /// + /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. + /// + /// The number of bits to read (should not exceed 16). + /// A ushort value. + public abstract uint ReadValue(int nBits); + + /// + /// Copies the raw encoded image data from the stream into a byte array. + /// + /// The input stream. + /// Number of bytes to read as indicated from the chunk size. + /// Used for allocating memory during reading data from the stream. + protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) + { + using (var ms = new MemoryStream()) + 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) + { + ms.Write(buffer.Array, 0, read); + bytesToRead -= read; + } + + if (bytesToRead > 0) + { + WebPThrowHelper.ThrowImageFormatException("image file has insufficient data"); + } + + this.Data = ms.ToArray(); + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index bdb1d0e03..cac683b1f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -2,10 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.IO; + +using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - internal class Vp8BitReader + /// + /// A bit reader for VP8 streams. + /// + internal class Vp8BitReader : BitReaderBase { /// /// Current value. @@ -43,14 +49,34 @@ namespace SixLabors.ImageSharp.Formats.WebP private bool eof; /// - /// Reads the specified number of bits from read buffer. - /// Flags an error in case end_of_stream or n_bits is more than the allowed limit - /// of VP8L_MAX_NUM_BIT_READ (inclusive). - /// Flags eos_ if this read attempt is going to cross the read buffer. + /// Initializes a new instance of the class. /// - /// The number of bits to read. - public int ReadBits(int nBits) + /// The input stream to read from. + /// The raw image data size in bytes. + /// Used for allocating memory during reading data from the stream. + public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) + { + this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); + } + + /// + public override bool ReadBit() + { + throw new NotImplementedException(); + } + + /// + public override uint ReadValue(int nBits) { + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + + throw new NotImplementedException(); + } + + public int ReadSignedValue(int nBits) + { + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + throw new NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index d3a13035d..27dab4687 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,18 +1,16 @@ // 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 { /// - /// A bit reader for VP8 streams. + /// A bit reader for VP8L streams. /// - internal class Vp8LBitReader + internal class Vp8LBitReader : BitReaderBase { /// /// Maximum number of bits (inclusive) the bit-reader can handle. @@ -20,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private const int Vp8LMaxNumBitRead = 24; /// - /// Number of bits prefetched (= bit-size of vp8l_val_t). + /// Number of bits prefetched. /// private const int Vp8LLbits = 64; @@ -40,8 +38,6 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff }; - private readonly byte[] data; - /// /// Pre-fetched bits. /// @@ -71,17 +67,13 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Initializes a new instance of the class. /// /// The input stream to read from. - /// The image data size in bytes. + /// The raw 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 = imageDataSize; - using (var ms = new MemoryStream()) - { - CopyStream(inputStream, ms, (int)imageDataSize, memoryAllocator); - this.data = ms.ToArray(); - } + this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); this.len = length; this.value = 0; @@ -96,19 +88,15 @@ namespace SixLabors.ImageSharp.Formats.WebP ulong currentValue = 0; for (int i = 0; i < length; ++i) { - currentValue |= (ulong)this.data[i] << (8 * i); + currentValue |= (ulong)this.Data[i] << (8 * i); } this.value = currentValue; this.pos = length; } - /// - /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. - /// - /// The number of bits to read (should not exceed 16). - /// A ushort value. - public uint ReadBits(int nBits) + /// + public override uint ReadValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); @@ -125,13 +113,10 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - /// - /// Reads a single bit from the stream. - /// - /// True if the bit read was 1, false otherwise. - public bool ReadBit() + /// + public override bool ReadBit() { - uint bit = this.ReadBits(1); + uint bit = this.ReadValue(1); return bit != 0; } @@ -153,14 +138,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public void DoFillBitWindow() + public bool IsEndOfStream() { - this.ShiftBytes(); + return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); } - public bool IsEndOfStream() + private void DoFillBitWindow() { - return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); + this.ShiftBytes(); } /// @@ -171,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; - this.value |= (ulong)this.data[this.pos] << (Vp8LLbits - 8); + this.value |= (ulong)this.Data[this.pos] << (Vp8LLbits - 8); ++this.pos; this.bitPos -= 8; } @@ -187,24 +172,5 @@ 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/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs index ecbf68a39..c2cc2c068 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int extraBits = (distanceSymbol - 2) >> 1; int offset = (2 + (distanceSymbol & 1)) << extraBits; - return (int)(offset + bitReader.ReadBits(extraBits) + 1); + return (int)(offset + bitReader.ReadValue(extraBits) + 1); } // TODO: copied from WebPLosslessDecoder diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 214b385e4..415c0a88d 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.WebP else { var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream); - lossyDecoder.Decode(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize); + lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo.ImageDataSize, imageInfo.Vp8Profile); } // There can be optional chunks after the image data, like EXIF and XMP. @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // A VP8 or VP8L chunk should follow here. chunkType = this.ReadChunkType(); - // TOOD: image width and height from VP8X should overrule VP8 or VP8L info, because its 3 bytes instead of just 14 bit. + // TOOD: check if VP8 or VP8L info about the dimensions match VP8X info switch (chunkType) { case WebPChunkType.Vp8: @@ -361,15 +361,15 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8LBitReader(this.currentStream, imageDataSize, this.memoryAllocator); // One byte signature, should be 0x2f. - uint signature = bitReader.ReadBits(8); + uint signature = bitReader.ReadValue(8); if (signature != WebPConstants.Vp8LMagicByte) { WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } // The first 28 bits of the bitstream specify the width and height of the image. - uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; + uint width = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; if (width is 0 || height is 0) { WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); @@ -381,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. // TODO: should we throw here when version number is != 0? - uint version = bitReader.ReadBits(WebPConstants.Vp8LVersionBits); + uint version = bitReader.ReadValue(WebPConstants.Vp8LVersionBits); return new WebPImageInfo() { diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 14d2c0ff7..c9fd9bd51 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public WebPBitsPerPixel BitsPerPixel { get; set; } /// - /// Gets or sets a value indicating whether this image uses a lossless compression. + /// Gets or sets a value indicating whether this image uses lossless compression. /// public bool IsLossLess { get; set; } @@ -35,6 +35,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public uint ImageDataSize { get; set; } + /// + /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. + /// + public byte Vp8Profile { get; set; } + /// /// Gets or sets Vp8L bitreader. Will be null if its not lossless image. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 851ac213b..40f13aaea 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheSize = 0; if (colorCachePresent) { - colorCacheBits = (int)this.bitReader.ReadBits(4); + colorCacheBits = (int)this.bitReader.ReadValue(4); bool coloCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits; if (!coloCacheBitsIsValid) { @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (allowRecursion && this.bitReader.ReadBit()) { // Use meta Huffman codes. - uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; + uint huffmanPrecision = this.bitReader.ReadValue(3) + 2; int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision); int huffmanPixels = huffmanXSize * huffmanYSize; @@ -487,17 +487,17 @@ namespace SixLabors.ImageSharp.Formats.WebP // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros. // Read symbols, codes & code lengths directly. - uint numSymbols = this.bitReader.ReadBits(1) + 1; - uint firstSymbolLenCode = this.bitReader.ReadBits(1); + uint numSymbols = this.bitReader.ReadValue(1) + 1; + uint firstSymbolLenCode = this.bitReader.ReadValue(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadBits((firstSymbolLenCode is 0) ? 1 : 8); + uint symbol = this.bitReader.ReadValue((firstSymbolLenCode is 0) ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. if (numSymbols is 2) { - symbol = this.bitReader.ReadBits(8); + symbol = this.bitReader.ReadValue(8); codeLengths[symbol] = 1; } } @@ -507,7 +507,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. var codeLengthCodeLengths = new int[NumCodeLengthCodes]; - uint numCodes = this.bitReader.ReadBits(4) + 4; + uint numCodes = this.bitReader.ReadValue(4) + 4; if (numCodes > NumCodeLengthCodes) { WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); @@ -515,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); + codeLengthCodeLengths[KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); } this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); @@ -539,8 +539,8 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.bitReader.ReadBit()) { - int lengthNBits = 2 + (2 * (int)this.bitReader.ReadBits(3)); - maxSymbol = 2 + (int)this.bitReader.ReadBits(lengthNBits); + int lengthNBits = 2 + (2 * (int)this.bitReader.ReadValue(3)); + maxSymbol = 2 + (int)this.bitReader.ReadValue(lengthNBits); } else { @@ -574,7 +574,7 @@ namespace SixLabors.ImageSharp.Formats.WebP uint slot = codeLen - WebPConstants.KCodeLengthLiterals; int extraBits = WebPConstants.KCodeLengthExtraBits[slot]; int repeatOffset = WebPConstants.KCodeLengthRepeatOffsets[slot]; - int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); + int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { // TODO: not sure, if this should be treated as an error here @@ -598,7 +598,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Vp8LDecoder where the transformations will be stored. private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) { - var transformType = (Vp8LTransformType)this.bitReader.ReadBits(2); + var transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); var transform = new Vp8LTransform(transformType, xSize, ySize); // Each transform is allowed to be used only once. @@ -615,7 +615,7 @@ namespace SixLabors.ImageSharp.Formats.WebP case Vp8LTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. - uint numColors = this.bitReader.ReadBits(8) + 1; + uint numColors = this.bitReader.ReadValue(8) + 1; int bits = (numColors > 16) ? 0 : (numColors > 4) ? 1 : (numColors > 2) ? 2 @@ -636,7 +636,7 @@ namespace SixLabors.ImageSharp.Formats.WebP case Vp8LTransformType.CrossColorTransform: { // The first 3 bits of prediction data define the block width and height in number of bits. - transform.Bits = (int)this.bitReader.ReadBits(3) + 2; + transform.Bits = (int)this.bitReader.ReadValue(3) + 2; int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index c1293ff49..896c3e256 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -18,48 +18,26 @@ namespace SixLabors.ImageSharp.Formats.WebP private MemoryAllocator memoryAllocator; - public WebPLossyDecoder( - Configuration configuration, - Stream currentStream) + public WebPLossyDecoder(Configuration configuration, Stream currentStream) { this.configuration = configuration; this.currentStream = currentStream; this.memoryAllocator = configuration.MemoryAllocator; } - public void Decode(Buffer2D pixels, int width, int height, int imageDataSize) + public void Decode(Buffer2D pixels, int width, int height, uint imageDataSize, byte vp8Version) where TPixel : struct, IPixel { - // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. - this.currentStream.Skip(imageDataSize - 10); // TODO: Not sure why we need to skip 10 bytes less here - // we need buffers for Y U and V in size of the image // TODO: increase size to enable using all prediction blocks? (see https://tools.ietf.org/html/rfc6386#page-9 ) - Buffer2D yuvBufferCurrentFrame = - this.memoryAllocator - .Allocate2D(width, height); + Buffer2D yuvBufferCurrentFrame = this.memoryAllocator.Allocate2D(width, height); // TODO: var predictionBuffer - macro-block-sized with approximation of the portion of the image being reconstructed. // those prediction values are the base, the values from DCT processing are added to that // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V - // TODO weiter bei S.11 - - // bit STREAM: See https://tools.ietf.org/html/rfc6386#page-29 ("Frame Header") - // TODO: Vp8BitReader should be used here instead - /*Vp8LBitReader bitReader = new Vp8LBitReader(this.currentStream); - bool isInterframe = bitReader.ReadBit(); - if (isInterframe) - { - throw new NotImplementedException("only key frames supported yet"); - } - - byte version = (byte)((bitReader.ReadBit() ? 2 : 0) | (bitReader.ReadBit() ? 1 : 0)); - (ReconstructionFilter rec, LoopFilter loop) = this.DecodeVersion(version); - - bool isShowFrame = bitReader.ReadBit(); - - uint firstPartitionSize = (bitReader.ReadBits(16) << 3) | bitReader.ReadBits(3);*/ + var bitReader = new Vp8BitReader(this.currentStream, imageDataSize, this.memoryAllocator); + (ReconstructionFilter rec, LoopFilter loop) = this.DecodeVersion(vp8Version); } private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version) @@ -78,8 +56,10 @@ namespace SixLabors.ImageSharp.Formats.WebP case 3: return (ReconstructionFilter.None, LoopFilter.None); default: + // Reserved for future use in Spec. // https://tools.ietf.org/html/rfc6386#page-30 - throw new NotSupportedException("reserved for future use in Spec"); + WebPThrowHelper.ThrowNotSupportedException($"unsupported VP8 version {version} found"); + return (rec, loop); } } } From 6ba2aa7aa14b726ed60390f946cb3789c261263c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 Jan 2020 17:59:56 +0100 Subject: [PATCH 082/359] Fix checking the magick bytes in parsing the VP8 header --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 415c0a88d..239755c1f 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -321,8 +321,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Check for VP8 magic bytes. - this.currentStream.Read(this.buffer, 0, 4); - if (!this.buffer.AsSpan(1).SequenceEqual(WebPConstants.Vp8MagicBytes)) + this.currentStream.Read(this.buffer, 0, 3); + if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8MagicBytes)) { WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); } From ef35d52da2be8ad3f25ee964c0bbdc373d0ebbb4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 Jan 2020 18:42:26 +0100 Subject: [PATCH 083/359] Introduce VP8 Profile which contains the reconstruction filter and the loop filter --- src/ImageSharp/Formats/WebP/LoopFilter.cs | 12 ++++++++ .../Formats/WebP/ReconstructionFilter.cs | 12 ++++++++ src/ImageSharp/Formats/WebP/Vp8Profile.cs | 21 ++++++++++++++ .../Formats/WebP/WebPDecoderCore.cs | 1 + src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 4 +-- .../Formats/WebP/WebPLossyDecoder.cs | 29 ++++++------------- 6 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/LoopFilter.cs create mode 100644 src/ImageSharp/Formats/WebP/ReconstructionFilter.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8Profile.cs diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs new file mode 100644 index 000000000..80a6b95ed --- /dev/null +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum LoopFilter + { + Normal, + Simple, + None + } +} diff --git a/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs b/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs new file mode 100644 index 000000000..ff59e6b66 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum ReconstructionFilter + { + None, + Bicubic, + Bilinear + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8Profile.cs b/src/ImageSharp/Formats/WebP/Vp8Profile.cs new file mode 100644 index 000000000..b1d757cb5 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8Profile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// The version number setting enables or disables certain features in the bitstream. + /// + internal class Vp8Profile + { + /// + /// Gets or sets the reconstruction filter. + /// + public ReconstructionFilter ReconstructionFilter { get; set; } + + /// + /// Gets or sets the loop filter. + /// + public LoopFilter LoopFilter { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 239755c1f..fafb5e752 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -343,6 +343,7 @@ namespace SixLabors.ImageSharp.Formats.WebP IsLossLess = false, ImageDataSize = dataSize, Features = features, + Vp8Profile = (sbyte)version }; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index c9fd9bd51..5b602cce8 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -36,9 +36,9 @@ namespace SixLabors.ImageSharp.Formats.WebP public uint ImageDataSize { get; set; } /// - /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. + /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. /// - public byte Vp8Profile { get; set; } + public int Vp8Profile { get; set; } = -1; /// /// Gets or sets Vp8L bitreader. Will be null if its not lossless image. diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 896c3e256..f01d5d362 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; using SixLabors.ImageSharp.Memory; @@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly Stream currentStream; - private MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; public WebPLossyDecoder(Configuration configuration, Stream currentStream) { @@ -25,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.memoryAllocator = configuration.MemoryAllocator; } - public void Decode(Buffer2D pixels, int width, int height, uint imageDataSize, byte vp8Version) + public void Decode(Buffer2D pixels, int width, int height, uint imageDataSize, int vp8Version) where TPixel : struct, IPixel { // we need buffers for Y U and V in size of the image @@ -37,40 +36,30 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V var bitReader = new Vp8BitReader(this.currentStream, imageDataSize, this.memoryAllocator); - (ReconstructionFilter rec, LoopFilter loop) = this.DecodeVersion(vp8Version); + Vp8Profile vp8Profile = this.DecodeProfile(vp8Version); } - private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version) + private Vp8Profile DecodeProfile(int version) { - var rec = ReconstructionFilter.None; - var loop = LoopFilter.None; - switch (version) { case 0: - return (ReconstructionFilter.Bicubic, LoopFilter.Normal); + return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Normal }; case 1: - return (ReconstructionFilter.Bilinear, LoopFilter.Simple); + return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.Simple }; case 2: - return (ReconstructionFilter.Bilinear, LoopFilter.None); + return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.None }; case 3: - return (ReconstructionFilter.None, LoopFilter.None); + return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.None, LoopFilter = LoopFilter.None }; default: // Reserved for future use in Spec. // https://tools.ietf.org/html/rfc6386#page-30 WebPThrowHelper.ThrowNotSupportedException($"unsupported VP8 version {version} found"); - return (rec, loop); + return new Vp8Profile(); } } } - enum ReconstructionFilter - { - None, - Bicubic, - Bilinear - } - enum LoopFilter { Normal, From 1aca8ad8eb092b99aadafb337fa8f83c3f6d8e4d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 Jan 2020 19:48:05 +0100 Subject: [PATCH 084/359] Fix image data size in webpinfo for VP8: 10 bytes are already read during parsing the header --- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 2 +- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index fafb5e752..bdc73a939 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Height = height, BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, IsLossLess = false, - ImageDataSize = dataSize, + ImageDataSize = dataSize - 10, // 10 bytes are read here in the header already. Features = features, Vp8Profile = (sbyte)version }; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f01d5d362..9a1862981 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -60,13 +60,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - enum LoopFilter - { - Normal, - Simple, - None - } - struct YUVPixel { public byte Y { get; } From 821250729f24f9bf76f91e9a0c30fdf5481efc8c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 Jan 2020 07:45:49 +0100 Subject: [PATCH 085/359] Add VP8 bitreader to image info, remove image data size (not used except inside the bitreader) --- .../Formats/WebP/WebPDecoderCore.cs | 23 +++++++++++-------- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 12 +++++----- .../Formats/WebP/WebPLossyDecoder.cs | 14 ++++------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index bdc73a939..1816e56ae 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -89,13 +89,13 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader, this.memoryAllocator); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream); - lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo.ImageDataSize, imageInfo.Vp8Profile); + var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator); + lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo.Vp8Profile); } // There can be optional chunks after the image data, like EXIF and XMP. @@ -335,15 +335,17 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); } + var bitReader = new Vp8BitReader(this.currentStream, dataSize - 10, this.memoryAllocator); + return new WebPImageInfo() { Width = width, Height = height, BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, IsLossLess = false, - ImageDataSize = dataSize - 10, // 10 bytes are read here in the header already. Features = features, - Vp8Profile = (sbyte)version + Vp8Profile = (sbyte)version, + Vp8BitReader = bitReader }; } @@ -377,12 +379,16 @@ namespace SixLabors.ImageSharp.Formats.WebP } // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. + // TODO: this flag value is not used yet bool alphaIsUsed = bitReader.ReadBit(); - // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. + // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. - // TODO: should we throw here when version number is != 0? uint version = bitReader.ReadValue(WebPConstants.Vp8LVersionBits); + if (version != 0) + { + WebPThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); + } return new WebPImageInfo() { @@ -390,9 +396,8 @@ namespace SixLabors.ImageSharp.Formats.WebP Height = (int)height, BitsPerPixel = WebPBitsPerPixel.Pixel32, IsLossLess = true, - ImageDataSize = imageDataSize, Features = features, - Vp9LBitReader = bitReader + Vp8LBitReader = bitReader }; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 5b602cce8..08bfe2314 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -31,18 +31,18 @@ namespace SixLabors.ImageSharp.Formats.WebP public WebPFeatures Features { get; set; } /// - /// Gets or sets the bytes of the image payload. + /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. /// - public uint ImageDataSize { get; set; } + public int Vp8Profile { get; set; } = -1; /// - /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. + /// Gets or sets the Vp8L bitreader. Will be null, if its not lossless image. /// - public int Vp8Profile { get; set; } = -1; + public Vp8LBitReader Vp8LBitReader { get; set; } = null; /// - /// Gets or sets Vp8L bitreader. Will be null if its not lossless image. + /// Gets or sets the VP8 bitreader. Will be null, if its not a lossy image. /// - public Vp8LBitReader Vp9LBitReader { get; set; } = null; + public Vp8BitReader Vp8BitReader { get; set; } = null; } } diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 9a1862981..b41e5b1e3 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -11,20 +11,17 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal sealed class WebPLossyDecoder : WebPDecoderBase { - private readonly Configuration configuration; - - private readonly Stream currentStream; + private readonly Vp8BitReader bitReader; private readonly MemoryAllocator memoryAllocator; - public WebPLossyDecoder(Configuration configuration, Stream currentStream) + public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) { - this.configuration = configuration; - this.currentStream = currentStream; - this.memoryAllocator = configuration.MemoryAllocator; + this.memoryAllocator = memoryAllocator; + this.bitReader = bitReader; } - public void Decode(Buffer2D pixels, int width, int height, uint imageDataSize, int vp8Version) + public void Decode(Buffer2D pixels, int width, int height, int vp8Version) where TPixel : struct, IPixel { // we need buffers for Y U and V in size of the image @@ -35,7 +32,6 @@ namespace SixLabors.ImageSharp.Formats.WebP // those prediction values are the base, the values from DCT processing are added to that // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V - var bitReader = new Vp8BitReader(this.currentStream, imageDataSize, this.memoryAllocator); Vp8Profile vp8Profile = this.DecodeProfile(vp8Version); } From 023a64ce1287c99994f7c233d69596719b6f6a3e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 Jan 2020 18:23:16 +0100 Subject: [PATCH 086/359] WIP: Parse further VP8 header: SegmentHeader, FilterHeader, implement BitReader for VP8 (unfinished) --- src/ImageSharp/Formats/WebP/BitReaderBase.cs | 13 -- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 127 ++++++++++++++++-- .../Formats/WebP/Vp8FilterHeader.cs | 35 +++++ src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 34 ++++- .../Formats/WebP/Vp8PictureHeader.cs | 6 +- .../Formats/WebP/Vp8SegmentHeader.cs | 21 ++- .../Formats/WebP/WebPDecoderCore.cs | 110 +++++++++++++-- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 26 +++- 8 files changed, 324 insertions(+), 48 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs index a07c68ed9..0a62299ea 100644 --- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs @@ -19,19 +19,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// protected byte[] Data { get; private set; } - /// - /// Reads a single bit from the stream. - /// - /// True if the bit read was 1, false otherwise. - public abstract bool ReadBit(); - - /// - /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. - /// - /// The number of bits to read (should not exceed 16). - /// A ushort value. - public abstract uint ReadValue(int nBits); - /// /// Copies the raw encoded image data from the stream into a byte array. /// diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index cac683b1f..a3996b08d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; using System.IO; using SixLabors.Memory; @@ -13,15 +14,17 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8BitReader : BitReaderBase { + private const int BitsCount = 56; + /// /// Current value. /// - private long value; + private ulong value; /// /// Current range minus 1. In [127, 254] interval. /// - private int range; + private uint range; /// /// Number of valid bits left. @@ -36,18 +39,23 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// End of read buffer. /// - private byte bufEnd; + // private byte bufEnd; /// /// Max packed-read position on buffer. /// - private byte bufMax; + // private byte bufMax; /// /// True if input is exhausted. /// private bool eof; + /// + /// Byte position in buffer. + /// + private long pos; + /// /// Initializes a new instance of the class. /// @@ -57,27 +65,126 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) { this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); + + this.range = 255 - 1; + this.value = 0; + this.bits = -8; // to load the very first 8bits; + this.eof = false; + this.pos = 0; + + this.LoadNewBytes(); } - /// - public override bool ReadBit() + public int GetBit(int prob) { - throw new NotImplementedException(); + Guard.MustBeGreaterThan(prob, 0, nameof(prob)); + + uint range = this.range; + if (this.bits < 0) + { + this.LoadNewBytes(); + } + + int pos = this.bits; + uint split = (uint)((range * prob) >> 8); + bool bit = this.value > split; + if (bit) + { + range -= split; + this.value -= (ulong)(split + 1) << pos; + } + else + { + range = split + 1; + } + + int shift = 7 ^ this.BitsLog2Floor(range); + range <<= shift; + this.bits -= shift; + + this.range = range - 1; + + return bit ? 1 : 0; + } + + public bool ReadBool() + { + return this.ReadValue(1) is 1; } - /// - public override uint ReadValue(int nBits) + public uint ReadValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - throw new NotImplementedException(); + uint v = 0; + while (this.bits-- > 0) + { + v |= (uint)this.GetBit(0x80) << this.bits; + } + + return v; } public int ReadSignedValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + int value = (int)this.ReadValue(nBits); + return this.ReadValue(1) != 0 ? -value : value; + } + + private void LoadNewBytes() + { + if (this.pos < this.Data.Length) + { + ulong bits; + ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan().Slice((int)this.pos, 8)); + this.pos += BitsCount >> 3; + this.buf = this.Data[BitsCount >> 3]; + bits = this.ByteSwap64(inBits); + bits >>= 64 - BitsCount; + this.value = bits | (this.value << BitsCount); + this.bits += BitsCount; + } + else + { + this.LoadFinalBytes(); + } + } + + private void LoadFinalBytes() + { + // Only read 8bits at a time. + if (this.pos < this.Data.Length) + { + this.bits += 8; + this.value = this.Data[this.pos++] | (this.value << 8); + } + else if (!this.eof) + { + this.value <<= 8; + this.bits += 8; + this.eof = true; + } + else + { + this.bits = 0; // This is to avoid undefined behaviour with shifts. + } + } + + private ulong ByteSwap64(ulong x) + { + x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); + x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16); + x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); + return x; + } + + private int BitsLog2Floor(uint n) + { + long firstSetBit; throw new NotImplementedException(); + // BitScanReverse(firstSetBit, n); } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs new file mode 100644 index 000000000..7093b1bf0 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8FilterHeader + { + private const int NumRefLfDeltas = 4; + + private const int NumModeLfDeltas = 4; + + public Vp8FilterHeader() + { + this.RefLfDelta = new int[NumRefLfDeltas]; + this.ModeLfDelta = new int[NumModeLfDeltas]; + } + + /// + /// Gets or sets the loop filter. + /// + public LoopFilter LoopFilter { get; set; } + + // [0..63] + public int Level { get; set; } + + // [0..7] + public int Sharpness { get; set; } + + public bool UseLfDelta { get; set; } + + public int[] RefLfDelta { get; private set; } + + public int[] ModeLfDelta { get; private set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 27dab4687..893812472 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -95,8 +95,12 @@ namespace SixLabors.ImageSharp.Formats.WebP this.pos = length; } - /// - public override uint ReadValue(int nBits) + /// + /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. + /// + /// The number of bits to read (should not exceed 16). + /// A ushort value. + public uint ReadValue(int nBits) { Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); @@ -113,23 +117,37 @@ namespace SixLabors.ImageSharp.Formats.WebP return 0; } - /// - public override bool ReadBit() + /// + /// Reads a single bit from the stream. + /// + /// True if the bit read was 1, false otherwise. + public bool ReadBit() { uint bit = this.ReadValue(1); return bit != 0; } - public void AdvanceBitPosition(int bitPosition) + /// + /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow. + /// + /// The number of bits to advance the position. + public void AdvanceBitPosition(int numberOfBits) { - this.bitPos += bitPosition; + this.bitPos += numberOfBits; } + /// + /// Return the pre-fetched bits, so they can be looked up. + /// + /// Prefetched bits. public ulong PrefetchBits() { return this.value >> (this.bitPos & (Vp8LLbits - 1)); } + /// + /// Advances the read buffer by 4 bytes to make room for reading next 32 bits. + /// public void FillBitWindow() { if (this.bitPos >= Vp8LWbits) @@ -138,6 +156,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Returns true if there was an attempt at reading bit past the end of the buffer. + /// + /// True, if end of buffer was reached. public bool IsEndOfStream() { return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); diff --git a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs index f9f0fd37d..3f80b13b2 100644 --- a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs @@ -26,12 +26,16 @@ namespace SixLabors.ImageSharp.Formats.WebP public sbyte YScale { get; set; } /// - /// Gets or sets the colorspace. 0 = YCbCr. + /// Gets or sets the colorspace. + /// 0 - YUV color space similar to the YCrCb color space defined in. + /// 1 - Reserved for future use. /// public sbyte ColorSpace { get; set; } /// /// Gets or sets the clamp type. + /// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive). + /// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary. /// public sbyte ClampType { get; set; } } diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs index 993b74484..27bce1f04 100644 --- a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs @@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class Vp8SegmentHeader { + private const int NumMbSegments = 4; + + public Vp8SegmentHeader() + { + this.Quantizer = new byte[NumMbSegments]; + this.FilterStrength = new byte[NumMbSegments]; + } + public bool UseSegment { get; set; } /// @@ -16,18 +24,19 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool UpdateMap { get; set; } /// - /// Gets or sets the absolute or delta values for quantizer and filter. + /// Gets or sets a value indicating whether to use delta values for quantizer and filter. + /// If this value is false, absolute values are used. /// - public int AbsoluteOrDelta { get; set; } + public bool Delta { get; set; } /// - /// Gets or sets quantization changes. + /// Gets quantization changes. /// - public byte[] Quantizer { get; set; } + public byte[] Quantizer { get; private set; } /// - /// Gets or sets the filter strength for segments. + /// Gets the filter strength for segments. /// - public byte[] FilterStrength { get; set; } + public byte[] FilterStrength { get; private set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 1816e56ae..f0b0233cf 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -85,7 +85,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, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.ReadImageHeader(); WebPImageInfo imageInfo = this.ReadVp8Info(); - return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), imageInfo.Width, imageInfo.Height, this.Metadata); + return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); } /// @@ -200,12 +200,12 @@ namespace SixLabors.ImageSharp.Formats.WebP // 3 bytes for the width. this.currentStream.Read(this.buffer, 0, 3); this.buffer[3] = 0; - int width = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; + uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // 3 bytes for the height. this.currentStream.Read(this.buffer, 0, 3); this.buffer[3] = 0; - int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; + uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // Optional chunks ICCP, ALPH and ANIM can follow here. WebPChunkType chunkType; @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Read(this.buffer, 0, 4); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - // https://tools.ietf.org/html/rfc6386#page-30 + // See paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 // Frame tag that contains four fields: // - A 1-bit frame type (0 for key frames, 1 for interframes). // - A 3-bit version number. @@ -328,15 +328,103 @@ namespace SixLabors.ImageSharp.Formats.WebP } this.currentStream.Read(this.buffer, 0, 4); - int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; - int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; + uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer); + uint width = tmp & 0x3fff; + sbyte xScale = (sbyte)(tmp >> 6); + tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)); + uint height = tmp & 0x3fff; + sbyte yScale = (sbyte)(tmp >> 6); if (width is 0 || height is 0) { WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); } + var vp8FrameHeader = new Vp8FrameHeader() + { + KeyFrame = true, + Profile = (sbyte)version, + PartitionLength = partitionLength + }; + var bitReader = new Vp8BitReader(this.currentStream, dataSize - 10, this.memoryAllocator); + // Paragraph 9.2: color space and clamp type follow + sbyte colorSpace = (sbyte)bitReader.ReadValue(1); + sbyte clampType = (sbyte)bitReader.ReadValue(1); + var vp8PictureHeader = new Vp8PictureHeader() + { + Width = width, + Height = height, + XScale = xScale, + YScale = yScale, + ColorSpace = colorSpace, + ClampType = clampType + }; + + // Paragraph 9.3: Parse the segment header. + var vp8SegmentHeader = new Vp8SegmentHeader(); + vp8SegmentHeader.UseSegment = bitReader.ReadBool(); + if (vp8SegmentHeader.UseSegment) + { + vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); + bool updateData = bitReader.ReadBool(); + if (updateData) + { + vp8SegmentHeader.Delta = bitReader.ReadBool(); + for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) + { + bool hasValue = bitReader.ReadBool(); + uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; + vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; + } + + for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) + { + bool hasValue = bitReader.ReadBool(); + uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; + vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; + } + + if (vp8SegmentHeader.UpdateMap) + { + // TODO: Read VP8Proba + } + } + } + + // Paragraph 9.4: Parse the filter specs. + var vp8FilterHeader = new Vp8FilterHeader(); + vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Normal; + vp8FilterHeader.Level = (int)bitReader.ReadValue(6); + vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); + vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); + if (vp8FilterHeader.UseLfDelta) + { + // Update lf-delta? + if (bitReader.ReadBool()) + { + for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) + { + bool hasValue = bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); + } + } + + for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + { + bool hasValue = bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); + } + } + } + } + + // TODO: ParsePartitions + return new WebPImageInfo() { Width = width, @@ -345,6 +433,10 @@ namespace SixLabors.ImageSharp.Formats.WebP IsLossLess = false, Features = features, Vp8Profile = (sbyte)version, + Vp8FrameHeader = vp8FrameHeader, + Vp8SegmentHeader = vp8SegmentHeader, + Vp8FilterHeader = vp8FilterHeader, + Vp8PictureHeader = vp8PictureHeader, Vp8BitReader = bitReader }; } @@ -392,8 +484,8 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo() { - Width = (int)width, - Height = (int)height, + Width = width, + Height = height, BitsPerPixel = WebPBitsPerPixel.Pixel32, IsLossLess = true, Features = features, diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 08bfe2314..1eeb4d2e0 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the bitmap width in pixels (signed integer). /// - public int Width { get; set; } + public uint Width { get; set; } /// /// Gets or sets the bitmap height in pixels (signed integer). /// - public int Height { get; set; } + public uint Height { get; set; } /// /// Gets or sets the bits per pixel. @@ -36,7 +36,27 @@ namespace SixLabors.ImageSharp.Formats.WebP public int Vp8Profile { get; set; } = -1; /// - /// Gets or sets the Vp8L bitreader. Will be null, if its not lossless image. + /// Gets or sets the VP8 frame header. + /// + public Vp8FrameHeader Vp8FrameHeader { get; set; } + + /// + /// Gets or sets the VP8 picture header. + /// + public Vp8PictureHeader Vp8PictureHeader { get; set; } + + /// + /// Gets or sets the VP8 segment header. + /// + public Vp8SegmentHeader Vp8SegmentHeader { get; set; } + + /// + /// Gets or sets the VP8 filter header. + /// + public Vp8FilterHeader Vp8FilterHeader { get; set; } + + /// + /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image. /// public Vp8LBitReader Vp8LBitReader { get; set; } = null; From 5c27728a421eac89c04adc543e757c10a23572db Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 17:10:47 +0100 Subject: [PATCH 087/359] remove obsolete comments --- src/ImageSharp/Formats/WebP/WebPDecoderBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs index c2cc2c068..9779b2dfe 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs @@ -27,7 +27,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } - // TODO: copied from WebPLosslessDecoder protected int GetCopyDistance(int distanceSymbol, Vp8LBitReader bitReader) { if (distanceSymbol < 4) @@ -41,7 +40,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return (int)(offset + bitReader.ReadValue(extraBits) + 1); } - // TODO: copied from WebPLosslessDecoder protected int GetCopyLength(int lengthSymbol, Vp8LBitReader bitReader) { // Length and distance prefixes are encoded the same way. From f14f2a592561d7706ccaaf0e56b517110f49dc7a Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 17:13:05 +0100 Subject: [PATCH 088/359] WebP: Move ReadSymbol to WebPDecoderBase it's used in Lossy decoder for alpha decoding as well. --- .../Formats/WebP/WebPDecoderBase.cs | 24 ++++++++++++++ .../Formats/WebP/WebPLosslessDecoder.cs | 33 +++---------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs index 9779b2dfe..b9c82c7ec 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderBase.cs @@ -27,6 +27,30 @@ namespace SixLabors.ImageSharp.Formats.WebP return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } + /// + /// Decodes the next Huffman code from bit-stream. + /// FillBitWindow(br) needs to be called at minimum every second call + /// to ReadSymbol, in order to pre-fetch enough bits. + /// + protected uint ReadSymbol(Span table, Vp8LBitReader bitReader) + { + // TODO: if the bitReader field is moved to this base class we could omit the parameter. + uint val = (uint)bitReader.PrefetchBits(); + Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); + int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; + if (nBits > 0) + { + bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); + val = (uint)bitReader.PrefetchBits(); + tableSpan = tableSpan.Slice((int)tableSpan[0].Value); + tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); + } + + bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); + + return tableSpan[0].Value; + } + protected int GetCopyDistance(int distanceSymbol, Vp8LBitReader bitReader) { if (distanceSymbol < 4) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 40f13aaea..ab4dd6306 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -235,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green], this.bitReader); } if (this.bitReader.IsEndOfStream()) @@ -252,10 +252,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); + uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red], this.bitReader); this.bitReader.FillBitWindow(); - uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); - uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); + uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue], this.bitReader); + uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha], this.bitReader); if (this.bitReader.IsEndOfStream()) { break; @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Backward reference is used. int lengthSym = code - WebPConstants.NumLiteralCodes; int length = this.GetCopyLength(lengthSym, this.bitReader); - uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); + uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist], this.bitReader); this.bitReader.FillBitWindow(); int distCode = this.GetCopyDistance((int)distSymbol, this.bitReader); int dist = this.PlaneCodeToDistance(width, distCode); @@ -691,29 +691,6 @@ namespace SixLabors.ImageSharp.Formats.WebP decoder.Metadata.HuffmanMask = (numBits is 0) ? ~0 : (1 << numBits) - 1; } - /// - /// Decodes the next Huffman code from bit-stream. - /// FillBitWindow(br) needs to be called at minimum every second call - /// to ReadSymbol, in order to pre-fetch enough bits. - /// - private uint ReadSymbol(Span table) - { - uint val = (uint)this.bitReader.PrefetchBits(); - Span tableSpan = table.Slice((int)(val & HuffmanUtils.HuffmanTableMask)); - int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; - if (nBits > 0) - { - this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); - val = (uint)this.bitReader.PrefetchBits(); - tableSpan = tableSpan.Slice((int)tableSpan[0].Value); - tableSpan = tableSpan.Slice((int)val & ((1 << nBits) - 1)); - } - - this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); - - return tableSpan[0].Value; - } - private uint ReadPackedSymbols(HTreeGroup[] group, Span pixelData, int decodedPixels) { uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); From 6bda08f5338eba94bf53d81ce78051a03aa0762e Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 17:37:53 +0100 Subject: [PATCH 089/359] introduce function Is8bOptimizable --- .../Formats/WebP/WebPLossyDecoder.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index b41e5b1e3..da84079cf 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Memory; @@ -54,6 +56,28 @@ namespace SixLabors.ImageSharp.Formats.WebP return new Vp8Profile(); } } + + static bool Is8bOptimizable(Vp8LMetadata hdr) + { + int i; + if (hdr.ColorCacheSize > 0) + return false; + + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (i = 0; i < hdr.NumHTreeGroups; ++i) + { + List htrees = hdr.HTreeGroups[i].HTrees; + if (htrees[HuffIndex.Red][0].Value > 0) + return false; + if (htrees[HuffIndex.Blue][0].Value > 0) + return false; + if (htrees[HuffIndex.Alpha][0].Value > 0) + return false; + } + + return true; + } } struct YUVPixel From cdca3e16c0adbc94ff36149be4a47170aefc6a58 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 17:38:06 +0100 Subject: [PATCH 090/359] introduce WebPFilterType enum --- src/ImageSharp/Formats/WebP/WebPFilterType.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/ImageSharp/Formats/WebP/WebPFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/WebPFilterType.cs new file mode 100644 index 000000000..ca7a13085 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPFilterType.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + // TODO from dsp.h + public enum WebPFilterType + { + None = 0, + Horizontal, + Vertical, + Gradient, + Last = Gradient + 1, // end marker + Best, // meta types + Fast + } +} From 6edd386d0b1b1a2ad6ec0bf32910f961d6408a24 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 23:29:17 +0100 Subject: [PATCH 091/359] WebP: replace WebPFilterType enum by classes and implement them --- src/ImageSharp/Formats/WebP/WebPFilterType.cs | 370 +++++++++++++++++- 1 file changed, 362 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/WebPFilterType.cs index ca7a13085..f373da727 100644 --- a/src/ImageSharp/Formats/WebP/WebPFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFilterType.cs @@ -1,17 +1,371 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP { // TODO from dsp.h - public enum WebPFilterType + // public enum WebPFilterType + // { + // None = 0, + // Horizontal, + // Vertical, + // Gradient, + // Last = Gradient + 1, // end marker + // Best, // meta types + // Fast + // } + + abstract class WebPFilterBase + { + /// + /// + /// + /// nullable as prevLine is nullable in the original but Span'T can't be null. + /// + /// + /// + /// + /// + public abstract void Unfilter( + Span prevLine, + int? prevLineOffset, + Span preds, + int predsOffset, + Span currentLine, + int currentLineOffset, + int width); + + public abstract void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset); + + protected static void SanityCheck( + Span input, Span output, int width, int numRows, int height, int stride, int row) + { + Debug.Assert(input != null); + Debug.Assert(output != null); + Debug.Assert(width > 0); + Debug.Assert(height > 0); + Debug.Assert(stride > width); + Debug.Assert(row >= 0); + Debug.Assert(height > 0); + Debug.Assert(row + numRows <= height); + } + + protected static void PredictLine( + Span src, int srcOffset, + Span pred, int predOffset, + Span dst, int dstOffset, + int length, bool inverse) + { + if (inverse) + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] + pred[i]); + } + } + else + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] - pred[i]); + } + } + } + + protected void UnfilterHorizontalOrVerticalCore( + byte pred, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); + pred = output[i]; + } + } + } + + // TODO: check if this is a filter or just a placeholder from the C implementation details + class WebPFilterNone : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffset, Span input, int inputOffset, Span output, int outputOffset, int width) + { } + + public override void Filter(Span input, int inputOffset, int width, int height, int stride, Span output, int outputOffset) + { } + } + + class WebPFilterHorizontal : WebPFilterBase { - None = 0, - Horizontal, - Vertical, - Gradient, - Last = Gradient + 1, // end marker - Best, // meta types - Fast + public override void Unfilter( + Span prevLine, int? prevLineOffsetNullable, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + byte pred = prevLineOffsetNullable is int prevLineOffset + ? prevLine[prevLineOffset] + : (byte)0; + + this.UnfilterHorizontalOrVerticalCore( + pred, + input, inputOffset, + output, outputOffset, + width); + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int numRows = height; + int row = 0; + + const bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, numRows, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + + if (row == 0) + { + // leftmost pixel is the same as Input for topmost scanline + output[0] = input[0]; + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + + row = 1; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + + // filter line by line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset - stride, + output, 0, + 1, inverse); + PredictLine( + input, inputOffset, + preds, predsOffset, + output,outputOffset + 1, + width - 1, inverse); + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } + + class WebPFilterVertical : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int row = 0; + bool inverse = false; + + // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + // very first top-left pixel is copied. + output[0] = input[0]; + // rest of top scan-line is left-predicted: + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + row = 1; + inputOffset += stride; + outputOffset += stride; + } + else + { + predsOffset -= stride; + } + + // filter line-by-line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset, + output, outputOffset, + width, inverse); + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } + + class WebPFilterGradient : WebPFilterBase + { + + public override void Unfilter( + Span prevLine, + int? prevLineOffsetNullable, + Span input, + int inputOffset, + Span output, + int outputOffset, + int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + byte top = prevLine[prevLineOffset]; + byte topLeft = top; + byte left = top; + for (int i = 0; i < width; i++) + { + top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out + left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + output[outputOffset + i] = left; + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + // calling (input, width, height, stride, 0, height, 0, output + int row = 0; + int numRows = height; + bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + numRows; + SanityCheck(input, output, width, numRows, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + output[outputOffset] = input[inputOffset]; + PredictLine( + input, inputOffset+1, + preds, predsOffset, + output, outputOffset+1, + width-1, + inverse); + } + + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset-stride, + output, outputOffset, + 1, inverse); + + for (int w = 1; w < width; w++) + { + int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); + int signedPred = inverse ? pred : -pred; + output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); + } + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b + c; + return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; + } } } From fcd5a374049924317c092a28f98a4b414f57b2c0 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Mon, 20 Jan 2020 20:08:10 +0100 Subject: [PATCH 092/359] WebP: implement AlphaDecoder and AlphaApplyFilter, start implementing VP8Io --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 63 +++++++++++++++++++ src/ImageSharp/Formats/WebP/Vp8Io.cs | 70 +++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/ImageSharp/Formats/WebP/AlphaDecoder.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8Io.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs new file mode 100644 index 000000000..92a2c0156 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal ref struct AlphaDecoder + { + public int Width { get; set; } + + public int Height { get; set; } + + public int Method { get; set; } + + public WebPFilterBase Filter { get; set; } + + public int PreProcessing { get; set; } + + public Vp8LDecoder Vp8LDec { get; set; } + + public Vp8Io Io { get; set; } + + /// + /// Although Alpha Channel requires only 1 byte per pixel, + /// sometimes Vp8LDecoder may need to allocate + /// 4 bytes per pixel internally during decode. + /// + public bool Use8BDecode { get; set; } + + // last output row (or null) + private Span PrevLine { get; set; } + + private int PrevLineOffset { get; set; } + + // Taken from vp8l_dec.c AlphaApplyFilter + public void AlphaApplyFilter( + int firstRow, int lastRow, + Span output, int outputOffset, + int stride) + { + if (!(this.Filter is WebPFilterNone)) + { + Span prevLine = this.PrevLine; + int prevLineOffset = this.PrevLineOffset; + + for (int y = firstRow; y < lastRow; y++) + { + this.Filter + .Unfilter( + prevLine, prevLineOffset, + output, outputOffset, + output, outputOffset, + stride); + prevLineOffset = outputOffset; + outputOffset += stride; + } + + this.PrevLine = prevLine; + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs new file mode 100644 index 000000000..f1ab5c749 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + // from + public ref struct Vp8Io + { + /// + /// Picture Width in pixels (invariable). + /// Original, uncropped dimensions. + /// The actual area passed to put() is stored in /> field. + /// + public int Width { get; set; } + + /// + /// Picture Width in pixels (invariable). + /// Original, uncropped dimensions. + /// The actual area passed to put() is stored in /> field. + /// + public int Height { get; set; } + + /// + /// Position of the current Rows (in pixels) + /// + public int MbY { get; set; } + + /// + /// number of columns in the sample + /// + public int MbW { get; set; } + + /// + /// Number of Rows in the sample + /// + public int MbH { get; set; } + + /// + /// Rows to copy (in YUV format) + /// + private Span Y { get; set; } + + /// + /// Rows to copy (in YUV format) + /// + private Span U { get; set; } + + /// + /// Rows to copy (in YUV format) + /// + private Span V { get; set; } + + /// + /// Row stride for luma + /// + public int YStride { get; set; } + + /// + /// Row stride for chroma + /// + public int UvStride { get; set; } + + /// + /// User data + /// + private object Opaque { get; set; } + } +} From 72f3499146ff223560220f68a23d55c01889fed5 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Thu, 23 Jan 2020 20:51:37 +0100 Subject: [PATCH 093/359] WebP: split Filter classes to different files --- .../Formats/WebP/Filters/WebPFilterBase.cs | 94 +++++ .../WebP/Filters/WebPFilterGradient.cs | 103 +++++ .../WebP/Filters/WebPFilterHorizontal.cs | 92 +++++ .../Formats/WebP/Filters/WebPFilterNone.cs | 14 + .../WebP/Filters/WebPFilterVertical.cs | 84 ++++ src/ImageSharp/Formats/WebP/WebPFilterType.cs | 371 ------------------ .../Formats/WebP/WebPLossyDecoder.cs | 1 + 7 files changed, 388 insertions(+), 371 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs delete mode 100644 src/ImageSharp/Formats/WebP/WebPFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs new file mode 100644 index 000000000..9e1a03125 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + // TODO from dsp.h + // public enum WebPFilterType + // { + // None = 0, + // Horizontal, + // Vertical, + // Gradient, + // Last = Gradient + 1, // end marker + // Best, // meta types + // Fast + // } + + abstract class WebPFilterBase + { + /// + /// + /// + /// nullable as prevLine is nullable in the original but Span'T can't be null. + /// + /// + /// + /// + /// + public abstract void Unfilter( + Span prevLine, + int? prevLineOffset, + Span preds, + int predsOffset, + Span currentLine, + int currentLineOffset, + int width); + + public abstract void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset); + + protected static void SanityCheck( + Span input, Span output, int width, int numRows, int height, int stride, int row) + { + Debug.Assert(input != null); + Debug.Assert(output != null); + Debug.Assert(width > 0); + Debug.Assert(height > 0); + Debug.Assert(stride > width); + Debug.Assert(row >= 0); + Debug.Assert(height > 0); + Debug.Assert(row + numRows <= height); + } + + protected static void PredictLine( + Span src, int srcOffset, + Span pred, int predOffset, + Span dst, int dstOffset, + int length, bool inverse) + { + if (inverse) + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] + pred[i]); + } + } + else + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] - pred[i]); + } + } + } + + protected void UnfilterHorizontalOrVerticalCore( + byte pred, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); + pred = output[i]; + } + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs new file mode 100644 index 000000000..ce751b1f5 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs @@ -0,0 +1,103 @@ +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + class WebPFilterGradient : WebPFilterBase + { + + public override void Unfilter( + Span prevLine, + int? prevLineOffsetNullable, + Span input, + int inputOffset, + Span output, + int outputOffset, + int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + byte top = prevLine[prevLineOffset]; + byte topLeft = top; + byte left = top; + for (int i = 0; i < width; i++) + { + top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out + left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + output[outputOffset + i] = left; + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + // calling (input, width, height, stride, 0, height, 0, output + int row = 0; + int numRows = height; + bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + numRows; + SanityCheck(input, output, width, numRows, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + output[outputOffset] = input[inputOffset]; + PredictLine( + input, inputOffset+1, + preds, predsOffset, + output, outputOffset+1, + width-1, + inverse); + } + + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset-stride, + output, outputOffset, + 1, inverse); + + for (int w = 1; w < width; w++) + { + int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); + int signedPred = inverse ? pred : -pred; + output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); + } + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b + c; + return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs new file mode 100644 index 000000000..61c384d7d --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs @@ -0,0 +1,92 @@ +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + class WebPFilterHorizontal : WebPFilterBase + { + public override void Unfilter( + Span prevLine, int? prevLineOffsetNullable, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + byte pred = prevLineOffsetNullable is int prevLineOffset + ? prevLine[prevLineOffset] + : (byte)0; + + this.UnfilterHorizontalOrVerticalCore( + pred, + input, inputOffset, + output, outputOffset, + width); + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int numRows = height; + int row = 0; + + const bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, numRows, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + + if (row == 0) + { + // leftmost pixel is the same as Input for topmost scanline + output[0] = input[0]; + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + + row = 1; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + + // filter line by line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset - stride, + output, 0, + 1, inverse); + PredictLine( + input, inputOffset, + preds, predsOffset, + output,outputOffset + 1, + width - 1, inverse); + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs new file mode 100644 index 000000000..fcecadfb0 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs @@ -0,0 +1,14 @@ +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + // TODO: check if this is a filter or just a placeholder from the C implementation details + class WebPFilterNone : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffset, Span input, int inputOffset, Span output, int outputOffset, int width) + { } + + public override void Filter(Span input, int inputOffset, int width, int height, int stride, Span output, int outputOffset) + { } + } +} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs new file mode 100644 index 000000000..59ab12caf --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs @@ -0,0 +1,84 @@ +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Filters +{ + class WebPFilterVertical : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int row = 0; + bool inverse = false; + + // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + // very first top-left pixel is copied. + output[0] = input[0]; + // rest of top scan-line is left-predicted: + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + row = 1; + inputOffset += stride; + outputOffset += stride; + } + else + { + predsOffset -= stride; + } + + // filter line-by-line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset, + output, outputOffset, + width, inverse); + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/WebP/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/WebPFilterType.cs deleted file mode 100644 index f373da727..000000000 --- a/src/ImageSharp/Formats/WebP/WebPFilterType.cs +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.WebP -{ - // TODO from dsp.h - // public enum WebPFilterType - // { - // None = 0, - // Horizontal, - // Vertical, - // Gradient, - // Last = Gradient + 1, // end marker - // Best, // meta types - // Fast - // } - - abstract class WebPFilterBase - { - /// - /// - /// - /// nullable as prevLine is nullable in the original but Span'T can't be null. - /// - /// - /// - /// - /// - public abstract void Unfilter( - Span prevLine, - int? prevLineOffset, - Span preds, - int predsOffset, - Span currentLine, - int currentLineOffset, - int width); - - public abstract void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset); - - protected static void SanityCheck( - Span input, Span output, int width, int numRows, int height, int stride, int row) - { - Debug.Assert(input != null); - Debug.Assert(output != null); - Debug.Assert(width > 0); - Debug.Assert(height > 0); - Debug.Assert(stride > width); - Debug.Assert(row >= 0); - Debug.Assert(height > 0); - Debug.Assert(row + numRows <= height); - } - - protected static void PredictLine( - Span src, int srcOffset, - Span pred, int predOffset, - Span dst, int dstOffset, - int length, bool inverse) - { - if (inverse) - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] + pred[i]); - } - } - else - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] - pred[i]); - } - } - } - - protected void UnfilterHorizontalOrVerticalCore( - byte pred, - Span input, int inputOffset, - Span output, int outputOffset, - int width) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); - pred = output[i]; - } - } - } - - // TODO: check if this is a filter or just a placeholder from the C implementation details - class WebPFilterNone : WebPFilterBase - { - public override void Unfilter(Span prevLine, int? prevLineOffset, Span input, int inputOffset, Span output, int outputOffset, int width) - { } - - public override void Filter(Span input, int inputOffset, int width, int height, int stride, Span output, int outputOffset) - { } - } - - class WebPFilterHorizontal : WebPFilterBase - { - public override void Unfilter( - Span prevLine, int? prevLineOffsetNullable, - Span input, int inputOffset, - Span output, int outputOffset, - int width) - { - byte pred = prevLineOffsetNullable is int prevLineOffset - ? prevLine[prevLineOffset] - : (byte)0; - - this.UnfilterHorizontalOrVerticalCore( - pred, - input, inputOffset, - output, outputOffset, - width); - } - - public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) - { - int numRows = height; - int row = 0; - - const bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, numRows, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - - if (row == 0) - { - // leftmost pixel is the same as Input for topmost scanline - output[0] = input[0]; - PredictLine( - input, inputOffset + 1, - preds, predsOffset, - output, outputOffset + 1, - width - 1, inverse); - - row = 1; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - - // filter line by line - while (row < lastRow) - { - PredictLine( - input, inputOffset, - preds, predsOffset - stride, - output, 0, - 1, inverse); - PredictLine( - input, inputOffset, - preds, predsOffset, - output,outputOffset + 1, - width - 1, inverse); - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } - - class WebPFilterVertical : WebPFilterBase - { - public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) - { - int row = 0; - bool inverse = false; - - // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - // very first top-left pixel is copied. - output[0] = input[0]; - // rest of top scan-line is left-predicted: - PredictLine( - input, inputOffset + 1, - preds, predsOffset, - output, outputOffset + 1, - width - 1, inverse); - row = 1; - inputOffset += stride; - outputOffset += stride; - } - else - { - predsOffset -= stride; - } - - // filter line-by-line - while (row < lastRow) - { - PredictLine( - input, inputOffset, - preds, predsOffset, - output, outputOffset, - width, inverse); - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } - - class WebPFilterGradient : WebPFilterBase - { - - public override void Unfilter( - Span prevLine, - int? prevLineOffsetNullable, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - byte top = prevLine[prevLineOffset]; - byte topLeft = top; - byte left = top; - for (int i = 0; i < width; i++) - { - top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out - left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); - topLeft = top; - output[outputOffset + i] = left; - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, int inputOffset, - int width, int height, int stride, - Span output, int outputOffset) - { - // calling (input, width, height, stride, 0, height, 0, output - int row = 0; - int numRows = height; - bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + numRows; - SanityCheck(input, output, width, numRows, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - output[outputOffset] = input[inputOffset]; - PredictLine( - input, inputOffset+1, - preds, predsOffset, - output, outputOffset+1, - width-1, - inverse); - } - - while (row < lastRow) - { - PredictLine( - input, inputOffset, - preds, predsOffset-stride, - output, outputOffset, - 1, inverse); - - for (int w = 1; w < width; w++) - { - int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); - int signedPred = inverse ? pred : -pred; - output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); - } - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - - private static int GradientPredictor(byte a, byte b, byte c) - { - int g = a + b + c; - return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; - } - } -} diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index da84079cf..89f58f633 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; From 2acb3df0eaeccd73a136377787ac466fd5a83760 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Jan 2020 17:00:11 +0100 Subject: [PATCH 094/359] Fix some issues in Vp8BitReader --- src/ImageSharp/Formats/WebP/LoopFilter.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 38 ++++++++++--------- .../Formats/WebP/WebPDecoderCore.cs | 27 +++++++++++-- .../Formats/WebP/WebPLossyDecoder.cs | 2 +- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs index 80a6b95ed..acf7a1114 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal enum LoopFilter { - Normal, + Complex, Simple, None } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index a3996b08d..5264f5bc1 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -36,16 +36,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private byte buf; - /// - /// End of read buffer. - /// - // private byte bufEnd; - - /// - /// Max packed-read position on buffer. - /// - // private byte bufMax; - /// /// True if input is exhausted. /// @@ -68,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.range = 255 - 1; this.value = 0; - this.bits = -8; // to load the very first 8bits; + this.bits = -8; // to load the very first 8bits. this.eof = false; this.pos = 0; @@ -87,7 +77,8 @@ namespace SixLabors.ImageSharp.Formats.WebP int pos = this.bits; uint split = (uint)((range * prob) >> 8); - bool bit = this.value > split; + ulong value = this.value >> pos; + bool bit = value > split; if (bit) { range -= split; @@ -117,9 +108,9 @@ namespace SixLabors.ImageSharp.Formats.WebP Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); uint v = 0; - while (this.bits-- > 0) + while (nBits-- > 0) { - v |= (uint)this.GetBit(0x80) << this.bits; + v |= (uint)this.GetBit(0x80) << nBits; } return v; @@ -140,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.WebP ulong bits; ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan().Slice((int)this.pos, 8)); this.pos += BitsCount >> 3; - this.buf = this.Data[BitsCount >> 3]; + this.buf = this.Data[this.pos]; bits = this.ByteSwap64(inBits); bits >>= 64 - BitsCount; this.value = bits | (this.value << BitsCount); @@ -182,9 +173,20 @@ namespace SixLabors.ImageSharp.Formats.WebP private int BitsLog2Floor(uint n) { - long firstSetBit; - throw new NotImplementedException(); - // BitScanReverse(firstSetBit, n); + // Search the mask data from most significant bit (MSB) to least significant bit (LSB) for a set bit (1). + // https://docs.microsoft.com/en-us/cpp/intrinsics/bitscanreverse-bitscanreverse64?view=vs-2019 + uint mask = 1; + for (int y = 0; y < 32; y++) + { + if ((mask & n) == mask) + { + return y; + } + + mask <<= 1; + } + + return 0; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f0b0233cf..f5e1af2cb 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -284,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.webpMetadata.Format = WebPFormatType.Lossy; - // VP8 data size. + // VP8 data size (not including this 4 bytes). this.currentStream.Read(this.buffer, 0, 4); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); @@ -339,6 +339,13 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); } + // The size of the encoded image data payload. + uint imageDataSize = dataSize - 10; // we have read 10 bytes already. + if (partitionLength > imageDataSize) + { + WebPThrowHelper.ThrowImageFormatException("bad partition length"); + } + var vp8FrameHeader = new Vp8FrameHeader() { KeyFrame = true, @@ -346,7 +353,10 @@ namespace SixLabors.ImageSharp.Formats.WebP PartitionLength = partitionLength }; - var bitReader = new Vp8BitReader(this.currentStream, dataSize - 10, this.memoryAllocator); + var bitReader = new Vp8BitReader( + this.currentStream, + imageDataSize, + this.memoryAllocator); // Paragraph 9.2: color space and clamp type follow sbyte colorSpace = (sbyte)bitReader.ReadValue(1); @@ -394,10 +404,14 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.4: Parse the filter specs. var vp8FilterHeader = new Vp8FilterHeader(); - vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Normal; + vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; vp8FilterHeader.Level = (int)bitReader.ReadValue(6); vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); + + // TODO: use enum here? + // 0 = 0ff, 1 = simple, 2 = complex + int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; if (vp8FilterHeader.UseLfDelta) { // Update lf-delta? @@ -423,7 +437,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // TODO: ParsePartitions + // Paragraph 9.5: ParsePartitions. + int numPartsMinusOne = (1 << (int)bitReader.ReadValue(2)) - 1; + int lastPart = numPartsMinusOne; + // TODO: check if we have enough data available here, throw exception if not + + return new WebPImageInfo() { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 89f58f633..86fb099fd 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP switch (version) { case 0: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Normal }; + return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Complex }; case 1: return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.Simple }; case 2: From 795617bdcf2ed771373787d813f9b7bdb149df93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Jan 2020 18:55:34 +0100 Subject: [PATCH 095/359] Paragraph 9.6: Dequantization Indices --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 48 ++++++++++++- .../Formats/WebP/WebPDecoderCore.cs | 68 +++++++++++++++++-- 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index 92a2c0156..fb5663464 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -1,8 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Formats.WebP.Filters; + namespace SixLabors.ImageSharp.Formats.WebP { internal ref struct AlphaDecoder diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 6f0c4618c..19babc4a3 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int MaxColorCacheBits = 11; /// - /// The maximum number of allowed transforms in a bitstream. + /// The maximum number of allowed transforms in a VP8L bitstream. /// public const int MaxNumberOfTransforms = 4; @@ -104,5 +104,51 @@ namespace SixLabors.ImageSharp.Formats.WebP NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, NumDistanceCodes }; + + // VP8 constants + public const int NumMbSegments = 4; + + public const int MaxNumPartitions = 8; + + // Paragraph 14.1 + public static readonly int[] DcTable = + { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 + }; + + public static readonly int[] AcTable = + { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 + }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index f5e1af2cb..8de4c350a 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -374,6 +374,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.3: Parse the segment header. var vp8SegmentHeader = new Vp8SegmentHeader(); vp8SegmentHeader.UseSegment = bitReader.ReadBool(); + bool hasValue = false; if (vp8SegmentHeader.UseSegment) { vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); @@ -383,14 +384,14 @@ namespace SixLabors.ImageSharp.Formats.WebP vp8SegmentHeader.Delta = bitReader.ReadBool(); for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) { - bool hasValue = bitReader.ReadBool(); + hasValue = bitReader.ReadBool(); uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; } for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) { - bool hasValue = bitReader.ReadBool(); + hasValue = bitReader.ReadBool(); uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; } @@ -419,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) { - bool hasValue = bitReader.ReadBool(); + hasValue = bitReader.ReadBool(); if (hasValue) { vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); @@ -428,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) { - bool hasValue = bitReader.ReadBool(); + hasValue = bitReader.ReadBool(); if (hasValue) { vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); @@ -442,7 +443,61 @@ namespace SixLabors.ImageSharp.Formats.WebP int lastPart = numPartsMinusOne; // TODO: check if we have enough data available here, throw exception if not + // Paragraph 9.6: Dequantization Indices + int baseQ0 = (int)bitReader.ReadValue(7); + hasValue = bitReader.ReadBool(); + int dqy1Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; + hasValue = bitReader.ReadBool(); + int dqy2Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; + hasValue = bitReader.ReadBool(); + int dqy2Ac = hasValue ? bitReader.ReadSignedValue(4) : 0; + hasValue = bitReader.ReadBool(); + int dquvDc = hasValue ? bitReader.ReadSignedValue(4) : 0; + hasValue = bitReader.ReadBool(); + int dquvAc = hasValue ? bitReader.ReadSignedValue(4) : 0; + for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + { + int q; + if (vp8SegmentHeader.UseSegment) + { + q = vp8SegmentHeader.Quantizer[i]; + if (!vp8SegmentHeader.Delta) + { + q += baseQ0; + } + } + else + { + if (i > 0) + { + // dec->dqm_[i] = dec->dqm_[0]; + continue; + } + else + { + q = baseQ0; + } + } + + var m = new Vp8QuantMatrix(); + m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; + m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; + + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. + m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; + if (m.Y2Mat[1] < 8) + { + m.Y2Mat[1] = 8; + } + m.UVMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; + m.UVMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; + + // For dithering strength evaluation. + m.UvQuant = q + dquvAc; + } return new WebPImageInfo() { @@ -578,5 +633,10 @@ namespace SixLabors.ImageSharp.Formats.WebP throw new ImageFormatException("Invalid WebP data."); } + + private int Clip(int v, int M) + { + return v < 0 ? 0 : v > M ? M : v; + } } } From 66c719e606392fa7467679707ed2f9097d030897 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Jan 2020 19:16:06 +0100 Subject: [PATCH 096/359] Rename constants --- src/ImageSharp/Formats/WebP/ColorCache.cs | 4 ++-- src/ImageSharp/Formats/WebP/Vp8LBitReader.cs | 4 ++-- src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 2 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 17 ++++++++++++----- src/ImageSharp/Formats/WebP/WebPDecoderCore.cs | 4 ++-- .../Formats/WebP/WebPLosslessDecoder.cs | 14 +++++++------- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index d5579cbf1..d5834a4c8 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal class ColorCache { - private const uint KHashMul = 0x1e35a7bdu; + private const uint HashMul = 0x1e35a7bdu; /// /// Gets the color entries. @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int HashPix(uint argb, int shift) { - return (int)((argb * KHashMul) >> shift); + return (int)((argb * HashMul) >> shift); } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index 893812472..97a1c242f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private const int Vp8LWbits = 32; - private readonly uint[] kBitMask = + private readonly uint[] BitMask = { 0, 0x000001, 0x000003, 0x000007, 0x00000f, @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (!this.eos && nBits <= Vp8LMaxNumBitRead) { - ulong val = this.PrefetchBits() & this.kBitMask[nBits]; + ulong val = this.PrefetchBits() & this.BitMask[nBits]; int newBits = this.bitPos + nBits; this.bitPos = newBits; this.ShiftBytes(); diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs index aaca80285..a5d718a48 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int[] Y2Mat { get; set; } - public int[] UVMat { get; set; } + public int[] UvMat { get; set; } /// /// Gets or sets the U/V quantizer value. diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index 19babc4a3..fe9d93308 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int LengthTableBits = 7; - public const uint KCodeLengthLiterals = 16; + public const uint CodeLengthLiterals = 16; - public const int KCodeLengthRepeatCode = 16; + public const int CodeLengthRepeatCode = 16; - public static readonly int[] KCodeLengthExtraBits = { 2, 3, 7 }; + public static readonly int[] CodeLengthExtraBits = { 2, 3, 7 }; - public static readonly int[] KCodeLengthRepeatOffsets = { 3, 3, 11 }; + public static readonly int[] CodeLengthRepeatOffsets = { 3, 3, 11 }; - public static readonly int[] KAlphabetSize = + public static readonly int[] AlphabetSize = { NumLiteralCodes + NumLengthCodes, NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, @@ -131,6 +131,7 @@ namespace SixLabors.ImageSharp.Formats.WebP 138, 140, 143, 145, 148, 151, 154, 157 }; + // Paragraph 14.1 public static readonly int[] AcTable = { 4, 5, 6, 7, 8, 9, 10, 11, @@ -150,5 +151,11 @@ namespace SixLabors.ImageSharp.Formats.WebP 213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284 }; + + // Paragraph 9.9 + public static readonly int[] Bands = + { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 + }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 8de4c350a..35eed2ed7 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -492,8 +492,8 @@ namespace SixLabors.ImageSharp.Formats.WebP m.Y2Mat[1] = 8; } - m.UVMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; - m.UVMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; + m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; // For dithering strength evaluation. m.UvQuant = q + dquvAc; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index ab4dd6306..86fd34bae 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -380,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Find maximum alphabet size for the hTree group. for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.KAlphabetSize[j]; + int alphabetSize = WebPConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var codeLengths = new int[maxAlphabetSize]; for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++) { - int alphabetSize = WebPConstants.KAlphabetSize[j]; + int alphabetSize = WebPConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; @@ -560,7 +560,7 @@ namespace SixLabors.ImageSharp.Formats.WebP HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); uint codeLen = huffmanCode.Value; - if (codeLen < WebPConstants.KCodeLengthLiterals) + if (codeLen < WebPConstants.CodeLengthLiterals) { codeLengths[symbol++] = (int)codeLen; if (codeLen != 0) @@ -570,10 +570,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - bool usePrev = codeLen == WebPConstants.KCodeLengthRepeatCode; - uint slot = codeLen - WebPConstants.KCodeLengthLiterals; - int extraBits = WebPConstants.KCodeLengthExtraBits[slot]; - int repeatOffset = WebPConstants.KCodeLengthRepeatOffsets[slot]; + bool usePrev = codeLen == WebPConstants.CodeLengthRepeatCode; + uint slot = codeLen - WebPConstants.CodeLengthLiterals; + int extraBits = WebPConstants.CodeLengthExtraBits[slot]; + int repeatOffset = WebPConstants.CodeLengthRepeatOffsets[slot]; int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { From beb04d010d959d1acb4939c595682b52179298d7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jan 2020 13:06:40 +0100 Subject: [PATCH 097/359] Paragraph 13.4: Parse probabilities --- src/ImageSharp/Formats/WebP/VP8BandProbas.cs | 18 ++ src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 25 +- src/ImageSharp/Formats/WebP/Vp8Proba.cs | 23 ++ src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs | 18 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 296 +++++++++++++++++- .../Formats/WebP/WebPDecoderCore.cs | 53 +++- 6 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/VP8BandProbas.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8Proba.cs create mode 100644 src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs new file mode 100644 index 000000000..6a48eef55 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// All the probabilities associated to one band. + /// + internal class Vp8BandProbas + { + public Vp8BandProbas() + { + this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx]; + } + + public Vp8ProbaArray[] Probabilities { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 5264f5bc1..4fee28d39 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -46,23 +46,19 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private long pos; + public int Pos { get { return (int)pos; } } + /// /// Initializes a new instance of the class. /// /// The input stream to read from. /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. - public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) + /// Start index in the data array. Defaults to 0. + public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, int startPos = 0) { this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); - - this.range = 255 - 1; - this.value = 0; - this.bits = -8; // to load the very first 8bits. - this.eof = false; - this.pos = 0; - - this.LoadNewBytes(); + this.InitBitreader(startPos); } public int GetBit(int prob) @@ -124,6 +120,17 @@ namespace SixLabors.ImageSharp.Formats.WebP return this.ReadValue(1) != 0 ? -value : value; } + private void InitBitreader(int pos = 0) + { + this.range = 255 - 1; + this.value = 0; + this.bits = -8; // to load the very first 8bits. + this.eof = false; + this.pos = 0; + + this.LoadNewBytes(); + } + private void LoadNewBytes() { if (this.pos < this.Data.Length) diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs new file mode 100644 index 000000000..ac635cf48 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Data for all frame-persistent probabilities. + /// + internal class Vp8Proba + { + private const int MbFeatureTreeProbs = 3; + + public Vp8Proba() + { + this.Segments = new uint[MbFeatureTreeProbs]; + this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; + } + + public uint[] Segments { get; } + + public Vp8BandProbas[,] Bands { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs new file mode 100644 index 000000000..0e7defd4a --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Probabilities associated to one of the contexts. + /// + internal class Vp8ProbaArray + { + public Vp8ProbaArray() + { + this.Probabilities = new uint[WebPConstants.NumProbas]; + } + + public uint[] Probabilities { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index fe9d93308..dfef15260 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// Constants used for decoding VP8 and VP8L bitstreams. + /// internal static class WebPConstants { /// @@ -105,11 +108,25 @@ namespace SixLabors.ImageSharp.Formats.WebP NumDistanceCodes }; - // VP8 constants + // VP8 constants from here on public const int NumMbSegments = 4; public const int MaxNumPartitions = 8; + public const int NumTypes = 4; + + public const int NumBands = 8; + + public const int NumProbas = 11; + + public const int NumCtx = 3; + + // Paragraph 9.9 + public static readonly int[] Bands = + { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 + }; + // Paragraph 14.1 public static readonly int[] DcTable = { @@ -152,10 +169,281 @@ namespace SixLabors.ImageSharp.Formats.WebP 249, 254, 259, 264, 269, 274, 279, 284 }; - // Paragraph 9.9 - public static readonly int[] Bands = + // Paragraph 13 + public static readonly byte[,,,] CoeffsUpdateProba = { - 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 + { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } + }; + + // Paragraph 13.5: Default Token Probability Table. + public static readonly byte[,,,] DefaultCoeffsProba = + { + { + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } }; } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 35eed2ed7..89f54dec6 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -373,8 +373,9 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.3: Parse the segment header. var vp8SegmentHeader = new Vp8SegmentHeader(); + var proba = new Vp8Proba(); vp8SegmentHeader.UseSegment = bitReader.ReadBool(); - bool hasValue = false; + bool hasValue; if (vp8SegmentHeader.UseSegment) { vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); @@ -398,10 +399,18 @@ namespace SixLabors.ImageSharp.Formats.WebP if (vp8SegmentHeader.UpdateMap) { - // TODO: Read VP8Proba + for (int s = 0; s < proba.Segments.Length; ++s) + { + hasValue = bitReader.ReadBool(); + proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; + } } } } + else + { + vp8SegmentHeader.UpdateMap = false; + } // Paragraph 9.4: Parse the filter specs. var vp8FilterHeader = new Vp8FilterHeader(); @@ -442,8 +451,13 @@ namespace SixLabors.ImageSharp.Formats.WebP int numPartsMinusOne = (1 << (int)bitReader.ReadValue(2)) - 1; int lastPart = numPartsMinusOne; // TODO: check if we have enough data available here, throw exception if not + int partStart = bitReader.Pos + (lastPart * 3); + for (int p = 0; p < lastPart; ++p) + { + // int psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + } - // Paragraph 9.6: Dequantization Indices + // Paragraph 9.6: Dequantization Indices. int baseQ0 = (int)bitReader.ReadValue(7); hasValue = bitReader.ReadBool(); int dqy1Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; @@ -499,6 +513,39 @@ namespace SixLabors.ImageSharp.Formats.WebP m.UvQuant = q + dquvAc; } + // Ignore the value of update_proba + bitReader.ReadBool(); + + // Parse probabilities. + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; + int v = bitReader.GetBit(prob) == 0 ? (int)bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; + proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v; + } + } + } + + for (int b = 0; b < 16 + 1; ++b) + { + // TODO: This needs to be reviewed and fixed. + // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + } + } + + // TODO: those values needs to be stored somewhere + bool useSkipProba = bitReader.ReadBool(); + if (useSkipProba) + { + var skipP = bitReader.ReadValue(8); + } + return new WebPImageInfo() { Width = width, From 7f5a48137bce45de62881deaf32d1c13a3a7e1c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jan 2020 14:44:23 +0100 Subject: [PATCH 098/359] Move parsing VP8 infos into separate methods --- src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 7 +- .../Formats/WebP/WebPDecoderCore.cs | 179 ++++++++++-------- 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 4fee28d39..2fdd4959f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -46,8 +46,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private long pos; - public int Pos { get { return (int)pos; } } - /// /// Initializes a new instance of the class. /// @@ -61,6 +59,11 @@ namespace SixLabors.ImageSharp.Formats.WebP this.InitBitreader(startPos); } + public int Pos + { + get { return (int)this.pos; } + } + public int GetBit(int prob) { Guard.MustBeGreaterThan(prob, 0, nameof(prob)); diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 89f54dec6..73fb3fed2 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -288,6 +288,9 @@ namespace SixLabors.ImageSharp.Formats.WebP this.currentStream.Read(this.buffer, 0, 4); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + // remaining counts the available image data payload. + uint remaining = dataSize; + // See paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 // Frame tag that contains four fields: // - A 1-bit frame type (0 for key frames, 1 for interframes). @@ -296,6 +299,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // - A 19-bit field containing the size of the first data partition in bytes. this.currentStream.Read(this.buffer, 0, 3); uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); + remaining -= 3; bool isKeyFrame = (frameTag & 0x1) is 0; if (!isKeyFrame) { @@ -334,14 +338,13 @@ namespace SixLabors.ImageSharp.Formats.WebP tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)); uint height = tmp & 0x3fff; sbyte yScale = (sbyte)(tmp >> 6); + remaining -= 7; if (width is 0 || height is 0) { WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); } - // The size of the encoded image data payload. - uint imageDataSize = dataSize - 10; // we have read 10 bytes already. - if (partitionLength > imageDataSize) + if (partitionLength > remaining) { WebPThrowHelper.ThrowImageFormatException("bad partition length"); } @@ -355,10 +358,10 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8BitReader( this.currentStream, - imageDataSize, + remaining, this.memoryAllocator); - // Paragraph 9.2: color space and clamp type follow + // Paragraph 9.2: color space and clamp type follow. sbyte colorSpace = (sbyte)bitReader.ReadValue(1); sbyte clampType = (sbyte)bitReader.ReadValue(1); var vp8PictureHeader = new Vp8PictureHeader() @@ -372,90 +375,18 @@ namespace SixLabors.ImageSharp.Formats.WebP }; // Paragraph 9.3: Parse the segment header. - var vp8SegmentHeader = new Vp8SegmentHeader(); - var proba = new Vp8Proba(); - vp8SegmentHeader.UseSegment = bitReader.ReadBool(); bool hasValue; - if (vp8SegmentHeader.UseSegment) - { - vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); - bool updateData = bitReader.ReadBool(); - if (updateData) - { - vp8SegmentHeader.Delta = bitReader.ReadBool(); - for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; - vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; - } - - for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; - vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; - } - - if (vp8SegmentHeader.UpdateMap) - { - for (int s = 0; s < proba.Segments.Length; ++s) - { - hasValue = bitReader.ReadBool(); - proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; - } - } - } - } - else - { - vp8SegmentHeader.UpdateMap = false; - } + var proba = new Vp8Proba(); + Vp8SegmentHeader vp8SegmentHeader = ParseSegmentHeader(bitReader, proba); // Paragraph 9.4: Parse the filter specs. - var vp8FilterHeader = new Vp8FilterHeader(); - vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.Level = (int)bitReader.ReadValue(6); - vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); - vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); + Vp8FilterHeader vp8FilterHeader = ParseFilterHeader(bitReader); - // TODO: use enum here? - // 0 = 0ff, 1 = simple, 2 = complex - int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; - if (vp8FilterHeader.UseLfDelta) - { - // Update lf-delta? - if (bitReader.ReadBool()) - { - for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - - for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - } - } - - // Paragraph 9.5: ParsePartitions. + // TODO: Review Paragraph 9.5: ParsePartitions. int numPartsMinusOne = (1 << (int)bitReader.ReadValue(2)) - 1; int lastPart = numPartsMinusOne; // TODO: check if we have enough data available here, throw exception if not int partStart = bitReader.Pos + (lastPart * 3); - for (int p = 0; p < lastPart; ++p) - { - // int psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); - } // Paragraph 9.6: Dequantization Indices. int baseQ0 = (int)bitReader.ReadValue(7); @@ -516,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // Ignore the value of update_proba bitReader.ReadBool(); - // Parse probabilities. + // Paragraph 13.4: Parse probabilities. for (int t = 0; t < WebPConstants.NumTypes; ++t) { for (int b = 0; b < WebPConstants.NumBands; ++b) @@ -562,6 +493,90 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + private static Vp8FilterHeader ParseFilterHeader(Vp8BitReader bitReader) + { + var vp8FilterHeader = new Vp8FilterHeader(); + vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; + vp8FilterHeader.Level = (int)bitReader.ReadValue(6); + vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); + vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); + + // TODO: use enum here? + // 0 = 0ff, 1 = simple, 2 = complex + int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; + bool hasValue; + if (vp8FilterHeader.UseLfDelta) + { + // Update lf-delta? + if (bitReader.ReadBool()) + { + for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) + { + hasValue = bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); + } + } + + for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + { + hasValue = bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); + } + } + } + } + + return vp8FilterHeader; + } + + private static Vp8SegmentHeader ParseSegmentHeader(Vp8BitReader bitReader, Vp8Proba proba) + { + var vp8SegmentHeader = new Vp8SegmentHeader(); + vp8SegmentHeader.UseSegment = bitReader.ReadBool(); + bool hasValue; + if (vp8SegmentHeader.UseSegment) + { + vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); + bool updateData = bitReader.ReadBool(); + if (updateData) + { + vp8SegmentHeader.Delta = bitReader.ReadBool(); + for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) + { + hasValue = bitReader.ReadBool(); + uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; + vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; + } + + for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) + { + hasValue = bitReader.ReadBool(); + uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; + vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; + } + + if (vp8SegmentHeader.UpdateMap) + { + for (int s = 0; s < proba.Segments.Length; ++s) + { + hasValue = bitReader.ReadBool(); + proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; + } + } + } + } + else + { + vp8SegmentHeader.UpdateMap = false; + } + + return vp8SegmentHeader; + } + /// /// Reads the header of a lossless webp image. /// From 3fb621f28aeac98d0036b614f6639def6fc14fe5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 27 Jan 2020 15:04:52 +0100 Subject: [PATCH 099/359] Move Vp8 specific parsing into LossyDecoder --- .../Formats/WebP/WebPDecoderCore.cs | 198 +--------------- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 10 - .../Formats/WebP/WebPLossyDecoder.cs | 218 +++++++++++++++++- 3 files changed, 217 insertions(+), 209 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 73fb3fed2..060ec0f0f 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -366,117 +366,14 @@ namespace SixLabors.ImageSharp.Formats.WebP sbyte clampType = (sbyte)bitReader.ReadValue(1); var vp8PictureHeader = new Vp8PictureHeader() { - Width = width, - Height = height, + Width = (uint)width, + Height = (uint)height, XScale = xScale, YScale = yScale, ColorSpace = colorSpace, ClampType = clampType }; - // Paragraph 9.3: Parse the segment header. - bool hasValue; - var proba = new Vp8Proba(); - Vp8SegmentHeader vp8SegmentHeader = ParseSegmentHeader(bitReader, proba); - - // Paragraph 9.4: Parse the filter specs. - Vp8FilterHeader vp8FilterHeader = ParseFilterHeader(bitReader); - - // TODO: Review Paragraph 9.5: ParsePartitions. - int numPartsMinusOne = (1 << (int)bitReader.ReadValue(2)) - 1; - int lastPart = numPartsMinusOne; - // TODO: check if we have enough data available here, throw exception if not - int partStart = bitReader.Pos + (lastPart * 3); - - // Paragraph 9.6: Dequantization Indices. - int baseQ0 = (int)bitReader.ReadValue(7); - hasValue = bitReader.ReadBool(); - int dqy1Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dqy2Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dqy2Ac = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dquvDc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dquvAc = hasValue ? bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebPConstants.NumMbSegments; ++i) - { - int q; - if (vp8SegmentHeader.UseSegment) - { - q = vp8SegmentHeader.Quantizer[i]; - if (!vp8SegmentHeader.Delta) - { - q += baseQ0; - } - } - else - { - if (i > 0) - { - // dec->dqm_[i] = dec->dqm_[0]; - continue; - } - else - { - q = baseQ0; - } - } - - var m = new Vp8QuantMatrix(); - m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; - m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; - - // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. - // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; - if (m.Y2Mat[1] < 8) - { - m.Y2Mat[1] = 8; - } - - m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; - - // For dithering strength evaluation. - m.UvQuant = q + dquvAc; - } - - // Ignore the value of update_proba - bitReader.ReadBool(); - - // Paragraph 13.4: Parse probabilities. - for (int t = 0; t < WebPConstants.NumTypes; ++t) - { - for (int b = 0; b < WebPConstants.NumBands; ++b) - { - for (int c = 0; c < WebPConstants.NumCtx; ++c) - { - for (int p = 0; p < WebPConstants.NumProbas; ++p) - { - var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; - int v = bitReader.GetBit(prob) == 0 ? (int)bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v; - } - } - } - - for (int b = 0; b < 16 + 1; ++b) - { - // TODO: This needs to be reviewed and fixed. - // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; - } - } - - // TODO: those values needs to be stored somewhere - bool useSkipProba = bitReader.ReadBool(); - if (useSkipProba) - { - var skipP = bitReader.ReadValue(8); - } - return new WebPImageInfo() { Width = width, @@ -486,97 +383,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Features = features, Vp8Profile = (sbyte)version, Vp8FrameHeader = vp8FrameHeader, - Vp8SegmentHeader = vp8SegmentHeader, - Vp8FilterHeader = vp8FilterHeader, Vp8PictureHeader = vp8PictureHeader, Vp8BitReader = bitReader }; } - private static Vp8FilterHeader ParseFilterHeader(Vp8BitReader bitReader) - { - var vp8FilterHeader = new Vp8FilterHeader(); - vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.Level = (int)bitReader.ReadValue(6); - vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); - vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); - - // TODO: use enum here? - // 0 = 0ff, 1 = simple, 2 = complex - int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; - bool hasValue; - if (vp8FilterHeader.UseLfDelta) - { - // Update lf-delta? - if (bitReader.ReadBool()) - { - for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - - for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - } - } - - return vp8FilterHeader; - } - - private static Vp8SegmentHeader ParseSegmentHeader(Vp8BitReader bitReader, Vp8Proba proba) - { - var vp8SegmentHeader = new Vp8SegmentHeader(); - vp8SegmentHeader.UseSegment = bitReader.ReadBool(); - bool hasValue; - if (vp8SegmentHeader.UseSegment) - { - vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); - bool updateData = bitReader.ReadBool(); - if (updateData) - { - vp8SegmentHeader.Delta = bitReader.ReadBool(); - for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; - vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; - } - - for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; - vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; - } - - if (vp8SegmentHeader.UpdateMap) - { - for (int s = 0; s < proba.Segments.Length; ++s) - { - hasValue = bitReader.ReadBool(); - proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; - } - } - } - } - else - { - vp8SegmentHeader.UpdateMap = false; - } - - return vp8SegmentHeader; - } - /// /// Reads the header of a lossless webp image. /// @@ -695,10 +506,5 @@ namespace SixLabors.ImageSharp.Formats.WebP throw new ImageFormatException("Invalid WebP data."); } - - private int Clip(int v, int M) - { - return v < 0 ? 0 : v > M ? M : v; - } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 1eeb4d2e0..dace37aad 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -45,16 +45,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8PictureHeader Vp8PictureHeader { get; set; } - /// - /// Gets or sets the VP8 segment header. - /// - public Vp8SegmentHeader Vp8SegmentHeader { get; set; } - - /// - /// Gets or sets the VP8 filter header. - /// - public Vp8FilterHeader Vp8FilterHeader { get; set; } - /// /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 86fb099fd..59528067e 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,11 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; -using System.IO; -using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -36,6 +33,28 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V Vp8Profile vp8Profile = this.DecodeProfile(vp8Version); + + // Paragraph 9.3: Parse the segment header. + var proba = new Vp8Proba(); + Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); + + // Paragraph 9.4: Parse the filter specs. + Vp8FilterHeader vp8FilterHeader = this.ParseFilterHeader(); + + // TODO: Review Paragraph 9.5: ParsePartitions. + int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; + int lastPart = numPartsMinusOne; + // TODO: check if we have enough data available here, throw exception if not + int partStart = this.bitReader.Pos + (lastPart * 3); + + // Paragraph 9.6: Dequantization Indices. + this.ParseDequantizationIndices(vp8SegmentHeader); + + // Ignore the value of update_proba + this.bitReader.ReadBool(); + + // Paragraph 13.4: Parse probabilities. + this.ParseProbabilities(proba); } private Vp8Profile DecodeProfile(int version) @@ -58,11 +77,191 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) + { + var vp8SegmentHeader = new Vp8SegmentHeader + { + UseSegment = this.bitReader.ReadBool() + }; + if (vp8SegmentHeader.UseSegment) + { + vp8SegmentHeader.UpdateMap = this.bitReader.ReadBool(); + bool updateData = this.bitReader.ReadBool(); + if (updateData) + { + vp8SegmentHeader.Delta = this.bitReader.ReadBool(); + bool hasValue; + for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + uint quantizeValue = hasValue ? this.bitReader.ReadValue(7) : 0; + vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; + } + + for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + uint filterStrengthValue = hasValue ? this.bitReader.ReadValue(6) : 0; + vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; + } + + if (vp8SegmentHeader.UpdateMap) + { + for (int s = 0; s < proba.Segments.Length; ++s) + { + hasValue = bitReader.ReadBool(); + proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; + } + } + } + } + else + { + vp8SegmentHeader.UpdateMap = false; + } + + return vp8SegmentHeader; + } + + private Vp8FilterHeader ParseFilterHeader() + { + var vp8FilterHeader = new Vp8FilterHeader(); + vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; + vp8FilterHeader.Level = (int)this.bitReader.ReadValue(6); + vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); + vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); + + // TODO: use enum here? + // 0 = 0ff, 1 = simple, 2 = complex + int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; + if (vp8FilterHeader.UseLfDelta) + { + // Update lf-delta? + if (this.bitReader.ReadBool()) + { + bool hasValue; + for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.RefLfDelta[i] = this.bitReader.ReadSignedValue(6); + } + } + + for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.ModeLfDelta[i] = this.bitReader.ReadSignedValue(6); + } + } + } + } + + return vp8FilterHeader; + } + + private void ParseDequantizationIndices(Vp8SegmentHeader vp8SegmentHeader) + { + int baseQ0 = (int)this.bitReader.ReadValue(7); + bool hasValue = this.bitReader.ReadBool(); + int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Ac = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + { + int q; + if (vp8SegmentHeader.UseSegment) + { + q = vp8SegmentHeader.Quantizer[i]; + if (!vp8SegmentHeader.Delta) + { + q += baseQ0; + } + } + else + { + if (i > 0) + { + // dec->dqm_[i] = dec->dqm_[0]; + continue; + } + else + { + q = baseQ0; + } + } + + var m = new Vp8QuantMatrix(); + m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; + m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; + + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. + m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; + if (m.Y2Mat[1] < 8) + { + m.Y2Mat[1] = 8; + } + + m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; + + // For dithering strength evaluation. + m.UvQuant = q + dquvAc; + } + } + + private void ParseProbabilities(Vp8Proba proba) + { + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; + int v = this.bitReader.GetBit(prob) == 0 + ? (int)this.bitReader.ReadValue(8) + : WebPConstants.DefaultCoeffsProba[t, b, c, p]; + proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v; + } + } + } + + for (int b = 0; b < 16 + 1; ++b) + { + // TODO: This needs to be reviewed and fixed. + // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + } + } + + // TODO: those values needs to be stored somewhere + bool useSkipProba = this.bitReader.ReadBool(); + if (useSkipProba) + { + var skipP = this.bitReader.ReadValue(8); + } + } + static bool Is8bOptimizable(Vp8LMetadata hdr) { int i; if (hdr.ColorCacheSize > 0) + { return false; + } // When the Huffman tree contains only one symbol, we can skip the // call to ReadSymbol() for red/blue/alpha channels. @@ -70,15 +269,28 @@ namespace SixLabors.ImageSharp.Formats.WebP { List htrees = hdr.HTreeGroups[i].HTrees; if (htrees[HuffIndex.Red][0].Value > 0) + { return false; + } + if (htrees[HuffIndex.Blue][0].Value > 0) + { return false; + } + if (htrees[HuffIndex.Alpha][0].Value > 0) + { return false; + } } return true; } + + private int Clip(int v, int M) + { + return v < 0 ? 0 : v > M ? M : v; + } } struct YUVPixel From 30eeeaa7af2cb0d89ed0147d464cee93774c41e9 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Mon, 27 Jan 2020 22:00:56 +0100 Subject: [PATCH 100/359] code cleanup, make method static --- src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 59528067e..bc4ef5aa9 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -201,20 +201,20 @@ namespace SixLabors.ImageSharp.Formats.WebP } var m = new Vp8QuantMatrix(); - m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; - m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; + m.Y1Mat[0] = WebPConstants.DcTable[Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebPConstants.AcTable[Clip(q + 0, 127)]; + m.Y2Mat[0] = WebPConstants.DcTable[Clip(q + dqy2Dc, 127)] * 2; // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; + m.Y2Mat[1] = (WebPConstants.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; if (m.Y2Mat[1] < 8) { m.Y2Mat[1] = 8; } - m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; + m.UvMat[0] = WebPConstants.DcTable[Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebPConstants.AcTable[Clip(q + dquvAc, 127)]; // For dithering strength evaluation. m.UvQuant = q + dquvAc; @@ -287,9 +287,9 @@ namespace SixLabors.ImageSharp.Formats.WebP return true; } - private int Clip(int v, int M) + private static int Clip(int value, int max) { - return v < 0 ? 0 : v > M ? M : v; + return value < 0 ? 0 : value > max ? max : value; } } From 305d8efba22846ce493738b17c2a8e8cea29a4eb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 31 Jan 2020 15:12:13 +0100 Subject: [PATCH 101/359] Add ParseResiduals --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 68 +++++ src/ImageSharp/Formats/WebP/Vp8LDecoder.cs | 7 +- src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs | 2 +- src/ImageSharp/Formats/WebP/Vp8Proba.cs | 3 + src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs | 4 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 9 +- .../Formats/WebP/WebPLossyDecoder.cs | 280 +++++++++++++++++- 7 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8Decoder.cs diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs new file mode 100644 index 000000000..4efa1b412 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Holds information for decoding a lossy webp image. + /// + internal class Vp8Decoder + { + public Vp8Decoder() + { + this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; + } + + public Vp8FrameHeader FrameHeader { get; set; } + + public Vp8PictureHeader PictureHeader { get; set; } + + public Vp8FilterHeader FilterHeader { get; set; } + + public Vp8SegmentHeader SegmentHeader { get; set; } + + public bool Dither { get; set; } + + /// + /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). + /// + public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } + + public Vp8Proba Probabilities { get; set; } + + /// + /// Gets or sets the width in macroblock units. + /// + public int MbWidth { get; set; } + + /// + /// Gets or sets the height in macroblock units. + /// + public int MbHeight { get; set; } + + /// + /// Gets or sets the current x position in macroblock units. + /// + public int MbX { get; set; } + + /// + /// Gets or sets the current y position in macroblock units. + /// + public int MbY { get; set; } + + /// + /// Gets or sets the parsed reconstruction data. + /// + public Vp8MacroBlockData[] MacroBlockData { get; set; } + + /// + /// Gets or sets contextual macroblock infos. + /// + public Vp8MacroBlock[] MacroBlockInfo { get; set; } + + /// + /// Gets or sets filter strength info. + /// + public Vp8FilterInfo FilterInfo { get; set; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index fa4a570ec..bf8d949b2 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -6,10 +6,15 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Holds information for decoding a lossless image. + /// Holds information for decoding a lossless webp image. /// internal class Vp8LDecoder { + /// + /// Initializes a new instance of the class. + /// + /// The width of the image. + /// The height of the image. public Vp8LDecoder(int width, int height) { this.Width = width; diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs index b867893f5..8ecaa2c83 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { /// - /// Info about a macro block. + /// Contextual macroblock information. /// internal class Vp8MacroBlock { diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs index ac635cf48..a884bd3c7 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs @@ -14,10 +14,13 @@ namespace SixLabors.ImageSharp.Formats.WebP { this.Segments = new uint[MbFeatureTreeProbs]; this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; + this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1]; } public uint[] Segments { get; } public Vp8BandProbas[,] Bands { get; } + + public Vp8BandProbas[,] BandsPtr { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs index 0e7defd4a..37ccc358b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.WebP { public Vp8ProbaArray() { - this.Probabilities = new uint[WebPConstants.NumProbas]; + this.Probabilities = new byte[WebPConstants.NumProbas]; } - public uint[] Probabilities { get; } + public byte[] Probabilities { get; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index dfef15260..fd6fb1d25 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.WebP NumDistanceCodes }; - // VP8 constants from here on + // VP8 constants from here on: public const int NumMbSegments = 4; public const int MaxNumPartitions = 8; @@ -169,6 +169,13 @@ namespace SixLabors.ImageSharp.Formats.WebP 249, 254, 259, 264, 269, 274, 279, 284 }; + // Residual decoding (Paragraph 13.2 / 13.3) + public static readonly byte[] Cat3 = { 173, 148, 140 }; + public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; + public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 }; + public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; + public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + // Paragraph 13 public static readonly byte[,,,] CoeffsUpdateProba = { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index bc4ef5aa9..f5a39c016 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; +using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -77,6 +79,260 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private bool ParseResiduals(Vp8Decoder decoder, Vp8MacroBlock mb) + { + byte tnz, lnz; + uint nonZeroY = 0; + uint nonZeroUv = 0; + int first; + var dst = new short[384]; + var dstOffset = 0; + Vp8MacroBlockData block = decoder.MacroBlockData[decoder.MbX]; + Vp8QuantMatrix q = decoder.DeQuantMatrices[block.Segment]; + Vp8BandProbas[,] bands = decoder.Probabilities.BandsPtr; + Vp8BandProbas[] acProba; + Vp8MacroBlock leftMb = null; // TODO: this value needs to be set + + if (!block.IsI4x4) + { + // Parse DC + var dc = new short[16]; + int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); + int nz = this.GetCoeffs(GetBandsRow(bands, 1), ctx, q.Y2Mat, 0, dc); + mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); + if (nz > 0) + { + // More than just the DC -> perform the full transform. + this.TransformWht(dc, dst); + } + else + { + int dc0 = (dc[0] + 3) >> 3; + for (int i = 0; i < 16 * 16; i += 16) + { + dst[i] = (short)dc0; + } + } + + first = 1; + acProba = GetBandsRow(bands, 1); + } + else + { + first = 0; + acProba = GetBandsRow(bands, 3); + } + + tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); + lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); + + for (int y = 0; y < 4; ++y) + { + int l = lnz & 1; + uint nzCoeffs = 0; + for (int x = 0; x < 4; ++x) + { + int ctx = l + (tnz & 1); + int nz = this.GetCoeffs(acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); + l = (nz > first) ? 1 : 0; + tnz = (byte)((tnz >> 1) | (l << 7)); + nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); + dstOffset += 16; + } + + tnz >>= 4; + lnz = (byte)((lnz >> 1) | (l << 7)); + nonZeroY = (nonZeroY << 8) | nzCoeffs; + } + + uint outTnz = tnz; + uint outLnz = (uint)(lnz >> 4); + + for (int ch = 0; ch < 4; ch += 2) + { + uint nzCoeffs = 0; + tnz = (byte)(mb.NoneZeroAcDcCoeffs >> (4 + ch)); + lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> (4 + ch)); + for (int y = 0; y < 2; ++y) + { + int l = lnz & 1; + for (int x = 0; x < 2; ++x) + { + int ctx = l + (tnz & 1); + int nz = this.GetCoeffs(GetBandsRow(bands, 2), ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); + l = (nz > 0) ? 1 : 0; + tnz = (byte)((tnz >> 1) | (l << 3)); + nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0); + dstOffset += 16; + } + + tnz >>= 2; + lnz = (byte)((lnz >> 1) | (l << 5)); + } + + // Note: we don't really need the per-4x4 details for U/V blocks. + nonZeroUv |= nzCoeffs << (4 * ch); + outTnz |= (uint)((tnz << 4) << ch); + outLnz |= (uint)((lnz & 0xf0) << ch); + } + + mb.NoneZeroAcDcCoeffs = outTnz; + leftMb.NoneZeroAcDcCoeffs = outLnz; + + block.NonZeroY = nonZeroY; + block.NonZeroUv = nonZeroUv; + + // We look at the mode-code of each block and check if some blocks have less + // than three non-zero coeffs (code < 2). This is to avoid dithering flat and + // empty blocks. + block.Dither = (byte)((nonZeroUv & 0xaaaa) > 0 ? 0 : q.Dither); + + return (nonZeroY | nonZeroUv) is 0; + } + + private int GetCoeffs(Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) + { + // Returns the position of the last non - zero coeff plus one. + Vp8ProbaArray p = prob[n].Probabilities[ctx]; + for (; n < 16; ++n) + { + if (this.bitReader.GetBit((int)p.Probabilities[0]) is 0) + { + // Previous coeff was last non - zero coeff. + return n; + } + + // Sequence of zero coeffs. + while (this.bitReader.GetBit((int)p.Probabilities[1]) is 0) + { + p = prob[++n].Probabilities[0]; + if (n is 16) + { + return 16; + } + } + + // Non zero coeffs. + int v; + if (this.bitReader.GetBit((int)p.Probabilities[2]) is 0) + { + v = 1; + p = prob[n + 1].Probabilities[1]; + } + else + { + v = this.GetLargeValue(p.Probabilities); + p = prob[n + 1].Probabilities[2]; + } + + int idx = n > 0 ? 1 : 0; + coeffs[WebPConstants.Zigzag[n]] = (short)(this.bitReader.ReadSignedValue(v) * dq[idx]); + } + + return 16; + } + + private int GetLargeValue(byte[] p) + { + // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 + int v; + if (this.bitReader.GetBit(p[3]) is 0) + { + if (this.bitReader.GetBit(p[4]) is 0) + { + v = 2; + } + else + { + v = 3 + this.bitReader.GetBit(p[5]); + } + } + else + { + if (this.bitReader.GetBit(p[6]) is 0) + { + if (this.bitReader.GetBit(p[7]) is 0) + { + v = 5 + this.bitReader.GetBit(159); + } + else + { + v = 7 + (2 * this.bitReader.GetBit(165)); + v += this.bitReader.GetBit(145); + } + } + else + { + int bit1 = this.bitReader.GetBit(p[8]); + int bit0 = this.bitReader.GetBit(p[9] + bit1); + int cat = (2 * bit1) + bit0; + v = 0; + byte[] tab = null; + switch (cat) + { + case 0: + tab = WebPConstants.Cat3; + break; + case 1: + tab = WebPConstants.Cat4; + break; + case 2: + tab = WebPConstants.Cat5; + break; + case 3: + tab = WebPConstants.Cat6; + break; + default: + WebPThrowHelper.ThrowImageFormatException("VP8 parsing error"); + break; + } + + for (int i = 0; i < tab.Length; i++) + { + v += v + this.bitReader.GetBit(tab[i]); + } + + v += 3 + (8 << cat); + } + } + + return v; + } + + /// + /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. + /// + private void TransformWht(short[] input, short[] output) + { + var tmp = new int[16]; + for (int i = 0; i < 4; ++i) + { + int a0 = input[0 + i] + input[12 + i]; + int a1 = input[4 + i] + input[8 + i]; + int a2 = input[4 + i] - input[8 + i]; + int a3 = input[0 + i] - input[12 + i]; + tmp[0 + i] = a0 + a1; + tmp[8 + i] = a0 - a1; + tmp[4 + i] = a3 + a2; + tmp[12 + i] = a3 - a2; + } + + int outputOffset = 0; + for (int i = 0; i < 4; ++i) + { + int dc = tmp[0 + (i * 4)] + 3; + int a0 = dc + tmp[3 + (i * 4)]; + int a1 = tmp[1 + (i * 4)] + tmp[2 + (i * 4)]; + int a2 = tmp[1 + (i * 4)] - tmp[2 + (i * 4)]; + int a3 = dc - tmp[3 + (i * 4)]; + output[outputOffset + 0] = (short)((a0 + a1) >> 3); + output[outputOffset + 16] = (short)((a3 + a2) >> 3); + output[outputOffset + 32] = (short)((a0 - a1) >> 3); + output[outputOffset + 48] = (short)((a3 - a2) >> 3); + outputOffset += 64; + } + } + private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) { var vp8SegmentHeader = new Vp8SegmentHeader @@ -109,8 +365,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int s = 0; s < proba.Segments.Length; ++s) { - hasValue = bitReader.ReadBool(); - proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; + hasValue = this.bitReader.ReadBool(); + proba.Segments[s] = hasValue ? this.bitReader.ReadValue(8) : 255; } } } @@ -235,15 +491,14 @@ namespace SixLabors.ImageSharp.Formats.WebP int v = this.bitReader.GetBit(prob) == 0 ? (int)this.bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v; + proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; } } } for (int b = 0; b < 16 + 1; ++b) { - // TODO: This needs to be reviewed and fixed. - // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + proba.BandsPtr[t, b] = proba.Bands[t, WebPConstants.Bands[b]]; } } @@ -251,7 +506,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool useSkipProba = this.bitReader.ReadBool(); if (useSkipProba) { - var skipP = this.bitReader.ReadValue(8); + uint skipP = this.bitReader.ReadValue(8); } } @@ -287,6 +542,19 @@ namespace SixLabors.ImageSharp.Formats.WebP return true; } + private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) + { + nzCoeffs <<= 2; + nzCoeffs |= (uint)((nz > 3) ? 3 : (nz > 1) ? 2 : dcNz); + return nzCoeffs; + } + + private static Vp8BandProbas[] GetBandsRow(Vp8BandProbas[,] bands, int rowIdx) + { + Vp8BandProbas[] bandsRow = Enumerable.Range(0, bands.GetLength(1)).Select(x => bands[rowIdx, x]).ToArray(); + return bandsRow; + } + private static int Clip(int value, int max) { return value < 0 ? 0 : value > max ? max : value; From 834deae68cb9a952a8030dda9fb25bc90a006813 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 31 Jan 2020 20:23:07 +0100 Subject: [PATCH 102/359] Implement VP8 decoder init --- src/ImageSharp/Formats/WebP/LoopFilter.cs | 6 +- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 153 ++++++++++++++++++ src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs | 6 +- src/ImageSharp/Formats/WebP/Vp8Io.cs | 10 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 7 + .../Formats/WebP/WebPLossyDecoder.cs | 27 ++++ 6 files changed, 202 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs index acf7a1114..2f6766108 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -5,8 +5,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal enum LoopFilter { - Complex, - Simple, - None + None = 0, + Simple = 1, + Complex = 2, } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 4efa1b412..303fd1e46 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -11,6 +11,129 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8Decoder() { this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; + this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; + } + + public void Init(Vp8Io io) + { + int extraPixels = WebPConstants.FilterExtraRows[(int)this.Filter]; + if (this.Filter is LoopFilter.Complex) + { + // For complex filter, we need to preserve the dependency chain. + this.TopLeftMbX = 0; + this.TopLeftMbY = 0; + } + else + { + // For simple filter, we can filter only the cropped region. We include 'extraPixels' on + // the other side of the boundary, since vertical or horizontal filtering of the previous + // macroblock can modify some abutting pixels. + this.TopLeftMbX = (io.CropLeft - extraPixels) >> 4; + this.TopLeftMbY = (io.CropTop - extraPixels) >> 4; + if (this.TopLeftMbX < 0) + { + this.TopLeftMbX = 0; + } + + if (this.TopLeftMbY < 0) + { + this.TopLeftMbY = 0; + } + } + + // We need some 'extra' pixels on the right/bottom. + this.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; + this.BotomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; + if (this.BotomRightMbX > this.MbWidth) + { + this.BotomRightMbX = this.MbWidth; + } + + if (this.BottomRightMbY > this.MbHeight) + { + this.BottomRightMbY = this.MbHeight; + } + + this.PrecomputeFilterStrengths(); + } + + private void PrecomputeFilterStrengths() + { + if (this.Filter is LoopFilter.None) + { + return; + } + + Vp8FilterHeader hdr = this.FilterHeader; + for (int s = 0; s < WebPConstants.NumMbSegments; ++s) + { + int baseLevel; + + // First, compute the initial level + if (this.SegmentHeader.UseSegment) + { + baseLevel = this.SegmentHeader.FilterStrength[s]; + if (!this.SegmentHeader.Delta) + { + baseLevel += hdr.Level; + } + } + else + { + baseLevel = hdr.Level; + } + + for (int i4x4 = 0; i4x4 <= 1; ++i4x4) + { + Vp8FilterInfo info = this.FilterStrength[s, i4x4]; + int level = baseLevel; + if (hdr.UseLfDelta) + { + level += hdr.RefLfDelta[0]; + if (i4x4 > 0) + { + level += hdr.ModeLfDelta[0]; + } + } + + level = (level < 0) ? 0 : (level > 63) ? 63 : level; + if (level > 0) + { + int iLevel = level; + if (hdr.Sharpness > 0) + { + if (hdr.Sharpness > 4) + { + iLevel >>= 2; + } + else + { + iLevel >>= 1; + } + + if (iLevel > 9 - hdr.Sharpness) + { + iLevel = 9 - hdr.Sharpness; + } + } + + if (iLevel < 1) + { + iLevel = 1; + } + + info.InnerLevel = (byte)iLevel; + info.Limit = (byte)((2 * level) + iLevel); + info.HighEdgeVarianceThreshold = (byte)((level >= 40) ? 2 : (level >= 15) ? 1 : 0); + } + else + { + info.Limit = 0; // no filtering + } + + info.InnerLevel = (byte)i4x4; + } + } } public Vp8FrameHeader FrameHeader { get; set; } @@ -28,6 +151,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } + public bool UseSkipProba { get; set; } + + public byte SkipProbability { get; set; } + public Vp8Proba Probabilities { get; set; } /// @@ -40,6 +167,26 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public int MbHeight { get; set; } + /// + /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbX { get; set; } + + /// + /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbY { get; set; } + + /// + /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. + /// + public int BotomRightMbX { get; set; } + + /// + /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. + /// + public int BottomRightMbY { get; set; } + /// /// Gets or sets the current x position in macroblock units. /// @@ -60,6 +207,12 @@ namespace SixLabors.ImageSharp.Formats.WebP ///
public Vp8MacroBlock[] MacroBlockInfo { get; set; } + public int MacroBlockPos { get; set; } + + public LoopFilter Filter { get; set; } + + public Vp8FilterInfo[,] FilterStrength { get; } + /// /// Gets or sets filter strength info. /// diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs index 0cfaf1e04..4204386bc 100644 --- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs @@ -11,12 +11,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the filter limit in [3..189], or 0 if no filtering. /// - public sbyte Limit { get; set; } + public byte Limit { get; set; } /// /// Gets or sets the inner limit in [1..63]. /// - public sbyte InnerLevel { get; set; } + public byte InnerLevel { get; set; } /// /// Gets or sets a value indicating whether to do inner filtering. @@ -26,6 +26,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the high edge variance threshold in [0..2]. /// - public sbyte HighEdgeVarianceThreshold { get; set; } + public byte HighEdgeVarianceThreshold { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index f1ab5c749..dfcf73a48 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,6 +62,14 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public int UvStride { get; set; } + public int CropLeft { get; set; } + + public int CropRight { get; set; } + + public int CropTop { get; set; } + + public int CropBottom { get; set; } + /// /// User data /// diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index fd6fb1d25..5db5a0bbc 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -121,6 +121,13 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int NumCtx = 3; + /// + /// How many extra lines are needed on the MB boundary for caching, given a filtering level. + /// Simple filter: up to 2 luma samples are read and 1 is written. + /// Complex filter: up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). + /// + public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; + // Paragraph 9.9 public static readonly int[] Bands = { diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f5a39c016..364b4b3be 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -79,6 +79,33 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private void DecodeMacroBlock(Vp8Decoder dec) + { + Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockPos - 1]; // TODO: not sure if this - 1 is correct here + Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockPos + dec.MbX]; + Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockPos + dec.MbX]; + int skip = dec.UseSkipProba ? blockData.Skip : 0; + + if (skip is 0) + { + this.ParseResiduals(dec, macroBlock); + } + else + { + left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0; + if (blockData.IsI4x4) + { + left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0; + } + + blockData.NonZeroY = 0; + blockData.NonZeroUv = 0; + blockData.Dither = 0; + } + + // TODO: store filter info + } + private bool ParseResiduals(Vp8Decoder decoder, Vp8MacroBlock mb) { byte tnz, lnz; From 4eb7914327b4d969d41e2ce9bfc44531182200d8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 3 Feb 2020 06:42:26 +0100 Subject: [PATCH 103/359] Start implementing ParseFrame, ParseIntraMode --- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 200 ++++++++++-------- src/ImageSharp/Formats/WebP/Vp8Io.cs | 8 + .../Formats/WebP/Vp8MacroBlockData.cs | 7 +- src/ImageSharp/Formats/WebP/WebPConstants.cs | 124 +++++++++++ .../Formats/WebP/WebPDecoderCore.cs | 2 +- .../Formats/WebP/WebPLossyDecoder.cs | 119 ++++++++++- 6 files changed, 369 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 303fd1e46..5643b798b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -8,14 +8,128 @@ namespace SixLabors.ImageSharp.Formats.WebP ///

+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⪚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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/WebP/lossless_color_transform.pgm b/tests/Images/Input/WebP/lossless_color_transform.pgm new file mode 100644 index 000000000..663f633ba --- /dev/null +++ b/tests/Images/Input/WebP/lossless_color_transform.pgm @@ -0,0 +1,4 @@ +P5 +512 768 +255 +vvvuuuuttttssssrrrrrqqqppppoooooonnnmmmmlllllkkjjjjiiiiiiihhhggggffffeeeeeddcccbbbbbbaaaa````____^^^^]]]]\\\\\[\[[ZZZZYYYXXXXWWWWWVVVVUUUUTTTTTSSSSSRQRQQPPPPPPPOONNNNNNMMLLLLKKKKKJJJJJJIIHHHHHGGGFFFFFEEEEDDDDDCCCBBBBAA@@A@@@????>>>>>====<<<<;;::::::9999888877776666554454444333222221110100///.....----,,,,+++++*+*)*))))((('''&&&%%%%%%$%$$######""""!!!! wvvvuuuutttttsssssrrqqqqpppoooooooonnnmmmmllllkkjkjijiiiiiihhhgggffffeeededddccccbbbbbbaaa```_`___^^]^]]]\\\\\\\[[[Z[ZZYYYYXXXXXWWWVVVVUUUUTTTTTTSRSRQQQQQPPPPPPPOOOOONMMMMLLLLLKKKKJJJJIIIIHHHHGHGGFFFFFEEEEDDDDCCCCCBBBAAA@@A@@@???>>>>>=====<<<;;;;::::9988888887777666555455443322222221111000/////....----,,,++++++***)))())(('''&&&&%%%%%%$$$######"""!!!! wvvvvuuuuuuutsstsrrrrrqqqqppppoooonnnnnmmmmlllkkkjkjjjiiiiiihhhhgggfffefeeeddddccccbbbbaaaaa``_____^^^^]]]]]\\\[\[[[[[ZZYYYYXXXWWWWWVVVVUVVUTTTTTSSSRRRRRQQQPPPPPOOOOONNNMMMMLLLLKKKKKJJJIIIIIIHHHHGGGFFFEEEEDDDDDCCCBBBBAAAAA@@@@???>>>>>>===<<<<<;;;::::999988888877766666555544343332222211110000////./.-----,,+,+++++***)*)))(((''&&'&&%%%%%%$$$$$###""""!!! vwvvvvuuuuutttttsssrrrrqqqpppppoooonnnnmmmmmllllljkjjjjiiiiiihhhgggfgfeffeededddccccbbbbaaaa`a```____^^]]]]]\\\\\[[[[[ZYYZYYXYXXXXWWVVVVVVVUUUTTTTSSSRRRRRQQQQPPPPOOOONNNMMMMLMLLLLKKJJJJJJIIIHIHHHGGGGFFFFFEEDDDDDDDCCCBBBAAAA@@@@@??>?>>>=====<<<;;;;:::::9999888877776666555554433332322221111000/////...----,,,,,++++*****))))(((('''&&&&%%%%%$$$#$###"""""!!! wwvvvvvuuuuuttttsssrrrrrqqqpppppoooonnnnmmmlmllkklkkjjjiiiiiiihhhgggfgffefeedddddcccbbbbbbaaaa`````__^^^]]]]]\\\\\\[[[ZZZYYYYYXXXXXWWVVVVVUUUTTUTTSTSSSSRRRQQQQPPPPOOONONNNMMMMMLLLKKKKJJJJJIIIIHHHHHGGFFFFEFEEEDDDDDCCCBBBBBAAAA@@@@???>>>>>>>==<<<;;;;;:::::999888887776665655544444333222222111000//////..-.---,,,+++++****)*))((((('''&&&&&%%%$%%$$$###"""!"!!!! wwwwvvvuuuuuuuttssssrrrrqqqqqpppoooooonnnmmmmlllkkkkkjjjjiiiiiiihhgggfffffefeeddddccbbbbbbbaaa````____^^^^]]]]\\\\\[[[[[ZZZYZYYXXXXWWWVVVVVVUUUTTTTTTSSSRRQQQQQQPPPPOOONONNNNMMMLLLLKKKKJJJJIJIIIHHHHGGGGGFFEEEDDDDDDDCCCBBBBAAAA@@@@@????>>>>>===<<<<<;::::::99998888777766666555544443332222211100000///....-----,,,,++++*+***))))(('('''&&&&%%%%%$%$$$####"""!!! ! xwwwwvvvvuuuuutttsssssrrrrrqqqqpopooooonnnmmmmmlllllkjkjjiiiiiihhhhgggfffffeedeedddcccbbbbbbaaa`a````__^^^^]]]]]\\\\[[[[ZZZZYYYYXXXXWWWWVVVVVVUUTUTSTTSSRRRRRQQQQPPPPPOOOONNMNMMMLLLLKKKJJJJJJIIIIIHHHGHGFFFFEEEEEDDDDCCCCCBBBAAA@@@@@????>>>>>=>==<<<<;;;;::::99988888877776666555444433332222221110110///.....---,,,,++++++*+*)))()(((('''&&&&%%%%%%$$$$##"#"""!!!! xxwwwwvvvvvuuuututtssssrrrqqqqqqoppoooooonnnmmmmlllllkkjjjjiiiiiihhhgggggfefeeeedddcccccbbbbaaaa````_____^^]^]]]\\\\\[[[ZZZZZYYYYXXXXWWWWVVVVUVUUTTTSTSSSSRRRQRQQPPPPPOOOONNNNMMMLLLLLKKKKKJJJJIIIIHHHGGHGFFFFFEEEEDDDDDCCCBBBBBAAA@@@@????>>>>>===<<<<<;;;;::::999988888777766665555543433322222211010000////....---,,,,+++++*****)())(('(''&&&&%%%%%$$$$$##"#""!"!! yxwwwwvvvvvvuuuutttttssrrrrqqqpqppppooooonnnmmmmlllllkkjjjjjjiiiiiihhghgggffeeeeeddddcccbbbbbbaaaa````___^^^^^]]]\\\\\\[[[[ZZZZYYYXXXWWWWWVVVVUUUUTUTTTSSSSRRRQQQQQPPPPOOOONNNNMMMMMLLLLKKKKJJJJJIIIHHHHHGGGFFFFEEEEDDDDDDCCCBBBAAAAAA@@@???>>>>>>==<=<<<<;;::::999988888887766665555454444332222221110000///./...-----,,,+++++****)))()((('''&'&&%%%%%%$$$#####"""!!!!! yxxwwwwvwvvvuuuuuuttssssssrrrqqqqpppooooooonnnmmmlmlklkkkkjjijiiiihhhhhgggggffeeeedddddccbbbbbaaaaaa`_`____^^^^]]]\\\\\[[[[[ZZYZYYYXXXXWWWWVVVVUUUUUTTTSTSSSRRRQQQPPPPPPPOOOONNNNMMMLLLLKKKKJJJJJIIIHHHHGHGFGFFFFEEEEDDDDDCCCCBBBBAAA@@@@@???>>>>>====<<<;<;;;:::::99988887777776665554444433332222121110000//./...----,,,,+++++*****)))(((''''&&&&&%%%%$%$$$####"""!!!!  yxxxxwwwvwvvvvuuuuuttsssssrrrqrqqppppoooooonnnnnmmlllllkkkjjjjiiiiihhhhhgggffffeedddddddcccbbbbbaaa```_``___^^^]]]]\\\\\\\[[Z[ZZYYYYXXXWXWWWVVVVVUUUUTTTTSSSRRRRQQQQQPPPPOOOONNNNMMMMMLLKLKKKJJJJJIJIHHHHHHGFGFFFFEEEEDDDDDCCCBBCBAAA@@@@@????>>>>>====<<<<<<:;;:9:99988888877777666555554443332222221111000////....----,,,,,+++++****)))((((''''&&&&%%%%%$$$$####""""!!! yxxxxxxwwwwwvvuuuuutttsssssrrrrrqqqqpopoooonnnnnmmmllllklkkjjjjiiiiihhhhggggfgffeeeedddcdcccbbbbbaaaa````___^^^^]]]]]\\\\\[[[ZZZZYYYYYXXWXWWWVVVVVUUUTTTTSTTSSSRRRQQQQPPPPOOOOONNNNMMMMLLLLKKKJJJJJJIIIHHHHGGGGFFFEEEEEDDDDCCCCCBBBBAAA@A@@@???>>>>>====<<<<;;;;:::9999998888777666655545444433332222211110000////....---,,,++++++****))())((''''&'&&%%%%%%$$$$###"#"!!!! ! yyyyyxxxwwvvvvuuuuuutttsssssrrrqrqqpppppoooononnnnmmmlllklkjjjjjjiiiihhhhhgggfffefeeeddddcccbbbbbbaaaa````___^^^]]]]]]\\\\[[[[[ZZZZZYYXXXXWWWWVVVVVUUUUTTSTSSSRRRRRRQQQPPPPPOOONNNNMMMMLLLKKLKKJJJJJIIIHHHHGGGFFFFFFFEEEDDDDDCCCCBBBBBAAA@@@@??>>>>>>>===<<<<<;;;:::9999988888877766666554444443332222221111000///...----,,,,+++++++***))))(((((''&&&&&%%%%%$$$######""!"!!!  zyyyyxxxxxwwwvvvuuuuuutttsssrsrrrqqqqpppoooooonnnmmmmmllllkkkkjjjjiiiiiihhgggggfffeeeedddddccbbbbbbaaaa```_____^^^^]]]\\\\\\[[[[[ZYYYYYXXXXXWWWVVVVVUUUUUTTSSSSSRRRQRQPQPPPPPOOOONNNNMMMMLLLKKKKKJJJJIIIIIHHHGGGFFFFFFEEEEDDDDCCCBCBBBAAA@@@@@????>>>>>====<<<<;;::;:9:9998888887777665555544433322222212110000////...-.--,,,,+++++*+**)*))(((('''''&&&%%%%%$$$$$####""!!!!! zzyyyyxxxxwwwwvvvvuuuuuttttsssrrrrqqqqqpppoooooonnnmmmlmllklkkkjjjiiiiihhhhgggggffffeeeeddccccbbbbbbbaaa```_____^^]^^]]]\\\\\[[[[ZZZYYYYXXXXXWWWWVVVVVUUUUTTSSSSRRRRQQQPQPPPPPOOONNNNNNMMMLLLLKKKKJJJJIIIIHHHHHHGGFFFFEEEEDDDDDCCCBBBBBAAAA@@@????>>>>>>====<<<;;;;:::::9988888878777666655544333333222212210000000.....-----,,,++++*++***)))(((''''&&&&%%%%%%%$$$###"#"""!!! zzzyyyyyxxwwwwwvvvuuuuutttttsssrrrrrqqqqppppooooonnmmmmmlmlkkkjjjjjiiiiiihhhhggggfffeeededddcccbbbbbabaaaa`_`___^^^^^]]]]\\\\[[[[ZZZZYYYYYXXWXWWVWVVVVUUUTTTTSSSSSRRRRQQQPPPPPOOOOONNNNMMMMLKLLKKJKJJJJIIIIHHHGHHGFFFFFEEEEDDDDDCCCBBBBABAAA@@@@???>>>>>>====<<;;;;:;:::9989988887777666565555443433322222111110000////..---,,,,,+++++*+*))))((((''''&'&&%%%%%$%$$####""""""!! zzzzzyyxyxxxwwwvwvvvuuuutttttsssrrrqrqqqqppooooooonnnmmmlllllkkjjkjjjiiiiihhhhgggfffeffeeeddddccbbbbbaaaa`a````____^^^]]]]\\\\[[[[[ZZZZYYYYYXXXWWWVVVVVVUUUTTTTSSSRRRRQRQQPPPPPPOOONNNNMMMMLMLLLKKKKJJJJIIIIHHHHGGGGGFFEEEEEDDDDDDCCCBBBBAAA@@@?@????>>>>>===<<<<<<;;;:::9998988887777766666554443333322222211110000///....----,,,++++++**)*)))))(''''''&%&%%%%$$$$$###""""!!! {zzzzyyyyxxxxxwvvvvvvuuuututttssssrrrqqqqpppoooooooonnnmmmllkllkkkkjjjiiiiiihhggggffffeeeddeddcccccbbbbbaaaa```____^^^]]]]]\\\\\[[[[ZZYYYYXYYXXXWWVVVVVVVUUTTTTTTSSSSRRRQQQQPPPPOOOONNNNNMNMMMLLKKKKJJJJJJIIIHHHGHGGGGGEFFEEEEDDDDCCCCCBBBAA@@A@@????>>>>>>====<=<<;;;:;:::99988888887666665554444333322222221100000/////..-----,,,,+++++*+*))))((((('''&&&%%%%%$$$$$###"#"""!!!! {{zzzzyyyyxxxwwwwwvvvuuuuutttttssssrrrrqqpqqpopooooonnnmmmllllklkkjjjjiiiiiihhhgggggffeefeeeddddccbbbbbbbaaa`a`____^^^^^]]\]\\\\\\[[[[ZZYYYXYXXXXWWWVVVVVUUUUUTTTTTSSRRRRQQQQPPPPPOOOONNNNMMMLLLLKKKKKJJJJJIIIIHHHHGGGGGFFFEEEEDDDDCCCBBCBBAAA@@@@@@???>>>>>====<<<;;;;;::::99998888777766656555454333333222211101000//./..-----,,,,++++++*****))(((('''''&&&%%%%%%$$$#####""!!!! ! {{{zzzyyyyyxxxxxwvwvvvuuuuuutttsssssrrrrqqqqppoooooonnnnmmmlmllkkkkkjjjiiiiiiihhhggfffffeeeedddddcccbbbbbbbaaa```____^^^]]]]]\\\\\[[[[ZZZYZYYYXXXWWWWVVVVVVVVUTTTTSSSSRRRRRQQPPPPPPOOOONONMMMMLLLLKKKKJJJJJIIIIIHHHGGGGGFFFEEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>====<=<<<;;::::::99888888877666665555544433322222211110000//../...----,,,++++++****)))(((''''''&&&%%%%%$$$$$##""""!!!!!  {{{{{zzzzyyxxxxwwwvwvvvuuuuuutttttsrrrrrqqqqpppoooooonnnnmmmlllllkkkkkjjiiiiiihhhghggffffeeededdddcccbbbbbaaaaa```_`__^^^^^]]\\\\\\\[[Z[[ZZZYYXYXXXXWVWVVVVVUUTTTTTSSSSSRRRQQQPPPPPPOOONNONNNMMMLLLKKKKKJJJJJIIIIHHHHHGGGGFFFEEEDDDDDDCCBBBBAAAAA@@@@????>>>>>====<<<<;;;;:::9:998888887877766555455444333222222111000/0//...------,,+,+++++***)))))((((''&&&&&%%%%%$$$$####"""!!!! ! {{{{{zzzzyyyyxxwxwxwwvvvuuuuuuutttssssrrrrqqqpqppoooooonnnnnmmmllklkkjjjjiiiiiihhhhgggggffffededdddcccbbbbbbaa`a````___^^^^^]]]\\\\\[[[[[ZZYYYYYXXXWWWWWVVVVVVUUUTTSSSSSSRRQQQQQPPPPPPOOONNNNMNLMLLLKKKKKJJJJJIIIIHHHHHGGFFFFFEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>>=====<<;<;;:;:::9999888878777666655554433333222221111000/0////...---,,,+,+++++****))())((('''&&%%%%%%%$$$#$###""""!!!! |{{{{{{zzzyyyyyxxxwwwwvvvvuuuuutttstsssrrrrrqqqppppoooonnnnnnmlmllllkkjjjjjiiiihhhhhgggffffefeedddddccccbbbbaaa`a``______^^]^]]]]\\\\[[[[ZZZZYZYXXXXXWWWVVVVVUUUUUUTTSSSSRSQQRQQPPPPPPOOOONNNNMMMMLLLLKKJKJJJJIJIIIHHHGGGGGGFFFEEEEDDDDDCCCBBBBAAAA@@@@???>>>>>>====<<<<;;;:::::999888887777766666555444333332222211110000////..-----,,,+++++*****))(((((('''&&&&%%%%%%$$$$###"#"""!! ! ||{{{{{zzzzyzyyxxxxwwwwvvvvuuuuutttstssrrrqqqqqqpppooooonnnnnmmmmlllkkkkkjjjiiiihihhhgggfgffefeeeddddcccbbbbbbaa`a``_`___^^^^]]]]\\\\[[[[[ZZZYYYYXXXXXWWWWVVVVVUUUUTTTTSSSRSRRQQQQQPPPPPOOONNNNNMMMLLLLLKJKJJJJJIJIIIHHHGGGGFFFFEEEEDDDDDCCBBCBBBAAA@@@@@????>>>>>===<<<<;;::;:::9998888877777665555455443332222221110000///////.----,,,,,+++++***)))))((('''''&&&%%%%%$$$$###"""""!!!! ||{|{{{{{zzyyyyyyxxxwwwwvvvvvuuuttttssssrrrqrqqpqqpppoooonnnnnmmmlllkkkkkkjjjiiiiihhhhghgfffffeeededdccccbbbbbbaaa`````___^^^^]]]]\\\\\[[[[[ZZZZYYYXXXXWWWWVVVVUVUUTTTSTTSSSRRRRQQQPPPPPOOOONNNNNMMMLLLKKKKJJJJJIIIIIHHHHHGGFGFFFEEEEDDDDDCCCCBBBAAA@@@@@????>>>>>>====<<;;;;::::99988888877776666665554433333222221111000////......---,,,++++++****))))((((''''&&%%%%%%$$$$####"""!!!! }||||{{{{{zzzyyyxxxxxwwwwwvvvuuuuuuttttssssrrqrqqqpppooooonnnnmmmmlllllkjkjjjjiiiiihhhgggggffffefeedddccccbbbbbbba`aa`____^^^^^]]]]\\\\\\[[[[ZZZYYYYXXXXWWWWVVVVVUUUUTTTSSSSRRRQQQQQQPPPPOOOOONNNNMMMLLLLKKKKJJJJIJIIHHHHGGGGGFFFFEEEDDDDDDCCCBBBBBAA@@@@??????>>>>=>==<<<;;;;;:::9:9988888777767666555544433332222211111000////...-.-,-,,,,++++*****))))(((''''&&&&%%%%%%$$$$####""!"!! }|||||{{{{{zzzzyyyxxxxwwwwvvvvuuuuutttsssssrrrqrqqqqpppooooonnnnnmlmlllkkkkjjjjjiiiihhhhggggffffeeeedddcccccbbbbbaaa````_____^]]]]]]\\\\\[\[[ZZZZYYYYXXXXWWWWVVVVUUUUUUTTTSSSRRRRQQPQPPPPPPOOONNNMNMMLLLLKKKKKJJJJJJIIIHHHHGGGFFFFEEEEDDDDDCCCCBBBBAAAAA@@@????>>>>>>====<<<;;;;;::999998888887776666555544433333222211101000////....---,-,,,+++++******))(((('''&&&&&%%%%%$$#$##"""!""!!! }}|||||{{{{{zzzzyyyyxxwwxwwwvvvuuuuuttttsssrsrrqqqqqppppoooonnnnnmmmlllllkkkjjjiiiiiihhhghggggffefeedddddccccbbbbbaaaa```_`__^^^^^]]]\\\\\\[[[Z[ZZYYYYXXXXWWWWVVVVVVUUUTTTTTSSRRRRQRQQPPPPPOOOONNNNMMMMMLLLLKKJKJJJJIIIIHHHGGGGFFFFFEEEDDDDDDDCCCCBBAAAAAA@??????>>>>====<<<<<;;:::::999988888777776655555544433232222111110000//.....-----,+,+++++****)))((((('''&&&&&%%%%%%$$$##"""""!! ~}}|||||{{{{{zzzzzyxxxxwwwwvvvvvvuuuuutttsssrrrrqqqqqppppooooononmmmmlllkkkjjkjjjiiiiihhhggggfgfffeeeedddcccccbbbbaabaa````____^]^]]]]\\\\\[[[[ZZZYYZXYXXXXWWWVVVVVVUUUTTTTSSSSSSRRRQQQPPPPPPOOOONNMMNMMMLLLKKKKJJJJIIIIIHHHHGGGGFFFFEEEEDDDDDCCCCBBBAAAA@@@@@???>>>>>====<<<<<;;;;::9:9988888877776666655544343322222221110000/////...---,,,+,+++++****)))(((('(''&&&&&%%%%%$$#####""""!!!!! ~}}}|}||{{{{{{zzzzyyyyyxxwwwwwvvuvuuuututtsssssrrqrqqqppppooooonnnnmmmlllllkkkjjjiiiiiihhhghggggffeeeeeddcddcccbbbbbaaaa````___^^^^^]]]\\\\\\[[[ZZZZYYYYYXXXXWWWVVVVVVUUUTTTTSSSRRRRRQQQPPPPPOOOONNNNNNMMLLLLLKKJJJJJJIIIIHHHHHHGGFFEEEEEDDDDDCDCCCBBBAAAAA@@@????>>>>>=====<<<<;;:::::99998888877767666555544433332222221110000////.....---,,,++++++*+*)*))(((((''&&&&&&%%%%%%$$#$###""!"!!! ~}}}}||||{{{{{{zzzzyyyyxxxwxwwwvvvuuuuuuttttsssrrrqqqqpqpppoooonnnnmmmmmlllkkkkkjjjjiiiihihhghggfffefeededdcccccbbbbbaaaa```_`__^^^]]]]]]\\\\\[[[ZZZZYZYXXXXXWWWVWVVVUVUUTTTTTSSSSRRRQQQQQPPPPPOOOONNNMMMMLLLLKKKKKJJJJJIIHHHHHHGFGFFFFEEEDDDDDCCDCCBBBBAAA@@@@????>>>>>====<<<<<<;;:::9999888888777766665555444333322222111010000///....---,-,,,++++++**)*)))(((((''&&&&%%%%%%$$######""""!!!! ~}}}}}}|||{{{{{{{zzyyyyxxxxxwwwwvvvvuuuuuttsssssrrrrqqpqqpooooooonnnnmmlmllklkkkkjjijiiiihhhhgggfgffffeeeddddcccbbbbbbbaaaa````__^_^^]]]]\\\\\[[[[Z[ZZZYYYYXXXWWWWWVVVVVUUUUTTSSSSSSRRRQQQQPPPPPPOOONNNMNMMLLLLKKKKJJJJJIIIIHHHHGGFGFFFFEEEEDDDDCCCCCBBBBAAA@@@@?????>>>>====<<<<;;:;:::999999888877777665654544433323222211110000/0////...---,,,,++++******))(((((('''&&&%%%%%$$$$#####"""!!!! ~~}~}}}}|||{{{{{zzzyyyyxxxxxwwvvvvvuuuuuutttssssrrrrqqqqqpppooooonnnmmnmmlllkkkkkjjjiiiiihhhhhggffgfeeeeeddddcccbbbbbbaaa``````___^^^^]]]\\\\\\[[[[[[YZYYYYXXXXWWWVVVVVUVUTTTTTSSSSSRRQQQQQPPPPOOOOONNNNNMMMLLLKKKKJJJJJIIIHIHHHGGGFFFFEEEEEDDDDDCCCBBBBAAAAA@@@?????>>>>====<<<;<<;:::::999998888877776666545444333332222211100000//.....----,,,+++++++*****)))(''('&''&&&%%%%%$$$####""""!! ! ~~~~~}}|||||{{{{{zzzzzyyyxxwxwwvwvvvvuuuuuttttssssrrrrqqqpppppoooonnnmmmmmllllkkkkkjjiiiiihhhhhggfggffffeeddddccccbbbbbaaa`a````___^^^]]]]]\\\\\\[[[[ZYZZYYXXXXXWWWVVVVVVUUTTTTTSTSRRRRRQQQQPPPPOOOONNNMMMMMLMLLLKKJKJJJJJIIIHHHGHGGGFFEEEEEDDDDDDCCCBBBBBBAAA@@@@???>>>>>>===<<<<;;;::::::9988888877777666555544443332222221111000////....----,,,,++++*****)))))(((''''&&&%%%%%$$$$####""""!!! ~~~~}}}}||||{{{{{zzzzzyyyxxxwwwwwvvvuuuuuttttsssssrrrrqqppppoooooonnnnmmmmlllllkjkkjjiiiiiihhhhgggfgfffeeeeddccccccbbbbaaaa`````__^^^^^]]]]\\\\\[[[[ZZYYYYYXXXWXWWWVVVVVUUUUTUTTSSSSRRQRQQQQPPPPPOOONNNNMMMMLLLLLKKKJJJJJIIIIIHHHHHGFFFFFFEEDDDDDDCCCBBBBAAAA@@@@?????>>>>==>==<<;<;;:::::99999888877776666655454343332322212111000///./....---,,,,,++++*****)))))('(''&&&&%%%%%%$$$$##"""""""!  €~~~~~}}}||||{{{{{zzzzyyyyxxxxwwwwvvuvuuuutttstsssrrrrqqqqpppoooooonnnmnmmmlllkkkkkjjjijiiihihhhgggggfeffededcdccccbbbbbaaaa````____^^^^]]\]\\\\[[[[ZZZYZYYXYXXXXWWWWVVVVVUUUTTTSTSSSRRRRQQPQPPPPPOOOOONNNMMMMLLLLKKJKJJJJJIIIIHHHGGGGGFFFEEEEDDDDDDCCCCBBAAAAA@@@???>>>>>>=====<<<;;;:;:::99998888877767666555444443333222211111000/////...---,,,,,++++++**)*))))((''''''&%%%%%%%$$$$##"#"""!!! €~~~}}}}}||||{{{{{zzzyzyyxyxwwxwwwwvvuuuuuuttttsssrrrqqqqqpppppoooonnnnmmmlmlllkkkkkjjijiiiihhhhgggfgfffeedddddcccccbbbaaaa`a``____^^^]]]]]]\\\\\\[[Z[ZYYZYYXXXXWWWWVVVVVUUUTTTTTTTSSRRRRQQQQPPPPOOOOONNNNMMMLMLLLKKKJJJJJJJIIHHHHGGGFFFFEFEEEDDDDDCCCBBBBBAAAA@@@@@??>>>>>>>==<<<;<;;;;::::9989888877776666555444333332222211111000///./....----,,,+++++***))))))(((''''&&&&%%%%%%$$$###"#"""!!! €€~~~~}}}}|||{{{{zzzzzyyxxxxxwwwwwvvvvuuuuttttstsssrrrrqqqpppooooooonnnnmmmlllkkkkjkjjjiiiiiihhhhggfffeeeeededdcccbbbbbbaaaaa```___^^^^]]]]]\\\\\[[[[ZZZZYYYYYXXWWWWVVVVVVUVUTTTSTSSSRRRRQQQPPPPPPOOOONNNMNMMMLLLLKKKKJJJJIIIIHIHHGHGGGFFFFEEDDDDDDCCCBBBBBAAA@A@@????>>>>>=====<<<;<;;::::99998888887776665655544434333222221111000/0////..-.--,,+,+++++++***))))((''''&&&&%%%%%%$$$$####""""!!!  €€€€~~~}}}|||||{{{{{zzzzzyyyxxxxwwwwvvvuuuuuutttssssrrrrqqqpqpppoooooonnnmmmmmllkkkkkjjjjjiiiihhhgghgfgffefeededddcbcbbbbbaaa``````__^_^^^]]]]\\\\\\[[ZZZZZZYYYXXXWWWWVVVVVVVUUTTTTSTSSSRRQQQQPPPPPPOOOONNMMNMMLLLLLKKKKJJJJIIIIIIHHGHFGGFFEFEDEDDDDDDCCCBBBAAA@A@@@@@??>>>>>>====<<<;;;;;;::99989888887776765555554434323222221110010/0///....----,,,++++++***))))(((''('&'&&&%%%%%%$$####"#"""!!!!! €€~~~}}}}||||{{{{{zzzyyyyyxxxxwwwwvvvvuuuuuttttsssrrrrrqqppppopoooonnnmmmmlmlkllkkkjjjjiiiiihhhggggggfffeeeedddcccbcbbbbbaa````____^^^^^]]]\\\\\[\[[[[ZZYYYYXXXXXWWWVVVVVVUUUTTTTTSSRRRRRQQQQPPPPPPOOONNNMNMMLLLLLKKKKJJJJJIIIHIHGGGGGFFFEFEEEEDDDDCCCBCBBBAA@@@@@@@???>>>>>===<<<<<;;;:::9:99988888777767655555544433332222211110000////....----,,,,++++****)))))(('(''&'&&&&%%%%%$$$####""""!! ! €€€€~~~}}}}||||{{{{{zzzzzyyyxxxwwwwwwvvuuuuuuttttssrrrrrrqqqqpooooooooonnmmmlllllkkkkjjjiiiiiihhhhgggffffffeeddcdcccccbbbbbaa````_`___^^]]]]]\\\\\[[[[[[ZZYYYYXXXXXWWWVVVVVVUUUUTTSSTSSSRRQRQQQPPPPPOOOONNNNNMMMLLLLKKKJJJJJJIIIHHHHHGGGGFFFFEEDEDDDDCCCCBBBBAAA@A@@?@???>>>>>>===<<<<;;;:::::9988888887776666655554444333222221111000//////..-----,,,++++++****))))('(('''&&&&%%%%%$$$#####"""""!!!  €€€~~~~}~}}}||||{{{{{zzzyyyxxxwxwwwvvvvvuuuuuuttstssrrrqrqqqqpoppoooonnnnmmmmlllkkkkkjjjjjiiiiihhhgggfgfeeeeeeddcccccbbbbbaaaaa``_____^^^^]]]]\\\\\\[[ZZZZYYYYYXXXWWWWVVVVVVUUUUTTTSSSRSRRRQQQQPPPPPPOONNNNMMMMLMLLLKKJKJJJJJIIIIHHHHGGFFFFFEEEEDDDDDCCCCCBBBAAAAA@@@@???>>>>>=====<<<;;:;:::9:8998888777776665555444443332222221101000////...----,,,,++++++***))))(((((''''&&&%%%%%$$$#####""!!!!!!! €€€€~~}~}}|}||{{{{{{zzzzyyyxxxxxxwvwvvvvuuuuuutttssssrrrrqqqqppopooooonnnnmmmmllkkkkkjjjiiiiiiihhhghgggfffeeedddddcccbbbbbbaaaa````____^^^^]]\\\\\\[[[ZZZZYZZYXXXXWWWWWVVVVVUUUTUTTSSSRRRRQQQQPPPPPPPOOONNNNMNMMLLLKKKKKJJJJJIIIHHHHHGGGGFFFFFEEEDDDDDCCCCBBBAAA@A@@@?????>>>>====<<<;<;;;;::99898888877776666565444444333222222111100//////.-.----,,,,+++++***))))((((('''&&&&&%%%%$$$$$##"#""!"!!!! €€€€~~}}}}|||||{{{{{zzzzyyyyxxwwwwwwwvvvuuuuutttssssrrrqrrqpqppppoooonnnnnmmmmlllllkjjjjjiiiiiihhhggggffffeeeeddddcccbbbbbaaaaa````___^^^^^]]]\\\\\[[[[ZZZZZYYYXXXXWWWWVVVVVUUUTTTTTSSSRRRRQQQQPPPPPPOONNNNMMMMMLLLLKKKKJJJJJIIIHIHHGGGGFFFEEEEEDDDDDDCCCBBBBAAAA@@@@@???>>>>>=>===<;<;;;;:::::999888877776666555555443332222221111000/0///...-.---,,,+++++++***))(()(''('''&&&%%%%%%$$#$$#""""!!!! €€€€€~~~~~~}}}}||||{{{{z{zzyyyxxyxxwwwwvvuuuuuuuutttssssrrrrrqqpppppooooonnnmmmlllllkkkkkjjiiiiiiihhhggggffeeeeeeddcdcccbbbbbaaaa`````__^^^^]^]]\\\\\\\[[[[ZZYZYYYYXXWWWWWVVVVVUUUUTTTTSSSSSRRRQQPPPPPPOOONOONNMMMMMLKLKKKKJJJJJIIIIIHHHGGGGGFFEEEEDDDDDCCCCBBBBBA@A@@@@????>>>>>===<<<<<;;;;;::9999988887777766665544444333222222111100000/./...-----,,++++++***)*))(((('('''&&&&%%%%%%%$######"""!!!!! ‚€€€€~~~~~~}}}|||{{{{{{zzzzyyyxxwxwwwwvvvvuuuuuutttsssrsrqqqqqpppppooooonnnnmmmmlllkljkkjjijiiiihhhhggggfffeeeeeedddccccbbbbbaaa`````___^_^^]]]]\\\\\\[[[[ZZYZYYYYXXXWWWVVVVVVUUUTTTSTTSSRRRRQQQQQPPPPOOOONNNMNMMMLLLLKKKKJJJJIIIIIHHHHHGGGFFFFEEEDDDDDDCCCCBBBAAAA@@?@@????>>>>====<<<<;;::::::99888888777776666555554433332222211111000////....----,,,,++++*+**)))()(((('''&&&&&%%%%%$$$#####""""!!! ‚€€€€~~~}~}}}||||{{{{{zzzyzyxxxwxwwwvvvvvvuuuututsssssrrrqqqqpppopoooooonnmmmmmlllklkjjjjjiiiiiihhhhgggffffeeeeddddccbbbbbbbaaaaa```____^^^]]]\\\\\\[[[[[ZZZZYYXXXXXWWVWVVVVVVUUTTTTTSSSSRRRQQQQPPPPPPOOONNNNMNMMLLLLLKKKJJJJJJIIHHHHHGGGFGFFFEEEEDDDDDCCCBBBABAAA@@@@???>>>>>>====<<<<;;;::::::99988888777676655554543333232222211100000//./..-----,,,,++++*****))))((('(''&&&&%%%%%$%$####"#"""!!!!! ‚‚‚€€€~~~}}}}}||||{{{{zzzzyyyyyxxxxwwwvvvvuuuuuuttttssssrrrqqqqppppoooonnnnnmmmmmlllkkjkjijiiiiihhhhgggggffffeeeddcdcccbbbbbbaaa``````___^^^]]]]\\\\\[[[[[ZZZZYYXYXXWWWWWWVVVVVUUTTTTSSSRRRRRRQQQQPPPPOOONONNNNMMMMLLLKKKJJJJJJJIIIHHHGGGGGGFEFFEEEDDDDCCCCCBBBAAAA@@@?????>>>>>====<<<<;;;:::9::9998888877776665554454343332222211111000////....---,,,,,++++*+**))))((('(''&'&%&%%%%%%$$#$##"#""""!!! ‚‚‚€€€~~~~}}}}||||{{{{{zzzyzyyxxxxwwwwwvuvvuuuuttttsssrsrrrrqqqpppoooooononnmmmllllkkkkjjijiiiiiihhghgggfgfefeeedddccccbbbbbbbaa`a`____^^^^^]]]\\\\\\[\[[[[ZZYYYYYXXXXWVVVVVVUUUUTTTTSSSSRRRRRQQQPPPPPPOOOOONNNMMMMLLLKKKKJJJJJIIIIHHHGGGGFGFFEEEEDDDDDDDCCCCBBBBA@A@@@@@??>>>>>>===<<<<;;;;;::::998888887876666655554444332322221111000000///..-----,,,+++++*****)))()(('''&&&%&%%%%%%$$$$$##"""""!! ! ‚‚‚‚€€€~~~}}}}}||||{{{{{zzzyzyyyxxwwwwwwvvvvuuuuutttsssssrrqqqqqqppooooonnnnmnmmmlllklkkjjjjiiiiihhhhggggfgffeeedddddcccbbbbbbaaaaa``_`_^^^^^]]]]\\\\\[[[[ZZZYYYYXYXXXXWWWVVVVUVUUTTTTTTSRSSRRQQQPQPPPPPOOOONNNMMMLMLLKKKKKJJJJJJIIHIHHHGGGFFFFFEEEEDDDDDCCCCBBAAAA@@@@@@????>>>>>===<<<<;;;;;:99999988887777776665545544433332222111100000////....---,,,,++++******))(((('('''&&&%%%%%$$$$$##"#"""!!!! ƒƒ‚‚‚‚‚€€€€~~~~}}}|||||{{{{{zzzyyyyxxxwxwwwvvvvuuuuutttttssrrrrrqqqqppppooooonnnnmmmmlllkkkkjjjjiiiiiihhhggggfffeeeedddddcccbbbbbbbba```_`__^^^^^]]]]]\\\\[[[[[ZZZZYYYYXXXXWWWVVVVVVUUTTTTSSSSSRRRRRQPQPPPPOOOONNNNNMMMLLLLKKKKKJJJJIIIIIHHHHGGFGFFFFEEDDDDDCCCCCCBBBBAAA@@@?@??>>>>>>===<<<<;;;:::::99998888877766666655554433323222221111000////....----,,,,+++++*+**))))(('(''''&&&&%%%%$$$$$##"#""""!!!! ƒƒƒ‚‚‚‚€€€~~~}}}}|||{{{{{zzzzyyyyyxxxxwwwvvvvvuuuutttsstssrrrrqqqpqpppoooooonnnnmlmllllkkkjjjjiiiiiihhhhggggfffeeededdccccbbbbbbbaa````_____^^^^^]]\\\\\\[[[[^ciou|€ƒ……ƒ€{tnf`ZVVUUUUTTTTSSSSRRRRQQQQPPPPPOOONNNNNMMMMLLLLKKKJJJJJIJIIHHHGGGGGGFFFEEEEDDDDDCCCBBBBABAAA@@@?????>>>>=====<<<<<:::::::99988888777766665554544433332222121110000////..-.---,,,+++++****))))))(('''&&&&&&%%%%$$$$######""!!!! „ƒƒƒ‚‚‚‚€€€€€~~~~}}|}|||{{{{(=ZuvuuuuuuuuttUTqqppppiicccb]]]]\\.Bc‚………………………}m/SSRRRRRRQQPOOONNNNMMF1$%0;GJJIIIHHHGHGGGFFFFFEEDDDDDDDCCBBBBAAAAA@@@????>>>>>>===<<<<<<;;:::::9988888887766665555444433333222221110000///./...---,,,,,,++++******))((((''''&&&%%%%%%$$$#$####""""!!! ƒƒƒƒ‚ƒ‚‚‚€€€~~~~~}}}}|||{{{Tvvvuuuuutt00qqqqppjjcccb]^]]]a\…………………„„„MTSSSRRRRQPPPOONNNH'JJIIIIHHHGGGFFGFFFEEDDDDDDDCCBCBBBAAA@@@@@????>>>>====<<<;<;;;:::::9998888877777665655544333333322221111000/////..--.--,,,++++++*****)(()((('''&&&&&%%%%$%$##$###"""!!! „„ƒƒƒƒƒ‚‚‚€€€€€~~}~}}}|||||{{9vvvuuuuukiqqqppjjcccc^^`l‚‡N……………………„+TTSSSSRRRPPPPOOOFJJJIIHIHHHHGGGFFFEEEEEDDDDCCCCCCBBBAAA@@@@@???>>>>>>===<<<<;;;;:::999998888877766666555454433333222221211000///.//..--,--,,,+++++****))))((((''''&&&%%%%%%$$$###"##""!!!! „„„ƒƒƒƒ‚‚‚‚€€€€~~}~}}}||{|{CvvuuuuuHGqrqqpjjcddcdv‡ˆ‡‡o……………………PTSSSSRRRPPPPOONJJJIIIIHHHHHGFGGFFFFEEDDDDDDCCCCBBBBAAA@@@@@????>>>>>===<<<<;;;;:::::9998888887776665555554433332222211111100/////..-----,,,,+++++*****))((((((''''&&%%%%%%%$$####""""!!!!! ……„„„ƒ‚ƒ‚‚‚‚‚€€€€~~~}}}|}|||ivvvuuu""rrqqqkkddddˆˆˆˆ‡‡?……………………9uZTSSRRRPPPPPO7JJJJJIIIHHHHGGGGFFFFEEEEDDDDDCDCCBBBAAAA@@@?@????>>>>>===<<<<<;;:;::::9998888888776666654554433332222221111000///.....---,-,,,,+++++***)))))(((''''&&&&%%%%$$$$$####""""!!!! ………„„ƒƒƒ‚‚‚‚€€€~~~~}}}||||{zzzyviP!Avvvuua^rqqqqpppppooonmlllllkkkkjjjiiiiiiggfffffeedddcbbbaaaaa`d{ˆˆˆˆˆˆ‡‡‡†††€oK ……………………nƒsXSSSSQPPPPO!/AJLJF;/KJJJJIIIIIHHHHGGGFFFFEEEEDDDDDCCCBBBBAAAA@A@@????>>>>>>====<<<;;;;;::9999998888777676655554544433322222211100000///...-.---,,,,,+++++****))))(((''''&&&&%%%%%%$$#$####"""!!!! ……„„„„ƒ„ƒƒƒ‚‚‚€€~~~}}}}|||zzzzzyyyv-%vvvvv: 9rrqrqqqppoooommmmlllkkkkkkjijiiiigggfffefeedddbbbbbaabu‰‰‰ˆˆˆˆˆˆˆ‡‡†††††[……………………&2ƒƒƒkTSSQQPPPP?>>>>=====<<<;;;:::::999988888777666665554444333332222211111000////...-.-,-,,,++++++****))))((''''&&&&&%%%%%$$$$###"#"""!! …………„…„„„ƒƒƒ‚‚‚‚€€€~~~~}|}|{{zzzzyyygwwvvsFEorrrrqqppppoonnmmlmllllkkjjjijiiighggfffefeeddcbbbbak‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡†††……………………djƒƒƒƒ_SQQQPPPLNNNMMMLLLLLLKKKJJJJJIIIIHHHHGGGFFFFEEEEEDDDDCCCCBBBBABAAA@@@????>>>>>>==<<<<;;;;;:::998988888787766666545544433333222221110000////...----,,,,+++++****)*)))((('''&&&&&%%%%%$$$$###""""""!! †…………„„„ƒƒƒ‚‚‚‚‚€€€€~~~~~~}|}{{{zzzzzyvwwvwSkjQsrrrqqqpppopnnmmmmmllllkkkjjjjiigggggfgfffedebbbbcx‰‰‰‰‰‰‰‰‰ˆˆˆˆ‡‡‡††††€……………………„/-„ƒƒƒƒoQQQPPP7ONNNMMMLMLLLKKKJJJJJJIIIHHHHHGGGGGFEEEEEEDDDDCCCCBBBAAAA@@@@@????>>>>>>==<<<<;;;;:;::999998888787766666655544333332222211110000/////..-----,,+++++++*****))(((((''''&%&%%%%%$$$$$####""""!!! †…†……„…„„„ƒƒƒƒ‚‚‚€€€€~~}~~}}{{{{zzzzyfwwwv,.uu-+srrrrqqqqqppnnnmmmmllklkkkkjjiiihhggggfffeeeecbci‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆ‡‡‡‡‡††Z;……………………„md„ƒƒƒƒRRQQQP!%8FNNMMMMLLLLKKJKJJJJJIIIHHHHHGGFGGFEFEEEDDDDDCCCCCBBBAAAAAA@@????>>>>>====<<<<;;;;::99998888888777766655554543333332222111110000//......---,,,+++++****)))()(((('''&&&&%%%%%$$$$$###"""""!!! ††…………„…„„ƒƒƒƒ‚ƒ‚‚‚€€€€€€~~~~~}}|{{{{zzzw-%wxwkVuuTgrrrrqqqqppponnmnmmmmlllkkkjjjjjhhggggfffffee‰‰‰‰‰ˆˆ‡‡‡‡pJk………………………„7'ƒƒƒƒƒRRQQQP8%1?KLLLKKKKKKJJJJJIIIIHHHHGGFGFFFFEEEDDDDDDCCCCBBBBAAAA@@@@??>>>>>>====<<<<;;;;:::99999888888777766555545444333322221111100////.....---,,,,++++++****)))))((''''&&&%%%%%%%%$$$####"!"!!!! ††††……„„…„ƒ„„ƒƒ‚‚‚‚€€€€~~~~||{{{wkP!CxwxEtuusDsrrrrqqqqpponnnmnnnmlmllkkkjjjiiihhhggffffef‰‰‰‰‰‰ˆD††………………………t^ƒ„ƒƒRRQQQPP (BLLLKKKJKJJJJIJIIIHIHGHGGGGFFFEEEEDDDDDCCCCBBBABA@@@@@????>>>>>====<<<<;;;;:::999998888777766665555544443332222221110000///....-----,,,++++++***)))))((('''&'&&&%%%%%%$$$$##"#""""! ! ‡†††††………„„„„ƒƒƒƒƒ‚‚‚€€€€~~~ixxx=vuuu<ssrsrrqqqqqooonnnnmmmmllklkkkjjiihhhggggffff‰‰‰‰‰‰ˆM††……………………………A"‚„ƒƒSRRRRQQM"+KLLLKKKKJJJJIJIIIHHHGGGFGFFFEEEEEDDDDDDCCCBBBBAAA@@@@@???>>>>=====<<<;;;;;;:::9998888877777666555544433333322222111100////...-.----,,,++++++**)*))))((('''&&&&&%%%%%%%$$$###""""!!! ‡‡†††…………„„…„„ƒƒƒƒƒ‚‚‚€€~~Dyxx^dvvvub\sssrrrrqqpooooonnnmmmmllllkkjjiiihhhghggffe‰‰‰‰‰‰ˆo††††…………………………{X„„„TRSRQQQQQ;%MLLKKKKKJJJJJIIIHHHHGGGGGFFFFEEEEDDDDCCCBCBBBBAAAA@@@??>?>>>>>>==<<=<;;;;:;:9::9988888877776665554544333323222221111000////....----,,,,+++++*****)))((('(''&&&%&%%%%$$$$###"##""!!! ‡‡‡†††††……„„„„„ƒƒƒƒƒ‚‚‚€€€€:yyyx7%wvvvvu$5ssssrrrrrqoooonnnnnmmmmlllklkkiiihhhhhggggf‰‰‰‰‰‰‰h††††…………………………J€„„xTRRRRRQQQQF4%>>>>====<<<<;;;::::::9998888787766665654444343333222211110100////./...---,,,,++++****)))(((((''''&&&&&%%%%%$$$#$#"#""""!!!  ‡‡‡‡†††††…………„„„ƒƒƒƒƒ‚‚€€€€VzyyytKvwvvuvJotssrrrrqqooooonnnnnmmmmlllkkkiiiihhhhggggf‹‹Š‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰u††††††…………………Q„„ƒvSSRRQQQQPPPPPMB5$$MMLLKKKKKJJJJJIIIHIHHHGGGGFFFFEEEDDDDDCDCCCCBBAAAA@@@?@??>?>>>>====<<<;<;;::::::989888878776666555544444333222222111000/////...-.---,,,++++++***))))(((('''&'&&&%%%%%%$$$$###"""!!!!! ˆ‡‡‡‡‡††……………„„„„„ƒƒ‚‚‚‚€€€€€(>\zzzzzyPMsssssrrrqppoooonnnnmnmmlllkkljiiiiiihghggg‹‹‹‹‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆ…x[&0†††††††…………………T~„ƒƒqSSSRQRQQQPPPPPPOJMMMLLLKKKJJJJJJIIIIHHHHHGGGGFFEEEEEDDDDDCDCBBBBBAA@A@@@@????>>>>===<<=<;;;;;::::99998888877766655555544433332222211110000///...-.----,,,,++++******)))((('''&&&&%&%%%%%$$$####""""!!!! ˆ‡‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚€€€€~}}|||{|{{{{{zzzz((ttsssrsrrpoooooonnnnnnmmmlllkiiiiiihhhgggg‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆ…2j††††††…………………‚ K„ƒƒƒkSSRRQRQQQPPPPPOOEMMMMLLLKKKJJJJJJIIIIHHHHGGFFGFFFFEDEDDDDCCCCBCBABAAA@@@@@??>?>>>>>====<<<;;;:;:::99989888877776666555444433332222222111100////...-.--,,,,,+++++*****)()((((('&'&&&%%%%%$$$$$####"""!!!!! ˆˆˆ‡‡‡‡†††††………„„ƒ„„ƒƒƒ‚‚‚‚‚€€€€€~~}}}||||{{{{{zzjettssssrrqpppoooooonnmmmmmllljjiiiiiihhhgg‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆ4‡†††††††………………^|ƒƒƒƒcS ARRQQQPQPPPPPLNMMMLLLLLKKKKJJJJIIIIHHHHGGGGFFFFEEEEDDDDDCCCCCBBAAAAA@@?????>>>>>====<<<<<;:;:::::99988888877666656555444333322222111100000//./...---,-,,,+++++***)*))(((((''''&&&%%%%%%$$$$#####"!!!!! ˆˆˆˆ‡‡‡‡‡†††…………„„„„„ƒƒƒ‚‚‚‚€€€€~~~}}}|||{{{{{{zB@tttttssrqpppoooooonnnnmmmlmljjjiiiiiihhhm‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰Š‰‰‰‰ˆˆˆˆˆˆSv‡††††††…………………(FƒƒƒƒƒZ9NRQQQPPPPP4NNMMMLLLLLKKKKJJJJIIIIIHHHGGGFFFFFEEEEEDDDDCCCBBBBBBAAAA@@@???>>>>>>====<<<;<;:;:::99998888887776665555444444322222221111100////.....----,,,+++++***)*))(((((''''&&%%%%%%%%$$#####""""!!!!  ˆˆˆˆˆ‡‡‡‡†‡†††………„„„„ƒƒƒƒƒ‚‚‚€€~~~}}}}|||{{{{{ztttsssssqqppppooooooonmmmmmlkjjjiiiiihhi‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆ‚B‡‡†††††…………………gƒƒƒƒƒ‚%4BJNPK?('NMMNMMLLLLKKKKJJJJJIIIHHHHGGGGFGFFFFEEDDDDDDDCCBBBBBBAA@@@@????>>>>>>>==<<<;;;;;::::99998888877766766555444433332222212110000/0//...---,,,,,++++++****))))((('''''&&&%%%%%%%$$#####"""!!! ˆ‰ˆˆˆˆ‡‡‡‡‡†††††………„„ƒ„ƒƒƒ‚‚‚‚€€~~~}}}|}|||{{{\jyyxxwxwwvwhXuttstssqqqqppoooooonnnmmmllkkjjjjiiiih}ŒŠŠŠ‰Š‰‰‰‰ˆˆˆˆˆF€‡‡††††……………………1„ƒƒƒƒƒ=NNNNNMMLLLLKKKKKJJJJIIIIHHHGHGGGGFFFEEEEDDDDCCCCCBBBAAAAAA@@@???>>>>>>===<=<<<;;:;;:::99988888777766565555544433332222211100000//./.-.----,,,,,++++*****)((()(''''''&&%%%%%%$$$#####""!!"!! ‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„„„ƒƒ‚‚‚‚}~}}}|||||{{3*zyyxyxwxwwww(2uutttttqqqpppoooooononnnnmmkkjjjjiiiiqŒŒŠŠŠŠ‰‰‰‰‰ˆ‰ˆˆˆyQ‡‡‡†††……………………o„„ƒƒƒƒ"OONNNNMMMMLLLKKKKJJJJJIIIIIIHHGGGFGFFFEEEEEDDDDCCCBBBBBAA@A@@??@???>>>>=====<<<;<;;::::99888888877776666665444443332222222111000////.....---,,,+++++++***))))((((''&''&&%%%%%$%$#$$###""""!!! ‰‰‰‰‰ˆˆ‡‡‡‡‡†††††………„…„„„ƒƒƒ‚‚‚‚~~~}~}}}||||tRyzyxxxxwwwwwPnuutttsrrqqqppppoooooonnnmnkkjjjjjiijŒŒŒŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆ8†‡†‡††………………………:„„„„„ƒLOONONMMMMMLLLLLKKKKJJJJIIIIIHHHHGGGGFFFFEEEEDDDDCCCCBBBBAAAAA@@@???>>>>>>====<<<;;;;;::::99888888777766666544443343322222221110100///....---,,,,,+++++****)))((((''''&&&&&%%%%%$$$$###"#""!!!!! ŠŠ‰‰ˆˆˆˆ‡‡‡‡‡‡†††…………„„„„„ƒƒƒ‚‚‚‚€~~}~}}}}|||MvzyzyyxxxxwxwrJuuutttrrrqqpppppoooooonnmnkkkkjjjji{ŒŒŒ‹ŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆm_‡‡‡††………………………w„„„„„ƒgB0NPOOOONNNNMMMMLLLLKKKKJJJJJIIIIHHHGGGGGFFFFEEEEDDDDCCCBBBBBAAA@@@@?@???>>>>=====<<;;;;;:::::999888887777666665545443333322222211100000///....----,,,+++++****)*)(((((''&&&&&%%%%%%$$$$$##"#"!!"!! ! ŠŠŠ‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„„„„„ƒƒƒƒ‚‚‚€~~~~~}}|}|%9zzzzyyyyxyxwww8#uuuuttrrqqqqpppppooooonnnnllkkkjjjnŒŒŒŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆ**‡‡‡‡†††……………………D„„„„„„ƒƒƒO;-$+>>>>>==<=<<;<;;;::::99888888877767666555544433322222211101000////..-----,,,,++++**+*))))((((((''''&&%%%%%%$$$$###"""!!!!!! ‹Š‰‰‰ˆˆ‰ˆˆˆ‡‡‡‡‡††††…………„„„ƒƒƒƒƒ‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyxxxxxwwwvvvvuuuuuuttssssrrrrrrqqpppooooooonnnmmmmmllllkkkk‚ŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†‡††††††……………………………………………„„„„„„„ƒƒtSSSSRSRRRQQQQPPPPPOOONONNNMMMMMLKKLKJJJJJJJJIIHHHHGGGGFFFEEEEEDDDDDDCCCBBBBAAAA@@@@????>>>>=====<<<;;;:;::::999988888777766655554444333322222111010000////...---,,+,++++++***)))))(((('''&&&&%%%%%$$$$$##"""""!!! ! ‹ŠŠ‰‰‰ˆ‰ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒƒ‚‚‚‚€€€€€~~}}}}}}|||{{{{{zzzzyyyyxxxxwwwwvvvvuuuuutttsssssrrrrqqqppppooooonnnnmmmmlllllkkpŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††………………………………………………„„„„„ƒƒ„[STSSRRRRRRQQQPPPPPOOOONNNNMMMMLLLKKKKKJJJJJIIIIHHHGHGGFFFFEEEEDDDDDCCCCCBBAAAA@@@@@????>>>>>===<<<<;;;:;::::99898888877767656555444433332222211111000///./...---,,,+,+++++***)*)))((((''&&&&&%%%%$$$$$####""""!!!! ‹Š‹Š‰Š‰ˆ‰ˆˆˆ‡‡‡‡‡‡††††……………„„ƒƒƒƒƒ‚‚‚‚€€€~~~}}}}}|||{{{{{{zzyzyyxxxwwxwwwvvvuuuuuttttsssssrrqqqqqpqopooooonnnnmmmmlllllkƒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††…†……………………………………„…„„„„„„„uTSSTSSSRRQQQQQPPPPOPPOONNNNNMMMMLLKLKKJJJJJJIIIIHHGHGGGFGFFFEEDDDDDDDCCCCBBBAAA@@@@@??>>>>>>>=====<<;;;;::::999988888877766666554544433332222111111000////...----,,,,,++++****)))))(((''''&&&&&%%%%%$$$$##""""!!!!! ‹‹‹ŠŠŠŠ‰‰‰ˆˆˆ‡‡‡‡‡‡††††……………„„„ƒƒƒƒ‚‚€€€€~~~}}}|||||{{{{{{zzzyyyyxxxxwwwwvvvvuuuuuuttstssssrrrqqqpppppoooonnnnmmmlllllpŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††………………………………………………„„„„„„„ZTTSSSSRRRRQQQQQPPPPPOOOONNNMMMMLLKLKKKKJJJJIIIIIHHHHGGGGFFFEEEEDDDDDDCCCCBBBAAAA@@@??????>>>>=====<<<;;;;::::9998888877777666555544443333222221111100/00//...----,,,,,+++++***)))))(((''''&&&&%%%%%$$$$####""""!! ! Œ‹‹‹‹ŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡‡‡‡†††………„„„„„ƒƒƒƒ‚‚‚‚€€€€~~~~}}||}||{|{{{{{zzzzyyyyxxxxwwwvvvuuuuuuttttsssssrrqqqqpppooooooonnnnmmmlll€ŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††……………………………………………„„„„„„ƒpTTTTTSSSRRRRQQQPPPPOPOONNNNNMMMMLLLKLKKKJJJJJIIIHHHHHGGGFFFEEEEDEDDDDCCCBBBBBBAA@@@@@??>?>>>>====<=<<<;;;;;:::99988888877766666555444343332222212111000///....-----,,,+++++**+*)))()(((('''&&&&%%%%%%$$$$###"""""!! ! Œ‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††…†……„„„„ƒ„ƒ‚‚‚‚‚‚€€€€~~~~~}~}}}|||{{{{{zzzzyzyyxxxxwwwvwvvvuuuutttttssssrrqrqqqpppopoooonnnmmmmmmnŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…†……………………………………………„„„„„„VTTTTSSSRSRQQQQQPPPPPOOONONNMMMMMLLLLKKKJJJJJJJIIHHHHGGGFGFFFFEEEDDDDDDCCBBBBBBAAAA@@@@???>>>>>====<<<<<<;;::9::9988888877777665655454443332222211101000////....----,,,+++++****))))((((('''&&&%%%%%%%$$$$####""""!! Œ‹‹‹ŠŠ‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~~}}}|||{{{{{z{zzzyyyxxxwwwwvvvvvuuuuuttttsssssrrrqqqqpppooooononnnmmmxŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††…………………………………………„„„„„„eUUTSTSSSSRRRQQQQPPPPPPOOOONNMMMMMLLLKKKKJJJJJJIIIIHHHGGGGGGFEEEEDEDDDDCCCCBBBBAAAAA@@?@???>>>>>====<<<<;;;:::::9989888887776666565545443322222221121100////.....----,,,+++++*****))))(((('''&&&%&%%%%$$$$###"#"""!!! !  ŒŒŒ‹‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡††††…†……„„ƒ„„ƒƒƒ‚‚‚‚€€€€~~~~~}}}}|||{{{{{{zzzyyyyyxwxxwwwvvvvvuuuuuttstsssrrrrqqqqpppoooooonnmnmmŠŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††……………………………………………„„„„~UUUTTSSSSSRRRRQQQQPPPPOOOONNNMMMMMMLLLKKKKJJJJJJIIHHHHGHGGFFFFEEEEEDDDDDCCCBBBBBAAAA@@@@???>>>>=>===<<<;;;;:::::99988888777766665555544333332222211100000////.....--,-,,,++++****)*)))(((('''&&&&%%%%%%%$$$###""""!!!! ŒŒŒ‹‹‹‹‹‹ŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡‡†††…………„„„„ƒƒƒƒ‚‚‚€€€~~~~}}}||||{{{{{{zzyyyyyyxxxxwwvvvvuvuuuutttttsssrrrrqqqpppppooooonnnnqŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡†‡†††††…†………………………………………„„„„„ZUUTTTTSSSRRRRQQQQPPPPPPOONNNNNNMMMMLLLKKKKJJJJJIIIHIHHGGGFGFFFFEEEDDDDDCCCCBCBBAAA@@@@@????>>>>>>==<<<<<;;;;:::9:99988888877676665555444433332222111110000///...----,,,,++++++***)))))((((''''&&&%%%%%$$$#$###"#"!"!! ! ŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰‰ˆˆ‡‡‡‡‡‡††††…†………„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|||{{{{{zzzzyyzyyxxxxwwvvvvvvuuuuutttssssrrqqqqqpppppooooonnnzŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††……………………………………………„„„gUUUTTTSTSSSRRRRQQQQPPPPPOOONNNNMMMMMLLLKKKKJJJJJIIIIHHHGGGFGGFFFEEEDDDDDDCCCBBBBBAAA@@@@@???>>>>>>===<<<<;;;::::999899888878766665555444433333222222110000///./...----,,,,++++++***))))((''''&&&&&&%%%%%%$$####""""!!! ŒŒŒ‹Œ‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††…………„„„„ƒ„ƒƒƒ‚‚€€€~~}~}}||||{|{{{{{zzzyyyxxxxxxwwwwvvuuuuuuttttsssssrrqqqqqqpppooooonn‰ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡‡†††††††…………………………………………„„}VUUUTTTTSSSSRRQRRQQQPPPPPOOOONNMNMMMLLLLLKKKJJJJJIIIIHIHGGGGGFFFEEEEDDDDDCCCCCBBBAAAA@A@@????>>>>>===<<<<<<;;;:;:9:99988887777666555554544333332222111100000//./....--,,,++++++*****)))(((((('''&%&%%%%%$$$$####""!!!! ! ŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡††††………„„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|||{|{{{{{{zzzyyxyxxxwwwwwvvvuuuuuuttsssssrrrqqqqqppppooooopŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††………………………………………„„„XUUUTTTTTSTSSRRRQQQPPPPPPPOONNNNMMMMMMLLLLKKKKJJJJIIIHIHHHGGGGGFFFEEDDDDDDDDCCBBBBAAA@@@@?@???>>>>>>==<<=<;<;;:::::99998888877766665554444434332222222110000////...-----,,,++++++****))((((('''''&&%&%%%%%$$$###""#"""!!! ŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒ‚‚‚‚‚€€€€€€~~~~}}}}||||{{{{{{zzzyyyxxxxxxwwwwvvvuuuuutttstsssrrrqrqqppppooooovŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡†††††…†……………………………………………aVVUUTTTTTSSSRRRRRQQQPPPPPPOOONNNNMMMMMLLLLKKKJJJJJJIIHIHHHHGGFFFFFEEEDDDDDCCCCCCBBAAA@@A@@?????>>>>>==<=<<<;;::;::999988888877777665554544443332222211110000////...------,,++++++****)))((((('''&'&&%%%%%%$$$$###"""""!!!! ŒŒŒ‹Œ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††………………„„„ƒ‚ƒƒ‚‚‚€€€€€~~~}~}}}|||{{{{{{zzzyyyxyxxwwwwvvvvvuuuuuttttsssrrrrrqqqqqppooooŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡††††††††…………………………………………mVVUUUUUTTTSSSRSRRRQQQPPPPPOOOOONNNMMMLLLLKLKKKJJJJJIIIIHHHHGGGGGFFFEEEEDDDDDCCCBCBBABAAA@@@@????>>>>>==<<<<;;;;;::::99998888777776666554544443323222221110000////...-----,,,++++++*****)))(((('''&&&&%%%%%$$$$#####""!!!!! ŽŒŒŒ‹Œ‹‹ŠŠŠ‰‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚‚‚€€€€€~~}}}}}|}||{{{{{{zzzyyyyxxxxwwwwvvvvvuuuuuttttsssrrqrqqqqqpopooŠŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…†………………………………………}VVVUUUTTTTTTSSSRRRQQQQQPPPPPOOOONNNNMMMMLLLLKKKJJJJJJIIHHHHHGGGGGFEFEEEEEDDDDCCCBBBBAAAA@@@@?????>>>>====<<<<<;;:;:::99999888877777666554444433332222222111000/0/./....---,,,,+++++****))))((((''''&&&&%%%%$$$$$###"#""""!! ŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„ƒƒ„ƒƒƒ‚‚‚€€€€€~~~}~}}}||||{{{{{zzzyzyyxxxxwwwwwvvvuuuuuttutsssssrqrrqqqpppopŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††………………………………………WVVVUUUUTTTTSSRSRRQRQQQPPPPPOOOOONNNNNMMLLLLKKKKKJJJJIIIIHHHHGGGGFFFFFEEEDDDDDCCCBBBBAAAA@A@@@@???>>>>====<<<;<;;;::::999988888777666665555444433322222111110000///....--,-,,,,+++++*+*))))(()((''''&&&%%%%%$$$$$$#""#"""!!!! ŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††……………„„„„„ƒƒ‚‚‚€€~~~~}}}|||{{{{{{{zzzzyyxyxwwxwvvvvvvuuuuttttssssrrrrqqpqqpprŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††……………………………………[VVVVVUUUUTTSSSSSRRRRQQQQPPPPOOOONNNNMMMMMLLLKKKKJJJJJIIIIIHHHGGGFFGFFEEEEDDDDDDCCCCBBBAAA@@@@???>?>>>>====<<<<;<;;:::::99988888877766655555443433222222211110000////...---,,,,+,++++****))))((((''''&&&&%%%%%$$$$####""""!!! ŽŽŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††††……„„„„ƒƒƒƒ‚‚‚‚‚€€€€€~~~~}}}}}}||{{{{{{zzzzyyyxxxxxwwwvvvvvuuuuttttssssrrrqqqqpppvŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡ˆ‡‡‡‡‡†‡††††††††………………………………`WVVVVUVUUUTTSTSSSRRRQQQQQPPPPPPOOONNNNMMMLLLLKKKKKJJJJIIIIHHHHHGGFGFFFEEEEDDDDDCCCBCBBBAAA@@@?@??>?>>>>>====<<<;;;:;::9:99988888877776665555544433322222211101000///....-----,,,++++++**)*)))(((('''&&&&&%%%%%%$$$$##"""!!!!!! ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡†‡†††…………„„„„ƒƒƒ‚‚‚‚€€€€€€~~}}}}||||{{{{{{zzzzzzyyxxwwxwwvvvvvuuuuttttssssrrrrqqqqp{ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††…………………………………gWVVVVVUVVUTTTSTSSRRRRRQQQPPPPPPOOOONNNNMMMLLLLLKKJJJJJJIIIIHHHGGHGGFFFFFEEDDDDDDDCCCCBBABAA@@@@@???>>>>>=====<<<<;;;;:::999998888777766666655444333232222221101000/////...---,,,,++++++***))))((('('''&&&&%%%%%%$$$$###"""!!!!! ŽŽŽŒŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒƒƒ‚‚‚€€€€~~~}}}}}||||{{{{{zzzzzyyyxxxwwwwwvvvvuuuuutttssssssrrqqqq€ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡†††††††††…………………………nWWVVVVVVVUUTTTTTSSSRRRQQQQPQPPPPOOOOOONNNMMLLLLLKKKJJJJJJIIIHHHGHGGGFFFFEEEEDDDDDCCCCBBBBAAA@@@@@????BHOXajt|ƒˆŽŒˆ‚zsh^UKC=:888887767666665554444332222212110000/0//./...--,-,,+,+++++***)))(((((''''&&&&%%%%%$$$#$###"""!!!!!! ŽŽŽŽŒŒ‹‹Œ‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„„ƒ‚ƒ‚‚€€€€~~~}}||||||{{{{{zzzyzyyyyxxwwwwvvvuvuuuuutttttsssrrrrqq…ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††…………………………uWWVVVVVVUUUTTTTSTSSSSRRQQQQQPPPPPOOOONNNMMMMMMLLLKKKKJJJJIJIIIHHHHGFGGFFFFEEDDDDDDDCCCBBBBAAA@ABKYkŽŽŽŽŽŽ}fRC:88766765555554444323222221111000/////...-----,,,+++++****)*)()((('''&'&&&%%%%%$$$$#####"""!!!! ŽŽŽŽŽŒŒŒŒŒ‹‹ŠŠŠŠ‰Š‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„ƒƒƒƒ‚ƒ‚‚‚€€€€€~~~}}}}||||{{{{{zzzyyyyxxxxxxwwvvvvvuuuuuttttssssrrrrq‰ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡†††††††……………………{WWWWWVVVVVUUUTTTTTTSSRRRRQQQPPPPPPOOONONNNMMMMLLKLKKKJJJJJJJIIIHHHGHGGFFFFFEEEDDDDDCCCCBBBBERi†‘‘‘‘‘‘‘ŽŽŽŽŽŽbJ:76666555544444332222221111000/0///...-----,,+++++++***)))))(('('''&&&&%%%%%$$$$#$"#"""!!!!! ŽŽŽŽŒŒ‹‹‹‹Š‹Š‰Š‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„„…„ƒ„ƒƒƒƒ‚‚‚€€€€~~~~}}}}||||{{{{{zzzyyyyyxxxwxwwvvvvvuuuuutttssssrsrrrŒ‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡††‡†††††††…………………€XWXWWVVVVVVUUUTTTTSSSSRRRQRQQQPPPPPPOOONNNMNMMMLLLKKKKKJJJJIJIIHHHGGGGGGGFFEEEEEDDDDCCCCLc„’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ~ZB76555555444333222222211100000///...-----,,,+++++****)))(()(('''&'&&&%%%%$$$$$###"""""!! !! ŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆ‡‡‡‡‡††††……………„„ƒƒƒƒƒƒ‚‚‚€€€€~~~~}}|}||{{{{{{{zzyyyyyxxxxwwwvvvvuuuuutttttsssrrrq‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡†‡††††††……………………ƒXXWWWWWVVVVVVUUTTTSSSSRSRRRQQPPPPPPPOOONONNNMMMMMLKKKKKKJJJJIJIIIHHHHGGGGFFFEEEDDDDDDNj’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽ`B6655444444333322222211000/0///...----,,,,,++++****)))(()(((''''&&&%%%%%%$%$$###"""!!!!! ŽŽŽŒŒŒ‹Œ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††…………„…„„„„ƒƒƒ‚‚‚€€€~~~~~~}}}|||{{{{{{zzzyyyyxxxwxwwwwvvvuuuuutttsstssrr‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡†††††……………………XXXXWWWVVVVVUUUUTTTTSTSSRRRRQQQQPPPPPPOONNNNNMMMMLLLKLKKKJJJJJIJIHIHHGHGGGFFFFEEEDJe““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŠY<5554444333222222111110000/.//.----,-,,,++++++***)))())(('''''&&&%%%%$$%$#$###"""!"!!!  ŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡‡‡†…†………„„„„ƒƒ‚‚‚‚‚€€€€~~}~}}|||||{{{{{{zzzyyyyxxxxxwwwvvvvvuuuuuuttsssrr‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡‡††††††…………………XXXXXXWVVVVVVVUUUTTTSSSSSRSRRRQPQQPPPPOOONNNNNMNMMMLLKKKJKJJJJIIIIHHHHGGGGFFFFEFU|““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒrG65544333323222221111000/////..----,,,,,++++****))))(((('''&'&&&%%%%%$$$$#$###"!!!!!! ‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuuttttssss‘‘‘‘‘‘ŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††………ƒYYYXXWWWWWVVVVVUUUUTTSTSSSRRRQQQQPQPPPPPOOOONNNMMMMMLKLKKKKJJJJJIIIIHHGGGGGFFH`‘““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒ‰R7554433332222211110100////..-..---,,,,+++++****))())((('''&&&&%%%%%$$$$$###"""!!!!! ‘‘‘ŽŽŽŒŒŒ‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆ‡‡‡‡††††………………„„ƒƒƒƒƒ‚‚‚€€€€€~~~~~}}}}}|||{{{{{{zzzyyyyyxxwwwwvvvvuuuuuuutttsss‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††………€YYYXXXXWWVWVVVVVVUUTTTTTSSSRRRRRQQQPPPPPPOOONNNMNNMLLLLLKKKKJJJJJIIIIHHHHHGJf”““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒY74444323322221111000/////..-..---,,,+++++*****))))((''''''&&&%%%%%%$$$#$###""!!!!! ‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††††………„„ƒ„„ƒƒ‚‚‚‚€€€€€~~~}}}}||||{{{{{zzzzzyyyyxxxwwwvvvvvuuuuuttttssŠ‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††……|YZXXXXXWWWWWVVVVVUUUUTTTTSSSRRRRQQQQQPPPPOOOOONNMNMMMLLLKKKKKJJJJJIIIIHHHJg”””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒX64443333222221100100/////..-----,,+,+++++***)*)))((((('&&&&&%%%%%%%$#$####""!"! ! ’‘‘‘‘ŽŽŽŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††…………„„…„„„ƒƒ‚‚‚‚€€€~~}}}}}|||||{{{{{zzzyyyyyyxxwwwwvvvvuuuuutttss†‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††…vZYYYXXXXXWWWVVVVVVVUUUUTTTTSRRRRRRQQQPPPPPPOOOONNNMMMMLLLLKKKKKJJJJJIIHIa”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒQ53433232222111110000///.....---,,,,+++++***)))))((('''''&&&%%%%%%$$$$$##""""!!!!! ’’’‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡†††…………„…„„ƒƒƒ‚ƒ‚‚‚‚€€~~}~}}}|||||{{{{zzzzzzyyxxxxxwwvwvvvuuuuututt‚‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††oZYYYYYXXXXXWWVVVVVVVUUTTTSSTSSSRRRRQQQPPPPPPOOONNNNMMMMLMLLLKKKJJJJJIIYŽ••””””””““““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒ…G4333322222211100000///...-.---,,,,++++++*)*)))(((((''''&&&%%%%%$$$######"""!!! ’’’‘’‘‘ŽŽŽŒŒ‹Œ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††…†…………„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}||||{{{{{zzzzyyyyxxxxwwwwwvvvuuuuuutt~‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††iZZYYZYYXXXXWWWVVVVVVUUUUTTTTTSSSRRQQQQQPPPPPPOOOOONMMNMMLMLLKKJKJJJJOz•••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒl;33332222221111000////...-----,,,++++++*****))(((((('''&&&%%%%%$$$$$$#""""""!!! “’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹Š‹Š‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††……………„„„„ƒƒƒƒƒ‚‚‚€€€~~~}}}}|||||{{{{zzzzyyyyyxxxxwwwvvvvvuuuuttz‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††d[ZZZZZYXXXXWWWWWVVVVVUVUUTTTSSSSRRRRRQQQQPPPPOOOONNNMNMMMMLLLLKKKKJd•••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒR33332222221110000////....----,,+++++++****))))((((''''&&&&%%%%%$$$$$##"""""!!!! “’’“’’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰Š‰‰‰ˆˆˆ‡‡‡‡‡††††…†………„„„„ƒƒƒƒ‚‚‚€€€€~~~}~}}}||||{{{{{{zzyyyyxxxwxwwwwvvuvuuuuuv‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡†††_[[ZZYYZYYXXXXXWWWVVVVVUUUTTTTTSSSRRRRRQQQPPPPPPOOOONNNMMMMMLLLKKKR„••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒw=33332222211100000////...----,,,,+++++***))))()((((''&&&&&%%%%%$$$$$####""!!! ! “““’’’’‘‘ŽŽŽŽŽŒŒŒ‹‹‹Š‹ŠŠ‰Š‰‰‰ˆˆˆˆˆ‡‡‡‡‡††…………„„„„ƒƒƒƒƒƒ‚‚‚‚€€€~~~~}}}}||||{{{{{zzzyyyyyxxxwwwvwvvvuuuuuu‘’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†‡†\[[ZZZZZYYXYXXXWWWWVVVVVUUUTTTTTSSSSRRRQQQQPPPPPPOOOOONNMNMMMLLLKc•–••••••••••””””””““““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹P333222221111100000///..-.----,,+++++++**)*))))('''''&&&%&%%%%$$$$$####""""!! ! “““’’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††…………„„„„„ƒƒƒƒ‚‚‚€€~~}}}}|}||{{{{{{zzzzzyyyxxxxwwwwvvvuuuuu‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†€\\[[Z[ZZZYYXYXXXWWWVVVVVVVUUTTTTTTSSSRRRRQQQPPPPPOOOOOONNMMMMMLOz–––•••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒk6333222221111000///./....----,,,++++++****)())((('''''&&&%%%%%$$$$###"""!!!! ! ““““’’’’’’‘‘ŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††……†………„„„„ƒƒƒƒ‚‚‚€€€€€~~}}}}||||{{{{{zzzzzyyyyxxxwwwwwvvuuuuuƒ’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡q\[[[[ZZZZYZYXYXXXWWWWVVVVVUUUUTTTTTSSRRRRQQQQQPPPPPOOONNNNMMMMW––––––••••••••••”””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹A332222221110000////.....--,,,,,+++++****))))(((('''''&&%%%%%$$$$#####""""!!!! ”“““““’’’’‘‘‘ŽŽŽŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††††…………„„ƒ„„ƒƒ‚‚‚€€€~~~~}}}}||||{{{{{zzzyyyyyyxwxwwvvvvvuuu{’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡f\\\[[[[ZZZZYYYYXXWWWWWVVVVVUUUUUUTTSSSRSRRQRQQPPPPPOOOOONNNMMc—––––––•–••••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹N3332222222110000///./..----,,,,,+++++***))((((('''''&&&%%%%%%%$$$$###""""! ! ”“““““’’’‘‘‘‘‘ŽŽŽŒŒ‹‹Œ‹‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒ‚ƒ‚‚‚‚€€€€~~~~}}}}|||{{{{{{zzzyyyyyxxxwxwwvvvvvuw’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡_\\\[[[[ZZZYYYYYYXXXWWWWVVVVVUUUUTTTTSSSSRRRRRQPPPPPPPOOONNNNr———–––––––––••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹^333222221111000////.....----,,++++++***)*)))((((('''&&&&%%%%%$$$#$##""""!!!! ””“““““’’’’’’‘‘ŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„ƒƒƒƒƒƒ‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyyxyxxxwwwwwwvvuuŽ’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠ‰Š‰‰‰‰‰‰‰‰‰‰ˆˆˆ‡‡††………~WVVVWVWWXXXYYYYYXXYXXWWWWVVVVVUUUUTTTSSSSSSRQQQQQPPPPPOOOONP————–––––––––•••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹o5332222221110000/0///....,,-,,++++++*+**)))(((('('''&&&&%%%%$$$$$$###"""!!!!!  ””””““““’’’’’’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„ƒƒƒƒ‚‚‚‚€€€~~~~~~}}|}||{|{{{zzzzzyyyyxxwxwwwvvvuu’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‡‡†…„„„„„„„„„iWWWVVVVVVVVVUUUUVWWXWXXWWWWVVVVUUUUTUTTSSSSSRRQQQPPPPPPOOOT‹——————––––––––•••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹|833222221111100/////...-----,,,+++++****))))(((('''''&&&%%%%$%$$$$#"#""""!!! ”•”””““““’“’’’’‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡†††…†………„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}}}|}|||{{{{{z{zzyyyyxxxwwwwvwvvx’’’’‘’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠ‰ˆ‡………………………„„„„„„„\WWWWWWVVVVVVUUUUUUUUUUVVWWVVVVVVVUUTTTTSTTSSSRRQQQQQPPPPOV–˜˜——————–––––––––•••••••••••””””””““““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‰;32222221111100//////..-----,,,,++++*+**)))()((('''&&&%%%%%%%%$$$####""!"!!! ••”””“““““’’’’’’‘‘‘ŽŽŽŒŒ‹‹‹‹‹Š‹ŠŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡‡††††……………„„„ƒƒƒ‚‚‚‚‚€€€€~~~}}}}}|||{{{{{zzzyzyyxxxxxwwwwvvu’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹Š‹Š‰ˆ……………………………………„„„„„WWWWWWWVVVVVVUUUUUUUUUUUTTTVVVVVVVUUUUUTTTTTSSRSRQQQQQPPPX˜˜˜˜˜˜———————––––––•–••••••••••””””””””“““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹=3322222121100000///...-----,,,++++++****)))))(('''''&&&%%%%%$$$$$###""""!!!!! ••••””“““““““’’’‘‘‘‘ŽŽŽŒŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††††………„„„„ƒƒƒƒ‚‚‚€€€~}}~}}}}|||{{{{{zzzzyyyyxxxwwwwvvv’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹‹‹‹‹‹Š‰‡………………………………………………„„„„gWWWWWWWVVVVVVVVUUUUUUUUUUUUTTTTUVVVUUUUTTTTTSSSRRRRQQQQPZ˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹>332222211101000//./....--,,,,,+++++***)))))((((('''&&&&%%%%$%%$#$##"#"""!!!!  –•••”””“““““““’’’‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡††††††……„„…„ƒƒƒ‚ƒ‚‚‚€€€~~~~}}|}|{|{{{{{zzzzyyyyxxxxwwwwvx’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‰‡†††††……………………………………………„„„ZWWWWWWWWVWVVVVVUVUUUUUUUUUTTTTTTTTUUUUUUTTTTSSSSSRRRQQQX™™˜˜˜˜˜˜˜————————––––––•••••••••••••”””””“”“““““““““““’’’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹=3322222111100000///....----,,,+++++****)))(((((''''&&&&&%%%$%$$####""""""!!! ––•••””””“““““’’’’‘‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆ‰ˆˆ‡‡‡‡‡†‡†††………„…„„„„‚ƒƒ‚‚‚‚€€€€€~~}}}|}||{{{{{{zzzyyyyxyyxxwwwww‡’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ˆ††††††††………………………………………………„rWWWWWWWWWWVWVVVVVUUUUUUUUUUUTTTTTTTSSTUUUUUTTTSSSSSRRRQX™™™˜™˜˜˜˜˜————————––––––––••••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹‹;33222222110100000//...-.---,,,,+++++***)))))(('('''&&&&%%%%%$$$$#####"""!!! ––••••”””“““““““’’’‘’‘‘ŽŽŽŽŒŒŒŒ‹‹Š‹ŠŠŠ‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††……………„„„„ƒ‚ƒ‚‚‚‚€€€~~}}}}|||||{{{{z{zyxxvutsrqppppu‘‘‘‘’‘’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŠ‡†††††††††††……………………………………………„]WWWWWWWWWWWWWVVVVVVVUUUUUUUUUUUTTTTTTTSSUUUUUTTSSSSRRRV—™™™™™˜˜˜˜˜˜˜—————————––––•––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒ‹‹‰83332222211010000//.....---,,,,,++++++***)))((((''''&&&&%%%%%%$$#####"""!!!!!! —–––••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹Š‹ŠŠ‰‰‰ˆ‰ˆˆˆ‡‡‡‡‡‡††……………„„„ƒƒƒƒƒ‚‚‚‚€€€~~~~}}}}|||||{{zwurqqqqqqqqpppppp†‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒ‹Š‡†††††††††††††††………………………………………xXXWWWWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTSSSSTUUTTSTSSRRU™™™™™™™˜˜˜˜˜˜—˜——————–––––––••••••••••••”””””””““““““““““““’’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹|5322222221111000////.....--,,,,,+++++*****)))((('(&''&&%%%%%%%$$#$###"""""!!!  –––•••••”””““““““’’’’‘‘‘‘ŽŽŽŒŒ‹‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††…†…………„„„ƒƒƒƒ‚‚‚‚€€€€~~~~~}~}}}}{xtrrrqqqqqqqqqqqpppppt‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒ‰‡‡‡‡‡†††††††††††††………………………………………_XXXWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUTTTTTTTTSSSSTTTTSTTSR‚™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒ‹n4332222212100000///.....---,-,++++++*+**)))()('('''&'&&%&%%%%$$$#####"""!"!! ! ——––••••••”””“““““’’’‘’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡†††……………„„„„„ƒ‚ƒƒ‚‚‚€€€~~~~~}|xssrrrrrrqqqqqqqqqqpppppp†‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŠ‡‡‡‡‡‡††††††††††††††††………………………………vXXXXXWWWWWWWWWWWWWVVVVVVUUUUUUUUUUTTTTTTTTTSSSSSTTTTSSv™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––•••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒ‹_3332222221110000////....----,,,,+++++****))))(((('''&&&&&%%%%%$$#$####"""!!! ! ——–––––•••”””““““““’’’’‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰ˆ‰ˆˆˆˆ‡‡‡‡†‡††………„„„„„ƒƒƒƒ‚‚‚‚€€€€€€~}yssssrrrrrrrqqqqqqqqqqqqppppt‘‘‘‘ŽŽŽŽŽŽŽŠ‡‡‡‡‡‡‡‡‡†††††††††††††††………………………………]XXXXWXWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUUTTTTTTSSSSSSSTTTi™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••””•””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒ‹‹N3323222221101000///....-----,,+,+++++****)))))(('''&&&&%&%%%%$%$$$##""""!!!!! ˜—–——––••••””””“““““’’’‘’’‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆˆ‡‡‡‡††††……………„„„ƒƒƒƒ‚‚‚‚€€}wstssssssrrrrrrqqqqqqqqqqqqpppp€‘‘‘ŽŽŽŽŽŽŒ‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††††…†………………………nXXXXXXXXWWWWWWWWWWWWVVVVVVUVVUUUUUUUUTTTTTTTTSSSSSSSS^™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•–••••••••••””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒ‹A33332222211110000///....----,,,,+++++*+**))))(((('''&&&&%%%%%%%$$$####""!!!! ˜————––•–••••””““““““’’’‘’‘‘‘‘ŽŽŽŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆˆ‡‡‡‡‡††…†……„„„„„ƒƒƒ‚‚‚‚€€|utttssssssrsrrrrrrqqqqqqqqqqqqpppr‘‘ŽŽŽŽŽŽŽŠ‡‡‡‡‡‡‡‡‡‡‡‡‡†‡††††††††††††††………………………ZXXXXXXXXWWWWWWWWWWWWWVVVVVVVVUUUUUUUUUUTUTTTTSSSSSSSU™šš™™™™™™™™™™™™™™˜˜˜˜˜˜—————————––––––•–••••••••••””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒ‹‹63333222211110000///.//.-.--,,,,++++++****)*))((((''''&&&%%%%%%%$$$##""""!"!!! ˜˜————––•–••””””““““““’’’‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡†††……………„„„„„ƒƒƒƒ‚‚‚‚}uttttttstssssssrrrrrrrqqqqqqqqqqqpppx‘‘ŽŽŽŽŽŽ‹‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††††††††††††††………………cXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVVUUUUUUUUUUUUTTTTSTTSSS™™™šš™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒŒk3333222222111000////....-----,,,+++++****)))))(((''''&&&&&%%%%%%$$$####"""!!!! ™˜—˜——––––•–•”•”””“““““’’“’‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒ‚‚‚‚‚vuuttttttttssssssrrrrrrrrqqqqqqqqqqqpppƒ‘ŽŽˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡††††††††††††††…………sYYYXXXXXXXXXWWWWWWWWWWWWWWVVVVVUUUUUUUUUUUUTUTTTTTTSSj™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•••••••••••””””””””“““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒP3333222222111000/////....---,-,,,+++++****))))(((('''&&&&&%%%%%%$$$###""""!"!! ˜˜˜˜————––––••”•”””“““““““’’’‘‘‘ŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆˆˆ‡ˆ‡‡‡‡††††…………„…„„ƒƒƒƒ‚‚‚yuuuuutttttttsssssssrrrrrrrrqqqqqqqqqqqpprŽŒ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††††………[YYXYXXXXXXXXXWWWWWWWWWWWWWWVVVVVVUVUUUUUUUUUUTTTTTTSZ™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒ=43332222211110000/////....---,,,,+++++****))))((((''''&&&%%%%%%$%$$$#""""""!!!! ™˜˜˜˜˜——–––•–•••”””“”“““““’’’‘‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒ‚}uuuuuuuuttttttstssssssrrrrrrrqqqqqqqqqqqqppu‘ŽŠˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††††††††……`YYYYXXXXXXXXXXXXWWWWWWWWWWWWWVVVVVUUUUUUUUUUUUUUTTTTT‰™™™™™™™™™™š™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————––––––––••••••••••”•””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒw333323222221110000////./..----,,,,,+++++***)))(((((('''&&&%%%%%%$$$######""!!!!! ™™˜˜˜———–——––••••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŽŒŒ‹Œ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††…†……„„„„„ƒ‚xuuuuuuuuuuutttttttssssssrsrrrrrqqqqqqqqqqqqpp{Žˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††††††††hYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVUVUUUUUUUUUTTTTlšš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜—˜——————–––––––•–••••••••”•”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒS4433322222211110000///......---,,,+++++*****)))((((''''&&&%%%%%%$$$$####""""!!  ™™™™˜˜————–—–––••••”””““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††††………„…„ƒvuuuuuuuuuuuutttttttssssssssrrrrrqrqqqqqqqqqqqpp€Œˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††††††††††pZZYYYYYXYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUUUUUUUUUUUUTTYšššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•–•••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŒŒŒŒŒŒ;4433332222211100100////....---,,,,++++*****)))(()((''''&&&%%%%%$$$$###"""""!!!! ™™™™˜˜˜˜————––––••••”””“““““’’’’’‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††††…„…ƒ{vvvuuuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqqqqqpppp„ŽŠˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††††††vZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVWVVVVVUUUUUUUUUUTTšššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————–––––••••••••••••”•”””””””““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒm54333332222211110000////....--,-,+,,++++****))))((('(''''&&&%%%%%$$$$#$##""!"!!! ™™™™™™˜˜——˜——–––•–•••”””“““““’“’’‘‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹Š‹ŠŠ‰‰‰ˆ‰ˆˆˆ‡‡‡‡‡†††………‚vvvvvvvuvuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqpqqqˆ‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†††††††z[ZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVUUUUUUUUUUTdššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–—––––––••••••••••”•””””””“““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒG44443322222221100000///./...---,,,,,+++++++**)))(((('''&'&&&%%%%%%$$#$#"""""!!! š™™™™™™˜˜˜————–––••••”””””““““““’’’’‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††…‚vvvvvvvvvuuuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqqqpq‰Žˆˆˆˆˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††|\ZZZZZZZYYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVUUUUUUUUUU•ššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜————————––––––••••••••••”””””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒ…54444333222212111000////.....----,,,++++++***))))(((''''''&&%%%%%%$$$#$###"""""!! šš™™™™˜˜˜˜˜————–––––••””””““““““’’’‘’‘‘‘‘ŽŽŽŽŽŒŒŒŒŒ‹‹Š‹Š‰‰‰‰‰ˆˆˆˆ‡‡‡‡†††vvvvvvvvvvvuuuuuuuuuututtttttsssssssrrrrrrrrrqqqqqqqqqqpqˆŽ‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†††{\[[ZZZZZZZYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVUUUUUUUlššššššššššš™™™™™™™™™™š™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒR444433332222211100000////...----,,,,++++++**)))))((((('''&&&&%%%%%$$$#####"""!"!!! ššš™™™™™˜˜˜—————–––––•••””””““““’’’’’’‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠ‰Š‰‰ˆ‰ˆˆˆˆ‡‡‡‡wvvvvvvvvvvvvuvuuuuuuuuttttttttssssssrssrrrrrrrqqqqqqqqqpqq„‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†v\[[[ZZZZZZZZYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVUUUUUWššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜——˜—————––––––––••••••••••”””””””“““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒ75543333322222111111000////.-.---,,,+++++++***)*))()(('''&&&&&%%%%%%$$$####"""!"!! ›šš™š™™™™™˜˜˜˜˜———––•••••””””“““““’’’‘’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡~wwwvvvvvvvvvvvuuuuuuuuuuuuttttttsssssssssrrrrrrrrqqqqqqqqqqpp€‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡q[[[[[[ZZZZZZZYZYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUVUUUqššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–—–––––••••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒY544444333222221111100/0///.....--,,,,++++++**)*)))(((''''''&&%%%%%%%$$$######"""!!! ›šššš™™™™™˜˜˜˜—————–––•••”””””“““““’’’‘’‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡wwwwwvvvvvvvvvvvvuuuuuuuuuututtttttsssssssssrrrrqqrqqqqqqqqqpqp{‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡j\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVVUUWšššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—————————–––––––•••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒ855443333332222211101000///...--,-,,,,,+++++*****))()('('''&&&%&%%%%%$$$$####"""!!!! ››šššš™™™™™™˜˜˜—˜———–––••••”””“““““““’’’‘‘‘ŽŽŽŽŽŒŒ‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆ€wwwwwwwvvvvvvvvvvvvvuuuuuuuuuuttttttttsssssssrrrrrrrqqqqqqqqqqqqpu‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†b\\\\[[[[[[[[ZZZZZZYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVUUqšššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––••••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒY5444443333222221111100////.....--,,,,,+++++++**)*)((((('''''&&&%%%%%%$$$###""""""!! ››š›ššš™™™™™˜™˜˜˜˜—––––––••””””““““““’“’‘‘’‘ŽŽŽŒŒŒŒ‹‹‹Š‹ŠŠŠŠ‰‰‰ˆxwxwwwwwvvvvvvvvvvvvvvvuuuuuuuuuuttttttttsssssrrrrrrrqqqqqqqqqqqppprƒŽ‹‹‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡u]\\\\\[[[[[[[[ZZZZZZYYYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWVVVVVVWššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—————–––––––•–••••••••••”•””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒ755544433323222211101000////...-.---,,,,+++++****)))(((((''''&&&&%%%%$$$$$####""!!!! œœ›››ššš™™™™™™˜˜˜˜˜——––––•–•••”””“““““’’’’‘‘ŽŽŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ƒxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuututtttttsssssssrrrrrrqrqqqqqqqqqqpppxŽ‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡f\\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWWWWVVVVVmšššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽS555444443332222211110000////....---,-,,++++++****)))((((('''&&&&%%%%%%$$$####""""!!!!! œ›››››ššš™™™™™˜˜˜˜————––––•••”•”””“““““’’’‘‘’‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠ‰‰…xxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuutttttttttsssssrsrrrrrrrqqqqqqqqqqpppr€Ž‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡q^\\\\\\\\\\[\[[[[[ZZZZZZZZYZYYYYYYXXXXXXXXXXWWWWWWWWWWWWWVVVV˜šššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————––––––––•••••••••••””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ‰75544543433332222111110000///...-----,,,,++++++***))))((('''''&&&%%%%%%%$#$###""""!!!!! œœ››››ššššš™™™™™™˜˜˜˜—–—–––••”••””””““““’“’’‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹ŠŠˆxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttsssssssssrrrrrrrqqqqqqqqqpqpppt†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡ya]]]]]\\\\\\\\\[[[[[[[ZZZZZZYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWWVVdšššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽH655444443333222222111100/0///....----,,,+++++******))))((('''&&&&%%%%%%$$$$####""!!!!! œœœœ››››ššš™™™™™™˜˜˜˜———–—––•••••”””“““““’’’’’‘‘‘ŽŽŽŒ‹Œ‹‹‹Š‰yxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttssssssssrrrrrrrqqqqqqqqqqpppppu†ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡{d]]]]]]]\\\\\\\\[[\[[[[Z[ZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWWV†šššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••–••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽs66554444343333222211110000////..-.-----,,,+++++*+**))))(((''''&&&&%%%%%$$$$$###"""""!!!! œœœœœ›››››ššš™™™™˜˜˜˜˜————–––••••””””““““““’’‘‘‘‘‘ŽŽŒŒŒŒ‹‹‹‹yxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttsssssssrrrrrrqqqqqqqqqqqqppppps‚‹ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Šˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡ˆ‡vc]]]]]]]]\\\\\\\\\\\\[[[[[[[Z[ZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWWWWWW\›ššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜————————–––––––––••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ<655555444333332222111100000///....----,,,+++++++****)))(((('''&&&&%%%%%%$$#$##"#""""!! œœœœœ››››ššš™™™™™™˜˜˜——————––••••”””“”““““’“’‘‘‘‘‘‘ŽŽŽŒŒŒ‹‹…yyxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvuuuuuuuuuututtttttsstssssrsrrrrrrrrqqqqqqqqqpppppppru‡ŒŒ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆˆˆˆˆƒl`]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYXXXXXXXXXXXXXWWWWWWWWWWs›››šššššššššššššššššššššš™™™™™™™™™™™™š™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••”””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽZ665555544343332222222110000/0//.....---,,++++++******)))))(((''&&&&&%%%%%$$$$####""""!!! œœœ›››ššššš™™™™™™˜˜˜˜—–——––––•”•””“”“““““’’‘‘‘‘‘ŽŽŽŒŒŒŠyyyyyxxxxxxxxwwxwwwwwvvvvvvvvvvvvvuvuuuuuuuuuutttttttsssssssrsrrrrrqqqqqqqqqqqpppppplggkv†Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‰ˆˆˆˆˆˆˆˆˆoc^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYYXXXXXXXXXXXXWWWWWWWWW™››šššššššššššššššššššššššš™š™™™™™™™™™š™™™™™™™™™™™™™™˜˜˜˜˜—˜˜——————–––––––•••••••••••””””””””“““““““““““’“’’’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŠ66655554444333222222111100000/////..----,,+++++++****)))(((('('''&&%&%%%%%$$$###""#"""!!! œœœ›››šššš™™™™™™˜˜˜˜———––––•••”””””““““““’’’’‘‘‘‘ŽŽŽŒ}yyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttttsssssssrrrrrrqqqqqqqqqqqppppnggggggioy†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ˆˆˆˆˆ‚sha_^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXXXWWWWWWW`››››š›šššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••”•””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽB7656554544433332222211110010////./.----,,,+,,+++++***)))(()((('''&&&&%%%%%$$$#$##"""""!!! žžœœœœ››››ššš™™™™™™™˜˜˜˜———–—––•–””””””“““““’’’’‘‘‘‘‘‘ŽŽŽŽ…zyyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuutttttttsssssrsrrrrrqrqqqqqqqqqqqppggggggggggfgimsy~ƒ‡‰‹‹‰‡ƒ~vnhc`_____^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[\[[[[[ZZZZZZZZYZYYYYYYXXXXXXXXXXWWWWWWWx›››››››ššššššššššššššššššššššššš™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––••••••••••”””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽa776655544444433333222211110000//./....--,-,,++++++*****))(((((''''&&&&%%%%%$$$####""""!"!!žžœœœ›››››šš™š™™™™™˜˜˜˜———–—–––••”””””““““““’’’‘‘‘‘ŽŽŽ‹zyyyyyyyyxxxxxxxxxxwxwwwwwwvvvvvvvvvvvvvuuuuuuuuututtttttttsssssssrrrrrqrqqqqqqqqqqqhggggggggggggfffffffffffffff```________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZYYYYYYXYXXXXXXXXXXWWWWWW››››››››ššššššššššššššššššššššššš™™™™™™™™™š™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••••”””””“””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ76766555455443432322222111000000//....---,,,,,++++++***))))(('(''''&&&&%%%%%$$$###"##"!""!žžžžœœœœœ››šššš™™™™™™™˜˜˜———–––––•••””””“““““’’’’’‘‘‘ŽŽŽŽŽzzzyyyyyyyyyxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuuuuttttttssssssssssrrrrrrrqqqqqqqqmhgggggggggggggffffffffffffffd```______^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZYZYYYYXYXXXXXXXXXXXXWW`››››››››š›ššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––•••••••••••”””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽB776665665544434332222221110000/////...---,,,,,++++**+***)))(((''&''&&&&%%%%%$$$$#####""!!Ÿžžžžœœ›œœ››ššššš™™™™˜˜˜˜˜˜———––––••••””””“““““’’’’‘‘‘‘‘ŽŽŽŽŠzzzzyzyyyyyyxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqqqohhgggggggggggggfgfffffffffffffa````______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYXYXXXXXXXXXWXWt›››››››››››ššššššššššššššššššššššš™š™™™™™™™š™™™™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ[777766665544443333332222211000/0///.....---,,,,,++++*****))))((('''&&&&&%%%%%%$$$###"#""!ŸŸžžžœœœœœ›››šššš™™™™™™™˜˜˜————–––•–••”””””““““““’’’‘‘‘ŽŽŽŽ}zzzzzzyyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuttttttttsssssssrrrrrrrqrqqqqrghhhgggggggggggggffffffffffffffe```_________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXXW››››››››››š›ššššššššššššššššššššššššš™™™™™™™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––––••••••••••””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ77777666655554443323222222211000/////....----,,+++++++***)))()((('''&&&&&%%%%%$%$$$##""""ŸŸŸžžžœœœœ›››š›ššš™™™™™™™˜˜———–––––••••”””“““““““’’’‘‘‘މ{zzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuttttttttssssssrrrrrrrqqqqqlhhhhgggggggggggggggfffffffffffffb```_`______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXX[››››››››››››››ššššššššššššššššššššššš™š™™™™™™š™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽ;87776666555554443332222221111000////....----,,,,++++++****)))((((('''&&&%%%%%$$$$$$###""Ÿ ŸžŸžžœœœœ››š›šš™™™™™™˜˜˜˜———–—––•••••”””““““““’’‘‘‘’‘Ž|{{zzzzzzyzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuututttttssssssssrrrrrrrrqqphhhhhhhghggggggggggggfffffffffffff``````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZYYYYYYYYYXXXXXXXXf››››››››››››››šššššššššššššššššššššššššš™™™™™™š™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––••••••••••””””””““““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽJ877776665555544443323222211111000/////...----,,,,+++++****))))((((''''&&&&%%%%%%$$$###"" ŸŸŸžžžžœœœœœ››››šššš™™™™™˜˜˜˜˜————––•–••”””””“““““’“’‘‘‘‘‘‰{{{zzzzzzzzzyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvvuuuuuuuuuuutttttssssssssrsrrrrrqrihhhhhhhhggggggggggggggffffffffffffb`````________^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXyœ›››››››››››››››šššššššššššššššššššššššš™™™™™™™š™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽb8887777666555544444332322211110010/0/......--,,,,,+++++*****))())('(''&&&&&%%%%%$%$##### Ÿ ŸŸžžžžœœœ›œ›››ššššš™™™™™™˜—————––––••””””””“““““’“’’‘‘‘‘}{{{{zzzzzzzzzzyyyyyyyxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuuuuutttttttttsssssssrrrrrrohhhhhhhhhgggggggggggggggfffffffffffe``````________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZYZYYYYYYYXXXXXX’œœ›››››››››››››››šššššššššššššššššššššššš™™™™™™šš™™™™™™™™™™™™™™™™˜˜˜˜˜˜——˜——————––––––•••••••••••••””””””“““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ‚88878777665555545433332222222111000/0//.....----,,,,+++++**)*))))((((''&&&&&&%%%%$$$####  Ÿ ŸŸŸžžžœœœœœ›››ššš™™™™™™˜˜˜˜————–––––••”•”””““““’’’’’‘‘‘‘Œ{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuuuuttttttttssssssssrrrrhhhhhhhhhhhhggggggggggggggfffffffffffa`````_`______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYXXXXXZœœœœ››››››››››››››ššššššššššššššššššššššššššš™™™™š™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————––––––––••••••••••••””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ;88877777666655544433333222221111000////...-.----,,++++++****))))((((''''&&&&%%%%%$$$$##    ŸŸŸŸžžžœœœœ››ššššš™™™™™™˜˜˜—————–––•••••”””“““““““’’‘‘‘‘‘‚{{{{{{{{zzzzzzzzyyyyyxyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttttstsssssrrrrmhhhhhhhhhhhhhgggggggggggggfffffffffffd```````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXaœœœœœœœ›››››››››››š›šššššššššššššššššššššššš™™™™™™šš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––••••••••••••”””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽD8888777766666554444443333222211110000//./...------,,+++++****)))))(((''''&&&%&%%%%%$$$#     ŸŸŸŸžžžžœœœœœ››š›ššš™™™™™˜˜˜˜————––•–••••””””“““““’’’’‘‘‘Ž||{{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuutttttttssssssssrqhhhhhhhhhhhhhhhghggggggggggggffffffffff```````_`______^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\[[\[[[[[ZZZZZZZZYYYYYYXYXXmœœœœœœ››››››››››››››ššššššššššššššššššššššššš™™™™™ššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽS98888877766666555544443332222211101000//./....----,,,,+++++***)*)))(('''''&&&&%%%%%$$$$      ŸŸŸžžžžžžœœœœœ›››š™š™™™™™™˜˜˜˜———–—–––••””””“““““““’’’‘‘ˆ||||{|{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuutttttttssssssrlhhhhhhhhhhhhhhhgggggggggggggggfffffffffc`````````_______^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYXX|œœœœœœœœœ›››››››››››š›ššššššššššššššššššššššššš™™™™ššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––•••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽf9988888777666665554444332222222111110000///....---,,,+,+++++***)*)((((((''&'&&&%%%%%$$$¡¡     ŸŸŸŸžžžœœœ›››ššššš™™™™™˜˜˜˜˜———–—––•••””””””““““’’’‘|||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvuuuuuuuuuutttttttssssssqhhhhhhhhhhhhhhhhhghggggggggggggffffffffff`````````______^_^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[\[[[[[[ZZZZZZZYYYYYYYYœœœœœœœ›››››››››››››››››ššššššššššššššššššššššššš™™šššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•–•–••••••••••””””””“““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽ~99988888777767665545444333322222111110000///....----,,,+++++******))((('(''&'&&%%%%%%%$¡¡¡     ŸŸŸžŸžžžœœœœœ›››ššš™™™™™˜˜˜˜˜———–––––••••””””““““’’’Ž|||||||{{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuutttttstsssskhhhhhhhhhhhhhhhhhhhgggggggggggggfffffffffa`````````_______^^^^^^^^^]]]]]]]]]]]\]\\\\\\\\\[\[[[[[ZZZZZZZZYYYYYYZœœœœœœœœœœ››››››››››››››šššššššššššššššššššššššššš™™ššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜—————–—–––––––••••••••••”””””””“““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽ:999888887776666665545434432322222111100////.....----,,,++++++*+**)))(((((''''&&&&%%%%%¡¡¡¡     ŸŸŸŸžžžžœœœ››››šššš™™™™™™˜˜˜————––––••••”””“““““““’‡|||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwxwwwwwwwvvvvvvvvvvvuuuuuuuuuuuuuttttttssrhhhhhhhhhhhhhhhhhhhhhgggggggggggggggffffffd```````````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZZZZZYYYYY\œœœœœœœœœœœœ›››››››››››››››ššššššššššššššššššššššš™™™ššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜———————–––––––•••••••••••”””””””““““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽ>:9998888877767666655444343332222221111000///....-.---,,,,++++****)))))((((''''&&&%%%%%¡¡¡¡¡     Ÿ ŸŸŸžžžžœœœœ›››››ššš™™™™™™˜˜˜˜——–—––••–••””””“““““’||||||||||{{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvvvvvuuuuuuuuututtttttsslhhhhhhhhhhhhhhhhhhhhhggggggggggggggfgffffffaa````````_`______^_^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[Z[ZZZZZYZYY`œœœœœœœœœœœœ››››››››››››››››ššššššššššššššššššššššš™™šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––•––•••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘ŽŽŽŽŽC:999888887777666666544544333222222111110000/////...---,,,,++++++**)*)))(('(''''&&&%%%%¢¡¡¡¡      ŸŸŸžžžžžžœœœœ››››šššš™™™™˜˜˜˜—————–––•••••””””“““||||||||||{{{{{{{{zzzzzzzyyyyyyyyyxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuvuuuuuuuttttttttrhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggfgfffffaa``````````_________^^^^^^^]]]]]]]]]]]]]\\\\\\\\[[[[[[[[Z[ZZZZZZYYYgœœœœœœœœœœœœœœ››››››››››››››ššššššššššššššššššššššššš™šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––•––••••••••••””””””““““““““““““’“’’’’’’’‘‘‘‘‘‘‘‘‘‘ŽŽŽŽL::999988887777667665554444433332222211100000///...-.--,,,,,++++**+***)))()((''''&&&%%%¢¢¢¡¡¡¡     ŸŸŸŸžžžžœœœœ›››ššššš™™™™˜™˜——˜———––––•••”””””““Š||||||||||||{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvuvuuuuuuuuuttttttnhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggfffffca```````````_________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[ZZZZZZZZYYnœœœœœœœœœœœœœœœ›››››››››››››››šššššššššššššššššššššššššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜—————–––––––•–••••••••••””””””“”““““““““““““’’’’’’’‘’‘‘‘‘‘‘‘‘‘‘ŽŽŽU:9:999988888877666665554444332332222221110000////...----,,,,+++++***)*))(((('('&'&&&&%¢£¢¢¡¡¡ ¡     ŸŸžŸžžœœœœœœ›››šššš™™™™˜˜˜˜————–—––––••””””““|||||||||||||{{{{{{{{{zzzzzyyyyyyyxxxxxxxxxxxxxwwwwwvwvvvvvvvvvvvvvuuuuuuuuuuuttsiihhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggffffeaaaa`````````_________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZZZwœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššššššššš™šššššššš™™™™™™™™™™™™™™˜™˜˜˜˜˜˜˜——————––––––––•••••••••••””””””””“““““““““““““’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽ`::::99898888887776666655444443332222221110000///./...--,-,,,,+++++****)))()((('''&&&&%£¢¢¢¢¡¡¡       ŸŸŸžžžžœœœ›œ›››šš™š™™™™™™˜˜˜˜————–••••••””““||||||||||||||{{{{{{{{z{zzzzzzyyyyyyxxxxxxxxxxxxwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuttoiihhhihhhhhhhhhhhhhhhhhhhhhghggggggggggggggfffaaaa```````````_______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZ~œœœœœœœœœœœœœœœœœœ›››››››››››š›ššššššššššššššššššššššššš›ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————––––––••••••••••••””””””“”““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŽŽi;:::::99988888777766666554444333222222221100000///.....----,,+++++++**)*)))(((((''&&&%££¢£¢¢¡¡¡¡     ŸŸŸŸŸžžžœœ››››ššššš™™™™™˜˜˜————––––••••”””|||||||||||||||||{{{{{{zzzzzzzzyyyyyyyxyxxxxxxxxwwwwwwwvwvvvvvvvvvvvuuuuuuuuuuuutiiiiihihhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggfgfbaa`a``````````_`______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZ†œœœœœœœœœœœœœœœœ›œœ››››››››››››››ššššššššššššššššššššššš›ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘s;;::::999998888777767665554444433322222221110000/////...--,,,,,++++++***)*)(((('''''&&¤£££¢¢¢¢¡¡      ŸŸŸžžžžžœœœ›››››š™™™™™™™˜˜˜—————–––•–••””Œ}|||||||||||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuriiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggfcaaaa`a```````````____^__^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZœœœœœœœœœœœœœœœœœœ›››››››››››››››››ššššššššššššššššššššš›››šššššš™™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–––––––––••••••••”•””””””“““““““““““““’“’’’’’‘‘‘‘‘‘‘‘‘‘‘‘{;;;::::99898888877777666555454433332222221111000/0////..-----,,,+++++******)()((('''&'¤¤££¢£¢¢¢¡¡¡¡     ŸŸŸžžžžžœœœœ››››šš™™™™™™˜˜˜˜————––––•••”†}|}|||||||||||||||{|{{{{{{{zzzzzzyyyyyyyxxxxxxxxxxxxwwwwwvwvvvvvvvvvvvvvvuuuuuuumiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggfeaaaaa````````````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZ“œœœœœœœœœœœœœœœœœ›œ›››››››››››››ššššššššššššššššššššššš›››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––••••••••••••”””””““““““““““““’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘ƒ<;;;::::99988888878766666555545444322322221111000////./..----,,,,++++*++*)))))((((''''¤££££££¢¢¢¡¡¡     ŸŸŸŸŸžžžžœœ›››››ššš™™™™™™˜˜˜˜˜——–—––––••~}}}|||||||||||||||{{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxxwwwwwwwwvvvvvvvvvvvvuuuuuuutiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggbaaaaaa```````````_______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZ˜œœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššššššššš›››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ˆ<<<;;;::::99888888777666655555443333322222221110000///.....---,,,,+++++****))))(((((''¤¤£££££¢¢¢¡¡¡¡       ŸŸžžžžžœœœœœœ›››šššš™™™™™˜™˜˜˜—–––––•••}}}}|}||||||||||||||||{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwxwwwwwwwvvvvvvvvvvvvuuuuuuqiiiiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhggggggggggggggbaaaaaaa````````````_______^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZšœœœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššššš››š›ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““““““““““’’’’’’‘’‘‘‘‘‘‘‘‘‘<<;;;;;:::9:9988888777777656555543343322222211110000//./....--,-,,,+++++****)))))(((('¥¤¤¤££££¢¢¢¡¡¡ ¡    ŸŸŸŸŸžžžžœœœ›››››š™š™™™™˜™™˜—˜———–—••“}}}}}}|||||||||||||||||{{{{{{{{zzzzzzyyyyyyxyxxxxxxxxxxwwwwwwvwvvvvvvvvvvvvuuuuuliiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggbbaaaaaaaaa`````````_______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššššššššššššš››››š›šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————––––––••••••••••••””””””””“““““““““““’“’’’’’‘‘‘‘‘‘‘‘‘‘‘Ž<<<<;;;;::::9998888887776765655544443333322221111000///./....---,,,,,+++++***)))((((((¥¤¥¤¤££££¢¢¡¢¡¡¡¡     ŸŸŸžžžžœœœœœ›››šššš™™™™™™˜˜˜˜———–––‘}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzzyyyyyyyxxxxxxxxxxwwwwwwvwvvvvvvvvvvvvvuuuiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggggggggcbbbaaaaaaa``````````_______^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[œœœœœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššššš››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘Ž==<<;;;;;:::9:9988888877766655555444443332222211100000//.//...---,,,++++++****))))((((¥¥¥¤¤¤¤££££¢¢¡¡¡¡       ŸŸžžžžžœœœœ››››šššš™™™™™˜˜˜˜——–––—Ž~~}}}}}}||||||||||||||||{{{{{{{z{zzzzzzzyyyyyyyxxxxxxxxxxwxwwwwvwvvvvvvvvvvvuvuriiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhggggggggggcbbabaaaaaa``````````_______^_^^^^^^^]]]]]]]]]]]]]\]\\\\\\\\[[[[[[›œœœœœœœœœœœœœœœœœœ›››››››››››››››šššššššššššššššššš›››››››šššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜——————–––––––––••••••••••”””””””“““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘Ž===<<<;;;::::99998888887767666555544443333222221111100//////.-.----,,,,+++++*****)()((¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡     ŸŸŸŸŸžžžœœ›››š›šš™™™™™™˜™˜˜˜————Œ~~~}}}}}|}|||||||||||||||{{{{{{{z{zzzzzyzyyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvvvvumiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggdbbbbbaaaaaaaa``````````_____^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[˜œœœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššššš››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–—–––––––••••••••••”””””””””“““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‰===<<<;;;;;::::99998888877766665555544343332222211111000///.....--,,,,,,++++++****))((¥¦¥¥¤¤¤¤¤£££¢¢¢¢¢¡ ¡     ŸŸŸžžžžœœœœ››››šššš™™™™™™˜˜˜———–ˆ~~~~}}}}}|||||||||||||||||{{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvvuiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggggebbbbaaaaaaaa```````````________^^^^^^^^]]]]]]]]]]]\\]\\\\\\\\[[[[”œœœœœœœœœœœœœœœœ›››››››››››››››š›šššššššššššššššš››››››››šššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–—––––––••••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘„====<<<<;;;:::::9999888887777666555544443333222221111000////./...----,,,++++++****))))¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¡¡¡¡¡     ŸŸŸžžžžžœœœœœ›››››šššš™™™™™˜˜˜˜——…~~~~~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzyyyyyyxyxxxxxxxxxxwwwwwwwwvvvvvvvvvvsiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhghggggggebbbbbbaaaaaaaa``````````_______^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[œœœœœœœœœœœœœœœœœœœœ››››››››››››››šššššššššššššššš›››››››ššššššš™šš™™™™™™™™™™™™™™˜™˜˜˜˜˜—˜——————––––––––••••••••••••””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘}>===<<<<<;;;:::::99988888877777666555544433322222221110000/0//....---,,,,+++++**+**))(¦¦¥¥¥¥¤¤¤¤£¤£¢¢¢¢¡¡¡¡¡      ŸŸŸŸžžžœœœœœ›››šššš™™™™™˜˜˜˜˜˜ƒ~~~~~~}}}}}}}|||||||||||||||{{{{{{{zzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwvvvvvvvvvpiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhgggggggfbbbbbbbaaaaaaa````````````______^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[†œœœœœœœœœœœœœœœœ›œ››››››››››››››ššššššššššššššš›œ›››››››ššššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–•••••••••”””””””””“““““““““““’“’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘t>==>=<<<<<;;;;::::99988888777776665654444443322222222111000///.....---,,,,+++++****)))¦¦¦¦¥¥¤¤¤¤£¤££¢£¢¢¢¡¡¡      ŸŸŸŸžžžžžœœœ›››››šššš™™™™˜™˜˜—~~~~}~~}}}}}}}||||||||||||||||{{{{{{{{zzzzzyyyyyyyyxxxxxxxxxxxxwwwwwwvvvvvvvvvjiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggggbbbbbbbbaaaaaaaa```````````_______^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[œœœœœœœœœœœœœœœœœœ›››››››››››››ššššššššššššššššœœœ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••”””””””““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘k>>>=====<<<<;;:::::999898888877666665555443433322222211001000//./..-.---,,,+++++++**))¦¦¦¦¥¥¥¥¤¤¤¤£¤£¢£¢¢¢¡¡¡      ŸŸŸŸžžžžœœœœ›››››ššš™™™™™™˜˜˜€~~~~~~~}}}}}}||||||||||||||||||{{{{{{{zzzzzzzyyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvviiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhgggggcbcbbbbbbaaaaaaaa`````````_`______^_^^^^^^^]]]]]]]]]]]\]\\\\\\\\\xœœœœœœœœœœœœœœœœœœœ›››››››››››››š›šššššššššššššœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––•––•••••••••••””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘‘‘‘‘b>>>>>====<<;;;;;;::99999888887776666655455443333222222111110000//.....---,,,,+++++****§¦¦¦¦¦¥¥¥¥¤¤££¤£¢¢¢¢¢¢¡¡       ŸŸŸžžžœœœœœœ›››šš™™™™™™˜˜˜~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxwwwwwwvvvvvvtiiiiiiiiiiiiiiiiiiiiiiihihhhhhhhhhhhhhhhhhhhhhhhhggggcccccbbbbbaaaaaaa```````````________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\qœœœœœœœœœœœœœœœœ›œ››››››››››››››ššššššššššššš›œœ›œ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––––•••••••••••”””””””“““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘X>>>>>>====<<;;;:;:::99999888887776666555554443333222222111000/////.....--,,,,++++++***§¦¦¦¦¦¥¥¥¥¥¤¤£¤££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžœœœœ››››šššš™™™™™™˜~~~~~~~~}}}}}}}}|||||||||||||||{|{{{{{{{{zzzzyzzyyyyyyxxxxxxxxxxxwwwwwwwvvvvpiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhggggccccbbbbbbbaaaaaaaa``````````______^_^^^^^^^^]]]]]]]]]]]]\\\\\\\\jžœœœœœœœœœœœœœœœœœœœœ››››››››››››ššššššššššššš›œœœœ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––––––––••••••••••••”””””“”““““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘Q?>>>>>>==<<<<<;<;;;::999988888877766666554444443332222211111100///./....----,,,++++++*§§§¦¦¦¦¥¥¥¥¤¥¤¤££££¢£¢¢¡¡¡ ¡    ŸŸŸŸžžžžœœœ››››ššššš™™™™™€~~~~~}}~}}}}}||||||||||||||||||{{{{{{{zzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwvvliiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhggcccccbbbbbbbaaaaaaaa```````````_______^^^^^^^]]]]]]]]]]]]]]\\\\\\cžžœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššš›œœœœœ›››››››šššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––•••••••••••••”””””””““““““““““““’’’’’’‘‘‘‘‘‘‘‘‘‘I???>>>>====<<<;;;;::::9999888888877776655555444433322222211110000////....---,,,+++++*+¨§§§¦¦¦¦¥¦¥¥¥¥¤¤£££¢¢¢¢¡¢¡¡ ¡    ŸŸŸŸŸžžžžœœœ›››››šššš™™™™‚~~~~~~}}}}}}}}|||||||||||||||{{|{{{{{{zzzzzzyyyyyyyxyxxxxxxxxxxxwwwwwwwwiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhggccccccbbbbbbbbaaaaaa`a``````````_______^^^^^^^]]]]]]]]]]]]\\\\\\\_žžžžœœœœœœœœœœœœœœœœœ›››››››››››››››››ššššššššššœœœœœœ›œ›››››ššššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—˜——————––––––••••••••••••””””””””““““““““““““’’’’’’’’’‘‘‘‘‘‘D@???>>>>>===<<<<;;;;:::::99888888877767655555444333322222211101000///....----,,,,+++++§§§§¦¦¦¦¦¥¥¥¥¥¤¤¤¤£££¢¢¢¡¢¡¡¡¡    ŸŸŸŸŸžžžœœœ››››ššššš™™™„~~~~~~~}}}}}}}}}|||||||||||||||{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxxxxwwwwwwwuiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhgccccccccbbbbbbbaaaaaa````````````_______^^^^^^]]]]]]]]]]]]]\\\\\\\žžžžœœœœœœœœœœœœœœœœœœœ››››››››››››››ššššššššššœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜˜——————––––––•–••••••••••””””””””““““““““““““’’’’’’’‘‘‘‘‘‘‘A????>>>>>=====<<;;;:;;::::999888888776765655544443333322221111000000//.....---,,,,++++¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤£££¢¢¢¢¡¡¡       ŸŸŸŸžžžžœœœœœ›››š›šš™™™‡€~~~~~~~~}}}}}}|||||||||||||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxxxxwwwwwtiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhfcccccccccbbbbbbaaaaaaaa``````````_______^_^^^^^^]]]]]]]]]]]]\\\\\\žžžžœœœœœœœœœœœœœœœœœœ››œ›››››››››››››ššššššššœœœœœœœ›œ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••”•””””””“““““““““““““’’’’’’’’‘‘‘‘‚@@???>>>>>>====<<<<;;;;;::::99998888877767665554443433332222211100000////...----,,,,+++¨¨¨¨§§§¦¦¦¦¥¥¥¥¤¤¤¤££££££¢¢¡¡¡¡     ŸŸŸŸžžžžœœœ›››ššššš™Š€~~~~~~}}}}}}}}||||||||||||||||{{{{{{{zzzzzzzyzyyyyyyxxxxxxxxxxxxwwwwqjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhfccccccccccbbbbbbbaaaaaaaa``````````______^^^^^^^^^]]]]]]]]]]]]\\\\žžžžžœœœœœœœœœœœœœœœœœœ›œ›››››››››››››š›šššššš›œœœœœœœœœœ›››››š›ššššššš™™™™™™™™™™™™™™™˜™˜˜˜˜˜—˜——————––––––––••••••••••””””””””““““““““““““’’’’’’’’‘‘‘k@@@?????>>>>====<<<;;;:;::::99988888878767666555544434332222211211000///./...----,,,,++©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¡¡¡¡¡     ŸŸŸŸŸžžžžœœœ›››››šš™Ž€~~~~~~~}~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzyyyyyyxxxxxxxxxxxxxwwwnjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhheccccccccccccbbbbbbaaaaaaaa``````````_____^_^^^^^^]^]]]]]]]]]]]]\\\ožžžžžžœœœœœœœœœœœœœœœœœ›œ›››››››››››››ššššššš›œœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—————————––––––••••••••••••”””””””“““““““““““““’’’’’’‘‘‘YAA@@@??>?>>>>=====<<<<;;;;;::99989888887776666555554443332222221110000////....-----,,,+©¨¨¨¨§§§¦¦¦¦¦¦¥¦¥¤¥¤¤¤££££¢¢¡¢¡¡¡¡     ŸŸŸŸžžžœœ››š›ššš‘€€€~~~~~~}~}}}}}||||||||||||||||||{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxxxwwkjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhheccccccccccccbbbbbbaabaaaaa```````````_______^^^^^^^]]]]]]]]]]]]\\\ežžžžžžžžœœœœœœœœœœœœœœœœ››››››››››››››››››šššš›œœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–––––––••–••••••••••”””””””““““““““““““’’’’’’’‘‘LAA@@@????>>>>>>===<<<;;;;;;::9999888888877666665555444433332222211110000///./..----,,,+©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¤¤£££££¢¢¢¢¡¡¡     Ÿ ŸŸžžžžžœœœœ›››››š•€€€~~~~~~~}}}}}}|||||||||||||||||{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxxxwxjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhdcccccccccccccbbbbbbbabaaaaa``````````_`_______^^^^^^]]]]]]]]]]]]]]]žžžžžžžžžœœœœœœœœœœœœœœœœœœœœ››››››››››››šššššššœœœœœœœœœœœœœ›››››››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””““““““““““““““’’’’’’’CA@A@@@@@???>>>>====<<<<<;;;;;::99998888877776666555544444332322221110100/0///....----,,©©©©©¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¢¡¡¡     Ÿ ŸŸžžžžœœœœ›››š˜€€€€€~~~~~~~~}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzzyyyyyyxxxxxxxxxxxwjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhdccccccccccccbbbbbbbbbaaaaaaa````````````_______^^^^^^^]]]]]]]]]]]]\”žžžžžžžžœœœœœœœœœœœœœœœœœ›œ››››››››››››››ššššœœœœœœœœœœœ›››››››ššššššš™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜———————––––––••••••••••••””””””“““““““““““““’’’’’’†AAA@A@@@@????>>>>====<<<<;;;;;:::::9988888777766666555444433322222211110000///./..--,--,©©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡    Ÿ ŸŸžžžžœœœ›œ›››™€€€€€€€~~~~~~~}}}}}}}}||||||||||||||||{{{{{{{z{zzzzzzzyyyyyyyxxxxxxxxxvjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhdccccccccccccccbbbbbbbbaaaaaaa````````````_____^^^^^^^^]]]]]]]]]]]]]|žžžžžžžžžžœœœœœœœœœœœœœœœœœ›››››››››››››››šššœœœœœœœœœœœœ›››››š›ššššš™šš™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––•••••••••••””””””””“““““““““““’’’’’iBBAA@@@@@@????>>>>=>===<<<<;;;:::9:99888888877776665554454433332222211110000//./...-----ªª©©©©¨¨§¨§§§¦¦¦¦¥¦¥¥¥¤¤¤¤£££¢¢¢¢¡¡¡¡¡     ŸŸŸžŸžžžœœœ››››€€€€€€~~~~~~}}}}}}}}}|||||||||||||||{{{{{{{{zzzzzzzzyyyyyyyxxxxxxxxujjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhdccccccccccccccccbbbbbbbaaaaaaa````X`````_________^^^^^^^]]]]]]]]]]]jžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››››››››šššœœœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––•••••••••••••”””””“”“““““““““““’’’’SBBAAAAA@@@@???>>>>>>===<=<<;;;;;;:::99998888877766666555444443323222222110000///./.-----ªª©©©©©¨¨¨§§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡¡     ŸŸŸžžžžœ›œ›››Š€€€€€~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxsjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhfcccccccccccccccccccbbbbbaaaaaaaaaa`````````_______^^^^^^^^]]]]]]]]]]`žžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››››››š›šœœœœœœœœœœœœœ››››››šššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————–––––––•••••••••”••”””””””““““““““““““’’FCCBBAAA@@@@@????>>>>>>===<<<<;;;;:::99998888877777766665454444332222211111100////....---«ªª©©©©¨¨¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¡¡¡¡¡     ŸŸŸŸžžžœœœœ›’€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{{{{{{{zzzzzzzyzyyyyyyxxxxxxrjjjjjjjjjjijiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhfdcccccccccccccccccbbbbbbbbaaaaaaaa``````````________^^^^^^^]]]]]]]]]]’žžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ››››››››››››››šœœœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜—————————–––––––••••••••••”•”””””””““““““““““““…CCBBBBBBAA@@@@????>>>>>=====<;;;;:::::99998888888776666655544443332222222111100/0///....-ªªªª©©©¨©©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¡¢¡¡      ŸŸŸžŸžžžžœœ›œ—€€€€€€€€€~~~~~~~}}}}}}|}||||||||||||||||{{{{{{{zzzzzzyzyyyyyyyxxxxxqjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhedddccccccccccccccccbbbbbbbabaaaaaaaa`````````_________^^^^^^]]]]]]]]]wžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ››››››››››››››œœœœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜—˜—————–—––––––•••••••••••”””””””““““““““““““dCCBBBBABAAA@@@@????>>>>=====<<<;;;;;;:99999888887877666555544443333222222111010000//./..-«««ªª©ª©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¥¤¤££££¢¢¢¡¢¡¡¡     ŸŸŸŸŸžžžœœœ›š€€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{{{{{{zzzzzzzyzyyyyyyxxxxxojjjjjjjjjjjjijiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhddddddccccccccccccccccbbbbbbbbaaaaa`a`````````_________^^^^^^^]]]]]]]]ežžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ››››››››››››œœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–••••••••••””””””””““““““““““NDCCBCBBBAAAA@@@???>?>>>>>=====<<<;;;:::::999888888777666665545444333322222121100000/....-«««ªªªªª©©¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¤¤£¤£££¢¢¢¡¢¡¡      ŸŸŸŸŸžžžžœœœœ†€€€€€€€€€€€~~~~~~}}}}}}}}|||||||||||||||||{{{{{{zzzzzzzzyyyyyyxxxxnjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhgddddddcdccccccccccccccbbbbbbbbaaaaaaaa`````````_`_______^^^^^^]]]]]]]]]žžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››››žžœœœœœœœœœœ››››››š››ššššš™š™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜———————––––––•••••••••••”•””””””““““““““““DDCCCCCBBAAAAA@A@?@???>>>>>=====<<<;;;;::::99988888877767666555544433323222211111000////..««««ªªª©©©©©©¨¨§§¦§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¢¡¡      Ÿ ŸŸžžžžœœœ‘€€€€€€€€€~~~~~~~}~}}}}}||}|||||||||||||||{{{{{{{zzzzzzzyyyyyyxyxxmjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhfddddddddcccccccccccccccccbbbbbbaaaaaa`````````````_______^^^^^^]^]]]]]]}žžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœ›œ›››››››››››žžœœœœœœœœœœœœ›››››š››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————–—––––––––••••••••••”””””””““““““““jDDDCCCCCBBBAAAA@@@@@????>>>>=====<<;<;;::::999988888887777666555544433323222221110000///..¬««««ªªª©©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤£¤£££¢¢¢¢¡¡¡      ŸŸŸŸŸžžžœœ˜€€€€€€€€€€€~~~~~~~}}}}}}}|||||||||||||||||{{{{{{{zzzzzzyyyyyyyyxxljjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhfeedddddddcccccccccccccccbcbbbbbbbaaaaaaaa````````_``______^^^^^^^^]]]]]fžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››››››žžžœœœœœœœœœœ››œ››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜————————–––––––••••••••••••”””””””“““““““ODDDDDCCCBBBBAAAA@A@?????>>>>>====<<<<;;;;;:::9999888887777666665554443333222222111100/////¬«««««ª«ªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¥¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡     ŸŸŸŸžžžœœ€€€€€€€€€€~~~~~~~}}}}}}}||||||||||||||||{{{{{{{{{zzzzzyyyyyyyykjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhheeeeddddddcccccccccccccccccbcbbbbabaaaaaa````````X```______^_^^^^^^^]]]]]œžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœ››››››››››žžžžœœœœœœœœœœœ››››››ššššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜——————–––––––––•••••••••••””””””””““““‘EDDDDDCCCCBCBBAAAAA@@@???>?>>>>>===<<<<;;;;:::::998888877776666655554444333222221111000/0//¬¬¬¬«««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžŽ€€€€€€€€€€~~~~~~~~}}}}}}}|||||||||||||||{|{{{{{{zzzzzzzyyyyyyykjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiihihgeeededddddddccccccccccccccccbcbbbbbbbaaaaaaa``````````_______^^^^^^^^]]]]xžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœ››››››››››žžžžžœœœœœœœœœœœ›œ››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––—–––––•••••••••••””””””””“““fEDEDDDDDCCCBBBBBAAA@@@@???>?>>>>====<<<<;;;:::::99888888877777666555544443333222221111100//¬¬¬¬«««ªªªªª©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£¤£££¢¢¢¡¡¡      ŸŸŸŸŸžžž˜€€€€€€€€€€€~~~~~~~~~}}}}}}}|||||||||||||||{{{{{{{zzzzzzzyyyyyyykkjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihfeeeeeeddddddddcccccccccccccccbcbbbbbaaaaaaaa```````````________^^^^^^]]]]cžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ›››››››žžžžžžœœœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜———————–––––––••••••••••••””””””““““KFEEEDDDDDDCCCBBBAAAA@A@@@????>>>>>===<<<<;;;;;;:::99888888777666665555544433332222111100000¬¬¬¬¬¬««ªªªªª©©©©¨¨¨¨§§¦¦¦¦¦¦¦¦¥¥¥¤¤¤¤££££¢¢¡¡¢¡¡      ŸŸŸžžžžžœ€€€€€€€€€€€€€~~~~~}~}}}}}}|}|||||||||||||||||{{{{{{zzzzzzyzyyykkkjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifeeeeeeddddddddccccccccccccccccbbbbbbbbbaaaaa`a``````````_______^^^^^^^]]]]‹žžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››››žžžžžžœœœœœœœœœœ›œ››››››››šššš™šš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––••••••••••••””””””””}FFEEEEDDDDCCCCCBBABAA@A@@@????>>>>>====<<<<;;;;:;:999988888887776666554554433333222222100000¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨§§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤££¢£¢¢¢¡¡¡¡      ŸŸŸŸžžž‘€€€€€€€€€€€€~~~~~~~~}}}}}}}|||||||||||||||{{{{{{{{{zzzzzzyzyykkkkjjjjjjjjjjjjjjjihgeca_^]\ZZZZZ[\^_abdeghiiiiiiigfeeeeeeeddddddddcccccccccccccccccbbbbbabaaaaaaa``````````________^^^^^^^]]]kžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›››››žŸžžžžžžœœœœœœœœœœ›œ››››››ššššššššš™™™™™™™™™™™™™™™˜™˜˜˜˜˜———————–––––––––••••••••••””””””“WFFFEEEDDDDDDCCCBBBBBAAAAA@@?????>>>>====<<<<;;:;:::::998888888776766655554444333322221211000­¬¬¬¬¬¬¬««ªªªª©ª©©©¨¨¨§¨§§§¦¦¦¦¥¥¥¥¤¤¤£¤££££¢¢¢¢¡¡       ŸŸŸžžžžž›€€€€€€€€€€~~~~~~~~~}}}}}}||||||||||||||||{|{{{{{{zzzzzzzyylkkkkjjjjjjjjihda][[[[ZZZZZZZZZZZZZZZZZZZZZZZ\`cfhifffeeeeeeeedddddddccccccccccccccccccbbbbbaaaaaaa`a``````````______^_^^^^^^^]^œžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœ››œŸžžžžžžžžœœœœœœœœœœœœ››››››š›ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””’HFFFFEEEDDDDDCDCCCCBBBAAA@A@?@??>>>>>>>>===<<<;;;;::::999898888777667655554444333222222211110­­¬­¬¬¬¬¬¬««ªªªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¡¡¡       ŸŸžŸŸžž‰€€€€€€€€€€€€€~~~~~}}}}}}}}}}||||||||||||||{|{{{{{{z{zzzzyzymkkkjkjjjiea[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZSR\cfeeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaaaa``````````_`______^^^^^^]^]sžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›œ›œŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––––••••••••••””””aGFFFFFEEEEDDDDCCCCCBBABBAA@@@@@????>>>>>>==<<<;;;;::::999998888877776665655544343332222221111®­­¬¬¬¬¬¬««««ªªªªª©©¨¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¤¤£¤££¢£¢¢¢¡¡¡¡     ŸŸŸŸžžžž˜€€€€€€€€€€~~~~~}}}}}}}|}||||||||||||||||{{{{{{zzzzzzzyokkkkjfa[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZHDDDDQ\ceeeeeddddddddccccccccccccccccbbbbbbbaaaaaaaa``````````________^^^^^^^]_žžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœ›œœŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————–––––––••••••••••”•””IGFGFFFFFEEEDDDDDCCCCBBBAAAA@@@@?????>>>>=>=<=<<<<;;;;:::9989888888876666555544443332222222211®®­­­­¬¬¬¬¬¬««ªªªªª©©©¨¨¨§¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡¡     ŸŸŸŸžžž„‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||||||||||{{{{{{{z{zzzzzpkie^[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZWDDDDDDDDJYbeeeedddddddcccccccccccccccbbcbbbbbaaaaaaa`aa``````````______^^^^^^^^xžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœœœœŸŸŸŸŸžžžžžžžžœœœœœœœœœ››››››››››šššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––––•–•••••••”••gHHHGFFFFEEEEDEDDDDDCCBBBBBAAAAA@@?@???>>>>====<=<<;;;;:;:9999988888777676666554444333322222211®­­­¬­¬¬¬¬¬«««««ªªª©ª©©¨¨¨¨§§§§¦¦¦¦¥¦¥¥¤¤¤¤£¤£££¢¢¢¢¡¡¡¡    ŸŸŸŸŸžž–€€€€€€€€€€€~~~~~~~}}}}}}||||||||||||||||{{{{{{{{{zzzxk][[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZJDDDDDDDDDDDIZceeeddddddcccccccccccccccbcbbbbbaaaaaaaa````````````_______^^^^^^^`žžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœœŸŸŸŸŸŸŸžžžžžžœœœœœœœœœœœ››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–––––––•–••••••••••KHHHGGFGFFFFEEEDDDDDCCCCCBBAAAAA@@?@???>>>>>>===<<<;;;;;;::::9989888877777666654454443332222221®®®­®­­¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££££¢¡¢¡¡¡     ŸŸŸŸŸžž‚‚‚€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||||||||||{|{{{{{zzuia^[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYEDDDDDDDDDDDDDDO_dddddddddcccccccccccccccbcbbbbbabaaaaa`````````````_______^^^^^^xžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœœŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————–––––••••••••••hHHHHGGGGFFFEFEEEDDDDDCDCCBBBBAAAAA@@@????>>>>>===<<<<;;;;:;::9999988887777766655555444333322222®®®®®­­­­¬¬¬¬«««««ªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤£££¢¢¢¢¢¢¢¡ ¡     ŸŸŸžž–‚‚€€€€€€€€€€~~~~~~~~}}}}}}||||||||||||||||{{{{{yqbbaa_[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZJDDDDDDDDDDDDDDDDDDYcdddddddccccccccccccccccbbbbbbbbaaaaa```````````_`_______^^^^^`žžžžžžžžžžžžžžžžžžžžžžžœœœœœœœœœœœœœœœžŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››š›šššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜————————––––––––•••••••KHHHHHHHGFFFFFFEEDDDDDDCCCCBBBAAAAA@@@?????>>>>>====<<<<;;;:::::99998888877766655555544333323222¯®®®®®­­­¬¬¬¬¬¬««««ªªªªª©©©¨¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¢¡¡     ŸŸŸŸžž…‚‚€€€€€€€€€€€~~~~~~~~~}}}}}}}||||||||||||||||{ynbbbaaa`[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZVDDDDDDDDDDDDDDDDDDDDCTadddddccccccccccccccccbbbbbbbbbaaaaaa`a`````````________^^^^^sžžžžžžŸŸ ¡£¤¦§¨©©ªªªª©©¨¦¥£¢¡ŸžœœœœœœœœœœœœœžŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––••••••cIIIIHHHHGGGGFFFEEEEEDDDDCCCCBBBAAAAA@@?@??>?>>>>>====<<<;;;;:::::9998888888776766665444433333322¯¯¯®®®®­­­­¬¬¬¬«¬«««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¢¢¡       ŸŸŸŸ™‚‚€€€€€€€€€€€~~~~~~~~}}}}}}|||||||||||||||ylbbbbbbba`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZHDDDDDDDDDDDDDDDDDDDDDCDQadddcccccccccccccccccbbbbbbbbaaaaaaa``````````_______^_^^^^_𡣦©ªªªªªªªªªªªªªªªªªªªªªªªªªªªª©¥¡ŸžœœœœœœœœœœœœœŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœœ››››››š›ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————–—––––•••••JJIIIHHHHGHGGGFFEEEEDDDDDDDCCBBBBBBAAAA@@@????>>>>>====<<<<;;;;;:::999988887777666565554444332322¯¯¯¯¯®®­®­­­¬¬¬¬¬««««ªª©ª©©©¨¨¨¨§§§§§¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¢¡¡¡       ŸŸž‹‚‚‚‚€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||||||ymbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZSDDDDDDDDDDDDDDDDDDDDDDCCCCQadddcccccccccccccccccbbbbbbabaaaaaa````````````______`djsz…«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª§¢Ÿœœœœœœœœœœ ŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœ›œ›œ›››››››ššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——˜—————–––––––•–•ZJJJIIIIHHHGHGGFFFFEEEEEDDDDCCCCBBBBAAAAA@@@????>>>>=>===<<<;;;;;::::99988888887766666554544333332°°¯¯®®®®®­­­­¬¬¬¬«««««ªªªª©©©©¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¢¢¡¡      ŸŸŸœ‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}|||||||||{obbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZGEDDDDDDDDDDDDDDDDDDDDDDDDDCCTcdcccccccccccccccccbcbbbbbbbaaaaa`a`````````_`_`dlwzzzzzzšªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¨£Ÿœœœœœœ ŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜———————––––––––{JJJJIIIIHIHGGGGGGFFEEEEEDDDDDDCCCBBBBAAA@@@@@?????>>>>>==<=<<<;;:;;:::9998888877776666655554434333°°¯¯®®®®­­®­­­¬¬¬¬«««««ªª©ª©©©©¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¤¤¤£¤£££¢¢¢¡¡¡¡¡     ŸŸŸ”‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}||||||sbbbbbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZMEEDEDDDDDDDDDDDDDDDDDDDDDDCDCCCZddcccccccccccccccccbbbbbbabaaaaa`aa```````bhr{zzzzzzzzz~««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¥ œœŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžœœœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜————————––––––PKJJJJJIIIHHIGHGGGGFFFEEEEDDDDDDCCCBBBBBBAA@@@@@???>>>>>>==<<<<<;;;;::::999988888877666565544543433°°°°¯¯¯¯®®®­­­­¬¬¬¬¬«««««ªªª©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤££¢£¢¢¡¡¡¡¡    ŸŸŸž‡‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}|||xfbbbbbbbbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZVEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCH_dddccccccccccccccccbbbbbbaaaaaaaaa```aiu{{z{zzzzzzzzzz‹«««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª§¡¡ŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœ›››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜˜————————––––fKJKJJJJJJIIHHHGGGGGGFFEFEEDEDDDDCCCCBBBBAB@@@@@@????>>>>>=====<<<;;;;::::98998888877766665555444443±°°¯¯¯¯®¯®®®­¬­­¬¬¬¬««««ª«ªªª©©¨¨¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¤¤¤¤¤£££¢£¡¢¡¡ ¡    ŸŸŸœ‚‚‚‚‚€€€€€€€€€€€€~~~~~~}}}}}}}||obbbbbbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZGEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCSccccccccccccccccccbbbbbbbaaaaaa``afr{{{{{{{{zzzzzzzzzz ««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª««¨£ŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››š›šššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———————––…LLKKKJJJJJJIIIHHHHGGGFFFFFEEEEDDDDCCCCBCBABAAAA@@?@???>>>>>>===<<<<<;:;;:::9998888888777766555544443°°°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨§§¦§¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¡¡¡¡      ŸŸ•‚‚‚‚‚€€€€€€€€€€€~~~~~~~}}}}}wbbbbbbbbbbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZLEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDDDDCCCCC\dcccccccccccccccccbbbbbbaaaaacn{{{{{{{{{{{{zzzzzzzzz«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«««««¥¡ŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››ššššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜————————–—SLLKKKJJJJJJJIIIIHHHHGGFFFFEEEEEDDDDDCCCCBBBBAAA@@@@@????>>>>>===<=<;;;;::::9:99888888777766665554544±°°°°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««ª«ªªª©©©©¨¨¨§§§¦§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¢¡¡      ŸŸŒ‚‚‚‚‚€€€€€€€€€~~~~~~~}}}}ocbbbbbbbbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZSEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDDDCDCCCCRcccccccccccccccccbcbbbbbbbfs{{{{{{{{{{{{{{zzzzzzzzzzŠ««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«««««««¨¢ŸŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ›››››››››ššššš™™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜—˜—————dLLLKKKKKJJJJJIJIIIHHHGHGGGFEFFEDDDDDDDDCCBCBAAAAA@@@@@???>>>>>>===<<<<;;:;::::99998888877766666554454±±±±°°°¯°®®¯®®®®­­¬¬¬¬¬«««««ªªªª©©©¨¨¨¨¨§§¦§¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¢¡ ¡     ŸŸ‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}zdcccccbbbbbbbbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZYFEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCE_cccccccccccccccccbbbbbhy{{{{{{{{{{{{{{{{{zzzzzzzzzz™««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬««««««««ª¢ŸŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœœœœœ›››››ššššššššš™™™™™™™™™™™™™™˜˜˜˜˜˜˜——————{MLMMLLLKKKKJJJJIIIIHHHGHHGGFFFFEEEEDDDDDCCCCBBBABA@@A@@????>>>>>=====<<<;;;:::9::998888887777666555444²±±°°°°°¯¯®¯¯®®®­­­¬¬¬¬¬¬«««ªªªª©©©©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤¤£¢£¢¢¢¡¡¡¡¡    Ÿœ‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~tcccccccbbbbbbbbbbbbbbbbbb\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZHEEEEEEEEEEEDDEDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCYcccccccccccccccccbbi{||||{{{{{{{{{{{{{{{{zzzzzzzzz|«««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª««««««««««««£ŸŸŸŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™™˜˜˜˜˜˜˜———–QNMMLMLLKKKJJJJJJIIIIHHHGGGGFFFFFEEDDDDDDDCCCBBBBAAA@@@@@@???>>>>>=>=<<<<<<;;;;:::999888887777666665555²±²±±±±°°°¯¯¯®®®®­­­¬¬¬¬¬«««««ªªª©©©©¨©¨¨¨¨§§§¦¦¦¦¥¥¥¥¥¤¥¤¤££¢£¢¢¢¡¡¡¡     Ÿ™‚‚‚‚‚‚€€€€€€€€€€€~~~~mccccccccccbbbbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[KFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCPcccccccccccccccci{|||||{{{{{{{{{{{{{{{{{{zzzzzzzzz‚«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬«««««««««««««£ŸŸŸŸŸŸŸžŸžžžžžžœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™˜˜˜˜˜˜˜——YNMNMLMMLLLKKKJJJJJIJIIHIHHGGGFFFFEEEDEDDDDCCCCCCBBBAAA@@@????>>>>>>====<<<<<;;:::::99998888787777666554²²²±±±°°°°¯¯¯®®®®­­­­­¬¬¬¬¬««««ª«ªª©©¨©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤¤££££¢¢¡¡¡      Ÿ•‚‚‚‚‚‚€€€€€€€€€€€~|fccccccccccccbbbbbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[MFFEEEEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDDDCCDCCCCCCG`cccccccccccchy|||||||{|{{{{{{{{{{{{{{{{{{zzzzzzzz‰««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬«««««««««««««ª¢ŸŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœ›››››››››ššššš™š™™™™™™™™™™™™™™™˜™˜˜˜˜˜dNNMNMMMLLLLKKKKKJJJJIIIIHHHHHGGFGFEFEEEEDDDDDCCCCBBBAAAAA@@@?????>>>>====<<<<;;;;;:::9999888888777766555²²²²±±±°°°°°¯¯®®®®®­­­­¬¬¬¬¬¬««««ªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡     ‘‚‚‚‚‚‚‚€€€€€€€€€€€€€yccccccccccccccbbbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[OFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCC]cccccccccft|||||||||||||{{{{{{{{{{{{{{{{zzzzzzzzz’««««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬«¬««««««««««««««§ ŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœ››››››››šššššššš™™™™™™™™™™™™™™™™˜˜˜˜rONONNMNMLMLLLKKKKKJJJJIJIIHHHHHGFFFFFFEEEEDDDDDCCCCBBBAAAA@@@@@????>>>>=====<<<<<;;;:::999888888787776666²²²²²±±±±°°°¯¯¯¯¯®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¥¤¤££££¢¢¢¢¢¡¡¡      ‚‚‚‚‚€€€€€€€€€€€wddccccccccccccccbbbbbbbbbbbbbbb\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCZccccccdo}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzz›«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬««««««««««««««««¥ŸŸŸŸŸŸžžžžžžžžœœœœœœœœœœœœœ›››››››ššššššš™™™™™™™™™™™™™™™˜˜˜€OOONNNNNMMMMLLKKKKKKJJJJJIIIHHHHGGGGFFFFEEEDDDDDCCCCCBBBBBAA@A@@????>>>>>=====<<<;;;;::::99998888877776665²²²²²±±±±°±°°°¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªª©©©©©©¨¨¨¨§¦§¦¦¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¢¡¡¡     Ÿ‚‚‚‚‚‚‚€€€€€€€€€€€€sddddcccccccccccccccbbbbbbbbbbbbb^[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCVcccci}}}|||||||||||||||{{{{{{{{{{{{{{{{zzzzzzzzz{¢«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬«««««««««««««««««¢ŸŸŸŸŸžžžžžžžžžœœœœœœœœœ›œ››››››šššššššš™™™™™™™™™™™™™™™™ŒROOOOONNNNMMMMLLLKKKKJJJJJIIIIHHHHHGGGGFFFEEEEDDDDDCCCCCBABAA@@@@@@????>>>>==>=<<<<<;;::::::999888887777666³³²²²²²²±±±°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢£¢¢¡¡¡¡      ‚‚‚‚‚‚‚‚€€€€€€€€€€€€qdddddcccccccccccccccbbbbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[QFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCSces}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzz}©««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸŸžžžžžžžžœœœœœœœœ›œœ›››››››šššššš™™™™™™™™™™™™™™™–UPPOOOOONNNMMMLMLLLKKKKKJJJJIIIIHHHHHHGGFFFFFEEEDDDDDCCCCBBBBAAA@@@@@????>>>>>>==<=<<<;;;;::9:99988888777676³³²²²²²²±±±±°°¯¯¯¯¯®®®®®­­­¬¬¬¬¬¬«««ªªª©©©©¨©©¨¨¨§§§¦¦¦¦¦¦¥¥¤¤¤¤¤££¢¢£¢¡¡¡¡¡¡     ’‚‚‚‚‚€€€€€€€pdddddddcccccccccccccccccbbbbbbbbbbb[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[OFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCU|}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{z{zzzzzz«««««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬¬««««««««««««««««««¢ŸŸŸŸžžžžžžžžœœœœœœœœœœœ›››››››šššššš™š™™™™™™™™™™™™™XPPPPPOOONNNNNMMMMLLKKKKJKJJJJIIIIHHHHGGGGFFFEEEEDEDDDDDCCBCBBBAAAA@@@@@???>>>>====<<<<<;;;;:::99989888877776³³³³²²²²±²±±±°°°¯°¯®®®®®­­­­¬¬¬¬¬¬¬««ªªªªª©©©¨¨¨§¨§¦¦¦¦¦¦¥¦¥¥¥¥¤¤£¤£££¢¢¢¢¡¡¡¡     •‚‚‚‚‚‚€€€€€oddddddddddccccccccccccccbbbbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[[[[[[MFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCDJNa|}}}}}}}|}||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzz€«««««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸŸŸŸžžžžžžžœœœœœœœœœœ››››››››šššššš™™™™™™™™™™™™YQQPPPPPPOONONNNMMMLLLLLKKKKJJJJIJIIHHHHGGFFFFFFEEEEDDDDDDCCCBBBABAAA@@@@???>>>>>>===<=<<<;;;;:::9998988887777´³³³²²²²²²±±±°°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬¬««ªªªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¢¢¡¡¡¡    Ÿ˜‚‚‚‚‚‚‚€€€€odddddddddddddcccccccccccccbbbbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[[[[[[KFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDCCCENNNNa|}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{zzzzzzzz€«««««ªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ª¡ŸŸŸžžžžžžžœœœœœœœœœœ›››››››››šššššš™™™™™™™™™™[QQQQPPPPPPOONNNNNMMMLLLLKKKKKJJJJJJIIHHHGGGGFFFFFEEEEEDDDDDCCBCBBBBAAAA@@@????>>>>=====<<;;;;;;:::999998888777´´³³³³²²²²²±±±±°°¯¯¯¯¯®®®®®­­­­¬¬¬¬¬«««ªªªª©ª©©©¨¨§§§§¦§¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¡¡¡¡ ¡     ›ƒ‚‚‚‚‚‚€€€pdddddddddddddcdcccccccccccccbbbbbbbbbba[[[[[[[[[[[[[[[[[[[[[[[[[ZJFGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCGNNNNNNb}}}}}}}}|}||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzz€«««««ª«ªªªªªªªªªªªªªªªªªªªªªªªªªª­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««£ŸŸŸžžžžžžžžœœœœœœœœ›œ›››››››ššššššš™™™™™™™ZRRQQQPQPPPPOOONNNMNMNLMLLLLKKKKJJJJIIIIIIIHHHGGGFFFEEEEDDDDDDCCCBBBBAAA@A@@@????>>>>=>===<<<;;;::::::9989888887´´³³³³³²²²²±±±±°°°¯°°¯¯¯®®®®­­­­¬¬¬¬«««««ªª©©©©©¨¨¨§¨§§¦§¦¦¦¦¦¥¥¤¤¤¤¤¤££¢¢¢¢¡¢¡¡¡     ž‚‚‚‚‚rdddddddddddddddccccccccccccccccbbbbbbbbb][[[[[[[[[[[[[[[[[[[[[[[SGGGFGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDJNNNNNNNNe}}}}}}}}|||||||||||||||||{{{{{{{{{{{{{{{zzzzzzzz©««««ª«ªªªªªªªªªªªªªªªªªªªªªªªª«¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸŸžžžžžžžžœœœœœœœœœœœ›››››››ššššššš™š™™™—YRRRRQQQQPPPPPPOONNNNNNNMMLLLLKKKKJJJJIIIIIIHHGGGGGFFEFEEEDDDDDDCCCBBBBAA@@A@@@????>>>>>====<<<;<;;;::9:998888887µµ´´³³³³²²²²²±±±±±°°¯¯¯¯¯®®®®­­¬­¬¬¬¬«««««ªªªª©©©¨¨¨¨¨§¦¦§¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡      Ÿ–‚‚‚‚‚‚udddddddddddddddddccccccccccccccccbbbbbbbb`[[[[[[[[[[[[[[[[[[[[[[NGGGGGGFFFFFFFFFFFFFFFFFFEFEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDDDEMNNNNNNNNNNj}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzz}¢««««««ªªªªªªªªªªªªªªªªªªªªªªª¬­¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««© ŸŸžžžžžžžœœœœœœœœœ››››››››šššššššš™™ŽXSRRRRRQQQQPPPPPOOOONNNNMMMMMLLKKKKJJJJJJIIIIIHGHGGGFGFFFEEEEDDDDCCCCCBBABAA@@@@@@???>>>>=>==<<<<;;;;::::::9998888´µ´´´³³³³³²²²²²±±°±°°°°¯®¯®®®®­­­¬¬¬¬¬«««««ª«ª©©©¨©©¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£¢£¢¢¢¡¡¡¡      Ÿœ‡‚‚‚‚‚xeedddddddddddddddddddcccccccccccccbbbbbbbbb[[[[[[[[[[[[[[[[[[[[WIGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEDEDDDDDDDDDDDDDDDDDFNNNNNNNNNNNNNn}}}}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzz{›«««ª««ªªªªªªªªªªªªªªªªªªªªªª­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¡ŸŸžžžžžžžžœœœœœœœœœœ›››››››šššššššƒVSSSRRRRRRQQQPPPPPOOOONNNNMMMLLLLKLKKKJJJJIIIIHHHHGGGGGFFFEEEEEDDDDDCCCBBBABAAA@@@@?????>>>======<<<;;;:::::9998888µµ´´´´´³³³³²²²²²±±±°°°¯¯¯¯¯®®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡      Ÿž”‚‚‚‚{eeeeddddddddddddddddddcccccccccccccbcbbbbbbb^[[[[[[[[[[[[[[[[[[NGGGGGGGGGGGGFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEDEDDDDDDDDDDDDDDDGONNNNNNNNNNNNNNs}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{z{zzzzzzz’««««««ªªªªªªªªªªªªªªªªªªªª«­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¢ŸŸŸžžžžžžœœœœœœœœœœœ››››››››šššššvTTTTSSSRRRRQQQPPPPPPPOOONNNNMMMMLLLLKKKKJJJJJIIIHHHHHGGGGGFFEEEEDDDDDCCCCCBBAAAAA@@@@????>>>>>===<<<<<;;;;::9:999888µµ´´´´´³³³³²²²²²²±±±°°°°¯¯¯®®®®®®­­¬¬¬¬¬¬«««ªªªªª©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤£¤£££¢¢¡¡¡¡      ŸŸœ‹‚‚eeeeeeddddddddddddddddddccccccccccccccbcbbbbba[[[[[[[[[[[[[[[[TIGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEFEEEEEEEEEEEEEDDDDDDDDDDDDDDIOONNNNNNNNNNNNNNNx}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzzz‰«««««ªªªªªªªªªªªªªªªªªªªª­­­­¬­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««£ŸŸžžžžžžžžœœœœœœœœœœœ›››››››šššjTUTTTTTSSRRRQRQQQPPPPOOOOOONNMMNMMLLLLKKKJJJJJJIIIHHHHHHGFGFFEEEEEEDDDDDCCCBBBABAAA@@@@????>>>>>>==<<<<;;;;:;::::9998¶¶µ´´´´´³³³³³²²²²±²±°°°°°¯¯¯®¯®®®­­­­¬¬¬¬¬¬«««ªªªª©©¨©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£¤££¢¢¢¡¡¡       ŸŸŸ™…€geeeeeededddddddddddddddddccccccccccccccbbbbbbb[[[[[[[[[[[[[[YLHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDIOOOONNNNNNNNNNNNNNS{}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{{zzzzzzzz‚««««««ªªªªªªªªªªªªªªªªªª­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¥ŸŸŸŸžžžžžžœœœœœœœœœœœ››››››››š_UUUTTUTSSSSRRRRRQQPQPPPPPPOOONNNMNMMMLLLLKKKJJJJJIJIIHHHHGGFFFFFEEEEEDDDDDCCCCBBBAAA@@@@@@????>>>>====<<<<<;;;::::9898¶¶µµµ´´´´³³³³²²²²²²±±°°°°°¯¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªª©©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¡¡¡¡¡¡    ŸŸŸŸteeeeeeeeeedddddddddddddddddcccccccccccccccbbbbb`[[[[[[[[[[[ZMHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDDDDDDDEJOOOOOOONNNNNNNNNNNNN`}}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzz|™««««ª«ªªªªªªªªªªªªªªª¬­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¦ŸŸŸžžžžžžžœœœœœœœœœœœ›››››XVVUUUUTTTSTSRSRRRQQQQPPPPPPOONNNNNNMMMLLLLKKKKKJJJIIIIHIHHHGGGGFFFEEEEDDDDDDDCBCBBBBBA@@@@@@???>>>>======<<<;;:;:::9999¶¶µµµ´´´´´´³³³²²²²²±±±°°°°¯°¯®®¯®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨¨¨¨¨§§§§¦¦¦¦¦¥¦¥¥¥¤¤¤¤££¢¢¢¢¡¢¡¡       Ÿ‡]^`deeeeeeeddddddddddddddddddcdccccccccccccbbbbbb[[[[[[[[[YMHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDEKOOOOOOOONNNNNNNNNNNNNNl}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{zzzzzzzzzŠ«««ª«ªªªªªªªªªªªªªªª­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¦ŸŸŸžžžžžžžœœœœœœœœœ›››››mVVVVVUUUUUTTSSSSSRRQQQQPQPPPPOOOONNNNNMMMLLLKLKKJJJJJJIIIIIHHHGGFFFFFFEEEEDDDDCCCCCBBBBAAA@@?@???>?>>>>>==<==<<;;;;;::::9¶¶¶µµµ´´´´´³³³²³²²²²±±±°±°°°¯¯¯®®®®®®­­­¬¬¬¬«¬«««ªªªª©©©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤££££¢¢¢¢¡¡¡      •]]]]]_ceeeeeedddddddddddddddddddccccccccccccbcbbbb^[[[[[[ULHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDEKOOOOOOOOOOONNNNNNNNNNNNNv}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzz «««ªªªªªªªªªªªªªª¬­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««§ŸŸŸžžžžžžžœœœœœœœœœœ›‹^VWVVVVVVUUUTTTTSSRSRRRRQQQPPPPPPOONONNMMNMMLLLLLKKKJJJJJIIIHHHHHGGFGFFFFEEEEDDDDCCCCBBBBBAA@A@@@@??>>>>>>====<<<;;;:;:::::·¶¶¶¶µµ´µ´´´´³³²²²²²²²±±±°±°°¯¯¯¯®¯®­®­­¬¬¬¬¬¬¬««««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¢¡¡¡   ža^]]]]]]^`eeeeedddddddddddddddddddcccccccccccccbbbbb[[[[PIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEDDEKOOOOOOOOOOOOOONNNNNNNNNNNQ{}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzzzz‹«««ªªªªªªªªªªªªª­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸžžžžžžžžœœœœœœœœpWWWVVVVVVVUUUTTTTSSSRSSRQRQQQPPPPPOOOONNNNMNMMLLKLLKKKJJJJJIIIHHHHGHGGFFFFFEEEEDDDDDCCCBBBBAAAAA@@?@????>>>>====<<<<<;;;;::9··¶¶¶¶µµµ´´´´³³³³³²²²²²±±±°°°°°¯¯®®¯®­­­­¬¬¬¬¬¬«««««ªª©©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¤¥¤¤£££¢¢¢¢¡¢¡¡¡  }^^^^]]]]]]]^`eeedddddddddddddddddcdcccccccccccccbbbb^QKHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEDDJOOOOOOOOOOOOOOONNNNNNNNNNNNd}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzz~š«««ªªªªªªªªªª«­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¦ŸŸŸžžžžžžœœœœœ„]XXXXWWWVVVVVVUUTTTTTTSSRSRRQQQQQPPPPPOONONNNNMMMMMLLKKKKJJJJJIIIIHHHHGGGGFFFFEEEDDDDDDCCCCBBBAAA@@@@@????>>>>>====<<<<<;;;:::··¶·¶¶¶µµ´µ´´´³³³³³²²²²²±±±°°°¯¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªª©ªª©¨¨¨¨¨¨§§¦§¦¦¦¦¥¥¥¥¥¤¤¤££££££¢¢¢¡¡¡’^^^^^^^]]]]]]]]^`bdddddddddddddddddccccccccccccccca\YWHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEJOOOOOOOOOOOOOOOOOONNNNNNNNNNNs}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{zz{zzzzzzzzz…§««ªªªªªªªªª­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««¥ŸŸžžžžžžžžœœœ—gYXYXXXXWWWVVVVVVUUUUTTTTSSRRRRRQQQPPPPPPOOONNNNNMMLLMLKLKKKJJJJIIIIIHHHHHGGFFFFFEEEDDDDDDCCCCBBBAAAA@@@@@???>>>>>====<=<<<;;;::·····¶¶¶µµµ´´´³´³³³²²²²²±±±±±°°°°¯¯®¯®®®®­­­¬¬¬¬¬««««ªªª©©ª©¨¨¨¨¨§§§¦¦¦¦¦¦¥¦¥¤¤¤¤¤£££££¢¢¡¡Ÿ__^^^^^^]]]]]]]]]]]]^_adddddddddddddddccccccccb_\ZYYYYXMHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEIOOOOOOOOOOOOOOOOOOONONNNNNNNNNN{}}}}}}}}}}}|||||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzzzzŠªªªªªªªªª«­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««£ŸŸŸžžžžžžœœpZZYYYYYXWWWWWVVVVVVUVTTUTTTSSSSRQRQQPQPPPPOOONNNNMMMMMLLKLKKKKKJJJJIIIHHHHHGGGGGFEEEEEDDDDDDDCCCBBBBBA@@@@@????>>>>>>===<<<<;;:::·¸··¶¶¶¶µµµµ´´´´³´³³³²²²²±²±±±°°°°¯¯¯¯®®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¡¢~_^^^^^^^^^]]]]]]]]]]]\]\\\]^`abbcddcccba`^]\[ZYYYYYYYYXVHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEFEEEEEEEEHOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNd}}}}}}}}}}}}}}|||||||||||{|{|{{{{{{{{{{{{{{{{{zzzzzzzzzz{Žªªªªªªª­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««¢ŸŸžžžžžžžžv\ZZZZYYXYXWXWWWVVVVVVVUUTTTTSSSRSSRRRQQQQPPPPPOOONNNNNMMLMLLLLKKKJJJJJIIIIHHHGGGGFFFFEFEEEDDDDDCCCBCBBAAAAA@@@????>>>>>======<;;<;:¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±°°°°¯¯¯¯®®®®­­­¬¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤£££££¢¢¢–____^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZZZYYYYYYYYOHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEGOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNu}}}}}}}}}}}}}||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzzzzzzz{Ž«ªªª¬­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««¡ŸŸŸžžžžžžžžv^[[ZZZZZYYYXXXXWWWWVVVVVVUUUTTTTSSSSRRRRQQQPPPPPOOOOONNNNMMMLLLLLKKKJJJJJIIIIHHHGGGGGFFFFEEEEDDDDDCCCCBBBBBAA@@@@@???>>>>>>===<=<<;;;¸¸¸···¶·¶¶µµµµµ´´´³´³³³²²²²²±±±±°°°°¯¯®®¯®®®­­­¬¬¬¬¬¬««««ªª©©©©©©©¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¡h______^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYYYYYWHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFFFFFFFFFFFFFFEEEEEGOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNU|}}}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{{zzzzzzzzzzzzz{Š©ª­­­­­­­­­­­­­­­­­­­­¬­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««« ŸŸžžžžžžžœr^\\[[[Z[ZZYYYYYXXXXWWWVVVVVUUUUTTTSSSRSRRRRRQQQPPPPPOOOONNNNMMMMLLLLKKJJJJJJIIIIHHHHGGFGGFFEEEEDDDDDDCCCCCBBBAAA@@@@@????>>>>>===<<<<<;¸¸¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±°°°¯¯¯¯®¯®®®­­­­¬¬¬¬¬«««««ª©©©©©©¨¨§§§§§¦¦¦¦¥¥¥¥¥¤¥¤¤£££Œ________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYYZYYYYYQHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEENOOOOOOOOOOOOOOOOOOOOOOOOONONNNNNNNNNm}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzz‡ ­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬«¬««««««««««««««««««©ŸŸŸŸžžž‹k]\\\\\[[[Z[ZZYYYXYXXXWWWVWVVVVUUUUTTTTTSSRRRRRQQQQPPPPOOOOONNNNMMMMLLLLKKKJJJJJIIIIHHHHGGGGFFFFFEEDDDDDDCCCCBBBBAAA@@@@????>>>>>====<<<<;¸¸¸¸¸···¶¶¶¶¶µµµµ´´´³³³³³²²²²²±±±°±°°°¯¯¯®®®®­­­­¬¬¬¬¬¬««ªªªª©ª©©©©©¨§§§¦¦¦¦¦¦¦¥¥¥¥¤¤£¤£ _________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZYYYYYXHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEKOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNN{}}}}}}}}}}}}}||||||||||||||||{{{{{{{{{{{{{{{{z{zzzzzzzzzzzzz€€„“¬­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««¦ŸŸŸœxc]]]\\\\\\\[[[ZZZZZYYYXWXWWWWWVVVVUUUUTTTSSSSSSRRRRQQQPPPPPOOOONNMNMMMLMLLLKKJJJJJJJIIIHHHHHGGFFFFEFEEEEDDDCCCCCBBBABAAAA@@????>>>>>>====<<<¹¸¸¸¸¸····¶¶¶µµµ´µ´´´´´³³²²²²²²±±±°°°°¯¯¯®®¯®®­­­­­¬¬¬¬¬««««ªªª©©©¨¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤£‚___________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZYYYYYYSHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFIOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNf}}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{zzzzzzzzzzzzzz~€€€€‡–­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««£~h^^^]]]]]\\\\[\[[[ZZYZZYYYXXWXWWWWVVVVVUUUTTTTTSSSSRRRQQPQPPPPPPOONONNNMMMLLLLLKKJJJJJJJIIIIHHHHGGGFFFFEEEEDDDDDCCCBBBBBAAA@@@?????>>>>>>===<=<¹¹¸¸¸¸¸¸¸··¶¶¶¶µµµ´´´³´³³³²²²²²²±±±°±°°°¯¯¯¯¯®®®­­¬¬¬¬¬¬««««ªªªª©©©¨©¨¨§§§§¦§¦¦¦¦¥¥¥¥¤¤ž`___________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[\\[[[[[ZZZZZZZZYYYYYKHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFGOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONONNNNNNNy}}}}}}}}}}}}}|}}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzzzzzzz{€€€€†’¥­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««£{d__^^^^]]]]\\\\\[[[[[ZYZYYYXYXXWWWVVVVVVUUUUUTTTSSSSRRRRQQQQPPPPPOOOONNNMNMMLLLLKKKKJJJJJIIIIHHHGHGGGGFFFEEEEDDDDDCCCBBBBAAAA@@@@@???>?>>>====<<¹¹¹¹¸¸¸¸····¶¶¶µµµµ´´´´´´³³²³²²²±±±±°°°°¯¯¯¯®®®®­­­­­¬¬¬¬¬«««ªªªª©ª©©©©¨§¨§§§§¦¦¦¦¥¥¥¥¥|```__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[\[[[[ZZZZZZZZYYYYYWIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGFGFFFFFFFFFFFFFFOOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNb}}}}}}}}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{{{zzzzzzzz€€€€€€€€‚‰–§­­­­­­­­­­­­¬­¬¬¬¬¬¬¬¬¬¬¬¬«¬««««««««¤“…}{{{{z`__^^^^]]]\\\\\\\[[[[ZZZZYYYXXXWWWWVVVVVVUUUUTTSTSSSSRRRRQQQPPPPPOOOONNNMMMMMLLLLKKKKJJJJJIIIHHHHHGGFFFFFFEEEDDDDDCCCCCBBAAAAA@@@?????>>>>=====¹¹¹¹¹¸¸¸¸····¶¶¶¶µµµ´´´´´³³²³³²²²²±±±±°°°°¯¯¯®®®®®­­­­¬¬¬¬¬«««ªªªª©ª©©¨©¨¨¨§§§¦¦¦¦¦¥¥¥ž````___________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYYQIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFKPPOPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNx}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{{{{{{zzzzzz~€€€€€€€€€€…˜¤­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««£–‹‚}{{{{{{{{{o__^^^^]^]\]\\\\\\[[[[ZZYYYYYXXXWWWWWVVVVUUUUTTTTSTSRSRRQRQQQQPPPPOOONNONMNMMMLLLKKKKJJJJJJIIIHIHHHGGFFFFFFEEDDDDDDCCCCBBBAAAA@@@@@????>>>>=>==º¹¹¹¹¹¸¸¸¸¸···¶¶¶¶µµµ´´µ³´´³³³²²²²²±±±±°°°¯¯¯®®®®­®­­­¬¬¬¬¬«««««ªª©ª©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥}````___________^_^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZZZZYZYYYIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFHPPPPOPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNb}}}}}}}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{{zzzzz{‚€€€€€€€€€€€„‰“˜¢¦©«¬¬«©¦¡—’Œ‡‚|||||{{{{{{{{{{{g___^^^^^]]\\\\\\\[[[ZZZYYYYXYXXXWWWWVVVVUVVUTTTTTSSRRSRQRQQPPPPPPOOOONNNNMNMMLLLLKKKKJJJJJJIIHHHHHGGGFFFFEEEEEDDDDCDCCBBBBBAA@@@@@????>>>>>==ºº¹¹¹¹¹¸¸¸¸¸···¶¶¶µ¶µµµ´´´³´³³³²²²²²±±±°°°°°¯¯¯¯®®®®®­­¬¬¬¬¬¬«««ªªªªª©©¨©¨¨¨§§§§§¦¦¦¦ a```````__________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZZZYYVIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFGPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNy}}}}}}}}}}}}}}}}}|||||||||||||{|{{{{{{{{{{{{{{{{{zzzz‚‚€€€€€€€€€€€€~~~~~~}}}}}}}}|||||||{{{{{{{{{{a____^^^]]]]\\\\\[[[[[ZZZYYYXXXXWWWWWVVVVVUUUTTTTSTSSRRRRRQQQPPPPPPOOOONNMNMMMLLLLKKKKJJJJIIIIHHHHHHGGFGFFFEEEDDDDDDCCCBBBAAAAA@@@?????>>>>>=»ºº¹¹¹¹¹¸¸¸¸·····¶¶¶¶µµµ´´´´³³³³³²²²±²±±±°°°¯¯¯¯¯¯®®®®­¬¬¬¬¬¬¬¬«««ªªª©©©¨©©¨¨¨§§¦§¦¦¦‚a````````___________^^^^^^]]]]]]]]]]]]]]\\\\\\\\\[\[[[[[[ZZZZZZZYYYYQIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFLPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNe}}}}}}}}}}}}}}}}}}|}||||||||||||||{{{{{{{{{{{{{{{{{z~‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}||}|||||{|{{{{{{{{p`___^^^^]]]\\\\\\[[[[[ZZYZYYXYXXWWWWVVVVVUUUUTTTTTSSSSSRRQQQPPPPPPOOOONNNMMMMMMLLLKKKKJJJJJIIIIHHGHGGGGFFFFEEEDDDDDCCCCBBBBAAAA@@@@???>>>>>>»ºº¹¹¹¹¹¸¸¸¸¸·····¶¶¶µ¶µ´´´´´³³³³³²²²²±±±±°°°°°¯¯®®®®®­­­¬¬¬¬¬«««««ªª©ª©©¨¨¨¨¨¨§§§¦¦£aaa```````____________^^^^^^^^]]]]]]]]]]\\\\\\\\\\\[[[[[[[[[ZZZZZZZYYYKIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFFFFHPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNN{}}}}}}}}}}}}}}}}}}}|||||||||||||{{{{{{{{{{{{{{{{{{|‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{{{{{{{{f`___^^^]^]]\\\\\\[[[ZZZZZZYYYXXXXWWWVVVVVUUUUTTTTTSSSSSRQRQQQQPPPPOOOONNNNMMMLLLLLKKKKJJJJJIIIIHHHGGGGGGFFEEEEEDDDDCCCCBBBAAAAAA@@@????>>>>»»»ºº¹¹¹¹¹¸¸¸¸¸····¶¶¶µ¶µµµ´´´³³³³²²²²²²±±°°°°°°¯¯¯¯®®®­­¬¬¬¬¬¬¬««««ªªª©©©©©¨¨¨¨§¦¦¦aaaaa```````__________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[ZZZZZZZYYWIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFGPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNl}}}}}}}}}}}}}}}}}}}||||||||||||||{|{{{{{{{{{{{{{{{ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||{{{{{{{{{a`____^^^]]]]\\\\\\[[[[ZZYZYYYXXXXXWWVVVVVVUUUTTTTSSSRSRRRQQQQQPPPPOOOONNNNNMMMMLLLKKKJJJJJJIIIIIHHGGGGGFFFEEEEDDDDDDCCCCBBBAAA@@@@@?????>>»»»ºººº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶µµµ´µ´´³³³²²²²²²±±±±°±°°¯¯¯¯®®®®®­¬¬¬¬¬¬¬««««««ªª©©©¨¨¨¨¨§§¦baaaa```````_`___________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZYYYUIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGFGFKPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNO}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{{{{{{{€ƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~}}}}}}}||||||||{{{{{{{{m___^__^^^^]]]\\\\\[[[[[ZZZYYYYXXXXWWVVVVVVVVUUUTTTSSSSRSRRQRQPPPPPPPOOONNNNNMMMLLLKKKKJJJJJJIIIIIHHGHGGGFFFFEEEEDDDDCCCCBCBABAAAAA@@?????>»»»ººº¹º¹¹¸¸¸¸¸¸¸····¶¶¶¶µµ´´´´´³³³³³²²²²²±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©¨©¨¨¨¨§™aaaaaaaa```````___________^^^^^^]]]]]]]]]]]]]\]\\\\\\\\\[[[[[[Z[ZZZZZZZZYQIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGFGPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNs}}}}}}}}}}}}}}}}}}}}|}|||||||||||||{{{{{{{{{{{{ƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}}}}}}}|||||||{{{{{{{c``____^^^]]]\]\\\\[[[Z[ZZZZYYYYXXWWWWWVVVVVVUUUUTTSSSSRRRRRQQQPPPPPOPOONNNNNMMLLLLLKKKKJJJJJIIIHIHHHGGGFFFFEEEEEDDDDCCCCCBBBAA@@@A@?@???>¼»»»ººººº¹¹¹¹¸¸¸¸¸···¶¶·¶¶¶µµµ´´´³³³³²²²²²±±±±±°°°¯¯¯®¯®®®®­­¬¬¬¬¬¬«««ªªªªª©©¨¨¨¨¨¨ybaaaaaa`a``````___________^^^^^^]^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYYMIIIIHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGGGGGGGMPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONN^}}}}}}}}}}}}}}}}}}}}|||||||||||||||{|{{{{{{{{{}ƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}}}|}|||||{{{{{{{{s``_`___^^^^]]]\\\\\[[[[ZZZZZYYYXXXXXWWWVVVVVVUUUTTTTSSSSRRRQQQPPPPPPOPOONONNMMMLMLLLKKKKKJJJJIIIIHHHHHGGFFFEFEEDEDDDDDCCCCCBBAAA@A@@@????¼¼¼¼»»»ºººº¹¹¸¸¸¸¸¸····¶¶¶µ¶µµµ´´´³³³³³²²²²²±±±°°°°¯¯¯®¯®®®­­­­¬¬¬¬¬«««ªªªª©ª©©©©¨£bbbaaaaaaa`````````__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZYIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGHPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNz}}}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{{{{{ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}~}}}}|||||||{{{{{{{{e``____^^^^^]]]]\\\\[[[[ZZZZYYYYXXXXWWWWVVVVVUUUTTTTSTSSRSRQQQQQPPPPPOOOOONNNMMMLLLLLKKKKJJJJJIIIHIHGHHGGGFFEFEEEEDDDDDCCCCCBAAAAA@@@@@??¼¼¼»»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶¶µµ´´µ´´³³³³³²²²²²±±±±°°°¯¯¯¯®®®­­­­­­¬¬¬¬««««««ªª©©©¨¨bbbbaaaaaaa````````___________^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZWIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGOPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONm}}}}}}}}}}}}}}}}}}}}}|}||||||||||||||{{{{{{{‚ƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~}}}}}}}||||||{{{{{{x```_`__^^^^]]]]]\\\\[\[[ZZZZYYYXXXXWWWWWVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOONONNNMMMMMLLLKKKJJJJJJIIIIIHHHHGGFFFFFFEEEDDDDCDCCBBBAAAAAA@@@@?¼¼¼¼»¼»º»ºº¹¹¹¹¹¸¸¸¸¸·····¶¶¶¶µµ´´´´´³³³²²²²²²±±±°°°°°¯¯¯®®®®®­­¬¬¬¬¬¬«««ªªªªª©©©¨mbbbbbaaaaaaa````````__________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYVIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGGIPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOW}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||{{{{{‚„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~}}}}}}}}|||||||{{{{{{h`````___^^^]]]\\\\\\\[[[[ZZYYZYYYXXXWWWWWVVVVVUUUTTTSSSSRRRRQQQQQPPPPPOOONONNNMMMMLLLKKKKJJJJJIIIIIHHHHGFFGFFEFEEEDDDDDCCCBBBABBAA@@@@@½¼¼¼¼»¼»»ºººº¹¹¹¸¹¸¸¸¸····¶¶¶¶µµµµ´´´³´³³³²²²²²²±±±°°°¯¯¯¯®®®®®­­­¬¬¬¬¬«««««ªª©ª©£cbbbbbbaaaaaaa````````__________^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\[[[[[Z[ZZZZZZZYUIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGHGGGGGGPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOy}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||{|{{{„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~}}}}}}}|||||||{{{{za````___^^^^]]]]]\\\\[[\[[ZZYYYYYXXXXWWWVVVVVVUUUUUTTSSSSSRRRRQQQPPPPPPOONOOONMMMLMLLLKKKJJJJJJJIIIHHHHHGFGFFFFEEEDDDDDDCCCCCBABAAAA@@@½½½¼¼¼»¼»ºº»ºº¹¹¹¸¸¸¸¸¸¸···¶¶¶¶µµµµ´´´´³³³²²²²²±±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªªªccbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[ZZZZZZZZSIIIIHIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGJPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOm~}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||{||„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~}}}}}}}|}||||{{{{{{ia````____^^]]]]]]\\\\[\[[[ZZYYZYXXXXWWWWWVVVVVVUUTTTTTSSSSRRRQRQQQPPPPPOOOONNNNMMMLLLLLKKKJJJJJJJIIHHHHGGGFFFFEEEEEDDDDDCCCCBBBBBAAA@@¾½½¼¼¼¼¼»»º»ºººº¹¹¹¸¸¸¸¸¸···¶¶¶µ¶µµ´´´´´³³³³²²²²±±±±°°°°°¯¯¯®®®®®­­­¬¬¬¬¬«««««ªªªoccbbbbbbaaaaaaa`a```````__________^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYYSIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX~~}}}}}}}}}}}}}}}}}}}}}}}}|}|||||||||||„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}|||||||{{{{{aa````_`_^^]^^]]]]\\\\\[[[Z[ZZYZYYYXWXWWWWVVVVVVUUTTTTTSTSSRRRRQQQQPPPPPOOOONNNMMMMMLLLLKKKKJJJJJIIHIHHHGHGGFFFFEEEEDDDDDDCCCBCBBBA@A@½½½½¼¼¼¼¼»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶µµµµ´´´´´³³³³²²²²±±±±±°°°°¯¯¯®®®®®­­­¬¬¬¬¬¬«««ªª¥ccccbbbbbbbaaaaaaaa```````_`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZYZRIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGHGJPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO{~}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||……„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{{haa````___^_^^^]]]]\\\\\[[[[ZZZZZYYYXXWWWWWVVVVVUVUUUTTTSSSRRRRRQQQQPPPPOOOONNNNNMMMMMLLKKKKJJJJJJIIIIHHHHGGGGFFFFEEDDDDDDCCCCCBBAAAA@¾½½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶¶µµ´´´´´´³³²²²²²±±±±°°°°¯°¯¯¯¯®®­­­­¬¬¬¬¬¬««««—ccccbbbbbbbaaaaaaa`a``````__`_________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[[ZZZZZYYRIIIIHIHHHHHHHHHHHHHHHHHHHHHHHGGOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOq~~~}}}}}}}}}}}}}}}}}}}}}}}}|||||||||…………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{{xaaaa``_`_^^^^]]]]]\\\\\[[[[[ZZZZZYYYXXXWWWWVVVVVVUUUTTTSSSRSSRRRQQPQPPPPPOOOONNNNNMMLLLLLKKJJJJJJJJIIIHHHHHGGFFFFFEEEDDDDCCCCCBBBBBAA¾¾½½½½¼¼¼»»»»»»ººº¹¹¹¹¹¸¸¸¸¸··¶¶¶¶¶¶µµµµ´´´´³³³²²²²²±±±°°°°¯°¯¯®®®®®­­­­¬¬¬¬«««ª~ccccccbbbbbbabaaaaaa`````````__________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYSIIIIIHHHHHHHHHHHHHHHHHHHHHHHHIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOa~~~~}}}}}}}}}}}}}}}}}}}}}}}}|||||||‚………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}}}}}||||||{||{gaaa````____^]^^]]]]\\\\\[[ZZZZZZYYYYXXWWWWVWVVVVUUUUTTTTSSSSSRRRQQQQPPPPPOOOOONNMMMMMLLKLKKKJJJJJJIIIIHHHHGGGGFFFEEEEDDDDDCCCCBBABAA¾¾¾½½½½¼¼¼¼¼¼»»ºººº¹¹¹¹¸¸¸¸¸·····¶¶¶¶µ´´´´´´³³²³²²²²²²±±±±°°¯°¯¯¯®®®­®­­­¬¬¬¬¬«ªccccccccbbbbbbbbaaaaa`a``````____________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZYTIIIIHIHHHHHHHHHHHHHHHHHHHHHHMPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOOOOOO}~~~~}}}}}}}}}}}}}}}}}}}}}}}||||||‚…………………„…„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}}||||||||{saaaa`````___^^]^]]]]\\\\[[[[[ZZYYZYYYYXXXWWVWVVVVUUUUTTTTSSSSRRQRRQQPPPPPPOOOOONNMMMMMLLLLKKKKJJJJJIIIIHHHHGGGGFFFEEEEDDDDDCCCCCBBAB¾¾¾¾¾½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸¸····¶¶¶¶µµ´´´´´³³³³²²²²²±±±±°°°°°¯¯¯®®®®­­­¬¬¬¬¬«£cccccccccbbbbbbbaaaaaaa```````_`__________^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYUIIIIIHHHHHHHHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOy~~~~~~}}}}}}}}}}}}}}}}}}}}}}||||ƒ††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}}}|||||||daaaaa``_`__^^^^]]]]\\\\\\[[[ZZZZZYYXXXXXXXWWVVVVVUUUUTTTTTTSRSRRRQQQQQPPPPOOOOONNMMNMMLLLLKKKKJJJJJIIIHHHHGHGGGFFFFEEEEDDDDCCCCCBBB¿¾¾¾¾¾½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´´´´´³³³²²²²±±±±±°±°¯¯°¯¯®®®­­­­­¬¬¬¬•cccccccccccbbbbbbaaaaaaaa```````____________^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[Z[ZZZZZZZVIIIIIHHHHHHHHHHHHHHHHHHHHKPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOo~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}|ƒ†††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~~}}}}}|}}||||||nbbaa`a```____^^^^]]]\\\\\\[[[Z[ZZZZYYYXXXXWWWWVVVVVUUUTTTTSSSRRRRRQQQQPPPPPOOOOONMNMMMLLLLKKKKKJJJJIIIIIHHHGGGGFFFFEEEEEDDDDCCCBBCB¾¿¾¾¾¾¾½½½¼¼¼¼¼¼»»ºººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶µµµµ´µ´³³³³³²²²²²±±±°°°°¯¯¯®¯®®®­­­­­¬¬¬€cccccccccccbbbbbbbaaaaaaaa```````___________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[ZZ[ZZZZZYXJIIIHHHHHHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOa~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}…††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}}|}||||||{bbbbaaa```_____^^]^]\\]\\\\[[[[Z[YYYYXXYXXXWWWWVVVVUUUUUUTTSSSSSRRRRQQQQPPPPOOOOONNNMMMLMLLLLKKKJJJJJIIIHHHHGHGGGFFFFEEEDDDDDCCCCCB¿¾¾¾¾¾¾¾¾½½½¼¼¼»»»»»ººº¹¹¹¹¸¸¸¸¸····¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±±±°°¯¯¯¯®®®­­­­¬¬¬eccccccccccccbbbbbbbbaaaaaaa```````____________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZYZYNIIIIHHHHHHHHHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOO~~~~~~~}~}}}}}}}}}}}}}}}}}}}…††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}}||||{ibbbaaa```_`___^^^]]]]]\\\\\[[[[ZZZZYYYXXXXXWWWWVVVVVUUUUTTSTTSSSRRRQQQPPPPPPOOONNONMMNMMLLLKKKKKJJJJJIIIIHHHHGGGGFFEEEEDEDDDDDCCBCÀ¿¿¿¾¾¾¾¾½½½¼¼½¼»¼»»»ºº¹¹¹¹¹¹¸¸¸¸·¸··¶¶¶¶µµµµµ´´´´³³³²²²²±²±±°°°°¯°¯¯®¯®®®®­­­©cccccccccccccccbbbbbbaaaaaaaa`````````_________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZZZZZZZZYRIIIIHHHHHHHHHHHHHHHHMPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOOOOOOOOO{~~~~~~~~}}}}}}}}}}}}}}}}}}‚†††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~~}}}}}}||||||tbbbaaaa```__`___^^^]]]\\\\\\[[[[[ZZYYYYYXXXXXWWWVVVVUUUUTTTTSSSSSRRRRQQQQPPPPOOONNNNNMMMLMLLLKKKKJJJJIIIIIHHHGGGGFFFEEEEEEDDDDCCCBÀ¿¿¿¿¿¾¾¾¾½½½¼¼¼¼¼»»»ºººº¹¹¹¹¹¸¸¸¸·····¶¶¶µ¶µ´´´´´³³³³²²²²²±²±±°°°°¯¯®¯®®®­­­¬¡ccccccccccccccccbbbbbbbaaaaaaaa```````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[Z[ZZZZZYZUIIIIHHHHHHHHHHHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOv~~~~~~~~~}}}}}}}}}}}}}}}}„††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}||||||dbbbaaaa`a```___^_^^]]]]\\\\\\[[[ZZZZYYYXYXXXWWWWVVVVUUUUTTTTTSSSRRRQRQQPPPPPPOPOONNNNNMMMLLLLKKKKJJJJJIIIHHHHGGGGFGFFEEEDEDDDDDCCÀÀ¿¿¿¿¿¾¾¾¾½½½½¼¼¼»»»»»ºººº¹¹¹¹¸¸¸¸¸··¶·¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±°°°°¯¯¯¯®®®®­”cccccccccccccccccbbbbbbabaaaaa``a`````____________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYXMIIIHHHHHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOm~~~~~~~~~}}}}}}}}}}}}}}€†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}||||kbbbbaaaaa````____^^^^]\]]\\\\\\[ZZZZZYYYXYXXXWWWVVVVVUVUUTUTTTSSSRRRRQQQQQPPPPOOOONONNMMMMLLLLKKKKJJJJJIJIHHHHHGHGGGFFFEEEEDDDDDCÀÀÀ¿¿¿¿¾¾¾¾¾¾½½¼½¼¼¼¼¼»»ºººº¹¹¸¹¸¸¸¸····¶¶¶¶¶µµµ´´´´´³³³³²²²²±±±±°°°°¯¯¯®¯®®®­ƒdcdccccccccccccccbbbbbbbbbaaaaa`a``````____________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[ZZZZZZZYZYSIIIHHHHHHHHHHHHOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOc~~~~~~~~~}~}}}}}}}}}}}ƒ‡‡†‡††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~}}}}}}}}}|||vbbbbbbaaaaa```____^^^]]]]\\\\\\[[ZZZZYZZYXXXXXWWWVVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOOONNNNMMMMMMLLKKKKJJJJJIIIHIHHHGGGFFFFEEEDDDDDDCÀÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»ººº¹º¹¹¹¸¸¸¸¸···¶¶¶¶µµµµ´´´³´³³³³²²²²±±±±°°°°¯°¯®®®®®pdddccccccccccccccccbbbbbbabaaaaaaa`````__`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[Z[ZZZZZZYYYWKIHHHHHHHHHHHIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOW~~~~~~~~~~~~}}}}}}}}†‡‡‡‡††††††††††††††††……………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}}}}}||ebbbbbbaaaa`````___^^^]]]]\\\\\\[[[ZZZZYYYYXXWXWWWWVVVVVVUUUTTTTSSSSRRRQQQQPPPPPOOOONNNNNMMMMLLKLKKJJJJJJIIIIHHHHGGGGFFFFEEEEDDDDÁÁÀÀ¿À¿¿¿¿¾¾¾¾¾½½½½¼¼¼»»»»ºººº¹¹¸¸¸¸¸¸¸···¶·¶¶¶µµµµ´´´´³³³²²²²²²±±±°°°¯¯¯¯®®®­dddddccccccccccccccccbbbbbbbaaaaaa`````````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZYZYYSIIHHHHHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOO}~~~~~~~~~~}~}}}}}}ƒˆ‡‡‡‡‡‡‡†††††††††††††††…†……………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}}|kbbbbbbaabaaa```___^^^^]]]]]\\\\\[[[Z[ZYZZYYXXXWWWWWVVVVVVVUUTTTTSSSSRRRQQQQPPPPPPOOOONNNNMMMLLLLLKKKKJJJJJIIIHHHGGGGFFFFEEEEEDDDÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼»»ººººº¹¹¹¹¸¸¸¸¸·¸·¶¶¶¶µµµ´´´´³³³³²³²²²²±±±±°°°°°¯¯¯¯«ddddddccccccccccccccccbbbbbbbaaaaaaa`````````__________^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYWMIHIIHHHHHNPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOO|~~~~~~~~~~~~~}}}‡ˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~~}}}}}}||uccbbbbbbaaaaa``_`__^^^^^]]\\\\\\[\[[ZZZZZYYYXXXWXWWWVVVVVUUUTTTSTTSSRRRRQQQQPPPPPOOOONNNNMMMMMLLLLKKKJJJJJIJIIHHHGHGGFGFEFEEEEDDÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½¾½¼¼¼¼¼»»»»ºº¹¹¹¹¹¸¸¸¸¸···¶·¶¶µµµ´µ´´³´´³³³²²²²±²±±±°°°¯¯¯®¥ddddddcdccccccccccccccbbcbbbbbbaaaaaaa````````__________^^^^^^]^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZZYYYYUIIIHHHHHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOx~~~~~~~~~~~~~~}†ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}}}}}}||dccbbbbbbaaa`a```___^^^^]]]]]\\\\\[[[[ZZZZYYYYXXXWWWWVVVVVVUUUUTTTSSSSRRRQQQQPPPPPOPOONNNNNMMMMLLLKKKKJJJJJIIIIIIHHHGGFFFFFEEEDDÂÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»¼»»º»ººº¹¹¸¸¸¸¸¸···¶¶¶¶¶µµµ´µ´´´³³³²²²²²±±±±±°°°¯¯¯Ÿdddddddccccccccccccccccbbbbbbbbaaaaaaaa```````___________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[Z[[ZZZZZYZYYYYSHHHHHJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOPOOOOOOOOOOOOOOOt~~~~~~~~~~~~~…ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~}}}}}}|hccccbbbbaaaaaa```____^^]^]]]\\\\\[[[[ZZZZZYYYXXXXWWWVVVVVVVUUUTTTTTSSRSRRRQQQPPPPPOOOOOONNNMMLMLLLKKKJJJJJJJIIIHHHGHGGGFFFFEEEEÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼¼»»ºººººº¹¹¸¹¸¸¸¸···¶¶¶¶¶µµ´µ´´´´³³³³²²²²±±±±±°°°°¯™eedddddddccccccccccccccccbbbbbbabaaaaaaaa``````__________^^^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[ZZZZZZZYZYYYYXQIHHLPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOp~~~~~~~~~~~„ˆ‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}~}}}}}pdccccbbbbbaaaa````__^^^^^]]]\]\\\\\[[[ZZZZYYYYXXXWWWWWVVVVUUUUTUTTSSSSSRRRQQQQQPPPPPOOOONNNNMMMLLLLKKKKJJJJJJIIHIHHHHGFFFFFEEEEÂÂÁÁÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼»»»º»ºººº¹¹¹¸¸¸¸¸···¶¶¶¶µ¶µ´´´´´³³³³³²²²²±±±±±±°°¯eeeddddddcdcccccccccccccccbbbbbbbaaaaaa````````_`__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZ[ZZZZYZYYYYYXQHPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOPOOOOOOOOOOOOj~~~~~~~~~„ˆ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}}}zddccccbbbbbaaaa```____^^^^]]]]]\\\\[[[[[[ZZYZXXXXXXXWWWVVVVVUUUUTTTSSSSSRRQRQQQQPPPPPOOOONNNNMMMLLLLLKKKJJJJJIIIIHHHHGGGFFFFFEEÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼»»»»ºººº¹¹¹¹¸¸¸¸¸¸··¶¶¶¶¶µµµ´´´´³³³³³²²²²±±±±°°°°ˆeeeeeddddddcccccccccccccccccbbbbbabaaaaaa```````___________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[ZZZZZZYYYYYYYYXUPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOf~~~~~~~…‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~~}}}}}fddccccbbbbbaaaa````___^^^^^]]]\\\\\[[[[ZZZZZYYYXXXXXWWVVVVVVUUUUTTTSTSSSSRRRRQQPPPPPPOOONNNNMMMMLLLLKKKJKJJJJJIIIHIHHGGGFFGFFEÂÃÂÂÁÁÁÁÁÀÀ¿À¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸···¶¶¶¶¶µµµ´´´´´³³³³²²²²²²±±±°°€eeeedddddddddccccccccccccccccbbbbbbaaaaaaaa```````____________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[ZZZZZZZZYZYYYYXXXVRPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOO`~~~~€‡‰ŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††††……………„„……„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~~~}}}}}jddcccccbbbbbbaaa``_`____^^]^^]]]\\\\\[[[ZZZZZYYYXXXWXWWWWVVVVVUUUTTTTSSSRRRRQQQQQPPPPPPOOONNNNMMMLLLLLKKKJJJJJJIIHHHHHGGGGFFFEÃÃÂÂÂÁÁÁÁÁÁÀ¿À¿¿¿¿¾¾¾¾½½½½½¼¼¼¼¼»»ººººº¹¹¸¹¸¸¸¸···¶¶¶¶¶¶µµµµ´´´³³³³²²²²²±±±±°yeeeeedddddddddcccccccccccccccbbbbbbbbaaaaaa```````_`__________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[\[[[[[[ZZZZZZZYYYYYYXXWWWTPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOO[ƒ‰ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡†‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~}}}}odddddcccbbbbbaaaaa```____^^^]]]]]\\\\\[[[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTSSSSSRRQRQQPPPPPPOOONNNNNMMMLLLLKKKKJJJJJJIIIIHHHHGGGFFFÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½½¼¼¼»»»ººº¹¹¹¹¸¹¸¸¸¸···¶¶¶¶¶µµµµ´´´´³³³³²²²²²±±±±reeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[ZZZZZZZYYYXXXXXWWXVRPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOYˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~}}}wedddccccbbbbbbbaaaa````___^^^^]]]]\\\\\\[[[ZZZZYYYYXXXWWWWVVVVVUUUTTTTTTSSRRRRQQQQPPPPPPPOONONNMNMMLMLLLKKKKJJJJJIIIIIHHHGGGFFÃÃÃÃÂÂÂÂÂÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»¼»ººººº¹¹¹¸¹¸¸¸¸¸··¶¶¶¶µµµµµ´´´´³³³²²²²²²±±±mfeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaaa```````____________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[ZZZZZZZZZYXXXXXWWWWWXURPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOQSTZ‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††…………………„„„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€~~~~~~~~}}eeddddcccccbbbbbaaa``_```_^^^^]]]]]\\\\\\[[[ZZZZYYYXXXXWWWWWVVVVUUUUTTTTSSSSRRRRRQQPPPPPPOOOONNNNMMMMLLLKKKJJJJJJJIIIHHHHHGGFFÄÄÃÃÂÂÂÁÂÂÁÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½¼¼¼»¼¼»º»ºººº¹¹¹¸¸¸¸¸¸···¶¶¶¶¶µµ´´´´³³³³³²²²²²²±jfefeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaa`a`````____________^^^^^^^]]]]]]]]]]]]\]\\\\\\\\\[[[[[[[[[ZZZZZZYZXXXXXXWWWWWWWWVTPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPRTUUUUW‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~~~}~geedddddcccbbbbbbaaa````____^^^^]]]]\\\\[\[[ZZZYYYYYXXXXWWWWVVVVVVVUUUTTTTSSRRRRRQQQQPPPPOOOOOONNNMMMMLLLLKKJJJJJJJIIIHHHGGHGGÄÄÃÃÃÂÂÂÂÂÂÁÁÁÀÀ¿ÀÀ¿¿¾¾¾¾¾¾½½½½¼¼¼»»»»»ººº¹¹¹¸¸¸¸¸¸····¶¶·¶µµµ´´´´´´³³²²²²²±±gffeeeeeeeeddddddddcccccccccccccccbbbbbbbabaaaaa`a`````_`___________^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZZZZZZYXXXXXXXXWWWWWWWWWWUTQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPRSUUUUUUUUV‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…†…………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€€~~~~~~~keedddddcccbbbbbbbaaa````_____^^^]]]]\\\\[[[[[[ZZYYYYYXXXXWWWWVVVVUUUUTTTSSSSSSRRQRQQQPPPPPOOOONNNNMMMLMLLKLKKKJJJJJIIIIHHHHGGÄÄÄÃÄÄÃÂÂÂÁÂÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ººººº¹¹¸¸¸¸¸¸¸··¶¶¶¶µ¶´µ´µ³´³³³²²²²²²ffffefeeeeeeedddddcdcccccccccccccccbbbbbbbaaaaaaaa``````_`_________^_^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZYXXXXXXXXWWWWWWWWWWWWWWVUSQPPPPPPPPPPPPPPPPPPPPPPPPPPPPRTUUVVVVVVUUUUUV‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒ‚ƒ‚ƒ‚‚‚‚‚‚€€€€€€€€€€€~~~~~~~offeeedddcccbbbbbbbaaaa````____^^^]]]]\\\\\[[[[ZZZZYYYYYXWXWWWWVVVVUUUUTTTTSSSSSRRRRQQPPPPPPOOOONNMNMMMMLLLLKKKJJJJJJIIIHHHHHGÄÄÄÃÄÃÃÃÂÂÂÂÁÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼»»»»ººº¹¹¹¹¹¸¸¸¸····¶·¶¶¶µµµµ´´´³³³³³²²²hffffeeeeeeedddddddddccccccccccccccccbbbbbbabaaaaaa```````__________^_^^^^^^^]]]]]]]]]]]]\]\\\\\\\\[[[[[[[[[ZZZZYXXXXXXXXXWWWWWWWWWWWWWWWWWWWVUUTSSRQQQPPPPPQRRSTTUVVVVVVVVVVVVVVVVUUUVŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~uffeeededdccccbbbbbaaa``a```___^^^^^]]]\\\\\[[[[[ZZYYYYYYXXWWWVWVVVVUVUUTUTTSSSSSRRRQQQQPPPPOOOOONONNMMMMLLKKKKKJJJJJIIIIIHHHHÄÄÄÄÄÃÃÃÂÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½½¼¼¼¼»»º»ºº¹º¹¹¹¸¸¸¸¸¸···¶·¶¶¶µµ´´´´´³³³²²²²jffffffeeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaa`````````__________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\[[[[[[ZZZZZYXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUXŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††††……………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€~~~~~{fefeeedddccccccbbbbbbaa`````____^^]]]]\\\\\\\\[ZZZYZZYYXXXXWWWWWVVVVVVUUTTTTTSSSRRRRQQQPPPPPPOOOONNNMMMMLMLLLKKJKJJJJJIIIHIHHÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»»»ººººº¹¹¸¸¸¸¸¸····¶¶¶µµµ´´´´´³³³²³²mffffffeeeeeeedddddddddcccccccccccccccbbbbbbbaaaaaaaaa`````__`__________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[ZZZZXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVUUUZŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆ‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~gffeeeededddccbcbbbbabaaa`````__^^^^]]]\\\\\\[[[[[ZYZZYYYYXXWWWWVVVVVVUUUUUTTTSSSRRRRRQQPPPPPPPOOONNNNMMMMLLLLKKKJJJJJIJIIIHHÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»»ººººº¹¹¹¹¸¸¸¸·····¶¶¶µµµ´´´´´´³³³³sffffffffeeeeeeeedddddddcccccccccccccccccbbbbbbbaaaaa````````_`_________^^^^^^^^^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[[ZXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWWVVVVVVVVVVVVVVVUU^ŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€€~~~hffffeeeeddcdcccbbbbbaaaa```______^^^]]\]]\\\\[[[[[ZZZYYYXXXXXWWWWVVVVVUUUUTTTSSSSSRRRQQQPPPPPPPPOONNNMMMMMLLLKKKJJJJJJJIIIHHÅÅÄÄÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÁÀ¿¿¿¿¿¾¾¾¾¾¾½½¼¼¼¼»»»»ººººº¹¹¹¹¸¸¸¸¸··¶·¶¶¶¶µµµ´´´´´³³³zfffffffeeefeeeeeddddddddcccccccccccccccbbbbbbbbaaaaaaa```````___________^^^^^^^]^]]]]]]]]]]]\\\\\\\\\\\[[[[[[[ZYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWVVVVVVVVVVVVVVVVUcŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~~jgfffeeeeedddcccccbbbbabaaaa``___^^^]]^]]\\\\\\\\[[ZZZYZYYYXXXXWWWWVVVVVUUTUUTTTSSSSRRRQQQQPPPPPOOONNNNMNMMMLLLLKKKKJJJJIJIIIÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»»»»»ºº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶¶µµµ´´´´³³³‚ffffffffeeeeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaa`a```````___________^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[YXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVViŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††…………………„……„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~lgfffffeeeddddcccccbbbbbaaaa````____^^^^]]]\\\\\[[[Z[[ZZZYYXXXXXWWWVVVVVVUUUUTTSTTSSRRRRRQQQQPPPPPOOONNNNMMMMMLLKLKKJJJJJIIIIÆÅÅÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÀÁ¿À¿¿¿¿¾¾¾¾½½½½½½¼¼¼¼»»»ººº¹º¹¹¸¸¸¸¸·¸···¶¶µ¶¶µµ´´´´´³Šfffffffffeeeeeeeeedddddddddccccccccccccccbbbbbbbbbbaaaa`````````____________^^^^^^]]]]]]]]]]]]\\]\\\\\\\\[\[[[ZYXYYXXXXXXXXXXXWXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWVVVVVVVVVVVVVVVoŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡†‡†††††††††††††††…………………„„„„„„„ƒ„„ƒƒƒƒƒƒƒƒ‚‚ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€nhgggffffedeeddccccbbbbbbaaa``````___^^]]]]\\\\\\[\[[[ZZZYYYYYXXXWWWWVVVVUUUUUUTTTSSSRRRRRQQQPPPPPPOOONNNNMMMLLLLLLKKKJJJJJIIÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÂÂÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´³´“ffffffffffffeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa````````____________^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[\[[ZYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVvŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€qgggfgfffeeeeeddcccccbbbbbaaa`````____^^^]]]]\\\\\[[[[[ZZZZYYXYXXWWWWWVVVVUUUUUTTTTTSSSRRRRQQQPPPPPOOOONNNMMMMLLLLKLKKKJJJJJIÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÃÂÂÁÂÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»»ºº¹º¹¹¹¸¸¸¸····¶¶¶¶µ¶µ´µ´´´œggggffffffffffeeeeeededdddddcccccccccccccccbbbbbbbbaaaaaaa````````_________^_^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[[[ZYYYYYXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVV|ŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€shhggfgffffeeedddddcccbbbbbaaaaa````____^^^]]]]\\\\[[[[Z[ZYZYYYYXXWXWWVVVVVVUUUUUTTTTSSSRRRQQQQQPPPPOOOOONNNNMMMMLLLKKKKKJJJIÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸······¶µ¶µµµ´´´¤gggffffffffffeeeeeeeeeeddddddcccccccccccccccbbbbbbbbaaaaaaaaa`````____________^^^^^^^]]]]]]]]]]]]]\\\\\\\\\\\[ZYYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡†‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€€vhhhggggffefeeeeddcccccbbbbbaaa`````___^^^^^]]]\\\\\\[[[[ZZZYYYXXXXXXWWWVVVVVUUUUTTTSSSSRRRRRRQQQPPPPPPOOONNMMNMMLLLLLKKKKJJJÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼»»»»ºººººº¹¹¸¸¸¸¸····¶¶¶¶µ¶µµ´´«gggfgfffffffffeeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaaa````````___________^^^^^^]]]]]]]]]]]]\\\\\\\\\\\[ZYYYYYYXXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV†ŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…†……………„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€€€€€xhhhhggfgffeeeeeedddccccbbbbbabaa````___^^^^^]]]]\\\\[[[[[ZZZYZYYXXXXWWWVVVVVVUUUUUTTTTSSRRRRRQQQQPPPPOOOONOONNMMMLLLLKKKKKJJÇÇÇÇÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÃÂÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼¼¼»»»ºººº¹¹¸¹¸¸¸¸·¸·¶¶¶¶¶µµµ´±ggggfggfffffffffeeeeeeedddddddccccccccccccccccbbbbbbbbbbaaaaaa```````__________^_^^^^^^^]]]]]]]]]]]\\\\\\\\\\\YYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV‹ŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€ziihhggggffffffedddcddcccbbbbabaaaa``_`____^^]]]]]\\\\\[[[Z[ZZZYYYYXXXXWWWVVVVVUUUTTTTTTSSSRRRQQQQPPPPPPOONOONNMMMLLLLLLKKJJJÈÇÆÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼»¼»»»ºº¹º¹¹¹¸¸¸¸¸···¶¶¶¶µµµµ´gggggfgfffffffffffeeeeeedddddddddcccccccccccccccbbbbbbbbaaaaaa````````__________^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\YYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVŒŒŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡†‡††††††††††††††………………………„„„„„ƒƒ„ƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€|iihhhghggffffefeddddddcccbbbbbaaaa```_`__^^^^^]]]]\\\\\[[[Z[ZZZZYXXXXWWWWWWVVVVUUUUTTTTTSSSSRRQRQPQPPPPPOOOONNNNMMMMLLKKKKKKÈÇÇÇÇÇÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÂÁÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»»ºº¹º¹¹¹¸¸¸¸¸¸¸···¶¶¶¶µµµtggggggffffffffffffeeeeeededddddddcccccccccccccccbbbbbbbaaaaaaa```````__`________^^^^^^^^]]]]]]]]]]]]]\\\\\\\\YYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVV_ŒŒŒŒŒŒŒŒŒŒŒ‹Œ‹‹‹‹‹ŠŠ‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††……………………„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€~iiihhhghgggfffffeeedddccccbbbbbaaaa`a```__^^^^^]]]\\\\\[[\[[ZZYZZYXXXXXWWWWVVVVVVUUUTTTSTSSRRRRRQQQQPPPPPOOOONNNNNMLLLLLLKKKÈÈÈÈÇÇÆÆÅÆÆÅÄÅÄÄÄÄÄÄÃÃÃÃÂÁÁÁÁÁÀÀÀ¿À¿¿¿¾¾¾¾¾½¾½½¼¼¼¼¼»»»»ººº¹¹¹¹¸¸¸¸¸···¶¶¶¶¶µµˆhggggggfgfffffffffeeeeeeededdddddccccccccccccccccbbbbbbbaaaaaaaa``````_____________^^^^^]^]]]]]]]]]]]\\\\\\\\ZYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVmŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…†……………„„„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€€~iiihihhhhggffffeeeedddccccbbbbbabaaa````___^^^^^]]]\\\\\[[[[ZZZZZZYYYXXXWWWWVVVVVUUUTTTTTSSSSRRRRQQQQPPPPOOOONNNMNMMMMLLLLKKÈÈÇÈÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÄÃÄÃÃÂÂÂÂÂÁÁÀÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ºº¹¹¹¹¹¹¸¸¸¸····¶¶¶µµšhgggggggfffffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaa``````__`________^^^^^^^]^]]]]]]]]]]]]]\\\\\YYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVzŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†‡†††††††††††††††…………………„……„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€iiiiihhhggggffffeeeeddddccccbbbbbaaaaa``___^^_^^^^]]]\\\\[\[[ZZZZZYYYYYXXWWWWVVVVVUVUUTTTTSTSRRRRRQQQPPPPPOOOONNNNNNMMMLLKKKÈÈÈÈÇÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»º»ºººº¹¹¹¹¸¸¸¸¸¸···¶¶¶©hhggggggggfffffffffffeeeeeeeddddddddccccccccccccccbbbbbbbbaaaaaaaa````````__________^^^^^^^^]]]]]]]]]]]]]\\\\YYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVV„ŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€€€jiiiiihhhggggggffeeeddddddcccbbbbbabaa`````___^^^^]]]\\\\\\[[[[ZZZZZYXYXXXWWWWVVVVVUUUUTTTTTSSRRRRQQQQPPPPPOOOOONNNNMMMLLLLKÉÉÉÈÇÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÂÂÂÂÁÁÀÁÁÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»ººººº¹¹¸¹¸¸¸¸¸···¶¶¶²hhhhhgggggggfffffffffeeeeeededddddddcccccccccccccccccbbbbbbaaaaaaaaa```````_________^^^^^^^^]]]]]]]]]]]]\]\\\ZYYYYYYYYYYYYYYXXXXXXXXXXXXXWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVŠŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡†††††††††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€€€€€€jijiiiiihhhhggffffeeeeeddcdcccbbbbbaaaaa`_`____^^]]]]]]\\\\[\[[[ZZZYZYYXXXXWWWWWVVVVUUUUTTTTTTSSSRRRQQQPPPPPOPOONNNNMMMMMLLLÉÉÈÈÈÈÈÇÇÇÆÇÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÂÂÁÂÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼»»»»ºº¹¹¹¹¹¸¸¸¸¸¸···¶¶¶ihhhgggggggggfffffffffeeeeeeddedddddddcccccccccccccccbbbbbbbaaaaaaa`````````_________^_^^^^^^^]]]]]]]]]]]]]\\ZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVWŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€~jjjiiiiihhhhggggfffeeeededdcccbbbbbbbaaa`a`_``__^^^^]]\]\\\\\[[[ZZZYZYYYXXXXWWWWWVVVVVUUTTUTTSTSSRRRRRQQQQPPPPOOONNNNNMMMMLLÉÉÉÈÈÈÈÇÈÇÇÇÆÆÆÆÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿À¿¿¿¾¾¾¾¾½½½½¼½¼»»»»»»ºº¹¹¹¹¸¸¸¸¸···¶¶¶‡hhhghggggggfgfffffffffeeeeeeeeedddddddccccccccccccccccbbbbbbaaaaaaaa```````____________^^^^^^^]]]]]]]]]]]\\\ZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVlŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡††‡††††††††††††††††………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€€€€}jjjjiiiiihhhghggggfeeeededdcccccbbbbbabaaa```____^^^]]]]]\\\\\\[[ZZZYYYYYXXXXWWWWWVVVVVVUTTTTTTSSSSRRRRQPPPPPPPPOOONNNMNMMMMÉÉÉÉÉÈÈÇÈÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÁÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½¾½½¼¼»»¼»º»ººººº¹¹¸¸¸¸¸¸···¶žhhhhhggggggggfgffffffffefeeeeeeddddddcccccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^]]]]]]]]]]]]]]ZZYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW|ŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€€€{kkjjjiiiiihhhhggggffffeeedddddcccbbbbbbaaa````____^^]^^]]]]\\\[[[[[Z[ZYYYYYXXXWWWWWVVVVVVUUTTTTTSSSRRRRRQQPQPPPPOOONNNNMMMLMÊÊÉÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¾¿¾¾¾¾½½½½¼¼¼»¼»»ºººº¹¹¹¹¸¸¸¸¸¸···®hhhhhhgggggggfggffffffffeeeeeeeeedddddcdccccccccccccccccbbbbbbbaaaaaaaa``````__________^^^^^^^^^]]]]]]]]]]]]ZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW‰ŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆ‡ˆˆ‡‡‡‡‡†††††††††††††††††…………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€€€zkkjjjjiiiihiihhggggfffffeeeddcdccccbbbbaaaa````____^^^^]]]\\\\\\[[[ZZ[ZYZYXYXXXWWWWWVVVVUUUTUTTSTSSRRRRRQQQQPPPPPOOONNONNNMMÊÊÊÊÉÉÈÉÈÈÈÇÇÇÇÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÃÃÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¿¾¾¾¾½½½½¼¼¼¼»»»»»º¹º¹¹¸¹¸¸¸¸¸··¶hhhhhhhgggggggggffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa``````______________^^^^^^^]]]]]]]]]][YZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡†‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€€€xkkkkjjijiiihhhhhhgggfffeeeeddddcccccbbbbbaaaa````____^^^]]]\\\\\\\[[[[[ZZYYXYXXWWWWVWVVVVUUUUTTTTTSSSRSRQQQQQQPPPPOOOONNNMNNÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½½¼¼»¼»»»»ººº¹¹¹¹¸¸¸¸¸··‡ihhhhhhhggggggggffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaaa``````_`__________^^^^^^^^^]]]]]]]]][ZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWlŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚€€€€€€vlkjkkjjjiiiiihhhghhgggffeeededdcdcbcbbbbbbaaaa````____^^^^]]\\\\\\\[[[ZZYZYYYXXXWXWWWVVVVVVUUUUTTTTSSSRRRRRQQQQPPPPPOOONNNMMÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÆÆÇÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÂÁÀÀÀ¿ÀÀ¿¿¿¾¾¾¾¾½½½¼½¼¼¼¼»»ººººº¹¹¹¹¸¸¸¸¸¸¢iihhhhhhghgggggffffffffffffeeeeeeedddddddcdcccccccccccccccbbbbbbbaaaaaaa````````_________^^^^^^^^^]]]]]]]]][ZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€€€tllkkjkjjjiiiiiihhggggggffeeeeddddcccbbbbbbaaaaa````___^_^]]]]]\\\\\\[[[[ZYYZZYXXXXXWWWWVVVVVUUUTTTTSSSRSSRRQQQQQPPPPOOONNNNNÊÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾½½½½½¼¼¼¼»»»»ººº¹º¹¹¹¸¸¸¸¸³iihhhhhhhggggggggffffffffffefeeeeededddddddcccccccccccccccccbbbbbbaaaaaaa`a```````___________^^^^^^]]]]]]]]\ZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW‹ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††………………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€rlllkkkjjjiiiiiiihhhggggffefeedddddcccbbbbbbbaaaaa`_`__^^^^^]]]]\\\\\[[[[ZZZYYZYXYXXXWWWWVVVVUUUUUTTTSTSSSSRRRQQQQPPPPPOOONNNËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÈÇÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼½¼¼¼»»»ººººº¹¹¸¸¸¸¸¸wiiiihhhhhggggggggfffffffffefeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaa`a``````___________^^^^^^^^^]]]]]]\ZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWaŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡†‡††††††††††††††††………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚€€plllkkkkjjjjiiiiihhhhgggfgffffeedddddcccbbbbbaaaa````_`___^^]^]]]\\\\\\[[[ZZZZZYYYXXXWWWWWVVVVVUUUUUTTSTSRSRRRRQQQQPPPPOPPONNËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÀÀÀÀÀ¿¿¿¾¿¾¾¾¾½½½½½¼¼¼¼»»»»ºº¹¹¹¹¸¸¸¸¸›iiiihhhhhhgggggggfggffffffffefeeeeeeedddddddcccccccccccccccbbbbbbbbbaaaaaa```````_`_________^^^^^^^^]]]]]]]ZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWW{ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‰ˆˆˆˆˆ‡‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€ollllkkkjkjjjiiiiiihhgggggfffeeedeedccccbbbbbbbaaaaa``_`___^^^]]]]\\\\\[\[Z[ZZZYYYYYXXXWWWVVVVVUUUUUTTTSSSSRRRRRQQQPPPPPPOOOOËËËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÇÇÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÁÁÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»»»»»ººº¹¹¹¸¸¸¸±iiiiihhhhhhhgggggggffffffffffffeeeeeeedddddddccccccccccccccccbbbbbbbaaaaaaaa```````__________^^^^^^^^^]]]]]ZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWŠŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚nllmlklkkkjjjjiiiiiihhhgggggfffeeeeedddccccbbbbbbaaa`````__^^^]^]]]\\\\\[[[[[ZZZYYYYYXXWWWWWVVVVUUUUUTTTTTSSRSRQRQQQQPPPPPPOOÌËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÆÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼¼»»»»ºººº¹¹¹¹¸¸¸viiiiihhhhhhgghggggggfffffffffefeeeeeeeedddddddccccccccccccccbbbbbbbbbaaaaaa`````````___________^^^^^^]]]]]ZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWW`ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚nmmlmllllkkjjjiiiiiihhhhgggfgffeeeeeeddddcccbbbbbaaaa```_`_^^^^^^]]]\\\\\\[[[[ZZYZZYYYXXXWWWVVVVVVUUUUTTTSTSSSSRRQQQQQPPPPOPOÌÌÌËËÊÊÊÊÊÉÉÉÉÈÉÈÈÈÈÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÂÂÃÂÁÁÁÁÀÁÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼»»»»ººº¹¹¹¹¸¸žiiiiiiihhhhhhggggggggffffffffeefeeeeeeddddddddccccccccccccccccbbbbbbbaaaaaaa`a`````_`_________^^^^^^^^]]]]ZZZZZZZZYZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWW|ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚nmmmmllklkkkjjjjjiiiihhhhhgggfffeeeededdcccccbbbbbaaa`````___^_^^]^]]\\\\\\[[[ZZZZYYYYXXXXXWWWVVVVVUUUUTTTTSSSSRRRRRQQPPPPPOOÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÂÃÂÂÁÂÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼»»»»»»ºººº¹¹¹¸´iiiiiihhhhhhhhhggggggfgfffffffffeeeeeeeddddddddcccccccccccccccbbbbbbbbaaaaaaaaa`````____________^^^^^^^]]]\ZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWŒŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…………………„…„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚znmmmmlllllkkkjjjjjiiiiihhhhggfgfffefeedddccccbbbbbaaaa`a`_______^^]]]]]\\\\\\[[Z[ZZYZYYXXXXXWWWWVVVVUUUUTTSSSSSSRRRQQQQQPPPPPÍÌÌËËËËËËÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÆÆÆÆÆÆÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÂÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼»¼»»»ººº¹¹¹¸¸„iiiiiihhhhhhhhhgggggggfffffffffffeeeeeeedddddddcccccccccccccccbbbbbbbaaaaaaaaaa``````____________^^^^^^]]\ZZZZZZZZZYZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWjŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡†‡†††††††††††††††………………„…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚vnnnmmmllllllkkkjjjjiiiiihhhhhggggffeeeeedcddcccbbbbaaaaa``_`____^^]^]]]\\\\\\\[[[ZZZYYYYYXXXXWWWVVVVVUVUTTTTTTTSRRRRRQQQQPPPPÍÍÌÌËËËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÈÇÇÇÆÆÅÆÆÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÂÁÁÁÁÀÀ¿¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»º»ºººº¹¹¹¨iiiiiiihihhhhhhgggggggffffffffffffeeeeeeedddddddcccccccccccccccccbbbbbbabaaaaaa``````_`___________^^^^^^]]ZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWW„ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰Š‰‰‰‰‰‰ˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††††………………„„„„„„ƒ„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚rnnnmmmmmmllklkkkjjjjiiiiihhhhgggfffffeedddddccccbbbbbaaaa````_____^^^^]]\\\\\\[[[Z[ZZZYYYXXXXWWWVWVVVVVUUUTTTSTSSSRRRQQQPQPPPÍÌÍÍÌÌËËËÊÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½¼¼¼¼¼»»»»»ºº¹¹¹¸iiiiiiiihhhhhhhhhggggggfgffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbbaaaaaa`````````_________^^^^^^^^]ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡††††††††††††††††††………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚‚‚‚‚poonnnmnmmlllklkkjjjjiiiiiihhhggggfgfefeededdddccbbbbbbaaaa`````__^^^^^]]]\\\\\\\[Z[ZZZYYYYYXXXXWWWVVVVVVUUUTTTTSSRRSRQQQQQQPPÎÍÍÍÍÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÆÆÅÆÆÅÅÅÅÄÄÄÃÄÃÃÃÂÂÂÁÂÁÁÁÁÀÀ¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼¼¼»»»ºº¹¹¹¹›iiiiiiiihhhhhhhhggggggggfffffffffffeeeeeeeedddddddcccccccccccccccbbbbbbbabaaaaa`````````___________^^^^^^ZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXXWWWWWWWWWWWWWWW{ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡††††††††††††††††……………………„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚ooonnnnnmmmllkkkkkkjjiiiiiihihhhhggfffffeeedddccccbbbbbbaaa```______^^^^]]]\\\\\\[[[[ZZZYYYXYXXXXWWWWVVVVUUUUTTTSTSSSSRRQRQQQPÎÍÍÌÌÍÌÌÌËËËÊÊÊÊÊÊÊÉÉÈÉÈÇÈÇÇÇÆÆÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÂÁÁÁÁÀÀ¿¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»»ººº¹¹µiiiiiiiiiiihhhhhghggggggffffffffffefeeeeeeedddddddccccccccccccccccbbbbbbbbaaaaaa```````_____________^^^^^[ZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXXWWWWWWWWWWWWWWŽ‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††…††……………„„„„„„„„ƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚}oooononnmmmlmllkkkkkjjjjiiiiihhhhgggfgffffeeddddccccbbbbbaaaaa``_____^^^^]]]]\\\\\[[[[ZZYYYYXYXXXWWWWVVVVVVVUUTTTTSSSSSRRRQQQPÎÎÎÍÍÍÌÌÌËËËÊËÊÊÊÊÉÉÉÈÉÈÈÇÈÇÇÆÇÆÆÆÆÅÄÅÄÄÄÄÄÃÄÃÂÃÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼»»»»»ººº¹¹iiiiiiiiihhhhhhhgggggggggfffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaaaa``````___________^^^^^\ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWs‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„„ƒƒƒƒƒƒƒƒƒ‚ƒ‚‚xooooonnnnnmmmmllkkkkkjjjiiiiihhhhgggggfffffeeeddddccbcbbbbbaaaa``_____^^^]]]]\\\\\[[[[[ZZZZYYYYXXXXWWVWVVVVVVUUTTTTSSSSRRRRRQQÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÊÉÉÉÈÈÈÇÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÂÁÂÁÁÁÀ¿¿¿¿¿¿¾¾¾¾½½½½½¼¼¼¼¼»»ººººº³iiiiiiiiiihhhhhhhhhggggggggffffffffffeeeeeeeeddddddccccccccccccccccbbbbbbbaaaaaaaa````````__________^^^^][[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXWWWWWWWWWWWŒ‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒ‚‚spooooonnnnmmmmllllkkkkjjjiiiiiiihhhggggfffeeeedddddcccbbbbbaaa````______^^]^]]]\\\\\[[[Z[ZZYYYXYXXXWWWWVVVVVVUUUTTTTSSSRSRRRQQÎÎÎÍÍÍÍÍÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¾¾¾¾¾¾½½½½½¼¼¼¼¼»º»ººº¹‹iiiiiiiiiiiihhhhhhghgggggfffffffffffeeeeeeeeeddddddcccccccccccccccccbbbbbbaaaaaaa`a`````____________^^^^[[Z[ZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXYXXXXXXXXXXXXXWWWWWWWWWWp‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒƒqpoooooonnnnmmmlllllkkkkjjjjiiiiiihhggggggffeeededddccccbbbbbaaaa````___^^^^^]]]\\\\\\[[[ZZZZYZYYYXXXXWWWWVVVVVUUUTUTSSSSSSRRRQÏÏÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÃÂÃÂÁÁÁÁÁÀÀ¿ÀÀ¿¿¿¾¾¾¾¾½½½½¼¼¼¼»»»»ºº¹²iiiiiiiiiiiihhhhhhhgggggggfgfffffffffeeeeeeedddddddddcccccccccccccccccbbbbbaabaaaaa````````___________^^[[[[ZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXYXXXXXXXXXXXXXWWWWWWWWWŒ‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Š‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡‡†††††††††††††††……………………„„„„„„„ƒƒƒƒƒƒƒƒƒƒ€ppppoooooonnmmmmmlmllkkkkjjjjiiiiiihhhggggfffeeeeeddddcccbbbbbbaaa````____^^^]]]]\\\\\\\[[ZZZZZZYYYXXXWWWWWVVVVUUUTUTTTTSSRRRRRÏÏÎÎÎÍÎÍÌÍÍÌÌÌËËËËÊÊÊÊÊÉÉÉÈÈÈÇÈÈÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÃÃÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼¼¼»»»»ººº‹iiiiiiiiiiiihhhhhhhgggggggggfffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbbbaaaaaa``````_``_________^^\[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXWXXWWWWWWp’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒƒyqqpoppoooonnnnmmmlmllllkkkjjjiiiiiihhhhgggfggfefeeeecdccccbbbbbbbaaaa````_^^^^^^]]\\\\\\\[[[ZZZZYZYXXXXXWWWVVVVVUUUUTUTTTSSSSRRÏÏÏÎÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÄÃÃÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½½½½½¼¼¼»¼»»»»º³iiiiiiiiiiiiihhhhhhghggggggfgfffffffffeeeeeeededddddddcccccccccccccccbbbbbbbbaaaaaa`a```````___________^[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWW’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆˆ‡‡‡‡‡††††††††††††††††††…………………„„„„„„„ƒƒƒƒƒƒƒƒtqqqppppoooononnnmmmmllklkkjjjjijiiiiihhggggggfeeeeeddddccccbbbbbaaaa````___^^^^]]]]]\\\\\\[[[ZZZZZYXYYWWXWWWVVVVVVUUUTTTTSTSSSRÐÏÏÏÏÎÎÎÍÍÍÍÍÌÌËÌËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÆÇÆÆÆÅÆÅÄÄÄÄÄÄÃÄÃÃÂÂÂÁÂÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½¼¼¼¼¼»»»»ºº“jiiiiiiiiiiiihhhhhhhggggggggggffffffffeeeeeeeedddddddccccccccccccccccbbbbbbbaaaaaaa`````````__________^[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWu’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡ˆ‡‡‡‡‡‡‡††††††††††††††††………………„„…„„„„„ƒƒƒƒƒƒƒrqqqqpppooooonnnnnmmlmllllkkkkjjjiiiiihhhhggggfffefeeeddcdcccbbbbbaaaaaa```___^^^]]]]]\\\\\[[[[ZZZZZYYYXXXXWWWVVVVVVVUUTTTTTTSRSÐÐÏÏÏÎÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÁÀÀ¿¿¿¿¾¾¾¾¾½½½¼¼¼¼¼¼»»ºº¶jjiiiiiiiiiiihihhhhhhhgggggggffffffffffefeeeeeeddddddddcccccccccccccccbbbbbbbaaaaaaaa```````___________\[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWW’’’’’‘‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒƒƒ~rqqqqqppopooooonnnnmmmllllkkkjkjjiiiiiihhhhhhggfffeeeeddddcccccbbbbbaaaa``____^_^^^^]]]\\\\\\[[[ZZZYYYYYXXXWXWWWVVVVVUUUTTTTTTSSÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌËËËËËÊÊÊÊÊÊÉÉÈÈÈÈÈÈÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾½½½½½¼¼¼¼»¼»»» jijiiiiiiiiiiiihhhhhhggggggggffffffffffefeeeeeedddddddcdccccccccccccccccbbbbbbaaaaaaaa``````__________^[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXX~’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆ‰ˆˆˆˆˆˆ‡‡‡‡‡‡‡‡††††††††††††††††…………………„„„„„„„„ƒƒƒwrrrrqqpqpppooooonnnmmmmllllkkkjjjjjiiiiihhhgggggfffeeeeddddcccccbbbbaaaa`````__^_^^^]]]]\\\\\[[[[[ZZZZYYYXXXWWWWWVVVVVVUUUTTTSTSÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌËËËËËÊÊÊÊÉÊÉÉÉÈÈÇÈÈÇÆÇÆÆÅÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼»»»ººtjjiiiiiiiiiiiihhhhhhhggggggggffffffffffeeeeeeeeedddddddcccccccccccccccbbbbbbbbaaaaaa``````````________[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXX_’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††††…………………„„„„„„„ƒƒƒtsrrrqqqqppoooooonnnnmmmmllllkkkkjjjjiiiiihhhhhggggfeefeededddccbbbbbbbaaa`a``_`___^^^^]]]]\\\\[[[[[ZZZYYYYYXXXWWWWVVVVVVUUUTTSTTÐÐÐÐÐÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½¼¼¼»»»º»¬jjiiiiiiiiiiiiihhhhhhhggggggggffffffffffffeeeeeeddddddddcccccccccccccccbbbbbbbaaaaaaa`````````________[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXˆ’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡‡‡‡†††††††††††††††…†………………„„„„„„„„€ssrrrrqqqpppppoooonnnnmmmmlllllkkkjjjjiiiiihhhhhgggfffeeeeeddddccbbbbbbaaaa````____^^^^^]]\\\\\\[[[[[ZZZYYYYXXXXWWWWVVVVUUUUTTTTTÐÐÐÐÐÏÏÏÏÎÎÎÍÎÍÍÌÌÌÌÌÌËËÊËÊÊÊÉÉÊÉÈÉÈÈÈÈÇÇÇÇÆÅÆÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾½½½½¼¼¼¼¼¼»»jjjiiiiiiiiiiiiihhhhhhgggggggggfffffffffefeeeeeeeddddddcdcccccccccccccccbbbbbbabaaaaa`a``````________][[[[[[[[Z[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXs’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‡‡‡‡ˆ‰‹”›¢©°´²¯¬¨¥ œ˜”Œ‡………„…„„„„„ƒxtsssrrrqqqqppppoooonnnnmmmmlmllklkkjjjjiiiiiihhhgggfgfffeeeeddddccccbbbbbaba`a````_^^^^^]]]]\\\\\\[[[ZZZYYYYYXXXXXWWWVVVVVUUUTTTTÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÇÈÈÇÇÇÆÆÆÅÆÅÄÅÄÄÄÄÃÃÃÃÂÃÂÂÂÁÁÀÁÀÀÀ¿¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»¸jjjjjiiiiiiiiiiiihhhhhhhggggggfgfffffffffeeeeeeeedddddddcdccccccccccccccbcbbbbbbaaaaaa`aa`````________[[[[[[[[[[[ZZZZZZZZZZZZYZZYYYYYYYYYYYYYYYYYXXXXXXXX‘’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰ˆˆ‰ˆˆˆˆˆ‰‰‹‹ŒŒ•𡦭³¶³°®«¨¥£¡žš˜–“Œˆ…„„„„„„tsssrrrqqqqqqpppooooonnnnnmmmmllllkkkkjjjjiiiihhhhhggggfffeeeedddccccbbbbbbaaa``````___^^^^]]]\\\\\[[[[ZZZZZYYYXXXXWWWWVVVVVUUUUTTÑÐÑÐÐÐÐÐÏÏÏÏÎÎÍÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÃÂÁÁÁÁÁÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼¼¼»¼»©jjjjiiiiiiiiiiiihhhhhhhhhgggggfffffffffffefeeeeededddddddccccccccccccccccbcbbbbbabaaaaa````````______\[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYXXXXXXX†’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‹ŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰ˆˆŠ‹ŒŒŒŒŒŒ•𠦬²¶³°®«©¦£¡Ÿš™–”’ŽŒ‹‰†…„„tttsssrrrrqqqqqpppooooonnnnmmmllllklkkjjjjjiiiiihhhgggggffefeedddcdcccbbbbbbabaa````____^^^^]]]]\\\\\[[Z[[ZZYYYYYXXXXWWWWVVVVUUUTTÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÍÍÍÌÍÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÀÀÁÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼¼»»jjjjiiiiiiiiiiiihhhhhhhhgggggggggfffffffffeeeeeeeddda\XTPKHDB?<:98778:;=@DHLPV\bbaaaaa``````````____^[[[[[[[[[[Z[ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYXXXXs““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‹‹”𠦬±¶³°®«©¦¤¡Ÿš™—•“ŽŒ‹Š‰‡†…„ztttssssrrrqrqqqqpppoooonnnnnmmmmmlkllkkjjjjiiiiiihhhhgggfffffeeedddddccbbbbbbaaaa``_`__^_^^^]]]\\\\\\[[[[[[ZZYZYXXXXXXWWVVVVVVVUUTÒÒÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÍÎÍÍÍÌÌÌËËËÊËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀ¿¿¿¿¿¾¾¾¾¾½½½½¼¼»»»¹njjjijiiiiiiiiiiiihhhhhhghggggggggfffffffffeeeb^YTOIGEDCA??><;:98866665555555566=DKS[aaa````````_____[[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXX[’“““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰Š‰Š‹Ž”™Ÿ¥«±¶´±®«©¦¤¡Ÿ›™–•“‘‹Š‰‡†…„ƒzywttsssssrrqrqqqppopooooonnnmnmmmlllkkkkkjjjjiiiihhhhgggfgffeeeeeddddcccbbbbbbaaaa```_____^^^^]]]\\\\\\[[[[[ZYYYYXYXXXWWWWWVVVVUUUÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÍÎÍÍÍÌÌÌÌËËËÊËÊÊÊÊÊÊÉÉÉÈÈÇÇÇÆÇÇÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÁÂÁÁÁÁÁÀÀÀ¿¿¿¾¾¾¾¾¾½½½½½¼»»¼¯jjjjjjiiiiiiiiiiiihhhhhhhghgggggfffffffffc_ZUQPNLJHFEDA@?>=;:99876665555555656678889<;:99776655555555656778999:;<=>GPY````__`__[[[[[[[[[[[[[Z[ZZZZZZZZZZZZZYYYYYYYYYYYYYYYYY““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŠŠŠ‹“™ž¤ª°¶´²¯¬©¨¤¢ ›š˜–“‘Œ‹‰ˆ‡†…ƒ‚zyxvutsrrssrrrrrrqqppppopooooonnnmmmllllkkkkkjjjjiiiihihhgggfgffffeeddddcccccbbbbbaaaa````_____^^^]]]\\\\\[[\[ZZZYZYYXYXWXWWWWVVVVVUÓÒÑÒÑÑÑÑÐÐÐÐÏÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÅÅÅÄÄÄÄÄÃÄÃÂÂÂÂÂÁÁÁÁÀÀÀÀ¿¿¿¿¾¾¾¾½¾½½¼¼¼¼»¼‰jjjjjiiiiiiiiiiiihhhhhhhhhggggggfeca^\ZWVTRPNLJIFEDB@?>=<;:9877655555555566678899:;<=>?@AAFOX````__\[[[[[[[[[[[[[ZZZZZZZZZZZZZZYZYYYYYYYYYYYYYYp””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹ŠŠŒŽ’˜ž¤ª¯¶µ²¯­ª¨¥¢ žœš˜–”’ŒŠŠ‰‡†…„‚yxwutsrpnlossrrrrqqqqqppoooooooonnnmmmmllkkkkkjjjiiiiihhihgggfffffeeeeeddddcbcbbbbbaaaa```______]^]]]]\\\\\\[[[[Z[ZZYYYXXXXWWWWVVVVVÓÓÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÌÌÌÌÌÌËËËÊÊÊÊÊÉÊÉÈÈÉÈÈÈÇÇÇÇÆÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÂÁÁÀÀÀÀ¿À¿¿¾¾¾¾¾¾½½½¼¼¼¼¼¹rjjjjjiiiiiiiiiiiiihhhhhhhgggijifdb_][YWUSQOMKIHFECA@?><;:99877655555555666778999;<<=>?@ACDEFHQY`__^[[[[[[[[[[[[[[[ZZZZZZZZZZZZZYYYYYYYYYYYYYY_“”””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Œ’˜£©¯µµ³¯­ª¨¥£ žœš˜–”’ŽŒ‹Š‰‡†…„‚zxwvusrpnliggnsssrrqrqqqpppooooonnnnmmmmlllkkkkjjjjjjiiihhhhggggggffefeeedddccccbbbbbbaaa```_`___^^]^^]]]\\\\\[[[[ZZYYYYXXXXXWWWVVVVVÓÓÒÒÑÑÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÉÈÈÇÈÇÇÆÆÆÅÅÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÁÁÀÀÀ¿À¿¿¾¾¾¾¾½½½½½¼¼¼»³jjjjjjjiiiiiiiiiiiihhhhhhjmnljhfca^]ZXVTRPNLKHFEDB@?>=<;:9877666555555666788999:;<=>?@ABDEFHHJKNV\_\[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYŽ””””“““““““’’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹ŽŽŽ’—£¨¯µµ³°®«¨¦£¡Ÿ›˜–”“ދЉ‡†…„ƒ‚€xwvutrqoljgeb`eosrrqrqqqppppoooooonnnmmmmmmlllkkjjjjjiiiihhhhhgggffffeeededdddccccbbbbbaaa```_____^^^^]]]\\\\\[[\[[ZZZZZYYYXXXXWWWVVVÓÓÓÓÒÒÑÑÑÐÑÑÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÍÍÌËÌËËËÊÊÊÊÊÉÉÉÉÈÈÈÈÈÈÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÁÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾½½½½½¼¼¼¼ªjjjjjjjiiiiiiiiiiiihhh„ rpnkigdb`][YWTSQOMKJHFECA@?><;:9987766555555566688899:;<=>?@@ACDEFHIKLMNPUe[[[[[[[[[[[[[[[ZZZZZZZZZZZZZYZZYYYYYYYYYˆ•””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒ‹‹ŒŽŽŽŽŽ‘—¢¨®´¶³°®«©¦£¡Ÿ›˜—”“ŒŠ‰ˆ†…„ƒ‚{xvusrqomjhfc`^[]jrrrrqqqqqppoooooonnnnnmmmmlllkkkkjjjiiiiiiihhhgggfffeffeddddddcccbbbbbbaaa`````____^^]]]]]\\\\\[[[ZZZZZZYYYXXXWWWWVVÔÓÓÓÓÒÒÒÒÑÑÐÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËÊÊÊÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÂÁÁÁÁÀ¿¿À¿¿¾¾¾¾¾¾½½½¼¼¼¼»¢jjjjjjiiiiiiiiiiiio©°tqomjheca^]ZXVTRPNLKIGEDB@??=<;:988776555556556678899::;<=>?@ABDEFHIJKLNOQppme][[[[[[[[[[[[[[ZZZZZZZZZZZZZYYYYYYYYY‚••”””””“”““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽ‘–œ¢¨®´¶³±®¬©§¤¡Ÿ›™—•“‘Œ‹‰ˆ‡†„ƒ‚€xwvtsromjhfca_\]_biprrrqqqppppppoooononnmmmlmllklkkjjjijiiiiiihhhggggfffeeeeeddddccccbbbbbaaaaa``_`__^^^]]]]\\\\\\[[[[ZZZZZYYYXXXWWWWVÓÔÓÓÓÒÒÒÑÑÑÑÑÑÐÐÐÏÐÐÏÎÎÎÎÍÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÉÉÉÈÈÈÇÇÇÇÇÇÆÆÆÆÅÅÄÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾¾½½½½¼¼¼¼»™jjjjjjjiiiiiiiis’°°±°spnkigdb`]\YWURQOMKJHFECB@?>=;::987776555555666778999:;<=>?@ABCDEFHJKLMNQQpppnnf^[[[[[[[[[[[[[ZZZZZZZZZZZZZZZYYYYY|•••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽŽŽ–›¡§­³·´±¯¬©§¤¡Ÿž›™—•“‘ŒŠ‰ˆ‡……„‚€zwvtsrpnkifda_\]`bdgipsrrqqqqqpppoooooonnmnmmmlmlkkkkkjjjjiiiihhhhhhgggffefeeededddccccbbbbbaaa```_`___^^^^]]]\\\\\\[[[[[ZZZYYYXYXXXWWWÔÔÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÈÈÇÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÄÃÃÃÂÂÂÂÁÁÀÀÀÀÀ¿¿¿¾¾¾¾¾¾½½½½¼¼¼»“jjjjjjiiiiiiu“°°°±±tqoljhfda^]ZXVTRPNLKIGEDBA??=<;:998777656556666678899::<==>?@ACDEFHIJKMNOQppppnooog^[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYw•••••””””””””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŒŒŒŒŒŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽ–›¡§­³·´²¯¬©§¤¢ ž›š—•“’ŒŠŠˆ‡†…ƒ‚wwutrqnkigdb`]^`begjknrrrqqqqqpppppoooonnnmnmmmmlllklkkjjjiiiiiihhhhgggfffeefeeedddccccbbbbbaaaa````____^^^^]]]\\\\\\[[[[[ZZZYYYXXXXXWWÔÔÔÔÔÓÓÒÓÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËËÊÊÊÊÊÊÊÉÉÈÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼¼¼jjjjjjjjiq’¯°°°°°±rpnkigdb`]\YWUSQOMKJHFECB@?>=;;:987766665556666788999:;<=>?@ABCEFGIJKLNOQRpppppnooopg][[[[[[[[[[ZZZZZZZZZZZZZZZZYr•••••••”•””””””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ•› ¦¬²·µ²¯­ª¨¥¢ žœš˜–“’ŽŒ‹Š‰‡†…„‚€yvutsqnljgeb`]]`bdgikmpsusrrrqqqppppooooooonnmmmmllllkkkjjjjjjiiiihhhggggfffffeeedddddccccbbbbabaaa```___^_^^]]]]]\\\\\\[[Z[ZZZZYYYXXXWWÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÏÎÎÍÍÍÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÇÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¿¾¾¾¾½½½½½¼¼¼»‹jjjjjjjŒ­¯¯°°°°°tqpmjhfda_]ZXVTRPNLKIGEDBA@?=<;:998876666666566778899:;<=>??AACDEFGIJKMNPQppppppoooopppf[[[[[[[[[[ZZZZZZZZZZZZZZZq••••••••”””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ”𠦬²·µ²°­ª¨¦¢ žœš˜–”’ŽŒ‹Š‰‡†…„ƒ}wvusqomjhec`^]_begilnpsuwyurrrqqqppppoooooonnnnmmmmlkllkkkjjjjiiiiihhhhgggggfffeeeedddccccbbbbbbaaa````____^^^]]]]]\\\\[\[[ZZZZYYYYYXXXWÕÔÔÔÔÓÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌÌËËÊÊÊÊÊÊÊÊÉÈÈÈÈÇÈÇÇÇÇÆÅÆÅÅÅÄÄÄÄÄÃÃÃÃÃÂÂÂÁÁÁÀÀÀÀÀÀ¿¿¿¾¾¾¾¾½½½½½¼»»‹jjjj„¥­®¯°°±±±±rpnljgeb_]\YWUSQONKJGFECB@?>=<;:988776666666667778999:<<=>?@ABCEFGIJKLNOPRqqqpqpqooppppqoc[[[[[[[[[[ZZZZZZZZZZZZr•–••••••••”•””””””””““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŒŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ”™Ÿ¥«±·¶³°­ª¨¦£¡Ÿš˜–”’ŽŒŠ‰ˆ‡…„ƒ‚~xvttromkhfca^]`bdfiknpsuwz}yrrrqqqqpppoooooonnnmmmllmllkkkkkjjjiiiiiihhhhgggffffeeeedddcccbbbbbbaaaaaa``___^^^^^]]]]\\\\\\[[[ZZZZYYYYYXXÕÕÕÔÔÓÔÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÌÌÌËËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÄÃÄÄÃÃÂÂÂÂÁÁÁÁÀÀÀ¿¿¿¿¾¾¾¾¾¾½½¼½¼¼»jwš­­®¯°°°±±±uqpmjhfda_][XVTRPOLKIGEDBA@?><;:998877666666677778999:;<=>?@ABCDFFHJKLMNPQqqqpqqqqpopppqqrrl`[[[[[[[[ZZZZZZZZZZZs•––•••••••••””•””””””“““““““’’’’’’’’’’’’’’’’‘’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ“™Ÿ¤ª±¶¶³°®«©¦£¢Ÿž›™—”“ŒŠ‰ˆ‡†„ƒ‚~{vutrpnkhfca_]_bdgiknpsuxz}†Š~rrrqqpppppooooonnnnnmmmllllkkkkkjjjjiiiihhhghgggfffeeededddcccccbbbbaaaa```______^]^]]]]\\\\\[[[Z[ZZZZYYYXXÕÕÕÔÕÔÓÔÓÓÓÓÓÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÍÌÌÌËÌËËËÊÊÊÊÊÉÉÉÈÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÀÁÀÀÀÀ¿¿¾¾¾¾¾¾½¾½½¼½¼¼¨«¬­®®°¯°±±±±spnkjgdb`][YWUSQOMKJHFECB@?>=<;:99877776666666778899:;;<=>?@ACDEFGIJKMNPQRqqqqqqqqqopppqqrrssh[[[[[[[[ZZZZZZZZZw––––•••••••••••””•”””””“““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ“™ž¤ª°¶¶´±®¬©¦¤¢Ÿž›™—•“‘Œ‹‰ˆ‡†…ƒ‚~~wutspnkigdb_]`begikmpsuwz}…Š“ƒrrrrqqqppppooooonnnnmmmlllllkkjkjjiiiiiihihhhgggfffffeedddddccccbbbbbbaaaa``_`___^^^^]]]\\\\\\[[[[ZZYZYXYXÖÕÕÕÕÕÔÔÔÓÓÓÓÓÒÑÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÍÌÍÌÌËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÇÇÇÇÆÆÅÆÅÅÅÄÄÄÄÄÄÃÃÃÃÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¾¾¾¾¾½½½½¼ÀÊÏÀ­­®¯°°°±±±trpmjhfdb_]ZXVTRPNLKIGEDCA@?=<;:99877776666666778899::;==>?@ABCDFGHIKLMOPRqqqqqqqqqqqppqqrrrsstpa[[[[[[[[ZZZZZZ}––––––––•••••••••””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ’˜ž¤ª°¶¶´±®¬©¦¤¢Ÿžœš—•“‘Œ‹Š‰‡†…ƒ‚‚€~zutsqnligeb`]`bdgilnpruwz}…‰Ž’—š„rrqqqqqppppoooonnnnmmmmmlllklkjkjjjjiiiiihhggggfgffffeeeeddcccccbbbbbaaa````___^_^^^^]]]]\\\\\[[[[ZZZYYYYÖÕÖÕÕÔÔÔÔÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÎÎÍÌÌÌÌÌËËËËÊÊÊÊÉÉÉÉÈÈÈÇÈÇÇÇÆÇÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÂÁÁÀÀÀÀÀÀ¿¿¿¿¾¾¾¾½½½ÄÍÏÐÐÆ®®°°±±±±±spnligdc`^\ZWUSQPMKJHFEDBA?>=<;:99887766666666778899:;;==??@ABDEFGIKLMNOQRqqqqqqqqqqrpqqqrrssttuui[[[[[[[Z[ZZZ„—–—–––––––••••••••••””””””””““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ’—£©¯µ¶´±¯¬©§¥¢ žœš˜–”’ŽŒ‹‰‰ˆ†…„‚‚€~|vusqnmjhec`]_adgiknprtwz|…ŠŽ“–š™˜‚rrrqqpqppppooooonnnnmmmmlllklkkjjjjiiiiiihhhgggggffeeeeeddddcccbbbbbbaaaa```___^_^^]^]]]]\\\\[[[[ZZZZZYYÖÖÕÖÖÕÔÕÔÔÓÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÍÍÍÍÌÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÇÆÆÅÅÅÅÄÄÄÄÄÃÃÃÃÂÃÂÂÂÂÁÁÀÀÀÀÀ¿¿¾¾¾¾¾¾ÀÉÍÎÏÐÑÒ˯°°±±±±tqomkifdb_][YVTRQOLKIGEDCA@?=<<:99988766666677778899::;<=>?@ABCDEGHJKLNOPRqqqqqqqqrrrrrqqrrrsttuvvwq`[[[[[[ZZZ‹————–––––––•••••••••••””””””””““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽ’—£©®´·´²¯¬©§¥¢¡žš˜–”’ŽŒŠ‰ˆ††„ƒ‚€~|xusromkhfc`^`befikmpsuwz|€…‰Ž’–šš™˜—rrqqqqqppppooooonnnmmmmlllllkkkjjjjiiiiihhhhggggffffeeeeeddcccccbbbbaaba`````____^^^^]]]]\\\\\[[[ZZZYZYÖÖÖÕÕÕÕÕÕÔÔÔÓÓÒÓÒÒÑÒÑÑÐÑÐÐÐÐÏÏÐÏÏÏÎÍÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÊÉÉÈÈÈÈÇÇÆÆÇÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÂÁÁÁÁÁÀÀÀÀ¿¿¾¾¾¾ÂËÌÍÏÐÑÒÓÔд±±±²±spnljgec`^\ZWUTQPMKJHGECB@?>=<;:9988776666667777889::;;=>??@ACDEFHIKLMNPQRqqqqrqqrrrrrsrqrrssttuvwwxyg[[[[[[`‘—————–––––––––•••••••••”””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘ŽŽŽŽŽŽŽŽ‘—¢¨®´·µ²°­ª¨¥£¡ž›˜—”’ŽŒŠ‰ˆ††„ƒ‚€~}yutromkhfca^`bdfiknpruwy|€…‰’–›š™™—––{rrqqqpqqpppoooonnnnnmmmllllllkkjjjjiiiiihhhhhggggffeeeedddddccccbbbbaaaa`a``___^^^^^^]]]\\\\\\\[[[[ZZZ×Ö×ÖÖÕÕÕÕÔÔÓÔÓÓÒÓÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÍÌÌÌËËËËËÊÊÊÊÉÉÉÈÈÈÈÈÇÈÇÇÆÆÆÆÅÅÅÅÄÄÄÄÄÃÃÃÂÂÂÂÁÁÁÀÀÀÀÀ¿¿¿¿¿ÅËÌÍÏÐÑÑÒÔÔÔÔ¾±²±trpmkhfdb_]ZXWURQNLKIGFDCA@?><;:99988777766677788899:;<==>?@BCCEFGHJKLNOQRqqqqqqqqrrrrsssrrrsttuvvwxyyym[[[[r–˜———————–—–––––––••••••••”””””””””““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽŽŽ–œ¢¨®³·µ³°­ª¨¦£¡Ÿ›™—•’ŽŒŠŠˆ‡†…ƒ‚€}|vtrpnkifdb__bdfikmpruwy{€…‰’•šš™˜——–•’wrrrqqqqppppooooonnnnnnmmllklkkjjjjjiiiiihhhgggggfgffefedddddccccbbbbbaaaaa``____^^^^]]]\]\\\\[\[[ZZZZ×××ÖÖÖÕÕÕÔÔÔÔÔÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÎÏÎÎÍÎÍÍÌÌÌÌÌËËËÊÊÊÊÊÊÉÉÈÈÈÈÇÇÈÇÇÆÆÅÅÅÅÅÅÄÄÄÄÃÄÃÃÃÃÂÂÁÁÁÁÁÁÀÀ¿¿ÀÆÊËÍÏÏÐÑÒÓÔÔÕÕÖɲ²sqnljgdc`^\ZWVTQPMLJHGECB@??=<;:9988777777777788899::;<=>??@BCDEFHJKKMOPQSqqqqrrrqrrrsstttrssttuuwwxyyz{t`[‚˜˜˜————————–––––––••••••••••”””””””””““““““’“’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘ŽŽŽ–›¡§­³¸µ³°­«©¦£¡Ÿ›™—•“‘ŽŒŠ‰‰ˆ†…ƒ‚€~|wusqnkigeb__bdfiknprtwy{€„ˆ‘–šš™™˜—––•”Œrrrqrqqpppopoooooonnnmmmlllllkkkjjjijiiiiihhhggggffffeeeddddccccbbbbbbaaa`````_____^^]]]]]\\\\\\[[[ZZ×××ÖÖÖÖÖÕÕÕÔÔÔÓÓÓÓÓÒÒÑÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÍÍÌÌËËËËËÊÊÊÊÉÉÊÉÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÅÄÄÄÄÄÄÄÃÃÂÂÂÂÂÁÁÁÁÀÀÀÂÈÊËÌÎÏÐÑÑÒÓÔÕÕÖÖÖÑvrpmjieda_][XVTRQNMKIGFECB@?>=<::998877777777778899::;<<>>?@ABDEFGHJKLNPQRrrqqqqrrrrrsssttutsstuuvwxyyy{{|{‘˜˜˜˜˜—˜——————––––––•–••••••••••”””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘•›¡§­²¸¶³°®¬¨¦£¢Ÿ›™—•“‘Œ‹‰‰‡†…„ƒ€~}yutqoljgec`_bdfhknpruwy|„ˆŒ‘•™šš™˜—–––•”“…rrrrrqqpqppoooooonnnnmnmmllllkkkkjjjiiiiihhhhhggggffffeededdcccbbbbbbbaaaaa````__^^^^]]]]\\\\\[[[[ZZ×××××ÖÖÖÕÕÕÔÕÔÔÓÓÓÓÒÒÒÑÒÒÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÌÍÌÌÌËËËÊÊÊÊÊÊÉÊÉÈÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÄÄÄÄÄÄÃÃÃÃÂÃÂÂÁÁÁÁÀÃÈÊÊÌÍÏÐÑÑÒÓÔÕÕÖÕÖÖÖ—oljhec`^\ZWVTQPMLJIGEDBA??=<;:9998777777777778999:;;<=>?@ABCEEGHIKKNOPRSrqqqqrrrrsrssttuuutttuvvwxyyz{|‰˜š™˜˜˜˜˜˜——————––––––––•–••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘•› ¦¬²¸¶³±¯¬©¦¤¢ ž›™—•“‘‹Š‰ˆ†…„ƒ€~|{vtqomjgec`_bdfhkmpruwy|„ˆŒ‘•ššš™™˜—––”””“’rrrqqqqqpppoooooonnnmmmmmlllkkkkkkjjiiiiihhhhhgggfffeeeeeedddccccbbbbbbaa```_`__^_^^^^]]]]\\\\\[[[[××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÓÓÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÎÍÍÍÍÌÌËËËËËÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÇÇÆÆÆÆÅÅÅÅÅÄÄÄÄÃÃÃÂÃÂÂÂÁÁÁÃÇÉÊÌÍÏÏÐÑÒÓÓÔÕÕÖÖÖÖ™–”Šjifdb`][XWUSQOMKJHFECB@?>=<::998877777777788899:;;;=>??AACDEFGIKKMNPQRrrrrrrrrrrsrssuuuvvvtuvvwwyyzz|’™™š›š˜˜˜˜˜————————–––––––––•••••••••••”””””””“““““’’’’’’’’’’’’’’’’‘’‘‘‘‘‘‘‘”𠦬²¸·´±¯¬©§¤¢ žœš˜–”‘‹Š‰ˆ‡…„ƒ‚~|{wurpmkhfca_bdghkmpruwy{„ˆŒ‘•™›š™™˜——–•””“’‘wsrrrqqqpppppoooooonnnmmmlmllkkkkkjjjjiiiiihhhhgggfffffeeedddcccccbbbbbaaa`````____^^^^]]]]\\\\\\[[Ø×××××ÖÖÖÖÕÕÕÕÔÔÔÔÔÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÌÌÌÌËËËËÊÊÊÊÊÉÉÉÉÉÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÄÄÃÃÃÂÂÂÂÁÄÇÈÊËÌÎÏÐÑÒÓÓÕÕÖÖÖÖÖÖ—•’xeca^\ZWVSRPMLJIGEDCA??>=;:9998877777777888899:;<<>??@ACDDFGHJKLMOPRSrrrrrrrrrssstttuuvvvvuvvwxyyzˆ—˜™š››œ›˜˜˜˜˜˜———————––––––•••••••••••••””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘“šŸ¥«±¸·´²¯­ª§¤¢ žœš˜–”’ދЉˆ‡†…ƒ‚}|xurpnkhfda`bdfiknpruwy|ƒ‡Œ•™››š™™˜—–•””“’’‘ˆssrrrqqqqqppopoooonnnmmmmmmllkkkkjkjjjiiiihhhhgggfgfffeeeeddddccccbbbbbaaaa````___^_^^]]]]]\\\\\[[ØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÓÓÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÏÐÏÏÏÏÎÎÍÍÍÍÍÌÌÌËËÊËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÆÆÆÆÆÅÅÅÅÄÄÄÄÃÃÃÃÂÂÂÄÇÈÉÊÌÍÏÐÑÒÒÓÔÕÖÖÖÖÖ×™–”’Š‚f_][XWURQOMKIHFECA@?>=<;:998887777777788999::;<=>??@ACEEGGIKKMOPQRrrrrrrrrrrsssttuuvvvwwvvwxyy~’—˜™™š›œžœ˜˜˜˜˜—˜——————––––––––••••••••••••””””“”“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘”™Ÿ¥«±¶·µ²¯­ª¨¥£¡Ÿœš˜–”’ŽŒŠŠ‰‡†…„‚€}|yuspnkigda_bdfikmprtvy|ƒˆŒ”™››š™˜˜—––•””“’’‘srsrrrqqpqpppooooonnnnnmmllllkkkkjjjjjiiiiihhhhgggfffffeeeddddcccbbbbbbaaaaa```___^^^^^]]]]\\\\[[ØØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÔÓÓÓÒÒÒÒÒÑÑÑÑÐÐÐÐÏÏÏÏÏÎÎÎÎÍÍÍÍÌÌÌËÌËËËÊÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÅÅÅÅÅÅÄÄÄÄÄÃÃÂÃÄÅÇÉÊÌÍÎÏÐÑÒÓÓÕÖÖÖ×××ט–“ŽŒ‰‡„w\ZXVSQPNLJHGEDCA@?>=;::99887777777788899::;<<=??@ACDEFGIJKLNPPQSrrrrrrssssssttuuvvvwwwxwwxy–—˜˜™š››žŸŸž˜˜˜˜˜˜——————–—––––––•–•••••••••””””””””““““““““’’’’’’’’’’’’’’‘’‘‘‘‘‘‘‘“˜ž¥ª°¶·µ²°­«¨¥£¡Ÿ›˜–”’ŽŒŠ‰ˆ‡†…„ƒ€}|{vsqoligeb`bdfhkmprtwy|~ƒ‡Œ”™œ››š™˜˜—––•”“’‘‘Žvsrrrrqqqqpppppooooonnmmmmmlllklkjkjjjiiiiiiihhhggggffefeeeeddcdcccbbbbbaaaaa````____^^^]]]]\\\\[ÙØØØØ×××××ÖÖÖÕÕÕÔÕÔÔÓÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÍÎÍÍÍÌÌÌÌËËÊÊÊÊÊÊÊÊÉÈÈÉÈÈÈÇÇÇÇÆÆÆÅÅÅÅÄÄÄÄÄÃÃÃÃÅÇÈÉËÌÎÏÐÑÒÓÔÕÕÖÖÖ×××™—”‘Šˆ†„}kWUSQOMKJHFECB@??=<;:999887777777788999:;<<=>?@ABCDEGHIKKMOPQRrrrrrrrrsssssttuvvvwwwxyyx†”–——˜™™š›œžŸ  ž˜˜˜˜˜————————––––––––••••••••••””””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘“˜¤ª¯µ¸µ²°­«¨¦£¡Ÿ›™—”’ދЉ‡†…„ƒ‚€~}{vsroljhec`bdfiknprtwy|‚‡‹”˜››š™™——––•”““’‘Ž„ssrrrrqqqqpppppoooonnnmmmmmmllklkkkkjjjiiiiiihhhggggfffffeeddddcccbbbbbbaaa`a````___^^^^^]]\\\\\ÙÙØØ×Ø×××××ÖÖÖÕÕÕÔÔÔÔÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏÏÎÎÎÍÍÍÌÍÌÌËËËËËÊÊÊÊÉÉÉÈÉÈÈÈÇÇÇÆÆÆÆÆÆÅÅÄÄÄÄÄÄÄÃÄÆÇÉÊÌÍÎÐÐÑÓÓÔÕÖ×××××ט•“‘ŽŒŠ‡…‚€~|wcRPNLKIGEDCB@?><<;:999877777777889999;<<=>??ABBDEFHIJKMNOQRTrqrrrrrsssstttuuvvvwxxxy‡–••——˜™™š››žŸŸ ¡¢Ÿ˜˜˜˜˜˜———————–––––––––••••••••••””””””””“““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘’˜£©¯µ¸¶³±®«©¦£¡Ÿ›™—•“‘ދЉ‡††„ƒ‚€~}{wtronjhecaadfiknprtwy|~‚‡‹”˜œ››™™˜——––””“’‘‘ŽŒ{ssrsrrrrqqqppppoooonnnnmmmmllllkkkkjjjiiiiiihhhgggggffffeeeedddcccccbbbbaaaa````____^^^^]]]\\\\ÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÏÏÏÎÎÍÎÍÍÌÌÌËËËËËÊÊÊÊÊÊÉÉÉÉÈÈÇÇÇÇÇÆÆÆÆÅÅÅÅÄÄÄÃÄÅÇÈÊÌÍÎÏÐÑÒÓÔÕÕÖÖ××××™—”’‹ˆ‡„}{ywq^NKJHFECB@??=<;;:99887777778888999:;<==??@ABDEEGHIKLNOPQRqrrrrrrrsssttttuvvvwwwx†•™™™–—˜˜™™››œžŸ  ¡£¤ ˜˜˜˜˜˜—˜—————–––––––––•••••••••••””””””“““““““’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘—£©¯´¹¶´±®¬©¦¤¢ ž›™—•”‘ދЉˆ‡…„ƒ‚~}|xtrpnkhfcabdfikmprtvy|~‚†‹”˜œ››š™™˜—–••”“’‘‘ŽŒŒ†sssrrrrrrqpqppooooooonnnnmnmmllkllkkjjjiiiiiihhhgggggffffeededddccccbbbbbaaaa```_`___^^^^]]]]\\ÙÙÙÙØØØ××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÓÒÒÒÑÒÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÇÆÆÆÅÅÅÅÄÄÃÃÅÆÈÊËÌÎÏÐÑÒÓÔÕÖÖ×××××ט–“‘ŽŒŠ‡…ƒ~|zxutrm\IGEDCA@?><<;:99988777777888999:;;<=>?@ABCDEFGIJKMNPQRSrrrrrrrrsssttuuuvvvwx‡•˜™™š›š—˜™™š›œœŸŸ ¡££¥¥¢˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““’“’’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘—œ¢¨®´¹·³±¯¬©§¤¢ žœš—•”‘ދЉˆ‡†„ƒ‚€~}|xuspnligdbbdfhknortwy{~‚†‹“˜œœ››šš™˜—––•”““’‘ŽŒ‹‹}sssrrrrqrqqqppppooooonnnmmmmllllkkkkjjjjiiiiiihhhgggfffeeeeeeddddcccbbbbbbaaa````____^^^^]]]]\ÙÙÙÙÙØØØ×Ø××××ÖÖÖÕÕÕÕÔÔÔÓÔÓÓÓÒÓÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÎÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÉÈÈÇÇÆÇÆÆÆÆÅÅÅÃÃÄÆÇÉÊÌÍÎÐÑÒÓÔÕÕÖÖ×××××™—•“‹‰†„~{zwutqonj]EDC@@?=<;;:99887777778889999:;<=>??@ABDEFGIJKLNOQRSqrrrrsssssssttuuvvvw‹–˜™™šš››œš™™™›œœžžŸ ¡¢£¤¥¦§¡˜˜˜˜˜˜—˜—————––––––––•••••••••••””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘–œ¢§®´¹·´±¯­ª§¥£ žœš˜–”’ŽŒ‹‰ˆ‡†…ƒ‚~}|yvtpnligdbbdfhknprtwy|~‚†‹“—œœ›šš™˜——––”““’’‘ŽŽŒŒ‹Š†tssssrrrqqqqqpppoooooonnnmmmmmmlklkkjjjjijiiiihhhhhggfffeeeeeeddddcccbbbbbbba````_____^^^^]]]]ÚÚÚÙÙÙØ×Ø×××××ÖÖÖÖÕÖÕÕÕÔÔÔÓÔÓÓÓÒÑÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÏÎÎÍÍÍÍÌÌÌËËËËÊÊÊÊÊÊÉÉÉÉÈÈÈÈÇÇÇÇÆÆÆÅÆÂÂÄÅÇÉÊËÍÎÏÐÒÒÓÕÕÖÖ××רכ˜–”‘ŒŠˆ…ƒ~|zxvurpomkih_M@?>=;;:99988777787888999:;<<=>?@ABCDEFHIKLMNPQRrrrrrrsrsssttttuuv€–˜˜˜™™š››œž›™š›œžŸ ¡¡¢£¤¦¦§¨¢˜˜˜˜˜˜————————–––––––••••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘–›¡§­³¹·´±¯­ª¨¥£¡ž›˜—”’Œ‹Š‰‡†…„‚€~|yvtroljhebbdfhkmprtwy|~†ŠŽ“˜œœ››š™˜˜—––•”“’’‘Ž‹‹ŠŠ‰|tssssrrrrqqpqqpooooooonnnmmmmmlllkkkjkjjjiiiiiihhhggggffffeeeedddcccbcbbbbaaaa````_`___^^]]]]ÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÖÕÔÕÕÔÔÓÔÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÐÏÏÏÎÎÍÍÍÌÍÌÌÌÌËËËËÊÊÊÊÊÊÉÉÈÉÈÈÇÇÇÇÆÆÆÆÃÂÃÄÆÈÊÊÌÍÏÐÑÒÓÕÕÖÖ××××Ø×š˜•“ŽŒ‰‡…‚€~{zwusqpnljigfdaVC=;;:9988877778888999:;;<=>??AACDEFGIJKLNOQRSrrrrrrrssssstuuzŠ”–——˜˜™™š››œœœžŸœ›œžŸ ¡¢£¤¥¦§§ª¬¢˜˜˜˜˜˜———————–––––––––••••••••••”””””””““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘•›¡§¬²¹¸µ²°­ª¨¥£¡Ÿ›˜—•’‘ŽŽŒ‹Š‰‡†…ƒ‚‚€~}zvtromjhecadfhkmprtwy{~†ŠŽ“—›œœ›šš™˜——–•”““’‘‘ŽŽŒŒ‹Š‰ˆ…ttsssrrrrrqqqqppppoooononnnmmmllllklkkjjjjiiiiiihhhggggfffffeeeeddddccbbbbbbaaaa````___^^^^]]ÛÚÚÚÙÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÔÔÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËÊÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÆÆÄÁÂÄÆÇÉÊÌÍÏÐÐÒÓÔÕÖÖ××Ø×ØØ›™–”‘‹ˆ…ƒ|{yvurpomljhgfdba`]RA:9988877777889999:;<<=>?@ABCEFGIJKLMOPQRrrrrrsrssstttz‰’•–––—˜˜™™™š›œœžŸŸ žŸ ¡¡££¥¦¦§¨«®± ˜˜˜˜˜˜———————–––––––––•••••••••”””””””””“““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘•› ¦¬²¸¸µ³°­ª¨¦£¢Ÿ›™—”“‘ދЉˆ†…„‚‚~}zwuromkhfdadfhkmprtwy|~…ŠŽ’—›žœœ›š™˜——––•”““’‘‘ŽŒ‹Š‰ˆˆ‡zttsssrrrrqqqqqqpooooooonnnmmmmmmllkkkkjjjiiiiiiiihhggggffeeeeedddcdccccbbbbbaaaa`````__^^^]]ÛÚÚÚÚÙÙÙÙØØØØØ××××ÖÖÖÖÖÕÕÔÔÔÔÓÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌÌËËËÊÊÊÊÊÉÉÉÉÉÈÈÇÇÇÇÆÀÁÄÅÇÈÊËÍÎÏÐÒÒÓÔÖ××××ØØØØš˜•“Œ‰‡…‚€}|ywusronmkihfecba`_^][RF888877788999:::;<=>??@ACDEFGIKKMNOQRTrrrrsrssss€Œ’”••––——˜˜™™šš››œŸŸ  ¡ŸžŸŸ ¡¢£¤¥¦¦¨ª¬¯²²œ˜˜˜˜˜˜—————————––––––•••••••••••”””””””“““““““““’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘”𠥬±·¸¶³±®«©¦£¢Ÿ›™—•“‘ŽŒŠ‰ˆ‡†„ƒ‚€~}{wuspnkifdadfijnortvy{~…‰Ž’—›žœœ›šš™——–••”““’‘‘ŽŒŒ‹‹‰‰ˆˆ‡‚tttsssrrrrrqqqqqppooooonnnnmnmmmllkllkjkjjjjiiiihhhggggggffeeeeedddccccbbbbbbaaa`````_____^^ÛÛÚÚÚÚÚÙÙÙØØ×Ø××××××ÖÖÖÕÕÕÕÔÔÔÔÓÓÒÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÍÍÌÌÌÌËËËËËÊÊÊÊÊÊÉÉÉÈÈÈÈÇÇÁÁÃÄÆÇÉËÌÎÏÐÑÒÓÔÕÖ××××ØØØ›™—”’‹ˆ†ƒ}{ywusqomljigedcba`_^]\\\[WPF878899999:;<=>>@@ABCEFGHJKLMOQQSrrrrrrs€‰‘“”•”••–––——˜™™™š››œœžžŸ  ¡¢£ Ÿ ¡¢¢¤¥¦¦§©«®±³¶±˜˜˜˜˜˜˜˜———————––––––•–•••••••••”•””””””““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘”™Ÿ¥«±·¸¶´±®¬©¦¤¢ ž›š—•”‘ŽŒŠŠˆ‡†„„‚€}zwvsqnlifebcfiknortvx{~…‰Ž’—›žœœ››š˜˜——•••”“’‘‘ŽŽŒŒ‹Š‰ˆˆ‡†…xttsssssrrrqqqqqqpppooooononmmmlmlllkkkkkjjiiiiiihhhhhgggggfeeeededddccccbbbbbaaa`````___^^^ÛÛÚÛÚÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÖÕÕÔÔÔÔÔÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÉÉÉÉÉÈÈÈÇÂÁÂÄÅÇÈÊÌÍÏÐÐÒÓÔÕÖ×××רØÙØš˜–“‘ŽŒŠ‡…ƒ~|zxvtrpnmkihgedbba_^^]\\\[[[[[YUOG>99:;<=>>?@ABCDEFHIJKMNOQRSrxˆŽ’““””””•••–––——˜™™™šš›œžž  ¡¡££¤¢ ¡¢¤¥¥¦§¨ª­¯²´·¹­˜˜˜˜˜˜˜˜———————––––––••••••••••••”””””””““““““““’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘“šŸ¤«°¶¹·´±®«ª¦¤¢ ž›š˜•”‘ŽŒ‹Šˆ‡†…„‚€~{wvsroligebdfikmortvy{~€…‰‘—›žž››š™™˜—–••”““’‘‘ŽŒŒ‹Š‰‰ˆˆ†……~utttssssrrrqqqpqqppoooooonnnmnmmllllkkkkkjjiiiiiihhhhggggffffeeeeeddcccccbbbbaaaaaa`_`____^ÜÛÛÚÛÚÚÙÙÙÙØØØØ××××××ÖÖÖÕÕÕÕÕÕÔÓÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌËËËËËÊÊÊÊÉÉÉÉÈÈÄÀÁÄÅÇÈÊËÌÎÐÐÑÓÓÕÖ×××רØÙÙ›™—”’‹ˆ†„‚}{ywusqonljigfdcba`_^]]\\\\[[[[[[[\\\[ZWUQNKIFEDDEFIKPTY]chlq“““““””””””••••––——˜˜™™™šš›œžžŸ ¡¡¢£¤¤¥¤¢¢¤¥¦¦¨©«®±³¶¸º¼¨˜˜˜˜˜˜˜————————–––––––•••••••••••”””””””””““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘“˜ž¤ª±¶¹·´±¯¬ª§¥¢¡žœš˜–”’Ž‹Šˆ‡†…„ƒ‚€~{xvtromjhecdfhkmprtwy|~€„‰’–›Ÿžžœœ›š™™˜—–•••”“’’‘ŽŒ‹ŠŠ‰ˆ‡††…„ƒututtstsrsrrrrqqqpppooooonnnnmnmmllllkkkkjjjjiiiiihhhhggggfgffeeedddccccccbbbbaba```````__^ÜÛÛÛÛÛÚÚÚÙÚÙÙØØØ×××××××ÖÖÕÖÕÕÕÔÔÔÓÓÓÓÓÓÒÑÒÑÑÑÐÐÐÐÐÐÏÏÏÎÎÎÎÎÍÍÍÍÌÌÌËËËËËÊÊÊÊÊÉÉÉÆÀÁÃÄÆÈÉÊÌÎÏÐÑÒÓÔÕÖ××ØØØÙÙÙš˜–“‘ŽŒ‰‡…ƒ~|zxvtrpomkjhgedcba`_]]]\\\[[[[\\\\\\]]^^_`aabceefhijllmopqstu””“““””””””••••—–——˜˜™™šš›œžŸ  ¡¢£¤¥¥¦§¦£¤¦¦§¨«¬¯²µ·¹»½¿ ˜˜˜˜˜˜˜———————–––––––––•••••••••••””””””“““““““’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘“˜ž£©°µ¹·´²¯­ª§¥£¡Ÿš˜–”’‹Š‰ˆ†…„ƒ‚€~{xwtrpmkhfcdfhkmprtvy|~€„‰‘–šŸžžœ›š™™™˜—–••”““’‘ŽŽŒ‹‹Š‰‰ˆ‡‡†…„ƒyuuuttsssssrrrqqqqpppopooonnnnmmmlmllllkkkjjiiiiiiihhhhghgggfffeedeeddddcccbbbbaaaa`a`_`___ÜÜÛÛÛÛÛÚÚÚÚÙÙÙÙØØØØ×××××ÖÖÖÖÕÕÔÕÔÔÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÐÐÏÏÏÎÎÎÍÍÍÍÌÌÌÌËËËÊËÊÊÊÊÉÉÉÁÀÂÄÅÇÉÊËÍÎÐÐÒÓÔÕÖ××ØØØÙØÙœ™—”’‹‰†„‚~{zwusqonljihfedba`__^]\\\\\[[[\[\\\\]^^__aabcdefhhiklmnpqrtu”””“”””””””•••––——˜˜˜™™™šš›œžŸ  ¡¢¢¤¤¥¦§§¨¨¥¦¦§©¬®²³¶¸»¼¾À¸˜˜˜˜˜˜˜˜˜˜——————––––––––••••••••••””””””“””“““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘’˜£©¯µº¸µ²°­ª¨¥£¡Ÿ›™—”’Ž‹‹‰ˆ†…„ƒ‚€~{yxuspmkhfdcfhkmortwy{~€„‰‘•šžŸžœœšš™™˜——–•”““’‘ŽŽŒŒ‹‹Š‰ˆˆ††…„„ƒ~uututtsssrrrrrqqqqppppoooooonnnmmmmlllkkkkjjjjiiiiiihhhhgggfgfeeeeeeddcccccbbbbbaaaaa``___ÜÜÜÛÛÛÛÚÚÚÚÙÚÙÙÙØØØ×××××ÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÒÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÍÌÌÌÌËËËËÊÊÊÊÊÊÄ¿ÁÃÅÆÈÉÊÌÍÐÐÑÓÔÕÕÖ×ØØØÙÙÙÙ›˜–“‘Šˆ…ƒ}zxvtrponljigedcba`_^^]\\\\\\\\\\\\\]^^__aabcdeefhijkmnopqstu”””””””•”••••––———˜˜™™šš›››žŸŸ ¡¡¢£¤¥¥¦§¨©ªª§§¨«­°²µ·º»¾¿Á˜˜˜˜˜˜————————–—––––••••••••••••”””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘’˜£©®´»¸µ²°®«¨¦£¡Ÿ›™—•“‘ŽŒ‹Šˆ‡†„ƒ‚~{zxusqnligddfhjmortwy{~€ƒˆ‘–šžŸž›››š™˜˜—––”““’‘ŽŽŒ‹ŠŠŠ‰ˆ‡‡……„ƒ‚uuuuuttssssrrrrqqqqqpppooooonnnnnmlmmllkkkkkjjjjiiiihhhhggggfffeefedddddccccbbbbaaaa`a```_ÝÜÜÜÜÛÛÛÛÛÚÚÙÙÙØØØØ×××××××ÖÖÖÕÕÕÔÔÔÔÓÔÓÓÓÓÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÍÍÍÌÌÌËÌËËËÊÊÊÊÆ¿ÁÂÄÅÇÉÊÌÍÏÐÑÒÓÔÖÖ×ØØØÙÙÙÙ™˜”“‹‰‡„‚€}{zxutqpnlkihgedcb`__^]]\\\\\\\\\\\\]]^__`abbcdefhiiklmopqrtu•”””””””•••••–––—˜˜˜˜™™š››œœžŸŸ ¡¡¢£¤¥¥¦§¨¨ª«¬¬ª©¬¯²´¶¸»½¾ÀÁÃĤ˜˜˜˜˜˜˜˜—————————––––––•–••••••••””””””””“”““““““’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘–¢©®´º¸µ³°®«¨¦£¢Ÿœ™—•“‘ŽŒŠŠˆ‡†…ƒ‚€~{zxutqomigdcfikmortvy|~€ƒˆŒ‘•™Ÿžžœ››š™˜——–••”“’‘‘ŽŽŒŒ‹‹‰‰ˆ‡††…„ƒ‚‚yuuututttsssrrrrrqqpppppooooonnnnmmmmlllklkkkkjjjiiiiihhhggggfffeeeeeddddccccbbbbaaaaa````ÝÝÜÜÜÜÛÛÛÚÛÚÚÚÙÙÙØÙØØ×××××ÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÓÒÒÒÑÑÐÑÑÐÐÐÐÏÏÏÏÏÎÎÍÎÍÍÍÌÌÌÌËËËËÊÊÊÁÀÂÃÅÇÈÊËÌÎÐÑÒÓÔÕÖרØÙÙÙÙÙÙ›™—”‘Ї†ƒ|{xvusqonljigfddba``_^^]]\\\\\\\\\\]]^__`abbcdefgiiklmnoqrtuv”•””””””••••––—–—˜˜™™™š››œœžžŸ  ¡¢¢£¥¦¦§¨¨©ª«¬¬­®®°²µ¸º»¾¿ÁÃÄĽ˜˜˜˜˜˜˜˜˜———————–––––––•––•••••••••””””””””““““““““’’’’’‘‘‘‘‘‘‘‘‘‘‘—›¢¨­´¹¸µ³°®¬©¦¤¢ žœ™—•“‘ŽŽŒŠŠˆ‡†…„ƒ€}{zxvtqomjhecfhjmoqtvy{}€ƒ‡Œ‘•™žŸŸžœ››šš™˜—––•”“’’‘‘ŽŒŒ‹‹Š‰ˆˆ‡†…„„ƒ‚‚|uuuuutttstssrrrrrqqqqppoooooonnnmmmmmlllkkkkjjjjjiiiiihhhgggggffffeeedddcccccbbbbbaaaa```ÝÝÝÝÜÜÜÛÛÛÚÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÕÕÕÔÔÔÓÔÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÐÏÏÏÎÎÎÍÍÍÍÌÌÌËËËËÊÿÁÃÄÆÇÊÊÌÎÏÐÑÓÔÕÖÖרØÙÙÙÙÚš—•“Ž‹‰‡…‚€~|zxvtrpomkjhgedbba`_^^]]\\\\\\\\\\]^^__``abcdefghijkmnoprsuv•””•””•”••••–––——˜˜˜™™™š››œžžŸ  ¡¢¢£¤¥¦¦§¨©ª«¬¬­¯²³±´·¸»½¿ÀÁÃÄÅŰ˜˜˜˜˜˜˜˜˜˜————————–––––•••••••••••”••””””””“““““““’’’’‘‘‘‘‘‘‘‘‘‘‘–›¡§­³¹¹¶´±®¬©§¤¢ žœš˜–”‘Ž‹Šˆ‡†…„ƒ‚€}{zywuromjhfdfhjmortvy{~€ƒˆŒ•™žŸžžžœ››š™˜——–••““’‘‘ŽŽŒ‹‹‹‰‰ˆ‡††…„ƒƒ‚€~uuuuutttttssrrrrrqqqqppoooooooonnnmmmlllllkkkkjijjiiiiihhhgggggfffeeeeddcdccccbbbbabaaa``ÝÝÝÝÜÜÜÜÜÛÛÚÚÚÚÙÚÙØÙØØØ×××××ÖÖÖÖÕÖÕÕÔÔÔÔÔÓÓÒÓÒÒÒÑÑÐÑÐÐÐÐÐÏÏÏÎÎÎÎÍÍÍÍÌÌÌÌÌËËÈ¿ÀÃÄÆÇÉÊÌÎÎÐÑÒÔÔÖÖרÙÙÙÚÚÚÚ›™–”‘Šˆ†„}{yvusqonlkigfedcb``__^]]]]\\\\\\]]]^^__`abccdeghijklmopqstuv••••”••••••–––——˜˜™™™šš›œœžŸŸ  ¢¢£¤¥¦¦§¨©ªª¬¬­®°³¶¸¸¸º¼¾ÀÁÃÄÄÅÆÆ–˜˜˜˜˜˜˜˜˜˜————————–––––––••••••••••””””””””““““““’“’’’’’‘‘‘‘‘‘‘–›¡§¬²¸¹¶´±¯¬ª§¤¢ Ÿœš˜–”’ŽŒ‹Š‰ˆ†…„ƒ‚~{{yxurpnkhfdfijmortwy{}€ƒˆ‹”™ Ÿžžœ››š™™˜——–•”“’’‘ŽŒ‹‹Š‰‰ˆ‡‡†…„ƒƒ‚€€wuuuuuutttssssrrrrqqqqppppooooonnnnmmmmllllkkkjjjjjiiiiihhhgggggffefeeeeddccccbcbbbbbaa`aÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙØØØ×Ø××××ÖÖÖÖÕÕÕÕÕÔÔÔÔÓÓÓÓÒÒÑÑÑÑÑÐÐÐÐÐÐÏÏÎÎÎÎÎÍÍÍÌÌÌÌÌËÁÀÁÄÅÆÈÊËÌÏÐÑÒÓÕÖÖרØÙÙÚÚÙÚš˜–“ŽŒ‰‡…‚€~|{xvurqomljigfdcba``_^^]]\\\\\\\]]]]^__`abccdefgiijlmooqrsuv•••••••••––––———˜˜™™™šš›œœžŸŸ  ¡£££¥¦¦§§¨©««¬­®°²´·¹¼½»½¿ÁÂÄÄÅÅÆÆŒ˜˜˜˜˜˜˜˜˜˜———————–—––––––•–•••••••••””””””””““““““““’’’’’‘‘‘‘‘•› ¦¬²¸¹·µ²¯¬ª¨¥£¡Ÿš™–”’Ž‹ŠŠˆ‡…„ƒ‚€~|{zxuspnlifdfikmoqtvy{~€ƒ‡Œ”™ ŸŸžœ›š™™˜——–•””“’‘‘ŽŒŒ‹‰‰ˆˆ‡††„„ƒ‚‚€yvvuuuuttttstsssrrrrqqqqppppooooonnnmmmlllllkkkjjjjiiiiihhhhghhggfffeeeedddccccccbbbbaaaaÞÞÝÝÝÝÝÜÜÜÛÜÛÛÚÚÚÚÚÙÙØØØØØ××××Ö×ÖÖÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÐÏÎÎÎÎÎÍÍÍÍÌÌËÅ¿ÁÃÄÆÈÉËÌÍÏÐÒÒÔÕÖ×רÙÙÚÚÚÚÚ›™—”’‹ˆ†„‚€}{ywusqpnmkihgfdbba``_^^]]\\\]\\]]]]^^_`abbcdefghijlmnoprstuw–••••••••–––————˜™™™™šš›œžŸŸ  ¡¡£¤¤¥¦¦§¨©ª«¬¬­®±³¶¸»¾¾ÁÀÀÁÃÄÅÅÆÆˆ†˜˜˜˜˜˜˜˜˜˜˜——————–—––––––•••••••••••”””””””““““““““’’’’’’‘‘‘”𠥫²¸¹·µ²°­ª¨¥£¡Ÿ›™—”’Ž‹‹Šˆ‡†„ƒ‚~|{{xvsqnligeeikmoqtvy{}€‚‡‹”™œ  ŸŸžœ››š™˜——–•””“’’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„„ƒ€~zvvvuuuuttttssssrrrqrqqqppppooooonnnmnmmllllkkkkkjjjjiiiihihhhggfgfffeeeedddccccccbbbbbaaÞÞÞÝÝÝÝÜÜÜÜÛÛÛÚÚÚÙÙÙÙØÙØØØ×××××ÖÖÖÖÕÕÕÕÔÔÔÓÓÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÏÏÎÎÎÎÎÍÍÍÍÌ¿ÀÂÄÅÇÉÊÌÍÏÐÑÒÓÔÖ×רØÙÚÚÚÚÚš˜–“‘ŽŒŠ‡…ƒ~|zxvusqomljigfdccba``_^]]\\\\]]]]]]^__`abbcddeghijklnopqssuv••••–••••––—–——˜˜˜™™šš››œœžžŸ  ¡¡¢£¤¥¦¦§¨©ª«¬­­®°²µ·º¼¾ÁÂÄÅÂÄÄÅÆÆÆ‡……˜˜˜˜˜˜˜˜˜˜˜—˜——————––––––––••••••••••””””””””““““““’’’’’’’‘”™Ÿ¥«±·º¸µ²°­«¨¦¤¡Ÿ›™—•“ŽŽŒ‹Šˆ‡†…„}|{yvtqoljgefhkmorsvy{}€‚†‹”˜œ¡  Ÿžœ›š™˜———••”““’‘‘ŽŒ‹Š‰‰ˆˆ‡†…„„ƒ‚€~}{wvvvuuuuuutttssssrrqrqqqqppopoooonnnnnnmlllklkkkkjjjiiiiiiihhgggggfffeeeeeddddcccbbbbbaaÞÞÞÝÝÝÝÜÝÜÜÛÛÛÛÚÚÚÙÚÙÙÙÙØØØ×××××××ÖÖÕÖÕÕÕÔÔÓÓÔÓÓÒÒÒÒÑÑÑÑÐÐÐÐÐÐÏÏÎÎÎÎÍÍÍÍÍÄÀÂÃÄÆÈÉËÌÎÐÐÒÓÔÕÖרØÙÚÚÚÛÛÚœ™—•’‹ˆ†„‚€}|ywvtqpnmliigedcbaa`_^^]]]\\\]]]]]^^__`abbcdeggiiklmnoprsuvw–•––••–––––—–—˜˜˜˜™™š›››žŸ  ¡¡¢£¤¥¦§§¨¨ªª«¬­®¯²³¶¹»¾¿ÂÄÅÇÈÇÅÆÆÆ‰†ƒ‹˜˜˜˜˜˜˜˜˜˜˜—˜———————––––––••–••••••••””””””””“““““’’’’’’’”™ž¤ª±·»¸µ³±®«¨¦£¢ œš—–“‘ŽŒ‹Š‰‡†…ƒ€}|{ywtqpmjhffhjmoqtwy{}€‚†‹”˜œ¡  Ÿžœœœšš™˜——•••”“’’ŽŽŒ‹‹Š‰ˆˆ‡†……„ƒ‚‚€~~}|vvvvvuuuuuutttsssrrrrqqqqqppppoooonnnnmmmmlllkkkkjjjiiiiiihihhggggggfeeeeeddddccccbbbbbaÞÞÞÞÞÝÝÝÜÝÜÜÜÛÛÛÚÚÚÚÚÚÙÙÙØØØ×××××ÖÖÖÖÕÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÑÑÑÐÐÐÐÐÐÏÏÏÏÎÎÎÎÎÍÊ¿ÀÂÄÆÇÉÊÌÎÏÐÑÓÔÕÖ×רÙÚÚÛÛÛÚ›˜–“‘Ї†ƒ~}{ywusqonljihgedbbaa__^]]]]\\\]^]^^^__`abbcdefghijlmnopqstuw–––––––––––———˜˜˜˜™ššš››œžŸ  ¡¡¢¢¤¤¥¦§¨¨©««¬­­®°³µ¸º¼¾ÁÃÄÆÈÉÊÉÈÆÆ‡…‚€}•˜˜˜˜˜˜˜˜˜˜˜˜——————–––––––––••••••••••”””””””””“““’’’’’’“™ž¤ª°¶»¸¶³±®¬©¦¤¢ žœš˜–”‘ŽŒ‹Š‰‡‡…ƒ‚€~|{yxurpmkhefhjmortwy{}€‚†Š“˜œ¡¡ Ÿžžœ›šš™™——––•”“’’‘ŒŒ‹Š‰‰ˆ‡††……ƒƒ‚€€~}}xvvvvvuuuuuutttstsssrrrqqqqpppooooooonnnmmmmllllkkkjkjjjiiiiiihhhgggfgffeeeedddddcccbbbbbßßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÙÙÚÙØØØØØ××××××ÖÖÕÕÕÕÔÔÔÓÓÓÓÓÒÒÒÑÑÑÑÑÐÐÐÐÐÏÏÏÏÎÎÎÎÍÃÀÂÃÅÇÉÊËÍÎÐÑÒÔÕÖÖרØÙÚÛÛÛÛÛœš—•’‹‰‡„‚€}|zxvtrpomkjigeecbba``_^]]]]]]]^^^^___`abbbdefghijklnooqrtuvx–––––––––––—˜˜˜˜™™™šš››œœžŸŸ  ¡¢¢¤¥¥¦¦§¨©ª«¬¬®¯¯²´·¹¼¾ÀÂÄÅÇÈÊÊËËʉ†ƒ~{†˜˜˜˜˜˜˜˜˜˜˜˜˜———————–––––––––••••••••••””””””””““’’’’’’˜ž¤©¯µ»¸¶³±®¬©§¥¢ žœš˜–”’ŽŒ‹‹‰‡†…ƒ‚€~}|zxurpnkiffhjmoqtvy{}†Š“˜œ ¡¡Ÿžžœ›šš™˜—––•”““’‘‘ŽŒ‹Š‰‰ˆˆ‡†……„ƒ‚‚€~~}xtuwwvvuuvuuuuutttsssrrrqrrqqqppppoooononnnmmmlllklkkkkjiiiiiiihhhhgggggffeeeeeddcdcccbbbbßßßÞÞÝÝÝÝÝÝÝÜÜÛÛÛÛÛÚÚÚÙÚÙÙØÙØØ×××××ÖÖÖÖÖÕÕÕÕÔÔÓÔÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÏÐÏÏÎÎÎÊ¿ÁÂÄÆÈÊËÌÎÏÑÑÓÔÕÖרØÙÚÚÛÛÛÛž›™–“‘Šˆ†ƒ}{ywusqonljihfedcbaa___^^^]]]]]^]^___``abbcdefhiijlmnoqrtuuw–––––––——————˜˜˜™™™šš››œœžŸŸ  ¡¢££¥¥¦¦§¨©ª«¬¬­®¯±³¶¸»½¿ÁÃÅÇÈÉÊËËÌŒ‰‚€}{x“˜˜˜˜˜˜˜˜˜˜˜˜˜——————––––––––––••••••••••””””””””“’’’’’˜£©¯µ»¹¶³±®¬©§¥£¡žœš˜–”’Ž‹‹‰‰†…ƒ‚}|zxusqnligfhjmortvy{~€‚†ŠŽ“—œŸ¡¡ Ÿžœ›šš™™——––•”’’’‘ŽŽŒ‹‹Š‰‰ˆ‡‡†…„ƒƒ‚€€~}ytoqwwwvvvvuuuuutttssssssrrqqqqpppppoooononnmmmlllllkkkjjjjjiiiiihhhhghgggfeeeedeedcccccbbbßßÞÞÞÞÞÝÝÝÝÝÜÜÜÜÛÜÛÛÚÚÚÚÙÙÙÙØØ×Ø×××××ÖÖÖÖÕÕÕÕÔÔÔÔÔÓÓÒÒÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÎÎÄÁÂÄÅÇÉÊËÍÏÐÑÓÔÕÖרÙÙÚÚÛÛÛÛÛš˜”“‘ŽŒ‰‡…‚€~|zxvtsponkjigffdcbb`___^^]]]]]]^^___`aabbcdefghijlmnopqrtuwx––—–—––——–——˜˜™˜™™™š››œœœžŸŸ  ¡¡¢£¤¥¦¦§¨©©ª¬¬­®®°²´·¹¼¿ÀÃÄÅÈÉÊËËÌËŽ‹ˆ…‚|yw‚˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————––––––––••••••••••””””””””“’’’’—¢¨®´»º·´±®¬ª§¥£¡Ÿ›˜–”’‘ŽŒ‹Šˆ††„‚€~|{yvtqnljgfhjmortwy{}‚†ŠŽ“—› ¡¡ ŸŸžœ››š™™˜——–•”““’‘ŽŒ‹Š‰‰ˆ‡‡††„ƒƒ‚€~zupknwwwwvvvvuuuuututssssrrsrqrqqpqpppoooonnnnmmmmlllllkkjkjjjiiiiihhhhhggggffffeeeedddccccbßßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙØØØØ××××××ÖÖÖÕÕÕÔÔÔÓÔÓÓÓÒÒÒÒÑÑÑÐÐÐÐÐÐÐÏÏÏË¿ÂÄÄÆÈÊËÍÎÐÑÒÔÕÖ×רÙÙÚÛÛÛÜÜž›™–”’‹ˆ…„}{ywutrpolkjigedcbba`__^^^]]]]]^^^___`abbcdefghijklmopqrtuvx———–—————————˜˜™™™™š››œžžŸŸ ¡¡¢££¥¥¦§§¨©ª«¬¬®¯¯±´¶¸»½ÀÁÄÅÇÉÊÊËËÌŠ‡…‚{vs’˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————–––––––••••••••••”””””””“’’’—¢¨®´ºº·µ²¯­ª§¥£¡Ÿ›™—•“‘ŽŒ‹‰ˆ‡…„ƒ€~|{yvtromjgfhkmortvx{}‚…ŠŽ“—›Ÿ¢¡ Ÿžžžœœ›šš™˜——–•”““’‘ŽŒ‹‹Š‰ˆ‡‡††…„ƒ‚‚€~{uqkgixwwwwwvvvuuuuutttstssssrrqqqqpqppooooonnnnnmmmlmllkkkkjjjiiiiiihhhhgggggfffeeeeddddccccàßßßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÚÚÚÙÙÙÙÙØØ×Ø××××ÖÖÖÖÖÕÕÕÔÔÓÔÓÓÓÓÒÒÒÑÑÑÑÐÐÐÐÐÏÏÏÄÁÃÄÆÇÊÊÌÎÏÐÒÓÕÕ×רÙÙÚÛÛÜÜÜÜœš˜•“Ž‰‡…ƒ|zxvusqomlkihgedcbba`__^^^^^^^^^___``aabccdffghiklmnoqrsuvwy———————————˜˜™™™™™š›››œœŸŸŸ  ¡¢£¤¤¥¦¦§¨©ª«¬¬­®¯°³µ¸º½¾ÁÃÅÇÈÉÊËÌÌÌŽ‹‰†ƒ{yvƒ˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜———————–—–––––––•••••••••••””””’’’–œ¡§®´ºº·µ²°­ª¨¦£¡Ÿ›™—•“‘ŽŽŒ‹Šˆ‡†„ƒ‚€~|{zwtromjhfhjmoqtvx{}€‚…‰Ž’–šŸ¢¡ ŸŸžžœ››š™˜˜—–•”““’’‘Ž‹‹‹Š‰ˆ‡‡……„ƒƒ‚€~|vrlgbdwwwwwvvvvvuuuuuttttssssrrrrqqqqppppoooooonnmmmmmlllllkjkjjjiiiiihhhhggggfffeeeeeeddccccàààßßßÞßÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÙÙÙÚÙØØØØ×××××××ÖÖÖÖÕÕÔÔÔÔÔÓÓÓÒÓÒÑÑÑÑÑÐÐÐÐÐÐÎÀÂÃÅÇÈÊËÎÏÐÑÓÔÕÖרÙÚÚÚÛÛÜÜÜž›™–”’‹ˆ†„€~{zwutrpomkjihfedcbaa`__^^^^]^^^^__``aabccdefghijkmnoprrtuwx———–———————˜˜˜™™™™šš››œžŸŸ  ¡¢¢£¤¥¦¦§¨©ªª«¬­­¯°±´·¹¼¾ÀÂÄÆÇÉÊÊÌÌÌŠ‡…‚€}{xusžž˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜——————–––––––•–•••••••••”•”””“’–›¡§­³¹»¸µ³°­«©¦¤¢ œ™˜•“’‘ŽŒ‹Š‰‡†…ƒ‚€}|zxuspmkifhjmortvy{}€‚…‰’—›Ÿ¢¢¡ ŸŸžœœ››™™˜——–”””“’‘Œ‹ŠŠ‰ˆˆ‡†…„ƒƒ‚€|wrmhc^cxxxxwwwvvvvuuuuuutttstssrrrrqqqqqppooooonnnnmmmmmllllkkjjjjjiiiiihhhhgggggfffefeddddcccàààààßÞÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÛÚÙÙÙÙØØØØØ×××××ÖÖÖÖÕÕÕÔÔÔÔÓÓÓÓÒÒÒÒÒÑÑÑÐÐÐÐÐÆÁÃÄÆÈÉËÌÎÐÑÒÓÕÖ×רÙÚÛÛÛÜÜÜÜš˜•“‘ŽŒŠ‡…ƒ|{yvusqonlkihgfdcbba``___^^^^^^^___`aabbcdefghijklnooqrtuvwy———————˜˜˜˜˜˜™™™™ššš›œœžžŸ  ¡¢££¤¥¥¦¦¨¨ªª«¬­­®¯±³µ¸»½¿ÁÃÅÇÈÊËËÌÌÌŽŒ‰‡ƒ~|ywuq‰££ œ˜˜˜˜˜˜˜˜˜˜˜˜————————–––––––••••••••••••””’–›¡¦¬³¸»¹¶³±®«©¦¤¢ œš˜•”’‘ދЉ‡†…„‚€}|zxuspnlighjmoqtvx{}€‚…‰Ž’–šŸ£¢¡  Ÿžžœ››™™˜˜—–•””“’‘‘ŽŒŒ‹Š‰‰‡‡†…„„ƒƒ‚€€}xsmic_^axxxxwwwwvvvvvuuuuttttssssrrrrrqqqppopoooooonnnnmmmmllklkjkjijiiiiiihhhhggggffefeeeedddcáàààààßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÛÛÛÚÚÚÚÙÚÙÙÙØØØ×××××ÖÖÖÖÕÕÕÔÔÔÔÓÔÓÓÓÒÒÑÑÑÑÑÐÐÐÐÂÃÄÅÇÉËÌÍÏÐÑÓÔÖ×רÙÚÛÛÛÛÜÜÜž›™—”“‹‰†„‚€~{zxutqqomljihfedcbba``_^_^^^^^^__``aabbcdeeggiiklmnoprstuwx—˜—˜———˜˜˜˜˜˜˜™™™šš››œžžŸ  ¡¡¢£¤¤¥¦¦¨¨ªª«¬¬­®¯°²µ·º¼¾ÁÃÄÆÇÉÊËÌÌÌ‹‡…ƒ€}{xvsqt¤£££¢Ÿš˜˜˜˜˜˜˜˜˜˜———————––––––––•••••••••••””•› ¦¬²¸»¸¶³±®«©§¥£ žœš˜–”’‘ŽŒ‹‰ˆ‡…„ƒ‚€}}{xvsqnljghjmoqtvx{}‚„‰‘–šž£¢¡¡ ŸŸžœœ›š™˜˜——•””“’’‘‘ŽŒŒ‹‹‰ˆˆ‡††…„ƒƒ‚€€~ysoje`^^_xxxxxwwwvwvvuvuuuutttttsssrrrrqqqqppppooooonnnmnmmmlllkkkjjjjjiiiiihhhhggggfffffeeedddcáááàààßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙÙØØ×Ø×××××ÖÖÖÖÕÖÕÕÔÔÔÔÓÓÒÓÒÒÒÒÑÑÐÐÐÉÂÄÅÆÈÊËÍÏÐÑÓÔÕ×רÙÚÛÛÛÜÜÜÝÝš™–“‘ŒŠ‡…ƒ|{xwusqpnmkihgfedbbaa``_^^^^_^___```abbccdefghijllnopqstuvx—˜——˜˜—˜˜˜˜˜˜™™™ššš›››žžŸ   ¡¢£¤¤¥¦¦§¨©ª«¬¬­®¯¯±³¶¹»¾¿ÂÃÅÇÉÊËËÍÍÍŽŒ‰‡„|zvurpnŽ£¤££¢¢¡Ÿ›˜˜˜˜˜˜˜˜˜———————–––––––•–•••••••••”𠦬±¸¼¸¶´²®¬©§¤£ Ÿš˜—”’‘ŽŒ‹Šˆ‡†„ƒ‚€~}{yvtroljhhkmoqtvx{}€‚…‰‘–šž££¡¡  Ÿžœœ››š™˜˜—–•””“’‘ŽŽŒ‹‹Š‰ˆˆ‡†……„ƒ‚‚€~ytoje`____yxxxxwwwwwvvvvuuuuuttttsssrsrrqqrqpqpppooooonnnnmmlllllkkkkjjjjjiiiihhhggggfffffeeeeddcâááààààßßßÞÞÞÞÞÝÝÝÝÝÜÜÜÛÛÛÚÛÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÕÔÔÔÔÔÔÓÓÒÒÒÑÑÑÑÐÄÃÄÆÈÉËÌÎÏÐÒÓÕÖ×רÙÛÛÜÜÜÜÝÝŸœ™—•“Ž‹‰‡…‚€}|zxvurqonljihgfeccba````___^_^___`aabbbcdefghijklnopqrtuvwx———˜˜—˜˜˜˜˜™™™™ššš››œœžžŸŸ  ¡¢¢¤¤¥¥¦§¨©©««¬­®®°°²µ·º½¿ÂÃÅÇÈÊÊÌÌÍÍ‹ˆ…ƒ€}{xvtqomx¤¤££¢¢¢¢¢¡ ™˜˜˜˜˜˜——————––––––––•••••••••••Ÿ¥«±·¼¹¶´²®¬ª§¥£¡Ÿ›š—•“’‘ŽŒ‹Š‰ˆ†…„‚€}|yvurpmjhhkmoqtvx{}€‚„ˆŒ‘•šž¢£¢¡  Ÿžžœ››š™˜˜—–••”“’‘‘Œ‹Š‰ˆˆ‡†……„ƒ‚‚€€~zupkfa_____zyyyxxxwwwwvvvvuuuuuttttssssrrrrqqqpppppooooonnnnmmmmlllkkkkjjjjjiiiihhhhggggfffffeededâáááàààààßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÖÕÕÔÔÔÔÓÔÓÓÒÒÒÒÒÑÑÎÂÄÅÇÉÊÌÍÏÐÒÔÔÕ×רÙÚÛÛÜÜÜÜÝ ›˜–”‘‹ˆ†ƒ}{ywusrpomliihfedcbba```_________`aabbbcdefghiikkmnoqrsuuwx˜˜˜˜˜˜˜˜˜˜˜™™™™™šš››œœžž   ¡¡¢£¤¥¥¦§¨¨©ª«¬¬®¯¯°²³·¸¼¾ÀÂÄÆÇÉÊËÌÍÍÍŒ‰‡…|zwurpnli˜¤££££££¢¡¢¡¡¡Ÿœš˜˜———————–––––––•––••••••••¤«°¶½º·´²¯¬ª§¦¤¢ žœš˜–“’ދЉˆ‡…„ƒ‚€}|ywurpnkhhjloqtvx{}‚„ˆŒ‘•™ž££¢¡¡  žžžœœ›š™˜˜—–••””“’‘ŽŽŒ‹ŠŠ‰ˆ‡††……„ƒ‚‚€€{vqlga______yyyyxxxxwxwwwvvvuuuuutttttssssrrrqqqqqppooooonnnnnmmmlmllklkkkjjjjiiiihhhhgggggfffeeeeeâááááààààßßßßÞÞÝÞÝÝÝÝÝÜÜÜÛÛÛÛÛÛÚÚÚÙÙÙØØØ××××××ÖÖÖÕÕÕÕÕÕÔÔÓÓÓÓÒÒÒÒÒÑÈÃÅÆÈÊËÍÎÐÑÒÔÕÖ×ÙÙÚÛÛÜÜÝÝÝÝŸœš—•“ŽŒ‰‡„‚€~|{xvtsqonljihgfedcbaa```________``aabbccdegghijlmnopqstuwxy˜˜˜˜˜˜™˜˜˜™™™™™šš››œœœžžŸŸ  ¡¢¢¤¤¥¦¦§¨©ª«¬¬­¯¯°°³¶¸»½¿ÁÄÅÇÉÊËÌÌÍÍŽ‹ˆ†ƒ€~{yvtqomki¤£¤££¢¢¢¢¢¡¡¡¡¡  Ÿžœš˜——————–––––••–•••••••ª°¶»º¸µ²°®«©§¥£ žœš˜–““‘ŽŒ‹‰ˆ‡…„ƒ‚~|zwuspnkihjmortvxz}‚…‡Œ‘•™ž¢£¢¢¡¡ Ÿžžœ›šš™˜——–••““’‘‘ŽŽŒ‹ŠŠ‰‰ˆ††……„ƒ‚‚€€|wrlhb_______zyyyyyxxxxxwwvvvvuuuuuuttttssssrrqqqqqpppppoooonnnmmmmmlllkkkkkjjjiiiiiihhhghgggffeeeeeâáâááááààßàßÞßÞÞÝÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÙÙÙÙÙØØ×Ø××××ÖÖÖÖÕÕÕÕÔÔÔÓÓÓÓÓÒÒÒÒÄÄÆÈÊÊÌÎÏÑÒÔÕÖרÙÚÚÛÜÜÝÝÝÝ ›™–”‘‹ˆ†„~{yxutrponljihgfdccbba```_______```bbbccdefghiiklmnoprtuvwy˜˜˜—˜˜˜˜˜™™™™™™šš›››œœžžŸŸ ¡¡¢££¤¥¦¦§¨©ªª¬¬¬®¯¯±²´·¹¼¾ÀÃÄÇÈÉËÌÌÍÍÎŒŠ‡…}zwurpnlihk¤¤¤¤£££¢¢¢¢¡¡¡¡     Ÿ žžžœœ›š™˜——––•–––––––¯¶½¼¹¸´²¯­©¨¥£¡Ÿ›™–”“’ŽŒ‹Šˆ‡†„ƒ‚€~}zxvsqnkihjmnqtvx{}‚„ˆŒ‘”™ž¢¤£¢¢¡ Ÿžžœ››š™™——––•”“’’‘‘ŽŽŒŠŠŠˆˆ‡††…„ƒƒ‚€|xrmhc_______`zzyyyyyxxxxwwwvwvvvuuuutttttsssrrrqrrqppppoooooonnnmnmmmmlllkkkjjjjjiiiiihhhhgggfffeefeââââááàáààßàßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÜÛÛÚÚÚÚÚÙÙØÙØØ×××××××ÖÖÖÖÖÕÕÕÔÔÔÓÔÓÒÓÒÌÄÅÇÈÊËÍÏÐÑÓÔÖ×רÚÚÛÜÜÝÝÝÝÝ œš˜•“‘ŽŒ‰‡…ƒ~}{yvusqonmkiihgedcbbaa````____```aabbbcdefghiiklmnoqrsuuwyz˜˜˜˜˜˜™™™™™™™™ššš›œœœžŸŸŸ  ¡¢££¤¥¦¦§§©©ª«¬¬­¯°°±³¶¸»¾ÀÁÄÅÈÉÊËÌÍÍ΋ˆ†ƒ~{ywtromkigd¤¤¤¤££££¢¢¢¢¡¡        ŸŸžŸžžœœœœ››››šš´½½º·µ²°­ª¨¦¤¡Ÿ›™—”“’‘ދЉˆ†…„‚€~{yvsqoljhjmoqtvx{}€‚„‡‹•™¢¤£¢¢¡  Ÿžœœ›šš™˜——–•””“’‘ŽŽŒ‹ŠŠ‰ˆ‡‡††„„ƒƒ‚€}xsnid`_______azzzzyyxyxxwxwwwwvvvuuuuuutttstsssrrqqqqpqpopooooonnnnmmmllllllkkjjjjjiiiihhhhhggfgfffefââââááááààààßßßßÞÞÞÞÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÙÚÙÙÙØØØØØ×××××ÖÖÖÕÕÕÕÔÔÔÔÓÓÓÓÓÈÅÆÈÊËÍÎÐÑÒÔÕÖרÙÚÛÛÜÝÝÝÝÝ ž›™—”’‹ˆ†„‚€~|zwutsponljihgeddcbbba```````_``aabbbcdeeghhijkmnopqrtuvxy™™˜˜˜˜˜™™™™™™™ššš››œœžŸŸ  ¡¢¢£¤¥¦¦¦§©©ª«¬¬­®¯°±²µ¸º¼¿ÁÃÅÇÉÊËÌÍÍÎΊ‡…‚€}zxusqoljhedz¤¤¤¤¤¤££££¢¢¢¡¡ ¡    ŸŸŸŸŸžžžžœœœ››š›š·½º¸µ³°­ª¨¦¤¢ žœ™—•“’‘Œ‹Š‰ˆ‡…„ƒ~|ywtromjhjmnqtvx{}‚„‡Œ”™¡¤££¢¡  Ÿžžœ›š™™˜——–•””“’’‘ŽŽŒ‹‹Š‰ˆˆ‡††…„„ƒ‚€~ytoie`_`______d{zzzyyyyxyxxxxwwvwvvuuuuuutttstssrrrqrqqppppooooooonnnnmmmlllllkkjjjjjiiiihhhhgggggfffeãâââââááàààààßßßÞßÞÞÞÝÝÝÝÜÝÜÜÜÜÛÛÚÚÚÚÚÚÙÙÙØØØØ×××××ÖÖÖÖÕÕÔÕÕÔÔÓÓÓÒÄÆÇÉËÌÍÏÑÒÓÕÖרÙÚÛÛÜÝÝÝÝÝÝŸœš˜•“‘ŽŒŠ‡…ƒ}{ywutqonmkjigfedccbbaaa````_```aaabcccdefghijklmooqrtuvwyz˜™˜˜™˜™™™™™™™ššš››œœžŸŸ   ¢¢£¤¥¥¦¦§¨©ª«¬¬­®®¯±²´¶¹»¾ÀÃÄÆÈÊÊÌÍÍÎÎ‘Ž‹‰†ƒ~|ywtronkigecd¥¥¤¤¤¤£££££¢¢¢¡¡¡      ŸŸŸŸžŸžžœœœœ›››š¶»¸¶´±®«¨§¤¢ žœš˜•“’‘Œ‹‰ˆ‡…„„‚€~|ywtrpmkhjloqtvxz}€‚„‡‹”˜¡¥¤££¢¡ ŸŸœ››š™™———–•”““’‘ŽŽŽŒŒ‹ŠŠ‰ˆ‡‡†…„„ƒƒ€~ztoje`````_____e{{zzzyyyyyyxwxwwwvvvvuuuuuttttttsssrrqqqqqqpppoooooonnmnnmllllkkkkkkjjiiiiiihhhhggggfffããââââááááàààßàßßßßÞÞÞÝÝÝÝÝÜÜÜÛÛÛÛÚÛÚÚÙÙÙÙÙØØØ××××××ÖÖÖÕÕÕÕÕÔÔÔÓÓÎÅÆÈÊËÍÏÐÑÓÔÖ××ÙÚÚÛÛÝÝÝÝÝÝ¡žœ™—”’‹‰†„‚€~{zxvurqonmkiigfedccbba`````````aabbbccdefghhjklmnopqstuwxy™™™™™™™™™™™™šššš››œœžžŸ   ¡¢££¤¥¦¦§¨©ªª«¬­®®°±²²µ¸º½¿ÂÄÅÇÉÊËÍÎÎÎΊˆ…‚€~{xvsqomjifeba¥¤¤¤¤¤¤£¢£¢¢¢¢¡¡¡¡     ŸŸŸŸžžžžžœœœœ›››±¹¶´±®¬©§¥£¡žš˜–”’‘Œ‹Šˆˆ†…„‚€~}zwurpnkijmoqsvx{}‚„†‹”˜¡¥¤¤£¢¡  Ÿžžœ›››™™˜——–•””“’’‘ŽŒ‹‹Š‰ˆˆ‡††…„ƒƒ‚€zupkfa``````____h{{{zzzzyyyxxxxwxwwwvvvvuuuuuuttsssssrrqrqqqqpppooooonnnnmmmmllllkkkkkjjiiiiiihhhhgggfgfããããâââáááááààààßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÙÙÙØØØ×××××××ÖÖÖÕÕÕÕÕÔÔÔÓÊÆÈÉËÌÎÐÑÒÔÕ×רÚÚÛÜÜÝÝÝÝÞÞŸš™–“‘ŽŠˆ…ƒ}{ywutrpomljihffedcbbaa```````a`aabbccdeefhhijklnoorstuvxyz™™™™™™™™™ššššš›››œžžŸ  ¡¡¢££¤¤¦¦§§¨ªª«¬¬­®¯°±²´¶¹¼¾ÀÂÅÆÉÊËÌÍÎÎÎ‘ŽŒ‰†„|yvusonligedb`y¥¥¥¥¤¤£¤£££¢¢¢¡¡¡¡¡     ŸŸŸžŸžžžœœœœœ›¬·´±¯¬©§¦£¡Ÿš™—”“’ŽŒŒŠ‰ˆ‡…„‚€~}zxuspolijmoqsvx{}€„‡‹“˜œ¡¥¤¤£¢¡  Ÿžžœ››š™˜˜—–••”““’‘ŽŒŒ‹Š‰‰ˆ‡†……„ƒƒ‚€{vqlfb``````__`_`l{{{{zzzzyyyyyxxxwwwwvvvuuuuututtstsssrrrrqqqqpppooooonnnnnmmmllkllkjjjjjjiiiiihhhghgggfãããããâââáááááààßàßßßÞÞÞÞÝÝÝÝÝÜÝÜÜÛÛÛÛÚÚÚÙÚÙØØØØ×Ø××××ÖÖÖÖÖÕÕÕÕÕÔÔÆÇÉÊÌÍÏÐÒÓÕÖרÙÚÛÛÜÝÝÝÞÞÞ¡Ÿœ™—”’Ž‹‰‡…‚€~|zxvusqonlkjigffddcbbaaa````aaaabbbccdefghhijklmooqrsuuwxz™™™™™™™™™™™ššš››››œžŸ   ¡¡££¤¤¦¦¦¨¨©ª«¬¬­®¯°±²³¶¸»¾¿ÂÄÆÈÉÊËÌÎÎÏÏŠ‡…‚€~{yvtqomjigeba_c¥¥¥¤¤¥¤¤¤£££¢¢¢¡¡¡ ¡¡    ŸŸŸžžžžžžœœœ›¨µ²¯­ª¨¦¤¢Ÿš™—•““‘ŽŒŠ‰ˆ‡…„ƒ}{yvsqoljjloqsvxz}‚„†Š“˜œ¡¥¥¤¤¢¢¡ ŸŸžœœ›š™™˜—–••”““’‘‘Œ‹ŠŠ‰ˆˆ‡†…„„ƒ‚€{vqlgb`a````````__p{{{{{zzzzyyyxyyxwwwwwvvvuuuuututttssssrrrqrqqqpppooooonnnnnmmmmllkklkjjjjjiiiihhhhhhgggãããããââââááááàààßàßßÞßÞÞÝÞÝÝÝÝÝÜÜÛÛÛÛÛÛÚÚÚÙÙÙØØØØØ×××××ÖÖÖÖÕÕÕÕÔÐÆÈÊËÍÏÐÑÓÔÖרØÚÛÜÜÝÝÞÞÞÞÞ ›˜–“‘ŒŠˆ†„}{zwvtrqonljihgfeddcbbaa````aaaaabbbcdeefghiiklmnoqrstuwxz{™™™™™™™™™šššš›››œœžžŸŸ  ¡¡¢£¤¥¥¦§¨¨©©«¬¬­®®°°²²µ·¹¼¾ÁÃÅÇÉÊËÌÎÎÏÏ‘Œ‰‡ƒ|zwuspnljhfdb`_\”¦¥¥¥¤¤¤££££££¢¡¢¡¡¡¡      ŸŸŸŸžžžœœœ£²°­«©¦¤¢ ž›š—•”“‘Œ‹‰ˆ‡†„ƒ‚~{ywtromjjloqsvx{}‚„‡Š“˜œ ¥¥¥¤£¢¡  Ÿžœ››š™˜——–•””“’‘‘ŽŒ‹‹Š‰‰ˆ‡‡†„„ƒƒ|wrmhcaaa````````__t|{{{{{zzzyyyyyxxxwxwwwwvvuuuuuuuttsssssrrrqrqqppppooooonnnnmmmmlllkkkkkjjjjiiiiihhhhgggääããããâââââáááàààßßàßßßÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÚÚÙØÙØØØØ×××××ÖÖÖÖÕÖÕÕÍÈÉÊÌÎÐÑÒÔÕ×רÙÚÛÜÝÝÞÞÞÞÞ¡Ÿœ™˜•“Ž‹‰‡…‚|{yvusqpnmkjihgfedcbbaaa`a`a`aabbbbcddeffhiijllnopqrtuvxyz™™™™™™™™™™šš›››››œœžžŸŸ  ¡¢¢¢¤¤¥¦§§¨©©«¬¬¬®®¯°±²³¶¹»¾ÀÂÄÆÈÊËÌÍÎÏÎÏ‹ˆ…ƒ€}{yvtromkigeba_]\€¦¦¥¥¥¤¤¤¤££££¢¢¢¢¢¡¡ ¡      ŸžžžžœœœŸ±­«©§¥¢ ž›š˜–”“’ŽŒ‹Š‰ˆ†…ƒ‚~|ywuromkjlortvxz}€‚„†Š“—œ ¥¥¥¤£¢¡¡  žžœœ›š™˜˜—–••”““‘‘ŽŒ‹ŠŠ‰ˆ‡‡†„„„ƒ‚}xsnicaaaaaaa``````_x|{{{{{{zzzzyyyxyxxwwwwvvvvuuuuututtssssrrrrrrqqpqpoooooonnnnmmmmllllkkkkkjjjjiiiihhhhghäääããããââââáááàààààßßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÚÛÚÚÚÙÙÙØØØØ××××××ÖÖÖÖÕÕÕÊÈÊÌÍÏÐÑÓÕÖרÙÚÛÜÜÝÝÞÞßÞÞ ž›™–”‘‹ˆ†„€}{ywvtrponlkjigffedcbbbaaaaa`ababbbbddeefgiijklnopqrsuvwxz{šš™™™™™™ššš›››œœœœžžŸŸ   ¡¢££¤¥¦¦§¨¨©ª«¬¬­®¯°±²²µ¸º¼¿ÁÄÆÈÊÊÌÌÎÏÏÏ’Œ‰‡„|zwuspnljgfdba_]\l¦¦¦¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡      ŸŸŸŸžžžœ­«©§¥¢ Ÿœš˜–”“’ŽŒŠ‰ˆ†…„ƒ€|zwuspnkjloqtvx{}€‚„‡Š“—œ ¤¦¥¤£¢¢¡  Ÿžœ›š™™˜—–••””“’‘‘ŒŒŠŠ‰ˆ‡‡†……„ƒ‚‚~xtnidaaaaaaaa```````}||||{{{{zzzzyyyxxxxxwwwwwvvvuuuuutttttssrrrrrqqqppppooooooonnnmmmlllkkkkkjjjiiiiiihihhgääääãããããââââââáààààßßßÞÞÞÞÝÝÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙØÙØØØ××××ÖÖÖÖÖÖÔÈÊÊÍÎÐÑÒÔÕרÙÚÛÜÜÝÝÝÞÞßÞ¡Ÿš—•“ŽŒ‰‡…‚}{ywusrpomljiigfedcbbbaaaaaaabbbbbbcdeffgiiijlmnopqstuwxy{šššššš™šššš›››œœœœžŸŸŸ ¡¡¡££¤¥¦¦§¨©©ªª¬¬­®¯°±²²³·¹¼¾ÀÃÄÇÉÊËÌÎÏÏÏЋˆ†ƒ~{yvuqomkigecb`^\[Z£¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¢¡¡¡     ŸŸŸŸžžžžœœ¨©§¥£¡Ÿ›˜—•“’‘ŽŒŠ‰ˆ†…„ƒ‚€}zxvspnkjmnqsvxz}‚„†‰Ž’—›Ÿ¤¦¥¤¤¢¢¡¡ Ÿžžœ›šš™™˜—––””“’’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„ƒ‚‚ytojeabaaaaaaa``````f}}||||{{{{{zzzyyyxyxxwwwwwvvvvvuuuuutttssssrrqrqqqqppopooooonnnnmmmmmlklkkjkjijiiiiihhhhåäääããããããââââáááàààßßßßßßÞÞÞÝÝÝÝÝÜÝÜÜÛÛÛÛÚÚÙÚÚÙÙØØØØØ×××××ÖÖÖÖÑÉÊÌÎÏÐÒÔÕÖØØÙÚÛÜÝÝÝÞÞßßß ž›™–”‘‹ˆ‡„‚€~|zxvusqonmkiihgeedccbbaaaaaaabbbbccdeffghiijkmnopqstuvxyz{ššššššššššš››œœœœžŸŸ   ¡¢¢£¤¤¥¦¦§¨©ªª¬¬­®¯°°²²³¶¸»½¿ÁÄÆÈÊÊÌÍÎÏÐÏ’ŒŠ‡…€|{xusqnljhgdba_]\[Y¦¦¦¦¥¥¤¤¤¤¤¤££¢¢¢¢¡¡¡¡       ŸŸŸŸžžžžžœ¤¨¦¤¢Ÿ›™—•”“ŽŒ‹‰ˆ‡†„„‚€}{xvsqoljloqsvx{}‚„‡‰Ž’–› ¤¦¥¥¤£¢¡¡ ŸŸž››š™™˜—––””““’‘ŽŽŒ‹‹Š‰ˆ‡††…„„ƒ‚zupkfbabaaaaaaa``````l}}}|||{{{{{{zzyyyyyxxxxwwwwwvvvuuuuutttttssssrrqqqqqqpppoooonnnmnmmmllllkkkkkjjjiiiiihhhååääãäããããããââááááàààààßßßßÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÚÚÚÚÚÙÚØÙØØØ××××××ÖÖÖÏÊÌÍÏÐÒÒÔÖרÙÚÛÜÝÝÝÞÞßßß¡ œš˜•“‘ŽŒ‰‡†ƒ}{ywutrpomlkiihffdccbbbbaaaaaabbbccddefgghijkmmnpqrsuvwxz{ššššššššššš›œ›œœžžŸŸ   ¡£££¤¥¦¦§¨©©ª«¬¬­®¯°±²³´·¹¼¾ÁÄÅÇÉËÌÍÎÏÐÏÐŽ‹ˆ†ƒ~|ywtronkigecb`^]\ZY~¦¦¦¦¦¥¥¥¤¤¤¤££££¢£¢¢¡¡ ¡      ŸŸŸžžžž¡¦¤¢ žœ™—•”“’ŽŒ‹Š‰‡†…„‚€~{yvtqomjloqtvx{}‚„†‰Ž’–›Ÿ¤¦¦¥¤¤¢¢¡¡ Ÿžžœ››™™˜——–•”““’’‘ŽŒ‹‹‰‰ˆˆ‡†……„ƒƒ€{vqlfbbbbbbaaaaaa`a```s}}}}||||{{{{{zzzyyyyyxxxwwwwwvvuuuuuututtssssrrrrrqpppppoooooonnnmnmmmllllkkkjkjjiiiiihhåååääãããããããâââáááááàààßßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÚÙÙÙÙÙÙØØ×××××××ÖÍÊÌÎÐÑÒÔÕרÙÚÛÜÝÝÝÞÞßßßß¡žœ™—”’‹‰‡„‚€~|zywusrpnmljihggedcbbbbbbbaabbbbbccdefgghiiklmnoqrstuvxy{|ššššššš›š›š›œœœœœžžŸŸ   ¢¢££¤¥¦¦§¨¨©ª«¬¬­®¯°±²²³¶¸»¾ÀÂÄÆÈÊÌÍÍÏÐÐÐ’Š‡…‚€}zxvtqoljhgdbb_^\[ZYm§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¡¡¡¡     ŸŸŸŸŸžžžž¤¢¡žœš˜–•“’ŽŒŠ‰ˆ†…„ƒ€~|ywtromjlnqsux{}€‚„†‰Ž’—›Ÿ£§¦¥¤¤£¢¢¡ ŸŸžœ›šš™™˜—––•”““‘‘ŽŽŒ‹Š‰‰ˆ‡††…„ƒƒ€|wrlgbbbbbbaaaaaaaa````z~}}}||||{{{{{zzzzyyyyyxxxwxwwvvuvuuuuuttttssssrrrqqqqqppooooooonnnnmmmlllllkkkjjjjjiiiihæåååääääããããââââââáááààààßßßÞÞÞÞÞÝÝÝÝÜÜÜÜÛÛÛÛÛÚÚÙÚÙÙØØØØ×××××××ËÌÎÏÐÒÓÕ××ÙÚÚÜÝÝÝÝÞßßßߢ ›˜–“‘ŽŠ‡†ƒ}{ywutrqonlkjihgfddccbbbbbbabbbbbcdddffghiijkmnopqrtuvxyz{š›ššššš›šš›››œœžžŸ    ¡¢££¤¤¦¦¦§¨©ª«¬¬­®®°±±²³µ¸¹½¿ÁÄÅÈÊËÌÍÎÐÐÐÐ‘ŽŒˆ†ƒ~{ywuronlihecb`_]\[YX\§¦¦¦¦¦¦¥¥¥¥¥¤¤££££££¢¢¡¡¡ ¡    ŸŸŸžŸžžž¢ žœš˜–•”’‘ŽŒ‹Šˆ‡…„ƒ‚|ywtspmkmoqsuxz}„†‰Ž‘–›Ÿ£§¦¥¥¤£¢¢¡ ŸŸžœ››™˜˜——–•”““’‘‘ŽŽŒ‹‹Š‰ˆ‡‡†…„„ƒ‚|wsmhcbbcbbbbaaaaaaaaa`e~~}}}}|||||{{{{{zzzzzyyyxxxwwwwvvvvuuuuuttttssssrrrqqqqpqpopooooonnnnmmmllllkkkjkjjjjiiiiååååååäääãããããâââáâááàààààßßÞÞÞÞÞÞÝÝÝÝÝÜÜÜÜÛÛÚÛÚÚÚÚÙÙÙÙØØØ××××ÕËÍÎÐÒÓÔÖרÙÚÛÝÝÝÞÞßßààß žœ™—”’‹‰‡…ƒ€~|{yvusrpomlkiihfeddccbbbbbbbbbbccddeefgghijklnooqrsuuvxz{|›š››šš›››š›œœœœžžŸŸ   ¡¡££¤¤¥¦¦§¨©ªª«¬­®¯¯±±²³³¶¹¼¾ÀÂÅÆÉÊÌÍÎÏÐÐÐ’‹ˆ…‚€~zxvtqomjifeca_^\[ZYXW𧦦¦¦¦¥¥¥¥¥¤¤¤¤££££¢¢¡¢¡¡ ¡     ŸŸŸžžžžŸŸ›™—•”’‘‘ŽŽŒ‹Š‰ˆ†…ƒ‚}zxuspmklnqsuxz}€‚„†ˆ‘–šž£§¦¦¥¤££¢¡  Ÿžœ›šš™˜——––””“’’‘ŽŒŒ‹Š‰‰ˆ‡‡†„„ƒ‚|xsnhcccbbbbbaaaaaaaaaaal~~}}}}}||||{{{{{zzzyyyyyyxxwwwwwvvvuuuuuttttssssrrrqrqqqqpopooooonnnnmmmmmllkkkkjjjjiiiiææåååäåääããããããâââáâááààààßßßßÞÞÞÞÞÝÝÝÝÜÜÜÜÜÛÛÚÛÚÚÚÚÙÙÙÙØØ×Ø××ÒÌÎÐÐÓÔÕרÙÚÛÛÝÝÞßÞßßà࢟›™–“‘Šˆ†ƒ‚}{zxvtsqonmkjihgfedcccbbbbbbbbbccdddeffghijkkmnopqstuwxyz{››››››››››››œžžžŸ  ¡¡¢¢£¤¤¥¦¦§¨¨ªª«¬¬­®¯°±²²´µ¸º½¿ÂÄÆÈÊËÍÍÏÏÐÐÐ‘ŽŒ‰†„~|zwuspnlihfdb`_]\[ZXWVŒ§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¡¡¡¡     ŸŸŸŸžžžžžž›™—–•“‘‘ŽŒŠ‰ˆ†…ƒ‚}zxusqnlloqsuxz}ƒ†‰‘•šž£§§¦¥¤¤££¢¡ ŸŸžœ››š™™˜—–••””“’‘Ž‹‹ŠŠ‰ˆ‡‡†…„„‚}xtoidcccbbbbbbababaaaaaau~~~}}}}|||{{{{{{zzzzzyyyyxxwwwwwwvvuuuuuutttttsssrrrqqqqqppppooooonnnnmmmlllllkkjjjjiiiiææåæåååääääããããããâââááááààààßßßßÞÞÞÞÝÝÝÝÝÜÜÛÛÛÛÛÛÚÚÚÚÙÙÙØØØØ××ÑÍÏÐÑÓÔ×רÚÚÛÝÝÞÞßßßßà࡟™˜•“Ž‹‰‡…ƒ|{ywutrponlkiihffeddcbbbbbbbbbbccdeeeghhiiklmnoprrtuvwyz{}››››››››››œœœžŸŸŸ  ¡¡¢£¤¤¥¦¦§¨©ªª«¬¬­®¯°±²²³´·¹¼¾ÁÃÅÇÉÊÌÎÏÏÐÐÐ’Šˆ…ƒ€}{yvtronkigeca`^]\[YXVV}§§§¦¦¦¦¦¦¥¥¥¥¤¤£££££¢£¢¢¡¡¡¡     ŸŸŸžžžžžœ™˜–•“’‘ŽŒ‹‰ˆ‡…„‚‚€~{yvtqollnqsvxz}ƒ†ˆ‘•šž¢§§¦¦¤¤££¢¡¡ Ÿžœ››š™˜—––••”“’‘‘ŽŽŒ‹‹Š‰ˆ‡‡†…„„ƒytojeccccccbbbbbaaaaaaaab~~~}~~}}}|||{{{{{zzzzyyyyyxxwwwwwvvvvvuuuuttttsssssrrqrqqqpppooooonnnnnnmmlmlllkkkjkjjjiæææææååååääããããããââââáááàáàààßßÞÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÛÚÚÚÚÚÙÙÙÙØØØØÐÎÐÑÓÔÖרÙÚÛÜÝÞÞßßßààߢ ž›™–“’‹ˆ†„€~{zxvusqpomljiigffedcccbbbbbbbbccddeefghiijklmooqrstvwxz{|››››››››››œœœœžžŸŸ   ¡¡¡££¤¥¥¦§§¨©ª««¬­®®¯°²²³³¶¸»½¿ÂÄÆÉÊËÍÎÏÏÐГ‘Œ‰‡„|zxuspnljhfdba_^\[ZYWWVo§§§¦¦¦¦¦¦¦¥¥¥¤¥¤¤££££¢£¢¢¢¡¡¡¡     ŸŸŸžžž›˜–•”“‘ŽŒ‹‰ˆ‡……ƒ‚€~{ywtqpmlnqsuxz}„†ˆŒ‘•šž¢¦¨§¦¦¤¤£¢¡  Ÿžœ››š™˜˜—–••”“’’‘ŽŒŒ‹Š‰‰ˆ‡‡†……„zupjfccccccbcbbbbbbbbaaaak€~~}}~}}}}||{{{{{zzzzzyyyxxxxxwwwvvvvuuuuuuttttssssrrrrqqqqppoooooonnnmmnmmllkkkkkjjjjjççæææååååääääãããããââââááááàààßßßßßÞÞÞÞÝÝÝÝÝÜÜÜÜÜÛÛÛÚÚÚÙÙÙÙÙØØ×ÏÐÑÒÔÕ××ÙÚÜÜÝÝÞßßààààà¡Ÿš˜•“Ž‹Š‡…ƒ|{zwutrponlkjihgfedddcbbbbbbcbccddeefghiijklmnopqstuvxy{|}›››››œ›œœœœœœžžžžŸ   ¡¡¢¢£¤¥¥¦¦§¨©ª««¬­®®¯°±²³³´·º½¾ÂÄÅÇÊËÌÎÏÐÐÐÑ“Šˆ…ƒ€~{yvtromkigfcb`_]\[YXWVVb¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££££¢¢¢¢¡¡¡      ŸŸŸŸžœ—•”“‘ŽŒ‹Š‰‡†…ƒ‚~|zwuromlnqsvx{}„†ˆŒ”𢧧§¦¦¥¤¤¢¡¡ ŸŸžœœ›šš™˜—––•””“’‘ŽŒŒ‹ŠŠ‰‰ˆ†††„„€zuqkfddcccccccbbbbbbbbaabbu~~~~~}}}|||{{{{{{zzzzyzyxxxxwwwwwvvuvuuuuttttttsssrrrqqqpppppooooooonnmmmmllkllkkjjjjçæææååååååääääããããããâââááááàààßßßÞÞÞÞÞÞÝÝÝÝÝÝÜÜÜÛÛÚÚÚÚÚÙÙÙÙØØØÏÐÒÓÕÖ×ÙÙÛÜÝÝÞßààààà࣠ž›™—”’‹‰‡„‚€~|zxvusrpomlkjhhgfeedcccbbbcbccccdeefghhiijlmnopqstuvwyz{|››››››œ›œœœœžžžŸ Ÿ ¡¡¢¢£¤¤¥¦¦¨¨©©ª«¬­­®¯°±²³³´¶¹¼¾ÀÃÄÇÈÊÌÎÏÏÐÐÑ”’Œ‰‡„|zxusqoljhfdba_^\\ZYXWVVV¨¨¨§§§§¦¦¦¦¥¥¥¥¥¥¤££££¢££¢¢¡¡¡ ¡    ŸŸŸŸžŸ–”“’ŽŒŠ‰ˆ‡…„ƒ|zwuspmlnqsvxz}„†‰Œ•™¢¦¨§¦¦¥¤¤¢¢¡  Ÿžœ›šš™˜˜—–••”“’‘ŽŽŒŒ‹Š‰‰ˆ‡†……„€{vqlgdddcdcccccccbbbbbaaaac€€€~~~}~}}}||||{{{{{zzzyyyyyyxxwwwvwvvvuuuuuttttttsrrrrrqqqqqpppooooonnnnmmmmlllkkkkjjjçççæçææåååååääããããããâââââááááààßßßßßßÞÞÞÝÝÝÝÝÝÜÜÛÛÛÛÛÚÚÙÚÚÙÙØ×ÐÑÓÔÖרÙÛÜÝÝÞßßàààààà¡Ÿš˜•“‘ŽŒŠ‡†ƒ}{ywvtsqonmkjihggfedddcccbbccccddefffghiijklnnpqrstuwxy{|}››››œœœ›œœœœžžžŸ    ¡¡¢££¥¥¦¦¦¨¨©ª«¬¬­®¯°°±²³´µ¸º½¿ÁÄÆÈÊËÍÎÏÐÐÑÑ“‹ˆ…ƒ~{yvtronkihfdba_]\[ZXXWVVUž¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤££¢¢¢¡¢¡¡¡¡     ŸŸŸž™“’‘ŽŒŠ‰ˆ††„ƒ}{xvspnloqsvxz}„†ˆŒ”™¢¦¨§§¦¥¥¤£¢¡¡ Ÿžžœœ›š™™˜—––•”“’’‘ŽŽŒŒ‹‹‰‰‰‡††…„|wrmhddddddcccccccbbbbbbabbn€€€€~~~}~}|}|||{{{{{{{zzzyyxxyxxwxwvwvvvuuuuutttttssssrrrqqqqqpppooooonnnnmmmmlmlllkkjjçççæææææåååäåääãäãããããââáááááààààßßßÞßÞÝÞÝÝÝÝÝÜÜÜÜÛÛÛÛÚÚÙÚÙÙÙÖÐÒÔÕרÙÚÜÝÝÝßßàààááᣠžœ™—”’‹‰‡…‚}|{ywusrqonlkjihgffedddccccccccddeefgghiijkmmnopqstuwxy{{|›œœœ››œœœœžžŸŸŸ   ¡¡¢£¤¤¥¦¦§¨¨©ª««¬¬­®°±±²³³´·¹¼¾ÀÃÅÈÉËÌÎÏÐÐÑÑ”’‰‡…‚|zxusqomkigecb`^]\[YXWVVVU’©¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¢¡¡       ŸŸŸœ’‘ŽŒ‹‰ˆ‡†…ƒ‚€}{xvtqolnqsux{}ƒ†‰Œ”™¡¦¨¨§¦¦¥¤£¢¡¡  Ÿžœ›šš™˜——–••““’‘‘Œ‹ŠŠ‰‰ˆ‡†…„‚}xrniddddddddcccccccbbbbbbbaz€€€€€~~~~}}}}||||{{{{zzzzyyzyxyxxxwwwvvvuvuuuututtsssrrrrrqqqqpppoooooonnnnmmmmlllllkkjèèçççæææææåååääääãããããâââááááááàßßßßßßßÞÞÞÝÝÝÝÝÜÜÜÜÛÛÛÛÛÚÚÙÙÙÕÒÓÕÖרÚÚÜÝÞÞßàààáááá¡ š˜–“‘ŽŒŠˆ†ƒ€}{zwvusqpnmlkihhgfeeeccccccccccddeffghhiijlmnopqrtuvwxz{|~œœœœ›œœžžžŸŸ   ¡¡¢££¤¤¥¦§§¨©ªª«¬­­®¯°±²²³´µ¸»½ÀÂÄÇÈÊÌÍÏÐÐÑÑÑ“‘Ž‹‰†ƒ~|zwurpnlihfdba_]\[ZYXWVVVU‰¨©¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡      ŸŸŸ•ŽŒ‹Š‰‡†…ƒ‚€}|ywtromnqsuxz}ƒ†ˆ‹”˜¡¦©¨¨¦¥¥¥¤£¢¡¡ Ÿžžœ››š™˜˜—––”““’’‘ŽŒŒ‹ŠŠ‰ˆ‡†……‚}ysnjdeddddddddcddccccccbbbbi€€€€~~~}}|}|||{{{{{zzzzyyyyxyxxwwwvvvvvuuuuuttttsssrrrqqrqpppppooooonnnmmmmmlllkkkkèèçççççæææåæååääääãããããããââáááàáààààßÞßßÞÝÞÝÝÝÝÝÜÜÜÛÛÛÛÛÚÚÚÙÙÕÒÔÖרÙÚÜÝÝÞßßàààááᣠž›š—•’Œ‰‡…ƒ~|{ywutrqonmkjihggfeeddcccccccdddeffgghijjllnooqrstvvxy{|}œœœœœœœœžžžŸŸ   ¡¡¢££¤¤¥¦¦§¨©ªª«¬­­®¯°±²²³´µ·¹¼¿ÁÄÆÈÊËÌÎÐÐÑÑÑ”’ŒŠ‡…‚€}{xvtqomkigecb`_]\[YXWWVVVU€©©¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢¢¢¢¡¡¡¡     ŸŸŸ™ŽŒŠ‰‡†…ƒƒ~|ywtrpmnpsuxz}ƒ†‰‹“˜¡¥©¨¨¦¦¥¥¤£¢¡¡ Ÿžžœœ›š™™˜—–••”“’’‘ŽŽŒ‹‹Š‰ˆ‡††…ƒ~ytojeeeedddddddcccccccccbbbbv€€€€€€~~~~~}}}|||{|{{{{z{zzzyyyyxxxwwwwvvvvvuuuuttttsssssrrqqqqqqppppoooonnnnmmmmmmlklkèèèçççççææåæååååäääãããããââââáááááààßàßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÛÛÚÚÚÚÚÕÓÕÖ×ÙÚÛÝÝÞÞààááááᥢ ›™–“’‹ˆ†„‚~|zxvusqoomlkiihggfedcdcccdcddddeefgghiijklmnopqstuvxy{{}œœœœœœœžžžŸ     ¡¢££¤¤¤¦¦§§©©ªª«¬­®¯°°±²³´µ¶¸»¾ÀÂÅÇÉÊÍÍÏÐÑÑÒÑ“Ž‹‰‡ƒ~|zwuspnljhfdba`^]\ZYXWWVVUUx©¨©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¢¡¡¡¡     ŸŸ‘ŽŒ‹‰ˆ‡†„ƒ~|zxurpnnqsuxz}ƒ†‰‹”˜œ¡¥©©§§§¦¥¤££¢¡ ŸŸžœ›šš™˜——–•””““‘ŽŒ‹‹Š‰ˆˆ‡†…„zupkeeeeedeedddddddccccccbbbg‚€€€€€~~~~}}}|}||{|{{{{zzzzzyyyxxxwwwwwvvvvuuuutttttsssrrrrrqqqpppppooooonnnmmmmmlllkéèèèçççææçæææåååääääãããããâââââáááàààààßßßÞÞÞÝÝÝÝÝÝÝÜÜÜÜÛÛÚÚÚÚÕÔÖרÚÛÜÝÝÞßààááááᣡŸœ™—•“ŽŒ‰‡…ƒ}{ywutrqonmljiihggeeddcccddddddeeegghhijkllnoprrsuvwyz{|~œœœœžžžžŸŸ   ¡¡¢¢£¤¤¥¥¦§¨¨ªª««¬­®®¯°±²²´µµ¸º¼¿ÁÄÆÈÊÌÎÏÐÐÑÒÒ•’Ї…‚€}{xvtqomjihedb`_^\[ZYXWVVUUUp©©©©©¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¥¤¤££££¢¢¢¢¢¡¡¡¡    Ÿ—Œ‹‰ˆ‡†„ƒ‚}zxuspnnqsvxz}„†ˆ‹“˜œ¡¥©©¨§¦¦¥¤££¢¡  ŸŸœ››š™˜——–••”““’‘ŽŒŒ‹Š‰‰ˆ‡‡†„€{vqkfeeeeeeedddddddddccccccccu‚‚‚€€€~~~~}}}|||{{{{{zzzzyyyyyxxwwwwvwvvuuuuuttttttssrrrrrqqqqpppoooooonnnnmmmlllkééèèèçèççççæææåååääääãããããââââáááàààààààßßÞÞÞÞÞÝÝÝÝÜÜÜÜÛÛÛÛÚÚÕÕרÚÚÜÝÝÞßßáááááᥢ ›™–”’‹ˆ‡„‚€~|zyvutrponlkjiiggfedddcdccddddeeegghhiijklnooqrstvwxz{|}œœžžžžŸŸŸŸ   ¡¢¢£¤¤¥¥¦¦§¨©ª««¬¬­®¯¯±²²³´µ¶¹¼¾ÁÃÅÇÊËÍÎÐÐÑÑÒÒ”‘ŽŒ‰‡ƒ~|zwuspoljifecb`^]\[YXXWVVVUUjªª©©©©©¨§¨§§§§¦¦¦¦¦¥¥¥¤¤¤£££££¢¢¡¡¡¡¡     ŸŸŽŠ‰‡†…ƒƒ}zxvsqnnqsuxz}„†ˆ‹“˜› ¥©©¨§§¦¥¥¤£¢¢¡ ŸŸžœ›š™™˜—––•””“’‘ŽŽŒ‹‹‰‰ˆˆ‡†…€|wrmgeeeeeeeeeeddddddddccccccgƒ‚‚‚‚€€€~~}}}}}}||{{{{{{{zzyyyyyxxxxwwwwvvvvuuuuutttssssrsrrrqqqpppoooooonnnnmmmllllééèèèèèèççççæææåååääääãããããââââááááàààààßßßßÞÞÞÝÝÝÝÝÝÜÜÛÜÛÛÛÚÖÖØÙÚÜÜÝÞßàààáâââ⣡Ÿš—•“ŽŒŠ‡†ƒ}{zwvtsqoonljjihggfeeddddcddddeeffgghiikklmooprstuvxyz{}~œžžžžžŸŸ   ¡¡¡¢¢¤¤¤¥¦¦§¨¨©ª«¬¬®®¯°°²²³´´µ¸º½¿ÂÄÇÉÊÍÍÏÐÑÑÒÒ–“Šˆ…ƒ€~{ywtromlihfdba`^\[ZYXXWVVUUUeªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤¤£¤£££¢¢¡¡¡¡      –Šˆ†…„ƒ€~{yvtqonqruxz}ƒ†ˆ‹Ž“—œ ¤©©©¨§§¦¥¤£¢¢¡  Ÿžžœ›š™˜˜—–•””“’’‘ŽŽŒ‹‹Š‰‰ˆ‡†…|wrmheeeeeeeeeeeddddddddccccccvƒ‚‚‚‚‚€€€€€~~}~}}}|||{|{{{{{zzzyyyyxxxwxwwwvvvuuuuuuttttssssrrrqqqqppppooooonnnmnmmllléééèèèèçççççæææææååäääääããããããâââáááàààßàßßßÞÞÞÞÝÝÝÝÝÝÜÜÜÜÛÛÛ×רÚÛÜÝÞßàßáááââ⥢¡ž›™—“’‹‰‡…‚€~|{ywutrponmkjiihgfeeeeddcddedeeffgghiijklmnopqrtuvwxz{|~œžžžŸŸŸ    ¡¡¢££¤¥¥¦¦§§¨©ª«¬¬­®¯¯°²²³³µ¶·¹¼¾ÁÄÅÈÊËÍÎÐÐÑÒÒÒ”‘ŽŒ‰‡„|zxusqoljigecb`^]\[ZYXWVVVVUU`ªªª©©©©©¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¢¡¡¡¡¡     ‡†„ƒ€~|zwtroopsuxz|„†ˆŠŽ’—› ¤©©©¨§§¦¥¥¤£¢¡¡  Ÿž››š™™˜—–••”““’‘‘ŽŒŒ‹Š‰‰ˆ‡††‚}wsnheeeeeeeeeedeedddddddddccci„ƒ‚ƒ‚‚‚‚€€€~~~~~}}|||{|{{{{zzzzyyyyxxxxwwwwwvvvuuuuuutttsssssrqqqqqpqppooooononnmmmmléééééèèèèçççççææææåååäääããããããâââáááááàààßàßßÞÞÞÝÞÝÝÝÝÜÜÜÜÜÛÛרÙÛÛÝÝÞßàááâââã⤡Ÿœš˜•“‘ŽŠˆ†„€}{zxvusqpomlkjiihgfeeeddddddeeffffghiiiklmmopqrttuwxy{|}žžžžžžŸŸŸŸ   ¡¡¢¢£¤¤¥¦¦§§¨©©«¬¬¬®¯¯°±²³´´µ¶¸»¾¿ÃÄÇÉÊÌÎÐÑÑÒÒÓ•“Šˆ…ƒ€~{yvtrpnlihfdba_^\[[ZXXWVVVVUU\«ªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢£¢¡¡¡¡¡    ˜†…„|ywtrpnpsvxz|ƒ†‰‹Ž’—› ¤¨ª©¨¨§¦¦¥¤¤£¡¢¡ ŸŸœ›š™™˜——–•””“’‘‘ŽŒ‹‹Š‰ˆˆ‡†ƒ~xsojfeeeeeeeeeeeeeddddddddddccy„ƒƒƒƒƒ‚‚‚€€€~~}}}}||||{{{{{{zzyyyyyxyxwwwwvwvvvuuuuutttsssssrrrrqqqppppoooooonnnnmmmêéééééèèèèèççææææåæåååäääãããããâââââááááààààßßÞßÞÞÞÝÝÝÝÝÝÜÜÜÛÜ×ÙÚÛÜÝßßààáââââ⥣ ž›™—”’‹‰‡…ƒ|{ywutrpoomkjiihgffeeeedddddeeeffghiijjklnnopqstuvxy{||~žžžžžžžžŸŸŸ   ¡¡¡¢£££¤¥¥¦¦¨¨©©ª¬¬¬­¯¯°±²²³´µ¶·º¼¿ÂÄÆÈÊÌÎÏÐÑÒÒÒÓ”’Ї…‚}{xutqomkigedba_]\\ZYYWWVVVVUUY«ªªªª©©ª¨©¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¥¥¤¤££££¢¢¢¡¡¡¡     Ž„|{xurpnpsuwz|~„†ˆŠ’—›Ÿ¤¨ªª©¨§¦¦¥¥¤£¢¢¡ ŸŸœœœ›š™™˜—–••”““’‘ŽŒ‹Š‰‰ˆ‡†ƒytojfffeeeeeeeeeeedededddddddcn„„ƒ„ƒƒ‚‚‚‚‚€€€€~~~~}}}}|}||{{{{{zzzzyyyyxxxwwwwvwvvvvuuuutttttssrrrrrqqqqpppooooonnnnnnmêêééééééèèçççççææææåååäääääãããããââââááàààààààßßßÞÞÝÝÝÝÝÝÝÜÜÜÜØÙÛÜÝÝßààáââââã㥢 š˜•“‘ŽŒŠˆ†„€}|zxvusrponlkjiigggfeeeddddeeeffgghiijjklnnopqrtuuwxz{|~žžžžžžžžžžžŸŸŸ   ¡¡¢¢£¤¤¥¥¦¦§¨©ªª«¬¬­®¯¯±±²³´µµ·¸»¾ÀÃÅÇÉËÌÎÐÑÒÒÓÓ–“Ž‹ˆ†ƒ~|ywurpnljifeba`_]\[ZYXWVVVVVVVW««««ªª©ª©©©¨¨¨§§§¦§¦¦¦¦¥¦¥¥¤¤¤¤£¤£££¢¡¢¢¡¡¡   ›‚€|{xutqnpsuxz|ƒ…‰Š’—šŸ£¨«ª©©¨§¦¦¥¤£¢¢¡¡ŸŸžœœ›šš™˜—––•”““’‘ŽŽŒŒ‹ŠŠ‰ˆ‡‡„zupkfffefeeeeeeeeeeeeeddddddddd…„„„ƒƒ‚ƒƒ‚‚‚€€€€€€~~~~}}}}|||{{{{{zzzyyyyxxxxwwwwwvvvvuuuuuttttsssssrrqrqqqppppoooononnnnêêêêééééèèèèçççæææææååååääääãããããâââââáàààààßßßßÞÞÞÞÝÝÝÝÝÜÝÜÜÙÚÜÝÞßààááââãã㥣 Ÿ›™—”’Ž‹‰‡…ƒ}{ywvurqoomlkjihhgfffeeeeeeeeffggghiijkllmooqqstuwxy{|}~žžžžžžžžžŸžžŸ     ¡¡¢¢££¤¥¦¦¦§§¨©ª«¬¬­®®¯°±²³´´µ·¸º½¿ÂÄÆÈÊÌÎÏÐÒÒÓÓÓ”’‰‡„€}{xvtqomkigfdba`^\\[ZYXWWVVVVVVV««««ªªªª©©©©©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡¡¡  ‘}{xvtqopruxz|~ƒ…ˆŠ’–šŸ£§«ª©¨¨§¦¦¥¤¤£¢¢¡ Ÿžœœ›š™™˜——–••”“’‘ŽŽŒ‹‹Š‰‰ˆ‡…€{vpkfffffffeeeeeeeeeeeeddddddddv……„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}}|||{|{{{{{zzzzyyyyxxwxwwwvvvvuuuuutttsssssrrrqqqqqppppooooonnnmêêêéééééééèèçççççææææåååäåäääãããããâââááááàààààßßßßÞÞÝÝÝÝÝÝÝÜÜÚÛÝÝÞßàáááãããã㤢 ›™–“‘‹ˆ†„‚€}|{yvusrqonmlkjihggffeeeeeeeeeffgghiijklmmoopqrtuvxyz{|}€ŸžžžžžžžžžŸŸŸ    ¡¡¢¢££¤¤¥¦¦§¨¨©ªª«¬­­®¯°±²²³´µ¶·¹¼¾ÁÃÆÈÊËÍÏÐÑÒÒÓÓ–“‘Ž‹‰†„~{zwurpnljhfecb`_]\[ZYXWWVVVVVVVV¬¬«««ªªªªª©©©¨¨§¨¨§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡¡   …yvtroqsuxz|ƒ†ˆŠ’–šŸ£¨«ªª©©¨§¦¦¤¤£¢¡  Ÿžœœ›šš™˜—––•”“’‘‘ŽŽ‹‹ŠŠ‰ˆ‡…€{vqlgffffffefeeeeeeeeeeeeedddddm………………„ƒ„ƒƒ‚‚‚‚‚€€€€~~~~~}}}}||{{{{{{zzzyyyyxywxwwwwwvvvuuuuutttstssssrrrrqpqppoooooooonnêëêêêéééééèèèèçççççæææååäåäääãããããâââââááááàààßßßÞÞÞÞÞÝÝÝÝÝÜÝÛÝÝÞßàááââããã㦣¡Ÿœ™—•“ŽŒ‰‡…ƒ}{zwvusqponlkjiihggffeeeeeeeeeggghhijkllmnopqrsuvwxz{|}žžžžžžžžžŸŸŸŸ    ¡¡¢¢£££¤¥¥¦¦§¨©ª««¬¬­®¯°±²²³´µ¶·¸»½ÀÂÄÇÉËÌÏÐÑÑÓÓÔÔ”’Ї…‚€}{xvtqomkigfdba`^]\[ZYXWWWVVVVVVW¬¬««««««©©©©©¨©¨¨§¨§§§¦¦¦¦¦¥¥¥¤¤¤¤££££££¢¢¡¡¡   šxuroqsuxz|„…ˆ‹‘•šž£§¬ªªª©¨§¦¦¥¤¤¢¢¡ ŸŸžœ›šš™˜——–•””“’‘‘ŽŒ‹ŠŠ‰ˆˆ†|wrmhgggfffffeeefeeeeeeeeeeddddd€†………„„…„„ƒƒƒƒ‚‚‚‚€€~~~~~}}}}}|||{{{{{{zzzyyyyxxxxwwwwvvvuuuuuuttttstsssrrrrqqqqppooooonnnëëëêêêêéééééèèçççççççææåååäåäääããããããâââáááààààßßßßßÞÞÞÝÝÝÝÝÜÛÝÝÞßàááâãããã㥢 ›˜–“‘‹ˆ†„‚€~|{ywvtsqpnmlkjihhhffffeeeefeegghhhijjklmnooqrstuwxy{|}~€ŸžžžžŸŸžŸŸŸŸ    ¡¡¡¢££¤¤¥¥¦¦§¨¨ªª««¬­®¯°±²²²´µµ¶¸º¼¿ÁÄÆÉÊÌÎÐÐÒÓÔÓÔ–”‘Ž‹‰†„|zwusqomjigecba_^\\ZYYXWWWVVVVVVX¬¬¬««««ªªª©©©©¨©¨¨¨§§§¦¦¦¦¦¥¦¥¥¤¥¤¤££££¢¢¢¡¡¡¡¡  sppruwz|~ƒ†ˆŠ‘–šž£§««ªª¨¨¨§¦¥¤££¢¡¡ žžžœ››š™˜˜—–••”“’’‘ŽŒŒ‹Š‰‰ˆ‡‚}xsmhgggggfffffeefeeeeeeeeeeeedey‡†††…………„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}}}}|||{{{{{{zzzzyyyxxxwwwwwvvvvvuuuuttttsssssrrrqqqqpppoooooooëëëëêêêêééééèéèèççççææææåååäääääãããããââââââááààààßßßßÞÞÝÝÝÝÝÝÝÝÞßááââãããã㦤  œš˜•“ŽŒŠ‡†ƒ}|zxvtsrponmkkiihhggfffeeefeffgghhiijkkmnooprrtuvwyz{|~žžŸŸŸžŸŸŸŸŸŸ    ¡¡¡¡¢£¤¤¥¥¦¦§§©©ª««¬­®®¯°±²²³µµ¶·¸¼¾ÁÃÅÇÊËÍÏÐÑÒÓÔÔÔ•’Ї…‚€}{yvtronlihfdbb_^]\[ZYYWWWVVVVVVV[¬¬¬¬««««ªªª©ª©©©¨¨¨§§§§¦¦¦¦¦¦¦¥¥¤¤¤¤¤££££¢¢¡¡¡¡¡¡ „psuwz|~ƒ†ˆŠ•šž¢§«¬«ª©¨¨§¦¥¥¤££¢¡ Ÿžžžœ››š™™˜—–••”““’‘ŽŒ‹‹‰‰ˆ‡ƒ}xtnjhgggggfgffffffeeeeeeeeeeeeer‡†††††………„…„„„ƒƒƒ‚‚‚‚€€€~~~}}|||||{{{{{zzzzzyyyxxxxxwwwwvvvvuuuututtssssrrrqrqqqqpppoooooëëëëêëêêéééééééèèèççççææææååäåääããããããââââáááàààààßßßßÞÞÞÝÝÝÝÝÞßàáââãããäã㥢 ž›™—”’‹ˆ‡„‚€~}{yxutrqpomlkjiihhggffffffffffghhiijkllnoopqrttvwxz{|}€žžŸŸŸŸŸŸŸŸŸ    ¡¡¢¢¢£¤¤¤¥¦¦§§¨©ª««¬­®®¯°±²²³´µ¶·¸º½¿ÂÄÆÉÊÌÎÐÑÒÓÔÔÔ–”‘ŽŒ‰‡„|zxusqomkigedba_^\\ZZXXXWWWVVVVVW^¬¬¬¬¬¬««««ªªª©©©¨¨¨¨§¨§¦§¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¢¡¡  }uwz|ƒ†ˆŠ‘•™ž¢¦«¬«ªª¨¨§§¦¥¤£¢¢¡ ŸŸŸœ››š™˜—––•””““‘‘ŽŽŒŒ‹Š‰ˆˆƒ~ytojghggggggfffffffefeeeeeeeeeem‡‡‡‡††††……„……„„„ƒƒƒ‚‚‚‚€€€~~~~}}}}|||||{{{{{zzzzyyxyxxxxwwwwvvvvuuuuuttttssrrrrqrqqqppppooooëëëëëëêêêêééééèéèèèèçççæææåååäåääääãããããââáááááàáàßßßÞßÞÞÞÝÝÝÝßààââãããäã㦤¡Ÿœš˜–“‘ŽŒŠˆ…ƒ‚~|zxwutrqoomljjiihhggffffeffffghhiiijklmnopqrstuvxy{{}~€ŸŸŸŸŸŸŸŸŸŸ    ¡¡¡¡¢££¤¤¥¥¥¦§§¨¨©««¬¬­®¯°±²²³³µ¶¶·¹»¾ÁÃÆÇÊËÎÏÐÑÒÓÔÕÔ•“‹ˆ…ƒ€~{yvtrpnljhgdcb`_]\[ZZYXXWWWWWWWWWb­­¬¬¬¬«¬«««ªª©ª©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¢¡¡¡¡¡š{z|~ƒ…ˆŠ•™¢¦«¬¬«ª©¨¨§¦¥¤¤£¢¡  ŸŸžœœ›š™˜——––•”““’‘ŽŒŒ‹ŠŠ‰ˆ„zupkhggggghgggfgfgffffffeeeeeeehƒ‡‡‡‡††††……………„„„„ƒƒƒ‚‚‚‚€€~~~~}}}}||||{{{{z{zzyzyyxxxwwxwwvvvvvuuuuuttttssssrrrqqqqqpppoooëëëëëëêêêêéééééèèèèçççççæææåæåååäääãããããâãââáááàààààßßßÞßÞÞÝÝÞàááââãããää䥣 ›™—”’‹‰‡…ƒ}{ywvtsqponmkkjiihggfffffffgggghiiijklmmoopqstuvwxz{|~ŸŸŸŸŸ ŸŸ Ÿ    ¡¡¡¢¢££¤¥¥¥¦¦§¨©©ª«¬¬­®¯¯°²²³³´µ··¸º½ÀÂÄÇÉËÍÏÐÑÒÒÔÕÕ—”‘ŽŒ‰‡„}zxusqomkihfdba_^]\[ZZYXXWWWWWWWWWg­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤£££¢¢¢¡¢¢¡¡ —|„†ˆ‹Œ•˜¢¦ª¬¬«ª©©¨§¦¥¤¤££¡¡  Ÿžœœ›šš˜˜——–•”““’‘ŽŒ‹ŠŠ‰ˆ„€{uqlghhghggggggggfffffffeeeeeeeeˆ‡‡‡‡‡‡††††………„„„ƒ„ƒƒƒ‚‚‚‚€€€~~~~}}}|||{{|{{{{zzzzzyyyyxxwwwwwvvvuuuuutttstssrssrrrqqqppppooëëëëëëëêêêêêééééèèèèçççæçæææåååååäääããããããâââáááàáàààßßßÞßÞÝÝÞàáâãããããä䦤¡Ÿš˜•“‘Šˆ†„‚€~|zywutspponlkjiihhggggffffgggghhiijjllmoopqsttvwxz{|}~€ŸŸŸŸŸŸŸ Ÿ      ¡¡¢¢£££¤¥¦¦¦§¨©©ª««¬­­®¯°±²²³´µ¶·¸¹¼¾ÁÄÆÈÊÌÎÏÑÒÓÓÔÔÕ•“‹ˆ…‚~|ywuspnljifecb`_^\[[ZYYXWWWWWWWWWWm­­¬¬¬¬¬¬¬««««ªªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤£££¢¢£¢¢¡¡¡  ”„…ˆŠŒ”˜¡¦ª­¬«ªª¨¨¨¦¦¥¤¤£¢¡¡ Ÿžžœ›š™˜——–•””“’‘‘ŽŒ‹‹Š‰‰†{vqlhhhhgghgggggggfffffffeeeeeee|ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}~~}}}|||{{{{{zzzzzzyyyxxwwwwvvvvvvuuuuutttsssssrrqrqqqpppppëëëëëëëëëêêêééééééèèèççççæçæåæååååääääããããâãââááááàààßßßÞßÞÞÞßáââããããää䦣 žœ™—”“Ž‹‰‡…ƒ}{zxvusrponmkkjiihhhggggfggggghiiijjlmmnopqqstuwwyz||~ ŸŸŸŸ        ¡¡¢¢¢£££¤¥¥¦¦§¨¨©©ª¬¬¬­¯¯°±²²³´µ¶·¸¸»¾ÀÃÅÇÊËÎÏÐÑÓÔÔÕÕ—”’Œ‰‡„‚}{xvtqomlihfdca`^]\\[ZYYXXWWWWWWWXWu®­­­­¬¬¬¬««««ªªªªª©¨©©¨¨¨¨§§¦§¦¦¦¦¥¦¥¥¤¤¤¤¤££¢¢¢¢¢¡¡¡ •†ˆŠ”˜œ¢¦ª¬¬«ªª©¨§§¦¥¥¤£¢¡¡¡Ÿžžœ›š™™˜—––””““’‘‘ŽŒ‹Š‰‰†|wrmhhhhhhhggghghggggfgfffffefeezˆˆˆˆˆˆ‡‡‡‡††††…………„…„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}|||{{{{{zzzyzyyxxxxwwwwvvvvvuuuuttttttsssrrrrqqqpppoëëëëëëëëëëëêêêééééèèèèèççççææææååäåääããããããââââáááááààààßßßÞÞàáâãããäãää§¥¢ š˜–“’Šˆ†…€~|{ywutsqponmkjjiihhhggggggggghhiijjklmnnoprrtuuwyz{|~  Ÿ ŸŸŸ       ¡¡¢¢££¤¤¥¥¦¦§§¨©©ª«¬­­®¯°±±²³´´µ··¸º¼¿ÂÄÇÈÊÌÎÐÑÒÓÔÕÕÕ–“ŽŠˆ†ƒ~|ywusqoljigedba_^]\[ZZYYXXWWXWWWWXX|­­­­¬¬¬¬¬¬«««ªªªªªª©©©¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡¡–ŠŒ”˜¡¥ª®¬««ª©©¨§¦¥¥¤££¢¡¡ Ÿžœœ›š™™˜——–••”“’‘‘ŽŒ‹‹Š‰‡‚|xrnhihihhhghggghggggggfffffffeezЉ‰‰ˆˆˆ‡‡‡‡‡‡‡†††………„„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}~}}}||||{{{{{zzzzzyyxyxxxwwwwvvvvuuuutttttsssrrrrqqqpqppëëëëëëëëëëëêêêééééééèèèççççæææåææååååääãããããââââááááààààßßßßÞàâãããääää䦣 Ÿœ™—•“ŽŒ‰‡…ƒ}{zxvutrppomlkjjiihhggggggggghhiijjkkmmnopqrsuuwxy{|}€ ŸŸ          ¡¡¢£¢£¤¤¥¦¦¦§§¨©©ª«¬¬­®®¯°±²³³µµ¶·¸¸¼¾ÀÃÅÈÉÌÎÏÐÒÓÔÔÕÕ—”’Ї„‚€}{yvtronkihfecba_^\\[ZZYXXXXXXXWXXXX„®®®®­­­¬¬¬¬««««ªªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¤¤¤¤¤¤£££¢¢¢¡¡¡¡¡˜“˜œ ¥©®­¬«ª©©¨¨§¦¥¥¤£¢¡¡ Ÿžœœœ›š™˜——–••”“’’‘Ž‹ŠŠ‰‡ƒ}ysniiiihhhhghghggghgggggfffffffzЉ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…†……„„„„„ƒƒƒ‚‚‚‚‚€€€~~~~}}}|}||||{{{{zzzyyyyxxxxxxwwvvvuvuuuuttttsssssrrrqrqqqpëëëëëëëëëëëëêêêêééééèèèèèççççææææååååäääããããããâââáááàáàààßßßÞßâããäääää§¥¢ ž›™–“’‹ˆ‡…‚€|{zwvtsrponmlkjiiihggggggggghhhiijkkmnnopqrsuuwxyz{}~€           ¡¡¡¡¢¢£££¤¥¥¦¦§§¨¨©ª««¬­­®¯°±²²´´µ¶·¸¸»½ÀÂÄÇÉËÍÏÐÑÓÔÔÕÕ™–“Ž‹‰†ƒ|zwusqomjigfdba`^]\[[ZZYYXXXXXXXXXXYŽ®®®­­­­­¬¬¬¬««««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤££££¢£¢¢¡¢¡  ›—œ¡¥©®­¬¬ªªª¨¨§§¦¥¤££¢¡  Ÿžœœ›š™™˜——–•”““’‘‘ŽŒ‹ŠŠ‰ƒ~ytojiiiihhhhhhhgggggggggggffgff|ŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡††††…†……„……ƒ„ƒƒƒ‚‚‚‚€€€€~~~}}}}||||{{{{{zzzzyyyxxxxxxwwwvvvvuuuutttttsssrrrqrqqqpëëëëëëëëëëëëëêêêêéééééèèèççççæçæåæååäääääãããããâââââááááààßàßßàããäääåä䦤¡Ÿœš—•“ŽŒŠ‡†ƒ‚€}|zxwutrqponllkjiihhgggggghghhiiijjklmnooqqstuvwyz{|}€         ¡ ¡¡¡¢¢£££¤¥¥¦¦¦§¨¨©ª««¬­­®¯°±²²³´´µ¶·¸¹¼¾ÁÄÆÈÊÌÎÐÑÒÓÔÕÕÖ—”’Ї…‚€}{yvurpnljhfecba_]\\[[ZYYXXXXXXXXXYYY˜¯¯®®­­­¬­¬¬¬¬««««ªªªª©©©©¨¨¨¨¨§§¦¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¡¡¡  Ÿ ¥©®­­¬««ª©¨§¦¦¥¤££¢¢  Ÿžœ›šš™˜—–––•”“’‘‘ŽŽ‹‹Š‰„zupkjiiiiiihhhhhgghgggggggggfff~‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyzyyxxxxwwwvvvvuuuuuuttttssssrrrqqqqëëëëëëëëëëëëëëëêêêéééééèèèèçèççææææåååääääããããããââáááááàààßßßàãäääååå§¥£ ž›™–”’‹ˆ‡…‚}{ywvusrqonmlkjjihhhghgghhhhhiiijjklmnnopqrsuvvxz{|}~€         ¡¡¡¡¡¢¢¢£¤¤¤¥¥¦¦§¨¨¨ªª«¬¬­®®¯±±²³´µ¶¶·¸¹»¾ÀÃÄÇÉËÍÏÐÒÓÔÕÕÖ™–“‘Ž‹‰†„|zwusqomkigfdba`^]\\[[ZYXXXXXXXYYYYYY¤¯¯¯®­®­­­­¬¬¬¬¬«««ªªª©©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¢¢¡¡¡¡¡¨­®­¬«ªª©¨§¦¦¦¥¤£¢¡¡ ŸŸžœœ›šš™———–•”“’’‘ŽŒ‹‹Š…€{uqkjjijiiiihhhhhhhhghgghhggggj‚Œ‹‹ŠŠ‹ŠŠŠ‰ˆ‰‰ˆˆ‡‡‡‡‡‡†††…………„…„„„ƒƒ‚ƒƒ‚‚€€€€~~~}}}}}|||{{{{{{zzzzzyyyyxxxxwwvwvvuuuuuutttstssrrrrqqqëëëëëëëëëëëëëëëëêêêéééééèèèçççççææææååååäääãããããããââááááàààßßßãääååå妤¡Ÿ™˜•“‘ŽŒŠˆ†„‚€~|{ywutsqponmlkjjiihhggggghhiiiijjkllnoooqrstuvxyz{}~‚         ¡¡¡¢¢¡¢£¤¤¤¥¦¦¦§¨¨©©ª«¬¬­®®¯°±²²´µµ¶·¸¹¹¼¿ÂÄÆÉÊÍÎÐÑÓÔÕÕÖÖ˜•’Ї…‚€}{yvurpnljigecba_^]\\[[YYXXXXXYYXYYZY[¯¯¯¯¯®®­®­­¬¬¬¬¬«¬««««ªª©©©©©¨¨¨§¨§¦¦¦¦¦¦¦¥¥¥¥¤¤¤£££¢£¢¢¢¡¡¡¡¢ª­¬««ªª©¨§¦¦¥¤££¢¡ Ÿžžœœ›š™™˜——–•”““’‘ŽŒ‹‹Š…|vqljjijjiiiiihhhhhhghgghgggggp‡ŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡‡‡†††………………„„ƒƒƒƒ‚‚‚‚€€€€€~~~~}}}|||||{{{{{zzzyzyyyxxwxwwwvvvvvuuuuutttttssrrrrrqëëëëëëëëëëëëëëëëëêêéêééééèèèèèççææææåæååääääãããããããââáááààààßàãåååå娦¢ ž›™—”’‹‰‡…ƒ}{zxvutrqonmllkiiiihhhhhhhhhiiijjkllmnooqqstuvwxz{|}‚¡        ¡¡¡¡¢¢¢££¤¥¥¥¦¦§¨¨¨©ªª«¬¬®®¯°±²²³´µ¶·¸¸¹»¾ÀÃÅÈÊÌÎÏÑÒÔÔÕÖÖ™–“‘Œ‰‡…|zxvsqomlihgdcb`_^]\\ZZYYYXXXXYYYYYYZh°°¯¯¯¯®®®­­­­¬¬¬¬¬¬««««ªª©©©©¨¨¨¨§§§§§¦¦¦¦¥¦¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡¡ §¬«ª©©¨§¦¦¥¤¤£¢¡  Ÿžžœœ›š™˜˜—––””“’’‘ŽŒŒ‹Š†|wrmjjjjijiiiiiiiihhhghhggggggvŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒ‚ƒ‚‚‚‚€€€€€~~~}}}}|||{{{{{{zzzyyzyxxxwxwwwvwvvvuuuuutttssssssrqqëëëëëëëëëëëëëëëëëëêêêéééééèèèçççææææææååååäääããããããââââáááàààßãåååå妤¢ š˜–“‘Ž‹ˆ†„‚€~|{ywvtsqponmmkjjiiihhhhhhhihiiijkllmnoopqstuvwxz{{}‚¡¡¡     ¡¡¢¢¢¢£££¤¤¥¥¦¦§§¨©©©««¬¬­®¯°°²²³´µ¶¶·¸¹º½¿ÂÄÇÉÊÍÏÑÒÓÔÕÖÖÖ˜•’‹ˆ†ƒ~|ywuspomkihedba_^]\\[ZZYYYXXXYYYYYZZZu°¯¯¯¯¯¯®®­­­­­¬¬¬¬¬«««ªªªªªª©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¡¡¡¡¡¡ ¤ªª©©¨§¦¥¥¤£¢¢   Ÿžœœšš™˜˜—––””““’‘ŽŒŒ‹‹‡‚}wsnjjjjjjjijiiiihhhhhhhghgggh~ŒŒŒŒ‹‹ŠŠŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡††††………………„„ƒƒƒ‚‚‚‚‚€€€~~~}}}}}||{{{{{{zzzzzzyyxxxxwwwwwvvvvuuuuttttsssrrrqëëëëëëëëëëëëëëëëëëëêêêéééééèèèççççççæåæååååääääããããâãâââáááàààâååææ¨¦£ žœ™—”“ŽŒ‰‡…ƒ}|zxwutsqoonmlkjiiihhhhhhhhiiiijjklmnnopqrstuvxx{{}~‚¡¡  ¡ ¡¡¡¡¡¢¢¢¢££¤¥¥¥¦¦¦§¨¨©©ª¬¬¬­®¯°°±²²³´µ¶·¸¸º¼¾ÁÃÆÈÊÌÎÐÒÓÔÕÖÖ×™–”’ŽŒ‰‡„}zxvtqonlihfecb`_^]\\[ZZYYYXYXYYYZYZZ[ƒ°°°°¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¥¤£¤£££¢¢¢¢¡¡¡  ¢§¨¨§¦¥¥¤££¢¡¡ Ÿžœ›š™™˜——–•”““’‘‘ŽŒŒ‹‡‚}xsnjjjjjjjjjiiiiiiihhhhhhhggrˆŒŒ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡†‡‡†…†………„…„„ƒƒƒƒ‚‚‚€€€~~~~~}}}||||{{{{{zzzyyyyxxxxwwwwvvvvuuuuutttsssrssrëëëëëëëëëëëëëëëëëëëêêêêééééééèèèèççæçæåæåååäääääãããããâââáááááàâæææ©¦¤¢ š˜–“’‹ˆ†„‚€}{ywvusrpoommlkjiiihhhhhhhiiiijjkllmnooqrstuvwxz{|~€¡¡ ¡¡¡¡¡¡¡¡¢¢¢£££¤¤¥¥¥¦§§¨¨©©ª«¬¬­®¯°°±²²´´µ¶··¸¹º¾ÀÂÅÇÊËÍÏÑÒÓÕÖÖ×ט•“‹ˆ†ƒ~|ywusqomkihedba`_^]\[ZZZYYYYYYYZZYZZZ[“°°°°¯¯¯®®®®®­­­­¬¬¬¬¬««««ªªª©©©¨©¨¨¨¨¨¦§§¦¦¦¦¦¦¥¥¥¥¤¤£¤£¢£¢¢¢¢¡¡ ¡ £§§¦¥¤££¢¡¡  žžœ››š™˜——––””““‘‘‘ŽŒŒ‹ˆƒ~ytokkjkjjjjjjjjjiiihihhhhhhh}ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠ‰‰Š‰‰ˆˆˆˆˆ‡‡‡‡††††………„„„„ƒ„ƒƒ‚‚‚‚€€€€~~~~}}}}}||||{{{{z{zzzyyyxxxwxwwwvvvuuuuuttttssssrrëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèçèççæææåæååäåäääãããããâââáááàááæææ¨¦£¡žœ™—•“‘ŽŒŠ‡…ƒ€~|zywutsqponmlkjjiiiihihhihiiijjklmmnnopqstuuwxy{|}€‚¡¡¡¡¡¡¡¡¡¡¢¢¢£££¤¤¤¥¦¦¦¦¨¨©ªª«¬¬­­®¯¯°±²³´´¶··¸¹º¼¾ÁÄÆÈËÌÏÐÒÓÔÖÖ×Ö™—”’‰‡…‚€}{yvtronljifedba`_]\\\[ZYYYYYYXYZZZZ[[[£±°°°¯°¯¯¯®®®®­­­­¬¬¬¬¬«««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢£¢¢¢¢¡¡¡¡  ¢¤¥¥¤£¢¢¡  Ÿžœœ›š™™˜—––•””“’‘ŽŽŒ‹‰„zuokkkkkjjjjjjjjijiiiiihhhhuŠŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†‡†††……„…„„„ƒƒƒƒ‚‚‚‚€€€€€~~~}}}}||}|||{{{{{zzzyyyxxxxxxwwwvvvvuuuuututsssssëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèççççæææææåååååäääããããããâââááááææ©§¤¢ š™—“’‹‰†…ƒ}{zxvutrqoonmkkjjjiiiiiihiiiijjkklmnnopqrstuvxyz|}~‚¡¢¡¡¡¡¡¡¡¡¢¢¢£££¤¤¤¥¦¦¦¦§¨¨©ªª«¬¬­®¯°±±²³³´¶¶·¸¸º»¾ÀÃÅÇÊÌÎÐÑÓÔÕÖ××ט–“‘Ž‹ˆ†ƒ|zwusqomkigfdca`_^]\\[ZZZZYYYZYYZZ[Z[[a±±±°°°°¯¯¯¯®®®­­­­­¬¬¬¬««««ª«ªª©©©©¨¨¨¨§§§¦§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¡¡¡    ¡¤¤£¢¡  Ÿžœ››™™˜—––••”“’‘‘ŽŒŒŠ…€{vpkkkkkkkjjjjjjjijiiiihhhpƒŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰ˆ‰ˆˆˆ‡‡‡‡‡‡†††…†……„…„„„ƒ‚ƒ‚‚‚‚€€€€€~}~}}}|}|||{{{{zzzzyyyxxxxwwwwwvvvvuuuuuttttsssëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèèèçèçççææææåååäääãäãããããâââââááä稦£¡Ÿœ™˜•“‘ŽŒŠˆ†„‚€~|{ywuusqponnlkkjiiiiiiiiiiijikkllmmnopqrstuvwyy{|}€ƒ¡¡¡¢¡¡¡¡¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ªª«¬¬­®®°°±²³´´µ¶·¸¸¹»¼¿ÁÄÆÉËÍÏÐÒÓÕÖ××ך—”’Ї…‚€}{yvtspnljigedba`_^]\\[[ZZZYZYZYZZ[[[[[s±±±±°°°¯°¯¯¯®®®­­­­­¬¬¬¬¬¬«««ªªªª©ª©¨¨¨¨§§§§¦¦¦¦¦¦¥¥¥¤¥¤¤¤¤£££¢¢¢¡¡¡     ¡¢¢¡ Ÿžžœœ›š™™˜—–••”““’‘‘ŽŒŠ†€{vqmkkkkkkkkjjjjjjjjiiiiin€‘ŽŽŽŽŒŒŒŒ‹Œ‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡†††††…………„„ƒ„ƒƒ‚ƒ‚‚€€€€~~~}}}}}|||{{{{{zzzyyyyxxxxwwwvvvvvuuuuuuttsssëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççççææåæååååäääãããããââââááã©§¥¢ ž›™—”’Ž‹‰‡…ƒ~{zxwutrqponmllkjjiiiiiiiiiijjkkllmnooprrtuvwxyz|}~€‚¢¡¡¢¢¢¡¢¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ª««¬¬­®¯¯±±²²´´µµ·¸¸¹º¼¾ÀÄÅÈÊÌÎÐÑÓÔÕÖ×××™–“‘Ž‹‰†„~|zwutqomkihfecba_^]]\[[ZZZZZZZYZZ[[[[\\†²±±±±±°°°°¯¯¯®®®®­­­¬¬¬¬¬¬¬««ª«ªªª©©©¨¨¨¨§§§¦§¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡  ¡       Ÿžžœ›šš™˜—–••”““’‘‘‘ŽŒ‹†|wrmkkkkkkkkkkjjjjjjjjiio€‘‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†††…†……„…„„„„ƒƒ‚‚‚‚€€€€~~~}~}}|}|||{{{{zzzzzyyyxyxxwwwwwvvvvuuuututtsëëëëëëëëëëëëëëëëëëëëëëëëëêêééééééèèèèèççææææææåååäääããããããâââáá㨦¤¡Ÿœš˜•“‘ŒŠˆ†„‚€~}{zxvusrqponmkkjiiiiiiiiiiijjkkklmnoopqrsuuvxyz{}}€ƒ¢¢¢¢¡¢¢¢¢¢¢£££¤¤¤¥¥¥¦§§¨¨©ª«««¬­­®¯°±²²³´µ¶¶¸¸¹º»½¿ÂÄÇÉÌÍÐÑÒÔÕÖ×××™˜”“Šˆ…ƒ€~{yvurpolkigedba`_^]\\[[ZZZZZZZZZZ[[[\\\š²²²±±±±±°°°¯¯®®®®®­­¬¬¬¬¬¬¬«¬«««ªªª©©¨¨©¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤£¤£££¢¢¢¡¡¡       ŸŸ››š™˜——––””“’’‘ŽŒŒ‡‚}xsmkkkkkkkkkkkkjjjjjjjsƒ’’‘’‘ŽŽŽŽŽŒŒŒ‹Œ‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††……………„„„„„ƒƒ‚‚‚€€€€€€~~~~~}}||}||{{{{{{zzzzyyyxxxxxwwwwvvvuuuuuttttëëëëëëëëëëëëëëëëëëëëëëëëêëëêééééééèèèçèççæææææåååååäääããããããââáߨ¥¢ ž›™—”’Œ‰‡…ƒ€~|zxwutsqponmmlkjjiiiiiiiiijjkkkllnoopqrrtuvwxz{|}€‚¢¢¢¢¢¢¢¢¢¢¢£££¤¤¥¥¥¦¦¦§¨¨©ªª««¬¬­®¯°°²²²´´µ¶·¸¸¹º¼¾ÁÄÆÉÊÍÏÐÒÓÕÖ××××™–“ŽŒ‰†„|zxvtqomljhgecba`_^]\\[[ZZZZZZZZZZ[[\\\\¯²²²±±±±°°°°¯¯¯¯¯®®®®­­¬¬¬¬¬¬««««ªªªª©©©©¨¨¨§§§§§¦¦¦¦¥¥¥¥¤¥¤££££££¢¢¢¡¡¡¡¡    Ÿ ž››šš˜˜—––•”““’‘‘ŽŽŽŒˆ‚}xsnlkkkkkkkkkkkkkjjjlzŠ““’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆ‡ˆˆ‡‡‡‡††††………„„„„„„ƒƒ‚‚‚‚‚€€€€€~~}}}}}|||{{{{{zz{zzyyyxxxxxwwwwvvvvuuuuuttëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèçççæææææåååäääãããããããâââ´¤¡Ÿš˜•“‘Šˆ†„‚}{zxvutrqponmllkjjjiiiiiiiijkklllmnopqrstuuwwy{{}~€‚„¢¢¢¢¢£¢¢££££££¤¤¥¥¦¦¦§¨¨©©«««¬¬­®¯¯°±²²³µµ¶·¸¸¹º»¾ÀÂÅÇÊÌÎÐÑÓÕÖÖ××ך˜•’‹ˆ†ƒ€~{ywusqomkigfdcba_^^\\\[[[[ZZZZZZ[[\\\\\q³²²²²±±±°°°°°°¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©©©¨¨¨§¨§§§¦¦¦¦¥¥¥¥¤¤¤¤¤££££¢¢¢¡¡ ¡¡      ŸŸœ›™˜——–•””“’’‘ŽŽŒˆƒ~ytollkkkkkkkkkkkkklx…““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡‡†††…………„„„ƒƒƒƒ‚‚‚‚€€€€~~~}}}}|}||{{{{{{zzzzyyyxyxwxwwvwwvvvuuuutuëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèççççæçææåååååäääãããããâââÄ£ ž›™—”“Œ‰‡†ƒ‚€~|{ywvusrqoonmllkjjjjiiiijjjjkklmnnoppqsstuvwyz{}~‚‚£¢¢¢¢¢£¢£££££¤¤¥¥¥¦¦¦§§¨¨©ª««¬¬­®¯¯°°²²³´µ¶·¸¸¹º»¼¾ÁÄÆÉËÍÏÐÒÔÕÖ××ØØ™–”‘ŽŒ‰‡„|{xvtrpnljhgedbb`_^]]\[[[[[ZZ[Z[[[[\\\]]ˆ³³²²²²²±±±°°°°°¯¯¯¯®®®®­­­¬¬¬¬¬«¬««ªªªªª©©©¨¨¨¨¨§¦§¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¡¡¡¡      ŸŸŸŸžœ™—–”““’‘‘ŽŒ‰„ztpllllllkkkkkkkq|‡“””””““““““’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠŠ‰‰ˆ‰ˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚‚‚€€€€~~~~~}}}}}|||{{{{{{zzyyyyyyxxwwwwvvvvvuuuutëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèçççççææææååäåäãããããããââÓ¡ š˜–“‘‹ˆ‡„‚}{yxwutsqqonmmlkkjjjjijjijjjklllmmoopqrstuvwxz{|~€‚„£¢¢¢¢¢£¢££££¤¤¥¥¥¥¦¦¦¨¨¨©ª««¬¬¬­®¯°°²²³´µ¶¶·¸¹º»»¾ÁÃÅÈÊÌÎÐÑÓÕÖ×ØØØš˜•’‹ˆ…‚~|ywusqomkihfdcba`_^]\\\[[[[[[[[[[[\\\\]] ³³²²²²²±±±±°°°°°¯¯¯¯®®®­­­¬¬¬¬¬¬««««ªªªª©©©©©¨¨¨¨§§¦¦¦¦¦¥¦¥¥¤¤¤¤¤££££¢¢¢¢¡¡      ŸŸŸŸžŸžžœš˜•“‘Š…{upllllllkkllt~†•••”””””“““““’“’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡†‡††……………„„„ƒƒƒ‚ƒ‚‚‚€€€€€~~~~~}|}}|||{{{{{zzzzyyyyxxxwwwwvvvvvuuuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéêéééééèèèççççæææååååäåäãäãããããã⦞œ™—•“ŽŒŠ‡†ƒ€~|{ywvutsqoonmmlkkjjjjiijjjjkkllmnnopqrstuvwxyz{}‚„£¢££££¢££££¤¤¤¤¥¥¦¦¦§§¨¨©©ª«¬¬¬­®¯°°±²³³´¶¶·¸¹¹º»½¿ÁÄÇÉËÍÐÑÓÔÖ××ØØØ™–”‘Œ‰‡…‚€}{xvtrpnljigedcb``^]]\\[[[[[[[[[[[\\\\]]e³³³³³²²²²±±±±°°°°¯¯¯¯¯®®­®­­­¬¬¬¬¬¬««ª«ªª©ª©©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤£¤££££¢¢¢¢¡¡¡    ŸŸŸŸŸžžžžš˜—•’ˆ‚|vrmnqsv{…Š‘————––•••••”””“““““’’’’’‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡ˆ‡‡‡‡‡†††………………„ƒ„ƒƒƒƒ‚‚‚€€€~~~}}}|||||{{{{{{zzzyyyyxxwxwwwwvvvvuuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççççææææåååääääããããã⸛™–“‘‹‰‡…ƒ}|zywutsrqonnnlkkkjjjjjjjjkjkllmnnooqqssuuwxxz{|~€ƒ„£¢£££¢££¤¤¤¤¤¤¥¥¦¦¦¦§¨¨©©ª«¬¬­­®®°°±²³³´´¶··¸¹º»¼¾ÁÄÆÈËÍÏÐÒÔÕÖ××ØØš™•“‹ˆ†ƒ~|zxusqomkihgecba`_^]]\\[[[[[[[[[[\\\]]]]´´³³³²²²²²²±±±±±°°°¯¯®®®®®­­­­¬¬¬¬¬¬¬««ªªªª©©©¨¨©¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤£££££¢¢¢¢¡¡¡     ŸŸŸŸžžžžœœœœ››››šš™™™™™™˜˜˜˜˜—————––––••””””““““““’’’’‘‘‘‘ŽŽŽŽŒŒŒ‹‹‹‹‹‹ŠŠŠ‰‰ˆˆˆˆˆˆ‡‡‡‡‡††††…………„„„ƒ„ƒƒƒ‚‚‚€€€~~~~}}}||||{{{{zzzzzyyyxxxwxwwwvwuvvuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééèèèèçèççæææææååååäääãããããÌœ™˜•“‘ŽŒŠˆ†„‚€~}{yxwussqpoonmllkkjjjjjijjkkllmmnopqqrstuvxxy{|}€‚„£££¢£¢££¤¤¤¤¤¤¥¥¦¦¦§§¨¨©ªªª«¬¬­­®¯°±²²³´´¶·¸¸¹º»¼¾¿ÃÅÇÊÌÎÐÒÓÕÖ××ØØØš—”’ŽŠ‡…‚}{ywuspomkihfdcba`_]]\\\\\[\[[[[\\\\]]]^^™´³³³³³³²²²²²±±±±±°¯¯¯¯¯®®®®­­¬­¬¬¬¬¬«««ªª«ªªª©©©¨¨¨§¨§§¦¦¦¦¦¦¦¥¥¤¥¤¤£££££¢¢¢¢¡¡¡¡    ŸŸŸŸžžžžžžœœœ›››››ššš™™™™™™˜˜˜˜————–––••••••”””“““““’’’’’‘‘‘ŽŽŽŽŒŒŒ‹‹Š‹ŠŠŠŠ‰‰ˆˆˆˆˆ‡‡‡‡‡‡††…………„„„„ƒƒƒƒƒ‚‚‚‚€€~~~}}}}}|{{{{{{{zzzzyyyyxxxwwwwwvvvuuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèçççææçæååæååääääããããâ™–”’‹‰‡…ƒ~|zywvusrqoonmmllkkjjkjjjkkklkmmnoopqqstuvwxyz|}~€‚ƒ…££££££££¤¤¤¥¤¥¥¦¦¦§§§¨©©ª««¬¬­­®¯°°²²³´´µ¶·¸¸¹º»¼¿ÂÄÇÉËÍÏÑÓÔÖ×רÙÙ›˜–“Ž‹‰†ƒ~}zwusqonkjigedcaa_^^]]\\\\\\\\\\\\\\]]^^a´µ´´´³³³³²²²²²±±±±°°°¯¯¯¯¯®®®®­­¬¬¬¬¬¬¬«««««ªª©©©¨¨¨¨¨§§§§§¦¦¦¦¥¥¥¤¥¤¤¤££££¢¢¡¢¡¡¡       ŸŸžŸžžœœœœ›››ššššš™™™™™˜™˜˜——–—––––••••””””““““““’’’’‘‘‘‘ŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡††††………„…„„ƒƒƒƒƒ‚‚‚€€€€~~~}~}}||||{{{{{zzzyyyyxxxxwwwwwvvvuëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëëêêêééééèéèèèçççææææåååååääãäãã㳘•“‘ŽŠˆ†„‚}{zxvutsqpoonmllkkkkjjjkkkkkllmnnoopqssuuwxyz{|~€ƒ„££££££££¤¤¤¤¥¥¥¦¦¦§§§¨¨©ªªª«¬­­®¯°±±²³³´µ¶·¸¸¹º»¼½ÀÃÅÈÊÌÏÐÒÓÕÖרØÙÙš˜”’Ї…‚€~{ywusqomjihfecba`__]]]\\\\\\[\\\\\\]]^^^~µµ´´´´³³³³³²²²±²±±±±±°°¯¯¯®®®®­­­¬¬¬¬¬¬¬««««ªªª©ª©©©¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¢¡¡¡     ŸŸŸžžžžžœœœœœœ››››ššš™™™™˜™˜˜˜——–—–––––••••””““““““’’’’‘‘‘ŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚€€€€~~~}}}}}|||||{{{{{zzzzyyyyxxxxwwwvvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççææææææååååäääãããË—”’ދЇ†ƒ€~{{ywvutrqpoonmmlllkkkkkkkkkllmnnooqqrrtuvxyy{|}~€‚„…¤¤£££¤£¤¤¤¥¥¥¥¥¦¦§§§¨¨©©ª«¬¬¬­®®¯°±²²³´µ¶·¸¸¹º»¼¼¿ÁÄÇÉËÎÐÑÓÕÖ×ØØØÙ›˜–“ŽŒˆ‡„}zxvtqonljigfdcb`__^^]\\\\\\\\\\\\\\]^^^_šµµµ´´´³³³³³³²²²²±±±±°°°¯°¯¯¯®®®­­­¬¬¬¬¬¬«««««ªª©©ª©©©©¨¨§§§§§¦¦¦¦¦¥¥¥¥¤¤£¤££¢¢¢¢¡¡¡¡¡      ŸŸžžžžžœœœœœ›››šššš™™™™™˜˜˜—————–––••••”•”””““““““’’‘’‘‘‘ŽŽŽŽŒŒŒ‹‹Œ‹ŠŠŠŠŠ‰‰‰‰‰ˆˆ‡‡‡‡‡‡††††………„…„„ƒƒƒƒƒ‚‚‚‚€€€€~}}}}|}||{{{{{{zzzzyyyxyxxwxwwwvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèçççæææææååååäääãã㜓‘Œ‹‰‡…ƒ}{zywvtsqqpoonmmlkkkkkkkkkklllmnoopqrstuvwxyz{}~€ƒ„£¤£¤££¤¤¤¤¥¥¥¥¦¦¦¦¦§¨¨©©ª««¬¬­®®¯°±²²³´µµ¶·¸¹º»»¼¾ÀÃÅÈËÌÏÐÒÔÕרØÙØÙ𗕒Ї…ƒ€}{ywusqomkihfedbb`_^^]]]\\\\\\\\\\\]]]^__g¶µµµ´´´´´³³³³³²²²±±±±°°°°¯¯¯¯¯®®®­­­­¬¬¬¬¬¬¬«««ªªª©©©©¨¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡¡     Ÿ ŸŸžžžžžžœœœœœ›››››šš™š™™™™™˜˜———–——––•–•••””””““““’’’’’’‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„„ƒƒƒƒ‚‚‚‚€€€€€~~}}}}}|||{|{{{{{zzzzyyyxxxxwwwvvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééèèèèèççççæææååååäääããã·“ŽŒŠ‡†„€~|{zxvutsrqoonnmmlllkkkklkkllmmnoopqqrtuuwxyz{}~€ƒ„…¤¤¤¤¤¤¤¤¤¥¥¥¦¥¦¦¦§§¨¨©©ªª¬¬¬­­®¯°°²²²³µµ¶·¸¸¹º»¼½¿ÂÄÇÊÌÎÐÑÓÕÖרÙÙÙ›™–“‘ŽŒ‰‡„}{xvtrpnljigfecba`_^]]]\\\\\\\\\\]]]]^^_`†¶¶¶µµ´´´³´³³³³²²²²²±±±±°°°°¯¯¯®®®®®­­¬¬¬¬¬¬«¬««ªªªªª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¦¥¥¤¤¤¤££££¢¢¢¢¢¡¡¡     ŸŸŸŸžžžžžœœœœ››š›šššš™™™™˜˜˜————–––––••••””””““““’’’’‘’‘‘‘ŽŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰ˆ‰‰ˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒ‚‚‚‚€€€~~~~~}}}}|||{{{{{{{zzyyyyxxxwxwwvëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèéèèçççææææåæåääääääÓ‘‹‰‡…ƒ}|zxwvusrqpoonmmlklkkkkkllllmmnooppqrstuvwyz{|}~€‚„…¤¤¤¤¤¤¤¤¥¤¥¥¥¦¦¦¦¦§¨¨©©ª««¬¬­­®®°±±²²³´µ¶·¸¸¹º»¼½¾ÁÄÆÉËÍÐÑÓÕÖרÙÙÚÙš˜•“Šˆ…ƒ~|zwusqomljhgedbba`_^^]]\\\\\\\\\\]]]^^__`¥¶¶¶¶µµ´´´´´´³³³³²²²²²±±°±°°°°¯¯®¯®®®­­­¬¬¬¬¬«¬««ªªªª©©©©©©¨¨§§§§¦¦¦¦¦¦¥¥¥¥¤¤¤££££¢¢¢¢¡¡¡¡    ŸŸ ŸŸžŸžžžœœœœ›››šššš™™™™™™˜˜˜—————–––•••••”””“““““’’’‘‘‘‘‘ŽŽŽŒŒŒ‹‹‹‹Š‹ŠŠŠŠ‰‰‰ˆˆˆˆˆ‡‡‡‡‡†††…………„„„„ƒƒƒƒ‚‚‚‚€€€€~~}}}}}|||{{{{{z{zzyyyyxxxxwwwëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééèèèèçççæææåååååååäã㤎ŒŠˆ†„‚~}{zxwutsrppoonmllkkkkllllllmmnooopqrstuvwxy{{}~‚ƒ„†¤¤¤¤¤¤¥¤¥¤¥¦¦¦¦¦§§¨¨©©ªª«¬¬¬­®¯¯°±²³³´µ¶¶·¸¹º»¼½½ÀÃÅÈÊÌÏÑÒÔÖרÙÙÚÚœ™–“‘ދЇ„‚€}{yvtrpnmkigfecbb``^^]]\]\\\\\\\]]]]^^_``u···¶¶µµµ´µ´´³´³³²²²²²²±±±±°°°¯°¯¯¯®®®­­­­­¬¬¬¬¬««««ªªª©©©¨©©¨§§¨§§¦¦¦¦¦¦¥¥¥¤¤¤¤£££¢¢¢¢¢¡¡¡      ŸŸŸŸžžžœœ›œ››››ššš™™™™™˜˜˜˜—————––•••••”””“““““““’’‘‘‘‘‘‘ŽŽŽŽŽŒŒŒ‹‹‹‹ŠŠŠŠ‰Š‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„„„ƒƒƒƒ‚‚‚€€€~~}}}}}}|||{{{{{zzzzyyyxyxxxxwëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèéèèçççæææææåååäääãÄ‹ˆ‡…ƒ€~|{ywvutrrqooommlllkkklllllmmnnoopqrstuuwwyz{|}€ƒ„†¤¥¤¥¥¥¤¤¤¥¥¦¦¦¦¦¦§§¨©©ªª«¬¬¬­®¯¯°±²²²³µ¶¶·¸¸¹º¼¼½¿ÁÄÆÉËÎÐÒÓÕÖרÙÚÙš˜•“‹ˆ…ƒ|zwusqomljhgfdcba``_^]\\\\\\]\]]]]]^^^_``–···¶¶¶µµµ´´µ´´³³³³³²²²²²±±°°±°¯¯¯¯¯®®®®­­­­¬¬¬¬««««ªªªª©©©©¨©¨¨§§§¦¦¦¦¦¦¥¥¥¥¥¥¤¤¤£££¢¢¢¢¡¡¡¡¡     ŸŸŸŸžžžœœœ›››››šššš™™™™˜˜˜˜————––––••••”””””““““’’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡‡‡†…†…………„„„„ƒƒƒƒ‚‚‚€€€€~~~~}}}}||||{{{{zzzyyzyxxxxwxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèçççççææææååååää䕊ˆ†…‚}{zxwvusrqpoonmmmlllkkllllmmmnnopqrrstuvwxz{|}€‚ƒ…†¥¥¥¥¥¥¥¥¥¥¥¦¦¦¦¦§§¨¨©ªª«¬¬¬­­¯¯°±±²³³´µ¶·¸¹ºº»¼½¾ÁÃÅÈÊÌÏÑÒÔÖרÙÙÚÚ›™–”‘Œ‰‡…‚€}{yvusqomkihgecbba``_^]]\\\]\]\]]]]^^^_``i·····¶¶µµµµµµ´´´³³³³³²²²²±±±°°°°°°¯¯¯¯®®®­­­¬¬¬¬¬¬¬«««ªª©©©©©©¨¨¨¨§§§§¦¦¦¦¥¥¥¥¥¤¤¤£££¢¢¢¢¢¢¡¡ ¡    ŸŸŸŸŸžžœœœœœ››š›šš™š™™™™˜˜˜—————––––••••””””““““““’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„„„ƒƒ‚ƒ‚‚‚‚€€€~~~~~}|}}|||{{{{{zzzzyyyyxxxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèçèççççææååååää䶉‡…„€~}{yxvutsrqpoomnmlllllllllmmnnooppqrttuvwxy{{}~‚ƒ…†¥¥¥¥¥¥¥¥¥¥¥¦¦¦§¦§¨¨¨¨ªªª«¬¬­­®¯°°±²³³´µ¶·¸¸¹º»¼½½¿ÂÄÇÊËÎÐÒÓÕ××ÙÚÚÚ›˜•’‹ˆ†ƒ~|zxvtronljihfdcbba`_^]]]]\]]]]]]]]^^^^``a‹¸·····¶¶¶µµµµ´´´´³³³³³²²²²²±±±±±°°¯°¯¯®®®­­­­¬¬¬¬¬¬¬«««ªªªª©©©©©¨¨¨¨§§¦¦¦¦¦¥¥¥¥¤¥¤¤££££££¢¢¢¡¡¡      ŸŸŸžŸžžžœœœ›››››šššš™™™™˜˜˜˜————–––––••”•””“““““’’’’’‘‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡†‡†††………„„„„ƒƒƒƒ‚‚‚€€€~~~}}~}|}}|{|{{{{{zzzzyyyxxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèèççææææææåååå܈‡„ƒ}|zywvutrrpponmmllllllllmmmmnnoppqrrtuuwxy{{|}€ƒ„…‡¦¥¥¥¥¥¥¦¥¦¦¦¦¦§§¨¨¨©ªª««¬¬¬­®¯¯°±±²³´µµ··¸¸º»»¼¾¾ÁÃÆÈÊÍÏÑÓÔÖרÙÚÛÚœ™—”’ŒŠ‡…‚€~{ywusqomlihgedcbaa__^]]]]]]]]]]^^^^___aaa¯¸¸¸····¶¶¶¶µµµµ´´´´³³³²²²²²±±±±±°°¯°¯®¯¯®®­®­¬¬¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¦¥¥¥¥¤¤¤£££¢¢¢¢¢¢¡¡¡      ŸŸŸŸžžžœœœœœ›››š›ššš™™™™™˜™˜—˜˜—–—––•••”””””“““““’’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡†††…††…„…„„„ƒƒƒ‚‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèçççæææææåååä䮆ƒ‚€}{zxwutsrqpoonnmmmllllllmmmnnoopqrrtuuvxxy{|}€‚„…‡¥¥¥¥¥¥¥¥¦¦¦¦¦¦§§¨¨¨©©ªªª«¬­­­®°°±²²³´´µ¶·¸¹º»»¼½¾ÀÂÄÇÊÌÎÐÒÔÖרÙÚÚÚž›˜–“‹ˆ†„|zxvtqonmkihfecbba`_^^^]]]]]]]]^^^^____`b†¸¸¸¸¸¸··¶·¶¶¶¶µµµ´´´´³³³³²²²²±±±±°°°°¯¯¯¯¯®®®­­­¬¬¬¬¬¬«««««ªªª©©©¨¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¤¥¤¤£££££¢¢¢¡¡¡¡¡    ŸŸŸŸŸžžžœœœœœ›››››šš™™™™™™™˜˜˜˜———–––––•••””””“““““’’’’’’‘‘ŽŽŽŒŒŒŒŒ‹‹‹‹Š‰Š‰‰ˆˆˆˆ‡‡‡‡‡‡††††………„…„„„„ƒƒƒ‚‚€€€~~~}}}}}|||{{{{{{zzzzzyxxëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêééééèèèèèçççææææååååÖ…ƒ~|zyxvutsrqpoonmmmmlllmlmmnnooooqrrstuvwxyz{|~€ƒ„…‡¥¥¥¥¦¥¦¥¦¦¦¦¦§§§¨¨©©ªª««¬¬­®®¯°±²²²´µµ¶·¸¸¹º¼¼½¾¾ÁÄÇÉËÎÐÒÓÕרÙÚÚÛÛš—”’Ї…‚€~{zwusqonkjigfdcbaa___^]]]]]]]^^^^^^__``ab«¸¸¸¸¸······¶µµµµµ´´´´´´³³²²²²²²²±±°±°°¯¯¯¯¯®®­­­­¬¬¬¬¬¬««««ªªª©©©©¨©¨¨§§§¦§¦¦¦¦¦¥¥¥¤¤¤¤¤££¢¢£¢¢¢¡¡¡¡    ŸŸŸŸžžžžœœœœ››šššš™š™™™™™˜˜˜˜˜———–––•••••””””““““’’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰‰ˆˆ‡‡‡‡‡‡†††…†…„…„„„„ƒƒƒ‚‚‚€€€€€~~~~}}}}}|||{{{{{zzzzyyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççççæææååå媂€}{zywuutsrqooonnmmmmmmlmmnnnoopqqrstuvvxyz{|}~€‚……‡¥¥¦¥¦¦¥¦¦¦¦¦¦¦§§¨¨©©ªª«¬¬¬­­®¯°±±²²³´µ¶¶¸¸¹¹»¼¼½¾ÀÃÅÇÊÌÏÑÓÔÖרÙÚÛÛž›™–“‘Œ‰‡„‚}{xvtrpomkihfedcbb`__^^^]]]]]]^^^____``ab„¹¹¹¸¸¸¸¸····¶¶¶µµµ´´´´´´³³³³²²²²±±±±°°°¯°¯¯¯¯®®­­­¬­¬¬¬¬¬«««ªªªª©©©©©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¥¤¤£¤£££¢£¢¢¡¡¡¡     ŸŸŸŸŸžžžœœœœœœ››››šššš™™™™˜˜˜˜——˜——––••••”•”””“““““’“’’‘‘‘‘ŽŽŽŒŒŒŒ‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††††……„„„„ƒƒƒƒƒ‚‚‚€€€€~~~}}}}}|||{{{{{{{zzyyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééèèèçèççææææååæåÕ€~{{yxwutsrqpooonmmmmmlmmmmnoooopqrrsuuvxyz{|}~€‚ƒ…†¦¦¥¥¦¦¦¦¦¦¦¦¦¦§¨¨¨©©©ª««¬¬­­®¯¯°±²²³´µµ¶·¸¹ºº¼¼½¾¿ÂÄÇÉËÎÐÒÔÖרÙÚÛÛÛš˜”’Šˆ…ƒ~|zwusqonljigfedbb```_^^]]]]^]]^^____``abb¬º¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´´´³³³³²²²²²±±±±±°°¯¯¯¯¯®®®®­­­¬¬¬¬¬«««ª«ªª©©©¨©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢£¢¡¢¡¡¡      ŸŸŸžžžžžœœœ››››ššš™š™™™™˜˜˜˜————–—–––•••”””””“““““’’’‘‘‘‘‘ŽŽŽŽŽŒŒŒŒ‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒƒƒ‚‚‚‚€€€~~~~}}|}|||{{{{{{zzzyyëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèççççææææååå«}{zywvutsrqpoonnnmmmmmmmmnoooopqrrstuvwxyz{}~ƒ„†‡¦¦¦¦¦¦¦¦¦¦§¦¦§¨¨¨¨©©ª««¬¬¬®®®¯°±²²³³´¶¶·¸¸¹º»¼½¾¾ÀÄÅÈÊÍÏÑÓÕÖ×ÙÚÛÛÛžœ™–“‘ŽŒ‰‡„‚}{yvuspomkjigedcbaa___^^^^^^^^^____``aaab‡º¹¹¹¹¹¹¸¸¸¸···¶¶¶¶¶¶µµµ´´´´´³³²²²²²²±±±°°°°¯¯¯¯®®­®®­­¬¬¬¬¬¬««««ªªª©©ª©©¨¨¨¨¨§§¦¦¦¦¦¦¥¥¥¥¥¤¤¤£££¢£¢¢¡¡¡¡       ŸŸŸžžžœœ››››››šš™™™™™™˜˜˜˜——–—–––•••••””“““““““’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹‹ŠŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡††††…………„„ƒƒƒƒƒ‚‚‚€€~~~}}}}}|||{{{{{zzzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêéééééèèèèççççææææåÚ|{zxwuusrqpooonnmnnmmmmnmooooppqrstuvwxyz{|}€‚„…‡¦¦¦¦¦¦¦¦¦¦¦§¦§§¨¨©©©ªª«¬¬­­®¯¯°°±²²³´µ¶·¸¸¹º»¼½¾¾ÀÂÅÇÊÌÎÐÒÕÖרÚÚÛÜÜš˜•“‹ˆ…ƒ~|zxvtqpnljihgedcba``___^^^^^^^^____``aabb°ºººº¹¹¹¸¸¸¸¸····¶¶¶µµµµ´µ´³´³³³²²²²²²±±±±°°¯¯°¯¯®®®®­­­­¬¬¬¬¬«¬««ªªª©©©¨©¨¨¨¨¨§§§§¦¦¦¦¥¥¥¥¤¤¤£¤£££¢¢¢¡¡¡¡      ŸŸŸžžžžœœ›œ››››ššš™™™™˜™˜˜˜————–––••••””””“““““’’’’‘‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡†‡†††…………„„„„ƒƒƒ‚‚‚€€€€}~}}}|}|||{{{{{zzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèççççæææææå°|{ywvuusrqpooonnnmnmmmmnnoooppqrstuuvwxz{{}~€‚ƒ…†‡¦¦¦¦¦¦¦¦¦§§§§¨¨©©©©ªª«¬¬¬­­¯¯°°²²²³´µ¶¶·¸¹ºº¼½½¾¿ÁÄÆÉËÎÐÑÔÖרÙÚÛÜÜžœ™–“’Œ‰‡„‚€}{ywurqomkjhgfedbba``__^^^_^^^^^___``aaabººººº¹¹¹¸¸¸¸¸¸····¶¶¶¶¶µ´´µ´´³³³²³²²²²²±±±±±°°¯¯¯¯®®®®­­­­¬¬¬¬¬««««ªªªªª©©©©¨¨¨§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££¢¢¢¡¡¡¡¡¡     ŸŸŸŸžžžœœœœœœ›››šš™š™™™™™™˜˜˜————–––•–•••”””““““““’’’‘’‘‘ŽŽŽŽŽŒŒ‹‹‹‹ŠŠŠŠŠ‰‰‰ˆˆˆˆ‡‡‡‡‡††††………„„„„„ƒƒ‚‚‚‚‚‚€€€~~~}~}}}||||{{{{zzzëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêéééééèèèçççççæææåã‡zywvutrqqpoooonnnnmnnnnnooppqrsttuvwxy{{}~€ƒ„†‡¦¦¦¦¦¦¦¦§¦§§§§¨¨©©ªªª«¬¬¬­®®¯¯°±²²³´´µ¶¸¸¹ºº¼¼½¾¾ÀÃÅÇÊÌÏÑÒÕרÙÚÚÛÜÜš˜•“‹ˆ†ƒ|zxvtqpnlkihgedcbaa``__^^^^^^____```aabbm»»»»ººº¹¹¹¹¸¸¸¸¸¸···¶¶¶¶µµ´µ´´´³³³³³²²²²²±±±±°°°°¯¯®¯®®®®­­­¬¬¬¬¬««««ªªªª©©©©¨¨¨¨§§§¦¦¦¦¦¥¦¥¥¥¤¤¤£££££¢¢¢¢¡¡¡¡     ŸŸŸŸŸžžœœœœ››š›šššš™™™™™™˜˜˜˜——–––––••••””“”“““““’’’‘‘‘‘ŽŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††……………„…„„ƒƒƒ‚ƒ‚‚€~~~}}}|}}|||{{{{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèçççççææææ½ywwuttsrqpooonnnnnnnnnnooppqqrstuvwxyz{|~€ƒƒ…†‡¦¦¦¦¦¦¦§§§§§§¨¨¨©ªª«««¬¬¬­®¯°°±±²³³´µ··¸¸¹º»¼½¾¾¿ÂÄÇÉËÎÐÒÔÖ×ÙÚÛÛÜÜŸœ™—”‘Œ‰‡…‚€}{ywusqonljigfedbba```___^_^^____```aaabb™»¼»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´´´´´³³³²²²²±±±±±°°°°¯¯¯®®®®­­­­¬¬¬¬¬««««ªªª©©©©¨¨¨¨¨§§§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££¢¢¢¢¢¡¡¡¡     ŸŸŸŸžžžžžœœ››››šššš™™™™™˜˜˜˜˜————––•–•””””””“““““’’’‘‘‘ŽŽŽŽŒŒŒŒŒŒ‹‹‹‹ŠŠŠ‰‰‰‰‰ˆˆ‡‡‡‡‡‡†††…………„„„„„ƒƒƒƒ‚‚‚‚€€€€~~~~}}||||{{{{{zëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêééééèèèèèçççæçæææ—wvutsrqqpooonnnnnnnnnooopqrrstuuwwxy{|}~€‚ƒ…†‡§¦¦¦¦¦¦¦§§§§¨¨¨¨©ªªªª¬¬¬­­®¯¯°±²²³³´µ¶··¸¹º»¼¼¾¾¿ÀÃÆÈËÍÏÑÓÕרÙÚÛÛÜÜžš˜–“Ž‹‰†„|{xvtrpomkihgfdcbba``____^_______``aaabb{¼¼»»»»ººººº¹¹¹¸¸¸¸¸·····¶¶¶¶µµµ´´´´³³³²²²²²²±±±±°°°°°¯¯®®®®®­­­¬¬¬¬¬¬«««ªªªª©©©¨©¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤££££¢¢¢¢¡¡¡      ŸŸŸŸžžžžœœœœ›››šššš™™™™™™˜˜˜˜————––•–•••”””””“““““’’’’’‘‘ŽŽŽŽŽŽŒŒŒŒ‹‹‹‹‹Š‰Š‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††…………„…„„„ƒƒ‚‚‚‚‚‚€€€€€~~~}}}}|||{{{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêééééèèèèçèçççççæÐwvussrqppooooonnnnnoooppqqrstuuuwxy{{}~ƒ„…‡ˆ¦§¦¦¦§¦¦§¨§¨¨¨¨¨©ªª««¬¬­­®¯¯°±±²³´´µ¶¶¸¸¸ºº»¼½¾¿ÀÂÄÇÊÌÎÐÒÔÖ×ÙÚÛÜÜÝŸœš—”’Ї…‚€~{zwvsqonljihfedcbaa```___________`aaabbbª¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶¶µµ´´µ³³´³³³³²²²²±±±±±°°¯¯¯¯®®®®®­­­¬¬¬¬¬¬««««ªªª©©©¨¨¨¨¨§§¦§¦¦¦¦¦¥¦¥¥¥¤¤¤¤£¢£¢¢¢¡¢¡¡     ŸŸŸŸžŸžžžœœœœ››››ššššš™™™™˜˜˜—˜˜—––––•••••”””““““““’’’’‘‘ŽŽŽŽŽŒŒŒŒŒ‹‹‹Š‹ŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡††††……„„„„ƒƒ„ƒƒ‚‚‚€€€~~~}}}}}||||{{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêéééééèèèççççææææ«vtssrqpoooooonooooooopqqrsstuvwxyz{|}€ƒ„…‡ˆ§§§§¦§§¦§§¨¨¨¨¨©©ªª««¬¬­­®®¯°±±²²³´´µ¶·¸¸¹»¼¼½¾¾¿ÁÄÆÈËÎÐÑÔÕ×ÙÚÚÛÜÜÝž›˜–“‘Ž‹ˆ†„}{yvtrqomkjigfedbbaa``___`_____`_`aaabbb½½¼¼¼¼¼»»»ºººº¹¹¹¹¸¸¸¸·····¶¶¶µµµ´´´´³´³³³²²²²²²±±±±°°¯¯¯¯¯¯®®­­­¬­¬¬¬¬««««ªªª©ª©©©¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¤¥¤¤¤££¢££¢¢¡¡¡¡ ¡    ŸŸŸŸžžžžœœœœ›œ››››ššš™™™™™˜˜˜˜————––•–•••”””””“““““’’’’’‘‘‘‘ŽŽŽŽŒŒŒŒŒ‹‹ŠŠ‹ŠŠ‰‰‰ˆˆˆˆˆˆ‡‡‡‡†††…†……„…„„„ƒƒƒ‚‚‚‚€€€€~~}~}}}||||{{ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêééééééèèèèççççæææŠtsrrqpoooooonooooooppqqrstuuvwyy{|}~€‚ƒ„…‡ˆ§§§§§§§§§¨¨¨©¨©©ªªª«¬¬¬­®®®°±±²²³´´µ¶·¸¸¹º»¼¼¾¾¿ÀÂÅÈÊÌÏÐÓÕ××ÙÚÛÜÜÝ œš—•’Šˆ…‚€~|zxusronlkihgfdcbbaa````__`___````abbbbt½½½¼¼¼¼¼»»»ººººº¹¹¹¸¸¸¸¸····¶¶¶¶µµµ´´´´´³³³³²²²²²²±±±°°°°¯¯®®®®­®­­­¬¬¬¬¬¬«««ªªªªª©©©¨¨¨¨§§§¦§¦¦¦¦¥¥¥¤¤¤¤¤££¢£¢¢¢¡¡¡¡¡¡    ŸŸŸŸžžžœœœœ››››ššš™™™™™˜˜˜˜————––––••••”””“““““’’’’‘‘‘‘‘ŽŽŽŽŒŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰ˆˆˆ‡‡‡‡‡‡†††…………„„„ƒƒƒƒƒ‚‚‚‚€€€€~~~}}}}|}||||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêéééééééèèèèçççææÉssqqpppoooooooooooppqqrssuuvwxyz|}~€‚……‡ˆ§§§§§§§§§§¨¨¨©©©©ª««¬¬¬¬­¯¯¯°±±²³´´µ¶·¸¸¹ºº»¼¾¾¿ÀÁÄÆÉËÍÐÒÔÖ×ÙÚÛÜÝÝÝž›˜–“‘ŽŒ‰‡„‚€}{ywtspomljihgedcbbaa``````___``a`abbbbc§¾½½½½¼¼¼¼»»»ºººººº¹¹¸¸¸¸¸¸···¶¶¶¶¶µµ´´´´³³³³²²²²²²±±±±±°°°°¯¯®®®­­­­¬­¬¬¬¬¬¬«««ªªª©©©¨¨¨¨¨¨§§§¦¦¦¦¦¥¥¥¥¤¥¤£££££¢£¢¢¡¡¡      ŸŸŸŸžžžœœœœœ›››šššš™™™™™˜˜˜—˜———––––••••”””“““““““’’’’‘‘‘ŽŽŽŽŒŒŒŒ‹Œ‹‹‹ŠŠ‰Š‰‰‰‰ˆˆˆˆ‡‡‡‡†††…………„…„„„ƒƒƒ‚‚‚‚‚€€€€€~~~~}~}}}||||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêëêêêêéééééèèççççççæªsqqppoooooooooooppqqrsttuvwxzz{|~€‚„…†ˆ‰¨§§§§§§§¨¨¨¨©©©©ªª«¬¬¬¬­®®¯°±²²²³´µ¶··¸¹¹º»¼½¾¿¿ÀÃÅÈÊÍÏÑÔÕרÚÛÜÝÝÝ š—•’Šˆ…ƒ~|zxutrpomkihgeddbbbba``````_````aaabbbc޾¾¾½½½¼¼¼¼¼»»»»ºººº¹¹¹¸¸¸¸¸·¸··¶¶¶¶µµµµ´´´³´³³²²²²²²±±±±±°°¯°¯¯¯®®­­®­¬¬¬¬¬¬¬«¬«ª«ªª©©©©¨¨¨§§§§§§¦¦¦¦¥¥¥¥¤¤¤¤¤£££¢¢¢¢¡¡       ŸŸŸŸžžžœœœœ››š›ššš™™™™™™™˜—˜——–––––•••••”””““““““’’’‘‘ŽŽŽŽŒ‹‹‹‹‹ŠŠŠŠ‰‰‰‰ˆˆˆˆ‡‡‡‡‡†††††……„„„„„ƒƒƒ‚ƒ‚‚‚€€€~~~~~}}}||ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëêêêêêééééèèèèççççæærqpoooooooooooopqqrsstuvwxyz{|}~ƒ…†‡ˆ¨¨¨§§§¨§§¨¨©©©©ªªª«¬¬¬¬­®®¯°°±²²³´´µ¶·¸¸¹º»¼½¾¾¿ÀÁÄÇÊÌÎÐÓÔרÙÚÜÜÝÝÝž›™—“‘ŽŒ‰‡„‚€~{ywusqonljihfedcbbba``````````a`aabbbby¾¾¾¾¾½¾½½¼¼¼¼»»º»ºººº¹¸¸¹¸¸¸¸¸···¶¶¶¶µ¶µ´µ´´´´³³³²²²²²²±±±°°°°¯°®®®®®®­­­­¬¬¬¬¬¬«««ªªª©©©©¨¨¨§¨§§§¦¦¦¦¦¥¥¥¥¤¤¤¤£££££¢¢¢¡¡¡¡      ŸŸžžžžžœœœœœœ››šššš™™™™™˜˜˜˜—————–•–••••”””““““““’’’‘‘’‘‘‘ŽŽŽŒŒŒŒ‹‹‹‹‹ŠŠŠ‰‰‰‰ˆˆˆ‡‡‡‡‡‡†‡††………„„„„„„ƒ‚‚‚‚‚€€€€~~~}}}}}}|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‡‰Š‰‡…‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|sjdadjs|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„‰“””””ˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~veHBBBEi€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŠ€€€€€€€€ƒ“”””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~gNa€€€€€€€€xEBBBa€€€€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰””Š€€…ŠŠ‡€€€”””Š€€€€Š‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~fRBBa€€qabl€€€YBBBa€€€€dd~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡’””””Š€€Š”””ˆ€€Š”””Š€€…€‚“’†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lJBBBBa€€aBBBg€€`BBBa€€r€|FJm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€””””””Š€€Š”””ˆ€€”””Š€€Š†€Š””ˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~XBBBBBBa€€aBBBh€€ZBBBa€€ap€bBBh€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””Š€€…ŠŠ‡€€‚“”””Š€€Š‘€“”Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBa€€qabl€€yFBBBa€€aO€}HBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””Š€€€€€€€…’””””Š€€Š”‡€‰”Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBa€€€€€€€rJBBBBa€€aBk€eBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ŠŠŠŠŠŠ”””Š€€€€€€€Ž””””Š€€Š”’’Š€€‰„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€aaaaabBBBa€€€€€€€WBBBBa€€aBK~Ja€€et€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””Š€€Š”’ˆ€€‚“”””Š€€Š””‰€ˆŠ€€Š’ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€BBBBBBBBBa€€aCJj€€|FBBBa€€aBBf€ha€€aHy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””Š€€Š”””„€€‹”””Š€€Š””“ˆ€€Š”‘€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€BBBBBBBBBa€€aBBCv€€_BBBa€€aBCH}k€€aBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ŠŠŠŠŠŠ””Š€€Š”””€€‚”””Š€€Š”””Š€€€€Š””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€X€€€aaaaaaRBBa€€aBBBZ€€yCBBa€€aBBBa€€€€aBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡”€€€€€€€€€Š””Š€€Š””””‚€€Ž””Š€€Š”””“‚€€€Š””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€mB€€€€€€€€€aBBa€€aBBBD{€€WBBa€€aBBBE{€€€aBBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’”€€€€€€€€€Š””Š€€Š””””Š€€…””Š€€Š””””Œ€€€Š””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~JB€€€€€€€€€aBBa€€aBBBBa€€sBBa€€aBBBB\€€€aBBFZ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“”””””””””””””””””””””””””””””””””””””””””””””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~GBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBG~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””””””””””””””””‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€gBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBg€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘”””””””””””””””””””””””””””””””””””””””””””””””€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€NBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ”””””””””””””””””””””””””””””””””””””””””””””””””ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBCBBBBBBBBBBBCBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž”””””””””””””””””””””””””””””””””””””””””””””””””Ž€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€VBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€””””””””””””””””””””””””””””””””””””””””””””””””””“€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€EBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBE€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”””””””””””””””””””””””””””””””””””””””””””””””””””‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|BCBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBCBBBBBBB|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…”””””””””””””””””””””””””””””””””””””””””””””””””””„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~xqjebbejqx~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBCs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ„…………„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡”””””””””””””””””””””””””””””””””””””””””””””””””””‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|k_TEBBBBBBBBBBBBET_k|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€jBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„†‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡†„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””””‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€v_NBBBBBBBBBBBBBBBBBBBBBBN_v€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚†ˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ†‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š”””””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}eLBBBBBBBBBBBBBBBBBBBBBBBBBBBBLd}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€aBBBBBBBBBCBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””””””””””””””””””””Š€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€y\CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBC\y€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBd€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ”””””””””””””””””””””””””””””””””””””””””””””””””””‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{ZBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB[{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€jBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…”””””””””””””””””””””””””””””””””””””””””””””””””””…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sBBBBBBBBBCBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”””””””””””””””””””””””””””””””””””””””””””””””””””‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sLBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBLs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|BBBBBBBBBBBBBCBBBBBBBBBBBBBBCBBBBBBBBBBBBCBBBBBBBBB|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””””””””””””””””””””””””””””””””””””””””””””””””””€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€bBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€EBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBCBBBBBBBE€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž”””””””””””””””””””””””””””””””””””””””””””””””””Ž€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{PBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBO|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€VBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š””””””””””””””””””””””””””””””””””””””””””””””“’‘‚vvwy{~€€€€€€€€€€€€€€€€€€€€€€vJBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBJu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€cBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCEGJMyŸž›•ˆƒ€€€€€€€€€€€€€€€€€€€€€€‚‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ””””””””””””””””””””””””””””””””””””””””’‹‹‹‹‹pkkkkkkklqux~€€€€€€€€€€€€€€€€oEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBEo€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCHPTZ^^^^^²¾¾¾¾¾¾¾»­¢˜†€€€€€€€€€€€€€€€€ƒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘””””””””””””””””””””””””””””””””””””Ž‹‹‹‹‹‹‹‹‹…kkkkkkkkkkkkknt{€€€€€€€€€€€€mBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBEOV^^^^^^^^^q¾¾¾¾¾¾¾¾¾¾¾¾¾¸£‘€€€€€€€€€€€„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰”””””””””””””””””””””””””””””””””‘‹‹‹‹‹‹‹‹‹‹‹‹xkkkkkkkkkkkkkkkkmt|€€€€€€€€mBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€gBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCNW^^^^^^^^^^^^—¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¤Œ€€€€€€€€„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘–šŸ¤¨©¨¥¢ žœ—•”””””””””””””””””””””””“Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‰lkkkkkkkkkkkkkkkkkkkpy€€€€€rCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCr€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡•œ¡¨®²´°^TQOLECBBBBBBBBBBBBBBBBBBBBBBBER]^^^^^^^^^^^^^e»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²—€€€€‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚š§©©©©©©©©©¨¦¦§§§¦ ›•””””””””””””””””””“‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹zkkkkkkkkkkkkkkkkkkkkkknx€€yGBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBHy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚”¡±´´´´´´´´´‹YYZYYXQKCBBBBBBBBBBBBBBBBBBFT^^^^^^^^^^^^^^^^’¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶˜€‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰š©©©©©©©©©©©©©©§¦¦¦§§§§¦ ˜”””””””””””””””‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‡lkkkkkkkkkkkkkkkkkkkkkkkkoxMBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ¡´´´´´´´´´´´´´²eYYYYYYYYQHBBBBBBBBBBBBBBDR^^^^^^^^^^^^^^^^^j¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾´“‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¡©©©©©©©©©©©©©©©©©§¦¦§§§§§¦¦£š”””””””””””‘Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ukkkkkkkkkkkkkkkkkkkkkkkkkkJBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´©iYYYYZYYYY[dffb^^^^^^^^^^^^^^^^^n²¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾µ¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨§§¦§§§§§¤£££¢—‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹€mkkkkkkkkkkkkkkkkkkkkkkkkkkkkA9999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBK€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´±yYYYZYZY[dffffb^^^^^^^^^^^^^^^€º¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾ª¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§§¦¦§§¤££££££•‹‹‹‹‹‹‹‹‹‹‹‹‡vkkkkkkkkkkkkkkkkkkkkkkkkkkkkk[999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBl€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´—eYYYZZdffffffa^^^^^^^^^^^^jŸ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨§¦¦£££££££££‘‹‹‹‹‹‹‹‹‹‰zlkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkF9999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBS€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´²‹`YZdffffffff`^^^^^^^^^e“»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¬¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨£¢££££££££¡‹‹‹‹‹‹…xlklkkkkkkkklkkkkkkkkkkkkkkkkkkkke999999999999999999@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´±vfffffffffe_^^^^^^q—»¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¥  ¡¢£££££££œ‹‹ŠzokkkkkkkkkkkkklkkkkkkkkkkkkkkkkkkkkQ999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÀÐÞ‡jffffffd^^b}‘²¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾±¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£ŒŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§       ¡¡¡¢¡¡„nkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk@9999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBJ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ºÐÐÐÐÐÏȺ« › «°·¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾©¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡             œnkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkke999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÌÐÐÐÐÐÐÐÐÐÐÐÐÐÏ¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦•ŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©£               ˆkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkT9999989999999999999999ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÆÐÐÐÐÐÐÐÐÏÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾³¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŽŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                 okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkJ9999999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´»ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¢                 ‡kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;99999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µËÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                  œmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkf999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€°´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¿ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÏ¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                   kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk[9999999999999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBk€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐО¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¥                    kkkkkkkkkkkkkkkkkkkkkkkkkkklkkkkkkR9999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBa€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€£´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÁÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐʾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¤‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                     mkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkN99999999999999999999999999>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐп¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾°¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                      {kkkkkkkkkkkkkkkklkkkkkkkkkkkkkkkkI99999999999999999999999999:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBU€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¿ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐþ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾­¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦£‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¢ Ÿ                    ˆkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkC999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ËÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐȾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾«¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦“ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ÿ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                       “kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk>999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦—ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§       Ÿ                 vkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk:9999999999999999999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´»ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŸŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¤Ÿ                        }kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk>99999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBH€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÃÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐľ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¨¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                         ‚kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkC99999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBN€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÌÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÆ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾«¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦”ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©                          …kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkI99999999999999999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBU€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¶ÐÐÐÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐǾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦šŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¦                          …kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkN99999999999999998999999999999;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´½ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐǾ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾°¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¤                          ‚kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkR999999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÃÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÆ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾²¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ž©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©£                          }kkkkkkkkkkkkkkkkkkkkkkkkkkkkkklk[999999999999999999999999999999ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBk€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‘´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÇÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐľ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¡                          vkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkf999999999999999999999999999999?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´ÍÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©                           okkkkkklkkkkkkkkkkkkkkkkkkkkkkkkkk;99999999999999999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´µÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÀ¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦™ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨                           lkkkkkkkkkkkkkkkkikkkkkkkkkkkkkkkkI99999999999899999999999999999=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBW€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´·ÐÐÐÐÐÐÐÐÐÐÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐо¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾Å¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾®¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦›ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Ÿ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©§                          “kkkkkklkkkkkkkkkkkkkkkkkkkkkkkkkkkT99999999999999999999999999999BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBV€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”´´´´´´´´´´´´´´´´´´´´´´´´´³°¯¯¯¯¯°¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¡’’’’’’’’’’’’’’¢º¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½©vhhhx€€€€€€€€€€€€€†‘¦˜ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ž©©©©©©©©©©©©©©©©©©©©©©­¾ÅÅÅÅÅÅÅÅÂÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁ½µµµµµµµµµµµ´´µµµ³ŠlkkkkkkkkkkkkkkkkdZPNNNNNNK.+++++++++++++,,,,,,,++,6>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBF{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¦´´´´´´´´´´´´´´´´´´´´´´´±¯¯¯¯¯¯¯¯º¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼±’’’’’’’’’’’’’’’’”«¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾ª‹mhhhhhhj~€€€€€€€€€€€€‚…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€„¦©©©©©©©©©©©©©©©©©©©©µÅÅÅÅÅÅÅÅÅÅÂÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÀ¶µµµµµµµµµµµµµ´´´µµ§vkkkkkkkkkkkkj^RNNNNNNNNNNB,,+++++++++++,,,,,,,+++/02:ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…¯´´´´´´´´´´´´´´´´´´´´²°¯¯¯¯¯¯¯¯¯·¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¸–’’’’’’’’’’’’’’’’’’›¸¾¾¾¾¾¾¾¾¾¾¾¾º˜thhhhhhhhhhq€€€€€€€€€€€€tsv€‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Œ©©©©©©©©©©©©©©©©©©¬¿ÅÅÅÅÅÅÅÅÅÅÅÃÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÀ¸µµµµµµµµµµµµµµ´´´µµµ´‡kkkkkkkkki[NNNNNNNNNNNNNN7,,,+++++++++++,,,,,,,,000018ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBW€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€´´´´´´´´´´´´´´´´´´´°¯¯¯¯¯¯¯¯¯¯¯µ¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼ž’’’’’’’’’’’’’’’’’’’’“®¾¾¾¾¾¾¾¾¾¸Žkhhhhhhhhhhhhhx€€€€€€€€€€€€€sssst}‰ŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©©©©©©©©©°ÂÅÅÅÅÅÅÅÅÅÅÅÅÄÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁºµµµµµµµµµµµµµµµµ´´´µµµµšmkkkkkj[NNNNNNNNNNNNNNNNM2,+,+++++++++++,,,,,,-00000008ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBM~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•´´´´´´´´´´´´´´´´³°¯¯¯¯¯¯¯¯¯¯¯¯²¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼£’’’’’’’’’’’’’’’’’’’’’’’¢½¾¾¾¾¾ºihhhhhhhhhhhhhhhi{€€€€€€€€€€€€{sssssss~‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€•©©©©©©©©©©©©©©´ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÁÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁÁ»µµµµµµµµµµµµµµµµµµµµ´µµµµ¦qkkk`PNNNNNNNNNNNNNNNNNNJ/,,++++++++++++,,,,,.000000001;BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBGy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€š´´´´´´´´´´´´´´²¯°¯¯¯¯¯¯¯¯¯¯¯¯°¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼§’’’’’’’’’’’’’’’’’’’’’’’’’›»¾¾¾œohhhhhhhhhhhhhhhhhhk}€€€€€€€€€€€yssssssssuŠŠŠŠŠŠŠŠŠŠ‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€–©©©©©©©©©©©©·ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÂÀÀÀÁÁÁÁÁÁÁÁÁÁÁÁ»µµµµµµµµµµµµµµµµµµµµµµ´µµµµªufTNNNNNNNNNNNNNNNNNNNNNF,,+++++++++++++,,,,/00000000004?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBCr€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›´´´´´´´´´´´´²¯¯°¯¯¯¯¯¯¯¯¯¯¯¯¯¸¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼¨’’’’’’’’’’’’’’’’’’’’’’’’’’’™¹°yhhhhhhhhhhhhhhhhhhhhhm€€€€€€€€€€€€ussssssssssx†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€“©©©©©©©©©©¸ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀÀÀÁÁÁÁÁÁÁÁºµµµµµµµµµµµµµµµµµµµµµµ´µ´´µµµ¦\NNNNNNNNNNNNNNNNNNNNNNND,,,++++++++++++,,,0000000000000:BBBBBBBBBBBBBBBBBBBBBBBBBBBBn€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜´´´´´´´´´´²¯¯¯°¯°¯¯¯¯¯¯°¯¯¯¯µ¼¼¼¼¼¼¼¼¼¼¼¼¼¼¥’’’’’’’’’’’’’’’’’’’’’’’’’’’’’„ihhhhhhhhhhhhhhhhhhhhhhho€€€€€€€€€€€€~sssssssssssss€ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€©©©©©©©©¸ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÀÀÀÀÀÀÁÁÁÁÁÁÁ¹µµµµµµµµµµµµµµµµµµµµµµµµµ´´µ´ª§Ÿ[NNNNNNNNNNNMNNNNNNNNNNNC,,,++++++++++++,.000000000000004ABBBBBBBBBBBBBBBBBBBBBBBBBm€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€’´´´´´´´´²¯¯¯¯¯¯¯¯¯¯¯¯¯°¯¯¯¯±¼¼¼¼¼¼¼¼¼¼¼¼¼¡’’’’’’’’’’’’’’’’’’’’’’’’’’’’tkkihhhhhhhhhhhhhhhhhhhhhhho€€€€€€€€€€€€€zssssssssssssssxˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡¢©©©©©·ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÂÀÀÀÀÀÀÀÁÁÁ¿·µµµµµµµµµµµµµµµµµµµµµµµµµµµ´±©§§§WNNNNNNNNNNNNNNNNNNNNNNND-,,++++++++++++/0000000000000002>BBBBBBBBBBBBBBBBBBBBBBEo€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¬´´´´´²¯¯¯¯¯¯¯°°¯¯¯¯¯¯°¯¯¯¯¹¼¼¼¼¼¼¼¼¼¼µ™’’’’’’’’’’’’’’’’’’’’’’’’’’’’‰okkkkihhhhhhhhhhhhhhhhhhhhhhhn~€€€€€€€€€€€€wsssssssssssssssu…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€—©©©´ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀÀÀÀÀÁ»µµµµµµµµµµµµµµµµµµµµµµµµµµµµµ°§§§§§§˜SNNNMNNNNNNNNNNNNNNNNNNNH0,,++++++++++,000000000000000000;BBBBBBBBBBBBBBBBBBBBJu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚´´´²¯¯°¯¯¯¯¯¯¯°°¯¯¯¯¯°¯¯¯µ¼¼¼¼¼¼¼¼¼©“’’’’’’’’’’’’’’’’’’’’’’’’’’’’„kkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhl|€€€€€€€€€€ssssssssssssssssss‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆ °ÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÁÀÀÀÀÀÀ¾·µµµµµµµµµµµµµµµµµµµµµµµµµµµµµ¯§§§§§§§§‘ONNNNNNNNNNMNNNNNNNNNNNNK3,,+++++++++.00000000000000000009BBBBBBBBBBBBBBBBBBP|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€Š¨³¯¯¯°¯¯¯¯¯¯¯¯°°¯¯¯¯¯°¯¯°¼¼¼¼¼¼¼²š’’’’’’’’’’’’’’’’’’’’’’’’’’’’’€kkkkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhjz€€€€€€€€€zsssssssssssssssssssŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†»ÄÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÃÀÀÀÀ¾¹µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ®§§§¨§§§§§§zNNNNNNNNNNNNNNNNNNNNNNNNN=,,++++++++0000000000000000000008BBBBBBBBBBBBBBBBb€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¤¯¯¯°¯¯¯¯¯¯¯¯°¯¯¯¯¯¯¯¯¯·¼¼¼¼´Ÿ’’’’’’’’’‘’’’’’’’’’’’’’’’’’’’’}kkkkkkkkkkjhhhhhhhhhhhhhhhhhhhhhhhhht€€€€€€€tssssssssssssssssssss}ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€°¾¾ÀÃÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÀÀ¼¸µµµµµ´µµµµµµµµµµµµµµµµµµµµµµµµµ­§§§§§§§§§§§§gNNNNNNNNNNNNMNNNNNNNNNNNNG2,++++++-00000000000000000000008BBBBBBBBBBBBBLs€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€xvvƒš¯¯°°¯¯¯¯¯¯¯¯¯¯¯¯¯°°°²¼¼­œ’’’’’’’’’’’’’‘’’’’’’’’’’’’’’’’’|kkkkkkkkkkkkihhhhhhhhhhhhhhhhhhhhhhhhhm|€€€€€€|ssssssssssssssssssssss}ŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€›¾¾¾¾¾¾ÁÃÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅÅü¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ®§§§§§§§¨§§§§§SNNNNNNNNNNNNNNNNNNNNNNNNNM>-+++++/000000000000000000000009BBBBBBBBBBCc€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|vvvvvyŒš­°¯¯¯¯¯¯¯°¯¯¯¯¯¯¬š›”’’’’’’’’’’’’’’’’’’’’’’’’’‘’’’’’’~kkkkkkkkkkkkkkhhhhhhhhhhhhhhhhhhhhhhhhhhis€€€€€vsssssssssssssssssssssss~ŠŠŠŠŠŠŠŠŠŠŠ…€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰¾¾¾¾¾¾¾¾¾¾¿ÁÂÃÄÄÅÅÅÄÄÃÂÁ¿¾¾½µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ¯§§§§§§§§§§¨§§§§ˆNNNNNNNNNNNNNNNNNNNNNNNNNNNK9+++-0000000000000000000000000:BBBBBBBB[{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vvvvvvvvvw~•𢩭¯®©¢š•~wv|’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’‚kkkkkkkkkkkkkkkjhhhhhhhhhhhhhhhhhhhhhhhhhhhjw€€€|sssssssssssssssssssssssss€ŠŠŠŠŠŠŠŠ†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¯¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¸µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ°§§§§§§§§§§§¨¨§§§§eNNNNNNNNNNNNNNNNNNNNNNNNNNNNJ9,/0000000000000000000000000096@O`lmkkiihgb\UNP€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‰½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾º´¯ª¦£ žœœŸ¡¤¨¬±·½¾¾¾¾¾¾¾´¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬ŒWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVPJEDDFfxaOPTX[_behkmnpvƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vvvvvvvvvvvvvvvvvvvvvvvwxyy{z{{{{{{{{{zyxxvvvvvvvvi_______________________XLLLLLLLLLMLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLKC;5556>N^lmlkiihgfeeddk~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¸¯¦Ÿœœœœœœœœœœœœœœœœœœœ£ª³¼¾¾¾¼¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬žZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWUMFEEEDEaxdPPSW[^adgkmnprƒ‡‰†€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|vvvvvvvvvvvvvvvvvvwyz{{{{|{|{{{|{{{{{{{||{zyxwvvvs______________________\MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLJ@655555=L]kmlkjihgffeddcwzy|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€¨¾¾¾¾¾¾¾¾¾¾¾¾¾¾¶ª œœœœœœœœœœœœœœœœœœœœœœœœœœœ¥°»¾±¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬ªeWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVMFEEEEEE\yfQOSW[^adgjlnprx‡‰‹‹‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zvvvvvvvvvvvvvvxz{{{{||{|{{{||{|||{{|{|||{|{|{zywve_____________________OLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ?65555555555;IZimlkjihggfeddccboxxwvvtux{}‚…‰Ž’”“’‘ŽŽŒ‹Š‰ˆ‡††…„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}qnnnn^WWWWWWWWWWWWWWWWWWWWWWWWWHEEEUykTORVZ]adgjlnoqsx†ŠŒ“–šš–’ŽŠ†‚zslhhiiiiijjjkkkkklllllmmmp€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|niiiiVLLLLLLLLLLLLMLLLLLLLLLLLL9555:HXhmlkjihgffeddccgvyxwvuuuxz}‚„‰’”“’‘ŽŒ‹Š‰ˆ‡‡†…„ƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yonnkXWWWWWWWWWWWWWWWWWWWWWWWVEEEQwmVNRVZ]`cfilnoqt€‰ŠŒ’–𛗒І‚{tlhhiiiiijjjjjkkklllllmmmmn€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wkiifMLLLLLLLLLLLLLLLLLLLLLLLK6558FVgmlkjihhgfeddcepyyxwwuuuwz|„ˆ’”“’‘ŽŽŒ‹‹‰‰ˆ‡†…„ƒ‚€~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vnnaWWWWWWWWWWWWWWWWWWWWWWWTEEMtpYNRUY]`cfikmoq{†ˆŠŒŽ’•™›—“‹‡ƒ|umiiiiiiijjjjjkkkkllllmmmmno}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€tiiYLLLLLLLLLLLLLLLLLLLLLLLH558DTenmkjiihgfedddnyyyxxwvtuwz|„ˆŒ‘”“’’ŽŒ‹Š‰‰ˆ‡†…„ƒ‚‚€~y~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vlXWWWWWWWWWWWWWWWWWWWWWWREJqs[NQUY\`bfikmoyƒ†‰ŠŒŽ’•™›˜“‹‡ƒ}unihihiiijjjjjkkllllllmmmmmoq|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~sgMLLLLLLLLLLLLLLLLLLLLLLE57CSdmlkjiihgfeeenyzzyxwwvutwy|~ƒ‡Œ‘““’’ŽŒ‹Š‰ˆˆ‡†…„ƒ‚‚€zsz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€p]WWWWWWWWWWWWWWWWWWWWWPInt]OQUX\_beikoy‚„†ˆŠŒŽ’•˜›˜“‹‡ƒ~voihhiiiijijjjkkkklllmmmmmmoqr{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lSLLLLLLLLLLLLLLLLLLLLLC6AQbmmlkiihgfegqzzzzxxxwvutvy{~ƒ‡‹”““’‘ŽŒ‹‹Š‰ˆ‡†……„ƒ‚€ztmw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€whYWWWWWWWWWWWWWWWWWWRjw_NPTX\_beir{ƒ…†ˆŠŒŽ‘•˜›˜”Œˆ„~vpihhiiiiiijjjkkklklllmmmmmoqrsz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€taOLLLLLLLLLLLLLLLLLLC@O`lmlkihhggnw{{zzyyxxwvuuvy{~ƒ†Š”“’’‘ŽŒ‹‹Š‰ˆ‡†……„ƒ‚€{tmjv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€vj^WWWWWWWWWWWWWWWewbOPTX\_dmu}‚„†ˆ‰‹‘”˜›™•‘Œˆ„wpihhiiiijjjjjkkkkklllmmmmnnprssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€sdULLLLLLLLLLLLLLLGN^lmlkjilqx||{{zzzyxxwvutvy{~€ƒ†Š““’’‘ŽŒŒ‹Š‰ˆ‡†……ƒƒ‚€€|unjiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tkf\WWWWWWWWWWfdOPV^dkrwz}€ƒ„†‡‰‹”—›™•‘‰„€xqjhhiiiiijjjkkkkklllllmmmmnprsssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}qe^SLLLLLLLLLLN]lmosuy~~}||{{{zzyyxwvutvx{}€‚…Š“”“’‘ŽŒ‹Š‰ˆ‡††…„ƒ‚€|uojiiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}yuqnllnqra`eimpswz}~€‚„†‡‰‹“—›™–‘‰…€yrjhhiiiiijjjkjkkklllllmmmmnprssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|wrmiggimp~ƒƒ‚€~}}||{{zzyyxwvutux{}‚…ŠŽ“““’‘ŽŒ‹Š‰ˆ‡††„„ƒ‚€}vpjiiiv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€taeimpsvy|€‚„†‡‰‹“–šš–’Ž‰†zrkhhiiiiijjjjkkklkllllmmmmnprsssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚„„‚€€}}||{{zzyyxwvuuux{}€‚…‰Ž’““’‘ŽŽŒ‹Š‰ˆˆ‡†…„ƒ‚€}wpjjiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€weilpsvy|€‚„…‡‰ŠŒ“–šš–’ŽŠ†‚{slhhhiiiijjjjkkkkkklllmmmmnprssssss|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ‚€~~}||{{zzyyxwwuuuwz}„‰’”“’‘Œ‹Š‰‰‡‡†…„ƒ‚‚€€}xqkiiiiiy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{hlorvy|~€‚ƒ…‡‰ŠŒ’–™›—“ŽŠ†‚{tlhhiiiiijjjjkkkkkllllmmmmnpqsssssss~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚€~}||{{zzyyxxwvuuwz|‚„ˆ‘““’’‘ŽŒ‹‹‰‰‡‡†…„ƒ‚‚€€~xrkjjiiji|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€lorvx{~€‚ƒ…‡ˆŠŒŽ’–™›—“‹‡ƒ|umihiiiiijjjjkkkkkllllmmmmnoqssssssss€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚€€~}||{{zzyyyxwvuuwz|„ˆŒ‘”“’‘ŽŒ‹ŠŠ‰ˆ‡†…„ƒ‚‚€~yrlijjiijj€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€rrux{}€ƒ…‡ˆŠŒŽ’•™›˜“‹‡ƒ}vnihiiiiijjjjjkkkkklllmmmmnoqssssssssv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}|||{zzyyxxwvutwy|~ƒ‡Œ”“’‘ŽŒ‹Š‰‰ˆ‡†…„„ƒ‚€zslijiiiiim€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wtx{}ƒ„†ˆŠŒŽ‘•˜›˜”Œ‡ƒ~voihiiiiijjjjjkkkklkllmmmmmoqrssssssssx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}||{{zzyyyxwvttwy|~ƒ‡‹”“’’ŽŒ‹ŠŠ‰ˆ‡†…„ƒƒ‚€ztmiiiiiijir€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|w{}ƒ…†ˆŠ‹‘”˜›˜”Œˆ„~wpihiiiiijjjjkkkkkklllmmmmmoqrsssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~||{{{zyyxxwvutvy{~ƒ†‹”““’‘ŽŒ‹‹Š‰ˆ‡†…„„ƒ‚€{tniiiijiiijx€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€z}ƒ„†ˆ‰Œ”˜›™•‘ˆ„xpihhiiiijjjjkkkkkllllmmmmmoprssssssssss€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}||{{zzyyyxwvutux{~€ƒ†Š”“’‘‘Ž‹‹Š‰ˆ‡†…„„ƒ‚€{uniiiiiijjij~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~‚„†ˆ‰‹”—𙕑‰…€xqjhhiiijijjjjjkkllllllmmmmoprssssssssssv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~|{{{zzyxxwvutux{~€ƒ†ŠŽ“““’‘ŽŽŒ‹Š‰ˆ‡†……„ƒ‚€|vojiiiiiiiiio€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚„†‡‰‹“—𙕑‰…€yqjhhiiiijjjjjjkkkllllllmmmnprsssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|{{zzyyxwvuuvx{}€‚…ŠŽ“““’‘ŽŒŒ‹Š‰ˆˆ‡†„„ƒ‚€€}vpjiiiiiiiijjw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒ†‡‰‹“–šš–‘ŽŠ†zrkhhhiiiijjjjkkkklllllmmmnnprssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|{zyyyxwvutuxz}‚…‰Ž’”“’‘Œ‹Š‰ˆˆ‡†…„ƒ‚€€}wpjiiiiiiiiiik€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‡‰Š“–™š–’ŽŠ†zslhihiiijjjjjjkkkkllllmmmmnpqssssssssssssy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}zzyyxwwutuwz}‚„‰’”“’‘ŽŒ‹Š‰‰ˆ‡†…„ƒ‚€~xqkiiiiijiiiiis€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡‰ŠŒ“–™›—“ŽŠ†‚{tlhhhiiiijjjjkkkkllllmlmmmnpqsssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zyyywwvuuwz|‚„ˆ‘”“’‘ŽŽŒ‹‹‰ˆˆ‡†…„ƒ‚€~xrkjiiiiiiiiiij~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…ŠŒŽ’•™›—“‹‡‚|tmihhiiiiijjjjkkkkllllmmmmnoqsssssssssssssy€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€|yxxwvuuwy|„ˆŒ‘““’‘‘Œ‹‹‰‰ˆ‡†…„ƒƒ‚€yrliiiiiiiiiiiit€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‹Ž’•™›˜“‹‡ƒ}unihiiiiiijjjkkkkkllllmmmmnoqrsssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yxvvutwy|~„‡Œ”“’‘ŽŽŒ‹Š‰‰ˆ‡†…„ƒƒ‚€zsliiiijiijiiiik~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†‘•˜›˜”Œˆƒ~voihhiiiijjjjkjkkkllllmmmmmoqssssssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}wvutvy{~ƒ‡‹”“’‘‘ŽŒ‹Š‰ˆˆ‡†…„ƒƒ‚€zsmiiiiiiijijiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€˜›˜”Œˆ„~woihhiiiijjjjkkkkklllmmmmmnopssssssssssssssw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yutvy{~ƒ†‹”“’’‘ŽŽŒ‹‹Š‰ˆ‡††„ƒƒ‚€{uniiiiiiiijiijjp€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ˜™•‘Œˆ„wpihhhiiiijjjjkkkklllllmmmmoprssssssssssssst€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€uvx{~€ƒ†Š“”’‘‘ŽŽŒ‹‹Š‰ˆ‡†……„ƒ€|unjiiiiiiiiiijil~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†•‘‰…€xqjhhhiiiijjjjkkkkkllllmmmmoprssssssssssssst}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~x{}€‚…Š“““’‘ŽŒ‹‹Š‰ˆ‡‡……„ƒ‚€€|vojjiiiiiiiiijij{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‡‰…€yrkhhhiiijjjjjjkkklllllmmmmnprssssssssssssss{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~}€‚…‰Ž“”“’‘ŽŽŒ‹Š‰ˆ‡‡†…„ƒ‚}vpjijiiiiiiiiiiiw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…†yrkhhiiiiijjjjjkkkkllllmmmmnpqssssssssssssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚…‰Ž’”“’‘ŽŽŒ‹Š‰ˆ‡‡†…„ƒ‚€€}wpjjiijjiiiiiiiiv€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€zslhhhiiiijjjjkkkkkllllmmmmnprssssssssssssssz€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚‰’”“’‘ŽŽŒ‹ŠŠˆˆ‡†…„ƒ‚€€}xqjiijiijjiiiiiiu€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{mhhiiiiijjjjkkkkklllmmmmmnoqssssssssssssst{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘”“’‘ŽŽŒ‹Š‰ˆˆ‡†…„ƒ‚€~xqkiiiijijiiiiijw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{kiiiiiijjjkkkkkllllmlmmmoqsssssssssssssu}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€…‘’‘ŽŒ‹ŠŠ‰ˆ‡†…„ƒƒ€~yrkiiijijiiijiil{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~piiijjjjkkkklkllllmmmnoqrssssssssssssw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ŽŒ‹ŠŠˆˆ‡†…„ƒƒ‚€zsliiiijjijjiijp~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€wkijjjjkkkkllllmmmmmoqsssssssssssst{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†ŽŽŒ‹‹‰‰ˆ‡†…„„ƒ‚€ztmiiiiiiijijjkw€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tkjjkkkkllllmmmmnoqsssssssssssty€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ˆŒ‹‹Š‰ˆ‡†…„„ƒ‚€{uniiiiiiiijikt~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~tlkkklllllmmmnoprsssssssssty€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†‰‰ˆ‡†…„ƒƒ‚€|uojjiiiiiiikt~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€xplllllmmmmnprssssssssv{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒ††……ƒƒ‚€€|vojiijjiiiow€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~ytpmmmmnprsssssux|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒƒ‚€}vojijijnrx~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}{yxyzz{|~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}yvvwy|€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ \ No newline at end of file diff --git a/tests/Images/Input/WebP/lossless_color_transform.ppm b/tests/Images/Input/WebP/lossless_color_transform.ppm new file mode 100644 index 0000000000000000000000000000000000000000..4607dab20ed859ce8f6ac62843823af42df64979 GIT binary patch literal 786447 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