Browse Source

Write ALPH chunk (only uncompressed for now)

pull/1971/head
Brian Popow 4 years ago
parent
commit
96c1c725f9
  1. 42
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  2. 43
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  3. 35
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  4. 13
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  5. 11
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

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

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
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.
/// </summary>
internal static class AlphaEncoder
{
public static byte[] EncodeAlpha<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;
byte[] alphaData = new byte[width * height];
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 alphaData;
}
}
}

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

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// Calculates the chunk size of EXIF or XMP metadata. /// Calculates the chunk size of EXIF or XMP metadata.
/// </summary> /// </summary>
/// <param name="metadataBytes">The metadata profile bytes.</param> /// <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) protected uint MetadataChunkSize(byte[] metadataBytes)
{ {
uint metaSize = (uint)metadataBytes.Length; uint metaSize = (uint)metadataBytes.Length;
@ -103,6 +103,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
return metaChunkSize; 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(byte[] alphaBytes)
{
uint alphaSize = (uint)alphaBytes.Length + 1;
uint alphaChunkSize = WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
return alphaChunkSize;
}
/// <summary> /// <summary>
/// Writes a metadata profile (EXIF or XMP) to the stream. /// Writes a metadata profile (EXIF or XMP) to the stream.
/// </summary> /// </summary>
@ -128,6 +141,34 @@ 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>
protected void WriteAlphaChunk(Stream stream, byte[] dataBytes)
{
DebugGuard.NotNull(dataBytes, nameof(dataBytes));
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);
// Write flags, all zero for now.
stream.WriteByte(0);
stream.Write(dataBytes);
// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
}
/// <summary> /// <summary>
/// Writes a VP8X header to the stream. /// Writes a VP8X header to the stream.
/// </summary> /// </summary>

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

@ -409,7 +409,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// <param name="width">The width of the image.</param> /// <param name="width">The width of the image.</param>
/// <param name="height">The height 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> /// <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>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData)
{ {
bool isVp8X = false; bool isVp8X = false;
byte[] exifBytes = null; byte[] exifBytes = null;
@ -418,7 +419,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null) if (exifProfile != null)
{ {
isVp8X = true; isVp8X = true;
riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray(); exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes); riffSize += this.MetadataChunkSize(exifBytes);
} }
@ -426,11 +426,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (xmpProfile != null) if (xmpProfile != null)
{ {
isVp8X = true; isVp8X = true;
riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data; xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes); riffSize += this.MetadataChunkSize(xmpBytes);
} }
if (hasAlpha)
{
isVp8X = true;
riffSize += this.AlphaChunkSize(alphaData);
}
if (isVp8X)
{
riffSize += ExtendedFileChunkSize;
}
this.Finish(); this.Finish();
uint numBytes = (uint)this.NumBytes(); uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh; int mbSize = this.enc.Mbw * this.enc.Mbh;
@ -451,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0 // 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);
bitWriterPartZero.WriteToStream(stream); bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream. // Write the encoded image to the stream.
@ -639,7 +649,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
while (it.Next()); 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,
byte[] alphaData)
{ {
this.WriteRiffHeader(stream, riffSize); this.WriteRiffHeader(stream, riffSize);
@ -647,6 +668,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (isVp8X) if (isVp8X)
{ {
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha); this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
if (hasAlpha)
{
this.WriteAlphaChunk(stream, alphaData);
}
} }
this.WriteVp8Header(stream, vp8Size); this.WriteVp8Header(stream, vp8Size);

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

@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<byte> y = this.Y.GetSpan(); Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan(); Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.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 yStride = width;
int uvStride = (yStride + 1) >> 1; int uvStride = (yStride + 1) >> 1;
@ -322,8 +322,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock;
this.bitWriter = new Vp8BitWriter(expectedSize, this); this.bitWriter = new Vp8BitWriter(expectedSize, this);
// TODO: EncodeAlpha(); // Extract and encode alpha data, if present.
bool hasAlpha = false; byte[] alphaData = null;
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator);
}
// Stats-collection loop. // Stats-collection loop.
this.StatLoop(width, height, yStride, uvStride); this.StatLoop(width, height, yStride, uvStride);
@ -358,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Write bytes from the bitwriter buffer to the stream. // Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles(); 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);
} }
/// <inheritdoc/> /// <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="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="u">Span to store the u component of the image.</param>
/// <param name="v">Span to store the v 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> where TPixel : unmanaged, IPixel<TPixel>
{ {
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer; Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
@ -335,6 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<Bgra32> bgraRow1 = bgraRow1Buffer.GetSpan(); Span<Bgra32> bgraRow1 = bgraRow1Buffer.GetSpan();
int uvRowIndex = 0; int uvRowIndex = 0;
int rowIndex; int rowIndex;
bool hasAlpha = false;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{ {
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
@ -343,6 +345,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1); bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1);
if (rowsHaveAlpha)
{
hasAlpha = true;
}
// Downsample U/V planes, two rows at a time. // Downsample U/V planes, two rows at a time.
if (!rowsHaveAlpha) if (!rowsHaveAlpha)
@ -375,10 +381,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
else else
{ {
AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width); AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width);
hasAlpha = true;
} }
ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth); ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth);
} }
return hasAlpha;
} }
/// <summary> /// <summary>

Loading…
Cancel
Save