Skip to content

Commit b8b6423

Browse files
committed
Added ToArray and CopyTo Methods to Images; Made RefImage pinnable
1 parent f04d270 commit b8b6423

File tree

4 files changed

+225
-14
lines changed

4 files changed

+225
-14
lines changed

ScreenCapture.NET/Model/IImage.cs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23

34
namespace ScreenCapture.NET;
45

@@ -7,6 +8,11 @@ namespace ScreenCapture.NET;
78
/// </summary>
89
public interface IImage : IEnumerable<IColor>
910
{
11+
/// <summary>
12+
/// Gets the color format used in this image.
13+
/// </summary>
14+
ColorFormat ColorFormat { get; }
15+
1016
/// <summary>
1117
/// Gets the width of this image.
1218
/// </summary>
@@ -17,6 +23,11 @@ public interface IImage : IEnumerable<IColor>
1723
/// </summary>
1824
int Height { get; }
1925

26+
/// <summary>
27+
/// Gets the size in bytes of this image.
28+
/// </summary>
29+
int SizeInBytes { get; }
30+
2031
/// <summary>
2132
/// Gets the color at the specified location.
2233
/// </summary>
@@ -45,6 +56,28 @@ public interface IImage : IEnumerable<IColor>
4556
/// </summary>
4657
IImageColumns Columns { get; }
4758

59+
/// <summary>
60+
/// Gets an <see cref="RefImage{TColor}"/> representing this <see cref="IImage"/>.
61+
/// </summary>
62+
/// <typeparam name="TColor">The color-type of the iamge.</typeparam>
63+
/// <returns>The <inheritdoc cref="RefImage{TColor}"/>.</returns>
64+
RefImage<TColor> AsRefImage<TColor>() where TColor : struct, IColor;
65+
66+
/// <summary>
67+
/// Copies the contents of this <see cref="IImage"/> into a destination <see cref="Span{T}"/> instance.
68+
/// </summary>
69+
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
70+
/// <exception cref="ArgumentException">
71+
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImage"/> instance.
72+
/// </exception>
73+
void CopyTo(in Span<byte> destination);
74+
75+
/// <summary>
76+
/// Allocates a new array and copies this <see cref="IImage"/> into it.
77+
/// </summary>
78+
/// <returns>The new array containing the data of this <see cref="IImage"/>.</returns>
79+
byte[] ToArray();
80+
4881
/// <summary>
4982
/// Represents a list of rows of an image.
5083
/// </summary>
@@ -91,12 +124,32 @@ public interface IImageRow : IEnumerable<IColor>
91124
/// </summary>
92125
int Length { get; }
93126

127+
/// <summary>
128+
/// Gets the size in bytes of this row.
129+
/// </summary>
130+
int SizeInBytes { get; }
131+
94132
/// <summary>
95133
/// Gets the <see cref="IColor"/> at the specified location.
96134
/// </summary>
97135
/// <param name="x">The location to get the color from.</param>
98136
/// <returns>The <see cref="IColor"/> at the specified location.</returns>
99137
IColor this[int x] { get; }
138+
139+
/// <summary>
140+
/// Copies the contents of this <see cref="IImageRow"/> into a destination <see cref="Span{T}"/> instance.
141+
/// </summary>
142+
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
143+
/// <exception cref="ArgumentException">
144+
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImageRow"/> instance.
145+
/// </exception>
146+
void CopyTo(in Span<byte> destination);
147+
148+
/// <summary>
149+
/// Allocates a new array and copies this <see cref="IImageRow"/> into it.
150+
/// </summary>
151+
/// <returns>The new array containing the data of this <see cref="IImageRow"/>.</returns>
152+
byte[] ToArray();
100153
}
101154

102155
/// <summary>
@@ -109,11 +162,31 @@ public interface IImageColumn : IEnumerable<IColor>
109162
/// </summary>
110163
int Length { get; }
111164

165+
/// <summary>
166+
/// Gets the size in bytes of this column.
167+
/// </summary>
168+
int SizeInBytes { get; }
169+
112170
/// <summary>
113171
/// Gets the <see cref="IColor"/> at the specified location.
114172
/// </summary>
115173
/// <param name="y">The location to get the color from.</param>
116174
/// <returns>The <see cref="IColor"/> at the specified location.</returns>
117175
IColor this[int y] { get; }
176+
177+
/// <summary>
178+
/// Copies the contents of this <see cref="IImageColumn"/> into a destination <see cref="Span{T}"/> instance.
179+
/// </summary>
180+
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
181+
/// <exception cref="ArgumentException">
182+
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImageColumn"/> instance.
183+
/// </exception>
184+
void CopyTo(in Span<byte> destination);
185+
186+
/// <summary>
187+
/// Allocates a new array and copies this <see cref="IImageColumn"/> into it.
188+
/// </summary>
189+
/// <returns>The new array containing the data of this <see cref="IImageColumn"/>.</returns>
190+
byte[] ToArray();
118191
}
119192
}

ScreenCapture.NET/Model/Image.cs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ public sealed class Image<TColor> : IImage
1818
private readonly int _y;
1919
private readonly int _stride;
2020

21+
/// <inheritdoc />
22+
public ColorFormat ColorFormat => TColor.ColorFormat;
23+
2124
/// <inheritdoc />
2225
public int Width { get; }
2326

2427
/// <inheritdoc />
2528
public int Height { get; }
2629

30+
/// <inheritdoc />
31+
public int SizeInBytes => Width * Height * TColor.ColorFormat.BytesPerPixel;
32+
2733
#endregion
2834

2935
#region Indexer
@@ -84,6 +90,39 @@ internal Image(byte[] buffer, int x, int y, int width, int height, int stride)
8490

8591
#region Methods
8692

93+
/// <inheritdoc />
94+
public void CopyTo(in Span<byte> destination)
95+
{
96+
if (destination == null) throw new ArgumentNullException(nameof(destination));
97+
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));
98+
99+
int targetStride = Width * TColor.ColorFormat.BytesPerPixel;
100+
IImage.IImageRows rows = Rows;
101+
Span<byte> target = destination;
102+
foreach (IImage.IImageRow row in rows)
103+
{
104+
row.CopyTo(target);
105+
target = target[targetStride..];
106+
}
107+
}
108+
109+
/// <inheritdoc />
110+
public byte[] ToArray()
111+
{
112+
byte[] array = new byte[SizeInBytes];
113+
CopyTo(array);
114+
return array;
115+
}
116+
117+
/// <inheritdoc />
118+
public RefImage<T> AsRefImage<T>()
119+
where T : struct, IColor
120+
{
121+
if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested color format does not fit this image.", nameof(T));
122+
123+
return new RefImage<T>(MemoryMarshal.Cast<byte, T>(_buffer), _x, _y, Width, Height, _stride);
124+
}
125+
87126
/// <inheritdoc />
88127
public IEnumerator<IColor> GetEnumerator()
89128
{
@@ -172,6 +211,9 @@ private sealed class ImageRow : IImage.IImageRow
172211
/// <inheritdoc />
173212
public int Length => _length;
174213

214+
/// <inheritdoc />
215+
public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;
216+
175217
#endregion
176218

177219
#region Indexer
@@ -203,6 +245,23 @@ internal ImageRow(byte[] buffer, int start, int length)
203245

204246
#region Methods
205247

248+
/// <inheritdoc />
249+
public void CopyTo(in Span<byte> destination)
250+
{
251+
if (destination == null) throw new ArgumentNullException(nameof(destination));
252+
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));
253+
254+
_buffer.AsSpan(_start, SizeInBytes).CopyTo(destination);
255+
}
256+
257+
/// <inheritdoc />
258+
public byte[] ToArray()
259+
{
260+
byte[] array = new byte[SizeInBytes];
261+
CopyTo(array);
262+
return array;
263+
}
264+
206265
/// <inheritdoc />
207266
public IEnumerator<IColor> GetEnumerator()
208267
{
@@ -290,6 +349,9 @@ private sealed class ImageColumn : IImage.IImageColumn
290349
/// <inheritdoc />
291350
public int Length => _length;
292351

352+
/// <inheritdoc />
353+
public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;
354+
293355
#endregion
294356

295357
#region Indexer
@@ -301,8 +363,8 @@ public IColor this[int y]
301363
{
302364
if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException();
303365

304-
ReadOnlySpan<TColor> row = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
305-
return row[y * _step];
366+
ReadOnlySpan<TColor> data = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
367+
return data[y * _step];
306368
}
307369
}
308370

@@ -322,6 +384,31 @@ internal ImageColumn(byte[] buffer, int start, int length, int step)
322384

323385
#region Methods
324386

