Browse Source

Remove Image.DangerousGetRowSpan

af/UniformUnmanagedMemoryPoolMemoryAllocator-02-MemoryGuards
Anton Firszov 5 years ago
parent
commit
ebe7b9c516
  1. 50
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 26
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  3. 3
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  4. 11
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  5. 20
      src/ImageSharp/Image{TPixel}.cs
  6. 5
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
  7. 17
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  8. 48
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  9. 15
      tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs
  10. 63
      tests/ImageSharp.Tests/Image/ImageCloneTests.cs
  11. 71
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  12. 1
      tests/ImageSharp.Tests/Image/ImageTests.cs
  13. 17
      tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs
  14. 62
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  15. 32
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs

50
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -165,20 +165,24 @@ namespace SixLabors.ImageSharp.Formats.Png
private static void ClearTransparentPixels<TPixel>(Image<TPixel> image) private static void ClearTransparentPixels<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Rgba32 rgba32 = default; image.ProcessPixelRows(accessor =>
for (int y = 0; y < image.Height; y++)
{ {
Span<TPixel> span = image.DangerousGetRowSpan(y); Rgba32 rgba32 = default;
for (int x = 0; x < image.Width; x++) for (int y = 0; y < accessor.Height; y++)
{ {
span[x].ToRgba32(ref rgba32); Span<TPixel> span = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
if (rgba32.A == 0)
{ {
span[x].FromRgba32(Color.Transparent); span[x].ToRgba32(ref rgba32);
if (rgba32.A == 0)
{
span[x].FromRgba32(Color.Transparent);
}
} }
} }
} });
} }
/// <summary> /// <summary>
@ -914,27 +918,31 @@ namespace SixLabors.ImageSharp.Formats.Png
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean); using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean); using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> filter = filterBuffer.GetSpan(); pixels.ProcessPixelRows(accessor =>
Span<byte> attempt = attemptBuffer.GetSpan();
for (int y = 0; y < this.height; y++)
{ {
this.CollectAndFilterPixelRow(pixels.DangerousGetRowSpan(y), ref filter, ref attempt, quantized, y); Span<byte> filter = filterBuffer.GetSpan();
deflateStream.Write(filter); Span<byte> attempt = attemptBuffer.GetSpan();
this.SwapScanlineBuffers(); for (int y = 0; y < this.height; y++)
} {
this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(filter);
this.SwapScanlineBuffers();
}
});
} }
/// <summary> /// <summary>
/// Interlaced encoding the pixels. /// Interlaced encoding the pixels.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="pixels">The pixels.</param> /// <param name="image">The image.</param>
/// <param name="deflateStream">The deflate stream.</param> /// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7Pixels<TPixel>(Image<TPixel> pixels, ZlibDeflateStream deflateStream) private void EncodeAdam7Pixels<TPixel>(Image<TPixel> image, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = pixels.Width; int width = image.Width;
int height = pixels.Height; int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.Frames.RootFrame.PixelBuffer;
for (int pass = 0; pass < 7; pass++) for (int pass = 0; pass < 7; pass++)
{ {
int startRow = Adam7.FirstRow[pass]; int startRow = Adam7.FirstRow[pass];
@ -959,7 +967,7 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) for (int row = startRow; row < height; row += Adam7.RowIncrement[pass])
{ {
// Collect pixel data // Collect pixel data
Span<TPixel> srcRow = pixels.DangerousGetRowSpan(row); Span<TPixel> srcRow = pixelBuffer.DangerousGetRowSpan(row);
for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass])
{ {
block[i++] = srcRow[col]; block[i++] = srcRow[col];

26
src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs

@ -42,18 +42,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
// Special case for T4BitCompressor. // Special case for T4BitCompressor.
int stripPixels = width * height; int stripPixels = width * height;
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(stripPixels); this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(stripPixels);
Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan(); this.imageBlackWhite.ProcessPixelRows(accessor =>
int lastRow = y + height;
int grayRowIdx = 0;
for (int row = y; row < lastRow; row++)
{ {
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan();
Span<byte> pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); int lastRow = y + height;
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); int grayRowIdx = 0;
grayRowIdx++; for (int row = y; row < lastRow; row++)
} {
Span<TPixel> pixelsBlackWhiteRow = accessor.GetRowSpan(row);
Span<byte> pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width);
grayRowIdx++;
}
compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height);
});
} }
else else
{ {
@ -65,6 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip); Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip);
rows.Clear(); rows.Clear();
Buffer2D<TPixel> blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer;
int outputRowIdx = 0; int outputRowIdx = 0;
int lastRow = y + height; int lastRow = y + height;
@ -73,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int bitIndex = 0; int bitIndex = 0;
int byteIndex = 0; int byteIndex = 0;
Span<byte> outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); Span<byte> outputRow = rows.Slice(outputRowIdx * this.BytesPerRow);
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); Span<TPixel> pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width);
for (int x = 0; x < this.Image.Width; x++) for (int x = 0; x < this.Image.Width; x++)
{ {

3
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -389,13 +389,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private bool ConvertPixelsToBgra<TPixel>(Image<TPixel> image, int width, int height) private bool ConvertPixelsToBgra<TPixel>(Image<TPixel> image, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
bool nonOpaque = false; bool nonOpaque = false;
Span<uint> bgra = this.Bgra.GetSpan(); Span<uint> bgra = this.Bgra.GetSpan();
Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(bgra); Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(bgra);
int widthBytes = width * 4; int widthBytes = width * 4;
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> rowSpan = image.DangerousGetRowSpan(y); Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
Span<byte> rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); Span<byte> rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width);
if (!nonOpaque) if (!nonOpaque)

11
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -31,8 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static void ConvertRgbToYuv<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v) public static void 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>
{ {
int width = image.Width; Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
int height = image.Height; int width = imageBuffer.Width;
int height = imageBuffer.Height;
int uvWidth = (width + 1) >> 1; int uvWidth = (width + 1) >> 1;
// 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.
@ -46,8 +47,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int rowIndex; int rowIndex;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{ {
Span<TPixel> rowSpan = image.DangerousGetRowSpan(rowIndex); Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.DangerousGetRowSpan(rowIndex + 1); Span<TPixel> nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Extra last row. // Extra last row.
if ((height & 1) != 0) if ((height & 1) != 0)
{ {
Span<TPixel> rowSpan = image.DangerousGetRowSpan(rowIndex); Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width);

20
src/ImageSharp/Image{TPixel}.cs

@ -300,26 +300,6 @@ namespace SixLabors.ImageSharp
} }
} }
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// <para />
/// WARNING: Disposing or leaking the underlying image while still working with it's <see cref="Span{T}"/>
/// might lead to memory corruption.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> DangerousGetRowSpan(int rowIndex)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
this.EnsureNotDisposed();
return this.PixelSourceUnsafe.PixelBuffer.DangerousGetRowSpan(rowIndex);
}
/// <summary> /// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> in the source image's pixel format /// Gets the representation of the pixels as a <see cref="Memory{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous. /// stored in row major order, if the backing buffer is contiguous.

