Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Two questions about drawing on a TCanvas. 2

Status
Not open for further replies.

BobbaFet

Programmer
Feb 25, 2001
903
NL
Hi all, thanks for reading this.

Here's the deal: I'm making an application that draws random figures on a random form using random styles sizes and everything. Just for the fun of it. Big fan of pretty colors hahaha.

Here's my first question, I'm using the RGB format to generate the colors by using Random(255) three times and then I convert it to a TColor using the RGB method provided by Borland. Now if I want to exact opposite color I do this:

Code:
function NegativeColor(R,G,B: byte): TColor;
begin
R := 255 - R;
G := 255 - G;
B := 255 - B;
Result := RGB(R,G,B);
end;

But somehow this seems off to me, for example if the original color was a kind of green then it will result in a brownish color for the negative color, it just doesn't seem right to me, can anyone shed any light on this?

Second question: At first I was using TPaintBox but for some reasom it wouldn't show the TPen.Style (anyone know why that is btw?) that was applied so now I just directly draw to the Self.Canvas. But for some reason if I draw for example 10 figures and something pops over it, it will only show the last drawn figure when the form is on top again. Can anyone explain to me how I can make it remember all 10 drawings?

I suppose I could use CopyRect to copy it to a bitmap and then re-apply it using the Form's OnShow event but I feel I shouldn't have to do that (if it even works, haven't tried yet).

Thanks for reading my questions, bigger thanks if you bother with answering them ;-)

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Well, I've come up with the following solution:

I found that drawing on a TImage is in fact just as fast as drawing TForm until you start refreshing it, so what I've done now is use the Self.Canvas during generation and after this is done I'll show the TImage (which is hidden until then) and I get all the desired effects!

I would like to thank Buho for this idea and Stretchwickster for his excellent TRecord idea, although I am not going to use it for this application, I'm very certain that it will come in handy in the future!

Thanks guys!

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Just a note on my code above:
The record was a quick and dirty way of creating a TRandomShape. Ideally, TRandomShape would be defined as a class with appropriate member variables and its own methods (in its own unit) - resulting in true encapsulation (OOP). The unit could then be included wherever it was needed and a TRandomShape could be instantiated in many projects!

Clive
Runner_1Revised.gif

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"To err is human, but to really foul things up you need a computer." (Paul Ehrlich)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To get the best answers from this forum see: faq102-5096
 
A little update, having threaded this program and not using the OnPaint event you can not use Synchronize(Paint); to make sure the canvas isn't locked out.

TIP: When doing this like I am, use Synchronize(*****.Canvas.Refresh);, you get the same effect :)

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 

----------------------------
Warning: your tip is wrong!
----------------------------


In a multithread application, you need to be sure that only one thread is working on the canvas. You are not going to comply synchronizing Refresh with Synchronize.

To do the things right, use Canvas.Lock/Canvas.Unlock: lock your canvas, draw on it and unlock it.

Forget Synchronize. It never worked for multithread drawing. The example in the Delphi Demos\Threads folder is a very bad written application.

buho (A).

Note: from the Delphi help: "TCanvas.Refresh: Deselects the Pen, Brush, and Font from the device context." How are you going the have thread safeness synchronizing a call to this method?

 
I read that differently: it says "Deselects the Pen, Brush, and Font from the device context." Effectively it says according to me "you can't draw during a refresh".

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
I think you are very confused here.

Lets say you have two threads working in the same canvas. You need to assure that they do not interfere themselves.

Imagine the threads are trying to:

a) change the pen
b) change the brush
c) change the font
c) draw a text in the canvas
d) draw a line in the canvas


Thread A starts, it makes "a", "b", "c" and is interrupted in "d".

Thread B gets the control, it makes "a, "b", "c" and is interrupted in "d".

Now thread A regains control and continues "d".

Thread A is going to draw the text and the line with the pen, brush and font realized (selected) for thread B!

But not only that; due to the way canvases works, you will have resource leaking and possible access violations.

The only way to solve the issue is locking the canvas before starting the work and unlocking it when done:

lock the canvas
do a, b, c, d
unlock the canvas

Synchronizing (with Lock or Synchronize) only one action (like a or b or c etc) is not going to work. Synchronizing in a per-action basis can save you from the resource leaking and the access violations, but not from having your drawings objects changed.

Synchronizing with Synchronize the whole block of actions can work, but it defeats the multithreading (you are serializing your threads on the main one). That was the only way in the age when the Delphi TCanvas was not lockable, but today it is lockable.

BobbaFet said:
I read that differently: it says "Deselects the Pen, Brush, and Font from the device context." Effectively it says according to me "you can't draw during a refresh".

Sorry, you are totally wrong. Actually I fail to see what relation you think exists between Refresh (and object deselection) and thread safeness/synchronization. They are not related in any way.

buho (A).











 
no pen/brush... no drawing... simple as that

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
If you are not drawing on your canvas, what are you doing with it?

buho (A).
 
refreshing it

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Are you kidding? If so, lets end this here.

buho (A).
 
nope, refreshing the form causes the drawing to be shown, so the drawing has to stop (in TImage's case) for it to be shown. therefore in TImage's case, this works.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
BobbaFet said:
A little update, having threaded this program and not using the OnPaint event you can not use Synchronize(Paint); to make sure the canvas isn't locked out.

TIP: When doing this like I am, use Synchronize(*****.Canvas.Refresh);, you get the same effect :)

Your tip was about refreshing a TCanvas

BTW: TCanvas.Refresh deselects the objects only, it is not like TImage.Refresh, which fires a repaint.

Lets get back to the start: synchronizing a TCanvas.Refresh is not a way to assure thread safeness.

Multithread issues are about every and all operations on the canvas, not only about those which shows the canvas.

I must confess I fail to understand your other posts about not using pens or brushes and not drawing on the canvas, or about the form refresh stopping the drawing.

buho (A).
 
Well, ok here's how I threaded it:

I got the main app thread and a ThPaintStuff thread made in the normal. All the generating of the randoms (located in ThPaintStuff.Execute;) and the actual painting (in ThPaintStuff.PaintStuff;). Now if I don't use synchronize the app will crash, I am NOT using the OnPaint event as it is also drawn to a TImage which is made visible after all the drawing has been done for a permanent drawing, if I synchronize it with Form.Paint then nothing happens as I am not using the onPaint event (don't me wrong, I think that should have worked but for some reason it doesn't). The only thing that I could find that actually made it work is using the Synchronize(Form1.Refresh);

Now tell, what should I do other than Synchronize(Form1.Refresh);. I would be more than happy to post the entire project on my website for you to download so you can check it out and see what I am doing wrong and therefore causing Synchronize(Form1.Refresh); to work.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
1) When you have ill-synchronized threads, any line of code can change the application behaviour and hide it. You have your application crashing, you add some stupid and unrelated code and Yeah! No more crashes! But the problem is still there and it is going to bite you sooner or later.

2) Any time a worker thread touchs a canvas, lock/unlock it. Lock the canvas, do your work and unlock the canvas. Lock EVERY canvas, on-screen ones a off-screen ones.

3) If the canvas is a on-screen canvas (like the TForm canvas or the canvas of a TImage dropped in a form) probably it will be repainted as sooner the main thread regains control. If not, "Invalidate" it. (see Note 1 below, please).

4) If the canvas is an off-screen canvas (like a TBitmap), have a way for the MAIN thread to copy it in the on-screen canvas. The quick way is to code the bitmap copy operation in the form OnPaint method and send a WM_PAINT message to the form.

5) My best advice is not to mess with visual object canvases (like TForm, TImage, TPaintBox) in a worker thread.

Note 1: Invalidating from a worker thread. As I never work with on-screen canvases in worker threads, I have not direct experience with this, but I think it will work. The "Invalidate" method calls the Windows API function InvalidateRect, passing it the control parent handle. In turns, Windows generate a WM_PAINT message to said parent. This message will be read for the app in the main thread context.

buho (A).
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top