387+
/// <inheritdoc />
388+
public void CopyTo(in Span<byte> destination)
389+
{
390+
if (destination == null) throw new ArgumentNullException(nameof(destination));
391+
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));
392+
393+
if (_step == 1)
394+
_buffer.AsSpan(_start, SizeInBytes).CopyTo(destination);
395+
else
396+
{
397+
ReadOnlySpan<TColor> data = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
398+
Span<TColor> target = MemoryMarshal.Cast<byte, TColor>(destination);
399+
for (int i = 0; i < Length; i++)
400+
target[i] = data[i * _step];
401+
}
402+
}
403+
404+
/// <inheritdoc />
405+
public byte[] ToArray()
406+
{
407+
byte[] array = new byte[SizeInBytes];
408+
CopyTo(array);
409+
return array;
410+
}
411+
325412
/// <inheritdoc />
326413
public IEnumerator<IColor> GetEnumerator()
327414
{

ScreenCapture.NET/Model/RefImage.cs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,37 @@ public readonly ref struct RefImage<TColor>
1313

1414
private readonly int _x;
1515
private readonly int _y;
16-
private readonly int _stride;
1716

17+
/// <summary>
18+
/// Gets the width of the image.
19+
/// </summary>
1820
public int Width { get; }
21+
22+
/// <summary>
23+
/// Gets the height of the image.
24+
/// </summary>
1925
public int Height { get; }
2026

27+
/// <summary>
28+
/// Gets the stride (entries per row) of the underlying buffer.
29+
/// Only useful if you want to work with a pinned buffer.
30+
/// </summary>
31+
public int RawStride { get; }
32+
2133
#endregion
2234

2335
#region Indexer
2436

25-
public TColor this[int x, int y]
37+
public ref readonly TColor this[int x, int y]
2638
{
2739
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2840
get
2941
{
3042
if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException();
3143

32-
return _pixels[((_y + y) * _stride) + (_x + x)];
44+
ref TColor r0 = ref MemoryMarshal.GetReference(_pixels);
45+
nint offset = (nint)(uint)((_y + y) * RawStride) + (_x + x);
46+
return ref Unsafe.Add(ref r0, offset);
3347
}
3448
}
3549

@@ -40,19 +54,19 @@ public readonly ref struct RefImage<TColor>
4054
{
4155
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException();
4256

43-
return new RefImage<TColor>(_pixels, _x + x, _y + y, width, height, _stride);
57+
return new RefImage<TColor>(_pixels, _x + x, _y + y, width, height, RawStride);
4458
}
4559
}
4660

4761
public ImageRows Rows
4862
{
4963
[MethodImpl(MethodImplOptions.AggressiveInlining)]
50-
get => new(_pixels, _x, _y, Width, Height, _stride);
64+
get => new(_pixels, _x, _y, Width, Height, RawStride);
5165
}
5266
public ImageColumns Columns
5367
{
5468
[MethodImpl(MethodImplOptions.AggressiveInlining)]
55-
get => new(_pixels, _x, _y, Width, Height, _stride);
69+
get => new(_pixels, _x, _y, Width, Height, RawStride);
5670
}
5771

5872
#endregion
@@ -66,34 +80,57 @@ internal RefImage(ReadOnlySpan<TColor> pixels, int x, int y, int width, int heig
6680
this._y = y;
6781
this.Width = width;
6882
this.Height = height;
69-
this._stride = stride;
83+
this.RawStride = stride;
7084
}
7185

7286
#endregion
7387

7488
#region Methods
7589

76-
public void CopyTo(in Span<TColor> dest)
90+
/// <summary>
91+
/// Copies the contents of this <see cref="RefImage{TColor}"/> into a destination <see cref="Span{T}"/> instance.
92+
/// </summary>
93+
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
94+
/// <exception cref="ArgumentException">
95+
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefImage{TColor}"/> instance.
96+
/// </exception>
97+
public void CopyTo(in Span<TColor> destination)
7798
{
78-
if (dest == null) throw new ArgumentNullException(nameof(dest));
79-
if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest));
99+
if (destination == null) throw new ArgumentNullException(nameof(destination));
100+
if (destination.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));
80101

81102
ImageRows rows = Rows;
82-
Span<TColor> target = dest;
103+
Span<TColor> target = destination;
83104
foreach (ReadOnlyRefEnumerable<TColor> row in rows)
84105
{
85106
row.CopyTo(target);
86107
target = target[Width..];
87108
}
88109
}
89110

111+
/// <summary>
112+
/// Allocates a new array and copies this <see cref="RefImage{TColor}"/> into it.
113+
/// </summary>
114+
/// <returns>The new array containing the data of this <see cref="RefImage{TColor}"/>.</returns>
90115
public TColor[] ToArray()
91116
{
92117
TColor[] array = new TColor[Width * Height];
93118
CopyTo(array);
94119
return array;
95120
}
96121

122+
/// <summary>
123+
/// Returns a reference to the first element of this image inside the full image buffer.
124+
/// </summary>
125+
public ref readonly TColor GetPinnableReference()
126+
{
127+
if (_pixels.Length == 0)
128+
return ref Unsafe.NullRef<TColor>();
129+
130+
int offset = (_y * RawStride) + _x;
131+
return ref MemoryMarshal.GetReference(_pixels[offset..]);
132+
}
133+
97134
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
98135
[MethodImpl(MethodImplOptions.AggressiveInlining)]
99136
public ImageEnumerator GetEnumerator() => new(_pixels);

0 commit comments

Comments
 (0)