Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement discontiguous buffer handling #1109

Merged
merged 66 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
f91c50d
fast-dev-hack
antonfirsov Jan 28, 2020
3943751
initial skeleton of discontinuous buffer object model
antonfirsov Jan 28, 2020
1513b10
adjust names
antonfirsov Jan 28, 2020
635c727
better comments
antonfirsov Jan 28, 2020
a674abc
bufferLengthAlignment
antonfirsov Jan 28, 2020
f595ead
bufferLengthAlignment
antonfirsov Jan 28, 2020
b25cfb5
make stuff internal
antonfirsov Jan 28, 2020
4c1f057
Merge branch 'master' into af/discontinuous-buffers
antonfirsov Feb 2, 2020
c756c3a
test cases for MemoryGroup.Allocate()
antonfirsov Feb 2, 2020
98e5ca7
More tests for MemoryGroup.Allocate()
antonfirsov Feb 2, 2020
0dfb208
Allocate works
antonfirsov Feb 2, 2020
94eb3df
add some tests for CopyTo
antonfirsov Feb 2, 2020
bbd4daf
CopyTo WIP
antonfirsov Feb 2, 2020
1d5d994
Implemented: CopyTo, TransformTo, TransformInplace
antonfirsov Feb 2, 2020
dfc892d
SwapOrCopyContent() works
antonfirsov Feb 3, 2020
d7caa90
GetBoundedSlice, CopyTo/From span
antonfirsov Feb 3, 2020
fa04bf0
replace MemorySource with MemoryGroup
antonfirsov Feb 4, 2020
8bd19cb
Improve robustness of discontiguous Buffer2D
antonfirsov Feb 4, 2020
0639915
robust BufferArea
antonfirsov Feb 4, 2020
da65467
Actually resize a 30k x 30k image
antonfirsov Feb 4, 2020
3d832d7
Decode Jpegs to non-contiguous buffers
antonfirsov Feb 4, 2020
742b1d9
jpeg encoder tests for disco buffers
antonfirsov Feb 5, 2020
e2b9db5
fix JpegEncoder disco buffer handling
antonfirsov Feb 7, 2020
80e0eee
change AdvancedImageExtensions public API-s
antonfirsov Feb 7, 2020
48c6f3f
implement correct AdvancedImageExtensions behavior
antonfirsov Feb 7, 2020
8fda1ef
polish MemoryAllocator API
antonfirsov Feb 7, 2020
fda4785
Clean up public API
antonfirsov Feb 8, 2020
334f16b
fix bug found by DetectEdges_WorksOnWrappedMemoryImage
antonfirsov Feb 8, 2020
22db8e0
re-enable all target frameworks
antonfirsov Feb 8, 2020
077f809
remove commented code
antonfirsov Feb 8, 2020
8e0a516
no, we still can't cache images for 32bit tests
antonfirsov Feb 8, 2020
0675c80
Better exceptions for images with degenerate dimensions
antonfirsov Feb 9, 2020
7d631ee
Remove usage of GetSingleSpan() in bmp decoder
brianpopow Feb 9, 2020
9c6f118
Remove usage of GetSingleSpan() in tga encoder
brianpopow Feb 10, 2020
b9e7b62
Fix mistake counting equal pixels for encoding RLE tga
brianpopow Feb 10, 2020
1d75176
Add tests for discontiguous buffers for bitmaps
brianpopow Feb 10, 2020
c30eb20
Add tests for discontiguous buffers for tga
brianpopow Feb 10, 2020
f4d76e4
Remove not needed png file from the testfiles
brianpopow Feb 10, 2020
cf5f9f5
tests improvements:
antonfirsov Feb 10, 2020
82beb45
re-enable skipped test
antonfirsov Feb 10, 2020
edb60a8
re-enable skipped test
antonfirsov Feb 10, 2020
e726bca
TGA limit
antonfirsov Feb 10, 2020
a365b5d
fix my bug in Encode_WorksWithDiscontiguousBuffers
antonfirsov Feb 11, 2020
ba37134
Increase LimitAllocatorBufferCapacity to 400 for RLE tests.
brianpopow Feb 11, 2020
c21638b
Re-enable DegenerateMemoryRequest test, fix Exception to be ImageForm…
brianpopow Feb 11, 2020
8d3051f
Add tests for discontiguous buffers for png
brianpopow Feb 11, 2020
487195f
Add tests for discontiguous buffers for png
brianpopow Feb 11, 2020
2a01590
Add tests for discontiguous buffers for gif
brianpopow Feb 11, 2020
5153ed1
Merge branch 'master' into af/disco-buffers
JimBobSquarePants Feb 12, 2020
eb677f2
Fix warnings
JimBobSquarePants Feb 12, 2020
b35e3cb
Fix DegenerateMemoryRequest tests
brianpopow Feb 12, 2020
b12e1c4
merge back GetRowSpan and GetRowSpanUnchecked
antonfirsov Feb 13, 2020
ccabe41
temporarily disable Calliphora+Disco to see effect on CI
antonfirsov Feb 13, 2020
1df8d62
document ArgumentOutOfRangeException
antonfirsov Feb 13, 2020
55c6ae5
InBytes->InBytesSqrt, InPixels->InPixelsSqrt
antonfirsov Feb 19, 2020
da08852
fix JpegDecoder
antonfirsov Feb 20, 2020
9b815cf
Merge branch 'master' into af/disco-buffers
antonfirsov Feb 20, 2020
2ad20e8
update reference images
antonfirsov Feb 20, 2020
798aaa4
fix test execution
antonfirsov Feb 20, 2020
cc080b9
cover and harden ResizeProcessor
antonfirsov Feb 21, 2020
29b4a95
test common processors for disco buffers
antonfirsov Feb 21, 2020
ccdda97
fix remaining single-buffer specific code
antonfirsov Feb 21, 2020
a3f169f
cleanup
antonfirsov Feb 21, 2020
7e30e76
Merge branch 'master' into af/disco-buffers
antonfirsov Feb 21, 2020
b0646df
implement review suggestions
antonfirsov Feb 21, 2020
ad2632f
optimize RowOctet<T> indexer
antonfirsov Feb 21, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shared-infrastructure
3 changes: 2 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- TODO: re-enable when all Obsolete warnings are fixed -->
<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>-->
</PropertyGroup>

