-
-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathGraphics.cs
407 lines (355 loc) · 14.5 KB
/
Graphics.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// Copyright (c) 2022 The nanoFramework project contributors
// See LICENSE file in the project root for full license information.
using System;
using System.Drawing;
using Iot.Device.EPaper.Buffers;
using Iot.Device.EPaper.Drivers;
using Iot.Device.EPaper.Enums;
using Iot.Device.EPaper.Fonts;
using nanoFramework.UI;
namespace Iot.Device.EPaper
{
/// <summary>
/// A graphics class for ePaper displays with basic graphic APIs support.
/// </summary>
public sealed class Graphics : IDisposable
{
private bool _disposedValue;
/// <summary>
/// Gets the E-Paper display being controlled by this <see cref="Graphics"/> class instance.
/// </summary>
public IEPaperDisplay EPaperDisplay { get; }
/// <summary>
/// Gets or sets the current display orientation.
/// </summary>
/// <see cref="Rotation"/>
public Rotation DisplayRotation { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Graphics"/> class.
/// </summary>
/// <param name="ePaperDisplay">The E-Paper display device to draw to.</param>
public Graphics(IEPaperDisplay ePaperDisplay)
{
EPaperDisplay = ePaperDisplay;
DisplayRotation = Rotation.Default;
}
/// <summary>
/// Draws a line from the a starting point to an end point.
/// </summary>
/// <param name="startX">X position of the start point.</param>
/// <param name="startY">Y position of the start point.</param>
/// <param name="endX">X position of the end point.</param>
/// <param name="endY">Y position of the end point.</param>
/// <param name="color">The color of the line.</param>
public void DrawLine(int startX, int startY, int endX, int endY, Color color)
{
// This is a common line drawing algorithm. Read about it here:
// http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
int sx = (startX < endX) ? 1 : -1;
int sy = (startY < endY) ? 1 : -1;
int dx = endX > startX ? endX - startX : startX - endX;
int dy = endY > startX ? endY - startY : startY - endY;
float err = dx - dy, e2;
// if there is an error with drawing a point or the line is finished get out of the loop!
while (!(startX == endX && startY == endY))
{
DrawPixel(startX, startY, color);
e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
startX += sx;
}
if (e2 < dx)
{
err += dx;
startY += sy;
}
}
}
/// <summary>
/// Draws a circle defined by the specified center point and radius.
/// </summary>
/// <param name="centerX">X position of the center point.</param>
/// <param name="centerY">Y position of the center point.</param>
/// <param name="radius">The circle's radius.</param>
/// <param name="color">The color to use when drawing the circle.</param>
/// <param name="fill">True to fill the circle, otherwise; draws only the outline.</param>
public void DrawCircle(int centerX, int centerY, int radius, Color color, bool fill)
{
if (fill)
{
DrawCircleFilled(centerX, centerY, radius, color);
}
else
{
DrawCircleOutline(centerX, centerY, radius, color);
}
}
/// <summary>
/// Draws a rectangle defined by a starting point, width, and height.
/// </summary>
/// <param name="startX">Top left point X position.</param>
/// <param name="startY">Top left point Y position.</param>
/// <param name="width">The width of the rectangle in pixels.</param>
/// <param name="height">The height of the rectangle in pixels.</param>
/// <param name="color">The color to use when drawing the rectangle.</param>
/// <param name="fill">True to fill the rectangle, otherwise; draws only the outline.</param>
public void DrawRectangle(int startX, int startY, int width, int height, Color color, bool fill)
{
// This will draw points
int endX = startX + width;
int endY = startY + height;
if (fill)
{
DrawRectangleFilled(startX, startY, endX, endY, color);
}
else
{
DrawRectangleOutline(startX, startY, endX, endY, color);
}
}
/// <summary>
/// Writes text to the display.
/// </summary>
/// <param name="text">The text to write.</param>
/// <param name="font">The font to use.</param>
/// <param name="x">Starting X point.</param>
/// <param name="y">Starting Y point.</param>
/// <param name="color">The font color.</param>
public void DrawText(string text, IFont font, int x, int y, Color color)
{
var col = 0;
var line = 0;
foreach (char character in text)
{
if (col == EPaperDisplay.Width)
{
col = 0;
line += font.Height + 1;
}
var characterBitmap = font[character];
for (var i = 0; i < font.Height; i++)
{
var xPos = x + col;
var yPos = y + line + i;
var bitMask = 0x01;
var b = characterBitmap[i];
for (var pixel = 0; pixel < 8; pixel++)
{
if ((b & bitMask) > 0)
{
DrawPixel(xPos + pixel, yPos, color);
}
bitMask <<= 1;
}
}
col += font.Width;
}
}
/// <summary>
/// Draws the specified bitmap buffer to the display using the specified starting point.
/// </summary>
/// <param name="bitmap">The bitmap buffer to draw.</param>
/// <param name="start">The start point on the display to start drawing from.</param>
/// <param name="rotate"><see langword="true"/> to rotate the bitmap with the current <see cref="Rotation"/> specified. It might be slow.</param>
public void DrawBitmap(IFrameBuffer bitmap, System.Drawing.Point start, bool rotate = false)
{
if (DisplayRotation == Rotation.Default)
{
EPaperDisplay.FrameBuffer.WriteBuffer(bitmap, destinationStart: start);
return;
}
if (!rotate)
{
EPaperDisplay.FrameBuffer.WriteBuffer(bitmap, destinationStart: start);
}
else
{
// caller opted in to rotate (slow)
for (var y = 0; y < bitmap.Height; y++)
{
for (var x = 0; x < bitmap.Width; x++)
{
var currentPoint = new System.Drawing.Point(x, y);
EPaperDisplay
.FrameBuffer
.SetPixel(new System.Drawing.Point(start.X + currentPoint.X, start.Y + currentPoint.Y), bitmap.GetPixel(currentPoint));
}
}
}
}
/// <summary>
/// Gets the real X Position of a given point after considering the current <see cref="Rotation"/> of the display.
/// </summary>
/// <param name="x">The X Position in the current rotation.</param>
/// <param name="y">The Y Position in the current rotation.</param>
/// <returns>The real X position on the display.</returns>
private int GetRealXPosition(int x, int y)
{
switch (DisplayRotation)
{
case Rotation.Degrees90Clockwise:
return EPaperDisplay.Width - y - 1;
case Rotation.Degrees180Clockwise:
return EPaperDisplay.Width - x - 1;
case Rotation.Degrees270Clockwise:
return y;
default:
return x;
}
}
/// <summary>
/// Gets the real Y Position of a given point after considering the current <see cref="Rotation"/> of the display.
/// </summary>
/// <param name="x">The X Position in the current rotation.</param>
/// <param name="y">The Y Position in the current rotation.</param>
/// <returns>The real Y position on the display.</returns>
private int GetRealYPosition(int x, int y)
{
switch (DisplayRotation)
{
case Rotation.Degrees90Clockwise:
return x;
case Rotation.Degrees180Clockwise:
return EPaperDisplay.Height - y - 1;
case Rotation.Degrees270Clockwise:
return EPaperDisplay.Height - x - 1;
default:
return y;
}
}
/// <summary>
/// Gets the real Position of a given point after considering the current <see cref="Rotation"/> of the display.
/// </summary>
/// <param name="x">The X Position in the current rotation.</param>
/// <param name="y">The Y Position in the current rotation.</param>
/// <returns>The real position on the display.</returns>
public System.Drawing.Point GetRealPosition(int x, int y)
{
return new System.Drawing.Point(GetRealXPosition(x, y), GetRealYPosition(x, y));
}
private void DrawRectangleOutline(int startX, int startY, int endX, int endY, Color color)
{
endX -= 1;
endY -= 1;
for (int currentX = startX; currentX != endX; currentX++)
{
DrawPixel(currentX, startY, color);
}
for (int currentX = startX; currentX <= endX; currentX++)
{
DrawPixel(currentX, endY, color);
}
for (int currentY = startY; currentY != endY; currentY++)
{
DrawPixel(startX, currentY, color);
}
for (int currentY = startY; currentY <= endY; currentY++)
{
DrawPixel(endX, currentY, color);
}
}
private void DrawRectangleFilled(int startX, int startY, int endX, int endY, Color color)
{
for (int currentY = startY; currentY != endY; currentY++)
{
for (int xx = startX; xx != endX; xx++)
{
DrawPixel(xx, currentY, color);
}
}
}
private void DrawCircleOutline(int centerX, int centerY, int radius, Color color)
{
// Midpoint Circle Algorithm: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
void drawCircle(int xc, int yc, int x, int y, Color color)
{
DrawPixel(xc + x, yc + y, color);
DrawPixel(xc - x, yc + y, color);
DrawPixel(xc + x, yc - y, color);
DrawPixel(xc - x, yc - y, color);
DrawPixel(xc + y, yc + x, color);
DrawPixel(xc - y, yc + x, color);
DrawPixel(xc + y, yc - x, color);
DrawPixel(xc - y, yc - x, color);
}
int x = 0, y = radius;
// This determines when to decrement y.
int determinant = 3 - (2 * radius);
drawCircle(centerX, centerY, x, y, color);
while (y >= x)
{
// for each pixel we will draw all eight pixels
x++;
// check for decision parameter and correspondingly
// update d, x, y
if (determinant > 0)
{
y--;
determinant = ((determinant + 4) * (x - y)) + 10;
}
else
{
determinant = (determinant + 4) * (x + 6);
}
drawCircle(centerX, centerY, x, y, color);
}
}
private void DrawCircleFilled(int centerX, int centerY, int radius, Color color)
{
// Midpoint Circle Algorithm: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
// C# Implementation: https://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#C.23
int x = 0, y = radius;
// This determines when to decrement y.
var determinant = 3 - (2 * radius);
while (x <= y)
{
DrawLine(centerX + x, centerY + y, centerX - x, centerY + y, color);
DrawLine(centerX + x, centerY - y, centerX - x, centerY - y, color);
DrawLine(centerX - y, centerY + x, centerX + y, centerY + x, color);
DrawLine(centerX - y, centerY - x, centerX + y, centerY - x, color);
if (determinant < 0)
{
determinant += (2 * x) + 1;
}
else
{
determinant += (2 * (x - y)) + 1;
y--;
}
x++;
}
}
/// <summary>
/// Draws a pixel on the display with respect to the current <see cref="DisplayRotation"/>.
/// </summary>
/// <param name="x">The X position.</param>
/// <param name="y">The Y position.</param>
/// <param name="color">The color to use when drawing the pixel.</param>
public void DrawPixel(int x, int y, Color color)
{
EPaperDisplay.FrameBuffer.SetPixel(GetRealPosition(x, y), color);
}
#region IDisposable
private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
EPaperDisplay?.Dispose();
}
_disposedValue = true;
}
}
/// <inheritdoc/>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
}