Discussion:
Invalid Bitmap Handle
(too old to reply)
Simon Woods
2004-12-23 10:56:30 UTC
Permalink
Hi

[Warning: graphics newbie]

I've a picture box, whose picture I'm assigning to a StdPicture variable. (I
basically want to alpha blend it in-memory and then assign it back to the
picture box, so I'm using 2 in-memory device contexts, CSourceDC and
CTargetDC)

I'm using Karl Petersens CMemoryDC. I pass in the picture into his class.

His routine uses the handle of the picture as the handle of the bitmap, hBmp

Dim bmp as BITMAP

GetObject(hBmp, Len(bmp), bmp)

This fails with an invalid handle error. However, if I pass in the Image
property of picture box this call works fine.

1) Could someone explain this to me please.

2) I make the necessary alphablend to the image/picture. I'm happy that each
pixel gets alphablended (I've tried 2 techniques for alphablending a) the
API call b) a VB-only routine which means I can check the alphablended value
of each pixel and everything seems okay). There's a SetDIBitsToDevice call
to transfer the processed pixel info back to the CTargetDC in-memory device
contexts. However, when I get the picture out of the CTargetDC in-memory and
put it back into the picture box it's black. Can someone suggest how I can
dig further to find out where it's failing.

Thanks

S
Mike D Sutton
2004-12-23 11:44:56 UTC
Permalink
Post by Simon Woods
[Warning: graphics newbie]
I've a picture box, whose picture I'm assigning to a StdPicture variable. (I
basically want to alpha blend it in-memory and then assign it back to the
picture box, so I'm using 2 in-memory device contexts, CSourceDC and
CTargetDC)
I'm using Karl Petersens CMemoryDC. I pass in the picture into his class.
His routine uses the handle of the picture as the handle of the bitmap, hBmp
Dim bmp as BITMAP
GetObject(hBmp, Len(bmp), bmp)
This fails with an invalid handle error. However, if I pass in the Image
property of picture box this call works fine.
1) Could someone explain this to me please.
2) I make the necessary alphablend to the image/picture. I'm happy that each
pixel gets alphablended (I've tried 2 techniques for alphablending a) the
API call b) a VB-only routine which means I can check the alphablended value
of each pixel and everything seems okay). There's a SetDIBitsToDevice call
to transfer the processed pixel info back to the CTargetDC in-memory device
contexts. However, when I get the picture out of the CTargetDC in-memory and
put it back into the picture box it's black. Can someone suggest how I can
dig further to find out where it's failing.
Based on your previous post with Icons, I would assume that the StdPicture object you're setting into the picture box contains an
icon? If so then it's a slightly different creature than a standard Bitmap and contained within an HICON rather than an HBITMAP.
You can still use it with the API, but you have to do so in a slightly different way - Copy the area of the destination you're
drawing to to your CSourceDC and CTargetDC, then use DrawIconEx() to draw the icon over it and finally AlphaBlend() the composite
icon onto CTargetDC.
The image property of the picture box on the other hand is basically the drawing surface of the control which is always a Bitmap and
as such wraps a HBITMAP behind the scenes which Karl's class works with properly.
To find out whether your StdPicture wraps a Bitmap or not, call the GetObjectType() API call on it - A Bitmap will return OBJ_BITMAP
where as an icon will return null.
Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-23 11:59:12 UTC
Permalink
Post by Mike D Sutton
Post by Simon Woods
[Warning: graphics newbie]
I've a picture box, whose picture I'm assigning to a StdPicture variable. (I
basically want to alpha blend it in-memory and then assign it back to the
picture box, so I'm using 2 in-memory device contexts, CSourceDC and
CTargetDC)
I'm using Karl Petersens CMemoryDC. I pass in the picture into his class.
His routine uses the handle of the picture as the handle of the bitmap, hBmp
Dim bmp as BITMAP
GetObject(hBmp, Len(bmp), bmp)
This fails with an invalid handle error. However, if I pass in the Image
property of picture box this call works fine.
1) Could someone explain this to me please.
2) I make the necessary alphablend to the image/picture. I'm happy that each
pixel gets alphablended (I've tried 2 techniques for alphablending a) the
API call b) a VB-only routine which means I can check the alphablended value
of each pixel and everything seems okay). There's a SetDIBitsToDevice call
to transfer the processed pixel info back to the CTargetDC in-memory device
contexts. However, when I get the picture out of the CTargetDC in-memory and
put it back into the picture box it's black. Can someone suggest how I can
dig further to find out where it's failing.
Based on your previous post with Icons, I would assume that the StdPicture
object you're setting into the picture box contains an
Post by Mike D Sutton
icon? If so then it's a slightly different creature than a standard
Bitmap and contained within an HICON rather than an HBITMAP.
Post by Mike D Sutton
You can still use it with the API, but you have to do so in a slightly
different way - Copy the area of the destination you're
Post by Mike D Sutton
drawing to to your CSourceDC and CTargetDC, then use DrawIconEx() to draw
the icon over it and finally AlphaBlend() the composite
Post by Mike D Sutton
icon onto CTargetDC.
The image property of the picture box on the other hand is basically the
drawing surface of the control which is always a Bitmap and
Post by Mike D Sutton
as such wraps a HBITMAP behind the scenes which Karl's class works with properly.
To find out whether your StdPicture wraps a Bitmap or not, call the
GetObjectType() API call on it - A Bitmap will return OBJ_BITMAP

