sup { vertical-align:text-top; }

Foundations

Bitmaps And Pixel Bits

Charles Petzold

Contents

Using RenderTargetBitmap
Using WriteableBitmap
Arrays of Pixels
Bitmap Pixel Formats
PixelFormats Errata
Premultiplied Alphas
WriteableBitmap Applications

Send your questions and comments to mmnet30@microsoft.com.
Code download available at msdn.microsoft.com/magazine/cc135911.

The retained-mode graphics system of Windows® Presentation Foundation (WPF) has revolutionized Windows graphics programming. No longer are programs required to recreate their visual appearance on the screen whenever the system demands. The composition system retains all graphical figures and arranges them into a total visual presentation.

Retained-mode graphics certainly makes life more convenient, but convenience itself has never been a high priority for Windows programmers. It is really the combination of the retained-mode graphics system with notification mechanisms such as dependency properties that unleashes the flexibility and power of WPF. Graphical objects such as paths and brushes seem to remain "alive" within the composition system and continue to respond to property changes and graphics transforms, thus allowing these objects to be targets of data bindings and animations.

I recently discovered that WPF bitmaps have a similar dynamic quality. A rendered bitmap remains responsive to changes—not only to graphics transforms (this we all knew) but also to changes in the actual pixel bits within the bitmap.

The two bitmap classes that reveal this dynamic responsiveness are RenderTargetBitmap and WriteableBitmap, which are among the nine classes that derive from BitmapSource, the abstract class that is the basis for all bitmap support in WPF. Regardless of how a program uses one of these bitmap objects—whether it's displayed with an Image element, or made into a tiled brush with the ImageBrush class, or as part of a larger drawing (perhaps mixed with vector graphics) with the ImageDrawing class—the bitmap is never simply rendered and forgotten. The bitmap remains alive within the visual composition system and continues to be responsive to application changes.

Using RenderTargetBitmap

A RenderTargetBitmap is a bitmap on which you can effectively draw by transferring objects of type Visual to its surface. The only way to create a new object of type RenderTargetBitmap is with a constructor that requires the pixel dimensions of the bitmap, the horizontal and vertical resolution in dots per inch, and an object of type PixelFormat.

I'll have more to say about the PixelFormat structure and the related static PixelFormats class shortly. For creating objects of type RenderTargetBitmap you must use either PixelFormats.Default or PixelFormats.Pbgra32 as the last argument to the RenderTargetBitmap constructor. In either case, you're creating a bitmap with 32 bits per pixel and transparency.

Initially, the RenderTargetBitmap object is entirely transparent. You can then draw on the bitmap by calling the Render method with an object of type Visual (including classes that derive from Visual such as FrameworkElement and Control). You can restore the all-transparent image by calling Clear. If the bitmap is currently being displayed, these calls are reflected in the displayed bitmap without any further ado.

Figure 1 shows a small, complete program that demonstrates RenderTargetBitmap. The program creates a bitmap 1,200 pixels wide and 900 pixels high. These become the PixelWidth and PixelHeight properties of the bitmap object. Each pixel is 4 bytes wide, so the bitmap occupies more than 4 megabytes of memory.

Figure 1 RenderTargetBitmapDemo

class RenderTargetBitmapDemo : Window
{
    RenderTargetBitmap bitmap;

    [STAThread]
    public static void Main()
    {
        Application app = new Application();
        app.Run(new RenderTargetBitmapDemo());
    }
    public RenderTargetBitmapDemo()
    {
        Title = "RenderTargetBitmap Demo";
        SizeToContent = SizeToContent.WidthAndHeight;
        ResizeMode = ResizeMode.CanMinimize;

        // Create RenderTargetBitmap
        bitmap = new RenderTargetBitmap(1200, 900, 300, 300, 
                                        PixelFormats.Default);

        // Create Image for bitmap
        Image img = new Image();
        img.Stretch = Stretch.None;
        img.Source = bitmap;
        Content = img;
    }
    protected override void  OnMouseDown(MouseButtonEventArgs args)
    {
        Point ptMouse = args.GetPosition(this);
        Random rand = new Random();
        Brush brush = new SolidColorBrush(
            Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256), 
                                                (byte)rand.Next(256)));

        DrawingVisual vis = new DrawingVisual();
        DrawingContext dc = vis.RenderOpen();
        dc.DrawEllipse(brush, null, ptMouse, 12, 12);
        dc.Close();

        bitmap.Render(vis);
    }
    protected override void OnClosed(EventArgs args)
    {
        PngBitmapEncoder enc = new PngBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(bitmap));
        FileStream stream = new FileStream("RenderTargetBitmapDemo.png", 
                                      FileMode.Create, FileAccess.Write);
        enc.Save(stream);
        stream.Close();
    }
}

The call to the RenderTargetBitmap constructor also specifies a resolution of 300 dots per inch. The combination of the pixel dimension and the resolution results in the creation of a bitmap that is 4 inches wide and 3 inches tall. The device-independent WPF coordinate system is 96 units to the inch, so the bitmap has a device-independent width of 384 units and a height of 288 units. These are the numbers you'll see if you examine the Width and Height properties defined by BitmapSource.

The RenderTargetBitmapDemo program uses the Image element to display the bitmap without stretching. It also traps MouseDown events. For each mouse click, the program creates a DrawingVisual object of a little filled circle ¼ inch in diameter and then calls the Render method to add it to the bitmap image. The circle then shows up on the displayed bitmap. You can think of this program as a primitive painting application that combines rendering and storage functionality in a single bitmap.

You might know (or can probably guess) that the Image element displays a bitmap by calling the DrawImage method of the DrawingContext object passed to it during its OnRender method. The Image class does not get repeated calls to its OnRender method when the RenderTargetBitmapDemo program changes the bitmap. These changes to the bitmap are occurring much deeper in the visual composition system.

The RenderTargetBitmapDemo program saves the resultant bitmap in a PNG file format when the program terminates. If you examine the bitmap saved from RenderTargetBitmapDemo, you'll find that it is 1,200 by 900 pixels in size, and that each of the circles is 75 pixels in diameter—which is ¼ inch in units of 300 dots per inch.

Using WriteableBitmap

The WriteableBitmap class has two constructors, one of which is very similar to the RenderTargetBitmap constructor. The first four arguments are the pixel dimensions of the bitmap and the resolution in dots per inch. The fifth argument is a PixelFormat object, but you have much more flexibility than with RenderTargetBitmap. The WriteableBitmap constructor has an additional parameter for a color palette for those bitmap formats that require one.

The pixels of the WriteableBitmap are initialized to all zeroes. What this means depends on the pixel format. In many cases, the bitmap is entirely black. If the bitmap supports transparency, the bitmap is transparent. If the bitmap has a color palette, the entire bitmap is colored with the first color in the palette.

Changing a WriteableBitmap is very different from changing a RenderTargetBitmap. For WriteableBitmap you must call a method named WritePixels that copies actual pixel bits from a local array into the bitmap. Obviously it's crucial to match the format and size of the data in the array to the dimensions and pixel format of the bitmap.

Let's begin with a relatively easy example. The AnimatedBitmapBrush.cs program shown in Figure 2 creates a WriteableBitmap and uses it as the basis of a tiled ImageBrush that it sets to the Background property of the window. The program then sets a timer for 100 milliseconds and repeatedly calls WritePixels to change the bitmap.

Figure 2 AnimatedBitmapBrush

class AnimatedBitmapBrush : Window
{
    const int COLS = 48;
    const int ROWS = 48;

    WriteableBitmap bitmap;
    byte[] pixels = new byte[COLS * ROWS];
    byte pixelLevel = 0x00;

