Changing the colours in a bitmap.

Okay, what I have is:

A bitmap on a device context created by creating a blank device context and copying the bitmap to it using:
SelectObject hDC, oImage
where hDC is the device context handle and oImage is a StdPicture object.

This gives me a device context with the bitmap on it.

What I want is:

I have bitmaps in greyscale, I want to colour them in ONE colour, so that the image appears in differing shades of that colour, i.e. the original bitmap is a gradient from white to black, left to right.  The function I want will apply a colour to that grey bitmap so that the outcome will be that colour to black, left to right, in a gradient of that colour in between.

Somehow to modify the colours in that bitmap WITHOUT going through each individual pixel in the bitmap, the only possible solution I have come up with myself is to access the palette somehow and modify the 256 colours in the bitmap.  However, all the functions I have tried myself have either not worked or required a hPal (a handle to an existing palette), the stdPicture object has a hPal but it is always 0 because my device caps do not have/require/allow it.

The reason I want this without going through each pixel is because it will take too long, basically these images are required very quickly and may be very large so the quicker I can do it the better.

Furthermore, the images will never contain more than 256 colours if that helps.

Oh and another solution I have already tried is BitBlt-ing the bitmap (operation: SrcAnd) with a bitmap of a specific colour. With this solution if I put in 255 of red, green, blue or a combination of 255 with those colours (i.e. rgb(255,255,0) or rgb(255,0,255)), the bitmap comes out exactly how I want, but if I use rgb(129, 200, 20) or anything not uniform it does not work and the greyscale becomes fragmented into blocks of colour as if the whole bitmap had become 16 colours only.  Perhaps there is a solution here and I am doing something wrong.

Anyway, there is the problem, any solutions?
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

You can use GDI+ to draw a bitmap to your form using per-pixel alpha blending.  With the alpha channel set so that the highlights are more transparent than the shadows, it will appear that the image takes on the cast of the background color.

The following code opens a file and paints it to the form.  The alpha channel is set using a matrix operation with the following formula:

White = alpha 0 (transparent)
middle gray = alpha 128 (50% transparent)
black = alpha 255 (opaque)

The operation works on grayscale as well as full color bitmaps.

The alpha value is calculated like this:

ALPHA = 255 - ((RED * 0.299) + (GREEN * 0.587) + (BLUE * 0.114))

The following code requires gdiplus.tlb which can be downloaded here:

See also the MS GDI+ Docs:

'Form Code
Option Explicit

' Reference gdiplus.tlb

'Needed to close GDI+
Private token As Long

Private Sub Form_Load()
    Dim graphics As Long, bitmap As Long
    Dim stat As GpStatus
    Dim lHeight As Single, lWidth As Single
    Dim imgAttr As Long
    Dim clrMatrix As ColorMatrix
    Dim graMatrix As ColorMatrix
    Dim sFile As String

    Me.AutoRedraw = True
    ' Load the GDI+ Dll
    ' Very important!!
    Dim GpInput As GdiplusStartupInput
    GpInput.GdiplusVersion = 1
    If GdiplusStartup(token, GpInput) <> Ok Then
      MsgBox "Error loading GDI+!", vbCritical
      Unload Me
    End If
    ' Initialize the graphics class using Picture1.hDC
    stat = GdipCreateFromHDC(Me.hDC, graphics)
    'Load a sample bitmap
    sFile = "c:\imgp.bmp" '<<--- Modify to suit your file
    stat = GdipLoadImageFromFile(sFile, bitmap)
    'Get the height and width of the image
    If (stat = Ok) Then
        stat = GdipGetImageDimension(bitmap, lWidth, lHeight)
        Debug.Print "w = " & lWidth, "h = " & lHeight
        MsgBox "Cannot open file" & vbCrLf & sFile
    End If
    'Create storage for the image attributes struct
    stat = GdipCreateImageAttributes(imgAttr)

    'Setup the matrix for per/pixel alpha based on density
    With clrMatrix
    .m(0, 0) = 1: .m(3, 0) = -0.299
    .m(1, 1) = 1: .m(3, 1) = -0.587
    .m(2, 2) = 1: .m(3, 2) = -0.114
    .m(3, 3) = 1
    .m(4, 4) = 1
    End With
    'Setup the image attributes using the color matrix
    stat = GdipSetImageAttributesColorMatrix(imgAttr, ColorAdjustTypeBitmap, 1, clrMatrix, graMatrix, ColorMatrixFlagsDefault)
    'Draw the bitmap using the Image Attributes
    stat = GdipDrawImageRectRect(graphics, bitmap, 0, 0, lWidth, lHeight, 0, 0, lWidth, lHeight, UnitPixel, imgAttr)

    ' Cleanup
    Call GdipDeleteGraphics(graphics)
    Call GdipDisposeImage(bitmap)
    Call GdipDisposeImageAttributes(imgAttr)