Mike ...

I'm sorry I mislead you, it's bitmaps I'm working with.

But the principle of copying the area of the destination to both SourceDC
and TargetDC, does that still hold for bitmaps. ATM I'm
a) copying the picture into the SourceDC
b) getting the pixel data into an array
c) processing this
d) SetDIBitsToDevice to the TargetDC
e) extracting the StdPicture from the TargetDC
f) setting the picture back into the picturebox

Is that the correct methodology?

FYI, the picture box is a bit of a red herring because I'm working with
another OCX which uses stdpictures, so I'm just creating a testbed for the
technique with pictureboxes as the repository for stdpictures

Thanks again

Simon
Mike D Sutton
2004-12-23 12:19:20 UTC
Permalink
Post by Simon Woods
I'm sorry I mislead you, it's bitmaps I'm working with.
But the principle of copying the area of the destination to both SourceDC
and TargetDC, does that still hold for bitmaps.
Nope, the reason for copying it to both was that Icon's can have masked areas which effectively show through the underlying image so
by copying the destination area to both the source and destination, any area's where the icon was masked would simply come out as
the destination colour (since any colour blended with itself is itself.)
Post by Simon Woods
ATM I'm
a) copying the picture into the SourceDC
b) getting the pixel data into an array
c) processing this
d) SetDIBitsToDevice to the TargetDC
e) extracting the StdPicture from the TargetDC
f) setting the picture back into the picturebox
Is that the correct methodology?
Looks like it should work, yes. If by 'processing' the image data you're simply blending it then the AlphaBlend() API call will cut
out a few of those steps but the principle is correct.
Post by Simon Woods
FYI, the picture box is a bit of a red herring because I'm working with
another OCX which uses stdpictures, so I'm just creating a testbed for the
technique with pictureboxes as the repository for stdpictures
That's not a problem, it's actually being abstracted a level further into dealing with the GDI objects that the StdPicture's wrap
anyway so it's a long way from a solely Picture box based solution :)

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-23 12:32:25 UTC
Permalink
Post by Mike D Sutton
Post by Simon Woods
ATM I'm
a) copying the picture into the SourceDC
b) getting the pixel data into an array
c) processing this
d) SetDIBitsToDevice to the TargetDC
e) extracting the StdPicture from the TargetDC
f) setting the picture back into the picturebox
Is that the correct methodology?
Looks like it should work, yes. If by 'processing' the image data you're
simply blending it then the AlphaBlend() API call will cut
Post by Mike D Sutton
out a few of those steps but the principle is correct.
(Mike thanks for all your help, BTW)

Unfortunately, the solution may have to work on NT4 machines which AIUI
don't support the API call so I was using a link which, I seem to think, you
provided in another thread I found, FoxAlphaBlend

Set l_oSourceDC = New CMemoryDC
Set l_oTargetDC = New CMemoryDC

Set l_oSourceDC.Picture = p_oPic

FoxAlphaBlend l_oTargetDC.hdc, 0, 0, _
l_oSourceDC.Width, l_oSourceDC.Height, _
l_oSourceDC.hdc, 0, 0, 150, &HFF00FF, 0&

Public Function FoxAlphaBlend( _
ByVal DstDC As Long, _
ByVal DstX As Long, _
ByVal DstY As Long, _
ByVal DstW As Long, _
ByVal DstH As Long, _
ByVal SrcDC As Long, _
ByVal SrcX As Long, _
ByVal SrcY As Long, _
ByVal Alpha As Byte, _
ByVal TransColor As Long, _
ByVal Flags As Long) As Long

If Alpha = 0 Or DstW = 0 Or DstH = 0 Then Exit Function

Dim B As Long, H As Long, F As Long, I As Long
Dim TmpDC As Long, TmpBmp As Long, TmpObj As Long
Dim Sr2DC As Long, Sr2Bmp As Long, Sr2Obj As Long
Dim Data1() As Long, Data2() As Long
Dim Info As BITMAPINFO