<ItemGroup>
Expand Down
155 changes: 84 additions & 71 deletions src/ImageSharp/Advanced/AdvancedImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Linq;
using System.Runtime.InteropServices;

using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -40,23 +41,66 @@ public static Configuration GetConfiguration(this ImageFrame source)
=> GetConfiguration((IConfigurationProvider)source);

/// <summary>
/// Gets the configuration .
/// Gets the configuration.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the bounds of the image</returns>
private static Configuration GetConfiguration(IConfigurationProvider source)
=> source?.Configuration ?? Configuration.Default;

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
antonfirsov marked this conversation as resolved.
Show resolved Hide resolved
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source?.PixelBuffer.MemoryGroup.View ?? throw new ArgumentNullException(nameof(source));

/// <summary>
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.GetPixelMemory().Span;
{
Guard.NotNull(source, nameof(source));

IMemoryGroup<TPixel> mg = source.GetPixelMemoryGroup();
if (mg.Count > 1)
{
throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
}

return mg.Single().Span;
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
Expand All @@ -65,9 +109,16 @@ public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelSpan();
{
Guard.NotNull(source, nameof(source));

return source.Frames.RootFrame.GetPixelSpan();
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
Expand All @@ -79,7 +130,13 @@ public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowSpan(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

return source.PixelBuffer.GetRowSpanUnchecked(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
Expand All @@ -91,58 +148,12 @@ public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> sourc
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(rowIndex);

/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image frame</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);

/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();

/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="ImageFrame{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.PixelBuffer.MemorySource.Memory;
}
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="Image{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.Frames.RootFrame.GetPixelMemory();
return source.Frames.RootFrame.PixelBuffer.GetRowSpanUnchecked(rowIndex);
}

/// <summary>
Expand All @@ -153,9 +164,15 @@ internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowMemory(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

return source.PixelBuffer.GetRowMemorySafe(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
Expand All @@ -165,9 +182,15 @@ internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel>
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

return source.Frames.RootFrame.PixelBuffer.GetRowMemorySafe(rowIndex);
}

/// <summary>
/// Gets the <see cref="MemoryAllocator"/> assigned to 'source'.
Expand All @@ -176,15 +199,5 @@ internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> sour
/// <returns>Returns the configuration.</returns>
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;

/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer.
/// Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
/// <param name="source">The source image frame</param>
/// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp/Common/Exceptions/ImageFormatException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
{
/// <summary>
/// The exception that is thrown when the library tries to load
/// an image, which has an invalid format.
/// an image, which has format or content that is invalid or unsupported by ImageSharp.
/// </summary>
public class ImageFormatException : Exception
{
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static void Convolve4<TPixel>(
{
int offsetY = (row + i - radiusY).Clamp(minRow, maxRow);
int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY);
var currentColor = sourceRowSpan[offsetX].ToVector4();

vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor);
Expand Down Expand Up @@ -94,7 +94,7 @@ public static void Convolve4AndAccumulatePartials(
int sourceOffsetColumnBase = column + minColumn;

int offsetY = row.Clamp(minRow, maxRow);
ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY));
ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpanUnchecked(offsetY));
ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);

for (int x = 0; x < kernelLength; x++)
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public static void Convolve2DImpl<TPixel>(
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY);

for (int x = 0; x < matrixWidth; x++)
{
Expand Down Expand Up @@ -264,7 +264,7 @@ private static void ConvolveImpl<TPixel>(
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY);

for (int x = 0; x < matrixWidth; x++)
{
Expand Down
18 changes: 16 additions & 2 deletions src/ImageSharp/Formats/Bmp/BmpDecoder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Formats.Bmp
Expand Down Expand Up @@ -32,7 +33,20 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));

return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
var decoder = new BmpDecoderCore(configuration, this);

try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
}
}

/// <inheritdoc />
Expand Down
Loading