Results 1 to 2 of 2

Thread: [vb6] Overcoming LoadResPicture (ICON/CURSOR) Limitations in IDE

  1. #1

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    [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.
    Attached Files Attached Files
    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  2. #2

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width