GDI

Come renderizzare automi cellulari a due dimensioni con GDI e WPF

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);
}

 
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);
  }
}
game of life animated gif

Gioco della vita renderizzato a cubetti