Browse Source

Add lossless alpha compression

pull/1971/head
Brian Popow 4 years ago
parent
commit
d192941228
  1. 86
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  2. 11
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  3. 10
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  4. 31
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  5. 14
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  6. 6
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

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

@ -3,18 +3,98 @@
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>
/// Encodes the alpha channel data.
/// Data is either compressed as lossless webp image or uncompressed.
/// Methods for encoding the alpha data of a VP8 image.
/// </summary>
internal static class AlphaEncoder
{
public static byte[] EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator)
/// <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>
/// <returns>The alpha data.</returns>
public static byte[] EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress)
where TPixel : unmanaged, IPixel<TPixel>
{
byte[] alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
int width = image.Width;
int height = image.Height;
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, alphaData);
return lossLessEncoder.EncodeAlphaImageData(alphaAsImage);
}
return 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, 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.AsSpan(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 byte[] ExtractAlphaChannel<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;

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

@ -146,7 +146,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="dataBytes">The alpha channel data bytes.</param>
protected void WriteAlphaChunk(Stream stream, byte[] dataBytes)
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
protected void WriteAlphaChunk(Stream stream, byte[] dataBytes, bool alphaDataIsCompressed)
{
DebugGuard.NotNull(dataBytes, nameof(dataBytes));
@ -157,9 +158,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
// Write flags, all zero for now.
stream.WriteByte(0);
byte flags = 0;
if (alphaDataIsCompressed)
{
flags |= 1;
}
stream.WriteByte(flags);
stream.Write(dataBytes);
// Add padding byte if needed.

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

@ -410,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
/// <param name="alphaData">The alpha channel data.</param>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData)
/// <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, byte[] alphaData, bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
@ -461,7 +462,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, alphaData);
this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData, alphaDataIsCompressed);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@ -660,7 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
ExifProfile exifProfile,
XmpProfile xmpProfile,
bool hasAlpha,
byte[] alphaData)
byte[] alphaData,
bool alphaDataIsCompressed)
{
this.WriteRiffHeader(stream, riffSize);
@ -670,7 +672,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
if (hasAlpha)
{
this.WriteAlphaChunk(stream, alphaData);
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
}
}

31
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,32 @@ 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>
/// <returns>The encoded alpha stream.</returns>
public byte[] EncodeAlphaImageData<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
// Convert image pixels to bgra array.
this.ConvertPixelsToBgra(image, width, height);
// The image-stream does NOT contain any headers describing the image dimension, the dimension is already known.
this.EncodeStream(image);
this.bitWriter.Finish();
using var ms = new MemoryStream();
this.bitWriter.WriteToStream(ms);
return ms.ToArray();
}
/// <summary>
/// Writes the image size to the bitwriter buffer.
/// </summary>

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

@ -71,10 +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 bool alphaCompression;
private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
@ -105,6 +102,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 +112,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 +124,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
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator);
alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression);
}
// Stats-collection loop.
@ -363,7 +363,7 @@ 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, alphaData);
this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha, alphaData, this.alphaCompression);
}
/// <inheritdoc/>

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

@ -22,7 +22,6 @@ 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.
/// </summary>
private readonly bool alphaCompression;
@ -100,7 +99,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 +148,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.method,
this.entropyPasses,
this.filterStrength,
this.spatialNoiseShaping);
this.spatialNoiseShaping,
this.alphaCompression);
enc.Encode(image, stream);
}
}

Loading…
Cancel
Save