diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index c8bbf01c7..61fcb99db 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.Primitives; @@ -27,8 +29,8 @@ namespace SixLabors.ImageSharp.Memory { DebugGuard.NotNull(buffer, nameof(buffer)); DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); - DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, sourceIndex + columnCount, nameof(destIndex)); - DebugGuard.MustBeLessThanOrEqualTo(destIndex, buffer.Width - columnCount, nameof(destIndex)); + DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); + CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); int elementSize = Unsafe.SizeOf(); int width = buffer.Width * elementSize; @@ -36,9 +38,11 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - using (MemoryHandle handle = buffer.Memory.Pin()) + Span span = MemoryMarshal.AsBytes(buffer.Memory.Span); + + fixed (byte* ptr = span) { - byte* basePtr = (byte*)handle.Pointer; + byte* basePtr = (byte*)ptr; for (int y = 0; y < buffer.Height; y++) { byte* sPtr = basePtr + sOffset; @@ -164,5 +168,21 @@ namespace SixLabors.ImageSharp.Memory { return buffer.MemorySource.GetSpan(); } + + [Conditional("DEBUG")] + private static void CheckColumnRegionsDoNotOverlap( + Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) + where T : struct + { + int minIndex = Math.Min(sourceIndex, destIndex); + int maxIndex = Math.Max(sourceIndex, destIndex); + if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount) + { + throw new InvalidOperationException("Column regions should not overlap!"); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 9282c2128..ed505e221 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -148,14 +148,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int minY = this.currentWindow.Max - this.windowBandHeight; int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); + // Copy previous bottom band to the new top: + // (rows <--> columns, because the buffer is transposed) + this.transposedFirstPassBuffer.CopyColumns( + this.workerHeight - this.windowBandHeight, + 0, + this.windowBandHeight); + this.currentWindow = new RowInterval(minY, maxY); - this.CalculateFirstPassValues(this.currentWindow); + + // Calculate the remainder: + this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); } - private void CalculateFirstPassValues(RowInterval window) + private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); - for (int y = window.Min; y < window.Max; y++) + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.GetRowSpan(y); @@ -166,7 +175,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.conversionModifiers); // ref Vector4 firstPassBaseRef = ref this.buffer.Span[y - top]; - Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - window.Min); + Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min); for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) { diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 3d4087546..4af3b81e2 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -155,5 +155,28 @@ namespace SixLabors.ImageSharp.Tests.Memory } } } + + [Fact] + public void CopyColumns_InvokeMultipleTimes() + { + Random rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) + { + rnd.RandomFill(b.Span, 0, 1); + + b.CopyColumns(0, 50, 22); + b.CopyColumns(0, 50, 22); + + for (int y = 0; y < b.Height; y++) + { + Span row = b.GetRowSpan(y); + + Span s = row.Slice(0, 22); + Span d = row.Slice(50, 22); + + Xunit.Assert.True(s.SequenceEqual(d)); + } + } + } } } \ No newline at end of file