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!');
 
Hey

In response to your first question: What exactly is the opposite of a colour? Sure, opposite of white is black, but what do you imagine is the opposite of green/yellow/purple for that matter?

Just a thought. Sorry but I have no idea how to answer your second question.

Cheers
 
you know photos and their negatives? like that. I don't know why it seems off, just gut feeling tells me its wrong.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Does your program just convert shapes? If you can, test it out on a photograph, I'm sure that you will then see that your program is absolutely right
 
Bobba
Remember that the colours in a TColor are simply the lower 24 bits of a 32 bit number.
For most purposes the top 8 bits can be ignored.
but you can change the colours with a bit of simple bit masking.
ClGreen is $XX00FF00
e.g invert bits
ClNew := ClGreen xor $FFFFFF;
= XXFF00FF (magenta)

To be honest your code should do this anyway try it with something that give a different result colour

Inverting Cyan $XXFFFF00 should give you Red $XX0000FF


Majenta can be a problem (look brown) on some monitors

I just tried this and it looks fine (a nice fucia type purple)
Code:
  graphic.Color := $0000FF00;
  graphic.Color := Graphic.Color xor $00FFFFFF;


Steve: Delphi a feersum engin indeed.
 
Well, I'll just have to be satisfied with the fact that this function at least gets black and white opposites right and I'll just assume that the rest is correct too.

Update on question two: I've come to use a TImage now and it does remember all the individual drawings I make, only it takes terribly long now, anyone got a suggestion on how to handle that? (When drawing to the canvas of a TForm, my computer can do (with all the randomizing/generating on) about 100.000 drawings in about 3 seconds, but now it takes forever as I have use Image1.Refresh; in order to show each individual drawing as it is made before it continues to the next drawing.

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

In the past, I've put my drawing code in TPaintBox.OnPaint event - this creates a persistent drawing.

Not sure if you know this but there is a difference between painting and drawing, see this link for more info:

Hope this helps.

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
 
Well, that document isn't correct when you use a TImage, in Delphi 7 (what I use) TImage does not have an OnPaint event, yet anything I draw will stay there even after refreshing the Image or put objects over it and then hiding it, they will be there. On the other hand, I have tried painting using the TForm itself with the OnPaint event handler and only the last generated painting would be re-applied.

OnPaint doesn't seem to cause the form to actually remember what was painted on it, but rather it just re-applies it. The problem comes when you paint say 100 shapes on the form and something pops over it then only the last shape painted will be re-applied.

And that is where the problem lies, I want it to remember the other 99 as well. TImage does this but it terrible slow, TPaintBox doesn't and further more doesn't apply the set TPen.Style. So I think I am pretty much stuck with TImage dispite it being so slow.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Not sure I understand but are you trying to implement an Undo function.
I dont know what you mean by TImage remembering, do you mean Timagelist? You could store consequtive stages of 'the drawing' in one of these.



Steve: Delphi a feersum engin indeed.
 
With remembering I am mean once drawn it stays put. No matter wether object appear over it or not. I am not trying to make an undo function.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Well in Delphi 6, using a TPaintBox, as long as the commands for drawing each shape are issued from with TPaintBox it will "remember" because it redraws what is in OnPaint every time Windows requires the TPaintBox to be repainted.

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
 
Yes but if you draw 2 shapes in a row and something pops over it, it will not repaint the first one.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
It should do if the commands to draw both of those shapes are called via the OnPaint event.

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
 
For example, a while back I wrote a custom chart class descending from TPaintBox. In the OnPaint event I called a function called Draw, passing to it the TPaintBox's canvas.

In that Draw function I call a number of other functions such as:
- DrawChartTitle
- DrawAxesAndLabels
- DrawAxesMarks
- DrawGrid
and to each I pass the TPaintBox's canvas.

Finally, I draw a series of points stored in an array using the Polyline function.

