VB.NET
(1)
GCHandleType
(1)
UnsafeAddrOfPinnedArrayElement
(1)
XP
(1)
ImageLockMode
(1)
GCHandle
(1)
PixelFormat
(1)
IntPtr
(1)

key to speed - use byte array instead of writing to locked image bytes

Asked By James Maeding
24-Jan-07 06:25 PM
I generally work with large scans, so speed matters for me.
I wanted to set transparency for colors whose RGB byte total is less than 10 (light colors).
I tried the Bob powell method listed at:
http://www.bobpowell.net/lockingbits.htm

which locks a source and dest bitmap, then writes to the dest bitmap with:
Marshal.WriteByte(bmd.Scan0, offset, currentByte)

This is proving to be way too slow for images, say 2000x2000 pixels.

I think I have figured out a solution, based on looking at lots of others' code, I am interested in any comments on if I
am right or wrong...
Here is the solution summary:
1) lock source image and write bytes to byte array
2) make dest bitmap and lock
3) modify byte array as desired
4) write byte array to dest image memory all at once and unlock dest image

It seems a byte array is way faster to edit, and worth the trouble to write back at the end.
I have only done this for black and white conversions but I think the pattern applies to all bitmap manipulations.

Detailed code that changes image to black and white very fast (not transparency involved here):
1) lock the source bitmap,
marshall.copy the bytes to a byte array
unlock the source.
' Lock source bitmap in memory
Dim sourceData As BitmapData = source.LockBits(New Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)

' Copy image data to binary array
Dim imageSize As Integer = sourceData.Stride * sourceData.Height
Dim sourceBuffer As Byte() = New Byte(imageSize - 1) {}
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize)

' Unlock source bitmap
source.UnlockBits(sourceData)

2) Make destination bitmap,
lock bits
make desination buffer (leave bitmap locked for later write)
' Create destination bitmap
Dim destination As Bitmap = New Bitmap(source.Width, source.Height, PixelFormat.Format1bppIndexed)

' Lock destination bitmap in memory
Dim destinationData As BitmapData = destination.LockBits(New Rectangle(0, 0, destination.Width,
destination.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed)

' Create destination buffer
imageSize = destinationData.Stride * destinationData.Height
Dim destinationBuffer As Byte() = New Byte(imageSize - 1) {}

3) modify dest buffer as needed, this code loops through source and changes destination pixels to black or white:
Dim sourceIndex As Integer = 0
Dim destinationIndex As Integer = 0
Dim pixelTotal As Integer = 0
Dim destinationValue As Byte = 0
Dim pixelValue As Integer = 128
Dim height As Integer = source.Height
Dim width As Integer = source.Width
Dim threshold As Integer = 650

' Iterate lines
Dim y As Integer = 0
Do While y < height
sourceIndex = y * sourceData.Stride
destinationIndex = y * destinationData.Stride
destinationValue = 0
pixelValue = 128

' Iterate pixels
Dim x As Integer = 0
Do While x < width
' Compute pixel brightness (i.e. total of Red, Green, and Blue values)
pixelTotal = CInt(sourceBuffer(sourceIndex + 1)) + CInt(sourceBuffer(sourceIndex + 2)) +
CInt(sourceBuffer(sourceIndex + 3))
If pixelTotal > threshold Then
destinationValue += CByte(pixelValue)
End If
If pixelValue = 1 Then
destinationBuffer(destinationIndex) = destinationValue
destinationIndex += 1
destinationValue = 0
pixelValue = 128
Else
pixelValue >>= 1
End If
sourceIndex += 4
x += 1
Loop
If pixelValue <> 128 Then
destinationBuffer(destinationIndex) = destinationValue
End If
y += 1
Loop

4) write buffer to destination bits all at once
unlock dest bitmap
' Copy binary image data to destination bitmap
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize)

' Unlock destination bitmap
destination.UnlockBits(destinationData)

Then do whatever with the bitmap, save to file, write to form, whatever.
This runs like 50 times faster than writing to the destination bitmap's pixels one by one.
Am I off here?  I have only been doing GDI+ for a couple weeks...
thanks

I am doing it similar to the the way you are doing it and indeed foundit by

Asked By Patrick van Dijk
25-Jan-07 03:54 AM
I am doing it similar to the the way you are doing it and indeed found
it by far the best solution.
I've seen another implementation allocating unmanaged memory and giving
that to the bitmap/image,
but this tends to be slower when it is required to copy the data
anyway.

So I think you are on the right track.

Patrick

The fastest method is to avoid any copy of pixels.

Asked By Frank Hileman
25-Jan-07 10:12 AM
The fastest method is to avoid any copy of pixels. Since the Bitmap
constructor accepts a pointer to a pixel buffer, you only have to convert a
managed array of uint or byte to an IntPtr using GCHandle.Alloc to obtain a
pinned handle and Marshall.UnsafeAddrOfPinnedArrayElement to convert to an
IntPtr. Modifying the array contents will modify the pixels/memory within
the Bitmap directly. LockBits is not needed.

