[RESOLVED] Upload PNG images into native Windows image list or vbAccelerator ImageList
We have a clone of vbAccelerator ImageList control we are trying to support. Is there an easy way to implement the ability to upload PNG images "as is" into this control?
vbAccelerator ImageList is a handy wrapper for the native Windows image list functionality, so in fact it is a question how to upload PNGs into any Windows image list. The standard way to upload an image file into an image list is to get a handle to the compatible bitmap with the WinAPI LoadImage function and then pass this handle to ImageList_Add or ImageList_AddMasked (which is implemented in vbAccelerator ImageList). But this way does not work for PNG files as Windows API can't process them.
I tried to find a solution in the Internet, but saw posts like the following one:
We are advised to use external libraries we need to install. Is there a way to implement what we need without installing any additional packages? If it helps, we can admit that the surrounding version is not less than Windows XP and use any built-in Windows library that comes with this or later version of the OS.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Another option is IExtractImage. I just checked and it did load PNGs for me; someone would need to double check to make sure it works on XP (if you limit to Vista+ there's even more native shell options, but this one is available on XP). IExtractImage returns an HBITMAP that ImageList_Add will accept. This obviously requires a typelib, and there's a few out there which have this interface designed for VB6, obviously my top rec being my own, but as a reminder, TLBs are NOT something that you install on an end-user system, they are only required during design time in the IDE.
Last edited by fafalone; Jun 6th, 2017 at 07:56 PM.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Regular transparency in PNGs works fine in XP.. can you post a blended one to test?
----------------
IExtractImage for PNG does work in XP, I just finished writing an XP-compatible version :::shudder:::
With oleexp.tlb and mIID.bas, the following is a complete example:
Code:
Public Function GetFileThumbnailXP(sPath As String, sFile As String, cx As Long, cy As Long) As Long
Debug.Print "Called GetFileThumbnailXP(" & sPath & ", " & sFile & ", " & cx & ", " & cy & ")"
Dim isf As oleexp.IShellFolder
Dim pidl As Long
Dim pidlPar As Long
Dim pidlFQ As Long
Dim iei As oleexp.IExtractImage
Dim hBmp As Long
Dim uThumbSize As oleexp.SIZE
uThumbSize.cx = cx
uThumbSize.cy = cy
Dim sRet As String
Dim uThumbFlags As IEIFlags
On Error GoTo e0
If Right$(sPath, 1) <> "\" Then sPath = sPath & "\"
pidlFQ = ILCreateFromPathW(StrPtr(sPath & sFile))
If pidlFQ = 0 Then
Debug.Print "GetFileThumbnailXP::Failed to get fully qualified pidl"
GoTo clnup
End If
pidlPar = ILCreateFromPathW(StrPtr(sPath))
If pidlPar = 0 Then
Debug.Print "GetFileThumbnailXP::Failed to get parent pidl"
GoTo clnup
End If
isfDesktop.BindToObject pidlPar, 0&, IID_IShellFolder, isf
If (isf Is Nothing) Then
Debug.Print "GetFileThumbnailXP::Failed to get parent IShellFolder"
GoTo clnup
End If
pidl = ILFindLastID(pidlFQ)
If pidl = 0 Then
Debug.Print "GetFileThumbnailXP::Failed to get relative pidl"
GoTo clnup
End If
isf.GetUIObjectOf 0&, 1&, pidl, IID_IExtractImage, 0&, iei
If (iei Is Nothing) Then
Debug.Print "GetFileThumbnailXP::Failed to create IExtractImage"
GoTo clnup
End If
uThumbFlags = IEIFLAG_ASPECT
sRet = String$(MAX_PATH, 0)
iei.GetLocation StrPtr(sRet), MAX_PATH, 0&, uThumbSize, 32, uThumbFlags
Debug.Print "GetFileThumbnailXP::GetLocation OK, sret=" & sRet
hBmp = iei.Extract()
GetFileThumbnailXP = hBmp
If hBmp = 0 Then
Debug.Print "GetFileThumbnailXP::Failed to get HBITMAP"
End If
clnup:
Set iei = Nothing
Call CoTaskMemFree(pidlFQ)
Call CoTaskMemFree(pidlPar)
'do NOT call free on pidl (the child-only pidl)
Set isf = Nothing
On Error GoTo 0
Exit Function
e0:
Debug.Print "GetFileThumbnailXP::Error->" & Err.Description & " (" & Err.Number & ")"
End Function
Then the HBITMAP from that is added into an ImageList and drawn (with transparency) onto a picturebox with:
Code:
Public Sub hBitmapToPictureBox(picturebox As Object, hBitmap As Long, Optional x As Long = 0&, Optional y As Long = 0&)
Dim himlBmp As Long
Dim tBMP As BITMAP
Dim cx As Long, cy As Long
Call GetObject(hBitmap, LenB(tBMP), tBMP)
cx = tBMP.BMWidth
cy = tBMP.BMHeight
If cx = 0 Then
Debug.Print "Invalid image"
Exit Sub
End If
himlBmp = ImageList_Create(cx, cy, ILC_COLOR32, 1, 1)
ImageList_Add himlBmp, hBitmap, 0&
ImageList_Draw himlBmp, 0, picturebox.hDC, x, y, ILD_NORMAL
ImageList_Destroy himlBmp
End Sub
APIs and other support for the above code:
Code:
Public Declare Function SHGetDesktopFolder Lib "shell32" (ppshf As IShellFolder) As Long ' Retrieves the IShellFolder interface for the desktop folder.
Public Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
Public Declare Function ILFindLastID Lib "shell32" (ByVal pidl As Long) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal PV As Long) ' Frees memory allocated by the shell
Public Declare Function GetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
Public Type BITMAP
BMType As Long
BMWidth As Long
BMHeight As Long
BMWidthBytes As Long
BMPlanes As Integer
BMBitsPixel As Integer
BMBits As Long
End Type
Public Declare Function ImageList_Create Lib "comctl32" (ByVal MinCx As Long, ByVal MinCy As Long, ByVal flags As Long, ByVal cInitial As Long, ByVal cGrow As Long) As Long
Public Declare Function ImageList_Add Lib "comctl32" (ByVal hImageList As Long, ByVal hBitmap As Long, ByVal hBitmapMask As Long) As Long
Public Declare Function ImageList_Draw Lib "comctl32.dll" (ByVal himl As Long, ByVal i As Long, ByVal hdcDst As Long, ByVal x As Long, ByVal y As Long, ByVal fStyle As IL_DrawStyle) As Boolean
Public Declare Function ImageList_Destroy Lib "comctl32" (ByVal hImageList As Long) As Long
Public Enum IL_DrawStyle
ILD_NORMAL = &H0
ILD_TRANSPARENT = &H1
ILD_MASK = &H10
ILD_IMAGE = &H20
'#If (WIN32_IE >= &H300) Then
ILD_ROP = &H40
'#End If
ILD_BLEND25 = &H2
ILD_BLEND50 = &H4
ILD_OVERLAYMASK = &HF00
ILD_SELECTED = ILD_BLEND50
ILD_FOCUS = ILD_BLEND25
ILD_BLEND = ILD_BLEND50
End Enum
Public Enum IL_CreateFlags
ILC_MASK = &H1
ILC_COLOR = &H0
ILC_COLORDDB = &HFE
ILC_COLOR4 = &H4
ILC_COLOR8 = &H8
ILC_COLOR16 = &H10
ILC_COLOR24 = &H18
ILC_COLOR32 = &H20
ILC_PALETTE = &H800 ' (no longer supported...never worked anyway)
'5.0
ILC_MIRROR = &H2000
ILC_PERITEMMIRROR = &H8000
'6.0
ILC_ORIGINALSIZE = &H10000
ILC_HIGHQUALITYSCALE = &H20000
End Enum
Public Function isfDesktop() As IShellFolder
Static isf As IShellFolder
If (isf Is Nothing) Then Call SHGetDesktopFolder(isf)
Set isfDesktop = isf
End Function
And finally called as (note: picturebox must have autoredraw=true)
Code:
Dim hb As Long
hb = GetFileThumbnailXP("C:\temp2\png\", "256.png", 32, 32)
hBitmapToPictureBox Picture1, hb
Picture1.Refresh
Tested to work on a stock install of XP SP3
Last edited by fafalone; Jun 6th, 2017 at 09:59 PM.
Reason: Cleaned up code a little.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Thank you, guys, for all your suggestions. I think I should try them one-by-one. I would start from LeandroA's solution based on CreateIconFromResourceEx. It seems, it is the simplest one and it should work in any system starting from Windows 2000 (according to MSDN).
LeandroA, if I get you right, we can get the binary image of a PNG file in memory using VB's Open File For Binary, then feed it to CreateIconFromResourceEx to get the corresponding icon handle and then pass the handle to ImageList_AddIcon to add to the image list. Will it work for 32-bit PNG images with transparency?
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
The information at the current MSDN site has been sanitized to rewrite history. CreateIconFromResourceEx() is supported all the way back to Windows 95 and NT 4.0. However its actual behavior has been modified to support PNG icon images, and that didn't happen until Vista.
That scheme's sort of a hack that wraps the PNG with an ICO header, and internally it uses GDI+ to create the premultiplied bitmap from the PNG file image.
Storing the result into an ImageList is another matter. If there is an alpha channel involved for dropshadows and such (why else screw around with PNG?) you need an ImageList with the support required as described at the link i gave you above. Once in such an ImageList, using it for anything requires additional support in target controls. You can "paint over" a backdrop using DrawIconEx() but the translucency is not retained afterward without special support. That matters most for things like ToolBar Buttons supporting hover effects, and a few other cases of dynamic background changes in Common Controls.
We still don't know what you are really trying to accomplish, and so why you want to use PNG images is still unknown to us. You'll find it far less trouble to just use mask/GIF transparency for the small images used in things like ToolBar and Command Button graphics. For small images alpha translucency matters very little anyway.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Originally Posted by dilettante
We still don't know what you are really trying to accomplish, and so why you want to use PNG images is still unknown to us. You'll find it far less trouble to just use mask/GIF transparency for the small images used in things like ToolBar and Command Button graphics. For small images alpha translucency matters very little anyway.
I'd like to implement a universal solution. I need the ability to upload any PNG file (any size, with translucency, etc.) into an image list and use it with other controls like the TreeView from the Windows Common Controls pack or 3rd-party tools that can be linked to an external image list and consume icons from it. The latter implies that the tool can get the image list handle and use say the ImageList_DrawEx function to draw the specified icon. Is it possible to use all the features of PNG through the native OS image list this way?
P.S. We can already upload 32-bit icons with transparency into our ImageList clone. I hoped that we could do this for PNG files too.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Originally Posted by dilettante
CreateIconFromResourceEx() is supported all the way back to Windows 95 and NT 4.0. However its actual behavior has been modified to support PNG icon images, and that didn't happen until Vista.
That scheme's sort of a hack that wraps the PNG with an ICO header
I think we can consider this thing documented - see, for instance, the corresponding section in the ICO (file format) article on Wikipedia.
It's a little bit bad that it starts to work only from Vista, but I think we can live with that.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
While the IExtractImage method I put up above works fine for PNGs on Windows XP, if you're willing to limit it to Vista+ there's an even simpler way that's ideal for ImageList_Add:
Code:
Public Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
Public Declare Function SHCreateItemFromIDList Lib "shell32" (ByVal pidl As Long, riid As UUID, ppv As Any) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal PV As Long) ' Frees memory allocated by the shell
Public Function GetFileThumbnail(sFile As String, pidlFQ As Long, cx As Long, cy As Long) As Long
Dim isiif As IShellItemImageFactory
Dim pidl As Long
On Error GoTo e0
pidl = ILCreateFromPathW(StrPtr(sFile))
Call SHCreateItemFromIDList(pidl, IID_IShellItemImageFactory, isiif)
Call CoTaskMemFree(pidl)
End If
isiif.GetImage cx, cy, SIIGBF_THUMBNAILONLY, GetFileThumbnail
End Function
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
fafalone, thank you for your code. Here is also my first rough implementation of what we need based on LeandroA's "hack":
Code:
Public Function AddFromPNG( _
ByVal sFileName As String, _
Optional ByVal sKey As String, _
Optional ByVal vKeyAfter As Variant _
) As Long
Dim imgData() As Byte, fnr As Integer, lSize As Long
fnr = FreeFile()
On Error Resume Next
Open sFileName For Binary As #fnr
If Err Then
MsgBox Err.Description
Exit Function
End If
On Error GoTo 0
lSize = LOF(fnr)
If lSize = 0& Then
MsgBox "File does not exist", vbExclamation + vbOKOnly
Close #fnr
Exit Function
End If
ReDim imgData(0 To lSize - 1&)
Get #fnr, 1, imgData()
Close #fnr
Dim hIcon As Long
hIcon = CreateIconFromResourceEx(imgData(0), UBound(imgData) + 1&, 1&, &H30000, 0&, 0&, 0&)
If hIcon Then
AddFromPNG = AddFromHandle(hIcon, IMAGE_ICON, sKey, vKeyAfter)
DestroyIcon hIcon
End If
End Function
The AddFromHandle sub actually calls ImageList_AddIcon to add the image to the image list and does some extra helper work related to the control infrastructure.
However, if the above approach is based on a hack with wrapping PNGs into icons, do you think we consider your approach more robust?
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
If that's all you want you are probably working too hard.
For example with the Common Controls 6.x assembly for COMCTL32.OCX there isn't much to it:
Code:
Option Explicit
'Requires a reference to: Microsoft Windows Image Acquisition Library v2.0
Private Sub Form_Load()
ChDir App.Path
ChDrive App.Path
With New WIA.Vector
.BinaryData = LoadResData("FOLDER", "CUSTOM")
ImageList1.ListImages.Add 1, , .Picture
End With
With New WIA.ImageFile
.LoadFile "printer.png"
ImageList1.ListImages.Add 2, , .FileData.Picture
End With
With Toolbar1
Set .ImageList = ImageList1
.Buttons(1).Image = 1
.Buttons(2).Image = 2
End With
End Sub
Private Sub Toolbar1_ButtonClick(ByVal Button As ComctlLib.Button)
Select Case Button.Index
Case 1
MsgBox "Folder!"
Case 2
MsgBox "Printer!"
End Select
End Sub
That example takes one image from a resource and the other from a disk file.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Your code requires the WIA library. As I wrote, I would avoid using any external dependencies.
As for WinXP, I think we can forget about the compatibility with it. It was just my wish in the beginning to support this outdated OS version too, but it is not what must really limit us these days.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
GDI+ comes with a stock Windows install since at least Vista too right? So if you're not supporting XP there's no reason to avoid GDI+ either. And using GDI+ would give you one other major benefit... the Windows ImageList can't accept a picture smaller than its dimensions, so you can use GDI+ to draw it centered in a frame (with a border like Explorer does if you want) so that you can have smaller images too.
I assume you're doing this to display file thumbnails right? If you're interested in that technique you might want to look at my thumbnail view project which has that, the other 2 techniques I've mentioned, and a couple more set up to provide fallbacks and give you a preview no matter what.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Originally Posted by dilettante
WIA 2.0 has been part of Windows since Vista. As much a part of Windows as COMCTL32.DLL itself. You need Vista to do any of this anyway.
I don't think you understand what a "dependency" actually is. Your programs have hundreds of "dependencies."
Perhaps, it was a poor wording. What I really meant is that I do not want to install anything non-standard that does not come with Windows out-of-the-box. If it is a built-in component of the OS we can find in any modern version of Windows, it's ok to use it.
The only thing to be discussed is which versions of Windows we should consider "modern". As I see, it was a bad assumption that we still must support WinXP - though I know at least one serious developer who is still developing his software sold world-wide in Windows XP for the sake of compatibility.
As I wrote, I think, it's ok if we acknowledge Vista or even Windows 7 the minimal version we must support.
And yes, even if we compile a simple program in VB6, we have a lot of dependencies - VB6 runtime lib, GDI, common controls, and so on
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Originally Posted by fafalone
I assume you're doing this to display file thumbnails right?
Guys, I clearly explained my goal: I need to upload PNG files to our clone of vbAccelerator ImageList control to use these pictures stored in the ImageList for various purposes - in common controls like toolbars or TreeViews, 3rd-party tools that can exploit WinAPI image lists, to draw image list icons on a form surface, and the like. The original "universal" purpose an ImageList control was invented for.
The problem is that nowadays many new attractive images come in the PNG format, and we want to upload them "as is" into vbAccelerator ImageList. Without this feature, we need to convert these PNG images into equivalent files of another format keeping existing 32-bit color and transparency info before we can upload them into the ImageList (say, to 32-bit ICO files). It's a tedious and unneeded work we can avoid if we support PNG uploading directly into the ImageList.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
So yeah you're taking PNG images to make smaller versions for toolbars, treeviews, etc. So I stand by my suggestion to look at my file thumbnail project; the ultimate destination there is a ListView, but the way that's accomplished is by building a standard api imagelist so what control you apply the list to doesn't really matter. The code accepts PNG files directly and adds them to the imagelist without converting to another format first and without any dependencies that are not present in a standard Windows fresh installation. With vbaccelerator, so probably any similar project, it's likely using the same type of imagelist (winapi) under the hood, with an (unneeded) interface to VB image types like IPicture/StdPicture.
What you haven't been clear about is what issues haven't yet been addressed by the methods I and others have given to handle PNGs directly, if any.
Re: Upload PNG images into native Windows image list or vbAccelerator ImageList
Dear fafalone, it seems it is only your idea that I am making smaller versions of PNG images for controls like toolbars and treeviews as I never stated this. In my version of vbAccelerator ImageList, I implemented the "hack" with wrapping PNG images into icons suggested by LeandroA. I needed some time to test this approach. It seems, it works ok, so I think we can mark this thread as "RESOLVED" and finish this discussion.
Re: [RESOLVED] Upload PNG images into native Windows image list or vbAccelerator Imag
You're aware you just said "I never stated this" then stated exactly that in the next sentence right? (and previously, "in common controls like toolbars or TreeViews, 3rd-party tools that can exploit WinAPI image lists, to draw image list icons on a form surface, and the like.") ... "Why do you think I'm making thumbnail icons of PNGs? I'm not doing that I'm making thumbnail icons out of PNGs"
Everyone's code here arrives at the same place.
Re: [RESOLVED] Upload PNG images into native Windows image list or vbAccelerator Imag
ImageLists don't store original pictures, they're scaled down to the size of the imagelist. If you draw to a larger size or resize the imagelist, the smaller version that was added is scaled up, losing detail. At least VB ImageList, winapi ImageList, and IImageList do, as well as anything that wraps one of those. You could make it huge but you then have to manually scale up or frame smaller images (the gdi+ routine).