Cosmos/source/Cosmos.System2/Graphics/Canvas.cs
José Pedro a58e3f2fa0 g3
2017-07-30 18:38:20 +01:00

437 lines
14 KiB
C#

//#define COSMOSDEBUG
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Collections;
using System.Collections.ObjectModel;
namespace Cosmos.System.Graphics
{
public abstract class Canvas
{
protected Mode mode;
protected List<Mode> availableModes;
static protected Mode defaultGraphicMode;
protected Canvas(Mode mode)
{
//Global.mDebugger.SendInternal($"Creating a new Canvas with Mode ${mode}");
availableModes = getAvailableModes();
defaultGraphicMode = getDefaultGraphicMode();
this.mode = mode;
}
protected Canvas()
{
Global.mDebugger.SendInternal($"Creating a new Canvas with default graphic Mode");
availableModes = getAvailableModes();
defaultGraphicMode = getDefaultGraphicMode();
this.mode = defaultGraphicMode;
}
abstract public List<Mode> getAvailableModes();
abstract protected Mode getDefaultGraphicMode();
public List<Mode> AvailableModes
{
get
{
return availableModes;
}
}
public Mode DefaultGraphicMode
{
get
{
return defaultGraphicMode;
}
}
public abstract Mode Mode
{
get;
set;
}
/* Clear all the Canvas with the Black color */
public void Clear()
{
Clear(Color.Black);
}
/*
* Clear all the Canvas with the specified color. Please note that it is a very naïve implementation and any
* driver should replace it (or with an hardware command or if not possible with a block copy on the IoMemoryBlock)
*/
public virtual void Clear(Color color)
{
Global.mDebugger.SendInternal($"Clearing the Screen with Color {color}");
//if (color == null)
// throw new ArgumentNullException(nameof(color));
Pen pen = new Pen(color);
for (int x = 0; x < mode.Rows; x++)
{
for (int y = 0; y < mode.Columns; y++)
{
DrawPoint(pen, x, y);
}
}
}
public abstract void DrawPoint(Pen pen, int x, int y);
public abstract void DrawPoint(Pen pen, float x, float y);
private void DrawHorizontalLine(Pen pen, int dx, int x1, int y1)
{
int i;
for (i = 0; i < dx; i++)
DrawPoint(pen, x1 + i, y1);
}
private void DrawVerticalLine(Pen pen, int dy, int x1, int y1)
{
int i;
for (i = 0; i < dy; i++)
DrawPoint(pen, x1, y1 + i);
}
/*
* To draw a diagonal line we use the fast version of the Bresenham's algorithm.
* See http://www.brackeen.com/vga/shapes.html#4 for more informations.
*/
private void DrawDiagonalLine(Pen pen, int dx, int dy, int x1, int y1)
{
int i, sdx, sdy, dxabs, dyabs, x, y, px, py;
dxabs = Math.Abs(dx);
dyabs = Math.Abs(dy);
sdx = Math.Sign(dx);
sdy = Math.Sign(dy);
x = dyabs >> 1;
y = dxabs >> 1;
px = x1;
py = y1;
if (dxabs >= dyabs) /* the line is more horizontal than vertical */
{
for (i = 0; i < dxabs; i++)
{
y += dyabs;
if (y >= dxabs)
{
y -= dxabs;
py += sdy;
}
px += sdx;
DrawPoint(pen, px, py);
}
}
else /* the line is more vertical than horizontal */
{
for (i = 0; i < dyabs; i++)
{
x += dxabs;
if (x >= dyabs)
{
x -= dyabs;
px += sdx;
}
py += sdy;
DrawPoint(pen, px, py);
}
}
}
/*
* DrawLine throw if the line goes out of the boundary of the Canvas, probably will be better to draw only the part
* of line visibile. This is too "smart" to do here better do it in a future Window Manager.
*/
public virtual void DrawLine(Pen pen, int x1, int y1, int x2, int y2)
{
if (pen == null)
throw new ArgumentOutOfRangeException(nameof(pen));
ThrowIfCoordNotValid(x1, y1);
ThrowIfCoordNotValid(x2, y2);
int dx, dy;
dx = x2 - x1; /* the horizontal distance of the line */
dy = y2 - y1; /* the vertical distance of the line */
if (dy == 0) /* The line is horizontal */
{
DrawHorizontalLine(pen, dx, x1, y1);
return;
}
if (dx == 0) /* the line is vertical */
{
DrawVerticalLine(pen, dy, x1, y1);
return;
}
/* the line is neither horizontal neither vertical, is diagonal then! */
DrawDiagonalLine(pen, dx, dy, x1, y1);
}
public void DrawLine(Pen pen, Point p1, Point p2)
{
DrawLine(pen, p1.X, p1.Y, p2.X, p2.Y);
}
public void DrawLine(Pen pen, float x1, float y1, float x2, float y2)
{
throw new NotImplementedException();
}
//https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
public virtual void DrawCircle(Pen pen, int x_center, int y_center, int radius)
{
if (pen == null)
throw new ArgumentNullException(nameof(pen));
ThrowIfCoordNotValid(x_center + radius, y_center);
ThrowIfCoordNotValid(x_center - radius, y_center);
ThrowIfCoordNotValid(x_center, y_center + radius);
ThrowIfCoordNotValid(x_center, y_center - radius);
int x = radius;
int y = 0;
int e = 0;
while(x>=y)
{
DrawPoint(pen, x_center + x, y_center + y);
DrawPoint(pen, x_center + y, y_center + x);
DrawPoint(pen, x_center - y, y_center + x);
DrawPoint(pen, x_center - x, y_center + y);
DrawPoint(pen, x_center - x, y_center - y);
DrawPoint(pen, x_center - y, y_center - x);
DrawPoint(pen, x_center + y, y_center - x);
DrawPoint(pen, x_center + x, y_center - y);
y++;
if(e<=0)
{
e += 2 * y + 1;
}
if(e>0)
{
x--;
e -= 2 * x + 1;
}
}
}
//http://members.chello.at/~easyfilter/bresenham.html
public virtual void DrawEllipse(Pen pen, int x_center, int y_center, int x_radius, int y_radius)
{
if (pen == null)
throw new ArgumentNullException(nameof(pen));
ThrowIfCoordNotValid(x_center + x_radius, y_center);
ThrowIfCoordNotValid(x_center - x_radius, y_center);
ThrowIfCoordNotValid(x_center, y_center + y_radius);
ThrowIfCoordNotValid(x_center, y_center - y_radius);
int a = 2 * x_radius;
int b = 2 * y_radius;
int b1 = b & 1;
int dx = 4 * (1 - a) * b * b;
int dy = 4 * (b1 + 1) * a * a;
int err = dx + dy + b1 * a * a;
int e2;
int y = 0;
int x = x_radius;
a *= 8 * a;
b1 = 8 * b * b;
while (x>=0)
{
DrawPoint(pen, x_center + x, y_center + y);
DrawPoint(pen, x_center - x, y_center + y);
DrawPoint(pen, x_center - x, y_center - y);
DrawPoint(pen, x_center + x, y_center - y);
e2 = 2 * err;
if (e2 <= dy) { y++; err += dy += a; }
if (e2 >= dx || 2 * err > dy) { x--; err += dx += b1; }
}
}
public virtual void DrawPolygon(Pen pen, params Point[] points)
{
if (points.Length < 3)
throw new ArgumentException("A polygon requires more than 3 points.");
if (pen == null)
throw new ArgumentNullException(nameof(pen));
for (int i = 0; i < points.Length - 1; i++)
{
DrawLine(pen, points[i], points[i + 1]);
}
DrawLine(pen, points[0], points[points.Length - 1]);
}
public virtual void DrawRectangle(Pen pen, int x, int y, int width, int height)
{
/*
* we must draw four lines connecting any vertex of our rectangle to do this we first obtain the position of these
* vertex (we call these vertexes A, B, C, D as for geometric convention)
*/
if (pen == null)
throw new ArgumentNullException(nameof(pen));
/* The check of the validity of x and y are done in DrawLine() */
/* The vertex A is where x,y are */
int xa = x;
int ya = y;
/* The vertex B has the same y coordinate of A but x is moved of width pixels */
int xb = x + width;
int yb = y;
/* The vertex C has the same x coordiate of A but this time is y that is moved of height pixels */
int xc = x;
int yc = y + height;
/* The Vertex D has x moved of width pixels and y moved of height pixels */
int xd = x + width;
int yd = y + height;
/* Draw a line betwen A and B */
DrawLine(pen, xa, ya, xb, yb);
/* Draw a line between A and C */
DrawLine(pen, xa, ya, xc, yc);
/* Draw a line between B and D */
DrawLine(pen, xb, yb, xd, yd);
/* Draw a line between C and D */
DrawLine(pen, xc, yc, xd, yd);
}
public virtual void DrawFilledRectangle(Pen pen, int x_start, int y_start, int width, int height)
{
for (int i = 0; i != width; i++)
{
DrawLine(pen, x_start, y_start, x_start + height, y_start);
y_start++;
}
}
public virtual void DrawTriangle(Pen pen, int v1x, int v1y, int v2x, int v2y, int v3x, int v3y)
{
DrawLine(pen, v1x, v1y, v2x, v2y);
DrawLine(pen, v1x, v1y, v3x, v3y);
DrawLine(pen, v2x, v2y, v3x, v3y);
}
public void DrawRectangle(Pen pen, float x_start, float y_start, float width, float height)
{
throw new NotImplementedException();
}
// Image and Font aren't implemented in .NET Core
//public void DrawImage(Image image, int x, int y)
//{
// throw new NotImplementedException();
//}
//public void DrawString(String str, Font aFont, Brush brush, int x, int y)
//{
// throw new NotImplementedException();
//}
protected bool CheckIfModeIsValid(Mode mode)
{
Global.mDebugger.SendInternal($"CheckIfModeIsValid");
if (mode == null)
return false;
foreach (var elem in availableModes)
{
if (elem == mode)
{
return true; // All OK mode does exists in availableModes
}
}
return false;
}
protected void ThrowIfModeIsNotValid(Mode mode)
{
if (mode == null)
{
Global.mDebugger.SendInternal($"mode is null raising exception!");
throw new ArgumentNullException(nameof(mode));
}
#if false
/* This would have been the more "modern" version but LINQ is not working */
if (!availableModes.Exists(element => element == mode))
throw new ArgumentOutOfRangeException($"Mode {mode} is not supported by this Driver");
#endif
foreach (var elem in availableModes)
{
if (elem == mode)
{
Global.mDebugger.SendInternal($"mode {mode} found");
return; // All OK mode does exists in availableModes
}
}
Global.mDebugger.SendInternal($"foreach ended mode is not found! Raising exception...");
/* 'mode' was not in the 'availableModes' List ==> 'mode' in NOT Valid */
throw new ArgumentOutOfRangeException(nameof(mode), $"Mode {mode} is not supported by this Driver");
}
protected void ThrowIfCoordNotValid(int x, int y)
{
if (x < 0 || x >= Mode.Columns)
{
throw new ArgumentOutOfRangeException(nameof(x),$"x ({x}) is not between 0 and {Mode.Columns}");
}
if (y < 0 || y >= Mode.Rows)
{
throw new ArgumentOutOfRangeException(nameof(y), $"y ({y}) is not between 0 and {Mode.Rows}");
}
}
}
public class Point
{
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
int x;
public int X
{
get { return x; }
set { x = value; }
}
int y;
public int Y
{
get { return y; }
set { y = value; }
}
}
}