Browse Source

Change SpectralConverter<T> to use inplace converters and PackFromRgbPlanes

pull/1773/head
Anton Firszov 5 years ago
parent
commit
5f90e020fc
  1. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  2. 64
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  4. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  5. 31
      src/ImageSharp/Memory/Buffer2D{T}.cs
  6. 29
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
  7. 4
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs
  8. 4
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs
  9. 14
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  10. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  11. 26
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -234,9 +234,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public ComponentValues Slice(int start, int length)
{
Span<float> c0 = this.Component0.Slice(start, length);
Span<float> c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : Span<float>.Empty;
Span<float> c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : Span<float>.Empty;
Span<float> c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : Span<float>.Empty;
Span<float> c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : Span<float>.Empty;
Span<float> c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : Span<float>.Empty;
Span<float> c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : Span<float>.Empty;
return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
}

64
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -22,7 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private JpegColorConverter colorConverter;
private IMemoryOwner<Vector4> rgbaBuffer;
// private IMemoryOwner<Vector4> rgbaBuffer;
private IMemoryOwner<byte> rgbBuffer;
private IMemoryOwner<TPixel> paddedProxyPixelRow;
private Buffer2D<TPixel> pixelBuffer;
@ -40,23 +43,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height;
public Buffer2D<TPixel> PixelBuffer
public Buffer2D<TPixel> GetPixelBuffer()
{
get
if (!this.Converted)
{
if (!this.Converted)
{
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep);
int steps = (int) Math.Ceiling(this.pixelBuffer.Height / (float) this.pixelRowsPerStep);
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
this.ConvertNextStride(step);
}
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
this.ConvertNextStride(step);
}
return this.pixelBuffer;
}
return this.pixelBuffer;
}
/// <inheritdoc/>
@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight;
// pixel buffer for resulting image
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight, AllocationOptions.Clean);
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight);
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(frame.PixelWidth + 3);
// component processors from spectral to Rgba32
var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep);
@ -83,7 +84,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
// this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
this.rgbBuffer = allocator.Allocate<byte>(frame.PixelWidth * 3);
// color converter from Rgba32 to TPixel
this.colorConverter = this.GetColorConverter(frame, jpegData);
@ -115,7 +117,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
this.rgbaBuffer?.Dispose();
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
private void ConvertNextStride(int spectralStep)
@ -129,17 +132,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
buffers[i] = this.componentProcessors[i].ColorBuffer;
}
int width = this.pixelBuffer.Width;
for (int yy = this.pixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan());
Span<TPixel> destRow = this.pixelBuffer.GetRowSpan(yy);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
// TODO: Investigate if slicing is actually necessary
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow);
Span<byte> r = this.rgbBuffer.Slice(0, width);
Span<byte> g = this.rgbBuffer.Slice(width, width);
Span<byte> b = this.rgbBuffer.Slice(width * 2, width);
SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r);
SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g);
SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b);
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow);
}
else
{
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
}
}
this.pixelRowCounter += this.pixelRowsPerStep;