    [STAThread]
    public static void Main()
    {
        Application app = new Application();
        app.Run(new AnimatedBitmapBrush());
    }
    public AnimatedBitmapBrush()
    {
        Title = "Animated Bitmap Brush";
        Width = Height = 300;

        bitmap = new WriteableBitmap(COLS, ROWS, 96, 96, 
                                     PixelFormats.Gray8, null);
        ImageBrush brush = new ImageBrush(bitmap);
        brush.TileMode = TileMode.Tile;
        brush.Viewport = new Rect(0, 0, COLS, ROWS);
        brush.ViewportUnits = BrushMappingMode.Absolute;
        Background = brush;

        DispatcherTimer tmr = new DispatcherTimer();
        tmr.Interval = TimeSpan.FromMilliseconds(100);
        tmr.Tick += TimerOnTick;
        tmr.Start();
    }

    void TimerOnTick(object sender, EventArgs args)
    {
        for (int row = 0; row < ROWS; row++)
            for (int col = 0; col < COLS; col++)
            {
                int index = row * COLS + col;

                double distanceFromCenter = 
                    2 * Math.Max(Math.Abs(row - ROWS / 2.0) / ROWS,
                                 Math.Abs(col - COLS / 2.0) / COLS);
                pixels[index] =
                    (byte)(0x80 * (1 + distanceFromCenter * pixelLevel));
            }

        bitmap.WritePixels(new Int32Rect(0, 0, COLS, ROWS), pixels, COLS, 0);
        pixelLevel++;
    }
}

The constant COLS and ROWS values define the pixel dimensions of this bitmap. These same values are used for the tiled brush Viewport rectangle and also in the Tick event handler for the timer. The pixel format is set to PixelFormats.Gray8, which means that every pixel in the bitmap is represented by an 8-bit value that indicates a gray shade—the pixel value 0x00 is black and 0xFF is white. The last argument to the WriteableBitmap constructor is set to null because the Gray8 format does not require a palette.

Because each pixel is 1 byte, the dimension of the byte array that I've called pixels is calculated simply as COLS × ROWS. That pixels array is defined as a field so it doesn't have to be recreated in every call to the Tick event handler. The data in the array must begin with the topmost row from left to right, then the second row and so on. The Tick event handler has two loops for the rows and columns of the bitmap, but it combines those two values into a one-dimensional array index:

int index = row * COLS + col;

WritePixels will not accept multi-dimensional arrays. The first argument to WritePixels is an Int32Rect structure in units of pixel coordinates to indicate the rectangular subset of the bitmap to update. The X and Y properties of the Int32Rect object describe the coordinates of the upper-left corner of the rectangle relative to the upper-left corner of the bitmap; the Width and Height properties indicate the pixel dimensions of this rectangle. For updating the entire bitmap, set X and Y to 0 and Width and Height to the bitmap PixelWidth and PixelHeight properties. I'll discuss the final two arguments to WritePixels shortly.

I must confess that I was intent on coding a rather different animated pattern for this bitmap, but the one that I stumbled upon here seems to be rather entertaining—at least in small doses. One of the images is shown in Figure 3.

fig03.gif

Figure 3 AnimatedBitmapBrush Display

Arrays of Pixels

The mechanics of calling WritePixels can be more complex when the bitmap doesn't have a one-byte-per-pixel format and when only a rectangular subset of the bitmap is updated. It's an important technique to learn because the same array format of pixel bits is required in the static BitmapSource.Create method to create a new bitmap and in the CopyPixels methods of BitmapSource to copy pixel bits from a bitmap to an array. In all three methods you can alternatively use an IntPtr to point to a local buffer, but I'm going to focus on the array approach.

The total number of pixels in the bitmap is the product of the PixelWidth and PixelHeight properties defined by the BitmapSource class. BitmapSource also defines a get-only Format property of type PixelFormat, which itself defines a get-only property named BitsPerPixel that can range from 1 to 128. At one extreme, a single byte stores data for 8 consecutive pixels; at the other extreme, each pixel requires 16 bytes of data. You'll probably restrict yourself to arrays of byte, ushort, uint, or float for the pixel bits.

The Int32Rect object you supply to the WritePixels method defines a rectangular subset within the bitmap. The number of bytes in the pixel array must contain sufficient data for the number of rows and columns indicated by the Int32Rect object. This gets a little complicated because several pixel formats store multiple pixels in a single byte. For these formats, each row of data must begin on a byte boundary.

