Page 1 of 2

Create 'disabled' icons!

Posted: Sun Jun 28, 2009 1:18 pm
by srod
Whilst creating a new toolbar library, I was in need of a simple way of creating suitable looking 'disabled' icons for those instances when the user requested a default disabled image for a toolbar button etc. (Windows refuses to create 'em for us if we attach a disabled image-list to the toolbar in question! *astard! :) )

Long story suddenly cut short; the following code (tested on Vista, XP and Win 2000) takes an icon handle and, if successful, returns a new icon which I would claim is suitable for use as a disabled version of the original icon! :)

Reading through the code you will note that I apply a simple alpha-blending manually by trawling through the pixel data. This was done to ensure the code would run on all versions of Windows (an alternative was to use the AlphaBlend_() api function which is available only on Win 2000 and beyond). Unfortunately, PB's DrawAlphaImage() would not behave itself in this instance!

My thanks to Netmaestro for his work on gray-scaling images and to Kinglestat for testing on Win 2000.

Code: Select all

;/////////////////////////////////////////////////////////////////////////////////
;***Create disabled icon*** - version 1.0.0.
;*=========================
;*
;*©nxSoftWare (www.nxSoftware.com) 2009.
;*======================================
;*   Stephen Rodriguez (srod) with thanks to Netmaestro for his work on creating 8-bit gray-scale images
;*   and to Michael Vogel for a heads up on solving a 'dithering' problem.
;*   Created with Purebasic 4.31 for Windows.
;*
;*   Platforms:  Windows - all versions. (Tested on Vista, XP and Win 2000 only!)
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;*NOTES.
;* i)   Call the function CreateDisabledIcon() passing a handle to an existing icon and this function will, if successful,
;*      return the handle of a new icon suitable for use as a 'disabled' version of the original icon.
;*      Set the optional parameter 'backColor' to match the anticipated back color of the image/window to which the resulting icon is to
;*      be applied. This is needed to remedy a 'dithering' problems resulting from the icon's alpha/transparency channels.
;*      Set the optional 'alpha' parameter to a value between 1 and 255 to give the new icon a constant alpha-blended mix (XP onwards).
;*
;* ii)  The function works by creating a gray-scale version of the color image and then applying a 50% blending with a white background.
;*      This new image is then combined with the original icon mask to create a new icon.
;/////////////////////////////////////////////////////////////////////////////////


