Dessiner un rond sur une image au clic de souris

Résolu
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011 - 29 mai 2009 à 14:40
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011 - 2 juin 2009 à 16:47
Bonjour à tous,

Depuis ce matin, je cherche une solution pour dessiner un rond sur une image. J'ai réussi à le faire, par contre ce que j'aimerais c'est qu'il se fasse au clic de la souris et se place à l'endroit où a cliquer l'utilisateur.

L'idéal après serait que l'on puisse également déplacer ce rond pour ajuster le positionnement sur l'image. Et là, je suis vraiment perdu...

Voici déjà le code que j'ai pour dessiner le rond dans mon image :

procedure TFormGestion.JvImageCorpsHumainMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
    JvImageCorpsHumain.Canvas.Brush.Color := clBlue;
    JvImageCorpsHumain.Canvas.Font.Size := 15;
    JvImageCorpsHumain.Canvas.Ellipse(0,0,15,15);  //Ici je décide où je veux mettre mon ellipse

    JvImageCorpsHumain.Canvas.Brush.Color := clNone;
    JvImageCorpsHumain.Canvas.Pen.Width := 2;
end;

Donc logiquement, pour que mon ellipse se place à l'endroit du clic de la souris, au lieu des valeurs 0,0,15,15 de la fonction Ellipse, je mets :
    JvImageCorpsHumain.Canvas.Ellipse(Mouse.CursorPos.X - JvImageCorpsHumain.Left, Mouse.CursorPos.Y - JvImageCorpsHumain.Top,
    Mouse.CursorPos.X - JvImageCorpsHumain.Left + 15, Mouse.CursorPos.Y - JvImageCorpsHumain.Top + 15);
Ainsi je récupère les coordonnées de ma souris et je les soustraits aux coordonnées de mon objet Image pour que j'aie les coordonnées à l'intérieur de l'image, mais apparemment ça fonctionne pas, pask j'ai rien qui se dessine

Y'a un truc que je dois faire de faux, est-ce que qqun à une idée ?

Merci d'avance de votre aide
A voir également:

22 réponses

JulioDelphi Messages postés 2226 Date d'inscription dimanche 5 octobre 2003 Statut Membre Dernière intervention 18 novembre 2010 14
29 mai 2009 à 15:38
Salut
Tu as presque tout bon ! Tu dois aussi prendre en compte la place de la fenetre ! donc "- Form1.Left" et idem avec top.

