Ok, noi abbiamo il nostro automa cellulare bidimensionale, un divertente gioco della vita interattivo implementato in C# / WPF. Vediamo come estendere il rendering grafico ad armoniose e più interessanti varianti. Definiamo prima di tutto l’interfaccia IRender:
public interface IRender { int CellSize { get; set; } void SetColorPalette(IList<System.Drawing.SolidBrush> colorsPalette); void SetColorPalette(IList<System.Windows.Media.Color> colorsPalette); System.Windows.Controls.Image Render(byte[] buf, int width, int height); }
Il rendering base, basato cioè sulla corrispondenza “cella” -> “1×1 pixel su schermo” viene implementato agevolmente utilizzando BitmapImage.Create():
class RenderBase : IRender { public int CellSize { get { return 1; } set { } } private BitmapPalette Palette = BitmapPalettes.BlackAndWhite; public void SetColorPalette(IList<System.Drawing.SolidBrush> colorsPalette) { Palette = new BitmapPalette(ImageUtils.ConvertPaletteInMediaColor(colorsPalette)); } public void SetColorPalette(IList<System.Windows.Media.Color> colorsPalette) { Palette = new BitmapPalette(colorsPalette); } public System.Windows.Controls.Image Render(byte[] buf, int width, int height) { const double dpi = 96; return new Image() { Source = BitmapImage.Create((int)width, (int)height, dpi, dpi, PixelFormats.Indexed8, Palette, buf, (int)width) }; } public override string ToString() { return "1x1 Pixel (fastest)"; } }
Per ottenere diversi render più complessi, basati sulla forma della singola cella e relativamente al bordo della stessa, creiamo una classe astratta RenderGraphics che utilizza le librerie grafiche GDI+ per il disegno delle singole celle e la composizione dell’ambiente.
abstract class RenderGraphics : IRender { public int CellSize { get; set; } protected IList<System.Drawing.SolidBrush> Palette { get; private set; } public void SetColorPalette(IList<System.Drawing.SolidBrush> colorsPalette) { foreach (System.Drawing.SolidBrush brush in Palette) brush.Dispose(); Palette = colorsPalette; } public void SetColorPalette(IList<System.Windows.Media.Color> colorsPalette) { Palette = ImageUtils.ConvertPaletteInDrawingBrush(colorsPalette); } public System.Windows.Controls.Image Render(byte[] buf, int width, int height) { using (Bitmap b = new Bitmap(width * CellSize, height * CellSize)) { using (Graphics g = Graphics.FromImage(b)) { g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed; for (int i = 0; i < width * height; i++) { int x = (int)(i % width); int y = (int)(i / width); Draw(g, Palette[buf[i]], x, y); } } return ImageUtils.ConvertToWpfImage(b); } } protected abstract void Draw(Graphics g, System.Drawing.SolidBrush brush, int x, int y); }
- RenderFilledRects
- RenderFilledRectsBorder
- RenderEmptyRects
- RenderFilledDotsBorder
- RenderFilledDots
- RenderEmptyDots
I diversi rendering grafici si ottengono estendendo RenderGraphics e implementando il metodo Draw():
class RenderFilledRects : RenderGraphics { public override string ToString() { return "Filled Rects without border"; } protected override void Draw(Graphics g, System.Drawing.SolidBrush brush, int x, int y) { g.FillRectangle(brush, x + (CellSize - 1) * x, y + (CellSize - 1) * y, CellSize, CellSize); } } class RenderFilledRectsBorder : RenderGraphics { public override string ToString() { return "Filled Rects with border"; } protected override void Draw(Graphics g, System.Drawing.SolidBrush brush, int x, int y) { g.FillRectangle(brush, x + (CellSize - 1) * x, y + (CellSize - 1) * y, CellSize - 1, CellSize - 1); } } class RenderFilledDots : RenderGraphics { public override string ToString() { return "Filled Dots without border"; } protected override void Draw(Graphics g, SolidBrush brush, int x, int y) { g.FillEllipse(brush, x + (CellSize-1) * x, y + (CellSize-1) * y, CellSize+1, CellSize+1); } } class RenderFilledDotsBorder : RenderGraphics { public override string ToString() { return "Filled Dots with border"; } protected override void Draw(Graphics g, SolidBrush brush, int x, int y) { g.FillEllipse(brush, x + (CellSize - 1) * x, y + (CellSize - 1) * y, CellSize-1, CellSize-1); } } class RenderEmptyDots : RenderGraphics { public override string ToString() { return "Empty Dots"; } protected override void Draw(Graphics g, SolidBrush brush, int x, int y) { g.DrawEllipse(new Pen(brush, 1), x + (CellSize - 1) * x, y + (CellSize - 1) * y, CellSize - 2, CellSize - 2); } } class RenderEmptyRects : RenderGraphics { public override string ToString() { return "Empty Rects"; } protected override void Draw(Graphics g, SolidBrush brush, int x, int y) { g.DrawRectangle(new Pen(brush, 1), x + (CellSize - 1) * x, y + (CellSize - 1) * y, CellSize - 2, CellSize - 2); } }