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.
/// </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 +103,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(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 +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>
/// Writes a VP8X header to the stream.
/// </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="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>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData)
{
bool isVp8X = false;
byte[] exifBytes = null;
@ -418,7 +419,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null)
{
isVp8X = true;
riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes);
}
@ -426,11 +426,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 +461,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);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@ -639,7 +649,18 @@ 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,
byte[] alphaData)
{
this.WriteRiffHeader(stream, riffSize);
@ -647,6 +668,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
if (hasAlpha)
{
this.WriteAlphaChunk(stream, alphaData);
}
}
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> 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;
@ -322,8 +322,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock;
this.bitWriter = new Vp8BitWriter(expectedSize, this);
// TODO: EncodeAlpha();
bool hasAlpha = false;
// Extract and encode alpha data, if present.
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.
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.
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);
}
/// <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>

Loading…
Cancel
Save