Browse Source

Merge pull request #1971 from SixLabors/bp/webpalpha

Add support for encoding lossy webp images with alpha channels
pull/1974/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
b7e348ff81
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 133
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  2. 52
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  3. 45
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  4. 1
      src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
  5. 41
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  6. 48
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  7. 11
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  8. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  9. 7
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  10. 40
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  11. 1
      tests/ImageSharp.Tests/TestImages.cs
  12. 3
      tests/Images/Input/Png/transparency.png

133
src/ImageSharp/Formats/Webp/AlphaEncoder.cs

@ -0,0 +1,133 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp
{
/// <summary>
/// Methods for encoding the alpha data of a VP8 image.
/// </summary>
internal class AlphaEncoder : IDisposable
{
private IMemoryOwner<byte> alphaData;
/// <summary>
/// Encodes the alpha channel data.
/// Data is either compressed as lossless webp image or uncompressed.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="memoryAllocator">The memory manager.</param>
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
/// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns>
public IMemoryOwner<byte> EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
if (compress)
{
WebpEncodingMethod effort = WebpEncodingMethod.Default;
int quality = 8 * (int)effort;
using var lossLessEncoder = new Vp8LEncoder(
memoryAllocator,
configuration,
width,
height,
quality,
effort,
WebpTransparentColorMode.Preserve,
false,
0);
// The transparency information will be stored in the green channel of the ARGB quadruplet.
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
// that can improve compression.
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan());
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData);
return this.alphaData;
}
size = width * height;
return this.alphaData;
}
/// <summary>
/// Store the transparency in the green channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="alphaData">A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</param>
/// <returns>The transparency image.</returns>
private static Image<Rgba32> DispatchAlphaToGreen<TPixel>(Image<TPixel> image, Span<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
var alphaAsImage = new Image<Rgba32>(width, height);
for (int y = 0; y < height; y++)
{
Memory<Rgba32> rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y);
Span<Rgba32> pixelRow = rowBuffer.Span;
Span<byte> alphaRow = alphaData.Slice(y * width, width);
for (int x = 0; x < width; x++)
{
// Leave A/R/B channels zero'd.
pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
}
}
return alphaAsImage;
}
/// <summary>
/// Extract the alpha data of the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="memoryAllocator">The memory manager.</param>
/// <returns>A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</returns>
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
int height = image.Height;
int width = image.Width;
IMemoryOwner<byte> alphaDataBuffer = memoryAllocator.Allocate<byte>(width * height);
Span<byte> alphaData = alphaDataBuffer.GetSpan();
using IMemoryOwner<Rgba32> rowBuffer = memoryAllocator.Allocate<Rgba32>(width);
Span<Rgba32> rgbaRow = rowBuffer.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(configuration, rowSpan, rgbaRow);
int offset = y * width;
for (int x = 0; x < width; x++)
{
alphaData[offset + x] = rgbaRow[x].A;
}
}
return alphaDataBuffer;
}
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
}
}

52
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -47,6 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="stream">The stream to write to.</param>
public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes()));
/// <summary>
/// Writes the encoded bytes of the image to the given buffer. Call Finish() before this.
/// </summary>
/// <param name="dest">The destination buffer.</param>
public void WriteToBuffer(Span<byte> dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest);
/// <summary>
/// Resizes the buffer to write to.
/// </summary>
@ -94,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// Calculates the chunk size of EXIF or XMP metadata.
/// </summary>
/// <param name="metadataBytes">The metadata profile bytes.</param>
/// <returns>The exif chunk size in bytes.</returns>
/// <returns>The metadata chunk size in bytes.</returns>
protected uint MetadataChunkSize(byte[] metadataBytes)
{
uint metaSize = (uint)metadataBytes.Length;
@ -103,6 +109,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
return metaChunkSize;
}
/// <summary>
/// Calculates the chunk size of a alpha chunk.
/// </summary>
/// <param name="alphaBytes">The alpha chunk bytes.</param>
/// <returns>The alpha data chunk size in bytes.</returns>
protected uint AlphaChunkSize(Span<byte> alphaBytes)
{
uint alphaSize = (uint)alphaBytes.Length + 1;
uint alphaChunkSize = WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
return alphaChunkSize;
}
/// <summary>
/// Writes a metadata profile (EXIF or XMP) to the stream.
/// </summary>
@ -128,6 +147,37 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
}
}
/// <summary>
/// Writes the alpha chunk to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="dataBytes">The alpha channel data bytes.</param>
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
byte flags = 0;
if (alphaDataIsCompressed)
{
flags |= 1;
}
stream.WriteByte(flags);
stream.Write(dataBytes);
// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
}
/// <summary>
/// Writes a VP8X header to the stream.
/// </summary>