5
src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs

@ -30,11 +30,12 @@ namespace SixLabors.ImageSharp.Processing
Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height); Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height);
ulong sumX0 = 0; ulong sumX0 = 0;
Buffer2D<TPixel> sourceBuffer = source.Frames.RootFrame.PixelBuffer;
using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width)) using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width))
{ {
Span<L8> tempSpan = tempRow.GetSpan(); Span<L8> tempSpan = tempRow.GetSpan();
Span<TPixel> sourceRow = source.DangerousGetRowSpan(0); Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(0);
Span<ulong> destRow = intImage.DangerousGetRowSpan(0); Span<ulong> destRow = intImage.DangerousGetRowSpan(0);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan); PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
// All other rows // All other rows
for (int y = 1; y < endY; y++) for (int y = 1; y < endY; y++)
{ {
sourceRow = source.DangerousGetRowSpan(y); sourceRow = sourceBuffer.DangerousGetRowSpan(y);
destRow = intImage.DangerousGetRowSpan(y); destRow = intImage.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan); PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);

17
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -148,22 +148,5 @@ namespace SixLabors.ImageSharp.Tests.Advanced
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory3.Span); Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory3.Span);
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory10.Span); Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory10.Span);
} }
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
[WithBlankImages(100, 111, PixelTypes.Rgba32)]
[WithBlankImages(400, 600, PixelTypes.Rgba32)]
public void DangerousGetRowSpan_ShouldReferenceSpanOfMemory<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
using Image<TPixel> image = provider.GetImage();
Memory<TPixel> memory = image.DangerousGetPixelRowMemory(image.Height - 1);
Span<TPixel> span = image.DangerousGetRowSpan(image.Height - 1);
Assert.True(span == memory.Span);
}
} }
} }

