Browse Source

Bulk convert rows to rgba

pull/1552/head
Brian Popow 5 years ago
parent
commit
92f9bbaac6
  1. 47
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  2. 88
      src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs
  3. 2
      src/ImageSharp/Formats/WebP/WebpEncoderCore.cs

47
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
@ -80,16 +85,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// Initializes a new instance of the <see cref="Vp8Encoder"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="width">The width of the input image.</param>
/// <param name="height">The height of the input image.</param>
/// <param name="quality">The encoding quality.</param>
/// <param name="method">Quality/speed trade-off (0=fast, 6=slower-better).</param>
/// <param name="entropyPasses">Number of entropy-analysis passes (in [1..10]).</param>
public Vp8Encoder(MemoryAllocator memoryAllocator, int width, int height, int quality, int method, int entropyPasses)
public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.Width = width;
this.Height = height;
this.memoryAllocator = memoryAllocator;
this.quality = Numerics.Clamp(quality, 0, 100);
this.method = Numerics.Clamp(method, 0, 6);
this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10);
@ -1210,51 +1217,59 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private void ConvertRgbToYuv<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int uvWidth = (image.Width + 1) >> 1;
int width = image.Width;
int height = image.Height;
int uvWidth = (width + 1) >> 1;
// Temporary storage for accumulated R/G/B values during conversion to U/V.
using IMemoryOwner<ushort> tmpRgb = this.memoryAllocator.Allocate<ushort>(4 * uvWidth);
using IMemoryOwner<Rgba32> rgbaRow0Buffer = this.memoryAllocator.Allocate<Rgba32>(width);
using IMemoryOwner<Rgba32> rgbaRow1Buffer = this.memoryAllocator.Allocate<Rgba32>(width);
Span<ushort> tmpRgbSpan = tmpRgb.GetSpan();
Span<Rgba32> rgbaRow0 = rgbaRow0Buffer.GetSpan();
Span<Rgba32> rgbaRow1 = rgbaRow1Buffer.GetSpan();
int uvRowIndex = 0;
int rowIndex;
bool rowsHaveAlpha = false;
for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2)
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{
rowsHaveAlpha = YuvConversion.CheckNonOpaque(image, rowIndex, rowIndex + 1);
// Downsample U/V planes, two rows at a time.
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1);
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, rowSpan, rgbaRow0);
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, nextRowSpan, rgbaRow1);
rowsHaveAlpha = YuvConversion.CheckNonOpaque(rgbaRow0) && YuvConversion.CheckNonOpaque(rgbaRow1);
// Downsample U/V planes, two rows at a time.
if (!rowsHaveAlpha)
{
YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow1, tmpRgbSpan, width);
}
else
{
YuvConversion.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow1, tmpRgbSpan, width);
}
YuvConversion.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth);
uvRowIndex++;
YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width);
YuvConversion.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width);
YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width);
YuvConversion.ConvertRgbaToY(rgbaRow1, this.Y.Slice((rowIndex + 1) * width), width);
}
// Extra last row.
if ((image.Height & 1) != 0)
if ((height & 1) != 0)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
if (!rowsHaveAlpha)
{
YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width);
YuvConversion.AccumulateRgb(rgbaRow0, rgbaRow0, tmpRgbSpan, width);
}
else
{
YuvConversion.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width);
YuvConversion.AccumulateRgba(rgbaRow0, rgbaRow0, tmpRgbSpan, width);
}
YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width);
YuvConversion.ConvertRgbaToY(rgbaRow0, this.Y.Slice(rowIndex * width), width);
}
}

88
src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs

@ -17,28 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int YuvHalf = 1 << (YuvFix - 1);
/// <summary>
/// Checks if the image is not opaque.
/// Checks if the pixel row is not opaque.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image,</typeparam>
/// <param name="image">The image to check.</param>
/// <param name="rowIdxStart">The row to start with.</param>
/// <param name="rowIdxEnd">The row to end with.</param>
/// <param name="row">The row to check.</param>
/// <returns>Returns true if alpha has non-0xff values.</returns>
public static bool CheckNonOpaque<TPixel>(Image<TPixel> image, int rowIdxStart, int rowIdxEnd)
where TPixel : unmanaged, IPixel<TPixel>
[MethodImpl(InliningOptions.ShortMethod)]
public static bool CheckNonOpaque(Span<Rgba32> row)
{
Rgba32 rgba = default;
for (int rowIndex = rowIdxStart; rowIndex <= rowIdxEnd; rowIndex++)
for (int x = 0; x < row.Length; x++)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
for (int x = 0; x < image.Width; x++)
if (row[x].A != 255)
{
TPixel color = rowSpan[x];
color.ToRgba32(ref rgba);
if (rgba.A != 255)
{
return true;
}
return true;
}
}
@ -48,19 +38,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// <summary>
/// Converts a rgba pixel row to Y.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="rowSpan">The row span to convert.</param>
/// <param name="y">The destination span for y.</param>
/// <param name="width">The width.</param>
public static void ConvertRgbaToY<TPixel>(Span<TPixel> rowSpan, Span<byte> y, int width)
where TPixel : unmanaged, IPixel<TPixel>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ConvertRgbaToY(Span<Rgba32> rowSpan, Span<byte> y, int width)
{
Rgba32 rgba = default;
for (int x = 0; x < width; x++)
{
TPixel color = rowSpan[x];
color.ToRgba32(ref rgba);
y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf);
y[x] = (byte)RgbToY(rowSpan[x].R, rowSpan[x].G, rowSpan[x].B, YuvHalf);
}
}
@ -82,25 +68,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
public static void AccumulateRgb<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
public static void AccumulateRgb(Span<Rgba32> rowSpan, Span<Rgba32> nextRowSpan, Span<ushort> dst, int width)
{
Rgba32 rgba0 = default;
Rgba32 rgba1 = default;
Rgba32 rgba2 = default;
Rgba32 rgba3 = default;
Rgba32 rgba0;
Rgba32 rgba1;
int i, j;
int dstIdx = 0;
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4)
{
TPixel color = rowSpan[j];
color.ToRgba32(ref rgba0);
color = rowSpan[j + 1];
color.ToRgba32(ref rgba1);
color = nextRowSpan[j];
color.ToRgba32(ref rgba2);
color = nextRowSpan[j + 1];
color.ToRgba32(ref rgba3);
rgba0 = rowSpan[j];
rgba1 = rowSpan[j + 1];
Rgba32 rgba2 = nextRowSpan[j];
Rgba32 rgba3 = nextRowSpan[j + 1];
dst[dstIdx] = (ushort)LinearToGamma(
GammaToLinear(rgba0.R) +
@ -121,10 +100,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if ((width & 1) != 0)
{
TPixel color = rowSpan[j];
color.ToRgba32(ref rgba0);
color = nextRowSpan[j];
color.ToRgba32(ref rgba1);
rgba0 = rowSpan[j];
rgba1 = nextRowSpan[j];
dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1);
dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1);
@ -132,25 +109,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
public static void AccumulateRgba<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
public static void AccumulateRgba(Span<Rgba32> rowSpan, Span<Rgba32> nextRowSpan, Span<ushort> dst, int width)
{
Rgba32 rgba0 = default;
Rgba32 rgba1 = default;
Rgba32 rgba2 = default;
Rgba32 rgba3 = default;
Rgba32 rgba0;
Rgba32 rgba1;
int i, j;
int dstIdx = 0;
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4)
{
TPixel color = rowSpan[j];
color.ToRgba32(ref rgba0);
color = rowSpan[j + 1];
color.ToRgba32(ref rgba1);
color = nextRowSpan[j];
color.ToRgba32(ref rgba2);
color = nextRowSpan[j + 1];
color.ToRgba32(ref rgba3);
rgba0 = rowSpan[j];
rgba1 = rowSpan[j + 1];
Rgba32 rgba2 = nextRowSpan[j];
Rgba32 rgba3 = nextRowSpan[j + 1];
uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A);
int r, g, b;
if (a == 4 * 0xff || a == 0)
@ -186,10 +156,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if ((width & 1) != 0)
{
TPixel color = rowSpan[j];
color.ToRgba32(ref rgba0);
color = nextRowSpan[j];
color.ToRgba32(ref rgba1);
rgba0 = rowSpan[j];
rgba1 = nextRowSpan[j];
uint a = (uint)(2u * (rgba0.A + rgba1.A));
int r, g, b;
if (a == 4 * 0xff || a == 0)

2
src/ImageSharp/Formats/WebP/WebpEncoderCore.cs

@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
if (this.lossy)
{
var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height, this.quality, this.method, this.entropyPasses);
var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses);
enc.Encode(image, stream);
}
else

Loading…
Cancel
Save