45
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -409,7 +409,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha)
/// <param name="alphaData">The alpha channel data.</param>
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
public void WriteEncodedImageToStream(
Stream stream,
ExifProfile exifProfile,
XmpProfile xmpProfile,
uint width,
uint height,
bool hasAlpha,
Span<byte> alphaData,
bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
@ -418,7 +428,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null)
{
isVp8X = true;
riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes);
}
@ -426,11 +435,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (xmpProfile != null)
{
isVp8X = true;
riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes);
}
if (hasAlpha)
{
isVp8X = true;
riffSize += this.AlphaChunkSize(alphaData);
}
if (isVp8X)
{
riffSize += ExtendedFileChunkSize;
}
this.Finish();
uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh;
@ -451,7 +470,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha);
this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData, alphaDataIsCompressed);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@ -639,7 +658,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
while (it.Next());
}
private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile, XmpProfile xmpProfile, bool hasAlpha)
private void WriteWebpHeaders(
Stream stream,
uint size0,
uint vp8Size,
uint riffSize,
bool isVp8X,
uint width,
uint height,
ExifProfile exifProfile,
XmpProfile xmpProfile,
bool hasAlpha,
Span<byte> alphaData,
bool alphaDataIsCompressed)
{
this.WriteRiffHeader(stream, riffSize);
@ -647,6 +678,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
if (hasAlpha)
{
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
}
}
this.WriteVp8Header(stream, vp8Size);

1
src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs

@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary>
/// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format.
/// Defaults to true.
/// </summary>
bool UseAlphaCompression { get; }

41
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public Vp8LHashChain HashChain { get; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// Encodes the image as lossless webp to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
@ -236,10 +236,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
image.Metadata.SyncProfiles();
int width = image.Width;
int height = image.Height;
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
// Convert image pixels to bgra array.
bool hasAlpha = this.ConvertPixelsToBgra(image, width, height);
@ -253,11 +255,42 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha);
}
/// <summary>
/// Encodes the alpha image data using the webp lossless compression.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="alphaData">The destination buffer to write the encoded alpha data to.</param>
/// <returns>The size of the compressed data in bytes.
/// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed.
/// </returns>
public int EncodeAlphaImageData<TPixel>(Image<TPixel> image, IMemoryOwner<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
int pixelCount = width * height;
// Convert image pixels to bgra array.
this.ConvertPixelsToBgra(image, width, height);
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
this.EncodeStream(image);
this.bitWriter.Finish();
int size = this.bitWriter.NumBytes();
if (size >= pixelCount)
{
// Compressing would not yield in smaller data -> leave the data uncompressed.
return pixelCount;
}
this.bitWriter.WriteToBuffer(alphaData.GetSpan());
return size;
}
/// <summary>
/// Writes the image size to the bitwriter buffer.
/// </summary>

