in ExampleGallery/GameOfLife.xaml.cs [129:227]
void CreateEffects()
{
// The Game of Life is a cellular automaton with very simple rules.
// Each cell (pixel) can be either alive (white) or dead (black).
// The state is updated by:
//
// - for each cell, count how many of its 8 neighbors are alive
// - if less than two, the cell dies from loneliness
// - if exactly two, the cell keeps its current state
// - if exactly three, the cell become alive
// - if more than three, the cell dies from overcrowding
// Step 1: use a convolve matrix to count how many neighbors are alive. This filter
// also includes the state of the current cell, but with a lower weighting. The result
// is an arithmetic encoding where (value / 2) indicates how many neighbors are alive,
// and (value % 2) is the state of the cell itself. This is divided by 18 to make it
// fit within 0-1 color range.
countNeighborsEffect = new ConvolveMatrixEffect
{
KernelMatrix = new float[]
{
2, 2, 2,
2, 1, 2,
2, 2, 2
},
Divisor = 18,
BorderMode = EffectBorderMode.Hard,
};
// Step 2: use a color transfer table to map the different states produced by the
// convolve matrix to whether the cell should live or die. Each pair of entries in
// this table corresponds to a certain number of live neighbors. The first of the
// pair is the result if the current cell is dead, or the second if it is alive.
float[] transferTable =
{
0, 0, // 0 live neighbors -> dead cell
0, 0, // 1 live neighbors -> dead cell
0, 1, // 2 live neighbors -> cell keeps its current state
1, 1, // 3 live neighbors -> live cell
0, 0, // 4 live neighbors -> dead cell
0, 0, // 5 live neighbors -> dead cell
0, 0, // 6 live neighbors -> dead cell
0, 0, // 7 live neighbors -> dead cell
0, 0, // 8 live neighbors -> dead cell
};
liveOrDieEffect = new DiscreteTransferEffect
{
Source = countNeighborsEffect,
RedTable = transferTable,
GreenTable = transferTable,
BlueTable = transferTable,
};
// Step 3: the algorithm is implemented in terms of white = live,
// black = dead, but we invert these colors before displaying the
// result, just 'cause I think it looks better that way.
invertEffect = new LinearTransferEffect
{
RedSlope = -1,
RedOffset = 1,
GreenSlope = -1,
GreenOffset = 1,
BlueSlope = -1,
BlueOffset = 1,
};
// Step 4: insert our own DPI compensation effect to stop the system trying to
// automatically convert DPI for us. The Game of Life simulation always works
// in pixels (96 DPI) regardless of display DPI. Normally, the system would
// handle this mismatch automatically and scale the image up as needed to fit
// higher DPI displays. We don't want that behavior here, because it would use
// a linear filter while we want nearest neighbor. So we insert a no-op DPI
// converter of our own. This overrides the default adjustment by telling the
// system the source image is already the same DPI as the destination canvas
// (even though it really isn't). We'll handle any necessary scaling later
// ourselves, using Transform2DEffect to control the interpolation mode.
var dpiCompensationEffect = new DpiCompensationEffect
{
Source = invertEffect,
SourceDpi = new Vector2(canvas.Dpi),
};
// Step 5: a transform matrix scales up the simulation rendertarget and moves
// it to the right part of the screen. This uses nearest neighbor filtering
// to avoid unwanted blurring of the cell shapes.
transformEffect = new Transform2DEffect
{
Source = dpiCompensationEffect,
InterpolationMode = CanvasImageInterpolation.NearestNeighbor,
};
}