Here's what I found:
I created a class that subclasses Image.
public class MyImage : Image {
// the pixel format for the image. This one is blue-green-red-alpha 32bit format
private static PixelFormat PIXEL_FORMAT = PixelFormats.Bgra32;
// the bitmap used as a pixel source for the image
WriteableBitmap bitmap;
// the clipping bounds of the bitmap
Int32Rect bitmapRect;
// the pixel array. unsigned ints are 32 bits
uint[] pixels;
// the width of the bitmap. sort of.
int stride;
public MyImage(int width, int height) {
// set the image width
this.Width = width;
// set the image height
this.Height = height;
// define the clipping bounds
bitmapRect = new Int32Rect(0, 0, width, height);
// define the WriteableBitmap
bitmap = new WriteableBitmap(width, height, 96, 96, PIXEL_FORMAT, null);
// define the stride
stride = (width * PIXEL_FORMAT.BitsPerPixel + 7) / 8;
// allocate our pixel array
pixels = new uint[width * height];
// set the image source to be the bitmap
this.Source = bitmap;
}
WriteableBitmap has a method called WritePixels, which takes an array of unsigned ints as pixel data. I set the source of the image to be the WriteableBitmap. Now, when I update the pixel data and call WritePixels, it updates the image.
I store the business point data in a separate object as a List of Points. I perform transformations on the list, and update the pixel data with the transformed points. This way there's no overhead from Geometry objects.
Just FYI, I connect my points with lines drawn using something called Bresenham's algorithm.
This method is extremely fast. I'm updating about 50,000 points (and connecting lines) in response to mouse movements, with no noticeable lag.