THREAD ET BITMAP (DESSIN AU CRAYON)

djmmix Messages postés 152 Date d'inscription lundi 28 juillet 2003 Statut Membre Dernière intervention 29 avril 2009 - 26 août 2009 à 18:21
cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 - 31 août 2009 à 11:46
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/50482-thread-et-bitmap-dessin-au-crayon

cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 1
31 août 2009 à 11:46
J'ai oublié une ligne plus haut:

procedure TForm1.FormClose;
begin
NeedUpdate:=False;
MyThread.Terminate;
Event.SetEvent;
MyThread.WaitFor;
FreeAndNil(MyThread);
end;
cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 1
31 août 2009 à 11:45
Pour mettre un thread en standby généralement on utilise un TEvent. Exemple "caricatural":

var
Event:TEvent;
NeedUpdate:Boolean;
MyThread:TMyThread;

procedure TForm1.FormCreate;
begin
Event:=TEvent.Create(nil,False,False,'');
MyThread:=TMyThread.Create;
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
NeedUpdate:=True;
Event.SetEvent;
end;

procedure TForm1.FormClose;
begin
NeedUpdate:=False;
Event.SetEvent;
MyThread.WaitFor;
FreeAndNil(MyThread);
end;

procedure TMyThread.Execute;
begin
while not Terminated do begin
Event.WaitFor(INFINITE);
if NeedUpdate then begin
NeedUpdate:=False;
ProcessData;
end;
end;
end;
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
31 août 2009 à 11:27
Bon, merci bien pour ces pistes mais une question me viens.

Dans mon cas, comment faire en sorte que le thread s'arrête et affiche le résultat OU annule le traitement et recommence avec de nouvelles valeurs. Dans mon cas, il tourne en boucle, mais est-il possible de mettre le thread en standby une fois terminé OU le forcé à s'arrêter pour le redémarrer tout de suite.

En clair, dans mon évenement OnChange de ma scollbar, il y a deux cas :
1 - mon thread est en plein calcul, il faut le stopper et le relancer
2 - mon thread est en standby, il faut juste le redémarrer

Autre question, si mon thread sort de la fonction Execute, il suffit de faire un RESUME pour relancer cette fonction ou il y a autre chose à faire...

Bien sur, en fonction de toutes ces réponses, je metterai en ligne la version modifier et "optimum"

Barbichette
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
28 août 2009 à 03:55
Salut,

Hélas, je n'ai pas le temps d'approfondir pour le moment. :s
Mais il me semble bien que si tu regardais vers l'événement OnTerminate du thread, tu y trouverais ton bonheur, Barbichette.

Je ferais une petite démo sur la synchro Thread-secondaire/Thread-principal et Thread-secondaire/thread-secondaire dès que j'aurais un peu plus de temps.
Le sujet est vaste. Et qu'il s'agisse de Canvas ou d'autre chose n'a pas beaucoup d'importance de toute façon.

Cirec et Forman ont raison : c'est un problème de Thread, pas de bitmap dans ton cas.
Lock/Unlock, c'est qu'une section critique et ça ne devrait être réservé qu'à la synchro de 2 Threads secondaires selon moi. Entre le Thread principal et 1 thread secondaire il faut faire plus simple. Et, de façon générale, la synchronisation n'a d'intérêt que quand il y a possibilité de conflit. Ce qui n'est pas le cas dans ton code à première vue.

Autre chose :
Forman écrit « si tu dessines un Bitmap qui est en train d'être modifié dans un autre thread, sans ces sections critiques tu vas afficher le bitmap qui est en cours de traitement, donc incomplet »

Mais c'est beaucoup plus grave que ça !
Imaginons le traitement suivant dans le 1er Thread :

If PixelColor <> 0 then begin
X := Y/PixelColor;
end;

Et dans le 2ème thread :
...
PixelColor := 0;
...

