Drawing on TImage Canvas

I’m drawing some lines and text on a TImage canvas. In general it all works, but it has one bug that is eluding me.

Occasionally a drawing operation is causing the TImage canvas to go transparent as I can see the colour of the TScollBox that is hosting the TImage. The TImage is still there as the mouse events for it still work (popup menu, etc), but all subsequent drawing operations can’t be seen. I’ve checked the Transparent property though and it’s false.

What else can I check / do to track this bug down?

FMX or VCL ?

Maybe you can “fuzz test” it … by throwing

  • random unicode text at it 10,000 times, and
  • draw 10,000 random lines on the canvas.

If you set a seed first, the ‘random’ will be deterministic and repeatable. Might lead you somewhere?

This is a VCL application. I can actually replicate the issue now, but I can’t track down why it’s doing it.

Most of the drawing is for an audio graph, time scale and some marker lines. These are updated in response to user interaction and it never seems to have a problem with that.

A progress marker is updated when a 100ms progress timer fires and that does not cause an issue. The whole image is redrawn each time.

Where it dies is when I have the audio file playing back and it finishes. There’s an event triggered by the audio playback process that also calls the graph update to clean up the window.

If I remove the call to the update when the audio playback stops there is no issue, but then there’s a marker left over from the last progress event.

It’s like there’s a memory corruption occurring when the audio playback stop event calls the update which clobbers the TImage somehow.

Possibly related, would I be better off adding the progress marker as another layer on top of the graph’s TImage so I’m not redrawing it all the time when playback is in progress?

Maybe a long shot, but have you checked to make sure that the event is being fired in the main thread? If it’s not, that could certainly explain strange behaviour in the VCL.

I’m not creating any threads for the audio playback myself, but they are in a 3rd party DLL.

As far as I know I’ve not had any issues with any other code and the same event.

It would definitely be worth logging the thread identifier in the event handler and check that it matches the thread identifier of the main thread.

It’s not unusual for some things to work OK in a thread with the VCL some/most of the time, but they can fail in interesting ways randomly.

OK, this is getting into unfamiliar territory for me. I just tried using GetCurrentThread and got the same number for the event that the audio playback fires and the one in the form that triggers the update.

Something I also just tried was posting a custom message within the same form. That seems to decouple the end-of-play event from the graph update and no more disappearing TImage.

Just to be sure, you could call the following RunningInMainThread function to check that it’s definitely running in the main thread:

function CurrentThreadID: TThreadID;
begin
  Result := TThread.Current.ThreadID;
end;

function RunningInMainThread: Boolean;
begin
  Result := CurrentThreadID = MainThreadID;
end;

If it’s not running outside of the main thread, the only ideas that come to mind is a lack of remaining stack,. but that seems unlikely and should cause a stack overflow exception if that occurs.

If your event handler is calling back into the 3rd party DLL, maybe it’s not liking that for some reason and that’s then causing unexpected behaviour.

The custom message is a reasonable solution, so you probably don’t need to persist, but I’m curious as to what the actual problem is nonetheless :slight_smile:

Ah, I wasn’t using the ThreadID correctly before. The thread in the audio playback is indeed different to the main thread where the call to the graph update is.

I guess this means I’ve just been lucky until now that I haven’t had more issues. I’ll have to look into how to implement a better (thread correct) solution.

The custom message should work fine. Alternatively, you can just use TThread.Queue to run your code in the main thread.

TThread.Synchronize can be used if you need the graph to be updated before the event handler returns but that probably isn’t needed if the custom message is already working fine.

Cleaning up the graph isn’t time / sequence critical so the message works there as a fix for now.

I have had bug reports from users that show spurious GDI exceptions that could well be a symptom of a similar situation in other parts of my application.

So it seems like I need to look at the way those audio library events interact with my code and the GUI as that could be the root cause.

Wandering back on the canvas drawing, is there a way to overlay another TImage on the base one so I can draw the moving progress bar on that instead and not have to redraw the base image every 100ms ?

I looked at using a second TImage with the transparent property set, but the scrollbox wants to put the images end to end and not let me place it on top.

Am I barking up the wrong tree? Should I be looking at drawing the layers as separate bitmaps then copying them to the TImage in the order I want, omitting any that are not required?

Oh, for the thread issue I just changed the lower level audio event to use Synchronize and moved the event processing itself up a level (in the class I was casting to) so that it’s running in the main thread. Seems to be working well as I reverted back to using a direct call instead of the custom message and the updates are perfectly fine now. Checking the thread IDs in various places confirmed it. I learned a lot ! Thank you.

Why are you redrawing the TImage every 100ms. You should only need to draw the new updated area. With regards to the progressbar you could save what is behind it, when updating paint back what is behind it, and the draw the new progress bar.

I am not sure that Synchronize is the best option. Try dragging the window around while you are recording or playing back. I think that the Synchronize may cause the realtime thread to drop audio data, when the main thread is under heavy usage - like redrawing the window and other tasks.

The events that invoke Synchronize are usually at the end of the playback of the file.

Would Synchronize be any worse than the direct calling (with thread issues) I was previously doing in that respect?

If I understand correctly, TThread.Queue gets the target procedure processed the same as TThread.Synchronize, but doesn’t wait for it.

In that way it’s a similar situation to PostMessage vs SendMessage ?

Not in so far as running VCL code outside of the main thread - it will always run the code in the main thread.

IMO, Synchronize has a greater potential for inducing a deadlock if you’re also using mutexes, etc for thread synchronisation, but I think that it’s fairly safe to say that you’re not doing thread synchronisation at this point, so it’s probably not something that you need to be concerned about right now.

They’re functionally quite similar, but implemented in different ways with some specific idiosyncrasies that you probably don’t need to worry about. Generally, I’d say that TThread.Queue and TThread.Synchronize are nicer but it most likely won’t make any signficant difference for you which way you go.