Browse Source

Fix issue with AccumulateRgba in the last row

pull/1552/head
Brian Popow 5 years ago
parent
commit
a534513ee9
  1. 15
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  2. 32
      src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs
  3. 2
      tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs

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

@ -791,7 +791,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.DqUvDc, 0, 117)]; m.Uv.Q[0] = WebpLookupTables.DcTable[Clip(q + this.DqUvDc, 0, 117)];
m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)]; m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)];
var qi4 = m.Y1.Expand(0); int qi4 = m.Y1.Expand(0);
m.Y2.Expand(1); // qi16 m.Y2.Expand(1); // qi16
m.Uv.Expand(2); // quv m.Uv.Expand(2); // quv
@ -808,7 +808,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
do do
{ {
it.Import(y, u, v, yStride, uvStride, width, height, true); it.Import(y, u, v, yStride, uvStride, width, height, true);
int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha); int bestAlpha = this.MbAnalyze(it, alphas, out int bestUvAlpha);
// Accumulate for later complexity analysis. // Accumulate for later complexity analysis.
alpha += bestAlpha; alpha += bestAlpha;
@ -1211,19 +1211,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int uvWidth = (image.Width + 1) >> 1; int uvWidth = (image.Width + 1) >> 1;
bool hasAlpha = YuvConversion.CheckNonOpaque(image);
// Temporary storage for accumulated R/G/B values during conversion to U/V. // 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<ushort> tmpRgb = this.memoryAllocator.Allocate<ushort>(4 * uvWidth);
Span<ushort> tmpRgbSpan = tmpRgb.GetSpan(); Span<ushort> tmpRgbSpan = tmpRgb.GetSpan();
int uvRowIndex = 0; int uvRowIndex = 0;
int rowIndex; int rowIndex;
bool rowsHaveAlpha = false;
for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2) for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2)
{ {
rowsHaveAlpha = YuvConversion.CheckNonOpaque(image, rowIndex, rowIndex + 1);
// Downsample U/V planes, two rows at a time. // Downsample U/V planes, two rows at a time.
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex); Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1);
if (!hasAlpha) if (!rowsHaveAlpha)
{ {
YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
} }
@ -1243,7 +1245,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if ((image.Height & 1) != 0) if ((image.Height & 1) != 0)
{ {
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex); Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
if (!hasAlpha) if (!rowsHaveAlpha)
{ {
YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width);
} }
@ -1269,6 +1271,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int Vp8Sse16X16(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 16); private static int Vp8Sse16X16(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 16);
[MethodImpl(InliningOptions.ShortMethod)]
private static int Vp8Sse16X8(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 8); private static int Vp8Sse16X8(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 8);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -1319,6 +1322,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// is around q=75. Internally, our "good" middle is around c=50. So we /// is around q=75. Internally, our "good" middle is around c=50. So we
/// map accordingly using linear piece-wise function /// map accordingly using linear piece-wise function
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
private static double QualityToCompression(double c) private static double QualityToCompression(double c)
{ {
double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d;
@ -1334,6 +1338,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return v; return v;
} }
[MethodImpl(InliningOptions.ShortMethod)]
private int FilterStrengthFromDelta(int sharpness, int delta) private int FilterStrengthFromDelta(int sharpness, int delta)
{ {
int pos = (delta < WebpConstants.MaxDelzaSize) ? delta : WebpConstants.MaxDelzaSize - 1; int pos = (delta < WebpConstants.MaxDelzaSize) ? delta : WebpConstants.MaxDelzaSize - 1;

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

@ -21,12 +21,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type of the image,</typeparam> /// <typeparam name="TPixel">The pixel type of the image,</typeparam>
/// <param name="image">The image to check.</param> /// <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>
/// <returns>Returns true if alpha has non-0xff values.</returns> /// <returns>Returns true if alpha has non-0xff values.</returns>
public static bool CheckNonOpaque<TPixel>(Image<TPixel> image) public static bool CheckNonOpaque<TPixel>(Image<TPixel> image, int rowIdxStart, int rowIdxEnd)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Rgba32 rgba = default; Rgba32 rgba = default;
for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) for (int rowIndex = rowIdxStart; rowIndex <= rowIdxEnd; rowIndex++)
{ {
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex); Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < image.Width; x++)
@ -43,6 +45,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return false; return false;
} }
/// <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) public static void ConvertRgbaToY<TPixel>(Span<TPixel> rowSpan, Span<byte> y, int width)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -55,6 +64,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
/// <summary>
/// Converts a rgb row of pixels to UV.
/// </summary>
/// <param name="rgb">The RGB pixel row.</param>
/// <param name="u">The destination span for u.</param>
/// <param name="v">The destination span for v.</param>
/// <param name="width">The width.</param>
public static void ConvertRgbaToUv(Span<ushort> rgb, Span<byte> u, Span<byte> v, int width) public static void ConvertRgbaToUv(Span<ushort> rgb, Span<byte> u, Span<byte> v, int width)
{ {
int rgbIdx = 0; int rgbIdx = 0;
@ -184,9 +200,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
else else
{ {
r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba0.R, rgba1.R, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a);
g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba0.G, rgba1.G, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a);
b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba0.B, rgba1.B, rgba0.A, rgba1.A, rgba0.A, rgba1.A, a);
} }
dst[dstIdx] = (ushort)r; dst[dstIdx] = (ushort)r;
@ -196,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA)
{ {
uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3));
@ -212,10 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static uint GammaToLinear(byte v) private static uint GammaToLinear(byte v) => WebpLookupTables.GammaToLinearTab[v];
{
return WebpLookupTables.GammaToLinearTab[v];
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int Interpolate(int v) private static int Interpolate(int v)

2
tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs

@ -13,7 +13,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP namespace SixLabors.ImageSharp.Tests.Formats.WebP
{ {
[Trait("Format", "WebpLossless")] [Trait("Format", "Webp")]
public class PredictorEncoderTests public class PredictorEncoderTests
{ {
[Fact] [Fact]

Loading…
Cancel
Save