TmpDC = CreateCompatibleDC(SrcDC)
Sr2DC = CreateCompatibleDC(SrcDC)
TmpBmp = CreateCompatibleBitmap(DstDC, DstW, DstH)
Sr2Bmp = CreateCompatibleBitmap(DstDC, DstW, DstH)
TmpObj = SelectObject(TmpDC, TmpBmp)
Sr2Obj = SelectObject(Sr2DC, Sr2Bmp)
ReDim Data1(DstW * DstH * 4 - 1)
ReDim Data2(DstW * DstH * 4 - 1)
Info.bmiHeader.biSize = Len(Info.bmiHeader)
Info.bmiHeader.biWidth = DstW
Info.bmiHeader.biHeight = DstH
Info.bmiHeader.biPlanes = 1
Info.bmiHeader.biBitCount = 32
Info.bmiHeader.biCompression = 0

BitBlt TmpDC, 0, 0, DstW, DstH, DstDC, DstX, DstY, vbSrcCopy
BitBlt Sr2DC, 0, 0, DstW, DstH, SrcDC, SrcX, SrcY, vbSrcCopy
GetDIBits TmpDC, TmpBmp, 0, DstH, Data1(0), Info, 0
GetDIBits Sr2DC, Sr2Bmp, 0, DstH, Data2(0), Info, 0

For H = 0 To DstH - 1
F = H * DstW
For B = 0 To DstW - 1
I = F + B
If (Flags And &H1) And ((Data2(I) And &HFFFFFF) = TransColor)
Then
Else
Data1(I) = ShadeColors(Data1(I), Data2(I), Alpha)
End If
Next B
Next H

SetDIBitsToDevice DstDC, DstX, DstY, DstW, DstH, 0, 0, 0, DstH,
Data1(0), Info, 0

Erase Data1
Erase Data2
DeleteObject SelectObject(TmpDC, TmpObj)
DeleteObject SelectObject(Sr2DC, Sr2Obj)
DeleteDC TmpDC
DeleteDC Sr2DC

End Function

Public Function ShadeColors(ByVal Dst As Long, ByVal Src As Long, ByVal
Shade As Byte)
Select Case Shade
Case 0: ShadeColors = Dst
Case 255: ShadeColors = Src
Case Else:
ShadeColors = (Src And &HFF) * Shade / 255 + (Dst And &HFF) * (255 -
Shade) / 255 Or _
((Src And &HFF00&) * Shade / 255 + (Dst And &HFF00&) * (255 -
Shade) / 255) And &HFF00& Or _
((Src And &HFF0000) * (Shade / 255) + (Dst And &HFF0000) *
((255 - Shade) / 255)) And &HFF0000
End Select
End Function
Mike D Sutton
2004-12-23 13:19:15 UTC
Permalink
Post by Simon Woods
(Mike thanks for all your help, BTW)
Not a problem, that's what these groups are for!
Post by Simon Woods
Unfortunately, the solution may have to work on NT4 machines which AIUI
don't support the API call so I was using a link which, I seem to think, you
provided in another thread I found, FoxAlphaBlend
Not one of mine, looks like it came from the Spanish VB groups that one but some of the constructs look pretty familiar so it could
be partially based of one of my other posts I guess.
Here's my fixed-point version of ShadeColors() though which is a lot faster (although completely unreadable..;)

'***
Private Function LinearCol(ByVal inA As Long, _
ByVal inB As Long, ByVal inPos As Byte) As Long
LinearCol = (((((inA And &HFF&) * (Not inPos)) + _
((inB And &HFF&) * inPos)) \ &H100) And &HFF&) Or _
(((((inA And &HFF00&) \ &H100) * (Not inPos)) + _
(((inB And &HFF00&) \ &H100) * inPos)) And &HFF00&) Or _
(((((((inA And &HFF0000) \ &H10000) * (Not inPos)) + _
(((inB And &HFF0000) \ &H10000) * inPos))) And &HFF00&) * &H100)
End Function
'***

With a quick test here blending between two colours a million times I'm getting 1500ms with ShadeColors() and 1050ms with
LinearCol() in the IDE, and 340ms with ShadeColors() as opposed to _38ms_ with LinearCol() in the compiled EXE. Even with it's
special casing for completely transparent or opaque values ShadeColors() only gets 105ms compiled when using these where as
LinearCol() is still nearly 3 times the speed performing the full blend each time (ShadeColors() does perform better than mine using
these special cases in the IDE though, about 650ms there so nearly twice the speed of mine in that situation.)
Of course with constant alpha you can take advantage of the fact that one input (opacity) never changes and use lookup tables for
source and destination weights which will give you a huge speed increase as you only have to perform two lookups and an addition in
the main processing loop.
Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-23 16:31:52 UTC
Permalink
Sorry Mike need to pick your brain again

