What is the purpose of ImageLockMode in Bitmap.LockBits?
For ReadOnly the documentation only states that
ReadOnly: Specifies that a portion of the image is locked for reading.
But the following code proves, that this is not true.
I know the question has been asked before, this time I try with some actual code as I couldn't find an answer anywhere else.
If I run the following code, it behaves exactly as explained in the answer.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace LockBits_Trials
{
class Program
{
static readonly Random rnd = new Random(42);
static void Main(string[] args)
{
Bitmap bmp_fromFile = new Bitmap("example.png");
Bitmap bmp_fromCtor = new Bitmap(100, 100, PixelFormat.Format24bppRgb);
marshalCopy(bmp_fromFile, "result_marshalCopy_fromFile.png");
marshalCopy(bmp_fromCtor, "result_marshalCopy_fromCtor.png");
usePointer(bmp_fromFile, "result_usePointer_fromFile.png");
usePointer(bmp_fromCtor, "result_usePointer_fromCtor.png");
}
private static unsafe void usePointer(Bitmap bmp, string filename)
{
ImageLockMode mode = ImageLockMode.ReadOnly;
//code from turgay at http://csharpexamples.com/fast-image-processing-c/
if (bmp.PixelFormat != PixelFormat.Format24bppRgb)
throw new Exception();
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), mode, bmp.PixelFormat);
int bytesPerPixel = 3; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel;
byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
for (int y = 0; y < heightInPixels; y++) {
byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) {
currentLine[x] = (byte)rnd.Next(0, 255);
currentLine[x + 1] = (byte)rnd.Next(0, 255);
currentLine[x + 2] = (byte)rnd.Next(0, 255);
}
}
bmp.UnlockBits(bitmapData);
bmp.Save(filename, ImageFormat.Png);
}
private static unsafe void marshalCopy(Bitmap bmp, string filename)
{
ImageLockMode mode = ImageLockMode.ReadOnly;
if (bmp.PixelFormat != PixelFormat.Format24bppRgb)
throw new Exception();
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), mode, bmp.PixelFormat);
IntPtr ptrFirstPixel = bitmapData.Scan0;
int totalBytes = bitmapData.Stride * bitmapData.Height;
byte[] newData = new byte[totalBytes];
for (int i = 0; i < totalBytes; i++)
newData[i] = (byte)rnd.Next(0, 255);
Marshal.Copy(newData, 0, ptrFirstPixel, newData.Length);
bmp.UnlockBits(bitmapData);
bmp.Save(filename, ImageFormat.Png);
}
}
}
The pictures result_marshalCopy_fromFile.png and result_usePointer_fromFile.png both contain the original image, so nothing was overwritten (and no Exception thrown!).
The other two pictures contain the random noise, that was written to them while being locked.
I did not do further test to confirm the behaviour of parallel write access, as I don't do that anyways.
It's not a duplicate, but strongly related:
Does Bitmap.LockBits “pin” a bitmap into memory?
As you've observed, once you have gained access to the raw data pointer, there's nothing to stop you from writing to that memory, regardless of the lock type you requested. The lock type is only really useful in 2 situations:
1) If multiple bits of code request locks simultaneously, only one write lock is allowed to be issued at a time, while read locks can be shared. This, of course, depends on the code acquiring the locks using them appropriately.
2) Not all locks are directly backed by the Bitmap memory. In your example, it is because you created a memory Bitmap and then requested a lock in the same pixel format. However a bitmap may represent other GDI+ objects, such as pixels owned by a device context. Additionally, if you request a pixel format other than that of the source, it may have to be converted. In either of those cases, when a read lock is requested, GDI+ may have to pull a copy of the bitmap from the true source and give it to you in your requested pixel format. If you were to modify that copy, it would not get written back to the source context. If you requested a write lock, GDI+ would know to copy the pixels back to the source once you release the lock.
Related
I am trying to teach myself C# and have heard from a variety of sources that the functions get and setpixel can be horribly slow. What are some of the alternatives and is the performance improvement really that significant?
A chunk of my code for reference:
public static Bitmap Paint(Bitmap _b, Color f)
{
Bitmap b = new Bitmap(_b);
for (int x = 0; x < b.Width; x++)
{
for (int y = 0; y < b.Height; y++)
{
Color c = b.GetPixel(x, y);
b.SetPixel(x, y, Color.FromArgb(c.A, f.R, f.G, f.B));
}
}
return b;
}
The immediately usable code
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
public Int32[] Bits { get; private set; }
public bool Disposed { get; private set; }
public int Height { get; private set; }
public int Width { get; private set; }
protected GCHandle BitsHandle { get; private set; }
public DirectBitmap(int width, int height)
{
Width = width;
Height = height;
Bits = new Int32[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
public void SetPixel(int x, int y, Color colour)
{
int index = x + (y * Width);
int col = colour.ToArgb();
Bits[index] = col;
}
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);
return result;
}
public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
}
There's no need for LockBits or SetPixel. Use the above class for direct access to bitmap data.
With this class, it is possible to set raw bitmap data as 32-bit data. Notice that it is PARGB, which is premultiplied alpha. See Alpha Compositing on Wikipedia for more information on how this works and examples on the MSDN article for BLENDFUNCTION to find out how to calculate the alpha properly.
If premultiplication might overcomplicate things, use PixelFormat.Format32bppArgb instead. A performance hit occurs when it's drawn, because it's internally being converted to PixelFormat.Format32bppPArgb. If the image doesn't have to change prior to being drawn, the work can be done before premultiplication, drawn to a PixelFormat.Format32bppArgb buffer, and further used from there.
Access to standard Bitmap members is exposed via the Bitmap property. Bitmap data is directly accessed using the Bits property.
Using byte instead of int for raw pixel data
Change both instances of Int32 to byte, and then change this line:
Bits = new Int32[width * height];
To this:
Bits = new byte[width * height * 4];
When bytes are used, the format is Alpha/Red/Green/Blue in that order. Each pixel takes 4 bytes of data, one for each channel. The GetPixel and SetPixel functions will need to be reworked accordingly or removed.
Benefits to using the above class
Memory allocation for merely manipulating the data is unnecessary; changes made to the raw data are immediately applied to the bitmap.
There are no additional objects to manage. This implements IDisposable just like Bitmap.
It does not require an unsafe block.
Considerations
Pinned memory cannot be moved. It's a required side effect in order for this kind of memory access to work. This reduces the efficiency of the garbage collector (MSDN Article). Do it only with bitmaps where performance is required, and be sure to Dispose them when you're done so the memory can be unpinned.
Access via the Graphics object
Because the Bitmap property is actually a .NET Bitmap object, it's straightforward to perform operations using the Graphics class.
var dbm = new DirectBitmap(200, 200);
using (var g = Graphics.FromImage(dbm.Bitmap))
{
g.DrawRectangle(Pens.Black, new Rectangle(50, 50, 100, 100));
}
Performance comparison
The question asks about performance, so here's a table that should show the relative performance between the three different methods proposed in the answers. This was done using a .NET Standard 2 based application and NUnit.
* Time to fill the entire bitmap with red pixels *
- Not including the time to create and dispose the bitmap
- Best out of 100 runs taken
- Lower is better
- Time is measured in Stopwatch ticks to emphasize magnitude rather than actual time elapsed
- Tests were performed on an Intel Core i7-4790 based workstation
Bitmap size
Method 4x4 16x16 64x64 256x256 1024x1024 4096x4096
DirectBitmap <1 2 28 668 8219 178639
LockBits 2 3 33 670 9612 197115
SetPixel 45 371 5920 97477 1563171 25811013
* Test details *
- LockBits test: Bitmap.LockBits is only called once and the benchmark
includes Bitmap.UnlockBits. It is expected that this
is the absolute best case, adding more lock/unlock calls
will increase the time required to complete the operation.
The reason bitmap operations are so slow in C# is due to locking and unlocking. Every operation will perform a lock on the required bits, manipulate the bits, and then unlock the bits.
You can vastly improve the speed by handling the operations yourself. See the following example.
using (var tile = new Bitmap(tilePart.Width, tilePart.Height))
{
try
{
BitmapData srcData = sourceImage.LockBits(tilePart, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData dstData = tile.LockBits(new Rectangle(0, 0, tile.Width, tile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
byte* dstPointer = (byte*)dstData.Scan0;
byte* srcPointer = (byte*)srcData.Scan0;
for (int i = 0; i < tilePart.Height; i++)
{
for (int j = 0; j < tilePart.Width; j++)
{
dstPointer[0] = srcPointer[0]; // Blue
dstPointer[1] = srcPointer[1]; // Green
dstPointer[2] = srcPointer[2]; // Red
dstPointer[3] = srcPointer[3]; // Alpha
srcPointer += BytesPerPixel;
dstPointer += BytesPerPixel;
}
srcPointer += srcStrideOffset + srcTileOffset;
dstPointer += dstStrideOffset;
}
}
tile.UnlockBits(dstData);
aSourceImage.UnlockBits(srcData);
tile.Save(path);
}
catch (InvalidOperationException e)
{
}
}
It's been some time, but I found an example that might be useful.
var btm = new Bitmap("image.png");
BitmapData btmDt = btm.LockBits(
new Rectangle(0, 0, btm.Width, btm.Height),
ImageLockMode.ReadWrite,
btm.PixelFormat
);
IntPtr pointer = btmDt.Scan0;
int size = Math.Abs(btmDt.Stride) * btm.Height;
byte[] pixels = new byte[size];
Marshal.Copy(pointer, pixels, 0, size);
for (int b = 0; b < pixels.Length; b++)
{
pixels[b] = 255; //Do something here
}
Marshal.Copy(pixels, 0, pointer, size);
btm.UnlockBits(btmDt);
You can use Bitmap.LockBits method. Also if you want to use parallel task execution, you can use the Parallel class in System.Threading.Tasks namespace. Following links have some samples and explanations.
http://csharpexamples.com/fast-image-processing-c/
http://msdn.microsoft.com/en-us/library/dd460713%28v=vs.110%29.aspx
http://msdn.microsoft.com/tr-tr/library/system.drawing.imaging.bitmapdata%28v=vs.110%29.aspx
This code should be parallelized, there is a massive performance gain being missed by running this synchronously. Almost no modern Microchip will have less than 4 threads available and some chips will have 40 threads available.
There is absolutely no reason to run that first loop synchronously. You can go through either the width or the length using many, many threads.
private void TakeApart_Fast(Bitmap processedBitmap)
{
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
ConcurrentBag<byte> points = new ConcurrentBag<byte>();
unsafe
{
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
Parallel.For(0, heightInPixels, y =>
{
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
// red
byte redPixel = currentLine[x + 2];
//save information with the concurrentbag
}
});
processedBitmap.UnlockBits(bitmapData);
}
}`
a benchmark wouldn't mean much because the answer to how much this will speed up the proccess depends 100% on what hardware you are using, and what else is running in the background, it all depends on how many free threads are available. If your running this on a 4000 series graphics card with thousands of streaming proccessors you may be able to do iterate through every column of the image at the same time.
if your running it with and old quad core you may only have 5 or 6 threads which is still incredibly significant.
I am receiving the following exception:
Exception thrown:
'System.AccessViolationException' in System.Drawing.dll
When calling the Save function of a Bitmap. The procedure works fine the first time around, but subsequent calls throw this exception.
My application takes a single long image and vertically tiles it out into several separate images. I do this by first breaking out the whole image into bytes, then in a Parallel.For loop I generate the bitmap from a subset byte array.
// Generate Bitmap from width, height and bytes
private Bitmap GenerateBitmap(int width, int height, byte[] bytes)
{
Bitmap bmp = new Bitmap(width, height, Stride(width),
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0));
bmp.SetPalette();
return bmp;
}
That is the bitmap generation routine.
Here is the loop body that calls it.
Parallel.For(0, tileCount, i =>
{
byte[] bytes = new byte[imageWidth * tileHeight];
for (int j = 0; j < bytes.Length; j++)
{
bytes[j] = imageBytes[j + (imageWidth * (tileHeight * i))];
}
arr[i] = GenerateBitmap(imageWidth, tileHeight, bytes);
});
And here is the code elsewhere that the exception is thrown.
foreach(Bitmap tile in pattern.Tiles)
{
Console.WriteLine("Creating Tile " + count);
using (Bitmap bmp = new Bitmap(tile))
{
bmp.Save(Globals.patternOutputPath + "tile_" + count + ".png");
}
count += 1;
}
Where Tiles is a property of the pattern that calls the for loop function (which returns a list of Bitmaps).
I am assuming I'm missing some clean up somewhere in here.
Additional info: all images (input and output) are 256 (index) color format.
Edit: The comments below address the problem at hand, and I THINK I've solved the problem. I changed the GenerateBitmap routine to the following and am no longer getting this exception, but I have some more testing to do.
private Bitmap GenerateBitmap(int width, int height, byte[] bytes)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(bytes, 0, bmpData.Scan0, bytes.Length);
bmp.UnlockBits(bmpData);
return bmp;
/*Bitmap bmp = new Bitmap(width, height, Stride(width),
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0));
bmp.SetPalette();
return bmp;*/
}
I am trying to increase my image detection class using lockbits, yet this cause problems with the code and thus it does not run. How can i go about using lockbits and getpixel at the same time in order to speed up image detection, or can someone show me an alternative which is just as fast?
code:
static IntPtr Iptr = IntPtr.Zero;
static BitmapData bitmapData = null;
static public byte[] Pixels { get; set; }
static public int Depth { get; private set; }
static public int Width { get; private set; }
static public int Height { get; private set; }
static public void LockBits(Bitmap source)
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * 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);
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
// create byte array to copy pixel values
int step = Depth / 8;
Pixels = new byte[PixelCount * step];
Iptr = bitmapData.Scan0;
// Copy data from pointer to array
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
}
static public bool SimilarColors(int R1, int G1, int B1, int R2, int G2, int B2, int Tolerance)
{
bool returnValue = true;
if (Math.Abs(R1 - R2) > Tolerance || Math.Abs(G1 - G2) > Tolerance || Math.Abs(B1 - B2) > Tolerance)
{
returnValue = false;
}
return returnValue;
}
public bool findImage(Bitmap small, Bitmap large, out Point location)
{
unsafe
{
LockBits(small);
LockBits(large);
//Loop through large images width
for (int largeX = 0; largeX < large.Width; largeX++)
{
//And height
for (int largeY = 0; largeY < large.Height; largeY++)
{
//Loop through the small width
for (int smallX = 0; smallX < small.Width; smallX++)
{
//And height
for (int smallY = 0; smallY < small.Height; smallY++)
{
//Get current pixels for both image
Color currentSmall = small.GetPixel(smallX, smallY);
Color currentLarge = large.GetPixel(largeX + smallX, largeY + smallY);
//If they dont match (i.e. the image is not there)
if (!colorsMatch(currentSmall, currentLarge))
//Goto the next pixel in the large image
goto nextLoop;
}
}
//If all the pixels match up, then return true and change Point location to the top left co-ordinates where it was found
location = new Point(largeX, largeY);
return true;
//Go to next pixel on large image
nextLoop:
continue;
}
}
//Return false if image is not found, and set an empty point
location = Point.Empty;
return false;
}
}
You wouldn't want to rely on getPixel() for image processing; it's okay to make an occasional call to get a point value (e.g. on mouseover), but in general it's preferable to do image processing in image memory or in some 2D array that you can convert to a Bitmap when necessary.
To start, you might try writing a method that using LockBits/UnlockBits to extract an array that is convenient to manipulate. Once you're done manipulating the array, you can write it back to a bitmap using a different LockBits/UnlockBits function.
Here's some sample code I've used in the past. The first function returns a 1D array of values from a Bitmap. Since you know the bitmap's width, you can convert this 1D array to a 2D array for further processing. Once you're done processing, you can call the second function to convert the (modified) 1D array into a bitmap again.
public static byte[] Array1DFromBitmap(Bitmap bmp){
if (bmp == null) throw new NullReferenceException("Bitmap is null");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr ptr = data.Scan0;
//declare an array to hold the bytes of the bitmap
int numBytes = data.Stride * bmp.Height;
byte[] bytes = new byte[numBytes];
//copy the RGB values into the array
System.Runtime.InteropServices.Marshal.Copy(ptr, bytes, 0, numBytes);
bmp.UnlockBits(data);
return bytes;
}
public static Bitmap BitmapFromArray1D(byte[] bytes, int width, int height)
{
Bitmap grayBmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
Rectangle grayRect = new Rectangle(0, 0, grayBmp.Width, grayBmp.Height);
BitmapData grayData = grayBmp.LockBits(grayRect, ImageLockMode.ReadWrite, grayBmp.PixelFormat);
IntPtr grayPtr = grayData.Scan0;
int grayBytes = grayData.Stride * grayBmp.Height;
ColorPalette pal = grayBmp.Palette;
for (int g = 0; g < 256; g++){
pal.Entries[g] = Color.FromArgb(g, g, g);
}
grayBmp.Palette = pal;
System.Runtime.InteropServices.Marshal.Copy(bytes, 0, grayPtr, grayBytes);
grayBmp.UnlockBits(grayData);
return grayBmp;
}
These methods makes assumptions about the Bitmap pixel format that may not work for you, but I hope the general idea is clear: use LockBits/UnlockBits to extract an array of bytes from a Bitmap so that you can write and debug algorithms most easily, and then use LockBits/UnlockBits again to write the array to a Bitmap again.
For portability, I would recommend that your methods return the desired data types rather than manipulating global variables within the methods themselves.
If you've been using getPixel(), then converting to/from arrays as described above could speed up your code considerably for a small investment of coding effort.
Ok where to start. Better you understand what you are doing with lockBits.
First of all make sure, that you dont overwrite your byte array with.
LockBits(small);
LockBits(large);
due to the second call all the first call does is locking your image and that is not good since you doesn't unlock it again.
So add another byte array that represents the image.
You can do something like this
LockBits(small, true);
LockBits(large, false);
and change your Lockbits method
static public void LockBits(Bitmap source, bool flag)
{
...
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
if(flag)
PixelsSmall=Pixels;
else
PixelsLarge=Pixels;
}
where PixelsLarge and PixelsSmall are globals and Pixels isn't
Those 2 contain your image. Now you have to compare it.
Now you have to compare each "set of bytes" therefore you have to know the Pixelformat.
Is it 32b/pix 24 or only 8 (ARGB,RGB,grayscale)
Let's take ARGB images. In this case a set would consist of 4 bytes (=32/8)
I am not sure about the order but I think the order of one set is ABGR or BGRA.
Hope this may help you. If you don't figure out how to compare the right pixels then ask again. Ah and dont forget to use the UnlockBits command.
I'm trying to refactor this unsafe code to copy a single ARGB channel from one image to another using System.Runtime.InteropServices.Marshal.Copy as per this example on MSDN but I'm totally lost.
Could anyone walk me through how I would go about it?
public enum ChannelARGB
{
Blue = 0,
Green = 1,
Red = 2,
Alpha = 3
}
public static void transferOneARGBChannelFromOneBitmapToAnother(
Bitmap source,
Bitmap dest,
ChannelARGB sourceChannel,
ChannelARGB destChannel )
{
if ( source.Size!=dest.Size )
throw new ArgumentException();
Rectangle r = new Rectangle( Point.Empty, source.Size );
BitmapData bdSrc = source.LockBits( r,
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb );
BitmapData bdDst = dest.LockBits( r,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb );
unsafe
{
byte* bpSrc = (byte*)bdSrc.Scan0.ToPointer();
byte* bpDst = (byte*)bdDst.Scan0.ToPointer();
bpSrc += (int)sourceChannel;
bpDst += (int)destChannel;
for ( int i = r.Height * r.Width; i > 0; i-- )
{
*bpDst = *bpSrc;
bpSrc += 4;
bpDst += 4;
}
}
source.UnlockBits( bdSrc );
dest.UnlockBits( bdDst );
}
Edit
In an attempt to work through #Ben Voigt walk though I have come up with this so far. Unfortunately I am now getting the following error:
Attempted to read or write protected memory. This is often an
indication that other memory is corrupt.
private static void TransferOneArgbChannelFromOneBitmapToAnother(
Bitmap source,
Bitmap destination,
ChannelARGB sourceChannel,
ChannelARGB destinationChannel)
{
if (source.Size != destination.Size)
{
throw new ArgumentException();
}
Rectangle rectangle = new Rectangle(Point.Empty, source.Size);
// Lockbits the source.
BitmapData bitmapDataSource = source.LockBits(rectangle,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
// Declare an array to hold the bytes of the bitmap.
int bytes = bitmapDataSource.Stride * bitmapDataSource.Height;
// Allocate a buffer for the source image
byte[] sourceRgbValues = new byte[bytes];
// Get the address of the first line.
IntPtr ptrSource = bitmapDataSource.Scan0;
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptrSource,
sourceRgbValues,
0,
bytes);
// Unlockbits the source.
source.UnlockBits(bitmapDataSource);
// Lockbits the destination.
BitmapData bitmapDataDestination = destination.LockBits(rectangle,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
// Allocate a buffer for image
byte[] destinationRgbValues = new byte[bytes];
IntPtr ptrDestination = bitmapDataDestination.Scan0;
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptrDestination,
destinationRgbValues,
0,
bytes);
ptrSource += (int)sourceChannel;
ptrDestination += (int)destinationChannel;
for (int i = rectangle.Height * rectangle.Width; i > 0; i--)
{
destinationRgbValues[i] = sourceRgbValues[i];
ptrSource += 4;
ptrDestination += 4;
}
// Copy the RGB values back to the bitmap
// ******This is where I am getting the exception*******.
System.Runtime.InteropServices.Marshal.Copy(destinationRgbValues,
0,
ptrDestination,
bytes);
// Unlock bits the destination.
destination.UnlockBits(bitmapDataDestination);
}
Can anyone see what I have done wrong? This is all a bit over my head to be honest. I think I should buy some books.
LockBits the source.
Marshal.Copy the source BitmapData to a byte[] buffer.
UnlockBits the source.
LockBits the destination.
Marshal.Copy the destination BitmapData to a byte[] buffer.
Loop through and copy that channel from the source byte[] to the destination byte[] (note, use arithmetic on indexes instead of on pointers)
Marshal.Copy the destination byte[] back to the BitmapData.
UnlockBits the destination.
I'm not sure what the point is, though. Code that uses Marshal.Copy is just as dangerous as code that uses the unsafe keyword, and should require similar code security permission.
A potentially more efficient way would be to use ImageAttributes.SetColorMatrix to remove the desired channel from the destination image, remove all other channels from the source image, and then blend. See the example for ColorMatrix
Or use DirectX (or OpenGL) and a shader that just transfers the one channel.
You could use my simple LINQ based image processing framework from Nuget or Codeplex and write a simple query that swaps the channels around.
You could also use a ColorMatrix to perform the channel swap like in this code.
Unfortunately, a ColorMatrix won't work if you want to combine channels from two separate images. You would need an additive (or bitwise or) blending method, and the only blending provided by GDI+ is Over and Copy. It also looks to me like any methods that would allow you to access the bits directly, including LockBits, are locked down.
I think the only option is to use GetPixel and SetPixel on each pixel, something like this:
Color dstColor = bpDst.GetPixel(x, y);
Color srcColor = bpSrc.GetPixel(x, y);
int srcValue = (srcColor.ToArgb() >> (sourceChannel * 8)) & 0xff;
int dstArgb = (dstColor.ToArgb() & ~(0xff << (destChannel * 8))) | (srcValue << (destChannel * 8));
bpDst.SetPixel(x, y, Color.FromArgb(dstArgb));
Could some rewrite the following function to use any optimized mechanism? I'm pretty sure that this is not the way to proceed, copying pixel by pixel.
I have read about AlphaBlend, or BitBlt, but I'm not used to native code.
public static Bitmap GetAlphaBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
try
{
for (int y = 0; y <= srcData.Height - 1; y++)
{
for (int x = 0; x <= srcData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));
result.SetPixel(x, y, pixelColor);
}
}
}
finally
{
srcBitmap.UnlockBits(srcData);
}
return result;
}
IMPORTANT NOTE: The source image has a wrong pixel format (Format32bppRgb), so I need to adjust the alpha channel. This is the only mechanism that works for me.
The reason why the src image has a wrong pixel format is explained here.
I tried the following options without luck:
Creating a new image and draw the src image using the Graphics.DrawImage from src. Did not preserve the alpha.
Creating a new image using the Scan0 form src. Works fine, but has a problem when the GC dispose the src image (explained in this other post);
This solution is the only that really works, but I know that is not optimal. I need to know how to do it using the WinAPI or other optimal mechanism.
Thank you very much!
Assuming the source image does infact have 32 bits per pixel, this should be a fast enough implementation using unsafe code and pointers. The same can be achieved using marshalling, though at a performance loss of around 10%-20% if I remember correctly.
Using native methods will most likely be faster but this should already be orders of magnitude faster than SetPixel.
public unsafe static Bitmap Clone32BPPBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
int* srcScan0 = (int*)srcData.Scan0;
int* resScan0 = (int*)resData.Scan0;
int numPixels = srcData.Stride / 4 * srcData.Height;
try
{
for (int p = 0; p < numPixels; p++)
{
resScan0[p] = srcScan0[p];
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Here is the safe version of this method using marshalling:
public static Bitmap Copy32BPPBitmapSafe(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
Int64 srcScan0 = srcData.Scan0.ToInt64();
Int64 resScan0 = resData.Scan0.ToInt64();
int srcStride = srcData.Stride;
int resStride = resData.Stride;
int rowLength = Math.Abs(srcData.Stride);
try
{
byte[] buffer = new byte[rowLength];
for (int y = 0; y < srcData.Height; y++)
{
Marshal.Copy(new IntPtr(srcScan0 + y * srcStride), buffer, 0, rowLength);
Marshal.Copy(buffer, 0, new IntPtr(resScan0 + y * resStride), rowLength);
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Edit: Your source image has a negative stride, which means the scanlines are stored upside-down in memory (only on the y axis, rows still go from left to right). This effectively means that .Scan0 returns the first pixel of the last row of the bitmap.
As such I modified the code to copy one row at a time.
notice: I've only modified the safe code. The unsafe code still assumes positive strides for both images!
Try the Bitmap Clone method.
A utility class in my Codeblocks library http://codeblocks.codeplex.com allows you to transform a source image to any other image using LINQ.
See this sample here: http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home
While the sample transforms the same image format between source and destination, you could change things around, as well.
Note that I have clocked this code and it is much faster than even unsafe code for large images because it uses cached full-row read ahead.