LockBits Alternative: SecurityException Workaround
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);
}
}
}