Asked a few questions about a project I was working on, got some good feedback and made some progress. The idea is to create an application that generates images of fractals, accelerated by CUDA. I am creating the ui in C# and having a DLL do the heavy lifting.
Basically, I am allocating a byte array in C#, passing that to the dll to fill with pixel data, and then using that to create a Bitmap and display that with a Windows Forms PictureBox in the ui. Previous questions have helped - was using dll to allocate memory before, now using consistent calling convention between dll and c#, but the code still gives an System.ArgumentException at "img = new Bitmap(...)
Relevant Code:
C++
extern "C" __declspec(dllexport) void __cdecl generateBitmap(void *bitmap)
{
int width = 1920;
int height = 1080;
int *dev_bmp;
gpuErrchk(cudaMalloc((void**)&dev_bmp, (3*width*height*sizeof(int))));
kernel<<<BLOCKS_PER_GRID, THREADS_PER_BLOCK>>>(dev_bmp, width, height);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaDeviceSynchronize());
gpuErrchk(cudaMemcpy(bitmap, dev_bmp, (width*height*3), cudaMemcpyDeviceToHost));
cudaFree(dev_bmp);
}
c#
public unsafe class NativeMethods
{
[DllImport(#"C:\Users\Bill\Documents\Visual Studio 2012\Projects\FractalMaxUnmanaged\Debug\FractalMaxUnmanaged.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern void generateBitmap(void *bitmap);
public static Bitmap create()
{
byte[] buf = new byte[1920 * 1080 * 3];
fixed (void* pBuffer = buf)
{
generateBitmap(pBuffer);
}
IntPtr unmanagedPtr = Marshal.AllocHGlobal(buf.Length);
Marshal.Copy(buf, 0, unmanagedPtr, buf.Length);
Bitmap img = new Bitmap(1920, 1080, 3, PixelFormat.Format24bppRgb, unmanagedPtr);
Marshal.FreeHGlobal(unmanagedPtr);
return img;
}
}
//...
private unsafe void mandlebrotButton_Click(object sender, EventArgs e)
{
FractalBox1.Image = (Image)NativeMethods.create();
}
What am I still doing wrong? As far as I can tell, all the parameters are invalid, but I get an invalid parameter exception in System.Drawing when I try to create the bitmap.
I am not sure what happens exactly in your case cause you didn't specify which parameter is invalid in the exception. I see that your stride must not be correct.
stride Type: System.Int32
Integer that specifies the byte offset between the beginning of one
scan line and the next. This is usually (but not necessarily) the
number of bytes in the pixel format (for example, 2 for 16 bits per
pixel) multiplied by the width of the bitmap. The value passed to this
parameter must be a multiple of four..
So your constructor should be like this:
Bitmap img = new Bitmap(1920, 1080, 1920 * 3, PixelFormat.Format24bppRgb, unmanagedPtr);
Related
The length of an array I pass as ref from C# to a C++ library function returns with length of 1 instead of its actually length when run on Android.
The code works fine when written for windows, but not for Android.
FYI, this is a Unity project and I'm using OpenCV.
I have the following function in the library.
extern "C" void ApplyCannyEffect(Color32 **rawImage, int width, int height)
{
Mat image(height, width, CV_8UC4, *rawImage);
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
flip(image, image, 0);
}
Color32 is defined as
struct Color32
{
uchar red;
uchar green;
uchar blue;
uchar alpha;
};
In my C# code I declare this function by:
[DllImport("test")]
internal static extern void ApplyCannyEffect(ref Color32[] rawImage, int width, int height);
In C# I get the image data from the texture. Unity's GetPixels32 function returns an array of structs for each pixel starting with the pixel in the bottom left (Unity's convention, not OpenCV's). The structs are four bytes, one each for red, green, blue, and alpha values of each pixel. I then check the length of the array, and it is the expected size (width in pixels * height in pixels). I call the C++ function. I then check the length of the array and it is 1. Needless to say, I am not working with an image that is 1 pixel in size.
Color32[] rawImage = texture.GetPixels32();
Debug.Log(rawImage.Length.ToString()); //output is as expected on both Windows and Android
ApplyCannyEffect(ref rawImage, texture.width, texture.height);
Debug.Log(rawImage.Length.ToString()); //output is same as above on Windows but "1" on Android
It works on Windows as expected, but not on Android. And the relevant code is unchanged between my Windows code and my Android code.
What causes this?
I am not too familiar with C++. But I did not expect to need to in C++ have Color32 parameter passed as a pointer to a pointer (I got this example code from a tutorial). Is this because I am passing an array of structures? By using two reference operators on an array of structs does it end up as a pointer to the first uchar of the first struct element of the array? Is this the reason it becomes length 1 after the function call?
To test this I tried converting the array of Color32 Structs to a byte array that was 4 times as long as the Color32 array in C# (the r, g, b, and a data sequentially filling the byte array)
private byte[] Color32toByteArray(Color32[] rawImage)
{
int outputIndex = 0;
byte[] output = new byte[rawImage.Length * 4];
for (int c = 0; c < rawImage.Length; c++)
{
output[outputIndex] = rawImage[c].r;
outputIndex++;
output[outputIndex] = rawImage[c].g;
outputIndex++;
output[outputIndex] = rawImage[c].b;
outputIndex++;
output[outputIndex] = rawImage[c].a;
outputIndex++;
}
Debug.Log("rawImage byte array length " + output.Length.ToString());
return output;
}
then passing the output to my Windows C++ dll's function that I modified to:
extern "C" __declspec(dllexport) void ApplyCannyEffect_Byte(unsigned char* rawImage, int width, int height)
{
using namespace cv;
// create an opencv object sharing the same data space
Mat image(height, width, CV_8UC4, rawImage);
// start with flip (in both directions) if your image looks inverted
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
// flip again (just vertically) to get the right orientation
flip(image, image, 0);
}
By the following code
Color32[] rawImage = texture.GetPixels32();
int length = rawImage.Length;
byte[] rawByteImage = Color32toByteArray(rawImage);
ApplyCannyEffect_Byte(ref rawByteImage, texture.width, texture.height);
rawImage = BytetoColor32Array(rawByteImage, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
Debug.Log((rawByteImage.Length / 4).ToString() + " " + rawImage.Length.ToString());
but this also (on Windows) just crashes the program at the ApplyCannyEffect_Byte call (commenting that line out works as expected, the output for the lengths of the arrays is correct).
Why does the first set of code work on Windows, but not Android?
This may be a packing issue. Consider using Unity's Color32 struct, which is perfectly aligned for use in native code.
Also you can't pass managed array as ref (because ref may also add internal info, such as array length before actual data, which become overwritten by DLL code), for this call you should use
Color32[] rawImage = texture.GetPixels32();
var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(rawImage, 0);
ApplyCannyEffect_Byte(ptr, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
...
// Modified function import:
[DllImport("test")]
internal static extern void ApplyCannyEffect(IntPtr rawImage, int width, int height);
NOTE: there is no conversion, cause it's not actually needed and drops performance a lot, also creates useless GC allocations. With native function you can write directly to Color32 array.
NOTE2: For fully GC-free implementation consider using NativeArray Texture2D.GetPixelData() method, you can get IntPtr from NativeArray, and dispose it after SetPixels32 or preallocate Color32[] array and pin it with GCHandle (Not really convenient solution).
No need to change DLL function signature.
I am new to this kind of stuff. I'm trying to create a function which is equivalent to the histogram function of an image. I am using windows forms application to show the histogram (and load the image) and CUDA/c++ to make the histogram. I am mentioning from the beginning that I am NOT using openCV, glut, OpenGL or any other third library. Carrying on... I am trying to pass a bitmap to an unmanaged c++ DLL. The problem here is that I don't now how to reference that bitmap in the c++ code. (And even how to get the RGB out of it).
Snippets of code:
c# :
private void calculateHistogram(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)pictureBox1.Image;
unsafe {
int** matrixAcumulated;
var date = bmp.LockBits(new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
matrixAcumulated=NativeMethods.GenerateHistogram(date.Scan0, pictureBox1.Width);
bmp.UnlockBits(date);
// Write the string to a file.
System.Console.WriteLine(matrixAcumulated[0][0]);
}
}
Dll import :
using System;
using System.Runtime.InteropServices;
namespace HistogramProcessingCs
{
class NativeMethods
{
[DllImport("HistogramProcessingCpp.dll", CallingConvention = CallingConvention.StdCall)]
public static extern unsafe int** GenerateHistogram(IntPtr bmp, int dimensionImage);
}
}
c++ :
extern "C" __declspec(dllexport) int** __stdcall GenerateHistogram(unsigned char *bmp, int dimensionImage)
{
//How to refere the bitmap from the bmp pointer?
//dimensionImage is Width = Height
}
Okay I worked out something after 2 hours of googling, stackoverflowing and microsoft documentationing!
Because you're using CUDA I think you only want fast and nice solutions, that's why I tried to find a way with which you can modify data without copying it many times only because of C# and C++ connection.
That's what I've done so far.
Some things are important.
I used an integer pointer, that's a bit messy with the current code.
You can of course use instead a char pointer which makes much more sense (haven't tested this).
Another thing is the System.Drawing.Imaging.PixelFormat.
It's really important to know which you have choosen. In my example I've choosen PixelFormat.Format32bppRgb.
The byte order is (so far I learned) just how the name is.
Example:
32bppRgb stands for red, green and blue which consume each 8 bits.
However because it's this format and not 24bppRgb it consumes a whole integer(8 bits aren't used). In this case the first 8 bits arent used (left to right thinking) so to set a pixel to red it works like this. (Sorry formating didn't worked as expected...)
| 8 | 8 | 8 | 8 | consuming bits
|empty | red | green | blue | color
| 00 | FF | 00 | 00 | color code for red
So the code for red is this
=> 0x00 FF 00 00
and as decimal it's 16711680. That's where the number in the C++ comes from.
C++ Code:
Header file "NativeLibrary.h":
namespace NativeLibrary
{
extern "C" __declspec(dllexport) void __stdcall PassBitmap(int* number, int size);
}
Cpp file "NativeLibrary.cpp":
#include <NativeLibrary.h>
void NativeLibrary::PassBitmap(int* number, int size) {
for (int i = 0; i < size; i++) {
number[i] = 16711680;
}
}
C# Code:
using System.Drawing;
using System.Runtime.InteropServices;
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void PassBitmap(IntPtr bmp, int size);
public System.Drawing.Bitmap bitmap = null;
public void GenerateAndModifyBitmap()
{
//The pixel format is essential!
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(100, 100, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
//Just which region we want to lock of the bitmap
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(new System.Drawing.Point(), bmp.Size);
//We want to read and write to the data, pixel format stays the same (anything else wouldn't make much sense)
System.Drawing.Imaging.BitmapData data = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
//This is a pointer to the data in memory. Can be manipulated directly!
IntPtr ptr = data.Scan0;
// This code is specific to a bitmap with 32 bits per pixels.
// Ignore current calculations. They are still work in progress xD
int size = bmp.Height;
size *= Math.Abs(data.Stride);
size /= 4;
//Call native function with our pointer to the data and of course how many ints we have
PassBitmap(ptr, size);
//Work is finished. Give our data back to the manager
bmp.UnlockBits(data);
bitmap = bmp;
}
This code would generate a bitmap which is completely red.
I'm working on a simple imaging software for my University, and I ran into annoying problem while getting image from camera.
There is an .dll COM library for camera Apogee Alta U57 (Library and documentation is here: http://www.ccd.com/downloads.html) and there are two possible ways of getting image from camera (provided that image is ready):
using "ICamera2 camera.image", which returns
"Returns a 2D SAFEARRAY, of type LONG (4 bytes per
element) or INTEGER (2 bytes per element), which contains
the image data. The type of data (LONG or INTEGER)
returned is controlled by the associated property of
ConvertShortToLong."
using "ICamera2.GetImage(int pImageBuffer)" which is described as:
Returns a pointer to 16 bit, unsigned short data located
in memory. The image data region should be allocated by the
application prior to calling this method.
And I'm pretty well confused while using second method, because int != int* ,
and I really don't know how to pass POINTER TO 16 BIT USHORT.
My simplified method of getting image looks like this:
public unsafe uint[] getImage(int width, int height)
{
// Allocating array of image size (width * height)
// where pixel is size of unsigned int (4 BYTES)
// possible values: 0 to 4,294,967,295
uint[] pixels = new uint[width * height];
// Gets pointer to allocated array and fixes it,
// so that it won't be moved by Garbage Collector
fixed (uint* ptr = pixels)
{
camera.GetImage(ptr);
}
return pixels;
}
Anyone can explain? I'm really tired (been coding for past 10 hours) and maybe I'm missing something :(
Ok, looks like your GetImage function expects you to allocate the memory for image pixels and pass in a pointer to that allocated memory. You're also responsible for freeing up that memory when you're done with it.
I think the only change you need to do is casting your pointer to a long - the GetImage function takes a long, not a pointer (on 32-bit platforms, the pointer would be 32 bits; on 64-bit platforms, it'd be 64 bits).
// Gets pointer to allocated array and fixes it,
// so that it won't be moved by Garbage Collector
fixed (uint* ptr = pixels)
{
long ptrValue = (long) ptr;
camera.GetImage(ptrValue);
}
This cast is not terribly nice but it's safe since the number of bits is adequate for both platforms. (I suspect you already have to target either 32-bit or 64-bit platforms anyway.)
The documentation say it want an Uint16. I though you should be doing something like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("Apogee.dll.dll")]
extern static int CreateInstance(out IntPtr ICamera2Ptr);
[DllImport("Apogee.dll.dll")]
extern static void GetImage(ref IntPtr pImageBuffer);
static void Main(string[] args)
{
IntPtr ICamera2Ptr;
int results = CreateInstance(out ICamera2Ptr);
}
static UInt16[] getImage(int width, int height)
{
// Allocating array of image size (width * height)
// where pixel is size of unsigned int (4 BYTES)
// possible values: 0 to 4,294,967,295
// Allocate memory and calculate a byte count
//unsigned short *pBuffer = new unsigned short[ ImgXSize * ImgYSize ];
//unsigned long ImgSizeBytes = ImgXSize * ImgYSize * 2;
UInt16[] pixels = new UInt16[width * height];
IntPtr unmanaged_pPixels = Marshal.AllocHGlobal(Marshal.SizeOf(pixels));
//// External operations ApogeeCamera->ExternalShutter = true;
//ApogeeCamera->ExternalIoReadout = false;
//ApogeeCamera->IoPortAssignment = 0x08;
// Even though the exposure time will not be used, still call Expose
//ApogeeCamera->Expose( 0.001, true );
// Check camera status to make sure image data is ready
//while (ApogeeCamera->ImagingStatus != Apn_Status_ImageReady );
// Get the image data from the camera ApogeeCamera->GetImage( (long)pBuffer );
GetImage(ref unmanaged_pPixels);
pixels = (UInt16[])Marshal.PtrToStructure(unmanaged_pPixels, typeof(UInt16[]));
return pixels;
}
}
}
I’m having a problem using StretchBlt (or BitBlt) to copy an image from a PictureBox to a Bitmap for use in creating an AVI file.
The AVI file is an unimportant aspect I believe - I can just display the Bitmap on another PictureBox and see the problem.
The problem is that occasionally (not often) a single image is flipped (mirrored across the X axis, never the Y axis).
I’m not sure if this is a known problem with StretchBlt I haven’t yet found mention of or if I am doing something wrong.
Note this is NOT due to the intended functionality with StretchBlt of "If the signs of source and destination height or width are different then it creates a mirror image".
UPDATE: I changed things to force the source/destination to be the same size, and am using BitBlt with the same behavior.
I’ve included some code (c#), hopefully all of the important parts.
Stepping through the code I can see this happen for a single image that has exactly the same information being passed to StretchBlt (other than the hdc to copy to) as the previous image and the next image (and next, next) all of which are fine.
It doesn't happen often, and I dont see any reason when it does. Or a way to detect it happened (so I can flip it back).
I have a work around that doesn't use StretchBlt, but it is much slower and really degrades performance.
Another possibly useful bit: this flipped image is rare in normal usage (less than 1 in 100 frames). But when run in the IDE, stepping through image by image, it happens very regularly (maybe 1 in 10).
Any ideas what could be causing this or what I could be doing wrong? Or other FAST methods to copy the Bitmap including re-sizing.
NOTE: The bitmaps do vary in size (can't use BitBlt), but not by a lot.
Thank you!
Kate
// --- Import statement
[DllImport("GDI32.DLL", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern bool StretchBlt(
IntPtr hdcDest, int nXDest, int nYDest, int nDestWidth, int nDestHeight,
IntPtr hdcSrc, int nXSrc, int nYSrc, int nSrcWidth, int nSrcHeight, Int32 dwRop );
// --- A small class for creating/storing Bitmap and Graphics objects, which get reused
public class CAviImageInfo
{
private Bitmap mAviBitmap = null;
private Graphics mAviGraphics = null;
public CAviImageInfo(int width, int height )
{
mAviBitmap = new Bitmap(width, height);
mAviGraphics = Graphics.FromImage(mAviBitmap);
}
~CAviImageInfo()
{
mAviGraphics.Dispose();
mAviGraphics = null;
}
public Bitmap AviBitmap
{ get { return mAviBitmap; } }
public Graphics AviGraphics
{ get { return mAviGraphics; } }
}
// --- Two PictureBoxs placed on form at design time (one is just to watch for these mirrored images):
PictureBox mMainPictureBox; // --- Displays the images to be copied
PictureBox DebugPictureBox;
// --- The following done one time:
Graphics mMainPictureBoxGraphics = mMainPictureBox.CreateGraphics();
IntPtr mMainPictureBoxHdc = mMainPictureBoxGraphics.GetHdc();
// --- Method that does the copying. Called each time image on panel is updated.
Public void UpdateAviRecording()
{
// --- Gets unused Bitmap and Graphics objects (these are reused)
CAviImageInfo aviImageInfo = GetUnusedAviImageInfo();
IntPtr destinationHdc = aviImageInfo.AviGraphics.GetHdc();
StretchBlt(
destinationHdc,
0, 0, aviImageInfo.AviBitmap.Width, aviImageInfo.AviBitmap.Height,
mMainPictureBoxHdc,
0, 0, mMainPictureBox.Width, mMainPictureBox.Height, SRCCOPY);
// --- Show the copied Bitmap on the debug PictureBox
// --- (normally would pass it to be written to avi file)
DebugPictureBox.Image = aviImageInfo.AviBitmap;
DebugPictureBox.Refresh();
aviImageInfo.AviGraphics.ReleaseHdc(destinationHdc);
}
this is just a suggestion. I don't know if it will work..
Set the width and height using Math.Abs() of the image in the call to StretchBlt.
That might prevent possible memory corruption and inversion of signs (as GSerg mentioned).
..
StretchBlt(
destinationHdc,
0, 0, Math.Abs(aviImageInfo.AviBitmap.Width),
Math.Abs(aviImageInfo.AviBitmap.Height),
mMainPictureBoxHdc,
0, 0, Math.Abs(mMainPictureBox.Width),
Math.Abs(mMainPictureBox.Height), SRCCOPY);
Is this the absolute fastest I could possibly copy a Bitmap to a Byte[] in C#?
If there is a speedier way I am dying to know!
const int WIDTH = /* width */;
const int HEIGHT = /* height */;
Bitmap bitmap = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format32bppRgb);
Byte[] bytes = new byte[WIDTH * HEIGHT * 4];
BitmapToByteArray(bitmap, bytes);
private unsafe void BitmapToByteArray(Bitmap bitmap, Byte[] bytes)
{
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, WIDTH, HEIGHT), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
fixed(byte* pBytes = &bytes[0])
{
MoveMemory(pBytes, bitmapData.Scan0.ToPointer(), WIDTH * HEIGHT * 4);
}
bitmap.UnlockBits(bitmapData);
}
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);
Well, using Marshal.Copy() would be wiser here, that at least cuts out on the (one time) cost of looking up the DLL export. But that's it, they both use the C runtime memcpy() function. Speed is entirely throttled by the RAM bus bandwidth, only buying a more expensive machine can speed it up.
Beware that profiling is tricky, accessing the bitmap data the first time causes page faults to get the pixel data into memory. How long that takes is critically dependent on what your hard disk is doing and the state of the file system cache.
I'm reading into this that you're looking to do pixel manipulation of a bitmap.
So logically, you're wanting to access the pixels as an array in order to do this.
The problem your facing is fundamentally flawed, and the other guys didn't pick up on this - and maybe this is something they can take away too?
Basically, you've gotten so far to be messing with pointers from bitmaps, I'm going to give you one of my hard earned "secrets".
YOU DON'T NEED TO COPY THE BITMAP INTO AN ARRAY. Just use it as it is.
Unsafe pointers are your friend in this case. When you hit "bitmapData.Scan0.ToPointer()" you missed the trick.
So long as you have a pointer to the first pixel, and the Stride, and you're aware of the number of bytes per pixel, then you're on to a winner.
I define a bitmap specifically with 32 bits per pixel (memory efficient for UInt32 access) and I get a pointer to first pixel. I then treat the pointer as an array, and can both read and write pixel data as UInt32 values.
Code speaks louder than words. have a look.
I salute you for making it this far!
This is untested code, but much is copied from my source.
public delegate void BitmapWork(UInt32* ptr, int stride);
/// <summary>
/// you don't want to forget to unlock a bitmap do you? I've made that mistake too many times...
/// </summary>
unsafe private void UnlockBitmapAndDoWork(Bitmap bmp, BitmapWork work)
{
var s = new Rectangle (0, 0, bmp.Width, bmp.Height);
var locker = bmp.LockBits(new Rectangle(0, 0, 320, 200), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var ptr = (UInt32*)locker.Scan0.ToPointer();
// so many times I've forgotten the stride is expressed in bytes, but I'm accessing with UInt32's. So, divide by 4.
var stride = locker.Stride / 4;
work(ptr, stride);
bmp.UnlockBits(locker);
}
//using System.Drawing.Imaging;
unsafe private void randomPixels()
{
Random r = new Random(DateTime.Now.Millisecond);
// 32 bits per pixel. You might need to concern youself with the Alpha part depending on your use of the bitmap
Bitmap bmp = new Bitmap(300, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
UnlockBitmapAndDoWork(bmp, (ptr, stride) =>
{
var calcLength = 300 * 200; // so we only have one loop, not two. It's quicker!
for (int i = 0; i < calcLength; i++)
{
// You can use the pointer like an array. But there's no bounds checking.
ptr[i] = (UInt32)r.Next();
}
});
}
I would also look at System.Buffer.BlockCopy. This function is also very fast and it might be competetive in this setup as you are copying from managed to managed in your case. Unfortunately I can not provide some performance tests.
I think this is faster (I actually used it):
// Bitmap bytes have to be created via a direct memory copy of the bitmap
private byte[] BmpToBytes_MemStream (Bitmap bmp)
{
MemoryStream ms = new MemoryStream();
// Save to memory using the Jpeg format
bmp.Save(ms, ImageFormat.Jpeg);
// read to end
byte[] bmpBytes = ms.GetBuffer();
bmp.Dispose();
ms.Close();
return bmpBytes;
}
Original Source