0

For this question please refer to a previous question I asked a while back: Do I need TThreads? If so can I pause, resume and stop them? LU RD answered the question with a provided demo and some comments.

I stopped using Delphi for quite a long time but now I am getting back into it and redoing a project. This project has time consuming operations such as opening a Gif, extracting the frames and then adding those frames (bitmaps) into a TImageList and TListView. This time I actually add the bitmaps directly into a TObjectList, as seen here: How to add and retrieve Bitmaps to and from a TList?

Typically nothing special needs doing here to speed it up as most Gif animations are small, but with medium to large Gifs the application can hang. This is going to get worse though as those bitmaps are going to be modified at runtime using various imaging filters such as grayscale, change hue etc. So I am sure I need multi-threading for this otherwise accessing each bitmap and then manipulating is going to be very slow (as I found out before).


So with that said, I am foolishly trying to adapt (without a clue of what I am doing) some of my procedures to work with the TThread example posted by LU RD I linked to at the top.

I wish I spent a bit more time with the original question to ask for more information but I guess I got sidetracked and moved onto something else, which meant I learned nothing.

Take this snippet from the threading example:

const
  cWorkLoopMax = 500;

function TForm1.HeavyWork: boolean; // True when ready
var
  i, j: integer;
begin
  j := 0;
  for i := 0 to 10000000 do
    Inc(j);
  Inc(workLoopIx);
  Result := (workLoopIx >= cWorkLoopMax);
end;

For a start I have no idea what cWorkLoopMax is for, and why its value is set to 500?

Secondly I guess the HeavyWork procedure is just a sample, which runs in a loop a 10000000 times whilst incrementing the j variable?

Then we have the workLoopIx which I am unsure what is for? Maybe something to do with the position within the thread maybe?


So, here I have my current code (no threading) which handles opening the Gif and adding to the TListView and TImageList. The procedures I use are in another unit, if needed I will post it also, but this is what I use inside a TAction (actOpen):

if OpenPictureDialog.Execute then
begin
  Screen.Cursor := crHourGlass;
  try
    BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);

    ListView1.Items.BeginUpdate;
    try
      ListView1.Items.Clear;

      for I := 0 to BitmapCollection.BitmapList.Count - 1 do
      begin
        with ListView1.Items.Add do
        begin
          Caption := 'bitmap' + IntToStr(I+1);
          ImageIndex := I;
        end;
      end;
    finally
      ListView1.Items.EndUpdate;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

What I dont understand is how to put that into a thread procedure, such as HeavyWork? I just created a new one called Job_Open and did this:

procedure TForm1.actOpenExecute(Sender: TObject);
begin
  if OpenPictureDialog.Execute then
  begin
    if not Assigned(MyThread) then
    begin
      workLoopIx := 0;
      btnStartTask.Enabled := false;
      btnPauseResume.Enabled := true;
      btnCancelTask.Enabled := true;
      MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, Job_Open);
    end;
  end;
end;

function TForm1.Job_Open: boolean;
var
  I: Integer;
begin
  BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);

  for I := 0 to BitmapCollection.BitmapList.Count - 1 do
  begin
    with ListView1.Items.Add do
    begin
      Caption := 'bitmap' + IntToStr(I+1);
      ImageIndex := I;
    end;
    Inc(workLoopIx);
  end;

  Result := (workLoopIx >= BitmapCollection.BitmapList.Count);// cWorkLoopMax);
end;

This is clearly not right, the performance is slower and I am getting all kind of errors such as Invalid Handle.

I would be extremely grateful if someone could take some time to explain my comments, what I am doing wrong and what I should be doing instead, updated code and comments in source are welcome but I am hoping to learn a bit more of what is going on with the code ideally.

In a perfect world if there is a library of sorts that exists out there that is easy to use then that would be a massive help if I cannot understand what is happening above. Is there such a library that can do something like:

procedure DoSomething;
begin
  BeginThreading();
    HeavyWork;
  StopThreading();
end;

Thanks in advance, and apologies for the lengthy post.

