Browse Source

Fix issue with encoding 1 by 1 pixel lossless image

pull/1552/head
Brian Popow 5 years ago
parent
commit
c4a1b994e6
  1. 2
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 2
      src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
  3. 48
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  4. 34
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
  5. 1
      src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
  6. 8
      tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs
  7. 21
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  8. 4
      tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs
  9. 2
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  10. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

2
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;

2
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)
{

48
src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <param name="method">Quality/speed trade-off (0=fast, 6=slower-better).</param>
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<uint> 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<uint> 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++)
{

34
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<int> hashToFirstIndexBuffer = memoryAllocator.Allocate<int>(HashSize);
Span<int> 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);
/// <summary>
/// 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
/// <param name="quality">The quality.</param>
/// <returns>Number of hash chain lookups.</returns>
[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)

1
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;

8
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);

21
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<TPixel> 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<TPixel> 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<TPixel> 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<TPixel> 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<Rgba32>(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%

4
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());
}
}
}

2
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

2
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;

Loading…
Cancel
Save