Announcement

Collapse
No announcement yet.

Load Image Files Quickly

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Load Image Files Quickly

    I'd like to load a sizable number of images (BMP/JPG/PNG/GIF) from files, keeping the Graphic memory bitmap handles in an array for use at run time. The images might be as large as 2560x1440 size, perhaps a bit smaller. There might be up to 500 of those images, 50 of which I might want to resize and display side-by-side on a dialog as quickly as possible at run time, such as when resizing the dialog.

    Pre-loading the images in memory at startup seems the appropriate first step.

    In the code below, I used GDIP to load an image 500 times, keeping the handle in a DWord array. It takes almost 90s!

    Unless the loading time can be reduced to about 2 seconds, the pre-loading strategy would not be acceptable to most users.

    Are there faster image loading methods to consider?

    Code:
    'Compilable Example:
    #Compiler PBWin 10
    #Compile Exe
    #Dim All
    %Unicode = 1
    #Include "Win32API.inc"
    #Include "CGDIPLUS.inc"
    %IDC_Graphic = 500
    %IDC_Button  = 501
    
    Global hDlg,hBMP() As Dword
    Global pImage, pGraphics, token As Dword, StartupInput As GdiplusStartupInput
    Global qFreq, qStart, qStop As Quad
    
    Function PBMain() As Long
       Dialog New Pixels, 0, "PowerBASIC",,,1500,1000, %WS_OverlappedWindow To hDlg
       Control Add Button, hDlg, %IDC_Button, "Load Images", 10,10,150,25
       Control Add Graphic, hDlg, %IDC_Graphic,"", 0,40,1500,1000-50
       Dialog Show Modal hDlg Call DlgProc
    End Function
    
    CallBack Function DlgProc() As Long
       Local i As Long
       Select Case Cb.Msg
          Case %WM_InitDialog
             QueryPerformanceFrequency qFreq
             'initialize GDI
             StartupInput.GdiplusVersion = 1                    'initialize GDIPlus
             GdiplusStartup(token, StartupInput, ByVal %NULL)   'initialize GDIPlus
    
          Case %WM_Command
             Select Case Cb.Ctl
                Case %IDC_Button
                   LoadImages
             End Select
    
          Case %WM_Destroy
             If pImage Then GdipDisposeImage(pImage)            'cleanup
             If pGraphics Then GdipDeleteGraphics(pGraphics)    'cleanup
             GdiplusShutdown token                              'shutdown GDI+
       End Select
    End Function
    
    Sub LoadImages
       Local i, iCount As Long
       QueryPerformanceCounter   qStart
       'load the same image many times
       iCount = 500
       ReDim hBMP(iCount)
       For i = 1 To UBound(hBMP) : LoadImageToMemoryBitmap "bigbaby.jpg", hBMP(i) : Next i
       'just for reference, show the image
       Graphic Attach hDlg, %IDC_Graphic
       Graphic Copy hBMP(1), 0
       QueryPerformanceCounter   qStop
       Dialog Set Text hDlg, Format$((qStop-qStart)/qFreq,"###.000") & " seconds"
    End Sub
    
    Sub LoadImageToMemoryBitmap (ImgName As String, hBMP As Dword)
       Local hDC As Dword, w,h As Long
       GdipLoadImageFromFile((ImgName), pImage)           'load image
       GdipGetImageWidth(pImage,w)                        'get width
       GdipGetImageHeight(pImage, h)                      'get height
       Graphic Bitmap New w,h To hBMP
       Graphic Attach hBMP, 0
       Graphic Get DC To hDC
       GdipCreateFromHDC(hDC, pGraphics)                  ' Create the Graphic object
       GdipDrawImage(pGraphics, pImage, 0, 0)             ' Draw the image
    End Sub

  • #2
    The image files change over time, so including them in the EXE as resources won't work.

    Images are displayed on startup, so using a single thread to load the images in the background doesn't work either. I wonder if using multiple threads, as many as one per image to be loaded, might give significant improvement? I'll give that a try.

    Comment


    • #3
      For what it's worth, this variation took 7.302 seconds. The image I used is 1023x1379.
      Code:
      #COMPILE EXE
      #DIM ALL
      %Unicode = 1
      #INCLUDE "Win32API.inc"
      '#Include "CGDIPLUS.inc"
      %IDC_Graphic = 500
      %IDC_Button  = 501
      
      GLOBAL hDlg,hBMP() AS DWORD
      GLOBAL pImage, pGraphics, token AS DWORD', 'StartupInput As GdiplusStartupInput
      GLOBAL qFreq, qStart, qStop AS QUAD
      
      FUNCTION PBMAIN() AS LONG
         DIALOG NEW PIXELS, 0, "PowerBASIC",,,1500,1000, %WS_OVERLAPPEDWINDOW TO hDlg
         CONTROL ADD BUTTON, hDlg, %IDC_Button, "Load Images", 10,10,150,25
         CONTROL ADD GRAPHIC, hDlg, %IDC_Graphic,"", 0,40,1500,1000-50
         DIALOG SHOW MODAL hDlg CALL DlgProc
      END FUNCTION
      
      CALLBACK FUNCTION DlgProc() AS LONG
         LOCAL i AS LONG
         SELECT CASE CB.MSG
            CASE %WM_INITDIALOG
               QueryPerformanceFrequency qFreq
               'initialize GDI
               'StartupInput.GdiplusVersion = 1                    'initialize GDIPlus
               'GdiplusStartup(token, StartupInput, ByVal %NULL)   'initialize GDIPlus
      
            CASE %WM_COMMAND
               SELECT CASE CB.CTL
                  CASE %IDC_Button
                     LoadImages
               END SELECT
      
            CASE %WM_DESTROY
               'If pImage Then GdipDisposeImage(pImage)            'cleanup
               'If pGraphics Then GdipDeleteGraphics(pGraphics)    'cleanup
               'GdiplusShutdown token                              'shutdown GDI+
         END SELECT
      END FUNCTION
      
      SUB LoadImages
         LOCAL i, iCount, wyd, hyt, fnum AS LONG
         LOCAL filnam AS STRING
         QueryPerformanceCounter   qStart
         'load the same image many times
         filnam="C:\PBWin10\Programs\FACILITIES2\bitmaps\Alpha_word_list.bmp"
         iCount = 500
         REDIM hBMP(1 TO iCount)
         FOR i = 1 TO UBOUND(hBMP)
           fnum=FREEFILE
           OPEN "myimage.bmp" FOR BINARY AS fnum
            GET #fnum, 19, wyd
            GET #fnum, 23, hyt
           CLOSE #fnum
           GRAPHIC BITMAP LOAD filnam, wyd, hyt TO hbmp(i)
      
            'LoadImageToMemoryBitmap "bigbaby.jpg", hBMP(i) :
         NEXT i
         'just for reference, show the image
         GRAPHIC ATTACH hDlg, %IDC_Graphic
         GRAPHIC COPY hBMP(1), 0
         QueryPerformanceCounter   qStop
         DIALOG SET TEXT hDlg, FORMAT$((qStop-qStart)/qFreq,"###.000") & " seconds"
      END SUB
      
      'Sub LoadImageToMemoryBitmap (ImgName As String, hBMP As Dword)
      '   Local hDC As Dword, w,h As Long
      '   GdipLoadImageFromFile((ImgName), pImage)           'load image
      '   GdipGetImageWidth(pImage,w)                        'get width
      '   GdipGetImageHeight(pImage, h)                      'get height
      '   Graphic Bitmap New w,h To hBMP
      '   Graphic Attach hBMP, 0
      '   Graphic Get DC To hDC
      '   GdipCreateFromHDC(hDC, pGraphics)                  ' Create the Graphic object
      '   GdipDrawImage(pGraphics, pImage, 0, 0)             ' Draw the image
      'End Sub
      Rod
      "To every unsung hero in the universe
      To those who roam the skies and those who roam the earth
      To all good men of reason may they never thirst " - from "Heaven Help the Devil" by G. Lightfoot

      Comment


      • #4
        Hi, Rodney!
        Yes, if I restrict the incoming file types to BMP, there is a time benefit.

        On my PC,with 2270 x 1455 the code to about 18s.

        But, I do have to work with other image file types such as JPG/PNG/GIF.

        And, btw, you don't have to have the w h values to use Graphic Bitmap Load. Help mentions that if you set either of the w/h values to 0, the image will load ad

        Comment


        • #5
          If you re-load the same image then you'll get unrealistically quick times because the first load will cache the file and the subsequent loads then read the file from memory and not from disk.

          Also, 2270x1455 bitmap is around 10MB. To load 500 of them will consume 5GB of ram and you only have 2GB available.

          Comment


          • #6
            Howdy, Paul!

            Yes, you're correct about the unrealistic load time. I need to make a better test example.

            In practice almost all of the images will be JPG/PNG/GIF, so 0.5MB is more the norm. Then 0.5*500 = 250MB is a more likely memory demand - not desirable but doable. App code would need to monitor memory usage to cover the case where file size exceeds that available.

            Comment


            • #7
              You could solve at least some of the memory and speed problems by using JPG instead of BMP files. The GDI+ function will load a JPG as a BMP but you will run into memory limits as Paul mentioned with enough of them. Could you use a thumbnail approach to keep your absolute memory down ?
              hutch at movsd dot com
              The MASM Forum

              www.masm32.com

              Comment


              • #8
                Perhaps your solution is to use mipmapping and OpenGL (quad texture), and choose the best file format to store the image on file (do you need alpha channel).
                2270x1455 is 13 MB in 32-bit, 10 MB in 24-bit
                Using OpenGL you could store the image into the GPU rather than the CPU.
                Patrice Terrier
                www.zapsolution.com
                www.objreader.com
                Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                Comment


                • #9
                  From a memory usage viewpoint, I'd be happier with loading only the immediately needed images directly from the hard drive, but the load time for 50 images is just too much of a user annoyance.

                  Is there a significantly faster way to load an image from a file to memory?

                  Comment


                  • #10
                    Howdy, Patrice!

                    Sorry, just missed your post. My users would supply images of their own so I'm stuck with whatever file format they provide. Alpha channel not used by my code.

                    I'm not familiar with the technique of using the GPU for storage. Does the GPU have enough usable memory for this type of operation? And could I expect that to be true for all user hardware.

                    Comment


                    • #11
                      The fastest format would be DIB because then you can blit it directly to memory, but if the user provides his own file format, then there is no way for you to speed up the loading process, and you will have to use always 32-bit just in case, and that would mean 13 Mb per image.
                      The GPU is the solution when dealing with modern video card, and working in 64-bit would speed up the whole process with a 30% speed gain.
                      Remember that the latest video drivers are optimized for 64-bit not 32-bit (aka nVIDIA).
                      Patrice Terrier
                      www.zapsolution.com
                      www.objreader.com
                      Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                      Comment


                      • #12
                        Originally posted by Gary Beene View Post
                        Howdy, Paul!

                        Yes, you're correct about the unrealistic load time. I need to make a better test example.

                        In practice almost all of the images will be JPG/PNG/GIF, so 0.5MB is more the norm. Then 0.5*500 = 250MB is a more likely memory demand - not desirable but doable. App code would need to monitor memory usage to cover the case where file size exceeds that available.
                        That 250MB is as stored on disk, not in memory. Once you import a compressed JPG,PNG image with GdipLoadImageFromFile, it has to be expanded
                        Load any JPG image in Irfanview and look at the status bar) Here's an example:

                        Click image for larger version  Name:	JPGSize.jpg Views:	0 Size:	69.5 KB ID:	784494

                        Just to get an idea I went looking for a 500KB jpg and loaded that to see its memory requirements:
                        Click image for larger version  Name:	JPGSize2.jpg Views:	0 Size:	10.2 KB ID:	784495
                        So 500 of that jpg will require about 2.75 GB. Note the size of that image is only 1600 x 1200. Your 2560x1440 will take a lot more.

                        Comment


                        • #13
                          Bummer, Stuart, but you are correct.

                          It looks like I'll be sticking with the load-from-file option.

                          I'll still give the multi-thread option a try. Just haven't gotten to it yet.

                          Comment


                          • #14
                            Gary,

                            You obviously can't display all those images at one time on the screen, so why are you trying to load so many images at one time ?

                            Since a 32 bit app is limited in the amount of ram available, it makes little sense to try to load all those images, especially if large at one time.

                            Now if you plan on scrolling through all the images as if one huge image, then I would break up the task into smaller pieces. Load on demand only. If you need high speed scrolling and resizing then try this:

                            Start loading each image in the background one at a time. Load only what is necessary to display on the screen immediately. Also create a binary file to store all the images into one file. Create a kind of linked list in the file so it can handle multiple images. Load the image into a memory DC with a DIBsection. Then copy the DIB directly to the large file in the images proper place.Use some kind of indexing to track the images. Keep loading all the images into RAM into a DIB and then quicky move the image to the one large file.

                            Now you will have a single binary file with all your images (a lot less work tracking in the FAT for one file than many). Now your one file is a kind of disk based memory buffer where you can load blocks of images quickly into a DIBsection for display.

                            Now if the end user can set up a RAM disk and your app can be told where to store this temp image file, you will get the benefit of a huge fast reading disk file. Load what you need only on demand.

                            Your app can not store the entire group of images into memory. You would be slowing windows down because it will be forced to do a lot of page swapping. But using a disk memory buffer, your app only needs to load into memory what it needs on demand. The files size will be unlimited, so you can handle any number of images rather than being limited by ram. On 64 bit systems with plenty of ram a ram disk could be set up for the one big file to be stored.

                            You could use a Thread to load all the images one at a time, convert to 32 bit DIB and store back into your one big file.

                            90 seconds for 500 really large images is really not bad. That is .18 seconds (180 ms) per image. That is 5 images per second. Remember, that it is more than just the time to read the image from a disk file, but to have it converted to full bitmap in memory (no compression in memory).

                            I also would avoid using the PB Graphic commands since you have less control of how graphics are handled. I would create one single huge DIBsection (larger than your max supported image size) in a memory DC. Load each bitmap into that memory DC and then quickly copy back the bytes (only the image size not the entire DIB) directly to a disk file using some kind of indexing. This way you aren't creating and destroying multiple graphics in memory. You might be able to use GDI+ APIs instead to do the same thing. Don't keep creating and then freeing memory bitmaps. That is a waste of time.
                            Chris Boss
                            Computer Workshop
                            Developer of "EZGUI"
                            http://cwsof.com
                            http://twitter.com/EZGUIProGuy

                            Comment


                            • #15
                              Originally posted by Chris Boss View Post
                              Gary,

                              You obviously can't display all those images at one time on the screen, so why are you trying to load so many images at one time ?

                              Since a 32 bit app is limited in the amount of ram available, it makes little sense to try to load all those images, especially if large at one time.

                              Now if you plan on scrolling through all the images as if one huge image, then I would break up the task into smaller pieces. Load on demand only. If you need high speed scrolling and resizing then try this:

                              Start loading each image in the background one at a time. Load only what is necessary to display on the screen immediately. Also create a binary file to store all the images into one file. Create a kind of linked list in the file so it can handle multiple images. Load the image into a memory DC with a DIBsection. Then copy the DIB directly to the large file in the images proper place.Use some kind of indexing to track the images. Keep loading all the images into RAM into a DIB and then quicky move the image to the one large file.

                              Now you will have a single binary file with all your images (a lot less work tracking in the FAT for one file than many). Now your one file is a kind of disk based memory buffer where you can load blocks of images quickly into a DIBsection for display.
                              Sounds like a SQLite file full of BMP BLOBs may be a solution.

                              Comment


                              • #16
                                Hi Chris!

                                I want to display up to 50 images at a time, resized to fit all images on the containing dialog. I place all displayed images on a common Graphic control, blt'd to the appropriate locations and size. I wanted the rest of the images in memory to avoid the many seconds delay in reading the images directly from the hard drive.

                                But as folks have pointed out the memory need for 500 images is more than a system can handle.

                                I'm not sure I understand your suggested sequence.

                                1. Put all 500 images into one large file on the hard drive (have to do at run time because users can change the images)
                                2. In parallel with creating the large file, on startup load each of the immediately needed 50 images into their own DIBSections for resizing/display on the dialog
                                3. Subsequently, during run time implement image changes by reading images from the large memory file

                                If I have garbled what you said, please let me know.

                                I'm not clear on how the large file gives me a faster read time for the 50 images, at startup or any other time as compared to loading the 50 images from their respective files.

                                And, are you also saying that loading an image file into a DIBSection is faster than loading the image file into a Graphic Memory Bitmap?

                                Can you explain your suggestion a bit more? I know from past posts that you're a big promoter of DIBSections so if you've seen speed boosts over memory bitmaps, I'm interested in understanding how that is achieved.

                                Thanks for the response!

                                Comment


                                • #17
                                  Hi Stuart!

                                  If the image files were under my control, SQLite might seem more appropriate. But if I have to create the BLOB each time I start up the app to make sure I have a correct BLOB of images would that have any positive effect on the startup speed?

                                  Also, is it that case the reading an image from a BLOB is faster than loading directly from a file to a memory bitmap? And, does SQLite support image formats other than BMP?

                                  Is the suggestion of a BLOB file not unlike Chris' suggestion to have a file with multiple images in it, while keeping an index so I can lift the needed bytes out of it without loading the entire file? Once difference, of course, would be the built-in commands of SQLite should I need them.


                                  Comment


                                  • #18
                                    Does it have to be 50? 50 does not come out to whole rows and columns (except 2 x 25). 49 gives 7 x 7, 48 gives 8 x 6, etc., etc. Of course with varying image sizes and aspect ratios 50 is as good a target as any. If none of this applies, then I'm just thinking out-loud; sorry to have interrupted.

                                    Cheers,

                                    (missed 5 x 10 for 50, but that is still pretty narrow on one side, unless all images are all either landscape or all portrait.)
                                    Dale

                                    Comment


                                    • #19
                                      Originally posted by Gary Beene View Post
                                      Hi Stuart!

                                      1 If the image files were under my control, SQLite might seem more appropriate. But if I have to create the BLOB each time I start up the app to make sure I have a correct BLOB of images would that have any positive effect on the startup speed?

                                      2 Also, is it that case the reading an image from a BLOB is faster than loading directly from a file to a memory bitmap? And, does SQLite support image formats other than BMP?

                                      3 Is the suggestion of a BLOB file not unlike Chris' suggestion to have a file with multiple images in it, while keeping an index so I can lift the needed bytes out of it without loading the entire file? Once difference, of course, would be the built-in commands of SQLite should I need them.

                                      1. How often do these images change? Along with the Binary data, store the filename, size and modified date. Then on startup you can do a quick check to see if you need to add/remove or update any changed image files. That should generally be much faster than recreating every jpg/gif/png image to a DIB on every startup

                                      2. Mea Culpa!I shouldn't have said BMP BLOB, that was confusing. I was just using BMP as a shortcut for "bitmap", not the .BMP format.
                                      SQLite doesn't "support" any image formats. It just stores binary data in a Binary Large OBject (BLOB). Just store the image data in the exact form that you need for fastest loading and display purposes. Chris' DIB (Device Independent Bitmap) ?

                                      3. Yes, it's just my suggested method for implementing Chris' idea of storing all the indexed image data in one file. With SQLite, it doesn't matter how much space the DIB data occupies, you can have a 50GB file if you want and retrieving a specific image DIB will be just as fast.

                                      Comment


                                      • #20
                                        HI Dale,
                                        The 50 is an approximation. 32 or 48 will be the likely real answer.

                                        And, good morning Stuart!
                                        Images change no more than perhaps 5 times a day but most likely just once a day.

                                        I don't typically use DIBs but am interested in them if they are significantly faster loading with a than a Graphic command. I'd guess it's just a matter of choice but I'm not speaking from experience on the matter so I'll watch for more info.

                                        As soon as I get my honey-do chores done, I want to try out the thread option.





                                        Comment

                                        Working...
                                        X