Community
  • 1
  • 1
  • Using a thread won't speed up the processing. It will only allow you to process the image while your app remains responsive. I personally see no real reason to use a thread here, the user has to wait for the processing to complete anyway. The only reason to use a thread I can see is if you want to give the user an option to "Cancel" the processing in the middle of it. – Jerry Dodge Jun 28 '14 at 15:20
  • @Jerry To implement "Cancel" functionality it is not neccessary to use threading here too. Small form with single button "cancel" and Application.ProcessMEssages will do the job. – Andrei Galatyn Jun 28 '14 at 15:59
  • @Blobby. I think this question is offtopic here. You should learn at least basics of multithreading in Delphi first, there are a lot of manuals and examples, just try to google it. You need to read some tutorials and when/if you will have some specific problem, then you can ask here for help. – Andrei Galatyn Jun 28 '14 at 16:03
  • 1
    You should use a threading library like otl – David Heffernan Jun 28 '14 at 16:22
  • 1
    The value of `cWorkLoopMax` is just an example. It represents a maximum value of your progress. `HeavyWork` is a method for the thread to work on. Then the `workLoopIx` is updated for each progress step and posted by the thread to your main thread for updating the GUI. There are so many ways to do this, but the background task must fit into a thread. OTL might be a starting point if you are unfamiliar with working with threads. – LU RD Jun 28 '14 at 16:24
  • 8
    @AndreiGalatyn `Application.ProcessMessages` is evil, I tell you, EVIL! – Jerry Dodge Jun 28 '14 at 16:31
  • 4
    Just a note on your example, vcl components cannot be updated directly from a thread by design, so this is clearly wrong. – LU RD Jun 28 '14 at 16:31
  • 5
    There are literally dozens of previous posts here about multithreading in Delphi. Almost every single one of them mentions at least once that you **cannot access visual (VCL) controls from a thread other than the main thread**. It's even automatically added as a very large comment when you create a new thread object using File->New-Other from the main menu. To reiterate all of those prior mentions, the Delphi documentation, and that large comment block: **You cannot access VCL (visual) controls from a thread**. – Ken White Jun 28 '14 at 16:51
  • Technically Delphi allows you to write working code which can access VCL from another thread, but there's dangers behind the scene. Kinda like running a car with no oil in the engine. Sure it runs, but it will indeed break down. – Jerry Dodge Jun 28 '14 at 16:53
  • Reading your task description, you want to fill a listview with images and before presenting the images, do some image processing. If you prefill the list with "work on progress" images, and once they are prepared for presentation swap them in one by one. The processing can be done in a thread, but you have to synchronize the list updates. – LU RD Jun 28 '14 at 17:04
  • @Jerry Sometimes we have to choose between evil and Evil :) Guy already has working code and he is not experienced in development of multithreaded applications at all. If the only goal is to add cancel functionality/avoid of UI locking, i believe modal form + ProcessMessages is MUCH more reasonable than reimplementing all code with threading. – Andrei Galatyn Jun 28 '14 at 17:22
  • I agree with @David Heffernan, but you might find OTL quite a steep learning curve. Noting what KenW says about not touching VCL components from any thread apart from the main one, you might try this: You should be able to google the code which goes with Ray Lischner's classic Delphi in a Nutshell. An early example is a "TFuture" which is a way to wrap a task up in a thread. Using it, you kick it off, then in Application.OnIdle you periodically check whether it's "ready"; once it is, you can safely use the data you gave it in the main thread. Simples. Not suited to every task, though. – MartynA Jun 28 '14 at 17:42
  • Some good comments here. First I assume OTL means OmniThread Library? Back to my question, this is my mistake thinking I needed multi-threading, I dont need a cancel or progress form, just a faster way of executing tasks on the bitmaps. Suppose you have a imagelist with 50 bitmaps at 256x256, try iterating through them all in a loop and applying a image filter to them, it becomes very very slow (regardless of what is been changed to the bitmap). I am looking for a way to do this quicker and it looks like a mistake on my part thinking threading would be the answer. –  Jun 28 '14 at 19:19
  • @Blobby If you need to process many different files at the same time, then yes, threads are right for you. But if you're only processing one single file, there's no need, there will be absolutely no performance advantage. – Jerry Dodge Jun 28 '14 at 19:33
  • @Blobby - Yes OTL means OmniThread Library and it is a brilliant library. – Shambhala Jun 28 '14 at 19:40
  • 3
    There's a common misconception (that you seem to have gotten) that you're always going to improve speed by doing things in multiple threads, and that's just simply not true. You often actually lose speed, because there's overhead when each thread is executed, particularly once you've started more threads than the number of processor cores. Google "thread context switch", and you'll probably find some very good explanations. – Ken White Jun 28 '14 at 19:40
  • 2
    Think of multi-threading as a multi-lane road. Instead of one lane, you add a second lane. But you still only have one car. So you can change lanes, but you're still going to get to your destination at the same time. – Jerry Dodge Jun 28 '14 at 19:57
  • All you're showing here is adding a bunch of images to a standard TListView (I'm guessing). You included .BeginUpdate/.EndUpdate, so you're not going to get any improved performance by moving it into a separate thread, which as others have pointed out isn't even advised. What you're SAYING is that you want to increase the performance of some image processing tasks, but your code isn't doing anything like that. At least, it's not obvious. If your image processing is not saturating the CPU, then you can add threads to do that. But once saturated, more threads won't help. – David Schwartz Jun 28 '14 at 20:22
  • 4
    Image processing is CPU bound so threading will give benefits. But you have to do it right. I suggest you get to work on coding the low level without any GUI. Just the image processing. – David Heffernan Jun 28 '14 at 21:27
  • Re apparently conflicting comments about multi-threading: Your net processing time may or may not be reduced by multi-threading. The sure gain is that your main thread is free to do other work while background threads do the time consuming work-not a question of how much time it takes to get the work done, but of the work distribution and user experience. In your case, multi-threading can help if implemented correctly, as @DavidHeffernan has stressed. Load and process your image files in background threads, and your main thread will be free for other work - it will **seem faster** to users. – Vector Jun 29 '14 at 09:54
  • @JerryDodge Why do you believe Application.ProcessMessages is evil? There's actually a sizable contingent who believe that *threads* are evil, at least in non-functional languages: http://stackoverflow.com/questions/1191553/why-might-threads-be-considered-evil – alcalde Jun 30 '14 at 05:49
  • 1
    @JerryDodge: It's sad that your comment has so many up-votes - that's just cargo-cult-programming. Mantras are no replacement for understanding. Code that is not reentrant will cause problems whether `Application.ProcessMessages` or threads are used. The programmer has to understand the issues and act accordingly. OTOH - code that properly works with threads will probably not have problems with `Application.ProcessMessages` either. – mghie Jun 30 '14 at 12:04
  • Thanks all for your feedback, I now realise I have misunderstood the purpose of multi-threading, and especially using it with VCL. There is probably other ways of optimising what I am trying to do so I will now explore other options and will most likely switch to a graphics library that offer faster bitmap manipulations. I just felt there was possibly something else I could have done, I actually think it is the TImageList that slows operations down, but I will explore my options a bit more and when I feel a little wiser will take a close look at the OTL. Thanks all for your input. –  Jun 30 '14 at 14:29

