Skip to content

Commit e7e4c8d

Browse files
Merge branch 'master' into sp/guard-codegen-improvement
2 parents e740fb6 + ade5ba5 commit e7e4c8d

File tree

11 files changed

+431
-23
lines changed

11 files changed

+431
-23
lines changed

.github/workflows/build-and-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
6565

6666
- name: Update Codecov
67-
uses: codecov/codecov-action@v1.0.7
67+
uses: codecov/codecov-action@v1
6868
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
6969
with:
7070
flags: unittests

src/ImageSharp/Advanced/AdvancedImageExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ public static class AdvancedImageExtensions
2323
/// </summary>
2424
/// <param name="source">The source image.</param>
2525
/// <param name="filePath">The target file path to save the image to.</param>
26-
/// <returns>The matching encoder.</returns>
26+
/// <exception cref="ArgumentNullException">The file path is null.</exception>
27+
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
28+
/// <returns>The matching <see cref="IImageEncoder"/>.</returns>
2729
public static IImageEncoder DetectEncoder(this Image source, string filePath)
2830
{
2931
Guard.NotNull(filePath, nameof(filePath));

src/ImageSharp/Image.WrapMemory.cs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public abstract partial class Image
1717
{
1818
/// <summary>
1919
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
20-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
20+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
2121
/// </summary>
2222
/// <typeparam name="TPixel">The pixel type</typeparam>
2323
/// <param name="configuration">The <see cref="Configuration"/></param>
@@ -38,14 +38,15 @@ public static Image<TPixel> WrapMemory<TPixel>(
3838
{
3939
Guard.NotNull(configuration, nameof(configuration));
4040
Guard.NotNull(metadata, nameof(metadata));
41+
Guard.IsTrue(pixelMemory.Length == width * height, nameof(pixelMemory), "The length of the input memory doesn't match the specified image size");
4142

4243
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
4344
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
4445
}
4546

4647
/// <summary>
4748
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
48-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
49+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
4950
/// </summary>
5051
/// <typeparam name="TPixel">The pixel type</typeparam>
5152
/// <param name="configuration">The <see cref="Configuration"/></param>
@@ -64,7 +65,7 @@ public static Image<TPixel> WrapMemory<TPixel>(
6465

6566
/// <summary>
6667
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
67-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
68+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
6869
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
6970
/// </summary>
7071
/// <typeparam name="TPixel">The pixel type.</typeparam>
@@ -81,7 +82,7 @@ public static Image<TPixel> WrapMemory<TPixel>(
8182

8283
/// <summary>
8384
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
84-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
85+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
8586
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
8687
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
8788
/// It will be disposed together with the result image.
@@ -105,14 +106,15 @@ public static Image<TPixel> WrapMemory<TPixel>(
105106
{
106107
Guard.NotNull(configuration, nameof(configuration));
107108
Guard.NotNull(metadata, nameof(metadata));
109+
Guard.IsTrue(pixelMemoryOwner.Memory.Length == width * height, nameof(pixelMemoryOwner), "The length of the input memory doesn't match the specified image size");
108110

109111
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
110112
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
111113
}
112114

113115
/// <summary>
114116
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
115-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
117+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
116118
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
117119
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
118120
/// It will be disposed together with the result image.
@@ -134,7 +136,7 @@ public static Image<TPixel> WrapMemory<TPixel>(
134136

135137
/// <summary>
136138
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
137-
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
139+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
138140
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
139141
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
140142
/// It will be disposed together with the result image.
@@ -150,5 +152,73 @@ public static Image<TPixel> WrapMemory<TPixel>(
150152
int height)
151153
where TPixel : unmanaged, IPixel<TPixel>
152154
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
155+
156+
/// <summary>
157+
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
158+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
159+
/// </summary>
160+
/// <typeparam name="TPixel">The pixel type</typeparam>
161+
/// <param name="configuration">The <see cref="Configuration"/></param>
162+
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
163+
/// <param name="width">The width of the memory image.</param>
164+
/// <param name="height">The height of the memory image.</param>
165+
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
166+
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
167+
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
168+
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
169+
public static Image<TPixel> WrapMemory<TPixel>(
170+
Configuration configuration,
171+
Memory<byte> byteMemory,
172+
int width,
173+
int height,
174+
ImageMetadata metadata)
175+
where TPixel : unmanaged, IPixel<TPixel>
176+
{
177+
Guard.NotNull(configuration, nameof(configuration));
178+
Guard.NotNull(metadata, nameof(metadata));
179+
180+
var memoryManager = new ByteMemoryManager<TPixel>(byteMemory);
181+
182+
Guard.IsTrue(memoryManager.Memory.Length == width * height, nameof(byteMemory), "The length of the input memory doesn't match the specified image size");
183+
184+
var memorySource = MemoryGroup<TPixel>.Wrap(memoryManager.Memory);
185+
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
186+
}
187+
188+
/// <summary>
189+
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
190+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
191+
/// </summary>
192+
/// <typeparam name="TPixel">The pixel type</typeparam>
193+
/// <param name="configuration">The <see cref="Configuration"/></param>
194+
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
195+
/// <param name="width">The width of the memory image.</param>
196+
/// <param name="height">The height of the memory image.</param>
197+
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
198+
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
199+
public static Image<TPixel> WrapMemory<TPixel>(
200+
Configuration configuration,
201+
Memory<byte> byteMemory,
202+
int width,
203+
int height)
204+
where TPixel : unmanaged, IPixel<TPixel>
205+
=> WrapMemory<TPixel>(configuration, byteMemory, width, height, new ImageMetadata());
206+
207+
/// <summary>
208+
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
209+
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
210+
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
211+
/// </summary>
212+
/// <typeparam name="TPixel">The pixel type.</typeparam>
213+
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
214+
/// <param name="width">The width of the memory image.</param>
215+
/// <param name="height">The height of the memory image.</param>
216+
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
217+
public static Image<TPixel> WrapMemory<TPixel>(
218+
Memory<byte> byteMemory,
219+
int width,
220+
int height)
221+
where TPixel : unmanaged, IPixel<TPixel>
222+
=> WrapMemory<TPixel>(Configuration.Default, byteMemory, width, height);
153223
}
154224
}

src/ImageSharp/ImageExtensions.cs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,29 @@ namespace SixLabors.ImageSharp
1818
public static partial class ImageExtensions
1919
{
2020
/// <summary>
21-
/// Writes the image to the given stream using the currently loaded image format.
21+
/// Writes the image to the given file path using an encoder detected from the path.
2222
/// </summary>
2323
/// <param name="source">The source image.</param>
2424
/// <param name="path">The file path to save the image to.</param>
2525
/// <exception cref="ArgumentNullException">The path is null.</exception>
26+
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
2627
public static void Save(this Image source, string path)
2728
=> source.Save(path, source.DetectEncoder(path));
2829

2930
/// <summary>
30-
/// Writes the image to the given stream using the currently loaded image format.
31+
/// Writes the image to the given file path using an encoder detected from the path.
3132
/// </summary>
3233
/// <param name="source">The source image.</param>
3334
/// <param name="path">The file path to save the image to.</param>
3435
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
3536
/// <exception cref="ArgumentNullException">The path is null.</exception>
37+
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
3638
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
3739
public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
3840
=> source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
3941

4042
/// <summary>
41-
/// Writes the image to the given stream using the currently loaded image format.
43+
/// Writes the image to the given file path using the given image encoder.
4244
/// </summary>
4345
/// <param name="source">The source image.</param>
4446
/// <param name="path">The file path to save the image to.</param>
@@ -56,7 +58,7 @@ public static void Save(this Image source, string path, IImageEncoder encoder)
5658
}
5759

5860
/// <summary>
59-
/// Writes the image to the given stream using the currently loaded image format.
61+
/// Writes the image to the given file path using the given image encoder.
6062
/// </summary>
6163
/// <param name="source">The source image.</param>
6264
/// <param name="path">The file path to save the image to.</param>
@@ -73,12 +75,15 @@ public static async Task SaveAsync(
7375
{
7476
Guard.NotNull(path, nameof(path));
7577
Guard.NotNull(encoder, nameof(encoder));
76-
using Stream fs = source.GetConfiguration().FileSystem.Create(path);
77-
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
78+
79+
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
80+
{
81+
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
82+
}
7883
}
7984

8085
/// <summary>
81-
/// Writes the image to the given stream using the currently loaded image format.
86+
/// Writes the image to the given stream using the given image format.
8287
/// </summary>
8388
/// <param name="source">The source image.</param>
8489
/// <param name="stream">The stream to save the image to.</param>
@@ -115,6 +120,50 @@ public static void Save(this Image source, Stream stream, IImageFormat format)
115120
source.Save(stream, encoder);
116121
}
117122

123+
/// <summary>
124+
/// Writes the image to the given stream using the given image format.
125+
/// </summary>
126+
/// <param name="source">The source image.</param>
127+
/// <param name="stream">The stream to save the image to.</param>
128+
/// <param name="format">The format to save the image in.</param>
129+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
130+
/// <exception cref="ArgumentNullException">The stream is null.</exception>
131+
/// <exception cref="ArgumentNullException">The format is null.</exception>
132+
/// <exception cref="NotSupportedException">The stream is not writable.</exception>
133+
/// <exception cref="NotSupportedException">No encoder available for provided format.</exception>
134+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
135+
public static Task SaveAsync(
136+
this Image source,
137+
Stream stream,
138+
IImageFormat format,
139+
CancellationToken cancellationToken = default)
140+
{
141+
Guard.NotNull(stream, nameof(stream));
142+
Guard.NotNull(format, nameof(format));
143+
144+
if (!stream.CanWrite)
145+
{
146+
throw new NotSupportedException("Cannot write to the stream.");
147+
}
148+
149+
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
150+
151+
if (encoder is null)
152+
{
153+
var sb = new StringBuilder();
154+
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
155+
156+
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
157+
{
158+
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
159+
}
160+
161+
throw new NotSupportedException(sb.ToString());
162+
}
163+
164+
return source.SaveAsync(stream, encoder, cancellationToken);
165+
}
166+
118167
/// <summary>
119168
/// Returns a Base64 encoded string from the given image.
120169
/// The result is prepended with a Data URI <see href="https://en.wikipedia.org/wiki/Data_URI_scheme"/>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
using System;
4+
using System.Buffers;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace SixLabors.ImageSharp.Memory
9+
{
10+
/// <summary>
11+
/// A custom <see cref="MemoryManager{T}"/> that can wrap <see cref="Memory{T}"/> of <see cref="byte"/> instances
12+
/// and cast them to be <see cref="Memory{T}"/> for any arbitrary unmanaged <typeparamref name="T"/> value type.
13+
/// </summary>
14+
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="Memory{T}"/> instance.</typeparam>
15+
internal sealed class ByteMemoryManager<T> : MemoryManager<T>
16+
where T : unmanaged
17+
{
18+
/// <summary>
19+
/// The wrapped <see cref="Memory{T}"/> of <see cref="byte"/> instance.
20+
/// </summary>
21+
private readonly Memory<byte> memory;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="ByteMemoryManager{T}"/> class.
25+
/// </summary>
26+
/// <param name="memory">The <see cref="Memory{T}"/> of <see cref="byte"/> instance to wrap.</param>
27+
public ByteMemoryManager(Memory<byte> memory)
28+
{
29+
this.memory = memory;
30+
}
31+
32+
/// <inheritdoc/>
33+
protected override void Dispose(bool disposing)
34+
{
35+
}
36+
37+
/// <inheritdoc/>
38+
public override Span<T> GetSpan()
39+
{
40+
return MemoryMarshal.Cast<byte, T>(this.memory.Span);
41+
}
42+
43+
/// <inheritdoc/>
44+
public override MemoryHandle Pin(int elementIndex = 0)
45+
{
46+
// We need to adjust the offset into the wrapped byte segment,
47+
// as the input index refers to the target-cast memory of T.
48+
// We just have to shift this index by the byte size of T.
49+
return this.memory.Slice(elementIndex * Unsafe.SizeOf<T>()).Pin();
50+
}
51+
52+
/// <inheritdoc/>
53+
public override void Unpin()
54+
{
55+
}
56+
}
57+
}

src/ImageSharp/Memory/MemoryOwnerExtensions.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,27 @@ namespace SixLabors.ImageSharp.Memory
1313
/// </summary>
1414
internal static class MemoryOwnerExtensions
1515
{
16+
/// <summary>
17+
/// Gets a <see cref="Span{T}"/> from an <see cref="IMemoryOwner{T}"/> instance.
18+
/// </summary>
19+
/// <param name="buffer">The buffer</param>
20+
/// <returns>The <see cref="Span{T}"/></returns>
1621
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1722
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
18-
=> buffer.Memory.Span;
23+
{
24+
return buffer.Memory.Span;
25+
}
1926

27+
/// <summary>
28+
/// Gets the length of an <see cref="IMemoryOwner{T}"/> internal buffer.
29+
/// </summary>
30+
/// <param name="buffer">The buffer</param>
31+
/// <returns>The length of the buffer</returns>
2032
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2133
public static int Length<T>(this IMemoryOwner<T> buffer)
22-
=> buffer.GetSpan().Length;
34+
{
35+
return buffer.Memory.Length;
36+
}
2337

2438
/// <summary>
2539
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
@@ -56,8 +70,16 @@ public static void Clear<T>(this IMemoryOwner<T> buffer)
5670
buffer.GetSpan().Clear();
5771
}
5872

73+
/// <summary>
74+
/// Gets a reference to the first item in the internal buffer for an <see cref="IMemoryOwner{T}"/> instance.
75+
/// </summary>
76+
/// <param name="buffer">The buffer</param>
77+
/// <returns>A reference to the first item within the memory wrapped by <paramref name="buffer"/></returns>
78+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5979
public static ref T GetReference<T>(this IMemoryOwner<T> buffer)
60-
where T : struct =>
61-
ref MemoryMarshal.GetReference(buffer.GetSpan());
80+
where T : struct
81+
{
82+
return ref MemoryMarshal.GetReference(buffer.GetSpan());
83+
}
6284
}
6385
}

0 commit comments

Comments
 (0)