Pre-multiplied alpha

Pre-multiplied alpha is alpha blending done correctly. It involves changing your alpha blending states, and ensuring your texture colour channels (R, G, B) are pre-multiplied (hence the name) with the alpha channel (A).

Firstly the alpha blending operations will have to be changed:

  • Set your source alpha blend to ONE (probably instead of SRCALPHA) – How solid our source texture appears depends on our alpha value alone (pre-multiplied colour channels at work here).
  • Set your destination blend to INVSRCALPHA – The more solid our texture appears, the less light we should let through from behind
// Standard fare alpha texture operations
Device.SetTextureStageState(0, TextureStage.AlphaArg1, TextureArgument.Texture);
Device.SetTextureStageState(0, TextureStage.AlphaArg2, TextureArgument.Diffuse);
Device.SetTextureStageState(0, TextureStage.AlphaOperation, TextureOperation.Modulate);

// Important
Device.SetRenderState(RenderState.AlphaBlendEnable, True);

// New alpha blending render states
Device.SetRenderState(RenderState.SourceBlend, Blend.One);
Device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha);

The next part is loading the texture data and calculating the new colour channel values – below is a C# snippet for reading a 32-bit bitmap and performing this calculation. We take a bitmap that has been loaded into memory, iterate over each pixel in the image, calculate our new R, G and B values and save them back into the bitmap. Using SetPixel and GetPixel would be far too slow so this example locks the bitmap data and accesses the colour values directly.

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct RgbaColor
{
    // Huh?! turns out PixelFormat.Format32bppArgb should
    // actually be called Format32bppBgra.
    public byte Blue;
    public byte Green;
    public byte Red;
    public byte Alpha;
}

public unsafe static class DangerousTextureLoader
{
    private static Texture LoadFromBitmap(Bitmap bitmap)
    {
        // Lock the entire bitmap for Read/Write access as we'll be reading the pixel
        // colour values and altering them in-place.
        var bmlock = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                                     ImageLockMode.ReadWrite, bitmap.PixelFormat);

        // This code only works with 32bit argb images
        Debug.Assert(bmlock.PixelFormat == PixelFormat.Format32bppArgb);

        var ptr = (byte*) bmlock.Scan0.ToPointer();

        for (var y = 0; y < bmlock.Height; y++)
        {
            for (var x = 0; x < bmlock.Width; x++)
            {
                // Obtain the memory location where our pixel data resides and cast it
                // into a struct to improve sanity.
                var color = (RgbaColor*)(ptr + (y * bmlock.Stride) + (x * sizeof(RgbaColor)));

                var alphaFloat = (*color).Alpha/255.0f;

                (*color).Red = Convert.ToByte(alphaFloat * (*color).Red);
                (*color).Green = Convert.ToByte(alphaFloat * (*color).Green);
                (*color).Blue = Convert.ToByte(alphaFloat * (*color).Blue);
            }
        }

        // SlimDX specific stuff here
        var newTexture = new Texture(_device, bitmap.Width, bitmap.Height, 0,
            Usage.None, GetD3DFormat(bitmap.PixelFormat), Pool.Managed);

        // SlimDX specific stuff
        var textureLock = newTexture.LockRectangle(0, LockFlags.None);
        textureLock.Data.WriteRange(bmlock.Scan0, bmlock.Height * bmlock.Stride);

        // The bitmap lock is freed here
        bitmap.UnlockBits(bmlock);

        // SlimDX specific stuff
        newTexture.UnlockRectangle(0);
        newTexture.FilterTexture(0, Filter.Default);

        return newTexture;
    }
}

You can read a better explanation and a link to a research paper at TomF’s tech blog. He also highlights how pre-multiplied alpha will help compress your textures using DXTn formats.

 

Leave a Reply

Your email address will not be published. Required fields are marked *