1 Answers1

2

DISCLAIMER: While my answer is not an answer directly to your question it is a psobile solution to your problem.

After reading your questions I have one question to you:

Are you applying same image graphical effects on all ImageList images?

If you are then I must say that you started on working on your problem from wrong approach.

First you need to know that Imagelist doesen't store all those images seperately but that it is storing them all in same verry wide image. So when you read any ImageList image internally ImageList creates output bitmap and then uses Canvas.CopyRect which is quite. When you save image to image list it internally uses Canvas.Draw.

So when you do this many times you create lots of unnecessary data movment.

So instead of your approach where you work on seperate images I recomend you work on ImageLists internal image whose handle you can get using ImageList.GetImageBitmap. This will alow you to apply same graphical effect on all ImageList images at once. And if you don't need to apply graphical effects to all ImageList images I bet you can modify your mage processing method to work only on parts of the image.

In order to learn more about ImageList I recomed you read its documentation:

Image list explanation: http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.Controls.TImageList

Image list GetImageBitmap explanation http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.ImgList.TCustomImageList.GetImageBitmap

SilverWarior
  • 7,372
  • 2
  • 16
  • 22
  • Thanks for posting, yes I am storing all images in the imagelist, and then the filter I set such as grayscale will loop through each image in the imagelist accessing the bitmap using GetBitmap and then making the change. –  Jun 30 '14 at 14:31
  • Also having read your post, I think you absolutely understand my problem and may have indeed provided the solution... –  Jun 30 '14 at 14:32
  • I have not had time to investigate but I understand now from what you have said that an imagelist is just one large bitmap, and each bitmap in the imagelist is copied from the large single bitmap. When I get a chance I will now look at manipulating the "one" bitmap, rather than accessing each individual one. Thanks for writing this as it seemed to have eluded everyone when I asked a similar question a while back. –  Jul 01 '14 at 11:57
  • 1
    Most probable reason why I figured out what you really want is that I'm a game developer and most game graphical engines have system called texture manager which uses similar approach, but images are stored in VRAM rather than in RAM. This offers much faster access times since the necessary data is already stored in Video memory. Also I would recomed that you use Graphics32 component library (http://graphics32.org/wiki/) which offers great functionality for working with images and has verry fast performance since it is capable of using hadware acceleration if possible. – SilverWarior Jul 01 '14 at 12:15
  • Thanks SilverWarrior for your help. –  Jul 01 '14 at 12:52