I'm running this code ... when I've finished I'd expecting pixel information
in the array l_anSourcePixels(). However, when I look through the array it's
all 0's Is there anything obvious you can see wrong with it.

Thanks

Simon


With l_tInfo.bmiHeader
.biSize = Len(l_tInfo.bmiHeader)
.biWidth = p_nTargetW
.biHeight = p_nTargetH
.biPlanes = 1
.biBitCount = 32
.biCompression = 0
End With

' set up array ready for source bmp pixel data
ReDim l_anSourcePixels(p_nTargetW * p_nTargetH * 4 - 1)

' create a copy of the source Device context
l_hSourceCopyDC = CreateCompatibleDC(p_hSourceDC)

' create a copy of the source bmp - same size as target
l_hSourceCopyBmp = CreateCompatibleBitmap(p_hSourceDC, p_nTargetW,
p_nTargetH)

' move Source bmp into SourceCopy DC
l_hSourceCopyObj = SelectObject(l_hSourceCopyDC, l_hSourceCopyBmp)

' blit SourceDC's bmp into l_hSourceCopyDC
l_nRet = BitBlt(l_hSourceCopyDC, 0, 0, p_nTargetW, p_nTargetH,
p_hSourceDC, p_nSourceX, p_nSourceY, vbSrcCopy)

' populate array with each pixel's information from source bmp
l_nRet = GetDIBits(l_hSourceCopyDC, l_hSourceCopyBmp, 0, p_nTargetH,
l_anSourcePixels(0), l_tInfo, 0)
Mike D Sutton
2004-12-23 16:39:22 UTC
Permalink
Post by Simon Woods
Sorry Mike need to pick your brain again
I'm running this code ... when I've finished I'd expecting pixel information
in the array l_anSourcePixels(). However, when I look through the array it's
all 0's Is there anything obvious you can see wrong with it.
Try de-selecting the Bitmap from the DC before calling GetDIBits():

'***
Call SelectObject(l_hSourceCopyDC, l_hSourceCopyObj)
'***

Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-23 16:51:08 UTC
Permalink
Post by Mike D Sutton
Post by Simon Woods
Sorry Mike need to pick your brain again
I'm running this code ... when I've finished I'd expecting pixel information
in the array l_anSourcePixels(). However, when I look through the array it's
all 0's Is there anything obvious you can see wrong with it.
'***
Call SelectObject(l_hSourceCopyDC, l_hSourceCopyObj)
'***
Sorry no difference, I must be messing up my pointers somewhere along the
line ... no?

S


Here's the code in full

Public Function CreateSelectedImage(ByRef p_oPic As StdPicture, ByVal
p_bFlag As Boolean) As Long

Dim l_oSourceDC As CMemoryDC
Dim l_oTargetDC As CMemoryDC

Set l_oSourceDC = New CMemoryDC
Set l_oTargetDC = New CMemoryDC

Set l_oSourceDC.Picture = p_oPic

IfxAlphaBlend l_oTargetDC.hdc, 0, 0, _
l_oSourceDC.Width, l_oSourceDC.Height, _
l_oSourceDC.hdc, 0, 0, 128, &HFF00FF, 0&

Set p_oPic = l_oTargetDC.Picture

CreateSelectedImage = 0

Exit Function

Error:

'CreateSelectedImage = HandleError(C_MODULE, "CreateSelectedImage")

End Function


Public Function IfxAlphaBlend( _
ByVal p_hTargetDC As Long, _
ByVal p_nTargetX As Long, _
ByVal p_nTargetY As Long, _
ByVal p_nTargetW As Long, _
ByVal p_nTargetH As Long, _
ByVal p_hSourceDC As Long, _
ByVal p_nSourceX As Long, _
ByVal p_nSourceY As Long, _
ByVal Alpha As Byte, _
ByVal TransColor As Long, _
ByVal Flags As Long) As Long

Dim l_tInfo As BITMAPINFO
Dim l_anTempPixels() As Long
Dim l_anSourcePixels() As Long
Dim l_nBase As Long
Dim l_nHeight As Long
Dim l_nIndex As Long
Dim l_nOffset As Long
Dim l_hSourceCopyDC As Long
Dim l_hSourceCopyBmp As Long
Dim l_hSourceCopyObj As Long
Dim l_nRet As Long
Dim l_hTempDC As Long
Dim l_hTempBmp As Long
Dim l_hTempObj As Long

If (Alpha = 0) Or (p_nTargetW = 0) Or (p_nTargetH = 0) Then Exit
Function

With l_tInfo.bmiHeader
.biSize = Len(l_tInfo.bmiHeader)
.biWidth = p_nTargetW
.biHeight = p_nTargetH
.biPlanes = 1
.biBitCount = 32
.biCompression = 0
End With

