Skip to content

Commit 290b61e

Browse files
authored
Expose a generic, writable span for the pixels (#1242)
* Expose a generic, writable span for the pixels * Fixes #1240 * "Cache" the info object in size so we don' have to hop the interop. * Raw pixels depend on the CPU endianness
1 parent 928b729 commit 290b61e

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

binding/Binding/SKPixmap.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,14 @@ public SKImageInfo Info {
9595

9696
public int Height => Info.Height;
9797

98-
public SKSizeI Size => new SKSizeI (Width, Height);
98+
public SKSizeI Size {
99+
get {
100+
var info = Info;
101+
return new SKSizeI (info.Width, info.Height);
102+
}
103+
}
99104

100-
public SKRectI Rect => SKRectI.Create (Width, Height);
105+
public SKRectI Rect => SKRectI.Create (Size);
101106

102107
public SKColorType ColorType => Info.ColorType;
103108

@@ -119,9 +124,30 @@ public IntPtr GetPixels () =>
119124
public IntPtr GetPixels (int x, int y) =>
120125
(IntPtr)SkiaApi.sk_pixmap_get_pixels_with_xy (Handle, x, y);
121126

122-
public ReadOnlySpan<byte> GetPixelSpan ()
127+
public ReadOnlySpan<byte> GetPixelSpan () =>
128+
new ReadOnlySpan<byte> (SkiaApi.sk_pixmap_get_pixels (Handle), BytesSize);
129+
130+
public unsafe Span<T> GetPixelSpan<T> ()
131+
where T : unmanaged
123132
{
124-
return new ReadOnlySpan<byte> ((void*)GetPixels (), BytesSize);
133+
var info = Info;
134+
if (info.IsEmpty)
135+
return null;
136+
137+
var bpp = info.BytesPerPixel;
138+
if (bpp <= 0)
139+
return null;
140+
141+
// byte is always valid
142+
if (typeof (T) == typeof (byte))
143+
return new Span<T> (SkiaApi.sk_pixmap_get_writable_addr (Handle), info.BytesSize);
144+
145+
// other types need to make sure they fit
146+
var size = sizeof (T);
147+
if (bpp != size)
148+
throw new ArgumentException ($"Size of T ({size}) is not the same as the size of each pixel ({bpp}).", nameof (T));
149+
150+
return new Span<T> (SkiaApi.sk_pixmap_get_writable_addr (Handle), info.Width * info.Height);
125151
}
126152

127153
public SKColor GetPixelColor (int x, int y)

binding/Binding/SkiaApi.generated.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2547,6 +2547,10 @@ internal unsafe partial class SkiaApi
25472547
[DllImport (SKIA, CallingConvention = CallingConvention.Cdecl)]
25482548
internal static extern /* size_t */ IntPtr sk_pixmap_get_row_bytes (sk_pixmap_t cpixmap);
25492549

2550+
// void* sk_pixmap_get_writable_addr(const sk_pixmap_t* cpixmap)
2551+
[DllImport (SKIA, CallingConvention = CallingConvention.Cdecl)]
2552+
internal static extern void* sk_pixmap_get_writable_addr (sk_pixmap_t cpixmap);
2553+
25502554
// sk_pixmap_t* sk_pixmap_new()
25512555
[DllImport (SKIA, CallingConvention = CallingConvention.Cdecl)]
25522556
internal static extern sk_pixmap_t sk_pixmap_new ();

externals/skia

tests/Tests/SKPixmapTest.cs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.InteropServices;
34
using Xunit;
45

56
namespace SkiaSharp.Tests
67
{
78
public class SKPixmapTest : SKTest
89
{
10+
public static IEnumerable<object[]> GetAllColorTypes()
11+
{
12+
foreach (SKColorType ct in Enum.GetValues(typeof(SKColorType)))
13+
yield return new object[] { ct };
14+
}
15+
916
[SkippableFact]
1017
public void CanScalePixels()
1118
{
@@ -48,7 +55,7 @@ public void ReadPixelSucceeds()
4855

4956
Assert.True(result);
5057
}
51-
58+
5259
[SkippableFact]
5360
public void WithMethodsDoNotModifySource()
5461
{
@@ -176,5 +183,90 @@ public void EncodeWithWebpEncoder()
176183

177184
Assert.Equal(SKEncodedImageFormat.Webp, codec.EncodedFormat);
178185
}
186+
187+
[SkippableFact]
188+
public void MismatchingColorTypesThrow()
189+
{
190+
var info = new SKImageInfo(1, 1, SKColorType.Rgba8888);
191+
using var bmp = new SKBitmap(info);
192+
using var pixmap = bmp.PeekPixels();
193+
194+
Assert.Throws<ArgumentException>(() => pixmap.GetPixelSpan<ushort>());
195+
}
196+
197+
[SkippableTheory]
198+
[MemberData(nameof(GetAllColorTypes))]
199+
public void ByteWorksForEverything(SKColorType colortype)
200+
{
201+
var info = new SKImageInfo(1, 1, colortype);
202+
using var bmp = new SKBitmap(info);
203+
using var pixmap = bmp.PeekPixels();
204+
205+
Assert.Equal(info.BytesSize, pixmap?.GetPixelSpan<byte>().Length ?? 0);
206+
}
207+
208+
[SkippableTheory]
209+
[InlineData(0x00000000)]
210+
[InlineData(0xFF000000)]
211+
[InlineData(0xFFFF0000)]
212+
[InlineData(0xFF00FF00)]
213+
[InlineData(0xFF0000FF)]
214+
[InlineData(0xFFFFFFFF)]
215+
public void GetPixelSpanReadsValuesCorrectly(uint color)
216+
{
217+
var rgb888 = (SKColor)color;
218+
219+
var info = new SKImageInfo(1, 1, SKColorType.Rgba8888);
220+
using var bmp = new SKBitmap(info);
221+
using var pixmap = bmp.PeekPixels();
222+
223+
pixmap.Erase(rgb888);
224+
225+
// no need for swizzle
226+
Assert.Equal(rgb888, pixmap.GetPixelColor(0, 0));
227+
228+
// swizzle for some CPU endianness
229+
if (BitConverter.IsLittleEndian)
230+
rgb888 = new SKColor(rgb888.Blue, rgb888.Green, rgb888.Red, rgb888.Alpha);
231+
232+
Assert.Equal(rgb888, pixmap.GetPixelSpan<SKColor>()[0]);
233+
Assert.Equal(rgb888, pixmap.GetPixelSpan<uint>()[0]);
234+
}
235+
236+
[SkippableTheory]
237+
[InlineData(0x00000000, 0x0000)]
238+
[InlineData(0xFF000000, 0x0000)]
239+
[InlineData(0xFFFF0000, 0xF800)]
240+
[InlineData(0xFF00FF00, 0x07E0)]
241+
[InlineData(0xFF0000FF, 0x001F)]
242+
[InlineData(0xFFFFFFFF, 0xFFFF)]
243+
public void GetPixelSpanReads565Correctly(uint rgb888, ushort rgb565)
244+
{
245+
var info = new SKImageInfo(1, 1, SKColorType.Rgb565);
246+
using var bmp = new SKBitmap(info);
247+
using var pixmap = bmp.PeekPixels();
248+
249+
pixmap.Erase(rgb888);
250+
251+
Assert.Equal(rgb565, pixmap.GetPixelSpan<ushort>()[0]);
252+
}
253+
254+
[SkippableTheory]
255+
[InlineData(0x00000000, 0)]
256+
[InlineData(0xFF000000, 0)]
257+
[InlineData(0xFFFF0000, 53)]
258+
[InlineData(0xFF00FF00, 182)]
259+
[InlineData(0xFF0000FF, 18)]
260+
[InlineData(0xFFFFFFFF, 255)]
261+
public void GetPixelSpanReadsGray8Correctly(uint rgb888, byte gray8)
262+
{
263+
var info = new SKImageInfo(1, 1, SKColorType.Gray8);
264+
using var bmp = new SKBitmap(info);
265+
using var pixmap = bmp.PeekPixels();
266+
267+
pixmap.Erase(rgb888);
268+
269+
Assert.Equal(gray8, pixmap.GetPixelSpan<byte>()[0]);
270+
}
179271
}
180272
}

0 commit comments

Comments
 (0)