Dans le cas du multi-threading, il peut très bien arrivé que le Scheduler de Windows interrompe le 1er Thread entre la 1ère et la 2ème ligne... Puis donne la main au second Thread, et enfin revienne au 1er.
Là, c'est une belle division par zéro !
Et bonjour pour débuger ce genre d'erreur.
Donc, 'faut toujours prendre énormément de précautions avec les threads.

Encore autre chose :
le multi-threading, c'est bien pour simuler un semblant de pc multi-tâche. Mais, de nos jours, il y a de plus en plus de pc MultiCore. Il faut donc coder pour que, par exemple, ton traitement d'image se fasse sur un processeur dédié et ne pas laisser à la fantaisie de Windows de jongler avec tout ça.
Imagine ton application qui tourne sur un core et ton traitement d'image sur un autre. Là, ça devient un vrai traitement parallèle avec tous les bénéfices en terme de temps de traitement!
On se rapproche là du cerveau humain, de l'inconscient, etc...
'tain! Y'a encore de quoi faire, et j'ai l'impression que ma vie sera trop courte... :'(

Enfin, content d'avoir pu rencontrer mes bons amis ! :)))
Et j'espère pouvoir revenir bientôt sur ce site (s'il ne sombre pas dans la médiocrité de ceux qui n'y voient qu'un outil).
cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 1
27 août 2009 à 17:35
Une bonne idée pour ce genre de traitement ça peut être aussi de travailler sur un DIBSections, c'est un analogue GDI du HBITMAP "device dependant" usuel, sauf que là on a accès directement aux données du bitmap, sous la forme d'un tableau de RGBQuad par exemple. Du coup, en modifiant directement cette mémoire (sans avoir à passer par scanline) on modifie le bitmap sous-jacent de manière très rapide, et en plus on peut dessiner dessus de manière classique avec les fonctions GDI. Il faut éventuellement rajouter une section critique pour les accès concurrents, ainsi que GDIFlush() pour s'assurer que les traitements GDI sont bien finis avant de lire/écrire directement dans la mémoire.

Mais comme tu le dis Cirec, l'architecture des threads (oserais-je dire le tissage) passe avant tout et doit être rigoureusement pensée pour éviter au maximum d'avoir à verrouiller des ressources.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
27 août 2009 à 16:38
ce qui a été dit avant n'est pas faux pour autant ;)

oui "basiquement" ce n'est qu'un compteur qui s'assure qu'il n'y ait qu'un seul accès à la fois au Canvas en question

L'utilité de Lock/Unlock n'est justifié que si il y a plusieurs accès possible au Canvas et pour éviter que l'un n'efface ce que l'autre viens de dessiner on Lock le Canvas ...
à ce propos il faut impérativement s'assurer que le Canvas soit "Unlocké" sous peine de problème d'accès .... il est donc préférable d'utiliser un block "try finally end" pour être certain du déverrouillage du Canvas à la fin du traitement.

Mais encore une fois ici tout se passe dans le même Thread et il n'y a qu'un seul accès au Canvas à la fois ... codé autrement il n'y a pas besoin de Locker/Unlocker le Canvas !!! :p
cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 1
27 août 2009 à 15:35
Au temps pour moi.

Après relecture de Graphics.pas, je constate que la procédure Lock/Unlock de TCanvas se contente de verrouiller/ouvrir des sections critiques, ainsi que de gérer un compteur interne (LockCount). Dans ton cas je pense que si tu dessines un Bitmap qui est en train d'être modifié dans un autre thread, sans ces sections critiques tu vas afficher le bitmap qui est en cours de traitement, donc incomplet, ce qui explique que "ça ne marche pas sans Lock/Unlock".
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
27 août 2009 à 15:15
Salut,

je suis d'accord avec ce que dit Forman mais dans cas présent le canvas à "Locker" est crée et utilisé dans le même Thread.
Donc une construction différente du projet s'impose (si construit comme l'exemple donné sur le forum pas besoin de Locker)

D'ailleurs ici un seul Lock est utile (celui du Bitmap "thfinal") dans la procedure "Execute"