' set up array ready for source bmp pixel data
ReDim l_anSourcePixels(p_nTargetW * p_nTargetH * 4 - 1)

' create a copy of the source Device context
l_hSourceCopyDC = CreateCompatibleDC(p_hSourceDC)

' create a copy of the source bmp - same size as target
l_hSourceCopyBmp = CreateCompatibleBitmap(p_hSourceDC, p_nTargetW,
p_nTargetH)

' move Source bmp into SourceCopy DC
l_hSourceCopyObj = SelectObject(l_hSourceCopyDC, l_hSourceCopyBmp)

' blit SourceDC's bmp into l_hSourceCopyDC
l_nRet = BitBlt(l_hSourceCopyDC, 0, 0, p_nTargetW, p_nTargetH,
p_hSourceDC, p_nSourceX, p_nSourceY, vbSrcCopy)

' de-select the Bitmap from the DC
Call SelectObject(l_hSourceCopyDC, l_hSourceCopyObj)

' populate array with each pixel's information from source bmp
l_nRet = GetDIBits(l_hSourceCopyDC, l_hSourceCopyBmp, 0, p_nTargetH,
l_anSourcePixels(0), l_tInfo, 0)


' set up array ready for tmp pixel data
ReDim l_anTempPixels(p_nTargetW * p_nTargetH * 4 - 1)

' create a temporary Device context
l_hTempDC = CreateCompatibleDC(p_hSourceDC)

' create a temporary bmp
l_hTempBmp = CreateCompatibleBitmap(p_hTargetDC, p_nTargetW, p_nTargetH)

' move Temp bmp into Temp DC
l_hTempObj = SelectObject(l_hTempDC, l_hTempBmp)

' blit TargetDC's bmp into l_hTempDC
l_nRet = BitBlt(l_hTempDC, 0, 0, p_nTargetW, p_nTargetH, p_hTargetDC,
p_nTargetX, p_nTargetY, vbSrcCopy)

' get each pixel's information about target bmp
l_nRet = GetDIBits(l_hTempDC, l_hTempBmp, 0, p_nTargetH,
l_anTempPixels(0), l_tInfo, 0)

' cycle through each pixel, row by row
For l_nHeight = 0 To p_nTargetH - 1

' calculate row offset
l_nOffset = l_nHeight * p_nTargetW

' column by column
For l_nBase = 0 To p_nTargetW - 1

' convert coordinate into index
l_nIndex = l_nOffset + l_nBase

If (Flags And &H1) And ((l_anSourcePixels(l_nIndex) And
&HFFFFFF) = TransColor) Then
Else

Debug.Assert (l_anSourcePixels(l_nIndex) = 0)

' perform alpha blend
l_anTempPixels(l_nIndex) =
LinearCol(l_anTempPixels(l_nIndex), l_anSourcePixels(l_nIndex), Alpha)
End If

Next l_nBase

Next l_nHeight

' write pixel information back into TargetDC
l_nRet = SetDIBitsToDevice(p_hTargetDC, _
p_nTargetX, p_nTargetY, _
p_nTargetW, p_nTargetH, _
0, 0, _
0, _
p_nTargetH, _
l_anTempPixels(0), _
l_tInfo, _
0)

' tear down
Erase l_anTempPixels
Erase l_anSourcePixels
DeleteObject SelectObject(l_hTempDC, l_hTempObj)
DeleteObject SelectObject(l_hSourceCopyDC, l_hSourceCopyObj)
DeleteDC l_hTempDC
DeleteDC l_hSourceCopyDC

End Function

Private Function LinearCol( _
ByVal inA As Long, _
ByVal inB As Long, _
ByVal inPos As Byte _
) As Long
LinearCol = _
(((((inA And &HFF&) * (Not inPos)) + ((inB And &HFF&) * inPos)) \
&H100) And &HFF&) Or _
(((((inA And &HFF00&) \ &H100) * (Not inPos)) + (((inB And &HFF00&)
\ &H100) * inPos)) And &HFF00&) Or _
(((((((inA And &HFF0000) \ &H10000) * (Not inPos)) + (((inB And
&HFF0000) \ &H10000) * inPos))) And &HFF00&) * &H100)
End Function
Mike D Sutton
2004-12-23 17:23:44 UTC
Permalink
Post by Simon Woods
Sorry no difference, I must be messing up my pointers somewhere along the
line ... no?
l_anTempPixels() and l_anSourcePixels() are declared as Long, where as you're allocating you array for the byte size of the buffer,
instead use

'***
ReDim l_anSourcePixels(p_nTargetW * p_nTargetH - 1) As Long
'***