End Sub

Private Sub Form_Unload(Cancel As Integer)
   ' Unload the GDI+ Dll
   Call GdiplusShutdown(token)
End Sub


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
MaintexAuthor Commented:
Thankyou for your quick response, one thing that I never thought about (it wasn't really necessary until you gave your solution) but is the GDI+ type lib compatible with windows 98?  The solution will need to be compatible with windows 98.

Unfortunately, I won't be able to respond this quickly for a few hours as it is 3:43am here and I'm off to bed.  I'll try out your solution on my machine tomorrow and get back to you then.

Oh, I have just checked the MSDN link you provided, it is compatible (well GDI+ is), I assume that the type lib will also be compatible (please correct me if you think I am wrong to make that assumption).

Well, as I say will try in the morning, too much to do to test it tonight, thanks again though.
MaintexAuthor Commented:
I haven't tried it yet but I was thinking about it last night and if what I think is true, it won't work.  The reason is the balck content of it, i.e. when you alpha blend black, it does not remain black, it becomes a very dark shade of the colour it is being placed upon.  However, if the GDI+ actually applies those values to each pixel (something I am trying to avoid so hopefully not) then it might work since anything multiplied by 0 remains 0 (black would be rgb(0,0,0)), but anyway, am going to test it now...
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

MaintexAuthor Commented:
Well didn't need to test it, I downloaded the type library and example from your link above, the black does not remain black when being alpha blended, is there any way to adjust the values shown in your example that would keep the black as black but the other shades of grey semi-transparent?  It is important that the solution keeps the blackness of black (must be rgb(0,0,0)).

One thing however about your solution I do not like is that dark areas will not remain dark, for example, using the GDI+ example I tried putting grey (rgb(128,128,128)) on to a red background (rgb(255,0,0)), then I alpha blended it to 128 (50% transparency) and the result was rgb(191,64,64) and not rgb(128,0,0) as I would have wanted.

Although it was a good idea, it would not work as I would like and it does not satisfy the requests made "the outcome will be that colour to BLACK, left to right, in a gradient of that colour in between".

Sorry about that but I hope you can come up with a new solution (or perhaps the same solution with a bit of tweaking)?
My sample above uses per-pixel alpha, meaning the alpha value varies per-pixel.  Black has alpha 255 (opaque) so it will still appear black.  White has alpha 0 (completely transparent).  Colors in between have varying degrees of alpha, depending on the value of the pixel.

The alpha value FOR EACH PIXEL is calculated like this:
ALPHA = 255 - ((RED * 0.299) + (GREEN * 0.587) + (BLUE * 0.114))

so black pixels have alpha = 255 - 0 + 0 + 0 = 255

The sample you tried may have used a *constant* alpha value applied on the entire picture, so black would appear as semi transparent.  This is not the case in my sample.

Try it.
MaintexAuthor Commented:
Thanks Erick37 I am trying your solution now, I hope it works ok but I have a small problem, in your example you use the form's hDC, then add a bitmap, and you get the bitmap handle (variable: bitmap), I need it to work as though the bitmap is already loaded and all I have is the hDC, no bitmap handle and no filename to load up.  I will try to solve this myself but if you could modify your solution to accomodate this then if I cannot do it I can use your solution.

Thanks for all your help, I am new to EE, first time of using it and I know that this problem is hard so I will add my 5 points that I got today to the value since you are having to work a bit more.
MaintexAuthor Commented:
Hmm, I tried using the CreateCompatibleBitmap api to get a bitmap handle from the hDC, but VB crashed out on GdipDrawImageRectRect, so I think maybe that won't work, have you a solution yet?  I'm gonna check for more api's to get the bitmap handle from the hDC...
MaintexAuthor Commented:
Hey, is it not possible to use the AlphaBlend function?  Just found it in MSDN.  You can set the individual rgb alpha values with that?  Maybe I'll try that in a min.
Replace this line
'stat = GdipLoadImageFromFile(sFile, bitmap)
With this to load a bitmap from the picture in picturebox Picture1  
stat = GdipCreateBitmapFromHBITMAP(Picture1.Picture.Handle, Picture1.Picture.hPal, bitmap)
You can try using the AlphaBlend API, but you will have to make special bitmaps for this function to work properly.  Personally, I have never used this function, and have no sample code.

Here are some good examples if you want to try using the AlphaBlend API:
MaintexAuthor Commented:
First comment: Like I said before I am only given hDC and the colour to apply to it, I could get the width and the height but I do not have anything else, and definitely no picture object.

Sorry but that won't work.

Second Comment: I am trying AlphaBlend right now, thanks for the tip, if it works I will give you the points since I did not think of alpha blending.
You can use SelectObject() to get the hBitmap from the hDC.

I'll check back later.
MaintexAuthor Commented:
How do you use SelectObject?  After you suggested that I thought maybe you could use the GetObject API (not VB's) the way you can get the BITMAP type from the hDC using GetObjectApi, but the handle is a long not a type, so I figured using the following:

Dim hBitmap As Long

hBitmap=GetObjectApi(hDC, Len(hBitmap), hBitmap)

No luck, hBitmap comes back as 0.

Have got some questions though, when I put bitmap through into GdipDrawImageRectRect as 0 it works, bitmap comes through, however, it is not semi-transparent and how exactly do I apply colour to it using your solution?

I tried alpha blend, it makes black look grey unfortunately and adds grey to the colours. If I have a colour I want to apply to my greyscale image and the colour is rgb(200,128, 50).  I want this colour applied to all pixels at once *hopefully*. This is what I want each pixel to result in:
Greyscale pixel           +             Colour              =           Result
rgb(128,128,128)       +         rgb(200,128,50)    =        rgb(100,64,25)              <- exactly half the original values
rgb(200,200,200)       +         rgb(200,128,50)    =        rgb(156,100,39)             <- colour*0.78  (since 200/255=0.78)

Hope this helps, but it looks to me like the only solution is to modify the palette of the bitmap, I could really do with modifying that as it would mean only loop of 256 times only.

So far, no good solutions...
Can you test the code I posted as-is using a bitmap on disk or in a a picturebox just to verify that this is what you want?
If indeed this is what you need to do, then we will proceed with modifying it to suit your application.
MaintexAuthor Commented:
Brilliant, looks good on paper so to speak, I tried it and firstly it looked so, so, because the grey of the greyscale image was affecting the result, however, after I tinkered about with those values, which are now:

With clrMatrix
    .m(0, 0) = 0: .m(3, 0) = -0.3 ' -0.299
    .m(1, 1) = 0: .m(3, 1) = -0.7 ' -0.587
    .m(2, 2) = 0: .m(3, 2) = -0.2 ' -0.114
    .m(3, 3) = 1
    .m(4, 4) = 1
End With

It looks pretty good, I modified the values on the left and changed them to 0 and hey, hey! It got rid of the greyness, I think those on the left determine the amount of source to use (i.e. 0) and the ones to the right determine how much of the destination to allow through. I have to modify the numbers on the right slightly because if I applied rgb(255,0,0) to the greyscale image, the only part that came out that colour was the pixels in the grey closest to rgb(255,255,255) (white) and I wanted the whole image to look a bit brighter.  I'm sure I'll tinker about with them some more later but it looks good.

Anyway , I figured out that m(3,3) is the amount of source to use kind of an overall alpha but m(4,4) what does that do?

I'm just going to put it into my project and see if it all works correctly (I hope that the GdipDrawImageRectRect function works with bitmap as 0).  Oh and can you explain to me how you get the bitmap handle (which is a long) out of the DC handle? You said using SelectObject but I don't really think that will work, perhaps I am wrong.

Anyway, definitely getting there - thanx alot for your help so far...
MaintexAuthor Commented:
A problem:

In your example you take the form's DC, load a bitmap and apply the bitmap to the DC alpha blending it at the same time.

I have a DC with the bitmap already on it and I need to get the bitmap from it and apply it to another coloured DC.

I know how to create a coloured DC, I know how to apply it to that coloured DC but I really need to get the bitmap from the original DC first.

Do you understand?
MaintexAuthor Commented:
I think I've come up with a way around it, since I do have to load the image from a file in the beginning anyway, but that is a part of the project I did not wish to touch, but looks like a revamp may be the only solution.  HOWEVER, the way my project works is to load the image from a file.  Modify the image and save the image in a control (i.e. a propbag)  I can do this using DC's and the like because I can copy a DC to a StdPicture object and save that object, then load and assign back to the DC.  That is easy.  Using GDI+ however seems to be a bit trickier, if you can show me a way of loading and saving the image to a propbag then this solution will work and I will accept this answer.

So can you either find a way of getting a bitmap handle from the DC for use in the GdipDrawImageRectRect function or can you show me a way of saving and loading using GDI+ to a property bag (in an ActiveX control).  Thanx
MaintexAuthor Commented:
Sorry, it is late and I am tired, that was supposed to say:

HOWEVER, the way my project works is to load the image from a file, modify the image and then save the image in a control (i.e. a propbag).  It then later has to be able to be re-read, manipulated and saved again in the propbag.
I'm assuming you are using a StdPicture object in your property bag.  If so then the StdPicture object can be used this way:

'Create a GDI+ bitmap from a StdPicture Object
Dim m_pic As StdPicture

'Read in the StdPicture property in the ReadProperties event, then:
stat = GdipCreateBitmapFromHBITMAP(m_pic.Handle, m_pic.hPal, bitmap)

Saving the picture can be done by setting the control's picture = control's image after the GdipDrawImageRectRect call.

e.g. if you are using a usercontrol:

Set UserControl.Picture = Usercontrol.Image
Then save the picture to the prop bag

=or of using a picturebox=

Set Picture1.Picture = Picture1.Image
Then save the picture to the prop bag.

I'll check back tomorrow.

MaintexAuthor Commented:
Okay, perhaps we are both confused, here is the spec for GdipDrawImageRectRect function:

Function GdipDrawImageRectRect(Graphics As Long, IMAGE AS LONG, Dstx As Single, Dsty As Single, Dstwidth As Single, Dstheight As Single, Srcx As Single, Srcy As Single, Srcwidth As Single, Srcheight As Single, SrcUnit As GpUnit, [ImageAttributes As Long], [callback As Long], [callbackData As Long]) As GpStatus

I have capitalised the second parameter because that is where you but the bitmap handle, which it is not at all, the function you gave me last: GdipCreateBitmapFromHBITMAP(m_pic.Handle, m_pic.hPal, bitmap) does not even give anything back, what does it actually do?  bitmap goes in as 0 and comes out as 0?  Either it is not ByRef or it is not working.  Anyway, the real point here is that GdipDrawImageRectRect requires an Image handle, which appears to be quite different from a Bitmap handle in GDI+, though I can't say why.  The reason I think this is because there are several functions including GdipLoadImageFromFile that have parameters using "Image As Long" just as GdipDrawImageRectRect does, but there are other functions using "Bitmap As Long", so I think they are different.

I tried your example above and it does not work, what it does do is show the image in the colour I am trying to apply because the bitmap handle is coming out as 0, going into GdipDrawImageRectRect as 0 and so the coloured DC that I am trying to apply the bitmap to is not having a bitmap applied to it so it is remaining a coloured DC. I'm gonna keep checking the GDI+ type library for some functions...
MaintexAuthor Commented:
Okay, I need this done more quickly and I need more help, if anyone can tell me how I can use GDI with this problem, i.e. load the bitmap from the DC then apply it to another coloured DC with semi-transparency (using GDI+) or if someone can tell me how to modify the palette of a bitmap then please help.

I have increased the point value to 500 now.
Hi Maintex

The Bitmap is a member of the Image class and can be used in this function.  I did try the code before posting it and it works fine for me.

I first loaded a picture like this:

Dim m_pic as stdPicture

Set m_pic = LoadPicture("c:\grayscale.bmp")
'Or if reading from the propbag:
Set m_pic = PropBag.ReadProperty("Pic", Nothing)


stat = GdipCreateBitmapFromHBITMAP(m_pic.Handle, m_pic.hPal, bitmap)

At this point stat should be 0 (Ok) and bitmap should be a valid pointer.  m_pic has to be a valid picture.
Again I have tested this with several bmp files and it does produce the GDI+ bitmap.  

I am using Windows XP SP2.
MaintexAuthor Commented:
Hopefully now it is 500...
MaintexAuthor Commented:
Okay, I will try it again using what you have suggested, I will get back to you in about an hour I'm being pestered here...
What are the DC's which you are using?  Are they the UserControl DC, a memory DC, an external DC?
How do you get the DC of the source/target?
MaintexAuthor Commented:
Hmm, okay, my project is a DLL, so no UserControl or Form DC's available straight away, it loads a stdpicture initially containing many greyscale images, which is converted into a DC using CreateCompatibleDC(0) and then SelectObject-ing the stdpicture onto it.  This gives me my ghGlobalDC which is then used in all other CreateCompatibleDC calls and CreateCompatibleBitmap since it is of the original image that has been loaded.  This global DC is split into smaller images by creating a new DC and BitBlt-ing a section of the global DC onto the new DC. I used to use 0 in both calls to CreateCompatibleDC and CreateCompatibleBitmap originally but it only gave me a monochrome DC, unless, I found, if you applied a bitmap from a stdpicture object (it must have a bitmap palette within it that is SelectObject-ed to the main DC giving it all the palette colours).  Anyway, that is where all my other DC's come from until I come to render the image.  When I render the image the function I have aptly named "Render" takes in a Recipient object which could be a form or usercontrol.  Then it extracts the DC, copies the DC I want to render to that DC using BitBlt and refreshes the recipient object also so that the user of my DLL has almost nothing to do.

So to simplify:
GlobalDC= CreateCompatibleDC(0) then CreateCompatibleBitmap(0) then copy StdPicture into GlobalDC using SelectObject
OtherDC=CreateCompatibleDC(GlobalDC) then CreateCompatibleBitmap(GlobalDC)

Hope this helps, if you have any further questions, let me know.

I am just about to retry your solution using GdipCreateBitmapFromHBITMAP...
MaintexAuthor Commented:
Hmm, it seems my DCToPicture function is not working correctly, it is the reason it hasn't worked all along.

Okay, I added a form to my DLL that contains a picturebox, when I BitBlt my DC to that picturebox, then extract the StdPicture from the Picture1.Image property and use that in the GdipCreateBitmapFromHBITMAP function it works!  But when I try to do it without using a picture box using OleCreatePictureIndirect it doesn't work, here is my DCToPicture function, perhaps you can shed some light, it has worked for me loads of times (hence the reason why I never checked it before) but it is not working now.  It returns a competely black picture of width and height that I specified:

Public Function DCToPicture(ByVal DC As Long, ByVal Width As Integer, ByVal Height As Integer) As StdPicture
Dim hDCMemory As Long, hBmp As Long, hBmpPrev As Long, R As Long
Dim hPal As Long, hPalPrev As Long, RasterCapsScrn As Long, HasPaletteScrn As Long
Dim PaletteSizeScrn As Long, LogPal As LOGPALETTE
Dim Pic As PicBmp, IPic As IPicture, IID_IDispatch As GUID

'Create a compatible device context
hDCMemory = CreateCompatibleDC(DC)
'Create a compatible bitmap
hBmp = CreateCompatibleBitmap(DC, Width, Height)
'Select the compatible bitmap into our compatible device context
hBmpPrev = SelectObject(hDCMemory, hBmp)

'Raster capabilities?
RasterCapsScrn = GetDeviceCaps(DC, RASTERCAPS) ' Raster
'Does our picture use a palette?
HasPaletteScrn = RasterCapsScrn And RC_PALETTE ' Palette
'What's the size of that palette?
PaletteSizeScrn = GetDeviceCaps(DC, SIZEPALETTE) ' Size of

If HasPaletteScrn And (PaletteSizeScrn = 256) Then
    'Set the palette version
    LogPal.palVersion = &H300
    'Number of palette entries
    LogPal.palNumEntries = 256
    'Retrieve the system palette entries
    R = GetSystemPaletteEntries(DC, 0, 256, LogPal.palPalEntry(0))
    'Create the palette
    hPal = CreatePalette(LogPal)
    'Select the palette
    hPalPrev = SelectPalette(hDCMemory, hPal, 0)
    'Realize the palette
    R = RealizePalette(hDCMemory)
End If

'Copy the source image to our compatible device context
R = BitBlt(hDCMemory, 0, 0, Width, Height, DC, 0, 0, vbSrcCopy)

'Restore the old bitmap
hBmp = SelectObject(hDCMemory, hBmpPrev)

If HasPaletteScrn And (PaletteSizeScrn = 256) Then
    'Select the palette
    hPal = SelectPalette(hDCMemory, hPalPrev, 0)
End If

'Delete our memory DC
R = ApiDeleteDC(hDCMemory)

'Fill GUID info
With IID_IDispatch
    .Data1 = &H20400
    .Data4(0) = &HC0
    .Data4(7) = &H46
End With

'Fill picture info
With Pic
    .Size = Len(Pic) ' Length of structure
    .Type = vbPicTypeBitmap ' Type of Picture (bitmap)
    .hBmp = hBmp ' Handle to bitmap
    .hPal = hPal ' Handle to palette (may be null)
End With

'Create the picture
R = OleCreatePictureIndirect(Pic, IID_IDispatch, 1, IPic)

Set DCToPicture = IPic
'Clear up
DeleteObject hBmp
DeleteObject hPal

End Function
Ok I'll take a look at the function in a bit.

Since you are using memory DC's you should be able to get the hBitmap out of the DC like this:

Dim pic As Picture
Dim hMemDC As Long
Dim hBmp As Long, hPrevBmp As Long

'Get the StdPicture object
Set pic = LoadPicture("c:\256color.gif")

'Create a memory DC
hMemDC = CreateCompatibleDC(0)

'Select the bitmap into the DC
hPrevBmp = SelectObject(hMemDC, pic.Handle)

'Now get the hBitmap out of the DC
hBmp = SelectObject(hMemDC, hPrevBmp)

'Create the GDI+ bitmap from the DC bitmap handle
stat = GdipCreateBitmapFromHBITMAP(hBmp, ByVal 0, bitmap)

'Select the bitmap back to the DC
hPrevBmp = SelectObject(hMemDC, hBmp)

Call DeleteDC(hMemDC)
MaintexAuthor Commented:
firstly I tried that and it didn't work (using a bitmap handle decoy to get the real bitmap handle), perhaps for the same reason as I am having now but secondly, I am wondering about my results before, I extracted the picture using a stdpicture object from the Picture1.Image property, I did this because when I extracted it from the Picture1.Picture property I got the same result as using my DCToPicture function, perhaps there is a clue there as to why it isn't working, but my expertise here is being stretched to the max.
MaintexAuthor Commented:
The Image property works when I BitBlt the DC with the bitmap on to the PictureBox, then extract the StdPicture object from the Image property to use in the call to GdipCreateBitmapFromHBITMAP call.  The Image property in the MSDN says it is a persistent graphic to be used in API calls.  Well makes sense, but how can I create a persistent bitmap without using a picture box?

Basically it works, I've seen it, it is super fast but it only works with a picture box control and I'd rather not have to use a hidden form with a picture box on it just to get a persistent graphic.  Perhaps a modification of the IID_IDispatch numbers, although to be honest I haven't the foggiest why they need to be what they are specified as.

Once again, thankyou Erick37 for all your help so far, if I could put more points on this question I would if only for your sticking with me!
MaintexAuthor Commented:
Okay, I am trying lots of stuff here, since I am only getting a source DC, width and height, here is what I am trying

Just to make sure that it is not my DC that is the problem I create another based on the temp form I have included in my DLL (named frmTest)

hTmpDC = CreateCompatibleDC(frmTest.hDC)
hBitmap = CreateCompatibleBitmap(frmTest.hDC, Width, Height)
SelectObject hTmpDC, hBitmap
'Get the image from the DC
BitBlt hTmpDC, 0, 0, Width, Height, DC, 0, 0, vbSrcCopy
DeleteObject hBitmap
Set oImage = DCToPicture(hTmpDC, Width, Height)
Set frmTest.Picture = oImage
ApiDeleteDC hTmpDC

This performs exactly the same as if I had done this:

Set oImage = DCToPicture(DC, Width, Height)
Set frmTest.Picture = oImage

And the results of both is nothing, the image appears to be unchanged, when I debug and break on the line after the last line, and try to show the form I get an area on the form with the width and height that shows the window underneath, as though a hole has appeared in the form, but if I run it without breakpoints, the form appears unchanged (no hole).
MaintexAuthor Commented:
I don't mean to sound picky but I am just playing about with the way that it works (with a form in the background) and it is very slow, I am processing a total of 8 images and it is taking 2.4 seconds, each one taking about 0.3 seconds *regardless of size*, for example I have one image that is only 21x24 pixels and it takes almost the same time as an image with 500x400 image.

Unfortunately, this is unacceptable, I have a 1.8Ghz AMD processing with over 768 Mb ram with Windows XP Pro (SP 0) which can run Unreal Tournament 2003 in 1024x768 res no problem.  And to shade 8 images with a single colour takes 2.4 seconds??  I dread to think what the speed of this would be on a Win98 machine!
MaintexAuthor Commented:
I have another solution that is a bit quicker (I think)

Dim iDC As Long
Dim iBitmap As Long
Dim bi24BitInfo As BITMAPINFO, bBytes() As Byte, Cnt As Long
With bi24BitInfo.bmiHeader
    .biBitCount = 24
    .biCompression = 0&
    .biPlanes = 1
    .biSize = Len(bi24BitInfo.bmiHeader)
    .biWidth = 100
    .biHeight = 100
End With
ReDim bBytes(1 To CLng(bi24BitInfo.bmiHeader.biWidth) * bi24BitInfo.bmiHeader.biHeight * 3) As Byte
iDC = CreateCompatibleDC(DC)
iBitmap = CreateDIBSection(iDC, bi24BitInfo, DIB_RGB_COLORS, ByVal 0&, ByVal 0&, ByVal 0&)
SelectObject iDC, iBitmap
BitBlt iDC, 0, 0, bi24BitInfo.bmiHeader.biWidth, bi24BitInfo.bmiHeader.biHeight, DC, 0, 0, vbSrcCopy
GetDIBits iDC, iBitmap, 0, bi24BitInfo.bmiHeader.biHeight, bBytes(1), bi24BitInfo, DIB_RGB_COLORS
For Cnt = LBound(bBytes) To UBound(bBytes)
    If bBytes(Cnt) < 50 Then
        bBytes(Cnt) = 0
        bBytes(Cnt) = bBytes(Cnt) - 50
    End If
Next Cnt
SetDIBitsToDevice DC, 0, 0, bi24BitInfo.bmiHeader.biWidth, bi24BitInfo.bmiHeader.biHeight, 0, 0, 0, bi24BitInfo.bmiHeader.biHeight, bBytes(1), bi24BitInfo, DIB_RGB_COLORS
ApiDeleteDC iDC
DeleteObject iBitmap

The only problem is that when I try to modify the .biWidth and .biHeight to anything other than 100 VB crashes, can you explain this?
MaintexAuthor Commented:
Well, I am ready to give up on this, been at it with the same problem for more than 2 days, the alpha blending idea was good but it required overheads such as GDI+ (which required a redistributable) and was very slow.  I eventually just tried the option of going through each individual pixel (using GetPixel and SetPixel) with results, it is easy, requires no extra files and is quite fast, not as fast as BitBlt-ing using SrcAnd, but then that only works with some colours (which I still do not understand?!).  But you have to draw the line somewhere, if you (Erick37) or anyone else has a viable solution, please let me know.

Although this question was not answered fully, I will ask the moderators if I can give Erick37 some points for trying, it was close and was a good solution - it did work, but it was too slow.  I will do this after a few days to see if anyone has any other ideas before then.

Anyway, thanx again for trying Erick37, but if you can still think of anything please let me know.
When you benchmark the speed of the operations, are you including the GdiplusStartup and GdiplusShutdown in each processing, or do you process all the images withing one GdiplusStartup / GdiplusShutdown cycle?  It probably takes some time for everything to be initialized and it is better to call the GdiplusStartup function only once per group of images if at all possible.

I cannot believe that pixel operations is faster than GDI+.

MaintexAuthor Commented:
No I am not including the startup and shutdown, in fact the only line I did benchmark (after I narrowed it down because it was soooo slow) was the line with this function: GdipDrawImageRectRect, and it took a little over 0.3 seconds on my machine per image.  In my DLL I put the startup and shutdown in the primary class in the Initialize and Termnate events so that wasn't included at all in the benchmarking.
Back to the drawing board!

Just a thought - did you test the speed from the IDE or the compiled DLL?  I believe the compiled DLL will be much faster.  Also, do you have any optimizations set?
MaintexAuthor Commented:
I do have the solution of pixel manipulation in case anyone else is interested:

### START CODE ###

Public Sub ColouriseDC(ByVal DC As Long, ByVal Width As Integer, ByVal Height As Integer, ByVal Colour As Long)
Dim iX As Integer
Dim iY As Integer
Dim iCol As Long
Dim iPct As Byte
Dim iColours(0 To 100) As Long
Dim iRed As Long
Dim iR As Integer
Dim iGreen As Long
Dim iG As Integer
Dim iBlue As Long
Dim iB As Integer
Dim iAdjustment As Integer

'Translate the colour
OleTranslateColor Colour, 0, Colour

'Check for Blue
If Colour >= 65536 Then
    'Get the Blue value
    iBlue = Int(Colour / 65536)
    'Remove the value from the colour
    Colour = Colour Mod (iBlue * 65536)
End If
'Check for Green
If Colour > 256 Then
    'Get the Green value
    iGreen = Int(Colour / 256)
    'Remove the value from the colour
    Colour = Colour Mod (iGreen * 256)
End If
'Check for Red
If Colour > 0 Then
    'Get the Red value
    iRed = Colour
End If

'Get the necessary colours for this operation (don't need to do 0 as it should never come up plus we want it to
'remain black)
For iPct = 1 To 100
    'Calculate the necessary adjustment to the RGB values based on the current percentage (2.56=256/100)
    iAdjustment = 2.56 * IIf(iPct > 50, 1, -1) * Abs(50 - iPct)
    'Add the adjustment to the colours
    iR = iRed + iAdjustment
    If iR > 256 Then iR = 256
    If iR < 0 Then iR = 0
    iG = iGreen + iAdjustment
    If iG > 256 Then iG = 256
    If iG < 0 Then iG = 0
    iB = iBlue + iAdjustment
    If iB > 256 Then iB = 256
    If iB < 0 Then iB = 0
    'Load the colours into an array
    iColours(iPct) = RGB(iR, iG, iB)

For iY = 0 To Height
    For iX = 0 To Width
        'Get the colour
        iCol = GetPixel(DC, iX, iY)
        'Get the value
        iCol = Int(iCol / 65536) 'This is the easiest way to calculate the greyness of the pixel, since grey is rgb(n,n,n) where 0<n<255, you really only need
            'one of these n values, since blue is the easiest to get (see above where we extract the rgb from the colour, it is the first one we extract)
        'Turn into a percentage
        iPct = (iCol / 314) * 100 'The 314 is because my greyscale middle (50%) is actually 157 and not 128.
        SetPixel DC, iX, iY, iColours(iPct)
End Sub

### END CODE ###

And this does work, however, I think it could be better.

For anyone that likes to cut and paste, this only works on greyscale images unless you know enough to modify it.  It works by first, getting the RGB of the colour we wish to apply, then making an array of percentages of that colour so that when we come to apply the colour to the bitmap it is as fast as possible, you really don't want to be calculating the shade for each pixel again and again. Then it goes through the loops of x and y and gets the greyness of each pixel. Then it calculates the percentage of the colour (or rather shade of the colour) to use, using the colours array we created before.

This is extremely fast with small images, but it can be quite slow with larger ones.  Thus I am still awaiting any improvements anyone can make to this or another solution here...
MaintexAuthor Commented:
To be both honest and logical Erick37, I needn't really check because if my version of going through x and y is faster than using GDI+ in the IDE, it WILL also be faster in the compiled version, thus why would I need to check this?

Do you see my point?

Thinking about it though, Microsoft products have surprised me in the past, perhaps there is some value to testing a compiled version to see if it would be faster, I mean API's in the IDE do require more work than a simple loop.  Okay, I've convinced myself, I'll get back to you...
MaintexAuthor Commented:
Well, I changed a few things around to make my x and y loops more optimised and this caused the function to be called quite a bit more often since I last benchmarked it, but here are the stats:

Total Images: 18
Largest Dimension: 85x56 (pixels)
Total Time (Compiled Version - All Optimisations): 4.71892187499998 seconds - yup thats almost 5 seconds!
Total Time (IDE): 4.79680859375003 seconds, only approx 0.08 difference.

I know if I were reading this I wouldn't believe this, and seriously, I keep checking to make sure, but every time, about 5 seconds with 18 images.
MaintexAuthor Commented:
Just to be fair, with the x and y loops in the IDE no optimisations and the same 18 images came out at:
Total Time: 0.07769531250050 seconds - a hell of a lot less.
I meant the GDI+ version.

On my old laptop (700 mHz P3) I can alphablend a 256x256 grayscale picture in 0.040 sec.
An 800x600 image takes 0.30 sec.
MaintexAuthor Commented:
Well, I don't know what the difference is but those 5 second results are using GDI+.  I do not understand why there is such a difference, I think perhaps there is something wrong with my machine, nothing, not even VB6, should take that long processing something, but as I said, all that time is being eaten by that one function - I narrowed it down to that one line.
Ok, now for something completely different.

I found an interesting sample at which uses DIBs with customizable palettes.  By modifying the palette of the grayscale image, you can quickly achieve a colorization or duotone effect.

The sample is here:

256 Colour DIBSections

In the cDIBSection256.cls module, I added this code:

'// In the General Declarations section:
Private Type ColorBytes
    red As Byte
    grn As Byte
    blu As Byte
End Type
Private Declare Sub RtlMoveMemory Lib "kernel32.dll" (Destination As Any, Source As Any, ByVal Length As Long)

Public Sub DuoTone(ByVal Color As OLE_COLOR)

    Dim tRGBOut(0 To 256) As RGBQUAD
    Dim lC As Long, i As Long
    Dim cb As ColorBytes
    'Color factors
    Dim dR As Double, dG As Double, dB As Double
    lC = GetDIBColorTable(m_hDC, 0, 256, tRGB(0))
    Debug.Assert (lC = 256)
    'copy the color to a type where we can easily get the color byte values
    Call RtlMoveMemory(cb, Color, 3)
    Debug.Print Hex(, Hex(cb.grn), Hex(cb.blu)
    'calculate the color scaling
    dR = / 255
    dB = cb.blu / 255
    dG = cb.grn / 255
    'Alter the grayscale palette
    For i = 0 To lC
        tRGBOut(i).rgbBlue = tRGB(i).rgbBlue * dB
        tRGBOut(i).rgbGreen = tRGB(i).rgbGreen * dG
        tRGBOut(i).rgbRed = tRGB(i).rgbRed * dR
    lC = SetDIBColorTable(m_hDC, 0, 256, tRGBOut(0))
    Debug.Assert (lC = 256)
End Sub

'And in the main form it is called like this:

m_cDibIn.DuoTone RGB(&HCC, &HAA, &HEE) '<<-- specify a tint color

'You also have to load up your own image in Form_Load()

sFile = "c:\256color.gif"

In any case, it seems to work just as well as the GDI+ alphablending, but perhaps faster??

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Fonts Typography

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.