For example, suppose you're working with a bitmap with a format of 4 bits per pixel. The rectangular area of the bitmap that you're accessing or updating is 5 pixels wide and 12 pixels high. These are the Width and Height properties of the Int32Rect object you supply. The first byte in the array contains data for the first two pixels, the second for the next two pixels, but the third byte contains data for only the fifth pixel in the first row. The next byte corresponds to the first two pixels of the second row.

Each row of data requires 3 bytes, and the whole rectangular area requires 36 bytes. To help out with this calculation, the WritePixels method requires an argument called the stride. This is the number of bytes for each row of pixel data. The general calculation of the stride is this:

int stride = (width * bitsPerPixel + 7) / 8;

Width is equal to the Width property of the Int32Rect structure. Even if you use an array of ushort, uint, or float values, the stride value is always in units of bytes. You can then calculate the total number of bytes in the array like this:

int dimension = height * stride;

Divide by 2 or 4 or 8 if you're using an array of ushort, uint, or float, respectively.

You may recall that the Windows API required that each row of bitmap data begin on a 32-bit memory boundary so the stride had to be a multiple of four. This is not required in WPF. However, you can set the stride larger that the value calculated with the formula if that is convenient for you. For example, you might be working with a bitmap that has one byte per pixel, but your array is of type uint rather than byte. In this case, each element in the array stores four pixels. You might want to begin each row of the array on a unit boundary, even though that's not strictly required, because on many current hardware platforms, aligned copies are typically faster than unaligned copies.

Bitmap Pixel Formats

Each pixel in a bitmap is represented by one or more bits that define the color of that bitmap. In WPF, a particular pixel format is represented by an object of the structure type PixelFormat. The static PixelFormats class defines 26 static properties of type PixelFormat that you can use when creating a bitmap. These are shown in Figure 4, divided into two groups: writeable formats and non-writeable formats. With three exceptions (Bgr555, Bgr565, and Bgr101010), any number in the property name is the same as the number of bits per pixel.

Figure 4 Static Properties of the PixelFormat Class

Writeable Formats
Indexed1
Indexed2
Indexed4
Indexed8
 
BlackWhite
Gray2
Gray4
Gray8
 
Bgr555
Bgr565
 
Bgr32
Bgra32
Pbgra32
Non-Writeable Formats
Default
 
Bgr24
Rgb24
Bgr101010
Cmyk32
 
Gray16
Rgb48
Rgba64
Prgba64
 
Gray32Float
Rgb128Float
Rgba128Float
Prgba128Float

When creating a bitmap with BitmapSource.Create, you can use any of the static properties of the PixelFormats class except PixelFormats.Default. When creating a bitmap of type WriteableBitmap, you can only use the writeable formats.

The formats that begin with the word Indexed are those that require a ColorPalette object in the static BitmapSource.Create method or the WriteableBitmap constructor. Each pixel is an index into the ColorPalette object, so these four formats are associated with a maximum of 2, 4, 16, and 256 colors, respectively. The ColorPalette doesn't have to be as large as the maximum number of colors if the actual pixel bits don't go as high as they could.

For formats of fewer than 8 bits per pixels, the most significant bits in a byte correspond to the leftmost pixels. For example, with the Indexed2 format, a byte of 0xC9 is equivalent to the binary 11001001 and corresponds to four 2-bit values of 11, 00, 10, and 01. These, in turn, correspond to the fourth, first, third, and second colors in the ColorPalette collection.

The BlackWhite, Gray2, Gray4, and Gray8 formats are gray-shaded bitmaps with 1, 2, 4, or 8 bits per pixel. A pixel of all zeros is black and a pixel of all ones is white.

The remaining five writeable formats are color formats. The letters B, G, and R stand for the blue, green, and red primaries. The letter A stands for alpha channel and indicates that the bitmap supports transparency. The letter P stands for pre-multiplied alpha, which I'll discuss shortly.