Even when a window is passed over it, when the OnPaint event is next called it redraws the entire chart by calling the above functions.

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
 
They are and the variables generated are stored in private section of the form but as I overwrite the previous variables when drawing another figure it only repaints the last, so therefore I need something that "remembers" what was drawn before the last figure and TImage does that (without an OnPaint event handler mind you). Only thing is, drawing on a TImage is terribly slow compared to drawing on a Form.Canvas.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Is there any way to build up the "variables generated" into an array so that an OnPaint event handler can rattle through the array drawing each item, therefore "remembering" everything?

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
 
Perhaps it's helpful to show you how I draw the figures:

(typing from memory now)

Code:
procedure DrawStuff;
var i: integer;
var R,G,B: byte;
begin
randomize; // ok this is actually in the FormCreate event
// Generate random color
for i := 0 to seDrawNo.Value do // seDrawNo = spinedit
  begin
  L := Round(Random(Form1.Width)); // Private decl
  T := Round(Random(Form1.Height)); // Private decl
  R := Round(Random(255));
  G := Round(Random(255));
  B := Round(Random(255));
  MyColor := RGB(R,G,B); // Private decl
  Form.Paint;
  end;
end;

procedure Tform1.FormPaint(Sender...);
begin
case Random(2) do
  0: Self.Canvas.Rectangle(L,T,20,20,mycolor);
  1: Self.Canvas.Ellipse(L,T,20,20,mycolor);
  end;  
end;

To give you an idea of how I draw them. Like this, it will only remember the last figure for a Self.Canvas, but all for a TImage.Canvas

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Yes, I could just put everything in an array, wouldn't be too much trouble.

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
BobbaFet, try this example I knocked up:
Code:
unit uRandomPaint;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Spin;

type
  TRandomShape = record
    L, T: Integer;
    MyColor: TColor;
    ShapeType: Integer;
  end;

  TForm1 = class(TForm)
    SpinEdit1: TSpinEdit;
    Button2: TButton;
    procedure Button2Click(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    Shapes: Array of TRandomShape;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
var
  i: integer;
  R,G,B: byte;
  shape: TRandomShape;
begin
  SetLength(Shapes, 0);
  // Generate random color
  for i := 0 to SpinEdit1.Value - 1 do // seDrawNo = spinedit
  begin
    shape.L := Round(Random(Form1.Width)); // Private decl
    shape.T := Round(Random(Form1.Height)); // Private decl
    R := Round(Random(255));
    G := Round(Random(255));
    B := Round(Random(255));
    shape.MyColor := RGB(R,G,B); // Private decl
    shape.ShapeType := Random(2);
    SetLength(Shapes, i + 1);
    Shapes[i] := shape;
  end;
  Repaint;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to Length(Shapes) - 1 do
  begin
    Self.Canvas.Brush.Color := Shapes[i].MyColor;
    case Shapes[i].ShapeType of
      0: Self.Canvas.Rectangle(Shapes[i].L, Shapes[i].T, 20, 20);
      1: Self.Canvas.Ellipse(Shapes[i].L, Shapes[i].T, 20, 20);
    end;
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  randomize; // ok this is actually in the FormCreate event
  SetLength(Shapes, 0);
end;

end.
You could always change the contents of Button2Click such that you don't clear the array first. Thus you will retain a record of the previous shapes drawn.

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
 
For a general solution, write everything you are drawing in a TBitmap and blit the TBitmap on the form canvas after drawing *and* in the OnPaint event (the technique is called "off-screen back buffer").

Alternatively, you can use Invalidate to invalidate the form canvas after every writing to the TBitmap instead of bliting the TBitmap. Windows will send a paint message to the form, which in turn will fire the OnPaint event.

The OS will merge different paint messages in the queue leaving only the last one, so calling Invalidate a lot of times will not have such a performance hit as it appears. However, remember that calling Invalidate will take some time due to the call itself (don't call it in an inner loop).

buho (A).



 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top