Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
SkiaExtensions.cs
1using System.Drawing;
2using SkiaSharp;
3
4namespace Gondwana.SkiaSharp;
5
9public static class SkiaExtensions
10{
16 public static Rectangle ToRectangle(this SKRect rect)
17 => Rectangle.FromLTRB((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom);
18
24 public static SKColor ToSKColor(this Color color)
25 => new(color.R, color.G, color.B, color.A);
26
32 public static Color ToColor(this SKColor skcolor)
33 => Color.FromArgb(skcolor.Alpha, skcolor.Red, skcolor.Green, skcolor.Blue);
34
40 public static SKRect ToSKRect(this Rectangle rect)
41 => new(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
42
48 public static SKRect ToSKRect(this RectangleF rect)
49 => new(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
50
56 public static SKRectI ToSKRectI(this Rectangle rect)
57 => new(rect.Left, rect.Top, rect.Right, rect.Bottom);
58
65 public static SKPoint[] ToSKPoints(this Point[] points, bool enclose = false)
66 {
67 if (!enclose)
68 return points.Select(p => new SKPoint(p.X, p.Y)).ToArray(); ;
69
70 var len = points.Length + 1;
71 var result = new SKPoint[len];
72
73 for (int i = 0; i < points.Length; i++)
74 result[i] = new SKPoint(points[i].X, points[i].Y);
75
76 if (enclose)
77 result[^1] = result[0]; // append first point to close polygon
78
79 return result;
80 }
81
88 public static SKBitmap CopyRegion(this SKBitmap source, Rectangle sourceRect)
89 {
90 if (source == null || source.IsEmpty)
91 throw new ArgumentException("Invalid source bitmap.", nameof(source));
92
93 if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
94 throw new ArgumentException("Source rectangle must have positive size.", nameof(sourceRect));
95
96 var skSourceRect = sourceRect.ToSKRectI();
97
98 if (!source.Info.Rect.Contains(skSourceRect))
99 throw new ArgumentOutOfRangeException(nameof(sourceRect), "Source rectangle is outside the bitmap bounds.");
100
101 var copy = new SKBitmap(new SKImageInfo(
102 sourceRect.Width,
103 sourceRect.Height,
104 source.Info.ColorType,
105 source.Info.AlphaType));
106
107 using var canvas = new SKCanvas(copy);
108
109 canvas.Clear(SKColors.Transparent);
110
111 canvas.DrawBitmap(
112 source,
113 skSourceRect,
114 new SKRect(0, 0, sourceRect.Width, sourceRect.Height));
115
116 return copy;
117 }
118
125 public static SKImage CopyRegion(this SKImage source, Rectangle sourceRect)
126 {
127 if (source == null)
128 throw new ArgumentNullException(nameof(source));
129
130 if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
131 throw new ArgumentException("Source rectangle must have positive size.", nameof(sourceRect));
132
133 var skSourceRect = new SKRect(
134 sourceRect.Left,
135 sourceRect.Top,
136 sourceRect.Right,
137 sourceRect.Bottom);
138
139 var skDestRect = new SKRect(
140 0,
141 0,
142 sourceRect.Width,
143 sourceRect.Height);
144
145 var imageInfo = new SKImageInfo(
146 sourceRect.Width,
147 sourceRect.Height,
148 source.ColorType,
149 source.AlphaType,
150 source.ColorSpace);
151
152 using var surface = SKSurface.Create(imageInfo)
153 ?? throw new InvalidOperationException("Could not create SKSurface.");
154
155 surface.Canvas.Clear(SKColors.Transparent);
156
157 surface.Canvas.DrawImage(
158 source,
159 skSourceRect,
160 skDestRect);
161
162 return surface.Snapshot();
163 }
164
172 public static void SaveBitmapToFile(this SKBitmap bitmap, string filePath,
173 SKEncodedImageFormat format = SKEncodedImageFormat.Png, int quality = 100)
174 {
175 if (bitmap == null || bitmap.IsEmpty)
176 throw new ArgumentException("Invalid bitmap.");
177
178 using var image = SKImage.FromBitmap(bitmap);
179 using var data = image.Encode(format, quality);
180 using var stream = File.OpenWrite(filePath);
181 data.SaveTo(stream);
182 }
183
191 public static byte[] EncodeBitmapToBytes(this SKBitmap bitmap, SKEncodedImageFormat format = SKEncodedImageFormat.Png, int quality = 100)
192 {
193 if (bitmap == null || bitmap.IsEmpty)
194 throw new ArgumentException("Invalid bitmap.");
195
196 using var image = SKImage.FromBitmap(bitmap);
197 using var data = image.Encode(format, quality);
198 return data.ToArray();
199 }
200
208 public static void ApplyAlphaMask(this SKBitmap bitmap, SKColor targetColor, byte tolerance = 5)
209 {
210 if (bitmap == null || bitmap.IsEmpty)
211 throw new ArgumentException("Invalid bitmap.");
212
213 int width = bitmap.Width;
214 int height = bitmap.Height;
215
216 for (int y = 0; y < height; y++)
217 {
218 for (int x = 0; x < width; x++)
219 {
220 var color = bitmap.GetPixel(x, y);
221
222 if (IsColorClose(color, targetColor, tolerance))
223 {
224 // Fully transparent should also have RGB = 0 for premul correctness.
225 bitmap.SetPixel(x, y, new SKColor(0, 0, 0, 0));
226 }
227 }
228 }
229
230 bitmap.NotifyPixelsChanged();
231 }
232
240 public static SKBitmap PremultiplyAlpha(this SKBitmap bitmap)
241 {
242 if (bitmap == null || bitmap.IsEmpty)
243 throw new ArgumentException("Invalid bitmap.");
244
245 if (bitmap.AlphaType == SKAlphaType.Premul)
246 return bitmap;
247
248 var info = new SKImageInfo(bitmap.Width, bitmap.Height, bitmap.ColorType, SKAlphaType.Premul);
249 var premulBitmap = new SKBitmap(info);
250
251 int width = bitmap.Width;
252 int height = bitmap.Height;
253
254 for (int y = 0; y < height; y++)
255 {
256 for (int x = 0; x < width; x++)
257 {
258 var c = bitmap.GetPixel(x, y);
259 byte a = c.Alpha;
260
261 byte r = (byte)((c.Red * a + 127) / 255);
262 byte g = (byte)((c.Green * a + 127) / 255);
263 byte b = (byte)((c.Blue * a + 127) / 255);
264
265 premulBitmap.SetPixel(x, y, new SKColor(r, g, b, a));
266 }
267 }
268
269 premulBitmap.NotifyPixelsChanged();
270 return premulBitmap;
271 }
272
273 private static bool IsColorClose(SKColor a, SKColor b, byte tolerance)
274 {
275 return
276 Math.Abs(a.Red - b.Red) <= tolerance &&
277 Math.Abs(a.Green - b.Green) <= tolerance &&
278 Math.Abs(a.Blue - b.Blue) <= tolerance;
279 }
280}
@ Rectangle
Particles are spawned uniformly within a rectangular area. This is the default distribution.