(This is good reason to put the type in ReDim statements, makes bugs easier to find that way..)
Check the return value of GetDIBits() after the call and see if it's returning a non-null value, if not then check the value of
Err.LastDLLError to see what exactly it complained about. Also it's worth tracing through the entire method and check to see that
each call is returning a valid value, GDI objects have been initialised correctly and so on.
If you still have problems then send me a copy of your code including API declarations exactly as you're using it in your
application and I'll have a run through it here.
Hope this helps,

Mike

P.s. since you've got some repetition there when dealing with the GDI objects, you may want to wrap that up into a class instead of
having a huge method that attempts to do everything - this also promotes reusability should you require similar functionality
elsewhere.


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Mike D Sutton
2004-12-23 20:11:55 UTC
Permalink
Ok, I've just spent the last few hours going through your source, and your control's pictures are icons after all (Ctrl+G ->
?Picture1.Picture.Type == 3 == vbPicTypeIcon) which you'll need to work with rather than the .Image. The reason for this is that
the .Image property is held selected into an internal DC by the picture box object, and the Bitmap can only be selected into a
single DC at any one time - If you trace through the "Set l_oSourceDC.Picture = p_oPic" line in CreateSelectedImage(), you'll see
that Karl instantiates a new CMemoryDC object internally then sets it's Bitmap handle to the one you pass it however in the
UserBitmap() method the SelectObject() call fails (null return value) because the Bitmap is already selected into a DC.
Ok, so that's identified the cause of the problem, now to fix it.
Rather than wasting your time going through all the problems of supporting Icons and Bitmap in your routines, grab the IconHelper
library off my page and it's dependant libraries (ChromaBlt and CopyBitmap) which will perform the conversions for you.
As far as the routine itself goes, to be honest I'm a little confused as to what you're trying to do.. Whilst you have a target
Memory DC object, you never actually assign anything to it so it stays with the default 1*1 pixel Bitmap Karl creates in it's
initialisation. Also since you've not drawn anything to the target then what is the picture supposed to be blending with? By
default a newly created Bitmap should be created filled black which would mean the image is always blended with black, is this
really what you want or should it be blended over a colour or another image?
If you're always working with a solid colour then it greatly cuts down the complexity of the routine since you don't have to have
two sets of GDI objects (you know every pixel of the target will be a fixed colour.)
Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-24 07:40:56 UTC
Permalink
Post by Mike D Sutton
Ok, I've just spent the last few hours going through your source, and your
control's pictures are icons after all (Ctrl+G ->
Post by Mike D Sutton
?Picture1.Picture.Type == 3 == vbPicTypeIcon) which you'll need to work
with rather than the .Image. The reason for this is that
Post by Mike D Sutton
the .Image property is held selected into an internal DC by the picture
box object, and the Bitmap can only be selected into a
Post by Mike D Sutton
single DC at any one time - If you trace through the "Set
l_oSourceDC.Picture = p_oPic" line in CreateSelectedImage(), you'll see
Post by Mike D Sutton
that Karl instantiates a new CMemoryDC object internally then sets it's
Bitmap handle to the one you pass it however in the
Post by Mike D Sutton
UserBitmap() method the SelectObject() call fails (null return value)
because the Bitmap is already selected into a DC.
Post by Mike D Sutton
Ok, so that's identified the cause of the problem, now to fix it.
Rather than wasting your time going through all the problems of supporting
Icons and Bitmap in your routines, grab the IconHelper
Post by Mike D Sutton
library off my page and it's dependant libraries (ChromaBlt and
CopyBitmap) which will perform the conversions for you.
Post by Mike D Sutton
As far as the routine itself goes, to be honest I'm a little confused as
to what you're trying to do.. Whilst you have a target
Post by Mike D Sutton
Memory DC object, you never actually assign anything to it so it stays
with the default 1*1 pixel Bitmap Karl creates in it's
Post by Mike D Sutton
initialisation. Also since you've not drawn anything to the target then
what is the picture supposed to be blending with? By
Post by Mike D Sutton
default a newly created Bitmap should be created filled black which would
mean the image is always blended with black, is this
Post by Mike D Sutton
really what you want or should it be blended over a colour or another image?
If you're always working with a solid colour then it greatly cuts down the
complexity of the routine since you don't have to have
Post by Mike D Sutton
two sets of GDI objects (you know every pixel of the target will be a fixed colour.)
Hope this helps,
Mike I can't thank you enough.

Here's some background:

The application I work with has a custom tree control. I'm introducing cut
and paste and all I want to do is simply to issue a cut on a node and change
the appearance of the node's picture to show it has been cut a la explorer.
However, the tree exposes each node's picture (and image) as a StdPicture
hence I've been try to find a way to process the picture and then assign it
back to the node using in-memory DCs. I looked on vbAccelerator and there's
an app there which does it but it uses the ImageList_Draw and
ImageList_DrawEx and I didn't want to use comctl32. So I posted here asking
what I could use instead. You pointed to your IconEffects but again it's
working from a PictureBox. and I was struggling to see how I could cut in.
(I may have a better idea now). A colleague pointed me to Alphablend-ing to
give me the desired effect, hence I am where I am.

