We help IT Professionals succeed at work.

# Bitmap to byte conversion variance?

on
Medium Priority
526 Views

Okay, this is a tricky one... or at least tricky to explain.  I have one large bitmap, and a small bitmap that I am trying to compare to a rectangle inside the larger bitmap. I have a pair of pictures that are a perfect match... I iterate through the pixels of the small bitmap and compare the GetPixel color result to that of the pixel within the larger bitmap, and it's a match.

Anyway, I asked for ideas on how to do this quickly, and Sancler had the clever idea of using a ComputeHash on the binary data.  When this works, it is incredibly fast.  But it doesn't always work, and I don't understand why.  My code is below.

What happens is that even before I compute the hash, the byte array that results from the apparently identical bitmaps are different.  In fact, they are WAY different, the byte array returned by one conversion is length 938, and the length of the other byte array is 375.  From bitmaps that apparently are the same... any theories as to what could be different??

(Sancler, hoping you take a crack at this.... if this works I didn't give you enough for the original idea.  :)  )

******

Dim psmall As Bitmap = My.Resources.ComparePicA
Dim pbig As Bitmap = My.Resources.MainPic
Dim psmallcompare As Bitmap = pbig.Clone(New Rectangle(254, 189, 17, 17), pbig.PixelFormat)

MsgBox(ComparePixels(psmall, psmallcompare))     ' <---- this returns true, indicating that the subrectangle is a match

If psmall.PixelFormat = psmallcompare.PixelFormat Then
MsgBox("format is the same")   ' <----- this returns true, which I think means same format
End If

Dim ic As System.Drawing.ImageConverter = New System.Drawing.ImageConverter
Dim btImage1(1) As Byte
btImage1 = CType(ic.ConvertTo(psmall, btImage1.GetType), Byte())
Dim btImage2(1) As Byte
btImage2 = CType(ic.ConvertTo(psmallcompare, btImage2.GetType), Byte())

' problem is apparently above this point, since btImage1 and btImage2 are different byte arrays

Dim shaM As SHA256Managed = New SHA256Managed
Dim hash1 As Byte() = shaM.ComputeHash(btImage1)
Dim hash2 As Byte() = shaM.ComputeHash(btImage2)

.....

Private Function ComparePixels(ByVal b1 As Bitmap, ByVal b2 As Bitmap) As Boolean

Dim c2 As Color
Dim c1 As Color

For x As Integer = 0 To Min(b1.Width, b2.Width) - 1

For y As Integer = 0 To Min(b1.Height, b2.Height) - 1

c1 = b1.GetPixel(x, y)
c2 = b2.GetPixel(x, y)

If c1 <> c2 Then
Return False
End If

Next

Next

Return True

End Function
Comment
Watch Question

## View Solution Only

Commented:

Okay, got a little more info.  The only difference when I put a watch on the two bitmaps, the only difference is the flags parameter (could that be responsible for the big resultant byte array difference?).  The psmall (picture from the resource) flags parameter is 77840, and the psmallcompare (picture cloned from the large resource) flags is zero.  Looks like 77840 totals up to

ImageFlagsHasRealPixelSize (8192)
ImageFlagsHasRealDPI (4096)
ImageFlagsColorSpaceRGB (16)

Man... I have no idea what these mean, or how these differences occurred.  Does anybody know how I can resolve these differences, or how to create bitmaps whose binary is compatible?

Thanks.

Commented:

Some experiments with bitmap flags, created add'l bitmaps in the following ways:

Dim psmall As Bitmap = My.Resources.ComparePicA
Dim pbig As Bitmap = My.Resources.MainPic
Dim psmallcompare As Bitmap = pbig.Clone(New Rectangle(254, 189, 17, 17), pbig.PixelFormat)
Dim psmallclone As Bitmap = psmall.Clone(New Rectangle(0, 0, psmall.Width, psmall.Height), psmall.PixelFormat)
Dim psmallclone2 As Bitmap = psmall.Clone(New Rectangle(1, 1, psmall.Width - 1, psmall.Height - 1), psmall.PixelFormat)

resultant watch flags:

psmall.Flags = 77840
psmallcompare.Flags = 0
psmallclone.Flags = 77840
pbig.Flags = 77840
psmall.Flags = 77840
psmallclone2.Flags = 0

The WEIRDEST thing is that the original source bitmaps have the same flags.  For some reason the clones of subrectangles results in very different flags.

Commented:

It LOOKS like I can get the same flags if I take subrectangle clones of my original resources, instead of using a full-sized clone, or the orignal resource.  This is a big pain, I'll have to re-do all my resources; does anybody out there have any models of why this would be?  Or ideas on ways I can rectify these flags w/o this blind workaround??

Commented:

Crap.  Using that technique, there is a pair of bitmaps that SHOULD match, have the same flags (because I forced a subrectangle clone), but result in a vertical and horizontal resolution of 96 for one and a resoultion of 96.01199 for the otherone, which apparently throws off the byte conversion.  Crap!!

Anyone, please, any ideas??  So close, and this is by far the fastest way I know to do this...

Commented:
So, what is SHA256Managed and ComputeHash?
Generate Byte() array from every bitmap using LockBits and Marshal.Copy and compare two arrays.

Commented:

I've got that working fine, AlexFM:

http://www.experts-exchange.com/Programming/Programming_Languages/Dot_Net/VB_DOT_NET/Q_21786077.html

but it seems way slower than converting the object to a byte array using the ImageConverter technique.  (In fact strangely, as I said in that question, the Marshal.Copy was slower than GetPixel for my size bitmaps.)  The problem now seems to be that the Clone maintains the content but messes with the bitmap format, which throws off the byte compare.

The ComputeHash is just a cute way of comparing the arrays (suggested by Sancler also in the above question).  I have yet to benchmark that vs. a straight array comaprison.  (Actually I might end up using a combination -- check for negative by comparing the first couple of values, else verify positive by comparing the hashes.)

Commented:

Oh wait, I hadn't tried marshal.copy, I was using marshal.getbyte.  That might be faster if I don't care about individual pixels, I'll try it.

Commented:
And it shouldn't be screwed up by the clone since I'd just be accessing the BitmapData... will definitely try it.

Commented:

Actually AlexFM, I don't suppose you have any code that Marshal.Copys the bitmap data, I'm having some trouble:

marshal.Copy mybitmapdata.Scan0 ... something or other

Commented:

okay, how come this works:

Blue(x, y) = Marshal.ReadByte(bd.Scan0, (bd.Stride * y) + (4 * x))

but this doesn't work?

Dim m_btimage(1) As Byte
Dim bd As BitmapData
bd = m_bmp.LockBits(New Rectangle(0, 0, m_bmp.Width, m_bmp.Height), ImageLockMode.ReadOnly, m_bmp.PixelFormat)
Marshal.Copy(bd.Scan0, 0, m_btimage, m_bmp.Width * m_bmp.Height)

Commented:
Dim m_btimage(m_bmp.Width * m_bmp.Height * 3) As Byte

This is right for RGB24 image. For other format array size can be different. This code is right also if BitmapData.Stride is equal to Width*Height*3, which is not always true. If Stride is not equal to line size, every line should be copied separately. I will post more code later.

Commented:

Oh, right, size might be wrong too.  What was screwing me up was plugging bd.Scan0 into the first parameter of Marshal.Copy.  Yeah, let me know if you know how to do this, I'll keep trying...

Commented:

I've got a couple irons in the fire on this one (hope this isn't starting to become multiple questions), looking for an alternative to clone, which might be too smart and was messing with the bitmap format.  Went back to bitblt, hacked some other working code, and darned if the following SHOULD work to copy a bitmap to another, but doesn't.  Wow, I get stuck a lot.  If anyone can help me make progress on any one of these little issues, it might help me solve the big one.

Thanks all.

Public Function Copy(ByVal r As Rectangle) As Bitmap

Dim newbmp As New Bitmap(r.Width, r.Height, m_bmp.PixelFormat)

Dim newgraphic As Graphics = Graphics.FromImage(m_bmp)
Dim oldgraphic As Graphics = Graphics.FromImage(newbmp)

Dim newhdc As IntPtr = newgraphic.GetHdc
Dim oldhdc As IntPtr = oldgraphic.GetHdc

Dim i As Integer = BitBlt(newhdc, 0, 0, r.Width, r.Height, oldhdc, r.X, r.Y, SRCCOPY)  '<-- i returns 1, meaning success

newgraphic.ReleaseHdc(newhdc)
oldgraphic.ReleaseHdc(oldhdc)

newgraphic.Dispose()
oldgraphic.Dispose()
Commented:
This is tested code. You need first function.

' Create Byte array from RGB24 bitmap
Private Function BitmapToBytesRGB24(ByVal bmp As Bitmap) As Byte()
If bmp.PixelFormat <> PixelFormat.Format24bppRgb Then
Return Nothing
End If

Dim i As Integer
Dim length As Integer = bmp.Width * bmp.Height * 3

Dim bytes(length) As Byte

Dim data As BitmapData
data = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), _
bmp.PixelFormat)

If (data.Stride = bmp.Width * 3) Then
Marshal.Copy(data.Scan0, bytes, 0, length)
Else
For i = 0 To bmp.Height - 1
Dim p As IntPtr = New IntPtr(data.Scan0.ToInt32() + data.Stride * i)
Marshal.Copy(p, bytes, i * bmp.Width * 3, bmp.Width * 3)
Next
End If

bmp.UnlockBits(data)

Return bytes
End Function

' Create RGB24 bitmap from Byte array
Private Function BitsToBitmapRGB24(ByVal bytes() As Byte, ByVal width As Integer, ByVal height As Integer)
If (bytes.GetLength(0) < width * height * 3) Then
Return Nothing
End If

Dim bmp As Bitmap
Dim i As Integer

bmp = New Bitmap(width, height, PixelFormat.Format24bppRgb)

Dim data As BitmapData
data = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), _
ImageLockMode.WriteOnly, _
bmp.PixelFormat)

If (data.Stride = width * 3) Then
Marshal.Copy(bytes, 0, data.Scan0, width * height * 3)
Else
For i = 0 To bmp.Height - 1
Dim p As IntPtr = New IntPtr(data.Scan0.ToInt32() + data.Stride * i)
Marshal.Copy(bytes, i * bmp.Width * 3, p, bmp.Width * 3)
Next
End If

bmp.UnlockBits(data)

Return bmp
End Function

Not the solution you were looking for? Getting a personalized solution is easy.

Commented:

Imports System.Runtime.InteropServices
Imports System.Drawing.Imaging

Commented:

Awesome, thank you.  This whole thing has halved my compare time.

I'm going to figure out what was wrong with my BitBlt copy in another question, because I'd still like compare the current approach

Clone->LockBits->Marshal.Copy->ComputeHash, compare hash

to

BitBlt->ImageConverter to byte array->ComputeHash, compare hash

but right now I'm still way ahead.  Thanks!

Commented:

Actually... it's a good bit faster without the hash, just parsing through the byte array.  So the real magic bullet was just being able to convert to a byte array.
##### Thanks for using Experts Exchange.

• View three pieces of content (articles, solutions, posts, and videos)
• Ask the experts questions (counted toward content limit)
• Customize your dashboard and profile