48
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -411,21 +411,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
ColorType = colorType ColorType = colorType
}; };
Rgba32 rgba32 = Color.Blue; Rgba32 rgba32 = Color.Blue;
for (int y = 0; y < image.Height; y++) image.ProcessPixelRows(accessor =>
{ {
System.Span<Rgba32> rowSpan = image.DangerousGetRowSpan(y); for (int y = 0; y < image.Height; y++)
// Half of the test image should be transparent.
if (y > 25)
{ {
rgba32.A = 0; System.Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
}
for (int x = 0; x < image.Width; x++) // Half of the test image should be transparent.
{ if (y > 25)
rowSpan[x].FromRgba32(rgba32); {
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x].FromRgba32(rgba32);
}
} }
} });
// act // act
using var memStream = new MemoryStream(); using var memStream = new MemoryStream();
@ -441,20 +444,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
expectedColor = new Rgba32(luminance, luminance, luminance); expectedColor = new Rgba32(luminance, luminance, luminance);
} }
for (int y = 0; y < actual.Height; y++) actual.ProcessPixelRows(accessor =>
{ {
System.Span<Rgba32> rowSpan = actual.DangerousGetRowSpan(y); for (int y = 0; y < accessor.Height; y++)
if (y > 25)
{ {
expectedColor = Color.Transparent; System.Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
}
for (int x = 0; x < actual.Width; x++) if (y > 25)
{ {
Assert.Equal(expectedColor, rowSpan[x]); expectedColor = Color.Transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
} }
} });
} }
[Theory] [Theory]

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

@ -131,15 +131,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
uint[] bgra = new uint[image.Width * image.Height]; uint[] bgra = new uint[image.Width * image.Height];
int idx = 0; image.ProcessPixelRows(accessor =>
for (int y = 0; y < image.Height; y++)
{ {
Span<TPixel> rowSpan = image.DangerousGetRowSpan(y); int idx = 0;
for (int x = 0; x < rowSpan.Length; x++) for (int y = 0; y < accessor.Height; y++)
{ {
bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; Span<TPixel> rowSpan = accessor.GetRowSpan(y);
for (int x = 0; x < rowSpan.Length; x++)
{
bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue;
}
} }
} });
return bgra; return bgra;
} }

63
tests/ImageSharp.Tests/Image/ImageCloneTests.cs