[I have this feeling that you'll be able to show me some very simple
alternative which I've overlooked!!]

Simon
Simon Woods
2004-12-24 08:01:29 UTC
Permalink
Post by Mike D Sutton
Post by Mike D Sutton
Ok, I've just spent the last few hours going through your source, and your
control's pictures are icons after all (Ctrl+G ->
Post by Mike D Sutton
?Picture1.Picture.Type == 3 == vbPicTypeIcon) which you'll need to work
with rather than the .Image. The reason for this is that
Post by Mike D Sutton
the .Image property is held selected into an internal DC by the picture
box object, and the Bitmap can only be selected into a
Post by Mike D Sutton
single DC at any one time - If you trace through the "Set
l_oSourceDC.Picture = p_oPic" line in CreateSelectedImage(), you'll see
Post by Mike D Sutton
that Karl instantiates a new CMemoryDC object internally then sets it's
Bitmap handle to the one you pass it however in the
Post by Mike D Sutton
UserBitmap() method the SelectObject() call fails (null return value)
because the Bitmap is already selected into a DC.
Post by Mike D Sutton
Ok, so that's identified the cause of the problem, now to fix it.
Rather than wasting your time going through all the problems of supporting
Icons and Bitmap in your routines, grab the IconHelper
Post by Mike D Sutton
library off my page and it's dependant libraries (ChromaBlt and
CopyBitmap) which will perform the conversions for you.
Post by Mike D Sutton
As far as the routine itself goes, to be honest I'm a little confused as
to what you're trying to do.. Whilst you have a target
Post by Mike D Sutton
Memory DC object, you never actually assign anything to it so it stays
with the default 1*1 pixel Bitmap Karl creates in it's
Post by Mike D Sutton
initialisation. Also since you've not drawn anything to the target then
what is the picture supposed to be blending with? By
Post by Mike D Sutton
default a newly created Bitmap should be created filled black which would
mean the image is always blended with black, is this
Post by Mike D Sutton
really what you want or should it be blended over a colour or another
image?
Post by Mike D Sutton
If you're always working with a solid colour then it greatly cuts down the
complexity of the routine since you don't have to have
Post by Mike D Sutton
two sets of GDI objects (you know every pixel of the target will be a
fixed colour.)
so I took another look at your clsIconFX ...I've tried to get your Fade
method working

but ... You said above that the picture was an Icon but if I check the
picture type of the StdPicture passed into CreateSelected Image, it returns
a type of 1-bmp hence, presumably, the GetIconInfo call fails in the
clsIconFX.Create call.

S
Mike D Sutton
2004-12-24 12:50:23 UTC
Permalink
Post by Simon Woods
so I took another look at your clsIconFX ...I've tried to get your Fade
method working
but ... You said above that the picture was an Icon but if I check the
picture type of the StdPicture passed into CreateSelected Image, it returns
a type of 1-bmp hence, presumably, the GetIconInfo call fails in the
clsIconFX.Create call.
Ok, here's an alternative solution. Stick these in your module:

'***
Private Declare Function GetSysColor Lib "User32.dll" (ByVal nIndex As Long) As Long

Public Function EvalCol(ByVal inCol As Long) As Long
If ((inCol And &HFFFFFF00) = &H80000000) Then _
EvalCol = GetSysColor(inCol And &HFF&) Else _
EvalCol = inCol ' Return the true RGB from any colour
End Function

Public Function FlipRGB(ByVal inRGB As Long) As Long
FlipRGB = (inRGB And &HFF00FF00) Or _
((inRGB And &HFF&) * &H10000) Or _
((inRGB And &HFF0000) \ &H10000)
End Function
'***

Then replace your CreateSelectedImage() with this:

'***
Public Function CreateSelectedImage(ByRef inPicture As StdPicture, _
ByVal inTintCol As Long, ByVal inOpacity As Byte) As StdPicture
Dim SourceDC As CMemoryDC
Dim hIconBitmap As Long

Set SourceDC = New CMemoryDC

Select Case inPicture.Type
Case vbPicTypeBitmap ' Use Bitmap as is
Set SourceDC.Picture = inPicture
Case vbPicTypeIcon ' Convert Icon to Bitmap and assign to class
hIconBitmap = IconToBitmap(inPicture.handle, EvalCol(inTintCol))
SourceDC.hBitmap = hIconBitmap
Case Else ' Unsupported picture type
Set SourceDC = Nothing
End Select