Imagine tu es en résolution 800*600, ta souris se trouve en 400*300 (pile au milieu). Ta fenetre se trouve en 200*200 (haut gauche), ton image (quit fait 150*150, mais cela est pas important ici) en 10*10 (dans la fenetre, donc en 200+10*200+10 sur l'écran) et le clic de souris se fait en 20*20 dans l'image (donc en 20+10*20+10 selon le coin de la fenetre, soit en 200+20+10*200+20+10 selon le coin de la fenetre).

Petit screen (j'ai dessiné un curseur sous paint ^^) :

la souris se trouve en 20*20 sur le TImage
la souris se trouve en 40*40 sur la form
la souris se trouve en 200*200 sur mon écran.
si je clic ma souris aura comme coordonnées 260*260 sur mon écran
ça donne donc :
delphi > mouse.x - form.left - image.left = position exacte !
math > 260 - 200 - 40 = 20 !

Comprends tu ? ^^


a bientot
3
f0xi Messages postés 4204 Date d'inscription samedi 16 octobre 2004 Statut Modérateur Dernière intervention 12 mars 2022 34
30 mai 2009 à 13:17
fMsPos de type TPoint permet de recuperer la position de la souris sur la paintbox. cette variable est mise a jour dans l'evenement OnMouseMove de TPaintBox.

fMsSize de type integer est la taille (rayon) de l'ellipse dessinée pour remplacer le curseur de la souris. cela te permet de voir comment remplacer le curseur et d'en dessiner un autre.

fMsDown de type boolean (defaut = false) est la variable qui control le dessin de l'ellipse de selection, dessin si true.

fMsDPos de type TPoint est la position sauvegardée du click souris afin de savoir ou dessiner l'ellipse de selection.
fMsDown et fMsDPos sont initialisé dans la procedure OnClick de la paintbox.

fMsDSize de type integer est la taille (rayon) de l'ellipse de selection.

fMsPos, fMsSize, fMsDown, fMsDPos, fMsDSize sont privée et ne sont utilisée qu'en interne dans la classe TFormX.

fACTB type boolean (defaut = true) (fAutomaticConvertToBitmap) control la convertion automatique du Graphic contenu dans fPicture dans le Bitmap de fPicture. qu'on charge une image JPG, PNG, GIF ou BMP cela fonctionne a tout coups. par contre, si l'on desire avoir la transparence du PNG il faut invalider fACTB (=false) et activé fUGFX (=true) afin d'utiliser fPicture.Graphic plutot que fPicture.Bitmap.

fUGFX type boolean (defaut = false) indique si il faut utilise le Graphic de fPicture (=true) ou le Bitmap de fPicture (=false), pour fUGFX = false il est conseillé de mettre fACTB=true, elle se modifie via la propriété UseGraphic.

fPicture type TPicture, permet de charger une image dans n'importe quel format enregistré auprés de Delphi. fPicture nous permet d'avoir directement un composant fonctionnel non visible pour traiter les images. mieux que d'utiliser un TImage invisible ou une instance particuliere de TGraphic.
On placera donc notre image de fond dans fPicture via la methode LoadFromFile ou LoadFromResource.
fPicture s'utilise via la propriété Picture.

la procedure SetACTB permet de controler la modification de valeur de fACTB via la propriété AutomaticConvertToBitmap et de declencher automatiquement la convertion de Graphic vers Bitmap dans fPicture par la procedure ConvertPictureToBitmap si fACTB est passé a True.

la procedure SetPicture permet de controler la modification de fPicture via la propriété Picture, SetPicture declenche la ConvertToBitmap si fACTB=true.

les procedure contenue dans le gestionnaire d'evenement Paint de PaintBox, sont un exemple de possibilité, effectivement le code qu'elles contiennent pourrais etre placé directement dans la methode de dessin afin d'eviter plusieurs Call qui sont assé long a traiter.
On peut egalement, depuis Delphi 2005/2006 indiquer la directive Inline derriere les procedures, cette directive agit de la même façon que si nous copions le code directement dans la methode de dessin. Inline genere un code plus lourd mais egalement plus rapide. cela permet de controller correctement le rapport Poid/Performance de l'application.

on peu aisement remplacer SavePB et RestorePB par :

// Sauvegarde
OldPen.Assign(Pen);
OldBrush.Assign(Brush);

// Restauration
Pen.Assign(OldPen);
Brush.Assign(OldBrush);

on peu egalement appliquer ce principe a la plupart des objets contenus dans les composant, tel que TFont ou tout autre classe decendant de TPersistent, on pourrait egalement créer une liste pour appliquer un principe Push/Pop sur TPen et TBrush, mais la ça devient plus compliqué.

pour le reglage du timer :
40ms = 25 FPS
45ms = 22.2223 FPS (~23FPS)
50ms = 20 FPS

formules :
FPS = 1000/Reglage
Reglage = 1000/FPS désiré
 

il n'est pas utilise de descendre en dessous de 40ms. 50ms est un bon compromis fluidité/perf

Pour une fiche il y a deux façon d'indiquer des variables et methodes.
Qu'est ce qu'une fiche ? c'est tout simplement un composant dérivé de la classe TForm et il faut réellement la voir comme telle (une classe).

Si nous definissont des methodes et variables globale (partie implementation) cela crée des dependances qui ne sont pas forcement compatible avec les applications Threadées (voir l'aide delphi sur la directive ThreadVar).

On peu egalement donc considéré notre fiche comme une simple classe, un simple composant, auquel on vas ajouté des methodes, des variables et des propriétés. on utilisera donc les champs Private, Protected et Public de notre fiche, comme je l'ai fait dans ce cas.
a savoir que :
Private contiendra les variables (f) et methodes de propriété (Set et Get)  
Protected contiendra les methodes de traitement interne de la classe et les propriétés "cachés" si besoin
Public contiendra les methodes de traitement et propriétés visibles.

 

<hr size="2" width="100%" />
3
f0xi Messages postés 4204 Date d'inscription samedi 16 octobre 2004 Statut Modérateur Dernière intervention 12 mars 2022 34
29 mai 2009 à 15:35
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    PaintBox1 : TPaintBox;
    Timer1    : TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure PaintBox1Click(Sender: TObject);
  private
    fMsPos   : TPoint;
    fMsSize  : integer;
    fMsDown  : boolean;
    fMsDPos  : TPoint;
    fMsDSize : integer;
    fACTB    : boolean;
    fUGFX    : boolean;
    fPicture : TPicture;
    procedure SetACTB(const Value: Boolean);
    procedure SetPicture(Value: TPicture);
  protected
    procedure ConvertPictureToBitmap; virtual;
  public
    property AutoConvertTobitmap : boolean  read fACTB    write SetACTB default true;
    property UseGraphic          : boolean  read fUGFX    write fUGFX   default false;
    property Picture             : TPicture read fPicture write SetPicture;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  fUGFX   := false;
  fACTB   := true;

  fMsPos.X  := 0;
  fMsPos.Y  := 0;
  fMsSize   := 3;
  fMsDown   := false;
  fMsDPos.X := 0;
  fMsDPos.Y := 0;
  fMsDSize  := 7;

  DoubleBuffered := True;

  fPicture := TPicture.Create;
  fPicture.LoadFromFile('F:\WKDelphi\ImgProcessSample.bmp');

  PaintBox1.Cursor := crNone;
  // ----------

  { your code here !}
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  { your code here! }

  // ----------
  fPicture.Free;
end;

procedure TForm1.SetACTB(const Value: Boolean);
{ do not modify }
begin
  if Value <> fACTB then
  begin
    fACTB := Value;
    if fACTB then
      ConvertPictureToBitmap;
  end;
end;

procedure TForm1.SetPicture(Value: TPicture);
{ do not modify }
begin
  fPicture.Assign(Value);

  if fACTB then
    ConvertPictureToBitmap;
end;

procedure TForm1.ConvertPictureToBitmap;
{ do not modify }
begin
  if fPicture.Bitmap.Empty then
  begin
    fPicture.Bitmap.Width := fPicture.Width;
    fPicture.Bitmap.Height:= fPicture.Height;
    fPicture.Bitmap.PixelFormat := pf32bit;
    fPicture.Bitmap.Canvas.Draw(0,0,fPicture.Graphic);
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  { code before draw, here! }

  // ----------
  PaintBox1.Refresh;
  // ----------

  { code after draw, here! }
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
{ do not modify }
var GfxObj   : TGraphic;
    OldPen   : TPen;
    OldBrush : TBrush;

    procedure SavePB(const SavePen: boolean=true; const SaveBrush: boolean=true);
    begin { Save canvas Pen or Brush or both }
      if SavePen   then OldPen.Assign((Sender as TPaintBox).Canvas.Pen);
      if SaveBrush then OldBrush.Assign((Sender as TPaintBox).Canvas.Brush);
    end;

    procedure RestorePB(const RestorePen: boolean=true; const RestoreBrush: boolean=true);
    begin { Restore canvas Pen or Brush or both }
      if RestorePen   then (Sender as TPaintBox).Canvas.Pen.Assign(OldPen);
      if RestoreBrush then (Sender as TPaintBox).Canvas.Brush.Assign(OldBrush);
    end;
begin
  OldPen   := TPen.Create;
  OldBrush := TBrush.Create;
  try
    with (Sender as TPaintBox).Canvas do
    begin
      // Draw picture
      case fUGFX of
        false: GfxObj := TGraphic(fPicture.Bitmap);
        true : GfxObj := fPicture.Graphic
      end;

      if not GfxObj.Empty then
        Draw(0,0,GfxObj);
      // ------------

      { over picture and under selection ellipse, draw here! }

      // Draw selection ellipse
      if fMsDown then
      begin
        SavePB;
          Pen.Width   := 2;
          Pen.Color   := clBlue;
          Brush.Style := bsClear;
          Ellipse(fMsDPos.X-fMsDSize, fMsDPos.Y-fMsDSize,
                  fMsDPos.X+fMsDSize, fMsDPos.Y+fMsDSize);
        RestorePB;
      end;
      // ------------

      { over picture and selection ellipse, draw here! }

      // Draw mouse cursor
      SavePB;
        Pen.Color   := clGray;
        Brush.Color := clGray;
        Ellipse(fMsPos.X-fMsSize, fMsPos.Y-fMsSize,
                fMsPos.X+fMsSize, fMsPos.Y+fMsSize);
      RestorePB;
    end;
  finally
    OldPen.Free;
    OldBrush.Free;
  end;
end;

procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
{ Do not modify }
begin
  // Get current mouse cursor pos
  fMsPos.X := X;
  fMsPos.Y := Y;
end;

procedure TForm1.PaintBox1Click(Sender: TObject);
{ Do not modify }
begin
  // Activate mouse selection ellipse
  fMsDown := true;
  // Save current mouse cursor pos
  fMsDPos := fMsPos;
end;

end.




<hr size="2" width="100%" />
0
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011
29 mai 2009 à 15:51
Oui j'ai tout compris, merci JulioDelphi !
En fait j'avais complètement oublié de prendre en compte mon panel...

Merci pour ton explication, elle est vraiment claire !

Autrement, pour la réponse de foxi j'vais étudier ça, parce que je vois qu'il y explique comme déplacer l'ellipse, mais ça a l'air assez compliqué... J'reviens si j'ai des questions....

Merci encore !
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
f0xi Messages postés 4204 Date d'inscription samedi 16 octobre 2004 Statut Modérateur Dernière intervention 12 mars 2022 34
29 mai 2009 à 15:59
mais non c'est trés simple!
tu verra.

<hr size="2" width="100%" />
0
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011
29 mai 2009 à 16:08
Foxi,

Ta procédure SetACTB permet la transformation en bitmap juste ?

Pourquoi l'avoir appelé comme cela, ACTB correspond à qqch ?

Et le timer permet de rafraîchir tout le temps l'image ? C'est pas mauvais d'un point de vue ressources utilisées ?
0
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011
29 mai 2009 à 16:30
Encore une question pour le code à foxi

Dans l'évènement PaintBox1Paint, tu as mis dans les déclarations de variables des procedure !?
J'ai jamais vu ça, tu pourrais m'expliquer pourquoi tu fais cela ?

P.S. Je code depuis quelques mois en Delphi... je faisais du Java et un peu de C# avant...
0
JulioDelphi Messages postés 2226 Date d'inscription dimanche 5 octobre 2003 Statut Membre Dernière intervention 18 novembre 2010 14
29 mai 2009 à 16:38
Tu déclares toutes tes variables dans la form ? sous le "var Form1: TForm;" ??
Ça marche aussi mais en déclarant juste pour la procédure, la variable n'est déclarée en mémoire QUE quand la procédure est appelée et quand la procédure a fini, la variable est "supprimée" de la mémoire :]

Une métaphore : Pourquoi quand tu vas faire un contrôle de Math, tu ne révises que les maths et pas aussi la géo, le droit, la compta etc ... car tu sais que tu n'en auras pas besoin à ce moment, idem ici ^^
0
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011
29 mai 2009 à 16:45
Non, bien sur que je déclare mes variables dans les procédures en général , mais là il a déclaré une procédure à l'endroit où on déclare les variables d'habitude.

Voici le code :

procedure TFormGestionPlaies.PaintBox1Paint(Sender: TObject);
{ do not modify }
var GfxObj   : TGraphic;
    OldPen   : TPen;
    OldBrush : TBrush;

    procedure SavePB(const SavePen: boolean=true; const SaveBrush: boolean=true);
    begin { Save canvas Pen or Brush or both }
      if SavePen   then OldPen.Assign((Sender as TPaintBox).Canvas.Pen);
      if SaveBrush then OldBrush.Assign((Sender as TPaintBox).Canvas.Brush);
    end;

    procedure RestorePB(const RestorePen: boolean=true; const RestoreBrush: boolean=true);
    begin { Restore canvas Pen or Brush or both }
      if RestorePen   then (Sender as TPaintBox).Canvas.Pen.Assign(OldPen);
      if RestoreBrush then (Sender as TPaintBox).Canvas.Brush.Assign(OldBrush);
    end;
begin
  OldPen   := TPen.Create;
  OldBrush := TBrush.Create;

...... suite de la fonction......

Vous voyez ce que je trouve étrange ?
0
JulioDelphi Messages postés 2226 Date d'inscription dimanche 5 octobre 2003 Statut Membre Dernière intervention 18 novembre 2010 14
29 mai 2009 à 16:58
Meme explication, ces procédures (SavePB et RestorePB) ne sont utilisées QUE dans PaintBox1Paint, donc, pas besoin de les "sortir".
Métaphore : Tu as bien compris qu'il ne faut réviser QUE les maths pour le controle de math, mais vas-tu apprendre toutes tes leçons de maths ? non ! ^^

Mais pourquoi avoir fait des procédures et pas simplement mis ce code ? Car il fait plusieurs fois la même chose, donc au lieu de copier coller du code, il a créé des procédures.

;)
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 mai 2009 à 17:41
Salut,

Pour compléter un peu les explications de Julio...

On appelle cela des fonctions/procedures imbriquées.
L'avantage est que toutes les variables déclarées localement dans le bloc de PaintBox1Paint sont visibles par SavePB et RestorePB.
Par exemple, tu peux constater que la variable locale OldPen est utilisée par les deux procedure imbriquées SavePB et RestorePB, bien que n'étant pas une variable globale.
On pourrait dire qu'elle est "localement globale".  
En fait, une donnée déclarée localement dans un bloc est visible dans ce bloc et dans tous les blocs imbriqués dans celui-ci. Mais pas ailleurs!
Cela permet une meilleure optimisation des ressources ( et sur ce point, tu peux faire confiance à f0xi :).

Ces autorisations d'accès aux données situées dans des blocs imbriqués sont contenues dans la notion de règle de visibilité dans les langages à structure de bloc et ne concerne pas que le Pascal.
0
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
29 mai 2009 à 18:23
Précision complémentaire aux procédures et fonctions imbriquées

il faut savoir que le temps d'exécution est plus important avec les méthodes imbriquées qu'avec une répétitions de code dans la procédure principale !!!

on évitera donc d'utiliser cette méthode pour une procédure qui doit rafraichir souvent une image avec beaucoup de calculs où tout autre ou la vitesse est primordiale.

 
@+
Cirec

<hr siz="" />
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 mai 2009 à 18:48
... Ce qui d'ailleurs, en ce qui concerne le temps d'exécution, est valable pour tous les appels de procedure ou de fonction, qui sont à éviter tant que le code reste maintenable. Fragile équilibre entre l'optimisation et la lisibilité...  
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 mai 2009 à 19:34
Effectivement.
C'est là où je voulais en venir...       lolllll

Nan. C'est pas vrai !  Mais merci quand même, Julio.
   
0
madcap Messages postés 77 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 22 février 2011
30 mai 2009 à 14:55
Merci pour tous vos conseils !

Je vais regarder tout ça en début de semaine quand je serai au boulot, parce que là j'ai un week-end chargé ^^

Cette communauté Codes Sources est une mine d'or ! Merci à vous tous qui passez du temps à répondre à nos questions.

Hier avant de partir du job, j'ai implémenté la solution à Foxi, et ça me semblait bien tourner, mais je n'ai pas eu le temps de tout bien analyser.

Donc en début de semaine vous aurez de mes nouvelles
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
30 mai 2009 à 15:06
Ouais...
Il faut reconnaître que le code de f0xi a une beauté troublante.

Il n'empêche que beaucoup de débutants ou même de développeurs confirmés se seraient tournés vers TImage plutôt que vers TPaintBox.
 
Souvent je me dis que TPaintBox a été inventée pour f0xi et d'ailleurs, perso, je l'appelle le « compo à f0xi »...    

PS :   [Hors sujet]
En parlant de beauté troublante, je me demande si c'est pareil chez vous...
Depuis quelques temps j'ai la pub " PhotothèqueNouveau ! " qui ne me montre que des asian sexy girls... et c'est pas fait pour augmenter les rendements, je trouve...
- Est-ce un truc à la Google qui cible la pub par rapport à l'internaute?
- Et dans ce cas, comment Nix connaît mon goût pour les cultures asiatiques? 
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
30 mai 2009 à 17:22
Et dans ce cas, comment Nix connaît mon goût pour les cultures asiatiques? 

C'est pas dur ... les 3/4 des hommes aiment les asiatiques
et les 95% des informaticiens (ou des gars touchant à l'informatique ) aiment les asiatiques
donc place des asiatiques quelques part (pour peu qu'elles soient belles), et on cliquera dessus

Si en plus de ca , les catégories sont "Gorgeous asian girl in the water", c'est vraiment plus du jeu !!!
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
30 mai 2009 à 17:35
Bouton spécial informaticiens :

CLIQUEZ ICI !
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
30 mai 2009 à 17:42
... Reste quand même 5% des mecs dont on se demande ce qu'ils foutent de leur journée... 
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
30 mai 2009 à 18:46
mieux vaut l'ignorer
0
Rejoignez-nous