The Bgr555 and Bgr565 formats both require 16 bits (or 2 bytes) per pixel. The Bgr555 format uses 5 bits for each color primary (thus allowing 32 gradations) with one bit left over. If the bits of the blue primary are represented by B0 (least significant bit) through B4 (most significant) and similarly for green and red, two consecutive bytes of data store the three primaries, as shown in Figure 5.

fig05.gif

Figure 5 Two-Byte Pixel Format

Notice that the green bits spread over 2 bytes. This arrangement makes much more sense when you realize that the pixel is really a 16-bit unsigned integer with the least-significant byte stored first. The diagram in Figure 6 shows how the primaries are encoded in a single short integer.

fig06.gif

Figure 6 Short Integer Pixel Format

Just so you can assure yourself that this is the case, the Gradient555Demo program shown in Figure 7 creates a bitmap of this format and writes pixels to it that display a gradient from blue on the left to green on the right. Notice that the dimension of the ushort array is just the product of the total rows and columns.

Figure 7 Gradient555Demo

class Indexed2Demo : Window
{
    const int COLS = 50;
    const int ROWS = 20;

    [STAThread]
    public static void Main()
    {
        Application app = new Application();
        app.Run(new Indexed2Demo());
    }
    public Indexed2Demo()
    {
        Title = "Bgr555 Bitmap Demo";

        WriteableBitmap bitmap = new WriteableBitmap(COLS, ROWS, 96, 96,
                                        PixelFormats.Bgr555, null);

        ushort[] pixels = new ushort[ROWS * COLS];

        for (int row = 0; row < ROWS; row++)
            for (int col = 0; col < COLS; col++)
            {
                int index = row * COLS + col;
                int blue = (COLS - col) * 0x1F / COLS;
                int green = col * 0x1F / COLS;
                ushort pixel = (ushort)(green << 5 | blue);
                pixels[index] = pixel;
            }

        int stride = (COLS * bitmap.Format.BitsPerPixel + 7) / 8;
        bitmap.WritePixels(new Int32Rect(0, 0, COLS, ROWS), pixels,
                           stride, 0);

        Image img = new Image();
        img.Source = bitmap;
        Content = img;
    }
}

This code demonstrates the advantage of using an array of a type other than byte that corresponds to the number of bytes per pixel. For any row and column pixel address, the array index is simply the column plus the product of the row times the number of pixels per column.

The Bgr565 format is very similar to Bgr555 except that it uses 6 bits for green (to which the eye is most sensitive). The remaining three writeable formats are much easier to work with. All use 4 bytes per pixel beginning with blue. In the Bgr32 format, the last of the 4 bytes is zero; there is no transparency. In the other two formats, the fourth byte is the alpha channel. The alpha value ranges from 0x00 for transparent to 0xFF for opaque. When the pixel is treated as an unsigned 32-bit integer, the least significant 8 bits encode blue, and the most significant 8 bits are either zero or the alpha channel.

In general, the ordering of bytes in the bitmap corresponds to the ordering of the letters B, G, R, and A in the property name. The list of non-writeable formats begins with several color formats that use only 16 or 24 bits per pixel. The Bgr101010 format uses 32 bits per pixel, but 10 bits for each primary. When the pixel is represented as a 32-bit unsigned integer, the least significant 10 bits are for blue. The Cmyk32 format encodes levels of cyan, magenta, yellow, and black used in printing.

The Gray16, Rgb48, Rgba64, and Prgba64 formats encode a 16-bit gray shade and 16-bit color primaries. If you work with hardware and applications that require this much color precision—medical imaging, for example—you might be quite happy that you can now store and display such high-resolution bitmap data. However, there is no real reason to use these formats otherwise. The additional color precision is ignored on 8 bit-per-primary color displays or when saving to 8 bit-per-primary file formats.