;/////////////////////////////////////////////////////////////////////////////////
;The following function takes an icon and creates another icon suitable for use as a disabled image etc.
;Returns a hIcon if successful.
Procedure.i CreateDisabledIcon(hIcon, backColor = -1, alpha=$7f)
  Protected newIcon, icInfo.ICONINFO, width, height, bmp.BITMAP, hdc
  Protected MaskBmp, ColorBmp, i, j, color
  Protected *px.LONG, bmpMask.BITMAP, *rgbQuad.RGBQUAD
  If hIcon
    If GetIconInfo_(hIcon, icInfo)
      GetObject_(icInfo\hbmMask, SizeOf(BITMAP), bmp) 
      width = bmp\bmWidth
      height = bmp\bmHeight
      ;The height needs halving if the mask bitmap contains the color one.
        If icInfo\hbmColor = 0
          height>>1
        EndIf
      ;Create images for the color and mask bitmaps. We cannot simply use the bitmaps pointed to by the ICONINFO structure as both images
      ;may be combined into the mask (if the icon is a black and white monochrome icon).
      ColorBmp = CreateImage(#PB_Any, width, height, 32)
      If ColorBmp
        MaskBmp = CreateImage(#PB_Any, width, height, 32)
        If MaskBmp
          hdc = StartDrawing(ImageOutput(ColorBmp))
          If hdc
            If backColor = -1
              backColor = GetSysColor_(#COLOR_3DFACE)
            EndIf
            Box(0, 0, width, height, backColor)
            backColor = RGB(Blue(backColor), Green(backColor), Red(backColor))
            DrawIconEx_(hdc, 0, 0, hIcon, width, height, 0, 0, #DI_NORMAL)
            StopDrawing()          
            hdc = StartDrawing(ImageOutput(MaskBmp))
            If hdc
              DrawIconEx_(hdc, 0, 0, hIcon, width, height, 0, 0, #DI_MASK)
              StopDrawing()     
              GetObject_(ImageID(ColorBmp), SizeOf(BITMAP), bmp) : *px = bmp\bmBits
              For i=0 To height-1 
                For j=0 To width-1
                  If *px\l&$ffffff <> backColor ;Gray-scale and blend 50% with white.
                    *rgbQuad = *px
                    color = (*px\l&$ff + (*px\l>>8)&$ff + (*px\l>>16)&$ff)/6 + $7f
                    *rgbQuad\rgbBlue = color
                    *rgbQuad\rgbGreen = color
                    *rgbQuad\rgbRed = color
                    *rgbQuad\rgbReserved = alpha
                  EndIf
                  *px + SizeOf(LONG)
                Next 
              Next 
              With icInfo
                \hbmMask = ImageID(MaskBmp)
                \hbmColor = ImageID(ColorBmp)
              EndWith
              newIcon = CreateIconIndirect_(icInfo)
            EndIf
          EndIf
          FreeImage(MaskBmp)
        EndIf
        FreeImage(ColorBmp)
      EndIf
    EndIf
  EndIf
  ProcedureReturn newIcon
EndProcedure
;/////////////////////////////////////////////////////////////////////////////////

Posted: Sun Jun 28, 2009 1:22 pm
by srod
Some test code. Edit the LoadImage() statement as appropriate.

Code: Select all

hIcon = LoadImage(1, "myIcon.ico") ;Specify your own icon file here.
hNewIcon = CreateDisabledIcon(hIcon)

If hnewIcon And OpenWindow(0, 0, 0, ImageWidth(1)*2+20, 140, "PureBasic - Image", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
  Repeat
    EventID = WaitWindowEvent()
    If EventID = #PB_Event_Repaint
      ;Display both images.
        hdc = StartDrawing(WindowOutput(0))
        If hdc
          DrawImage(ImageID(1), 0, 0)
          DrawImage(hNewIcon, ImageWidth(1)+20, 0)
          StopDrawing()    
        EndIf
    EndIf
  Until EventID = #PB_Event_CloseWindow
EndIf

Re: Create 'disabled' icons!

Posted: Sun Jun 28, 2009 1:22 pm
by PB
Thanks, srod. I didn't copy the code yet but I know I'll need it one day. :)

Posted: Sun Jun 28, 2009 1:46 pm
by srod
Just updated the code - it was using more images than it needed!

Posted: Sun Jun 28, 2009 5:32 pm
by Edwin Knoppert
Maybe the DrawState_() API may help as well?

Posted: Sun Jun 28, 2009 10:11 pm
by srod
That was the first thing I tried. :wink:

No the DrawState_() api proved very unsatisfactory for creating disabled images. The resulting images were very bland and well... crude. Unless of course I misused it! However, I did encounter a thread on the web in which the author talked about just how poor this api is for creating such images - especially on XP. The author proceeded to outline an algorithm for creating such images, but when I coded my own version of this algorithm, well yes it was good for many icons, but still gave poor results on more than a few! The code above has, thus far, proved to give the best results.

Posted: Mon Jun 29, 2009 9:50 am
by srod
Update : added a optional 'alpha' parameter to the main function which (for use with Win XP onwards) allows you to specify the level of alpha-transparency used when the resulting icon is actually rendered on screen etc. Specify a value between 1 and 255.

Posted: Mon Jun 29, 2009 11:00 am
by Michael Vogel
I've just tried to use...

Code: Select all

hIcon = LoadIcon_(#Null,#IDI_INFORMATION) ;Specify your own icon file here.
...which fails in this simple way - transforming it via an image, the greyed version of this icon does not look very fine also :?

Posted: Mon Jun 29, 2009 11:06 am
by srod
The grayed image looks fine here on Vista. Not so great on XP I must admit unless you give it an alpha value of 60 or so - then it looks better on XP.

Code: Select all

hNewIcon = CreateDisabledIcon(hIcon, 60)
Not sure what you mean by "...fails in this simple way..." I see no failure. And what do you mean by transforming it by an image?

You need to be a bit clearer if you are reporting a problem.

Posted: Mon Jun 29, 2009 6:40 pm
by Michael Vogel
srod wrote:You need to be a bit clearer if you are reporting a problem.
You're right, did my posting while travelling, sorry :?

I couldn't use (one of the first versions of) your code without converting the icon into an image, but the actual version does not make any problems :)

Anyhow it does not look perfect, as you can see on the following picture (the right icon in the top line), I have written some lines to get the image below, which looks a little bit better here on Windows XP:
Image

Here's the whole stuff to be compared easily (sorry for the long code):

Code: Select all

Procedure.i ImageToGrayscale(imageIn)

	;The following utility function converts the given PB image to 'gray-scale' via a temporary 8-bit image.
	;Returns #True if successful.
	;/////////////////////////////////////////////////////////////////////////////////

	Protected result, imageOut, color, width, height, hdc
	Protected Dim colors.l(255)
	width = ImageWidth(imageIn) : height = ImageHeight(imageIn)
	If width And height
		;Set up a 'grey' colortable.
		For color = 0 To 255
			colors(color) = color + color<<8 + color<<16
		Next
		;Create an 8-bit image.
		imageOut = CreateImage(#PB_Any, width, height, 8)
		If imageOut
			;Convert the original image to gray-scale by simply setting the new image's color table and then copying the first image.
			;Windows will match the original colors to thos in the color table by making some 'shortest path' calculations.
			hdc = StartDrawing(ImageOutput(imageOut))
			If hdc
				SetDIBColorTable_(hdc, 0, 256, @colors())
				DrawImage(ImageID(imageIN),0,0)
				StopDrawing()
				;Now copy to the original image.
				If StartDrawing(ImageOutput(imageIn))
					DrawImage(ImageID(imageOut), 0, 0)
					StopDrawing()
					result = #True
				EndIf
			EndIf
			FreeImage(imageOut)
		EndIf
	EndIf
	ProcedureReturn result
EndProcedure
Procedure.i CreateDisabledIcon(hIcon, alpha=0)

	;The following function takes an icon and creates another icon suitable for use as a disabled image etc.
	;Returns a hIcon if successful.
	;/////////////////////////////////////////////////////////////////////////////////

	Protected newIcon, icInfo.ICONINFO, width, height, bmp.BITMAP, hdc
	Protected MaskBmp, ColorBmp, i, j
	Protected *px.LONG, *pxMask.LONG, bmpMask.BITMAP, *rgbQuad.RGBQUAD
	If hIcon
		If GetIconInfo_(hIcon, icInfo)
			GetObject_(icInfo\hbmMask, SizeOf(BITMAP), bmp)
			width = bmp\bmWidth
			height = bmp\bmHeight
			;The height needs halving if the mask bitmap contains the color one.
			If icInfo\hbmColor = 0
				height>>1
			EndIf
			;Create images for the color and mask bitmaps. We cannot simply use the bitmaps pointed to by the ICONINFO structure as both images
			;may be combined into the mask (if the icon is a black and white monochrome icon).
			ColorBmp = CreateImage(#PB_Any, width, height, 32)
			If ColorBmp
				MaskBmp = CreateImage(#PB_Any, width, height, 32)
				If MaskBmp
					hdc = StartDrawing(ImageOutput(ColorBmp))
					If hdc
						DrawIconEx_(hdc, 0, 0, hIcon, width, height, 0, 0, #DI_NORMAL)
						StopDrawing()
						hdc = StartDrawing(ImageOutput(MaskBmp))
						If hdc
							DrawIconEx_(hdc, 0, 0, hIcon, width, height, 0, 0, #DI_MASK)
							StopDrawing()
							;Convert the color bitmap to a gray-scale image.
							If ImageToGrayscale(ColorBmp)
								GetObject_(ImageID(ColorBmp), SizeOf(BITMAP), bmp) : *px = bmp\bmBits
								GetObject_(ImageID(maskBmp), SizeOf(BITMAP), bmpMask) : *pxMask = bmpMask\bmBits
								For i=0 To height-1
									For j=0 To width-1
										If *pxMask\l = #White
											*px\l = 0
										Else ;Alpha-blend 50% white.
											*rgbQuad = *px
											*rgbQuad\rgbBlue = (*px\l&$ff)>>1+$7f
											*rgbQuad\rgbGreen = *rgbQuad\rgbBlue
											*rgbQuad\rgbRed = *rgbQuad\rgbBlue
											*rgbQuad\rgbReserved = alpha
										EndIf
										*px + SizeOf(LONG) : *pxMask + SizeOf(LONG)
									Next
								Next
								With icInfo
									\hbmMask = ImageID(MaskBmp)
									\hbmColor = ImageID(ColorBmp)
								EndWith
								newIcon = CreateIconIndirect_(icInfo)
							EndIf
						EndIf
					EndIf
					FreeImage(MaskBmp)
				EndIf
				FreeImage(ColorBmp)
			EndIf
		EndIf
	EndIf
	ProcedureReturn newIcon
EndProcedure

Procedure CopyImageToMemory(ImageNumber,Memory)

	Protected TemporaryDC.L,TemporaryBitmap.BITMAP,TemporaryBitmapInfo.BITMAPINFO

	TemporaryDC=CreateDC_("DISPLAY",#Null,#Null,#Null)

	GetObject_(ImageID(ImageNumber),SizeOf(BITMAP),TemporaryBitmap.BITMAP)

	TemporaryBitmapInfo\bmiHeader\biSize       =SizeOf(BITMAPINFOHEADER)
	TemporaryBitmapInfo\bmiHeader\biWidth      =TemporaryBitmap\bmWidth
	TemporaryBitmapInfo\bmiHeader\biHeight     =-TemporaryBitmap\bmHeight
	TemporaryBitmapInfo\bmiHeader\biPlanes     =1
	TemporaryBitmapInfo\bmiHeader\biBitCount   =32
	TemporaryBitmapInfo\bmiHeader\biCompression=#BI_RGB

	GetDIBits_(TemporaryDC,ImageID(ImageNumber),0,TemporaryBitmap\bmHeight,Memory,TemporaryBitmapInfo,#DIB_RGB_COLORS)

	DeleteDC_(TemporaryDC)

EndProcedure
Procedure CopyMemoryToImage(Memory,ImageNumber)

	Protected TemporaryDC.L,TemporaryBitmap.BITMAP,TemporaryBitmapInfo.BITMAPINFO

	TemporaryDC=CreateDC_("DISPLAY",#Null,#Null,#Null)

	GetObject_(ImageID(ImageNumber),SizeOf(BITMAP),TemporaryBitmap.BITMAP)

	TemporaryBitmapInfo\bmiHeader\biSize       =SizeOf(BITMAPINFOHEADER)
	TemporaryBitmapInfo\bmiHeader\biWidth      =TemporaryBitmap\bmWidth
	TemporaryBitmapInfo\bmiHeader\biHeight     =-TemporaryBitmap\bmHeight
	TemporaryBitmapInfo\bmiHeader\biPlanes     =1
	TemporaryBitmapInfo\bmiHeader\biBitCount   =32
	TemporaryBitmapInfo\bmiHeader\biCompression=#BI_RGB

	SetDIBits_(TemporaryDC,ImageID(ImageNumber),0,TemporaryBitmap\bmHeight,Memory,TemporaryBitmapInfo,#DIB_RGB_COLORS)

	DeleteDC_(TemporaryDC)

EndProcedure
Procedure ImageDisabler(ImageNumber,nullcolor)

	Protected MemorySize,*Memory
	Protected Counter,Color

	MemorySize=(ImageWidth(ImageNumber) * ImageHeight(ImageNumber) << 2)
	*Memory=AllocateMemory(MemorySize)

	nullcolor=(Red(nullcolor) + Green(nullcolor) + Blue(nullcolor)) / 3

	CopyImageToMemory(ImageNumber,*Memory)
	For Counter=0 To MemorySize - 1 Step 4
		Color=PeekL(*Memory + Counter)
		Color=(Red(Color) + Green(Color) + Blue(Color)) / 3
		Color-nullcolor
		If Color<0
			Color/3
		EndIf
		Color+nullcolor
		PokeL(*Memory + Counter,RGB(Color,Color,Color))
	Next

	CopyMemoryToImage(*Memory,ImageNumber)
	FreeMemory(*Memory)

EndProcedure
Procedure PrepareIcon(Icon,x,y)
	Protected image
	Protected nullcolor=GetSysColor_(#COLOR_3DFACE)
	image=CreateImage(#PB_Any,x,y)
	StartDrawing(ImageOutput(image))
	Box(0,0,32,32,nullcolor)
	DrawImage(icon,0,0)
	StopDrawing()
	ImageDisabler(image,nullcolor)
	ProcedureReturn ImageID(image)
EndProcedure

hIcon = LoadIcon_(#Null,#IDI_INFORMATION) ;Specify your own icon file here.
hNewIcon = CreateDisabledIcon(hIcon,0)
TestIcon=PrepareIcon(hIcon,32,32)

If hnewIcon And OpenWindow(0, 0, 0, 80, 140, "PureBasic - Image", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
	Repeat
		EventID = WaitWindowEvent()
		If EventID = #PB_Event_Repaint
			;Display both images.
			hdc = StartDrawing(WindowOutput(0))
			If hdc
				DrawImage(hicon,0,0)
				DrawImage(hNewIcon,40,0)
				DrawImage(TestIcon,40,40)
				StopDrawing()
			EndIf
		EndIf
	Until EventID = #PB_Event_CloseWindow
EndIf

Posted: Mon Jun 29, 2009 8:00 pm
by srod
Ah yes I tried that kind of algorithm, but decided that it produced relatively poor results with certain icons. However, I think now that I may have made an error with my coding - let me try again; it is only a minor modification to my original code.

*Edit : yes, what is causing the problem with my original code is the mask when re-creating the icon. Same thing is happening when I modify the code to reflect the algorithm yours uses. The image gets created fine (a duplicate of yours), but it is the resulting icon. Will have to give this a little more thought! :)

Posted: Mon Jun 29, 2009 11:45 pm
by srod
Well this took some figuring out and some very fine examination of various icons! :)

@Michael, thanks for the heads up there. I could not work out why, even using the same basic procedure as your code, the resulting icons could be quite poor - especially the system icon you tested with? Turns out to be a kind of 'dithering' effect due to the icon's transparency and the per-pixel alpha values which XP and Vista support with icons. Basically, these 'offending' icons would typically have quite a few 'nearly transparent' pixels running very close to the fully transparent ones which, after rendering to a background image, would take on a color very close to the background one. Hence the dithering after the subsequent steps were taken to 'gray scale' the image etc.

The solution of course (as implied by your code) is to choose a back-color (preferably a shade of grey) close to that upon which the new icons would typically be rendered - usually the #COLOR_3DFACE system color! This means the dithering will be mimimized and appear almost invisible. I have tested with mine and your code and have confirmed that both suffer with this if a poor back-color is chosen. The alternative of stripping out the color XOR image from the icon was no good because I would then lose the alpha settings which would lead to far more dithering! :)

It just seems strange to have to pick a back-color when dealing with icons! :)

Anyhow, I have updated my code in the first post which, if you have a few spare moments, you are free to test. I am using essentially the same algorithm as you presented just applied to icons and with some alpha-blending. In the meantime, thanks again! 8)

Posted: Tue Jun 30, 2009 8:52 am
by Michael Vogel
srod wrote:Anyhow, I have updated my code in the first post which, if you have a few spare moments, you are free to test.
Hm,
checked your new code (still version 1 :lol:) but on my old Windows XP the still looks like a zombie :lol:

I did also made some attempts now to improve my code, it would now work, when the desault background is not gray :?

But because of some myterious color differences, the result is also far from perfect (but smooth :lol: ) -- the whole thing is like finding the great alchemy formula :shock:

Good luck,
Michael

Code: Select all

Procedure.l ColorDisabler(color,grey)

	If color>grey
		ProcedureReturn (color-grey)/2+grey
	Else
		ProcedureReturn grey-(grey-color)/3
	EndIf

EndProcedure
Procedure.l Distance(c1,c2)
	ProcedureReturn Abs(Red(c1)-Red(c2))+Abs(Green(c1)-Green(c2))+Abs(Blue(c1)-Blue(c2))
EndProcedure
Procedure ImageDisabler(ImageNumber,nullcolor)

	Protected MemorySize,*Memory
	Protected Counter,Color

	MemorySize=(ImageWidth(ImageNumber) * ImageHeight(ImageNumber) << 2)
	*Memory=AllocateMemory(MemorySize)

	;	nullcolor=(Red(nullcolor) + Green(nullcolor) + Blue(nullcolor)) / 3

	CopyImageToMemory(ImageNumber,*Memory)
	For Counter=0 To MemorySize - 1 Step 4
		Color=PeekL(*Memory + Counter)
		Color=RGB(ColorDisabler(Red(Color),Red(nullcolor)),ColorDisabler(Green(Color),Green(nullcolor)),ColorDisabler(Blue(Color),Blue(nullcolor)))
		If Distance(Color,nullcolor)>10
			;Color=#White
			Color=((Red(Color)+Green(Color)+Blue(Color))/3)*$10101
		Else
			Color=nullcolor
		EndIf
		PokeL(*Memory+Counter,Color)
	Next

	CopyMemoryToImage(*Memory,ImageNumber)
	FreeMemory(*Memory)

EndProcedure
Procedure PrepareIcon(Icon,x,y)

	Protected image
	Protected nullcolor=GetSysColor_(#COLOR_3DFACE)

	image=CreateImage(#PB_Any,x,y)
	StartDrawing(ImageOutput(image))
	Box(0,0,32,32,nullcolor)
	DrawImage(icon,0,0)
	StopDrawing()
	ImageDisabler(image,nullcolor)
	ProcedureReturn ImageID(image)
EndProcedure

Posted: Tue Jun 30, 2009 10:28 am
by srod
Strange; my new version looks fine here on XP (sp 3). Much better than my first attempt. Very smooth.

Which icons did you test with?

Posted: Tue Jun 30, 2009 7:45 pm
by Michael Vogel
srod wrote:Strange; my new version looks fine here on XP (sp 3). Much better than my first attempt. Very smooth.

Which icons did you test with?
I still have kept the #IDI_INFORMATION for simple comparing and it still looks exactly the same as the picture I posted above ?!

I rechecked the whole code puzzle (your procedures from the first post, my new method and the modified example), but it seems that I've put all pieces together in a correctly way :)

And, I'm also using XP/SP3 here, the skin (Bandit) is hopefully not the reason for this :roll:

So I do not have an idea what I've done wrong here, maybe someone else should give it also a try to get a feeling what (else) can happen :lol:

Michael