Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

LockBits alternative: SecurityException workaround

, 24 Jul 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
LockBits function has [SecurityPermissionAttribute(Flags = SecurityPermissionFlag.UnmanagedCode)] attribute on it, in hosting environment I did not have enough permissions and I was getting SecurityException
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 sepcified");

            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);
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

m_kramar

Australia Australia
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141030.1 | Last Updated 24 Jul 2013
Article Copyright 2013 by m_kramar
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid