diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs
index 5bd354345a..0a8e9fb4c4 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 22b111fbbc..61eb589305 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; }
+ }
}
+
+
}