From 75cdb2d1d52eaa98b5a67d1c431048b4cb3f4d07 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Nov 2020 21:03:47 +0100 Subject: [PATCH] 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 0be198c15d..f08b9f0b16 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 0000000000..02b0dcf713 --- /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 0000000000..04e2d511bc --- /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 16b5bce069..7c2cd46124 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 078710486f..a41d7295da 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 0262ac332f..54a711c384 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 53360a981d..c06d933586 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,