Monday, December 28, 2009

Icons in .NET. Why is so difficult?

Once more I find myself needing to use icons in .NET.

A quick search in Google shows a plethora of questions and workarounds to use icons in .NET. From simple calls to Win32 API to a whole library written from scratch.

Let's say you have the following code:
Dim ico As New Icon("file.ico", 16, 16)
That would load an 16x16 icon from the specified "file.ico", and if you use this icon, chances are that it will be OK for the most applications.

And even if you have
Dim ico As New Icon("file.ico", 32, 32)
it will pretty much be acceptable for almost any situation.

Now when you need to use the (not so) new Vista icons... Oh boy! you're in for a ride! Because
Dim ico As New Icon("file.ico", 256, 256)
Gives you a 32x32 icon! And not a 256x256 icon as per documentation.

What the heck is going on here?

With nothing better to do right now I decided to see if I could find why exactly .NET framework doesn't handle well icons when you need to load them with different sizes.

Having my faithful Reflector handy I dove down the Icon class constructor:
public Icon(string fileName, int width, int height) : this()
{
using (FileStream stream = new
FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read))
{
this.iconData = new byte[(int) stream.Length];
stream.Read(this.iconData, 0, this.iconData.Length);
}
this.Initialize(width, height);
}
Here the framework simply loads the whole file in memory and calls an initialization function, where the magic happens.
private unsafe void Initialize(int width, int height)
{
if ((this.iconData == null) || (this.handle != IntPtr.Zero))
{
throw new InvalidOperationException
(SR.GetString("IllegalState",
new object[] { base.GetType().Name }));
}
if (this.iconData.Length <
Marshal.SizeOf(typeof(SafeNativeMethods.ICONDIR)))
{
throw new ArgumentException(SR.GetString
("InvalidPictureType",
new object[] { "picture", "Icon" }));
}
if (width == 0)
{
width = UnsafeNativeMethods.GetSystemMetrics(11);
}
if (height == 0)
{
height = UnsafeNativeMethods.GetSystemMetrics(12);
}
if (bitDepth == 0)
{
IntPtr dC = UnsafeNativeMethods.GetDC(NativeMethods.NullHandleRef);
bitDepth = UnsafeNativeMethods.GetDeviceCaps(new HandleRef(null, dC), 12);
bitDepth *= UnsafeNativeMethods.GetDeviceCaps(new HandleRef(null, dC), 14);
UnsafeNativeMethods.ReleaseDC(NativeMethods.NullHandleRef, new HandleRef(null, dC));
if (bitDepth == 8)
{
bitDepth = 4;
}
}
fixed (byte* numRef = this.iconData)
{
short @short = this.GetShort(numRef);
short num2 = this.GetShort(numRef + 2);
short num3 = this.GetShort(numRef + 4);
if (((@short != 0) || (num2 != 1)) || (num3 == 0))
{
throw new ArgumentException(SR.GetString
("InvalidPictureType",
new object[] { "picture", "Icon" }));
}
byte bWidth = 0;
byte bHeight = 0;
int length = 0;
byte* numPtr = numRef + 6;
int num7 = Marshal.SizeOf(typeof(SafeNativeMethods.ICONDIRENTRY));
if ((num7 * num3) >= this.iconData.Length)
{
throw new ArgumentException(SR.GetString
("InvalidPictureType",
new object[] { "picture", "Icon" }));
}
for (int i = 0; i < num3; i++)
{
SafeNativeMethods.ICONDIRENTRY icondirentry;
icondirentry.bWidth = numPtr[0];
icondirentry.bHeight = numPtr[1];
icondirentry.bColorCount = numPtr[2];
icondirentry.bReserved = numPtr[3];
icondirentry.wPlanes = this.GetShort(numPtr + 4);
icondirentry.wBitCount = this.GetShort(numPtr + 6);
icondirentry.dwBytesInRes = this.GetInt(numPtr + 8);
icondirentry.dwImageOffset = this.GetInt(numPtr + 12);
bool flag = false;
int wBitCount = 0;
if (icondirentry.bColorCount != 0)
{
wBitCount = 4;
if (icondirentry.bColorCount < 0x10)
{
wBitCount = 1;
}
}
else
{
wBitCount = icondirentry.wBitCount;
}
if (wBitCount == 0)
{
wBitCount = 8;
}
if (length == 0)
{
flag = true;
}
else
{
int num10 = Math.Abs((int) (bWidth - width)) +
Math.Abs((int) (bHeight - height));
int num11 = Math.Abs((int) (icondirentry.bWidth - width)) +
Math.Abs((int) (icondirentry.bHeight - height));
if ((num11 < num10) || ((num11 == num10) &&
(((wBitCount <= bitDepth) && (wBitCount > this.bestBitDepth)) ||
((this.bestBitDepth > bitDepth) &&
(wBitCount < this.bestBitDepth)))))
{
flag = true;
}
}
if (flag)
{
bWidth = icondirentry.bWidth;
bHeight = icondirentry.bHeight;
this.bestImageOffset = icondirentry.dwImageOffset;
length = icondirentry.dwBytesInRes;
this.bestBitDepth = wBitCount;
}
numPtr += num7;
}
if ((this.bestImageOffset < 0) ||
((this.bestImageOffset + length) > this.iconData.Length))
{
throw new ArgumentException(SR.GetString
("InvalidPictureType",
new object[] { "picture", "Icon" }));
}
if ((this.bestImageOffset % IntPtr.Size) != 0)
{
byte[] destinationArray = new byte[length];
Array.Copy(this.iconData, this.bestImageOffset, destinationArray, 0, length);
fixed (byte* numRef2 = destinationArray)
{
this.handle = SafeNativeMethods.
CreateIconFromResourceEx(numRef2, length, true, 0x30000, 0, 0, 0);
}
}
else
{
this.handle = SafeNativeMethods.
CreateIconFromResourceEx(numRef + this.bestImageOffset,
length, true, 0x30000, 0, 0, 0);
}
if (this.handle == IntPtr.Zero)
{
throw new Win32Exception();
}
}
}
Grazing over the above code, it simply reads the array of bytes that the file was transformed into, following the icon file format specification reading through every icon format trying to match the width and size requested (or the best next thing).

And then at the very end it calls CreateIconFromResourceEx to create the icon handle... And BANG! There goes the carefully found measured icon down the drain.

Now, take a look at the call of the function:
this.handle = SafeNativeMethods.CreateIconFromResourceEx(numRef2, length, true, 0x30000, 0, 0, 0);

// OR

this.handle = SafeNativeMethods.CreateIconFromResourceEx(numRef + this.bestImageOffset, length, true, 0x30000, 0, 0, 0);
You see, CreateIconFromResourceEx is simply a proxy call to the native function in the user32.dll, and according to the documentation the function recieves the following parameters:
pbIconBits
[in] Pointer to a buffer containing the icon or cursor resource bits. These bits are typically loaded by calls to the LookupIconIdFromDirectoryEx and LoadResource functions.

cbIconBits
[in] Specifies the size, in bytes, of the set of bits pointed to by the pbIconBits parameter.

fIcon
[in] Specifies whether an icon or a cursor is to be created. If this parameter is TRUE, an icon is to be created. If it is FALSE, a cursor is to be created.

dwVersion

[in] Specifies the version number of the icon or cursor format for the resource bits pointed to by the pbIconBits parameter. This parameter can be 0x00030000.

cxDesired
[in] Specifies the desired width, in pixels, of the icon or cursor. If this parameter is zero, the function uses the SM_CXICON or SM_CXCURSOR system metric value to set the width.

cyDesired
[in] Specifies the desired height, in pixels, of the icon or cursor. If this parameter is zero, the function uses the SM_CYICON or SM_CYCURSOR system metric value to set the height.

uFlags
[in] Specifies a combination of the following values:
LR_DEFAULTCOLOR
Uses the default color format.

LR_DEFAULTSIZE
Uses the width or height specified by the system metric values for cursors or icons, if the cxDesired or cyDesired values are set to zero. If this flag is not specified and cxDesired and cyDesired are set to zero, the function uses the actual resource size. If the resource contains multiple images, the function uses the size of the first image.

LR_MONOCHROME
Creates a monochrome icon or cursor.

LR_SHARED
Shares the icon or cursor handle if the icon or cursor is created multiple times. If LR_SHARED is not set, a second call to CreateIconFromResourceEx for the same resource will create the icon or cursor again and return a different handle.
When you use this flag, the system will destroy the resource when it is no longer needed.
Do not use LR_SHARED for icons or cursors that have non-standard sizes, that may change after loading, or that are loaded from a file.
When loading a system icon or cursor, you must use LR_SHARED or the function will fail to load the resource.
Windows 95/98/Me: The function finds the first image with the requested resource name in the cache, regardless of the size requested.
If we check the parameters passed by the Initialize method we'll see that the cxDesired and cyDesired parameters are ZERO.

And once more, according to the documentation, if any of these parameters are ZERO, the API will call GetSystemMetrics in order to discover the values of SM_CXICON and SM_CYICON, respectively, in which case the Windows happily answer as (usually) 32.

And if the guys at Redmond manage to change the above lines to:
this.handle = SafeNativeMethods.CreateIconFromResourceEx(numRef2, length, true, 0x30000, width, height, 0);

// AND

this.handle = SafeNativeMethods.CreateIconFromResourceEx(numRef + this.bestImageOffset, length, true, 0x30000, width, height, 0);
They will fix the dam problem and we will not need to resort to workarounds (and the like) anymore.

Friday, December 25, 2009

Happy Holidays

Wednesday, December 23, 2009

Installing Microsoft Fingerprint Reader on Windows 7

According to Microsoft, its Fingerprint Reader will not run on Windows 7.

However according to several sources its possible to have this device running on Windows 7. A guy by the name "blogfeld" posted the steps to install the drivers on Windows 7. Although it works, there's too much that may go wrong, after all they are 10 setps, with three steps involving editing the installer file manually.

I thought poor average user... no luck there.

Anyway, I took the blogfeld's steps and edited the installer file even further in order to do every change properly, and if necessary.

The resulting file is posted below:

Installer for Microsoft Fingerprint Reader for Windows 7.

P.S.: If you have Windows 7 64 bits then, sorry, you're out of luck.

Thursday, October 22, 2009

Firefox's future features

Firefox's future features: 3.6, 3.7, and 4.0 Deep Tech - CNET News:
"Tabs behavior will get a significant change that could throw some people off. New tabs generally will appear immediately to the right of the active tab when opened from a link, rather than at the far right of the tab strip."

Yep... just like IE8.

Will it have nice grouping colors too?

Hehe.. I couldn't help myself but laught at this.

Saturday, October 17, 2009

Something I found interesting...

OK, I know I haven't post in a while. Things have been erract with me these couple of weeks, to say the least. Anyway, I was browsing over Wikipedia, a small hobby of mine, and found a little curiosity, that probably is somewhat old news, but I just found out.

Take a look on the videos below.

Fisrt the teaser trailer for Resident Evil: Apocalypse.



Then, the TV ad for a certain Avon product:

Monday, August 10, 2009

Tiger

Surfing the net today I came up with this video that shows an apparent glitch on the Tiger Woods PGA Tour '08: the "Jesus shot"



Then on the related videos, I found the EA response for it...



Simply hilarious...