The list of pixel formats concludes with four formats that use single-precision floating point values to represent color levels and transparency. These formats are based on the scRGB color space and a gamma value of 1, rather than the customary sRGB color space and a gamma value of 2.2. (See pages 24-25 in my book, Applications = Code + Markup, for an explanation.) A floating-point color value of 0.0 corresponds to a byte value of 0x00 (black), and a floating-point color value of 1.0 corresponds to a byte value of 0xFF, but the floating-point color values might exceed 1 for display devices that have a wider color gamut than video displays.

PixelFormats Errata

There seems to be some confusion in the PixelFormats documentation about some formats. The Gray16, Rgb48, Rgba64, and Prgba64 formats are all documented as being based on a gamma value of 1, but—except for Gray16—they're also paradoxically documented as being an sRGB format. This is just not so. Only the Float pixel formats use the scRGB color format and a gamma value of 1.

You might want a generalized method to break apart pixels into their color components, or to construct pixels from color components. The PixelFormat structure contains a property named Mask that is a collection of objects of type PixelFormatChannelMask. There is one PixelFormatChannelMask object for each color channel in the order blue, green, red, and alpha.

The PixelFormatChannelMask structure defines a Mask property that is a collection of bytes, the number of which equals the number of bytes per pixel, and which correspond to the byte ordering of the pixel. For example, for the Bgr555 format, there are three PixelFormatChannelMask objects, each of which has 2 bytes. For blue, the 2 bytes are 0x1F and 0x00; for green, they are 0xE0 and 0x03; for red, they're 0x00 and 0x7C. If you want to use this data, you'll have to derive your own bit-shifting factors.

I said that you can use any of the PixelFormats members except for PixelFormats.Default for the BitmapSource.Create method, but only the writeable formats of Figure 4 for the WriteableBitmap constructor. If you examine the WriteableBitmap documentation, you'll discover an alternative constructor that creates a WriteableBitmap object from any BitmapSource object.

You can indeed first create a BitmapSource object from a non-writeable format of Figure 4 and then create a WriteableBitmap based on that BitmapSource. But don't assume you're getting around the restriction: any bitmap with a non-writeable format is converted to a Bgr32 or Pbgra32 format depending on the presence of an alpha channel.

You can save any bitmap that you create to a file in any of the supported file formats, specifically BMP, GIF, PNG, JPEG, TIFF, and Microsoft Windows Media Photo. However, the bitmap data might be converted to a different format in the process. For example, when saving as a GIF file, the bitmap is always converted first to an Indexed8 format. When saving as a JPEG file, the bitmap is always converted to either Gray8 or Bgr32. At the present time, there is no combination of PixelFormat and BitmapEncoder that will leave you with a file that contains more than 8 bits of data per color primary.

Premultiplied Alphas

Three of the static properties of the PixelFormats class begin with the letter P, which stands for Premultiplied Alphas. This is a technique used to improve bitmap rendering efficiency for pixels that are partially transparent. It only applies to bitmaps that have an alpha channel.

Suppose you created a SolidColorBrush with a color calculated like this:

Color.FromArgb(128, 0, 0, 255)

That's a blue brush with 50 percent transparency. When that brush is rendered, the color must be combined with the existing color of the display surface. Drawn against a black background, the resultant RGB color is (0, 0, 128). Against a white background, the resultant color is (127, 127, 255). It's a simple weighted-average calculation.

The subscripts in the following formulas indicate the result of rendering a partially transparent pixel on an existing surface:

Rresult = [(255 – Apixel) * Rsurface + Apixel * Rpixel] / 255;
Gresult = [(255 – Apixel) * Gsurface + Apixel * Gpixel] / 255;
Bresult = [(255 – Apixel) * Bsurface + Apixel * Bpixel] / 255;

This calculation can be sped up if the R, G, and B values of the pixel have already been multiplied by the A value and divided by 255. The second multiplication in each formula can then be eliminated. For example, suppose a pixel in a Bgra32 bitmap is the ARGB value (192, 40, 60, 255). In a Pbgra32 bitmap, the same pixel would be (192, 30, 45, 192). The RGB values have already been multiplied by the alpha value of 192 and divided by 255.

For any pixel in a Pbgra32 bitmap, none of the R, G, or B values should be greater than the A value. Nothing will blow up if that's not the case. The values are bound to a maximum of 255, but you won't get the level of transparency you want.

WriteableBitmap Applications

You might make use of WriteableBitmap in applications where you need to display some simple dynamic graphics—perhaps a bar chart—and you find that you can update a bitmap faster than WPF can draw the corresponding vector graphics.

Perhaps the most common application for WriteableBitmap is in performing real-time image processing and nonlinear transforms. The TwistedBitmap project lets you load any 8 bit-per-pixel or 32 bit-per-pixel bitmap and use a Slider control to twist the image around its center, as shown in Figure 8. Use smaller images for best results.

fig08.gif

Figure 8 Twisted Bitmap

The program uses BitmapFrame.Create to load a bitmap from a file and then calls CopyPixels to copy all the pixel bits in an array named pixelsSrc. The SliderOnValueChanged event handler shown in Figure 9 is responsible for transforming the pixels from pixelsSrc into an array named pixelsNew that it uses for calling WritePixels.

Figure 9 Transform in TwistedBitmap

void SliderOnValueChanged(object sender,
                          RoutedPropertyChangedEventArgs<double> args)
{
    if (pixelsSrc == null)
        return;

    Slider slider = sender as Slider;
    int width = bitmap.PixelWidth;
    int height = bitmap.PixelHeight;
    int xCenter = width / 2;
    int yCenter = height / 2;
    int bytesPerPixel = bitmap.Format.BitsPerPixel / 8;

    for (int row = 0; row < bitmap.PixelHeight; row += 1)
    {
        for (int col = 0; col < bitmap.PixelWidth; col += 1)
        {
            // Calculate length of point to center and angle
            int xDelta = col - xCenter;
            int yDelta = row - yCenter;
            double distanceToCenter = Math.Sqrt(xDelta * xDelta +
                                                yDelta * yDelta);
            double angleClockwise = Math.Atan2(yDelta, xDelta);

            // Calculate angle of rotation for twisting effect 
            double xEllipse = xCenter * Math.Cos(angleClockwise);
            double yEllipse = yCenter * Math.Sin(angleClockwise);
            double radius = Math.Sqrt(xEllipse * xEllipse +
                                      yEllipse * yEllipse);
            double fraction = Math.Max(0, 1 - distanceToCenter / radius);
            double twist = fraction * Math.PI * slider.Value / 180;

            // Calculate the source pixel for each destination pixel
            int colSrc = (int) (xCenter + (col - xCenter) *
                                Math.Cos(twist)
                (row - yCenter) * Math.Sin(twist));
            int rowSrc = (int) (yCenter + (col - xCenter) *
                                Math.Sin(twist) 
                + (row - yCenter) * Math.Cos(twist));
            colSrc = Math.Max(0, Math.Min(width - 1, colSrc));
            rowSrc = Math.Max(0, Math.Min(height - 1, rowSrc));

            // Calculate the indices
            int index = stride * row + bytesPerPixel * col;
            int indexSrc = stride * rowSrc + bytesPerPixel * colSrc;

            // Transfer the pixels
            for (int i = 0; i < bytesPerPixel; i++)
                pixelsNew[index + i] = pixelsSrc[indexSrc + i];
        }
    }
    // Write out the array
    bitmap.WritePixels(rect, pixelsNew, stride, 0);
}

When working with bitmap transforms, it is crucial to perform the transform in reverse. If you examine every pixel in the original bitmap and determine where it should go in the new bitmap, it is very likely that different pixels in the original bitmap could map to the same pixel in the new bitmap. That means that some pixels in the new bitmap will not be set to any value! The image will have "holes."

Instead, for every pixel in the new bitmap, you must find what pixel in the original bitmap maps to that particular row and column. This approach ensures that every pixel in the new bitmap has a calculated value.

Charles Petzold is a Contributing Editor to MSDN Magazine. His most recent book is The Annotated Turing: A Guided Tour through Alan Turing's Historic Paper on Computability and the Turing Machine.