If (Not (SourceDC Is Nothing)) Then
If (TintMemoryDC(SourceDC, FlipRGB(EvalCol(inTintCol)), inOpacity)) Then _
Set CreateSelectedImage = SourceDC.Picture

' Clean up object (must be explicitly terminated so cleanup occurs
' and Bitmap is de-selected (can't delete currently selected Bitmap))
Set SourceDC = Nothing
If (hIconBitmap) Then Call DeleteObject(hIconBitmap)
End If
End Function
'***

This one properly handles icons as input pictures, but always returns (wrapped) Bitmap's - If you want to return an icon instead
with the same mask then it's perfectly possible with a call to BitmapToIcon() from my IconHelper library, you will have to do a
little work with dealing with the mask though to get that working correctly (let me know if you need that.)
For the image creation itself I've written a simpler routine which simply tints the input object with a given colour and opacity
which from the sounds of it should suffice for your requirements:

'***
Public Function TintMemoryDC(ByRef inMemoryDC As CMemoryDC, _
ByVal inCol As Long, ByVal inAmt As Byte) As Boolean
Dim ImageData() As Long
Dim BMInf As BitmapInfo
Dim LoopX As Long, LoopY As Long, ScanOff As Long

If (inMemoryDC Is Nothing) Then Exit Function
If (inAmt = 0) Then ' Image remains the same
TintMemoryDC = True
Exit Function
End If

With BMInf.bmiHeader ' Set up Bitmap header
.biSize = Len(BMInf.bmiHeader)
.biWidth = inMemoryDC.Width
.biHeight = inMemoryDC.Height
.biBitCount = 32
.biPlanes = 1

' Allocate data buffer
ReDim ImageData(.biWidth * .biHeight - 1) As Long
End With

' Grab Bitmap data
If (GetDIBits(inMemoryDC.hDC, inMemoryDC.hBitmap, 0, _
BMInf.bmiHeader.biHeight, ImageData(0), BMInf, 0&)) Then

' Tint image data with input colour
For LoopY = 0 To BMInf.bmiHeader.biHeight - 1
For LoopX = 0 To BMInf.bmiHeader.biWidth - 1
ImageData(ScanOff + LoopX) = LinearCol( _
ImageData(ScanOff + LoopX), inCol, inAmt)
Next LoopX

ScanOff = ScanOff + BMInf.bmiHeader.biWidth
Next LoopY

' Push new data back into image and return call success
TintMemoryDC = SetDIBits(inMemoryDC.hDC, inMemoryDC.hBitmap, 0, _
BMInf.bmiHeader.biHeight, ImageData(0), BMInf, 0&) <> 0
End If
End Function
'***

There also appears to be a bug in Karl's code when creating the Picture() property as the temporary MemoryDC object he creates
doesn't properly interpret its TerminateKillsBitmap property and ends up Killing the Bitmap we're trying to return! (This is why
the images you create are valid but returning blank.)
To fix this bug, have a look at his Class_Terminate() method, and change this bizarre line:

'***
If (m_UserBmp And m_TermKills) Xor (m_UserBmp = False) Then
'***

to:

'***
If ((Not m_UserBmp) And m_TermKills) Then
'***

(I'll pass this on to Karl at some point so the version on the site can be fixed)
Personally I think it should have the ability to delete user Bitmap's too (it would be useful in this case rather than having to
manage the IconBitmap outside of the class), just when you set the hBitmap property of the class it turns off the
TerminateKillsbitmap property by default, but *shrug* - not my place to re-write his class!
Finally, to test this all you can use this code in your Picture box's click events:

'***
Private Sub Picture1_Click()
With Picture1
Set .Picture = CreateSelectedImage(.Picture, .BackColor, &H80)
End With
End Sub
'***

This just tints the current picture 50% with the background colour (the methods are system colour safe.)
All this could easily be encapsulated into a class, and if you want to drop the MemoryDC class (all you're using it for is the
StdPicture stuff really) then the OLEPicture library on my site does the same job of taking a GDI object and converting it to a
StdPicture but works with Icons/Cursors and Metafiles too.
Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/
Simon Woods
2004-12-24 14:24:22 UTC
Permalink
"Mike D Sutton" <***@mvps.org> wrote in message news:***@tk2msftngp13.phx.gbl...

<snip>

Excellent

Thanks and Merry Christmas

Simon
Mike D Sutton
2004-12-24 14:29:47 UTC
Permalink
Post by Simon Woods
Thanks and Merry Christmas
Same to you, and everyone else on the group!
Try not to spend too much of it infront of your machine(s) ;)

Mike


- Microsoft Visual Basic MVP -
E-Mail: ***@mvps.org
WWW: Http://EDais.mvps.org/

Loading...