ensuite si tu tiens à conserver cette construction de code il ne faut pas utiliser
"while not Terminated do"
en effet avec ce code, le dessin tourne en boucle sur lui-même alors qu'un seul appel à la procédure donnerait le même résultat !!! (rien qu'avec ce changement le code tourne plus vite)

autre chose très importante ... tu crées un nouveau Thread à chaque nouvelle image mais l'ancien n'est jamais détruit !!! (2 Bitmap et un Thread jamais détruit à chaque nouvelle image)
Si tu veux que le Thread se libère automatiquement à la fin il faut déclarer, dans son constructeur (par Ex.), : "FreeOnTerminate := True"

Sinon ... pareil ...
je trouve aussi l'effet bien réussit et avec peu de code ... Bravo
cs_Forman Messages postés 600 Date d'inscription samedi 8 juin 2002 Statut Membre Dernière intervention 6 avril 2010 1
27 août 2009 à 13:54
La méthode TBitmap.Assign utilise des méthodes propres au TCanvas dans ton cas (API BitBlt). De même que tes routines de dessin durant le traitement. Du point de vue de Windows, toutes les API sous-jacentes travaillent sur un HDC (handle vers un device context, encapculé par la classe Delphi TCanvas) qui doit être préalablement associé au HBITMAP correspondant (Handle vers un bitmap, qui lui est encapsulé par la classe TBitmap) pour que ces opérations modifient effectivement le bitmap. Donc pour que ça fonctionne, il faut qu'un lien soit fait entre le HDC et le HBITMAP de manière explicite (avec SelectObject je crois) avant qu'une API de dessin (DrawRect, MoveTo, LineTo, BitBlt, etc...) puisse fonctionner. Ce lien est automatiquement créé dans le constructeur du TBitmap, mais n'existe que dans le contexte du thread qui l'a appelé. Si tu veux utiliser le bitmap dans un autre thread, il faut dans ce nouveau contexte demander de recréer le lien tout en empêchant qu'un autre thread le fasse: c'est à ça que sert Lock/Unlock.

Il me semble que dans la doc MSDN sur SelectObject, il est dit qu'un seul HDC à la fois peut être sélectionné dans un HBITMAP, et une seule fois par thread.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
27 août 2009 à 09:43
Juste quelques petites questions.
Est-ce que les LOCK/UNLOCK signalés sont obligatoire chez tout le monde ou juste chez moi? On ne sais jamais. C'est peut-être un bug de ma machine. J'utilise Delphi 7.
Est-ce un bug de Delphi ?
Est-ce une obligation ? Et Pourquoi ??

Barbichette
cs_cantador Messages postés 4720 Date d'inscription dimanche 26 février 2006 Statut Modérateur Dernière intervention 31 juillet 2021 13
27 août 2009 à 09:34
L'originalité de cette source a finalement occulté le souci pour lequel elle a été conçue à savoir :

c'est l'utilisation des Lock et Unlock, qui me semble de trop, mais qui doivent être présent car, sinon, ça marche pô...

on attend caribensila ou cirec sur le sujet...
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
27 août 2009 à 04:56
L'effet est très bien réussi, j'aime particulièrement le mode "Quantité de trait 100%", même si c'est un peu lent ...

Cordialement, Bacterius !
cs_cantador Messages postés 4720 Date d'inscription dimanche 26 février 2006 Statut Modérateur Dernière intervention 31 juillet 2021 13
26 août 2009 à 19:48
excellent Barbichette,
on va pouvoir singer picasso..
djmmix Messages postés 152 Date d'inscription lundi 28 juillet 2003 Statut Membre Dernière intervention 29 avril 2009
26 août 2009 à 18:21
salut,

merci pour ta source je vais apprendre en douceur la gestion des threads qui a première vue est pas si difficile :p

de plus ta source pour les traits de crayon sera bien pratique pour mes enfants lol
Rejoignez-nous