Results 1 to 14 of 14

Thread: [VB6] Another Method for Loading/Drawing PNG

  1. #1

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

    [VB6] Another Method for Loading/Drawing PNG

    I've known about this method for quite some time and even use it in some of my projects. But haven't seen it applied elsewhere. So, I thought I'd make it more known with an example.

    We know there are plenty of examples of loading a PNG file. Those typically use GDI+, WIA, or a custom control. However, since Vista, we can do this with basically one API call. The downside is that VB still won't display these correctly if PNG has alphablending, but we can draw them flawlessly to a DC while maintaining transparency within the PNG, if any.

    Requirement: Vista or better
    API: CreateIconFromResourceEx. Also needed is DrawIconEx to render the PNG to DC

    Edited: My comments above are a bit misleading. Only if the PNG contains alphablending will you want to use DrawIconEx to draw the image. Otherwise, just assign the returned stdPicture to any control's Picture property or even the form's Picture property. Also, see post #2 for a possible workaround.

    Recommend testing on some large PNGs. I am not sure what the limit of the API may be. I believe Vista topped out around 768x768. However, never really researched it and that may have been a soft-limit. In other words, this may not be a perfect solution for all PNGs. But on Win10, for example, no problem with typical 1024x768 PNGs.

    Simply get the PNG data into a zero-bound byte array (from file or via LoadResData) and pass to this function. The function creates a stdPicture object wrapped around an icon created from the PNG. You don't have to worry about destroying the icon since VB will do it when the stdPicture is released or goes out of scope.

    Code:
     ' APIs first
    
    Private Declare Function CreateIconFromResourceEx Lib "user32.dll" (ByRef 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 OleCreatePictureIndirect Lib "OleAut32.dll" (lpPictDesc As Any, riid As Any, ByVal fPictureOwnsHandle As Long, ipic As IPicture) As Long
    Private Declare Function DestroyIcon Lib "user32.dll" (ByVal hIcon As Long) As Long
    
    
    Public Function LoadPNGtoICO(pngData() As Byte) As IPicture
        
        Dim hIcon As Long
        Dim lpPictDesc(0 To 3) As Long, aGUID(0 To 3) As Long
        
        hIcon = CreateIconFromResourceEx(pngData(0), UBound(pngData) + 1&, 1&, &H30000, 0&, 0&, 0&)
        If hIcon Then
            lpPictDesc(0) = 16&
            lpPictDesc(1) = vbPicTypeIcon
            lpPictDesc(2) = hIcon
            ' IPicture GUID {7BF80980-BF32-101A-8BBB-00AA00300CAB}
            aGUID(0) = &H7BF80980
            aGUID(1) = &H101ABF32
            aGUID(2) = &HAA00BB8B
            aGUID(3) = &HAB0C3000
            ' create stdPicture
            If OleCreatePictureIndirect(lpPictDesc(0), aGUID(0), True, LoadPNGtoICO) Then
                DestroyIcon hIcon
            End If
            
        End If
        
    End Function
    Example. Some error checking provided, tweak to your liking. Replace the bold blue text below with a valid file/path name of a PNG.
    Code:
     ' DrawIconEx API:
    Private Declare Function DrawIconEx Lib "user32.dll" (ByVal hDC As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
    
    
    Private Sub Command1_Click()
        Dim tPicture As StdPicture
        Dim imgData() As Byte, fnr As Integer, lSize As Long
        
        fnr = FreeFile()
        On Error Resume Next
        Open [the file] For Binary As #fnr
        If Err Then
            MsgBox Err.Description
            Exit Sub
        End If
        On Error GoTo 0
        lSize = LOF(fnr)
        If lSize = 0& Then
            MsgBox "File does not exist", vbExclamation + vbOKOnly
            Close #fnr
            Exit Sub
        End If
        ReDim imgData(0 To lSize - 1&)
        Get #fnr, 1, imgData()
        Close #fnr
        
        Set tPicture = LoadPNGtoICO(imgData())
        Erase imgData()
        If tPicture Is Nothing Then
            MsgBox "Failed to load the file. May not be valid PNG format or pre-Vista operating system.", vbExclamation + vbOKOnly
        Else
            ' change the 3rd & 4th 0& to destination width & height respectively. Zero = actual size
            DrawIconEx Me.hDC, 0&, 0&, tPicture.Handle, 0&, 0&, 0&, 0&, &H3
        End If
        
    End Sub
    Last edited by LaVolpe; May 8th, 2017 at 05:41 PM. Reason: Added some clarification
    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] Another Method for Loading/Drawing PNG

    And some bonus code...

    For PNGs with transparency, many of the solutions you find will create a stdPicture with a background solid color and rendering the PNG over that color. Then the picture is added to the form, picturebox or image control. This works really well if the transparency doesn't need to overlap dynamic backgrounds.

    You can do this just as easily. When the stdPicture is returned by the LoadPNGtoICO function, apply this workaround:
    - use a picturebox, placed on the form, as a buffer. It should have no borders and its Visible property set to False
    - we'll call this picturebox: picBuffer
    Code:
        With picBuffer
            Set .Picture = Nothing
            .Width = ScaleX(tPicture.Width, vbHimetric, .Container.ScaleMode)
            .Height = ScaleY(tPicture.Height, vbHimetric, .Container.ScaleMode)
            .BackColor = vbButtonFace ' change to whatever backcolor picture will eventually be placed on
            .AutoRedraw = True
            DrawIconEx .hDC, 0&, 0&, tPicture.Handle, 0&, 0&, 0&, 0&, &H3
            Set .Picture = .Image
            .AutoRedraw = False
        End With
    
    Set Me.Picture = picBuffer.Picture
    Note: I used VB's ScaleX/ScaleY to retrieve image dimensions. That should not be used for DPI-aware applications. See this tutorial for more

    Edited: For those that may want to get the dimensions directly from the PNG...
    Since we have the PNG bytes before we sent them to the function to create the icon, we could cache the dimensions before or after the function returns. Suggest doing this on function return. If function failed, what you passed may not be a valid PNG or maybe corrupted.

    - Valid PNGs always start with 8-byte magic number: 137 80 78 71 13 10 26 10
    - Next 8 bytes are part of 1st PNG chunk & should always be: 0 0 0 13 73 72 68 82
    - The width is contained in bytes 19-16 in that order
    - The height is contained in bytes 23-20 in that order

    Width = pngData(19) Or pngData(18) * &H100& Or pngData(17) * &H10000
    Height = pngData(23) Or pngData(22) * &H100& Or pngData(21) * &H10000

    Note that we did not check the last byte in the width & height range. Why? Should always be zero, otherwise you have images that are over 16 million pixels in dimension. And the 3rd byte shouldn't need to be checked either. If it was non-zero, that would mean your PNG is over 65K pixels in dimensions.
    Last edited by LaVolpe; May 9th, 2017 at 10:17 AM.
    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}

  3. #3
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6] Another Method for Loading/Drawing PNG

    Just a nit:

    Doesn't OLEPRO32.DLL just pass that call through to OleAut32.dll? Why load both?

  4. #4
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6] Another Method for Loading/Drawing PNG

    Another thought:

    Can you run that StdPicture through one of the ImageList controls and extract a bitmap StdPicture from it w/o having to render to something like a PictureBox?

    Or I'm sure you could create a compatible hDC from the screen hDC and render to that and then get the bitmap into a StdPicture. Transparency is tougher to maintain though.

  5. #5

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

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by dilettante View Post
    Just a nit:
    Doesn't OLEPRO32.DLL just pass that call through to OleAut32.dll? Why load both?
    Good nit . Modified the declaration -- thanx
    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}

  6. #6

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

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by dilettante View Post
    Another thought:

    Can you run that StdPicture through one of the ImageList controls and extract a bitmap StdPicture from it w/o having to render to something like a PictureBox?

    Or I'm sure you could create a compatible hDC from the screen hDC and render to that and then get the bitmap into a StdPicture. Transparency is tougher to maintain though.
    1st Option: Requires including the common controls if not otherwise needed. Assuming it would work. And again, assuming it would work, another option would be to create an API ImageList. More code, for little gain maybe.

    2nd Option: Yes. Again, just more code if someone wants to do that. Filling the backcolor is easy enough with FillRect & CreateSolidBrush. But of course, you need all the supporting APIs like: CreateDibSection, GetDC/ReleaseDC (possibly), CreateCompatibleDC/DeleteDC, DeleteObject (for the brush), SelectObject, GetSystemColor (possibly) and maybe one or two others

    I think the hidden picturebox shouldn't be too much overhead & a user can always resize it back to near 0x0 if desired. Though I doubt that would be any benefit since AutoRedraw is set to False. Technically, one could always dynamically create the picturebox (Controls.Add), use it, then destroy it.

    Edited: If you were thinking of moving the LoadPNGtoICO method to a class, then yes, I agree. Other workaround options should be considered since a class won't typically have access to controls for a quick & easy buffer.
    Last edited by LaVolpe; May 8th, 2017 at 05:06 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}

  7. #7
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6] Another Method for Loading/Drawing PNG

    I've just noticed recently from question threads here that people are running into problems relying on rendering into a PictureBox, etc.

    The problems occur in scenarios such as a VB6 program running as a Windows Service, or in such a context like an IIS ASP application, etc. including a Remote Desktop session left running while its client has disconnected temporarily leaving the server-side code running.

    It seems to have to do with windows Sessions, Desktops, and so on.

    These are special cases, I admit, but they argue against general advice to use an invisible PictureBox just to host a DC to render to.

    I've found that creating a memory DC based on the system DC (probably the hard console device even though it works on a headless server too) gets around that. Example:

    Code:
        hDCScreen = GetDC(0)
        hDC = CreateCompatibleDC(hDCScreen)
    But I'm probably dragging your thread off topic now. Sorry.

  8. #8

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

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by dilettante View Post
    But I'm probably dragging your thread off topic now. Sorry.
    Maybe a bit, the workaround in post #2 has nothing to do with the code in post #1. I'll see if I can find some of those threads. Gotta wonder a bit whether its VB or lack of effort? For example, specifically toggling AutoRedraw forces a new hDC to be created (hDC property value changes). If that fails, then VB is suffering from more than a hidden picturebox. When toggling AutoRedraw, VB has got to be doing the GetDC/CreateCompatibleDC in the background I'd think. That being said, the DC types (class vs memory DC) is different depending on AutoRedraw -- maybe that comes into play? Maybe not toggling AutoRedraw results in a 'dead' DC in those cases? Don't know.

    Thanx for the head's up. I think I'd be tempted to investigate those rare/special cases if they arose.
    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}

  9. #9
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6] Another Method for Loading/Drawing PNG

    LoadIcon/LoadImage don't handle PNGs though hmm? I think I tried that myself earlier. LoadImage() would be a good way to go if only it worked.

  10. #10

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

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by dilettante View Post
    LoadIcon/LoadImage don't handle PNGs though hmm? I think I tried that myself earlier. LoadImage() would be a good way to go if only it worked.
    LoadIcon - probably, but too restrictive, loading only SM_CXICON and SM_CYICON sizes 32x32 to 64x64 (depending on DPI), which is why I never tested it specifically for PNGs.

    LoadImage - potentially. Not a direct PNG file, but if one were to wrap it in a valid ico format (include headers), then maybe. One thing I don't like about LoadImage - kind of restricted to passing it file names or res file references; don't believe any workaround exists to pass it data pointers. So, if LoadImage will work, one would likely have to open the PNG file, resave it to a temp file after prefixing the data with the 22-byte icon headers, then call LoadImage, and finally delete the temp file.

    Edited and follow-up. Yes, prefixing a 22-byte ico header does allow LoadImage to create the hIcon. This was expected, as would be expected from any API designed to load icons. By prefixing the 22-byte header, we are creating a valid PNG-encoded icon as far as Vista+ is concerned.
    Last edited by LaVolpe; May 9th, 2017 at 08:23 AM.
    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}

  11. #11
    Lively Member
    Join Date
    Oct 2014
    Posts
    94

    Re: [VB6] Another Method for Loading/Drawing PNG

    IMHO, in the first code snippet we need to destroy hIcon to avoid resource leaks even if OleCreatePictureIndirect failed.

  12. #12

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

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by wisekat View Post
    IMHO, in the first code snippet we need to destroy hIcon to avoid resource leaks even if OleCreatePictureIndirect failed.
    Yes, it's there, correct? Remember that OleCreatePictureIndirect returns non-zero if failure; unlike typical windows APIs. That probably threw you a bit.
    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}

  13. #13
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6] Another Method for Loading/Drawing PNG

    OleCreatePictureIndirect, being OLE/COM, returns an HRESULT. The value S_OK = 0 so that makes sense.

    The oddball that was meant to go away (and never did) was the older pre-OLE approach inherited from OS/2. You get a returned signal and then have to go fetch the actual error code. We also have NTSTATUS lurking in the rafters yet, seemingly a holdover from the early OS/2 partnership. Some non-OLE/COM Win32 calls return HRESULT values.

    A tangled mess, but Windows never did go "all COM" above the kernel layer as was planned before the Longhorn team took over future planning after Win2K was shipped. .Net helped throw a monkey wrench in the works too, derailing Microsoft into the shell of a company it is today.

  14. #14
    Lively Member
    Join Date
    Oct 2014
    Posts
    94

    Re: [VB6] Another Method for Loading/Drawing PNG

    Quote Originally Posted by LaVolpe View Post
    Yes, it's there, correct? Remember that OleCreatePictureIndirect returns non-zero if failure; unlike typical windows APIs. That probably threw you a bit.
    Oh, now I understand. The Picture object created successfully with OleCreatePictureIndirect when its returned result is 0 is responsible for destroying the icon as its 3rd parameter fOwn is set to True.

    It always becomes a tangled story when we mix COM and API calls in one code

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