@ -32,15 +32,17 @@ namespace SixLabors.ImageSharp.Tests
[WithTestPatternImages(9, 9, PixelTypes.Rgba32)] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)]
public void CloneAs_ToBgra32(TestImageProvider<Rgba32> provider) public void CloneAs_ToBgra32(TestImageProvider<Rgba32> provider)
{ {
using (Image<Rgba32> image = provider.GetImage()) using Image<Rgba32> image = provider.GetImage();
using (Image<Bgra32> clone = image.CloneAs<Bgra32>()) using Image<Bgra32> clone = image.CloneAs<Bgra32>();
image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) =>
{ {
for (int y = 0; y < image.Height; y++) for (int y = 0; y < imageAccessor.Height; y++)
{ {
Span<Rgba32> row = image.DangerousGetRowSpan(y); Span<Rgba32> row = imageAccessor.GetRowSpan(y);
Span<Bgra32> rowClone = clone.DangerousGetRowSpan(y); Span<Bgra32> rowClone = cloneAccessor.GetRowSpan(y);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < imageAccessor.Width; x++)
{ {
Rgba32 expected = row[x]; Rgba32 expected = row[x];
Bgra32 actual = rowClone[x]; Bgra32 actual = rowClone[x];
@ -51,22 +53,24 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(expected.A, actual.A); Assert.Equal(expected.A, actual.A);
} }
} }
} });
} }
[Theory] [Theory]
[WithTestPatternImages(9, 9, PixelTypes.Rgba32)] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)]
public void CloneAs_ToBgr24(TestImageProvider<Rgba32> provider) public void CloneAs_ToBgr24(TestImageProvider<Rgba32> provider)
{ {
using (Image<Rgba32> image = provider.GetImage()) using Image<Rgba32> image = provider.GetImage();
using (Image<Bgr24> clone = image.CloneAs<Bgr24>()) using Image<Bgr24> clone = image.CloneAs<Bgr24>();
image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) =>
{ {
for (int y = 0; y < image.Height; y++) for (int y = 0; y < imageAccessor.Height; y++)
{ {
Span<Rgba32> row = image.DangerousGetRowSpan(y); Span<Rgba32> row = imageAccessor.GetRowSpan(y);
Span<Bgr24> rowClone = clone.DangerousGetRowSpan(y); Span<Bgr24> rowClone = cloneAccessor.GetRowSpan(y);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < cloneAccessor.Width; x++)
{ {
Rgba32 expected = row[x]; Rgba32 expected = row[x];
Bgr24 actual = rowClone[x]; Bgr24 actual = rowClone[x];
@ -76,22 +80,23 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(expected.B, actual.B); Assert.Equal(expected.B, actual.B);
} }
} }
} });
} }
[Theory] [Theory]
[WithTestPatternImages(9, 9, PixelTypes.Rgba32)] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)]
public void CloneAs_ToArgb32(TestImageProvider<Rgba32> provider) public void CloneAs_ToArgb32(TestImageProvider<Rgba32> provider)
{ {
using (Image<Rgba32> image = provider.GetImage()) using Image<Rgba32> image = provider.GetImage();
using (Image<Argb32> clone = image.CloneAs<Argb32>()) using Image<Argb32> clone = image.CloneAs<Argb32>();
image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) =>
{ {
for (int y = 0; y < image.Height; y++) for (int y = 0; y < imageAccessor.Height; y++)
{ {
Span<Rgba32> row = image.DangerousGetRowSpan(y); Span<Rgba32> row = imageAccessor.GetRowSpan(y);
Span<Argb32> rowClone = clone.DangerousGetRowSpan(y); Span<Argb32> rowClone = cloneAccessor.GetRowSpan(y);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < cloneAccessor.Width; x++)
{ {
Rgba32 expected = row[x]; Rgba32 expected = row[x];
Argb32 actual = rowClone[x]; Argb32 actual = rowClone[x];
@ -102,22 +107,23 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(expected.A, actual.A); Assert.Equal(expected.A, actual.A);
} }
} }
} });
} }
[Theory] [Theory]
[WithTestPatternImages(9, 9, PixelTypes.Rgba32)] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)]
public void CloneAs_ToRgb24(TestImageProvider<Rgba32> provider) public void CloneAs_ToRgb24(TestImageProvider<Rgba32> provider)
{ {
using (Image<Rgba32> image = provider.GetImage()) using Image<Rgba32> image = provider.GetImage();
using (Image<Rgb24> clone = image.CloneAs<Rgb24>()) using Image<Rgb24> clone = image.CloneAs<Rgb24>();
image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) =>
{ {
for (int y = 0; y < image.Height; y++) for (int y = 0; y < imageAccessor.Height; y++)
{ {
Span<Rgba32> row = image.DangerousGetRowSpan(y); Span<Rgba32> row = imageAccessor.GetRowSpan(y);
Span<Rgb24> rowClone = clone.DangerousGetRowSpan(y); Span<Rgb24> rowClone = cloneAccessor.GetRowSpan(y);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < imageAccessor.Width; x++)
{ {
Rgba32 expected = row[x]; Rgba32 expected = row[x];
Rgb24 actual = rowClone[x]; Rgb24 actual = rowClone[x];
@ -127,7 +133,8 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(expected.B, actual.B); Assert.Equal(expected.B, actual.B);
} }
} }
} });
} }
} }
} }