You don't need unsafe code or pointer arithmetic (pointers add no speed),
and it is screaming fast, assuming your format is
PixelFormat.Format32bppPArgb.

Regards,
Frank Hileman

check out VG.net: http://www.vgdotnet.com
Animated vector graphics system
Integrated Visual Studio graphics editor

wow, can you point to any example code, I am intermediate at .

Asked By James Maeding
25-Jan-07 02:11 PM
wow, can you point to any example code, I am intermediate at .net so that will help me put things together.
I will check out your site too.
thanks for the tip, this should be fun.
I looked at your site, very slick.
Asked By James Maeding
25-Jan-07 02:23 PM
I looked at your site, very slick.
I will see if I can dig through the pointer method you described...
Hello James,My example uses LockBits purely because I wanted to show the
Asked By Bob Powell [MVP]
25-Jan-07 08:18 PM
Hello James,
My example uses LockBits purely because I wanted to show the principle
and be able to do this with exact equivalent code in both VB.Net and C#.

In fact, a similar methos that uses lockbits and eschews the Marshal
class in favour of unsafe pointers in c# is very fast indeed and has
been the basis of several commercial solutions provided by my company.

VB has pros and cons. This is a definite con.

Bob.

--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.
Yeah, listen to Frank too. His info is invariably good.
Asked By Bob Powell [MVP]
25-Jan-07 08:19 PM
Yeah, listen to Frank too. His info is invariably good.


--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.
Hi Bob,So you are saying the pointer method by Frank cannot be done in VB?
Asked By James Maeding
26-Jan-07 11:37 AM
Hi Bob,
So you are saying the pointer method by Frank cannot be done in VB?
I thought he was saying you could.
I am finding the byte array to be plenty fast so far and it can be done in  VB so I'll be sticking to that for a while.
The main questions I have at this point are if I must deal with 1 bit, 8 bit, and 24 bit color images differently.
I need to read your site more, I have only spliced other peoples code together for the bit manipulation part so far, not
authored it myself.
What I'd like to do is write subroutines for manipulating pixel colors that encapsulate the details of the different
formats.  Then I can focus on the algorythms instead of the formatting details.

Also, I tried reading a 750 meg tif into an image object yesterday, I immediately got an "out of memory" exception.
Is there a way around this?  My win XP machine has 2 gigs ram and 1.5 were free when I ran it.
Thanks
The VB language doesn't support the use of _unsafe pointers_ so the fastest
Asked By Bob Powell [MVP]
26-Jan-07 04:31 PM
The VB language doesn't support the use of _unsafe pointers_ so the
fastest method of access is not available to VB programmers.

When using the Marshal class you don't use pointers, just indexes from a
given starting point.

If you check out the article on LockBits you'll find a fairly
comprehensive description of the various memory buffer layouts for each
of the major graphic formats. Whatever method of access you use the
physical layouts remain the same.

The apparent size of an image on disc may not have any correlation to
it's memory footprint. Images are decompressed to a rastar that holds
the whole image and so they will take up X*Y*bit-depth/8 bytes
regardless of the size on disc. A jpeg of a few hundred K may end up at
3-4-7 hundred megabytes after loading.

A 750 meg TIFF might be ok as long as it was a simple format but TIFF is
such a huge spec that the GDI+ readers can't handle every different
form. It would also depend on things like total available memory and
swap-file size.

There are no set rules i'm afraid.

--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.
thanks for the reply.What would the limit be though, for a bitmap?
Asked By James Maeding
26-Jan-07 06:29 PM
thanks for the reply.
What would the limit be though, for a bitmap?
Is it the amount of Ram or ram + paged memory?
I am having luck with images about 150 meg and under.  These are aerial photos that engineers use.
I always have to resample them down and change to 8 bit color to work well in AutoCad.
thanks
Bob, your site is so helpful, thanks for taking the time to organize and share
Asked By James Maeding
26-Jan-07 06:41 PM
Bob, your site is so helpful, thanks for taking the time to organize and share all that stuff :)
Hi James,Here is the C# code, it works fine in VB since you don't need any
Asked By Frank Hileman
01-Feb-07 06:30 PM
Hi James,

Here is the C# code, it works fine in VB since you don't need any pointers,
you just modify integers in the pixels array. Don't forget to Free the
handle when you are completely finished with the bitmap. The "pixels" array
is an array of uint (unsigned integer). You can use signed integer but it is
more tricky to deal with the top bit. You can access the blue channel with a
0xFF mask, green with 0xFF00, etc. Or you can use an array of bytes, but
that isn't as fast.

// 4 bytes per pixel, format: ARGB
pixels = new uint[width * height];

// if not pinned the GC can move around the array
handle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0);
bitmap = new Bitmap(width , height, width * 4,
PixelFormat.Format32bppPArgb, pointer);

To access a pixel on the bitmap at x and y:

pixel at (x,y) = pixels[width * y + x]

Any modification to that array modifies the bitmap directly. No marshall or
copy needed.

Regards,
Frank Hileman

check out VG.net: http://www.vgdotnet.com
Animated vector graphics system
Integrated Visual Studio graphics editor
Post Question To EggHeadCafe