high performance ImageList all OS
Posted: Sat Feb 11, 2017 5:19 am
This example which uses Microsoft's comctl32 ImageList api to create a list of icons (16x16 @ 32bit) takes 21ms on my machine for 1000 icons, and 2 seconds for 10000. However getting exponentially worse, it takes 54sec for 50,000 and 211sec for 100,000 icons.
So I wrote my own, which does the the same 100,000 icons in ~28ms. It can be used for example as the basis for a custom list/combo/tree/animated/etc gadget.
The main benefit of using an ImageList is that you don't need a seperate Bitmap + open handle for every icon, yet every icon is still immediately ready for use at any time. Each icon is drawn onto 1 single image (or memory allocation) one after another like a linear set of tiles/subimages, so you're then only resource-limited, and finding the offset to any of them is as simple as Offset=(IconIndex*SizeOfIconBuffer).
Unlike comctl32's ImageList, which stores everything in one Bitmap which grows in pixel height, my ImgList doesn't use any drawing commands, just Copy/Free/Re/AllocateMemory. Comctl32's imagelist grows a Bitmap in height, my ImgList ReAllocatememory's.
It also only adds 1.1kb to the size of your PB app, going by win32 exe size. There is one exception - ImgList_Get() does use a couple of drawing commands, but you don't need to include that function as you can just use ImgList_Read() instead and provide your own existing CreateImage'd image.
However there are two main use cases for ImageLists - if you know a priori how many images you need to load you probably want to preallocate all memory up-front rather than repeatedly calling AllocateMemory. But if you don't know, or need the ability to also dynamically add and remove images, you probably do want each image allocated individually.
So mine efficiently supports both, which i simply refer to as Dynamic mode and NonDyn (pre-allocated) mode.
PROCEDURES OVERVIEW:
IMAGELIST.PBI
Code: Select all
CreateImage(1, 16, 16, 32)
Time1 = ElapsedMilliseconds()
*imagelist = ImageList_Create_(16,16,#ILC_COLOR32,0,10)
For i = 0 To 99999
ImageList_Add_(*imagelist, ImageID(1), 0)
Next i
Time2=ElapsedMilliseconds()
MessageRequester("Time", Str(Time2-Time1))
ImageList_Destroy_(*imagelist)
The main benefit of using an ImageList is that you don't need a seperate Bitmap + open handle for every icon, yet every icon is still immediately ready for use at any time. Each icon is drawn onto 1 single image (or memory allocation) one after another like a linear set of tiles/subimages, so you're then only resource-limited, and finding the offset to any of them is as simple as Offset=(IconIndex*SizeOfIconBuffer).
Unlike comctl32's ImageList, which stores everything in one Bitmap which grows in pixel height, my ImgList doesn't use any drawing commands, just Copy/Free/Re/AllocateMemory. Comctl32's imagelist grows a Bitmap in height, my ImgList ReAllocatememory's.
It also only adds 1.1kb to the size of your PB app, going by win32 exe size. There is one exception - ImgList_Get() does use a couple of drawing commands, but you don't need to include that function as you can just use ImgList_Read() instead and provide your own existing CreateImage'd image.
However there are two main use cases for ImageLists - if you know a priori how many images you need to load you probably want to preallocate all memory up-front rather than repeatedly calling AllocateMemory. But if you don't know, or need the ability to also dynamically add and remove images, you probably do want each image allocated individually.
So mine efficiently supports both, which i simply refer to as Dynamic mode and NonDyn (pre-allocated) mode.
PROCEDURES OVERVIEW:
Code: Select all
;All functions return non-zero on success.
;ImgList_Create(width,height,depth,pitch,numimages=0,flags=0)
; - Creates a new ImgList. Returns *ptr.IMGLIST
; If numimages>0 the entire memoryspace is preallocated (NonDyn mode)
; If numimages=0 each image has its own MemoryAllocate() (Dynamic mode) Allows for ImgList_Remove.
; All images must be the same width/height/depth, but there is no buffer overrun or security issue if not.
; The memory cost of both modes is maximally efficient and basically the same, ie. numimages*imagebuffersize,
; except Dynamic mode also has the tiny overhead of 1x integer-size pointer per image.
; Flags can be set to #PB_Memory_NoClear to also get a minor speed boost by not zeroing the image memory.
;ImgList_Destroy(*ImgList.IMGLIST)
; - Frees all images in the IMGLIST, and then frees the list itself.
;ImgList_Remove(*ImgList.IMGLIST, position)
; - Removes/frees a single image from the list. (Only applicable in Dynamic mode where each image is individually Alloc'd)
;ImgList_Add(*ImgList.IMGLIST, position, *imgbuf, pitch, height)
; - Add or overwrite an image in the list.
; In Dynamic mode -1 can be used to append a new image to the list and increment its \imgcnt by 1.
; In NonDyn mode -1 cant be used as there is no appending; the position to overwrite must be specified, as its space is already preallocated.
;ImgList_Read(*ImgList.IMGLIST, position, *dstbuf)
; - Read the buffer of a single image from the list. (Typically pointed at DrawingBuffer())
;ImgList_Get(*ImgList.IMGLIST, position)
; - Get a single image from the list as a CreateImage'd handle. The only function that uses drawing commands, you may prefer just to use ImgList_Read instead.
Code: Select all
#IMGLIST_MAXITEMS = 1048575 ;Max 1,048,575 items = maximum 1GB worth of 16x16 32bit images (seems a reasonable ceiling)
#IMGLIST_FWDBUF = 256 ;(for NonDyn) allocate a small (#IMGLIST_FWDBUF * SizeOf(Integer)) buffer so we dont have to ReAlloc with every ImgList_Add, only every #IMGLIST_FWDBUF'th
Structure pbuf
pbuf.i[0]
EndStructure
Structure IMGLIST
*IMG.pbuf
imgcnt.l
width.l
height.l
depth.l
pitch.l
bufsize.l
flags.l ;0 or #PB_Memory_NoClear
dynamic.l
EndStructure
Procedure ImgList_Create(width,height,depth,pitch,numimages=0,flags=0) ;Success=non-zero (ptr to IMGLIST)
If width > 0 And height > 0 And depth > 0 And pitch > 0
Protected *ImgList.IMGLIST = AllocateMemory(SizeOf(IMGLIST))
If *ImgList
*ImgList\width=width
*ImgList\height=height
*ImgList\depth=depth
*ImgList\pitch=pitch
*ImgList\imgcnt=numimages
*ImgList\flags=flags
*ImgList\bufsize = pitch * height
*ImgList\IMG = AllocateMemory(SizeOf(Integer))
If numimages > 0
*ImgList\IMG\pbuf[0] = AllocateMemory(*ImgList\bufsize * numimages, flags)
Else
*ImgList\dynamic = 1
EndIf
ProcedureReturn *ImgList
EndIf
EndIf
EndProcedure
Procedure ImgList_Destroy(*ImgList.IMGLIST) ;success=1
If *ImgList
If *ImgList\imgcnt
If *ImgList\dynamic
For i = 0 To *ImgList\imgcnt-1
If *ImgList\IMG\pbuf[i]
FreeMemory(*ImgList\IMG\pbuf[i])
EndIf
Next i
Else
FreeMemory(*ImgList\IMG\pbuf[0])
EndIf
EndIf
If *ImgList\IMG: FreeMemory(*ImgList\IMG): EndIf
FreeMemory(*ImgList)
ProcedureReturn 1
EndIf
EndProcedure
Procedure ImgList_Remove(*ImgList.IMGLIST, position) ;success=1. Only applicable with Dynamic mode (where each image is individually Alloc'd)
If *ImgList
If *ImgList\dynamic And *ImgList\IMG\pbuf[position]
FreeMemory(*ImgList\IMG\pbuf[position])
*ImgList\IMG\pbuf[position]=0
ProcedureReturn 1
EndIf
EndIf
EndProcedure
Procedure ImgList_Add(*ImgList.IMGLIST, position, *imgbuf, pitch, height) ;success=1
Protected rc=0, srcbuflen=pitch*height
If Not *ImgList Or Not *imgbuf
ElseIf srcbuflen > *ImgList\bufsize Or position < -1 Or position => *ImgList\imgcnt
Else
If *ImgList\dynamic
If position = -1 ;Add New (dyn)
If *ImgList\imgcnt = #IMGLIST_MAXITEMS: ProcedureReturn 0: EndIf
If Not (*ImgList\imgcnt % #IMGLIST_FWDBUF)
*ImgList\IMG = ReAllocateMemory(*ImgList\IMG, (*ImgList\imgcnt + #IMGLIST_FWDBUF) * SizeOf(Integer))
EndIf
*ImgList\IMG\pbuf[*ImgList\imgcnt] = AllocateMemory(*ImgList\bufsize, *ImgList\flags)
CopyMemory(*imgbuf, *ImgList\IMG\pbuf[*ImgList\imgcnt], *ImgList\bufsize)
*ImgList\imgcnt+1
Else ;Overwrite (dyn)
CopyMemory(*imgbuf, *ImgList\IMG\pbuf[position], *ImgList\bufsize)
EndIf
rc=1
Else ;Overwrite (non-dyn)
If position => 0
CopyMemory(*imgbuf, *ImgList\IMG\pbuf[0] + (position * *ImgList\bufsize), *ImgList\bufsize)
rc=1
EndIf
EndIf
EndIf
ProcedureReturn rc
EndProcedure
Procedure ImgList_Read(*ImgList.IMGLIST, position, *dstbuf) ;success=1
Protected rc=0
If *ImgList
If position => 0 And position < *ImgList\imgcnt
If *ImgList\dynamic
If *ImgList\IMG\pbuf[position] And *dstbuf
CopyMemory(*ImgList\IMG\pbuf[position], *dstbuf, *ImgList\bufsize)
rc=1
EndIf
Else
CopyMemory(*ImgList\IMG\pbuf[0] + (position * *ImgList\bufsize), *dstbuf, *ImgList\bufsize)
rc=1
EndIf
EndIf
EndIf
ProcedureReturn rc
EndProcedure
;this is the only function that uses drawing commands. You may just want to use ImgList_Read instead.
Procedure ImgList_Get(*ImgList.IMGLIST, position) ;success=non-zero (hImg). Callers responsibility to FreeImage
If *ImgList
hImg = CreateImage(#PB_Any, *ImgList\width, *ImgList\height, *ImgList\depth)
If StartDrawing(ImageOutput(hImg))
ImgList_Read(*ImgList.IMGLIST, position, DrawingBuffer())
StopDrawing()
ProcedureReturn hImg
EndIf
EndIf
EndProcedure