71
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -163,10 +163,14 @@ namespace SixLabors.ImageSharp.Tests
{ {
Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory());
image.GetPixelMemoryGroup().Fill(bg); image.GetPixelMemoryGroup().Fill(bg);
for (var i = 10; i < 20; i++)
image.ProcessPixelRows(accessor =>
{ {
image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); for (var i = 10; i < 20; i++)
} {
accessor.GetRowSpan(i).Slice(10, 10).Fill(fg);
}
});
} }
Assert.False(memoryManager.IsDisposed); Assert.False(memoryManager.IsDisposed);
@ -198,10 +202,13 @@ namespace SixLabors.ImageSharp.Tests
{ {
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory());
image.GetPixelMemoryGroup().Fill(bg); image.GetPixelMemoryGroup().Fill(bg);
for (var i = 10; i < 20; i++) image.ProcessPixelRows(accessor =>
{ {
image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); for (var i = 10; i < 20; i++)
} {
accessor.GetRowSpan(i).Slice(10, 10).Fill(fg);
}
});
} }
Assert.True(memoryManager.IsDisposed); Assert.True(memoryManager.IsDisposed);
@ -263,10 +270,13 @@ namespace SixLabors.ImageSharp.Tests
Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));
image.GetPixelMemoryGroup().Fill(bg); image.GetPixelMemoryGroup().Fill(bg);
for (var i = 10; i < 20; i++) image.ProcessPixelRows(accessor =>
{ {
image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); for (var i = 10; i < 20; i++)
} {
accessor.GetRowSpan(i).Slice(10, 10).Fill(fg);
}
});
} }
Assert.False(memoryManager.IsDisposed); Assert.False(memoryManager.IsDisposed);
@ -332,10 +342,13 @@ namespace SixLabors.ImageSharp.Tests
Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));
image.GetPixelMemoryGroup().Fill(bg); image.GetPixelMemoryGroup().Fill(bg);
for (var i = 10; i < 20; i++) image.ProcessPixelRows(accessor =>
{ {
image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); for (var i = 10; i < 20; i++)
} {
accessor.GetRowSpan(i).Slice(10, 10).Fill(fg);
}
});
} }
Assert.False(memoryManager.IsDisposed); Assert.False(memoryManager.IsDisposed);
@ -413,16 +426,19 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(width, img.Width); Assert.Equal(width, img.Width);
Assert.Equal(height, img.Height); Assert.Equal(height, img.Height);
for (int i = 0; i < height; ++i) img.ProcessPixelRows(accessor =>
{ {
var arrayIndex = width * i; for (int i = 0; i < height; ++i)
{
var arrayIndex = width * i;
Span<Rgba32> rowSpan = img.DangerousGetRowSpan(i); Span<Rgba32> rowSpan = accessor.GetRowSpan(i);
ref Rgba32 r0 = ref rowSpan[0]; ref Rgba32 r0 = ref rowSpan[0];
ref Rgba32 r1 = ref array[arrayIndex]; ref Rgba32 r1 = ref array[arrayIndex];
Assert.True(Unsafe.AreSame(ref r0, ref r1)); Assert.True(Unsafe.AreSame(ref r0, ref r1));
} }
});
} }
Assert.True(memory.Disposed); Assert.True(memory.Disposed);
@ -457,16 +473,19 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(width, img.Width); Assert.Equal(width, img.Width);
Assert.Equal(height, img.Height); Assert.Equal(height, img.Height);
for (int i = 0; i < height; ++i) img.ProcessPixelRows(acccessor =>
{ {
var arrayIndex = pixelSize * width * i; for (int i = 0; i < height; ++i)
{
var arrayIndex = pixelSize * width * i;
Span<Rgba32> rowSpan = img.DangerousGetRowSpan(i); Span<Rgba32> rowSpan = acccessor.GetRowSpan(i);
ref Rgba32 r0 = ref rowSpan[0]; ref Rgba32 r0 = ref rowSpan[0];
ref Rgba32 r1 = ref Unsafe.As<byte, Rgba32>(ref array[arrayIndex]); ref Rgba32 r1 = ref Unsafe.As<byte, Rgba32>(ref array[arrayIndex]);
Assert.True(Unsafe.AreSame(ref r0, ref r1)); Assert.True(Unsafe.AreSame(ref r0, ref r1));
} }
});
} }
Assert.True(memory.Disposed); Assert.True(memory.Disposed);

