[vb6] Overcoming LoadResPicture (ICON/CURSOR) Limitations in IDE
I doubt many are unaware, but some probably are. So let me start by stating the limitations of using LoadResPicture with the vbResIcon and vbResCursor options while in the IDE.
1. Cannot specify the size of the loaded icon. Always loaded as default (32x32 in 100% DPI)
2. Icons/cursors having color depths other than the screen will not be used. Assumes more than one bit depth is provided in the icon resource.
3. Cursors are always loaded/scaled to a default size
4. Cursors are loaded black and white in IDE, but color when compiled.
By using a couple of APIs to find and load a specified icon/cursor, we can force VB to use the size we want. Problem numbers 1, 2 and 4 above are overcome by this method. Problem number 3 remains but, typically, is expected behavior. When compiled, LoadImage() API can be used to retrieve desired icon sizes as an HICON which can be wrapped in a stdPicture. When compiled, that API solves problem number 1. Problem number 4 doesn't exist once compiled.
Here are a couple scenarios regarding icons.
Scenario 1: You have an icon file containing only a 128x128 image. If you set that icon, in design, to a VB picture property, you get a 128x128 icon. But if you add it to your resource file then use something like Me.Picture = LoadResPicture(##, vbResIcon), what you get is a scaled-down 32x32 icon.
Scenario 2: You have an icon file with multiple images of varying sizes and color/bit depths. If you set that icon to a VB picture property, you get the first image in the file and cannot choose a size or color depth. If you add the icon file to a resource file, LoadResPictre returns a 32x32 icon if it exists, else function selects an icon and scales it to 32x32.
By using a couple of APIs, we can ask Windows to select the icon size we want when the icon file, added to a resource file, contains multiple sized images. Even if we wanted a size not in the file, we can ask that it be scaled for us. This method works whether compiled or not.
Caveats...
1. Though the code will load a 32bpp icon (XP+) and those containing PNG compression (Vista+), VB still may not display it correctly. These types of icons may fail to be loaded via the resource editor. Adding these types of icons may require alternate tools like the Microsoft RC.exe application. Icons larger than 255x255 may also require such tools.
2. If you have an icon resource that contains multiple images of varying sizes and depths, it is assumed that for each size, you have the same number of icons per depth. For example, if having icon sizes of 16, 32, 48 and depths of 8 and 32 bits, it is expected you have 6 icons, an 8 and 32 bit version for each size. This assumption is a requirement if you use the code's option to select an icon of a specific color depth. After all, this is your project and you dictate what gets added to the resource file and what doesn't. Therefore, you shouldn't be asking for a color depth that doesn't exist.
3. This method is capable of properly finding/loading PNG-encoded icons (Vista+), but the VB Resource Editor breaks that. The Resource Editor doesn't know how to deal with these (editor code is too old and not updated). It assumes the bitcount and planes attributes of the image is at a fixed offset from where the icon data starts. That assumption fails for PNG encoding as those offsets point to the PNG IHDR chunk. Therefore, the bitcount is always reported 21060 and planes as 18505 which is part of that chunk. No API that looks at this invalid icon directory structure is going to be able to process it correctly -- it is in-effect corrupted. Use another tool to add PNG-encoded icons to the resource file. However, if the PNG image is the only one in the resource, it will be selected.
Above being said, even if the directory structure isn't corrupted, if you have icons of sizes 256x256 and 512x512, they are likely PNG compressed. However, when asking for one of those, the 1st one in the icon file will be used and scaled as needed. This is because the APIs being used cannot distinguish between icon sizes larger than 255x255. Reason is that width/height are each defined by a single byte. Max value of a single byte is 255. The value of zero basically means: larger than 255. This can be worked around and will consider it if wanted. The Vista API mentioned below may not have that problem but not tested it and it is not compatible with uncompiled projects. I'd suspect that API checks the icon's actual size vs. relying on the size provided in the icon directory structure -- that's the way I'd do it
FYI... LoadIconWithScaleDown is a newer API function that requires at least Vista and also cannot be used unless project is compiled. That function allows you to do what this code does but, again, not while uncompiled. That API also works with icon files on disc.
Post# 2 has the code. The zip below has a sample project with methods included.
The included resource file has one icon with just a 128x128 image, another icon with 9 images ranging from 16x16 to 128x128 and a cursor with 2 images: 32x32, 48x48. All icons and cursors are 8 bit.
Last edited by LaVolpe; Sep 30th, 2017 at 02:39 PM.
Reason: expanded comments
Insomnia is just a byproduct of, "It can't be done"
Re: [vb6] Overcoming LoadResPicture (ICON/CURSOR) Limitations in IDE
Here's the method used. Sample project is in above post.
Note: This may have minor enhancements over the method in the sample project.
Code:
Option Explicit
Private Declare Function CreateIconFromResourceEx Lib "user32.dll" (presbits As Any, ByVal dwResSize As Long, ByVal fIcon As Long, ByVal dwVer As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, ByVal Flags As Long) As Long
Private Declare Function LookupIconIdFromDirectoryEx Lib "user32.dll" (ByRef presbits As Any, ByVal fIcon As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, ByVal Flags As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "OleAut32.dll" (lpPictDesc As Any, riid As Any, ByVal fPictureOwnsHandle As Long, ipic As IPicture) As Long
Private Declare Function GetSystemMetrics Lib "user32.dll" (ByVal nIndex As Long) As Long
Private Function LoadResIconEx(ResName As Variant, _
Optional ByVal DesiredCx As Long, _
Optional ByVal DesiredCy As Long, _
Optional ByVal DesiredBitCount As Long, _
Optional ByVal IsCursor As Boolean = False) As StdPicture
' ResName: String or Integer resource name in the ICONS or CURSORS resource file section
' example: 101, "MyIcon"
' DesiredCx:
' Pass 0 for default icon size = 32x32 in 100% DPI
' Pass -1 for default small icon size = 16x16 in 100% DPI
' Pass desired width
' DesiredCy: Same as DesiredCx except this parameter is for icon height
' DesiredBitCount: by default, screen resolution is used
' Pass desired bitcount. Only applies to icons, not cursors
' If passed and exact match not found, default bitcount is returned
' IsCursor: Pass True if a cursor resource is being queried else pass as false
' Note: Intentionally allowed to break (generate an error) if you pass the wrong
' parameters. For example, passing the wrong ResName or IsCursor parameter value.
' If error occurs in IDE, you'll get your typical MsgBox. If compiled, crash unless you
' call this function using On Error GoTo xxx or On Error Resume Next. But you should
' never get an error compiled since you would be using this during IDE & fixed them.
Const ICRESVER As Long = &H30000
Const SM_CXICON As Long = 11
Const SM_CYICON As Long = 12
Const SM_CXSMICON As Long = 49
Const SM_CYSMICON As Long = 50
Const SM_CXCURSOR As Long = 13
Const SM_CYCURSOR As Long = 14
Const RT_ICON As Long = 3
Const RT_CURSOR As Long = 1
Const RT_GROUPCURSORICON As Long = 11
Dim hIcon As Long, iTypeRes As Long
Dim bData() As Byte, iIndex As Long
Dim adjIndex As Long, iPos As Long, x As Long
' set flag for icon or cursor and default sizes
If IsCursor = False Then
iTypeRes = RT_ICON
If DesiredCx = 0& Then
DesiredCx = GetSystemMetrics(SM_CXICON)
ElseIf DesiredCx < 0& Then
DesiredCx = GetSystemMetrics(SM_CXSMICON)
End If
If DesiredCy = 0& Then
DesiredCy = GetSystemMetrics(SM_CYICON)
ElseIf DesiredCy < 0& Then
DesiredCy = GetSystemMetrics(SM_CYSMICON)
End If
Else
iTypeRes = RT_CURSOR
If DesiredCx < 1& Then DesiredCx = GetSystemMetrics(SM_CXCURSOR)
If DesiredCy < 1& Then DesiredCy = GetSystemMetrics(SM_CYCURSOR)
End If
' get the icon/cursor directory information
bData() = LoadResData(ResName, iTypeRes + RT_GROUPCURSORICON)
iIndex = LookupIconIdFromDirectoryEx(bData(0), iTypeRes \ RT_ICON, DesiredCx, DesiredCy, 0&)
If iIndex = 0& Then Exit Function
If DesiredBitCount > 0& And IsCursor = False Then
' locate the icon/cursor found in previous step
For x = 6& To 6& + 14& * (bData(4) Or bData(5) * &H100&) - 14& Step 14&
If (bData(x + 12&) Or bData(x + 13&) * &H100&) = iIndex Then
If (bData(x + 6&) Or bData(x + 7&) * &H100&) = DesiredBitCount Then
adjIndex = x ' found item matches desired bitcount
Else
iPos = x ' cache found item location in array
End If
Exit For
End If
Next
If adjIndex = 0& Then ' look for a specific bitcount
For x = 6& To 6& + 14& * (bData(4) Or bData(5) * &H100&) - 14& Step 14&
If bData(x) = bData(iPos) Then ' match width
If bData(x + 1&) = bData(iPos + 1&) Then ' match height
If (bData(x + 6&) Or bData(x + 7&) * &H100&) = DesiredBitCount Then
adjIndex = x ' bitcount matched & done
Exit For
End If
End If
End If
Next ' set adjusted index
If adjIndex Then iIndex = bData(adjIndex + 12&) Or bData(adjIndex + 13&) * &H100&
End If
End If
' get the bitmap and mask data then create icon & wrap in stdPicture
bData() = LoadResData(iIndex, iTypeRes)
hIcon = CreateIconFromResourceEx(bData(0), UBound(bData) + 1&, iTypeRes \ RT_ICON, ICRESVER, DesiredCx, DesiredCy, 0&)
Set LoadResIconEx = HandleToStdPicture(hIcon, vbPicTypeIcon)
End Function
Private Function HandleToStdPicture(ByVal hImage As Long, ByVal imgType As PictureTypeConstants) As IPicture
' function creates a stdPicture object from an image handle (bitmap or icon)
Dim lpPictDesc(0 To 3) As Long, aGUID(0 To 3) As Long
lpPictDesc(0) = 16& ' faux PictDesc structure
lpPictDesc(1) = imgType
lpPictDesc(2) = hImage
' IPicture GUID {7BF80980-BF32-101A-8BBB-00AA00300CAB}
aGUID(0) = &H7BF80980
aGUID(1) = &H101ABF32
aGUID(2) = &HAA00BB8B
aGUID(3) = &HAB0C3000
' create stdPicture
Call OleCreatePictureIndirect(lpPictDesc(0), aGUID(0), True, HandleToStdPicture)
End Function
Edited: Some notes about the above LoadResIconEx method.
1. Reason why DesiredBitCount does not apply to cursors.
The cursor directory structure stored in a resource for cursors includes: width, height, bitcount as integers. Icons are different. But when a cursor is placed into a res file via the VB Resource Editor, the bitcount entry is always zero. Not reliable to search/filter on.
2. It is possible that above code could return the wrong icon depending on the what image sizes are in an icon's resource. If all are typical square icons (width=height), no problem. But could potentially be a problem if icons are rectangular. The reason for this is because of the way the VB Resource Editor writes the icon's directory structure. It doubles the actual height in that structure. The RC.exe application doesn't do this and haven't found any Microsoft documentation that says that entry should be doubled for icons. Cursor height is never doubled by VB's Resource Editor, but should be. It seems the VB Resource Editor logic got icons/cursors reversed when writing the dimensions.
Note: In above, we are talking about the icon directory entry structure, not the BitmapInfoHeader structure which does use doubled height. That doubled height value is not 'restored/fixed' when the project is compiled. The resource file is compiled as-is.
3. DPI Awareness. When passing 0 (large icon) or -1 (small icon) for the DesiredCx & DesiredCy parameters and your app is declared DPI aware, then the correct image size will be loaded. If passing any other size parameter values while app is DPI aware, you must pass the scaled desired sizes.
If interested, here is a project that will touch-up icon/cursor directory entries within a VB resource file. In short, it will correct those entries based on the values reported in the icon/cursor bitmap information headers and/or PNG headers.
Last edited by LaVolpe; Oct 21st, 2017 at 03:33 PM.
Insomnia is just a byproduct of, "It can't be done"