Skip to content

Instantly share code, notes, and snippets.

@tkouba
Created February 7, 2016 21:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tkouba/0b7f8496f1aadcfee2db to your computer and use it in GitHub Desktop.
Save tkouba/0b7f8496f1aadcfee2db to your computer and use it in GitHub Desktop.
Work with bitmap faster in C#, solution for positive and negative stride.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ImageHelper
{
public class LockBitmap : IDisposable
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public byte[] Pixels { get; set; }
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public int RowSize { get; private set; }
public bool IsDisposed { get; private set; }
public LockBitmap(Bitmap source)
{
this.source = source;
IsDisposed = false;
}
/// <summary>
/// Lock bitmap data
/// </summary>
public void LockBits()
{
try
{
if (IsDisposed)
throw new ObjectDisposedException(typeof(LockBitmap).Name);
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// Create rectangle to lock
Rectangle rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
// create byte array to copy pixel values
RowSize = bitmapData.Stride < 0 ? -bitmapData.Stride : bitmapData.Stride;
Pixels = new byte[Height * RowSize];
Iptr = bitmapData.Scan0;
// Copy data from pointer to array
// Not working for negative Stride see. http://stackoverflow.com/a/10360753/1498252
//Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
// Solution for positive and negative Stride:
for (int y = 0; y < Height; y++)
{
Marshal.Copy(IntPtr.Add(Iptr, y * bitmapData.Stride),
Pixels, y * RowSize,
RowSize);
}
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Unlock bitmap data
/// </summary>
public void UnlockBits()
{
try
{
if (IsDisposed)
throw new ObjectDisposedException(typeof(LockBitmap).Name);
if (bitmapData == null)
throw new InvalidOperationException("Image is not locked.");
// Copy data from byte array to pointer
//Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
for (int y = 0; y < Height; y++)
{
Marshal.Copy(Pixels, y * RowSize,
IntPtr.Add(Iptr, y * bitmapData.Stride),
RowSize);
}
// Unlock bitmap data
source.UnlockBits(bitmapData);
bitmapData = null;
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Get the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Color GetPixel(int x, int y)
{
if (IsDisposed)
throw new ObjectDisposedException(typeof(LockBitmap).Name);
Color clr = Color.Empty;
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = (y * RowSize) + (x * cCount);
if (i > Pixels.Length - cCount)
throw new IndexOutOfRangeException();
if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
byte a = Pixels[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (Depth == 24) // For 24 bpp get Red, Green and Blue
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (Depth == 8)
// For 8 bpp get color value (Red, Green and Blue values are the same)
{
byte c = Pixels[i];
clr = Color.FromArgb(c, c, c);
}
return clr;
}
/// <summary>
/// Set the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="color"></param>
public void SetPixel(int x, int y, Color color)
{
if (IsDisposed)
throw new ObjectDisposedException(typeof(LockBitmap).Name);
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = (y * RowSize) + (x * cCount);
if (i > Pixels.Length - cCount)
throw new IndexOutOfRangeException();
if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
Pixels[i + 3] = color.A;
}
if (Depth == 24) // For 24 bpp set Red, Green and Blue
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
}
if (Depth == 8)
// For 8 bpp set color value (Red, Green and Blue values are the same)
{
Pixels[i] = color.B;
}
}
#region IDisposable Members
~LockBitmap()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (bitmapData != null)
{
source.UnlockBits(bitmapData);
bitmapData = null;
}
source = null;
IsDisposed = true;
}
#endregion
}
}
@NickAustin1982
Copy link

Hello,

I've been using a slightly modified version of this for some time now and it has worked for every bitmap of every size and format I have tried, until now.

I have a 24 bpp Bitmap of size 2592x1944, and locking and unlocking this bitmap causes the image to go all weird. I have tried changing the width of the bitmap to other values (2436, 2588, 2589, 2590, 2591) and they all work well. Something seems to mess up when the width is 2592. Note that the image goes all weird even when I am not doing any image processing between locking and unlocking.

Anyway, for the 2592x1944 pixel bitmap, the Stride value is 7776 (which equals (24/8)*2592 without the need to round up to the nearest 4 as this is already divisable by 4, so there should be nothing too weird going on here).

Can I ask if you experience a similar problem with bitmaps with a depth of 24, and a width of 2592?

Thanks for any help you can give me.

Nick

@tkouba
Copy link
Author

tkouba commented Apr 25, 2017

Hello,

I've been using this code for small bitmaps. I haven't any experience with similar problem.

I'm sorry, I can't help you.

Tomas

@mzh99
Copy link

mzh99 commented Feb 15, 2020

Excellent version. I'm not sure you need to implement an IDisposable interface though as source (Bitmap) is not yours to worry about and is under the caller's ownership. If they call LockBitmap() methods after the source bitmap is disposed, that's on them. Either way, they'd get an exception.
I'd like to read the ideas regarding your approach as I'm always open to learning new ideas.

@tkouba
Copy link
Author

tkouba commented Feb 23, 2020

Dispose only unlock bits, not bitmap itself. You should lock bitmap only for short time, so IDisposable interface helps to remember that.
This code was from my old project, so I have some comments on it, but I have no time for better implementation and testing.

@jaydeep6720
Copy link

will it work with 1bpp Indexed file ??

@tkouba
Copy link
Author

tkouba commented Jun 10, 2021

Only 8, 24 and 32 bpp images are supported. See line 54

@jaydeep6720
Copy link

what if i want to make it worked for 1Bpp ?
too much needed

@tkouba
Copy link
Author

tkouba commented Jun 10, 2021

You must change condition on line 52 and add proper condition to GetPixel and PutPixel functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment