65.9K
CodeProject is changing. Read more.
Home

LockBits Alternative: SecurityException Workaround

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

Jul 24, 2013

CPOL
viewsIcon

7118

LockBits Alternative: SecurityException Workaround

Context

I was recently working on photo to stencil converter that does some heavy in-memory graphics rendering. I used Bitmap.LockBits and Bitmap.UnlockBits to transfer memory rendering buffer to and from bitmap.

The Problem

It all worked fine until I deployed to shared hosting. LockBits function has [SecurityPermissionAttribute(Flags = SecurityPermissionFlag.UnmanagedCode)] attribute on it, in hosting environment, I did not have enough permissions and I was getting SecurityException. Using Bitmap.GetPixel was not an option because of performance.

The Solution

Typical scenario for LockBits/UnlockBits is the following: copy bitmap into memory buffer, do your processing, then copy bytes back to bitmap. I use Save and Load functions to save/load to MemoryStream and then extracted bytes from the stream. Bitmap file format is very simple, all I have to do is analyze a couple of values from the header.

I have come up with ManagedBitmap class that can be used like this:

byte[] buffer = ManagedBitmap.GetBytes(bmp);
... do your stuff
Bitmap newBmp = ManagedBitmap.GetBitmap(buffer, PixelFormat.Format32bppArgb, bmp.Width);

This method is about 1/2 speed of LockBits/UnlockBits approach which is still pretty good because GetPixel/SetPixel method is about 200 times slower.

This is the code:

internal static class ManagedBitmap
{
    public static byte[] GetBytes(Bitmap bmp)
    {
        using (var ms = new MemoryStream())
        {
            bmp.Save(ms, ImageFormat.Bmp);

            int bpp = Image.GetPixelFormatSize(bmp.PixelFormat);
            if (bpp != 8 && bpp != 24 && bpp != 32) 
                 throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");

            var retval = new byte[bmp.Height * bmp.Width * bpp / 8];

            int lineSize = bpp / 8 * bmp.Width;
            byte[] from = ms.GetBuffer();

            for (int i = 0; i < bmp.Height; i++)
            {
                Buffer.BlockCopy(from, 54 + lineSize * (bmp.Height - i - 1),
                                    retval, lineSize * i,
                                    lineSize);
            }

            return retval;
        }
    }

    public static Bitmap GetBitmap(byte[] data, PixelFormat pixelFormat, int width)
    {
        var bpp = (short)Image.GetPixelFormatSize(pixelFormat);
        int height = data.Length / width * 8 / bpp;

        using (var ms = new MemoryStream())
        {
            using (var binary = new BinaryWriter(ms))
            {
                var infoHeader = new BITMAPINFOHEADER(bpp, width, height);
                var fileHeader = new BITMAPFILEHEADER(infoHeader);
                fileHeader.Store(binary);
                infoHeader.Store(binary);
                binary.Flush();

                ms.Write(data, 0, data.Length);

                ms.Seek(0, SeekOrigin.Begin);

                return (Bitmap)Image.FromStream(ms);
            }
        }
    }

    private class BITMAPFILEHEADER
    {
        private const ushort FBitmapFileDesignator = 19778;
        private const uint FBitmapFileOffsetToData = 54;
        private readonly uint bfOffBits; 		// Offset to get to pixel info

        private readonly short bfReserved1;		// Unused
        private readonly short bfReserved2;		// Unused
        private readonly uint bfSize; 			// File size in bytes
        private readonly ushort bfType; 		// File type designator "BM"
        private readonly uint sizeOfImageData; 	// Size of the pixel data

        public BITMAPFILEHEADER(BITMAPINFOHEADER infoHdr)
        {
            // These are constant for our example
            bfType = FBitmapFileDesignator;
            bfOffBits = FBitmapFileOffsetToData;
            bfReserved1 = 0;
            bfReserved2 = 0;

            // Determine the size of the pixel data given the bit depth, width, and
            // height of the bitmap.  Note: Bitmap pixel data is always aligned to 4 byte
            // boundaries.
            var bytesPerPixel = (uint)(infoHdr.biBitCount >> 3);
            uint extraBytes = ((uint)infoHdr.biWidth * bytesPerPixel) % 4;
            uint adjustedLineSize = bytesPerPixel * ((uint)infoHdr.biWidth + extraBytes);

            // Store the size of the pixel data
            sizeOfImageData = (uint)(infoHdr.biHeight) * adjustedLineSize;

            // Store the total file size
            bfSize = bfOffBits + sizeOfImageData;
        }

        public void Store(BinaryWriter bw)
        {
            bw.Write(bfType);
            bw.Write(bfSize);
            bw.Write(bfReserved1);
            bw.Write(bfReserved2);
            bw.Write(bfOffBits);
        }
    }

    private class BITMAPINFOHEADER
    {
        private const uint KBitmapInfoHeaderSize = 40;

        internal readonly short biBitCount; 	// Pixel bit depth
        private readonly uint biClrImportant; 	// Important colors
        private readonly uint biClrUsed; 		// Number of colors used
        private readonly uint biCompression; 	// Compression type
        internal readonly int biHeight; 		// Height of bitmap (pixels)
        private readonly short biPlanes; 		// Number of color planes
        private readonly uint biSize; 			// Size of this structure
        private readonly uint biSizeImage; 		// Size of uncompressed image
        internal readonly int biWidth; 			// Width of bitmap (pixels)
        private readonly int biXPelsPerMeter; 	// Horizontal pixels per meter
        private readonly int biYPelsPerMeter; 	// Vertical pixels per meter

        public BITMAPINFOHEADER(short bpp, int w, int h)
        {
            if (bpp != 16 && bpp != 24 && bpp != 32) 
               throw new Exception("Error: Non-supported bitmap pixel depth specified");

            biSize = KBitmapInfoHeaderSize;
            biWidth = w; 			// Set the width
            biHeight = h; 			// Set the height
            biPlanes = 1; 			// Only use 1 color plane
            biBitCount = bpp; 		// Set the bpp
            biCompression = 0; 		// No compression for file bitmaps
            biSizeImage = 0; 		// No compression so this can be 0
            biXPelsPerMeter = 0;	// Not used
            biYPelsPerMeter = 0; 	// Not used
            biClrUsed = 0; 			// Not used
            biClrImportant = 0; 	// Not used
        }

        public void Store(BinaryWriter bw)
        {
            // Must, obviously, maintain the proper order for file writing
            bw.Write(biSize);
            bw.Write(biWidth);
            bw.Write(biHeight);
            bw.Write(biPlanes);
            bw.Write(biBitCount);
            bw.Write(biCompression);

            bw.Write(biSizeImage);
            bw.Write(biXPelsPerMeter);
            bw.Write(biYPelsPerMeter);
            bw.Write(biClrUsed);
            bw.Write(biClrImportant);
        }
    }
}