2
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return new Image<TPixel>(this.Configuration, spectralConverter.PixelBuffer, this.Metadata);
return new Image<TPixel>(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata);
}
/// <inheritdoc/>

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
scanDecoder.ResetInterval = 0;
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
CopyImageBytesToBuffer(buffer, spectralConverter.PixelBuffer);
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer());
}
else
{

31
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -116,6 +116,36 @@ namespace SixLabors.ImageSharp.Memory
: this.GetRowMemorySlow(y).Span;
}
internal bool TryGetPaddedRowSpan(int y, int padding, out Span<T> paddedSpan)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
int stride = this.Width + padding;
if (this.cachedMemory.Length > 0)
{
paddedSpan = this.cachedMemory.Span.Slice(y * this.Width);
if (paddedSpan.Length < stride)
{
return false;
}
paddedSpan = paddedSpan.Slice(0, stride);
return true;
}
Memory<T> memory = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width);
if (memory.Length < stride)
{
paddedSpan = default;
return false;
}
paddedSpan = memory.Span.Slice(0, stride);
return true;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal ref T GetElementUnsafe(int x, int y)
{
@ -202,6 +232,7 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single();

29
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs

@ -61,6 +61,35 @@ namespace SixLabors.ImageSharp.Memory
return memory.Slice(bufferStart, length);
}
/// <summary>
/// Returns the slice of the buffer starting at global index <paramref name="start"/> that goes until the end of the buffer.
/// </summary>
internal static Memory<T> GetRemainingSliceOfBuffer<T>(this IMemoryGroup<T> group, long start)
where T : struct
{
Guard.NotNull(group, nameof(group));
Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!");
Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
int bufferIdx = (int)(start / group.BufferLength);
if (bufferIdx < 0)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (bufferIdx >= group.Count)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
int bufferStart = (int)(start % group.BufferLength);
Memory<T> memory = group[bufferIdx];
return memory.Slice(bufferStart);
}
internal static void CopyTo<T>(this IMemoryGroup<T> source, Span<T> target)
where T : struct
{

4
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs

@ -32,9 +32,7 @@ namespace SixLabors.ImageSharp.PixelFormats
{
Guard.NotNull(configuration, nameof(configuration));
int count = redChannel.Length;
Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!");
Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!");
Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!");
GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count);
SimdUtils.PackFromRgbPlanes(configuration, redChannel, greenChannel, blueChannel, destination);
}

4
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs

@ -67,9 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats
{
Guard.NotNull(configuration, nameof(configuration));
int count = redChannel.Length;
Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!");
Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!");
Guard.IsTrue(destination.Length > count, nameof(destination), "'destination' span should not be shorter than the source channels!");
GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count);
SimdUtils.PackFromRgbPlanes(configuration, redChannel, greenChannel, blueChannel, destination);
}

14
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -181,11 +181,7 @@ namespace SixLabors.ImageSharp.PixelFormats
Guard.NotNull(configuration, nameof(configuration));
int count = redChannel.Length;
Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!");
Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!");
Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!");
Guard.DestinationShouldNotBeTooShort(redChannel, destination, nameof(destination));
GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count);
Rgb24 rgb24 = default;
ref byte r = ref MemoryMarshal.GetReference(redChannel);
@ -201,5 +197,13 @@ namespace SixLabors.ImageSharp.PixelFormats
Unsafe.Add(ref d, i).FromRgb24(rgb24);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
internal static void GuardPackFromRgbPlanes(ReadOnlySpan<byte> greenChannel, ReadOnlySpan<byte> blueChannel, Span<TPixel> destination, int count)
{
Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!");
Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!");
Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!");
}
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.PixelBuffer, new ImageMetadata()))
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(), new ImageMetadata()))
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
{
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);

26
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -118,6 +118,32 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
}
[Theory]
[InlineData(10, 0, 0, 0)]
[InlineData(10, 0, 2, 0)]
[InlineData(10, 1, 2, 0)]
[InlineData(10, 1, 3, 0)]
[InlineData(10, 1, 5, -1)]
[InlineData(10, 2, 2, -1)]
[InlineData(10, 3, 2, 1)]
[InlineData(10, 4, 2, -1)]
[InlineData(30, 3, 2, 0)]
[InlineData(30, 4, 1, -1)]
public void TryGetPaddedRowSpanY(int bufferCapacity, int y, int padding, int expectedBufferIndex)
{
this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity;
using Buffer2D<byte> buffer = this.MemoryAllocator.Allocate2D<byte>(3, 5);
bool expectSuccess = expectedBufferIndex >= 0;
bool success = buffer.TryGetPaddedRowSpan(y, padding, out Span<byte> paddedSpan);
Xunit.Assert.Equal(expectSuccess, success);
if (success)
{
int expectedSubBufferOffset = (3 * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength);
Assert.SpanPointsTo(paddedSpan, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset);
}
}
public static TheoryData<int, int, int, int> GetRowSpanY_OutOfRange_Data = new TheoryData<int, int, int, int>()
{
{ Big, 10, 8, -1 },

Loading…
Cancel
Save