48
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -71,12 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary>
private int uvAlpha;
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[16];
private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
private readonly bool alphaCompression;
private const int NumMbSegments = 4;
@ -105,6 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// <param name="entropyPasses">Number of entropy-analysis passes (in [1..10]).</param>
/// <param name="filterStrength">The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).</param>
/// <param name="spatialNoiseShaping">The spatial noise shaping. 0=off, 100=maximum.</param>
/// <param name="alphaCompression">If true, the alpha channel will be compressed with the lossless compression.</param>
public Vp8Encoder(
MemoryAllocator memoryAllocator,
Configuration configuration,
@ -114,7 +110,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
WebpEncodingMethod method,
int entropyPasses,
int filterStrength,
int spatialNoiseShaping)
int spatialNoiseShaping,
bool alphaCompression)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
@ -125,6 +122,7 @@ 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.alphaCompression = alphaCompression;
this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll
: method >= WebpEncodingMethod.Level5 ? Vp8RdLevel.RdOptTrellis
: method >= WebpEncodingMethod.Level3 ? Vp8RdLevel.RdOptBasic
@ -174,6 +172,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.ResetBoundaryPredictions();
}
// This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs.
private static ReadOnlySpan<byte> AverageBytesPerMb => new byte[] { 50, 24, 16, 9, 7, 5, 3, 2 };
public int BaseQuant { get; set; }
/// <summary>
@ -297,10 +298,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
int width = image.Width;
int height = image.Height;
int pixelCount = width * height;
Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.GetSpan();
YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
int yStride = width;
int uvStride = (yStride + 1) >> 1;
@ -318,12 +320,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.SetLoopParams(this.quality);
// Initialize the bitwriter.
int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4];
int averageBytesPerMacroBlock = AverageBytesPerMb[this.BaseQuant >> 4];
int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock;
this.bitWriter = new Vp8BitWriter(expectedSize, this);
// TODO: EncodeAlpha();
bool hasAlpha = false;
// Extract and encode alpha channel data, if present.
int alphaDataSize = 0;
bool alphaCompressionSucceeded = false;
using var alphaEncoder = new AlphaEncoder();
Span<byte> alphaData = Span<byte>.Empty;
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
IMemoryOwner<byte> encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize);
alphaData = encodedAlphaData.GetSpan();
if (alphaDataSize < pixelCount)
{
// Only use compressed data, if the compressed data is actually smaller then the uncompressed data.
alphaCompressionSucceeded = true;
}
}
// Stats-collection loop.
this.StatLoop(width, height, yStride, uvStride);
@ -358,7 +374,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha);
this.bitWriter.WriteEncodedImageToStream(
stream,
metadata.ExifProfile,
metadata.XmpProfile,
(uint)width,
(uint)height,
hasAlpha,
alphaData,
this.alphaCompression && alphaCompressionSucceeded);
}
/// <inheritdoc/>

11
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -318,7 +318,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// <param name="y">Span to store the luma component of the image.</param>
/// <param name="u">Span to store the u component of the image.</param>
/// <param name="v">Span to store the v component of the image.</param>
public static void ConvertRgbToYuv<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
/// <returns>true, if the image contains alpha data.</returns>
public static bool ConvertRgbToYuv<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
@ -335,6 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<Bgra32> bgraRow1 = bgraRow1Buffer.GetSpan();
int uvRowIndex = 0;
int rowIndex;
bool hasAlpha = false;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
@ -343,6 +345,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1);
if (rowsHaveAlpha)
{
hasAlpha = true;
}
// Downsample U/V planes, two rows at a time.
if (!rowsHaveAlpha)
@ -375,10 +381,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
else
{
AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width);
hasAlpha = true;
}
ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth);
}
return hasAlpha;
}
/// <summary>

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default;
/// <inheritdoc/>
public bool UseAlphaCompression { get; set; }
public bool UseAlphaCompression { get; set; } = true;
/// <inheritdoc/>
public int EntropyPasses { get; set; } = 1;

7
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// TODO: not used at the moment.
/// Indicating whether the alpha plane should be compressed with Webp lossless format.
/// Defaults to true.
/// </summary>
private readonly bool alphaCompression;
@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// Encodes the image as webp to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
@ -149,7 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.method,
this.entropyPasses,
this.filterStrength,
this.spatialNoiseShaping);
this.spatialNoiseShaping,
this.alphaCompression);
enc.Encode(image, stream);
}
}

40
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -167,18 +167,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
public void Encode_Lossless_WorksWithTestPattern<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
[Fact]
public void Encode_Lossless_OneByOnePixel_Works()
{
@ -279,6 +267,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality));
}
[Theory]
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, false)]
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, true)]
public void Encode_Lossy_WithAlpha_Works<TPixel>(TestImageProvider<TPixel> provider, bool compressed)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
{
FileFormat = WebpFileFormatType.Lossy,
UseAlphaCompression = compressed
};
using Image<TPixel> image = provider.GetImage();
image.VerifyEncoder(provider, "webp", $"with_alpha_compressed_{compressed}", encoder, ImageComparer.Tolerant(0.04f));
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
public void Encode_Lossless_WorksWithTestPattern<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]

1
tests/ImageSharp.Tests/TestImages.cs

@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Tests
{
public static class Png
{
public const string Transparency = "Png/transparency.png";
public const string P1 = "Png/pl.png";
public const string Pd = "Png/pd.png";
public const string Blur = "Png/blur.png";

3
tests/Images/Input/Png/transparency.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:843bea4db378f52935e2f19f60d289df8ebe20ddde3977c63225f1d58a10bd62
size 48119
Loading…
Cancel
Save