1
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -271,7 +271,6 @@ namespace SixLabors.ImageSharp.Tests
// Image<TPixel> // Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => { var res = image.Clone(this.configuration); }); Assert.Throws<ObjectDisposedException>(() => { var res = image.Clone(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.CloneAs<Rgba32>(this.configuration); }); Assert.Throws<ObjectDisposedException>(() => { var res = image.CloneAs<Rgba32>(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.DangerousGetRowSpan(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _); }); Assert.Throws<ObjectDisposedException>(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _); });
// Image // Image

17
tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs

@ -154,6 +154,23 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
[Fact]
public void Disposed_ThrowsObjectDisposedException()
{
using var nonDisposed = new Image<L16>(1, 1);
var disposed = new Image<L16>(1, 1);
disposed.Dispose();
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(disposed, _ => { }));
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { }));
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { }));
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { }));
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { }));
Assert.Throws<ObjectDisposedException>(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { }));
}
[Fact] [Fact]
public void RetainsUnmangedBuffers1() public void RetainsUnmangedBuffers1()
{ {

62
tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

@ -93,25 +93,31 @@ namespace SixLabors.ImageSharp.Tests.Quantization
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);
Assert.Equal(256, result.Height); Assert.Equal(256, result.Height);
var actualImage = new Image<Rgba32>(1, 256); using var actualImage = new Image<Rgba32>(1, 256);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; actualImage.ProcessPixelRows(accessor =>
int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.DangerousGetRowSpan(y); ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
ReadOnlySpan<byte> quantizedPixelSpan = result.DangerousGetRowSpan(y); int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < accessor.Height; y++)
for (int x = 0; x < actualImage.Width; x++)
{ {
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; Span<Rgba32> row = accessor.GetRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.DangerousGetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
}
} }
} });
for (int y = 0; y < image.Height; y++) image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) =>
{ {
Assert.True(image.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); for (int y = 0; y < imageAccessor.Height; y++)
} {
Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y)));
}
});
} }
[Theory] [Theory]
@ -162,24 +168,30 @@ namespace SixLabors.ImageSharp.Tests.Quantization
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);
Assert.Equal(256, result.Height); Assert.Equal(256, result.Height);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; actualImage.ProcessPixelRows(accessor =>
int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.DangerousGetRowSpan(y); ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
ReadOnlySpan<byte> quantizedPixelSpan = result.DangerousGetRowSpan(y); int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < accessor.Height; y++)
for (int x = 0; x < actualImage.Width; x++)
{ {
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; Span<Rgba32> row = accessor.GetRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.DangerousGetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
}
} }
} });
} }
for (int y = 0; y < expectedImage.Height; y++) expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) =>
{ {
Assert.True(expectedImage.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); for (int y = 0; y < expectedAccessor.Height; y++)
} {
Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y)));
}
});
} }
} }
} }

32
tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs

@ -39,25 +39,29 @@ namespace SixLabors.ImageSharp.Tests
public override Image<TPixel> GetImage() public override Image<TPixel> GetImage()
{ {
var result = new Image<TPixel>(this.Configuration, this.Width, this.Height); var result = new Image<TPixel>(this.Configuration, this.Width, this.Height);
result.ProcessPixelRows(accessor =>
{
int midY = this.Height / 2;
int midX = this.Width / 2;
int midY = this.Height / 2; for (int y = 0; y < midY; y++)
int midX = this.Width / 2; {
Span<TPixel> row = accessor.GetRowSpan(y);
for (int y = 0; y < midY; y++) row.Slice(0, midX).Fill(TopLeftColor);
{ row.Slice(midX, this.Width - midX).Fill(TopRightColor);
Span<TPixel> row = result.DangerousGetRowSpan(y); }
row.Slice(0, midX).Fill(TopLeftColor); for (int y = midY; y < this.Height; y++)
row.Slice(midX, this.Width - midX).Fill(TopRightColor); {
} Span<TPixel> row = accessor.GetRowSpan(y);
row.Slice(0, midX).Fill(BottomLeftColor);
row.Slice(midX, this.Width - midX).Fill(BottomRightColor);
}
});
for (int y = midY; y < this.Height; y++)
{
Span<TPixel> row = result.DangerousGetRowSpan(y);
row.Slice(0, midX).Fill(BottomLeftColor);
row.Slice(midX, this.Width - midX).Fill(BottomRightColor);
}
return result; return result;
} }

Loading…
Cancel
Save