Results 1 to 13 of 13

Thread: How to draw images quickly

  1. #1

    Thread Starter
    Junior Member
    Join Date
    Feb 2010
    Posts
    31

    How to draw images quickly

    Hi all,

    I have a rather interesting problem. I'm trying to create a Windows Explorer type application, and the first step I'm trying to accomplish is to get the icons drawn.

    I got the icon boxes themselves working smoothly, but now I'm trying to draw the icons.

    VisibleItems is the array of the items I want to draw on the screen.

    FBIcon is a class I wrote that contains the icon's data (path, location, name, whether it is selected, ect). It doesn't inherit System.Windows.Forms.Control; it is a standalone class. I'm trying to stay away from controls if I can to maximize speed and efficiency.

    As you can see from the code below, it uses Icon.ExtractAssociatedIcon() to get the appropriate icon based on the file type, and then it draws it to the correct location.

    Code:
    If VisibleItems IsNot Nothing Then
        For Each element As FBIcon In VisibleItems
            Dim tileImage As Bitmap
            Try
                tileImage = Icon.ExtractAssociatedIcon(element.Path).ToBitmap()
            Catch ex As Exception
                tileImage = Image.FromFile("") 'placeholder image
            End Try
            e.Graphics.DrawImage(tileImage, element.Location.X + ((getIconWidth() - tileImage.Width) \ 2), element.Location.Y + ((getIconHeight() - tileImage.Height) \ 2))
        Next
    End If
    It works good; the layout logic performs the way I want it to, and there are (for the most part) no bugs.

    The problem I'm having is with its speed. This code can be run up to a few hundred times every time the form is resized. It's not severely laggy, but it does slow down the form noticeably (especially if the form is maximized and there are a lot of icons being drawn.

    So basically, I'm looking for a way to optimize this code. Do you guys know of a way to draw images really quickly? Also, is there a way to get a file icon based on its type really quickly?

    If not, is there a different way of approaching this that would achieve the same result?

    - Simek

  2. #2
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: How to draw images quickly

    Normally any time you're having speed issues with GDI+ I'd recommend making the switch to XNA, however I think I can see what's causing the speed issues... it's the Try/Catch inside your For Each loop. Some tips to speed up the process would be to switch the For Each loop to a For Next loop, this won't cause much of a difference, but it will improve the speed a bit. Next, if you're able to... Get rid of that Try/Catch statement as these are notoriously slow. One way that I'm thinking you could replace the Try/Catch would be like this:
    Code:
    Dim tileImage As Bitmap
    
    tileImage = Icon.ExtractAssociatedIcon(element.Path).ToBitmap()
    
    If IsNothing(tileImage) Then
        tileImage = Image.FromFile("")
    End If
    But this wouldn't work if you're having specific exceptions you're trying to handle.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  3. #3

    Thread Starter
    Junior Member
    Join Date
    Feb 2010
    Posts
    31

    Re: How to draw images quickly

    Thank you. I will try this when I get home.

  4. #4
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: How to draw images quickly

    I don't see how removing the Try/Catch would work, assuming that the reason for it is that ExtractAssociatedIcon is raising an exception.
    If it is raising an exception, you wouldn't get to the code where you could test "If IsNothing...".

    I would think the biggest slowdown is not the drawing, but the disk access and bitmap creation for every file each time you resize the window.
    If I was doing this, I would expect for a given directory, to read and cache all the icons (probably as part of FBIcon class).
    Even looking at Windows Explorer, if I have icon view selected, and change to a directory, you can usually see Windows create some default icons to quickly display the folder contents, and then see it updating the images in a second, slower pass. Once the icons are built for all the file, even if I resize the window, or scroll I don't see Windows recreating the icons, it just repositions them, and shows whichever are currently visible. It takes a lot of time to read files (relative to reading memory), so using some extra memory to cache file data is necessary if you want a responsive GUI.
    If after caching the images, you still have drawing speed issuses, then the first quickest way I know of speeding up drawing is to use the BufferedGraphics class, and the second would be using TextureBrushes instead of images to draw the icons, but you may not need that extra work if you just cache the images rather than re-read from the files.

  5. #5

    Re: How to draw images quickly

    Have you run a Profiling tool to determine this being the exact source of the slowness? I'd do that to double check that being the slow part before making any radical code changes.

  6. #6

    Thread Starter
    Junior Member
    Join Date
    Feb 2010
    Posts
    31

    Re: How to draw images quickly

    Thanks passel, you've given me lots to think about and a place to start.

    @ formlesstree4: Well, I can't be sure, but I commented out the code that retrieves and draws the icons, and without that, the form is buttery smooth. So I'd say it's a pretty good bet that my icons are what's slowing it down. Nevertheless, is there a profiling tool you could recommend?

  7. #7

    Re: How to draw images quickly

    Quote Originally Posted by Simek View Post
    Thanks passel, you've given me lots to think about and a place to start.

    @ formlesstree4: Well, I can't be sure, but I commented out the code that retrieves and draws the icons, and without that, the form is buttery smooth. So I'd say it's a pretty good bet that my icons are what's slowing it down. Nevertheless, is there a profiling tool you could recommend?
    It could be the physical drawing calls to GDI+ that's making it rather slow. You could try, first, to DoubleBuffer the form to see if that improves performance (or even the control, but you might have to resort to Reflection to get that happening). If not, you could use a BufferedGraphics class (like passel suggested) to handle some of the heavy lifting. Boops Boops also has a FastPix library, but that is probably more for image manipulation rather than image drawing (in fact, that might be the case..)
    As for a recommended profiling tool...I don't know of one off hand. At my job, I use VS2012 Ultimate and there's some profiling tool integrated in that which works rather nicely.

    If none of these suggestions help you out (I would hope something does in this thread), you might have to move to WPF which uses DirectX instead of GDI+

  8. #8
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: How to draw images quickly

    Well, by not retrieving and drawing the icons, you have done some profiling, in a manual way.
    To take that further, I would load a bitmap from the first file, outside the loop, then inside the loop, just use that image over and over, rather than retrieve one from the files.
    If the update is still acceptably smooth when you resize the form, then the issue is not the drawing, but the retrieval of the icons from the files. Since accessing a file is thousands of times slower than accessing memory, it just seems to me that accessing the disk (even with buffering) hundreds of times (opening and closing files, allocating a bitmap, and transfering data into it) has to be a hugh expense in time, and not something I would ever contemplate doing, unless I absolutely needed to. Minimizing disk access is always a big component of any intense File I/O operation, whether it be a text editor, image editor, sound editor, etc....

  9. #9
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: How to draw images quickly

    I would remove the Try...Catch, too. If you are getting exceptions, you need to be not getting those exceptions. Exceptions would slow the program to a crawl, though. Since an exception in this case is almost certainly a bug, I'd want to know about the exception so that I could change the code in such a way that it didn't happen.

    Having said that, I think that you probably aren't getting exceptions because the impact would likely be bigger than a bit of slowdown. I totally agree that loading the bitmaps one time will make a big difference, and it may be sufficient. When I was doing something like this, I squeezed every last cycle out of my code...and then switched to XNA. Squeezing every last cycle generally involves re-thinking the algorithm. In particular, you should look at every step and think, "How can I take this out of the inner loop?" Since you load the images once per icon per draw, that's an ideal item to move out, especially since loading from disk can be kind of slow, as can creating bitmaps. Therefore, you want to do that one time only, then use the cached bitmap for the drawing each time. That removes the only reasonable place for an exception, too (the file access is never entirely safe, so you should wrap that in exception handling), so any remaining exceptions are things that could be handled with a check in code rather than catching an exception.

    You might also be able to cache some of those calculations, but that will gain you nothing of any significance. Loading takes real time, calculations....well, they take darn near no time.

    When I was looking at the timing of my routines, I built a little profiler class. You can find it over in the CodeBank in a thread I started with something like Profiler or Lightweight Profiler in the title. It's a simple thing, but allows you to add lines to the code and get the time in milliseconds between things. I'd sprinkle a few labels through the code (I think I documented it well enough) after every significant step, then show a messagebox with all the timings at the end. Because it is millisecond timing, very fast things like the calculations will just show 0. In fact, it is possible that your entire existing drawing routine will show 0 for any one icon. After all, if you see a lag when your run that "a few hundred times", then each one might take as little as 1 ms, or less, and the mass of them would still cause a lag. Still, as a measuring tool, it can be useful.
    My usual boring signature: Nothing

  10. #10

    Thread Starter
    Junior Member
    Join Date
    Feb 2010
    Posts
    31

    Re: How to draw images quickly

    @ passel: Thank you very much for your suggestion about caching the icons. I now create the icons while the FBIcon objects themselves are created and then cache them to a new property in FBIcon. Then I use that property when the icons need to be re-drawn. That sped things up tenfold! I would say that the drawing is at least 10x faster than it was, and there's absolutely no slow-down when the form gets bigger. In fact, it's almost instant. Caching the icons really worked wonders; I'm very happy.

    The only thing is now, the form takes a little while to pop up now...it's no doubt building the icon list.

    Is there anything I can do about that?

    @dday9: I took your suggestion and removed the Try...Catch. I think it helped a little...not much though.

    @ Shaggy Hiker: Yes, I removed the Try...Catch. The Try Catch was only there because before I was using ExtractAssociatedIcon(), I was using SHGetFileInfo() (some Windows API voodoo) which didn't always behave predictably. But now, when FBIcon is initialized, it's initialized with an icon property set to null. If getting the icon fails, that property will stay set to null. Then when I go to draw the icon, I check to make sure it's not null before I try to draw it. I will look at my loop and see if there's a way to optimize further. I don't really need a profiler after all since I've managed to get my icon drawing up to a good speed now. Actually, I take that back...I now need to figure out how to speed up the actual icon loading itself (when the form starts). It's delaying my form showing up. I'll probably need a profiler for that...
    Last edited by Simek; Jan 31st, 2014 at 08:17 PM.

  11. #11
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: How to draw images quickly

    Quote Originally Posted by Simek View Post
    The problem I'm having is with its speed. This code can be run up to a few hundred times every time the form is resized.
    That may indeed be the problem. I guess you are Refreshing the form from the Resize or SizeChanged event. That is a mistake because it forces an immediate repaint every time the event fires, which could be 50 or so times per second. Repainting the form can take tens of milliseconds, so doing that can cause serious lag.

    If that is what you are doing, you should instead Invalidate the form. Invalidate places the all Paint messages on a queue which will be handled together as soon as there is some processor idle time. That will usually be more than fast enough to produce a smooth result.

    I agree with getting rid of the Try-Catch if you can, because every time an exception happens there will be a huge delay. Reading your tile image from disk every time that happens makes it much worse. Probably the only "error" that the ExtractAssociatedIcon method can return is Nothing, so just check for Nothing and take action accordingly. If you need a default tile image, read it once only in the Load sub or in the form variable declarations.

    BB

  12. #12
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: How to draw images quickly

    Loading may be inevitable. If there is a serious cost to loading all the icons one time, then you will have to pay that price at some time. Pay it all up front. People are used to a delay at program launch, so that's better than paying the cost later in the program.

    However, if the form in question is not your startup form then you may have even better options.
    My usual boring signature: Nothing

  13. #13
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: How to draw images quickly

    It was mentioned early on that it is a windows explorer type thing, so I'm assuming everytime you change directories, you don't really have much of a choice, you have to read all the files in the directory and build up an icon list based on the files there.
    When resizing the form, there was no reason to re-read the icons, since the directory didn't change, the icons just needed to be repositioned.
    So, that bottleneck is taken care of, but speeding the reading of the files to get the icons is not going to be that easy.
    If you watch Windows Explorer, you will see that it also takes on the order of seconds, to build all the icons for a directory.
    But, it does it in a background task type of way.
    If you have your explorer set up to show icons, and you change directory, I believe Windows does a quick directory to get a list of files, and their file type, and uses default icons, i.e. a plain folder for directories, a text type icon for text files, another icon for executables.
    It lays out and displays the directory with those icons so the page pops up fairly quickly.
    It then, while allowing you to navigate around the directory page shown, is updating the icons, which you can see as it works its way left to right, top to bottom, so it must only be able to do a fairly small number per second.

    If Windows has to do it, then I guess you'll have to do something similar.
    Now, I thought on some machines, I saw a file in the directory that looked like an icon cache, so Windows could cache the icons for all the files in the directory into a single file, then would only need to read one file and extract hundreds of icons, rather than open hundreds of files and extract one icon from each.
    I'm not suggesting collecting and creating a file in each directory, which would have to validated anyway against any changes in the directory.
    But if you're serious about trying to get rid of the initial form opening delay, then I thing the only option is to go to a two stage approach, and read the icons and update them after you've displayed placeholder icons, based on file type.

    You say the form use to pop-up quicker before, what was it displaying if the reading of the files took so long?
    Was it that the form pops-up quick, but empty and was populated fairly quickly, while you watch, and because you could see it, the perception was that it came up quickly, when in reality, the time until the page was filled is really the same, but now you're looking at nothing until the page shows, so the delay is obvious.

    If that is true then, perhaps you could mix the old with the new.
    Have the form appear, and draw as you fill the FBIcon array so the user sees stuff happening immediately, but then later when resizing, only do the drawing part, since all the icons have been read. Probably easier than doing a two stage approach.
    Last edited by passel; Jan 31st, 2014 at 09:52 PM.

Tags for this Thread

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