COMMENT OPTIMISER LES TIMAGE (GAIN DE TEMPS ET DE CALCUL IMPORTANT)
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 2009
-
30 juin 2004 à 11:06
cs_grandvizir
Messages postés1106Date d'inscriptionsamedi 8 novembre 2003StatutMembreDernière intervention 3 septembre 2006
-
27 juil. 2004 à 19:47
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.
cs_grandvizir
Messages postés1106Date d'inscriptionsamedi 8 novembre 2003StatutMembreDernière intervention 3 septembre 200622 27 juil. 2004 à 19:47
J'ai tout plein de réponses qui vont mettre un terme à tous ces dialogues réfléchis et aux bidouillages grotesques proposés (désolé, mais c'est ça).
>>>>>>>>>
En effet, rappatrier ExtCtrls est ridicule, car on en a déjà un et il suffit de modifier ce qu'il nous propose. Comment aurais-tu fait si t'avais pas eu le code source?? Proprement, tu aurais simplement écrit une ligne de code très simple:
procedure TForm1.FormCreate(Sender:TObject);
begin
Image1.Picture.OnChange:=nil;
end;
>>>>>>>>>
Quand t'utilises Image1.Canvas.Pixels[X,Y]:=clRed; voilà ce qui se passe en arrière-plan:
procedure TCanvas.SetPixel(X,Y:Integer; Value:TColor);
begin
Changing;
RequiredState([csHandleValid, csPenValid]);
Windows.SetPixel(FHandle, X, Y, ColorToRGB(Value));
Changed;
end;
Là, on voit que non seulement il y a ralentissement avec Changed qui appelle PictureChanged, mais également avec Changing qui appelle autre chose. On peut illustrer ceci. Teste donc les lignes suivantes. Derriere chaque OnBidule, il se passe quelque chose puisqu'un "1" est affiché dans les 3 cas.
ShowMessage(IntToStr(Ord(Assigned(Image1.Canvas.OnChanging))));
ShowMessage(IntToStr(Ord(Assigned(Image1.Canvas.OnChange))));
ShowMessage(IntToStr(Ord(Assigned(Image1.Picture.OnChange))));
Ainsi, si Canvas.Pixels[x,y]:=Couleur t'embête, alors tu remplaces simplement par:
Windows.SetPixel(Image1.Picture.Bitmap.Canvas.Handle, X, Y, Couleur);
Cette fonction est importée depuis GDI32:
function SetPixel(DC,X,Y,Color:integer):integer; external 'gdi32.dll';
Elle est aussi rapide que le TRGBTripleArray, et est plus claire. Donc...
Si tu regardes dans Windows.pas, tu verras dans l'énoncé de la fonction que Color est un COLORREF. Pas de problèmes: COLORRED=DWORD=integer. Et comme TColor=integer, t'as pas besoin d'utiliser la fonction ColorToRGB: inutile et perte de temps. Enfin, tout dépend de ce que tu veux faire, mais moi je ne l'utiliserai pas.
MAIS ATTENTION!! Cette fonction nécessite que tu ais déjà créé un bitmap dans ton TImage, ce qui n'est pas le cas quand tu places simplement un TImage sur la fenêtre. Il faut mettre un petit code dans l'évènement OnCreate de la fenêtre:
procedure TForm1.FormCreate(Sender:TObject);
begin
Image1.Picture.Bitmap.ReleaseHandle;
with Image1.Picture.Bitmap.Create do
begin
Canvas.Brush.Color:=clWhite;
Canvas.Pen.Color:=clBlack;
Canvas.Pen.Width:=1;
PixelFormat:=pf24bit;
Width:=Image1.Width;
Height:=Image1.Height;
end;
Image1.Refresh;
end;
Au final, ta boucle ressemble à ce qui suit. Et en plus, t'as même plus besoin de te préoccuper de PictureChanged.
procedure TForm1.Button1Click(Sender: TObject);
var x, y : Integer;
begin
for x:=0 to Image1.Width-1 do
for y:=0 to Image1.Height-1 do
Windows.SetPixel(Image1.Picture.Bitmap.Canvas.Handle, X, Y, clRed);
Image1.Refresh;
end;
>>>>>>>>>
Que le Canvas soit caché ou non, ça n'a pas d'importance puisque cela ne réduit pas tous les calculs qui se produisent...
>>>>>>>>> ALORS ?? :)) <<<<<<<<<
cs_jmic
Messages postés11Date d'inscriptionmardi 26 août 2003StatutMembreDernière intervention25 juillet 2006 6 juil. 2004 à 14:24
De nouveau moi.
Pour t'aider à comprendre l'utilisation des canvas cachés et des copies d'images (et aussi parce que prendre des idées sur delphifr, c'est bien, mais que l'on peut aussi participer :-), j'ai fait un petit programme montrant leur utilisation. C'est "compte à rebours style film année 60". Si tu as des questions n'hésite pas (mais je pars demain une semaine).
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 20095 6 juil. 2004 à 08:26
Le temps de changer un pixel est négligeable, par rapport au temps nécessaire à le localiser.
Si tu veux tracer une simple ligne verticale avec Pixels, tu divise le temps pour remplir toute l'image par le nombre de point en largeur ! (Ca peut faire 1000) ce n'est pas le cas pour ScanLine.
Dans ces cas là, je dirais même que l'essentiel du temps est utilisé à copier le Bitmap temporaire dans l'imge.
Il reste des essais à faire pour quantifier tout ça !
Ken@vo
cs_Zeroc00l
Messages postés367Date d'inscriptionlundi 1 avril 2002StatutMembreDernière intervention11 février 2010 5 juil. 2004 à 23:49
jmic :
J'avais deja pensé à utiliser la même méthode que toi.. utiliser un canvas caché maisje n'ais pas trouve comment manipuler un canvas : lui affecter une taille (Width and Height) le transferer "d'un coup" sur le TImage visible etc ...
Kenavo ... OK
J'ai pas encore testé si le fait de faire des changement de range (depointeur donc ) avant de changer un pixel (donc deux truc ) à changer quand je veux changer un pixel contre un avant.
Mais je pense que ca sera également plus rapide...
cs_jmic
Messages postés11Date d'inscriptionmardi 26 août 2003StatutMembreDernière intervention25 juillet 2006 5 juil. 2004 à 10:16
Bonjour,
Pour des raisons de rapidité (mais aussi pour d'autres raisons), j'utilise un canvas "masqué", de même taille mais non affiché à l'écran. J'y fais toutes les manipulations que je veux puis je le transfère d'un coup sur le Canvas affiché à l'écran (par la fonction canvas.copyrect () ). Je ne sais pas si, dans le cas qui te préoccupe, cela améliore la vitesse de l'exécution, mais en tous les cas ça évite les problèmes de réaffichage et de clignottement intempestif. Evidemment, ça mange un peu plus de mémoire !
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 20095 5 juil. 2004 à 08:56
Ah, si ! Encore quelques petites choses :
En fait, les adresses des buffers de lignes sont alignées sur des DWord, et si la largeur de l'image est impaire, on a des "trous" dans le tableau entre deux lignes si on utilise le format pixel pf24bit (nombre impair d'octets). Il vaut donc travailler sur une image dont la largeur est multiple de 4, ou utiliser le format pf32Bits.
Autrement, contrairement à ce que j'ai écrit plus haut, les coordonnées d'un pixel sont [Rangee, Colonne] et pas l'inverse.
Mille excuses
Ken@vo
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 20095 5 juil. 2004 à 07:48
Non, pas de Col[i], en fait, les pixels sont rangés en mémoire sous forme d'un tableau [x,y] de pixels (1 à 4 octets selon la valeur de PixelFormat). Le premier pixel du tableau est celui de coordonnées [0,Height-1] et non [0,0] ; c'est à dire que le balayage de l'image est effctué de gauche à droite et de bas en haut. En utilisant ce type de structure, on peut facilement travailler en colonne.
A+
Autrement, je n'ai pas moi-même mis de source avec ce code, mais je n'ai pas regardé s'il y en avait !
Ken@vo
cs_Zeroc00l
Messages postés367Date d'inscriptionlundi 1 avril 2002StatutMembreDernière intervention11 février 2010 3 juil. 2004 à 10:55
Mais sinon j'y pense ... dans ton code on utilise Row[i], pourrait-on utiliser un " Col[i] " ???
En toute logique, si les pixels des rangés sont alignés en mémoire, ceux des colonnes ne le sont pas.
Mais je demande. On ne sait jamais. Les concepteur on peut être fait un truc exotique pour le permettre ...
cs_Zeroc00l
Messages postés367Date d'inscriptionlundi 1 avril 2002StatutMembreDernière intervention11 février 2010 3 juil. 2004 à 10:48
Ah ouais !! Quand même ! C'est clairement plus rapide!
Mais y'avait déjà une source ou ton truc était expliqué ?
J'aurais bien aimé la trouver plus tôt, ça m'aurait évité d'écrire celle la...
En tout cas, MERCI pour cette remarque constructive.
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 20095 30 juin 2004 à 11:06
Salut,
Si tu as vraiment envie de booster tes fonctions de dessin en manipulant les pixels, utilise plutôt la fonction ScanLine, la fonction Pixels est terriblement lente !
Je te propose d'essayer ce code, et d'en comparer les performances avec celui utilisant la fontion Pixels. Pour moi, 2.5 sec avec ton astuce, 90 millisecondes avec scanline ! (image 1164 x 977 pixels)
______________________________________________
type // existent dans Graphics, mais pas publics
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array [Byte] of TRGBTriple;
var
i : INTEGER;
j : INTEGER;
Row: pRGBTripleArray;
Bitmap : TBitMap;
begin
Bitmap := TBitmap.Create; // Nouveau bitmap
try
Bitmap.PixelFormat := pf24bit; // format couleur 24bits
Bitmap.Width := Image1.Width; // dimensionnement
Bitmap.Height := Image1.Height;
for j := 0 to Bitmap.Height-1 do
begin
Row := Bitmap.Scanline[j]; // Pointeur sur ligne de pixels
for i := 0 to Bitmap.Width-1 do
with Row[i] do
begin
rgbtRed := 255- i mod 256; // Modif rouge pixel
rgbtGreen := 255- i mod 256; // Modif vert
rgbtBlue := 255- i mod 256; // Modif bleu
end
end;
// Affichage à l'écran
Image1.Picture.Graphic := Bitmap;
// Image1.Repaint si tu utilises ExtCtrls modifié !!
finally
Bitmap.Free
end;
end;
___________________________________________
27 juil. 2004 à 19:47
>>>>>>>>>
En effet, rappatrier ExtCtrls est ridicule, car on en a déjà un et il suffit de modifier ce qu'il nous propose. Comment aurais-tu fait si t'avais pas eu le code source?? Proprement, tu aurais simplement écrit une ligne de code très simple:
procedure TForm1.FormCreate(Sender:TObject);
begin
Image1.Picture.OnChange:=nil;
end;
>>>>>>>>>
Quand t'utilises Image1.Canvas.Pixels[X,Y]:=clRed; voilà ce qui se passe en arrière-plan:
procedure TCanvas.SetPixel(X,Y:Integer; Value:TColor);
begin
Changing;
RequiredState([csHandleValid, csPenValid]);
Windows.SetPixel(FHandle, X, Y, ColorToRGB(Value));
Changed;
end;
Là, on voit que non seulement il y a ralentissement avec Changed qui appelle PictureChanged, mais également avec Changing qui appelle autre chose. On peut illustrer ceci. Teste donc les lignes suivantes. Derriere chaque OnBidule, il se passe quelque chose puisqu'un "1" est affiché dans les 3 cas.
ShowMessage(IntToStr(Ord(Assigned(Image1.Canvas.OnChanging))));
ShowMessage(IntToStr(Ord(Assigned(Image1.Canvas.OnChange))));
ShowMessage(IntToStr(Ord(Assigned(Image1.Picture.OnChange))));
Ainsi, si Canvas.Pixels[x,y]:=Couleur t'embête, alors tu remplaces simplement par:
Windows.SetPixel(Image1.Picture.Bitmap.Canvas.Handle, X, Y, Couleur);
Cette fonction est importée depuis GDI32:
function SetPixel(DC,X,Y,Color:integer):integer; external 'gdi32.dll';
Elle est aussi rapide que le TRGBTripleArray, et est plus claire. Donc...
Si tu regardes dans Windows.pas, tu verras dans l'énoncé de la fonction que Color est un COLORREF. Pas de problèmes: COLORRED=DWORD=integer. Et comme TColor=integer, t'as pas besoin d'utiliser la fonction ColorToRGB: inutile et perte de temps. Enfin, tout dépend de ce que tu veux faire, mais moi je ne l'utiliserai pas.
MAIS ATTENTION!! Cette fonction nécessite que tu ais déjà créé un bitmap dans ton TImage, ce qui n'est pas le cas quand tu places simplement un TImage sur la fenêtre. Il faut mettre un petit code dans l'évènement OnCreate de la fenêtre:
procedure TForm1.FormCreate(Sender:TObject);
begin
Image1.Picture.Bitmap.ReleaseHandle;
with Image1.Picture.Bitmap.Create do
begin
Canvas.Brush.Color:=clWhite;
Canvas.Pen.Color:=clBlack;
Canvas.Pen.Width:=1;
PixelFormat:=pf24bit;
Width:=Image1.Width;
Height:=Image1.Height;
end;
Image1.Refresh;
end;
Au final, ta boucle ressemble à ce qui suit. Et en plus, t'as même plus besoin de te préoccuper de PictureChanged.
procedure TForm1.Button1Click(Sender: TObject);
var x, y : Integer;
begin
for x:=0 to Image1.Width-1 do
for y:=0 to Image1.Height-1 do
Windows.SetPixel(Image1.Picture.Bitmap.Canvas.Handle, X, Y, clRed);
Image1.Refresh;
end;
>>>>>>>>>
Que le Canvas soit caché ou non, ça n'a pas d'importance puisque cela ne réduit pas tous les calculs qui se produisent...
>>>>>>>>> ALORS ?? :)) <<<<<<<<<
6 juil. 2004 à 14:24
Pour t'aider à comprendre l'utilisation des canvas cachés et des copies d'images (et aussi parce que prendre des idées sur delphifr, c'est bien, mais que l'on peut aussi participer :-), j'ai fait un petit programme montrant leur utilisation. C'est "compte à rebours style film année 60". Si tu as des questions n'hésite pas (mais je pars demain une semaine).
6 juil. 2004 à 08:26
Si tu veux tracer une simple ligne verticale avec Pixels, tu divise le temps pour remplir toute l'image par le nombre de point en largeur ! (Ca peut faire 1000) ce n'est pas le cas pour ScanLine.
Dans ces cas là, je dirais même que l'essentiel du temps est utilisé à copier le Bitmap temporaire dans l'imge.
Il reste des essais à faire pour quantifier tout ça !
Ken@vo
5 juil. 2004 à 23:49
J'avais deja pensé à utiliser la même méthode que toi.. utiliser un canvas caché maisje n'ais pas trouve comment manipuler un canvas : lui affecter une taille (Width and Height) le transferer "d'un coup" sur le TImage visible etc ...
Kenavo ... OK
J'ai pas encore testé si le fait de faire des changement de range (depointeur donc ) avant de changer un pixel (donc deux truc ) à changer quand je veux changer un pixel contre un avant.
Mais je pense que ca sera également plus rapide...
5 juil. 2004 à 10:16
Pour des raisons de rapidité (mais aussi pour d'autres raisons), j'utilise un canvas "masqué", de même taille mais non affiché à l'écran. J'y fais toutes les manipulations que je veux puis je le transfère d'un coup sur le Canvas affiché à l'écran (par la fonction canvas.copyrect () ). Je ne sais pas si, dans le cas qui te préoccupe, cela améliore la vitesse de l'exécution, mais en tous les cas ça évite les problèmes de réaffichage et de clignottement intempestif. Evidemment, ça mange un peu plus de mémoire !
5 juil. 2004 à 08:56
En fait, les adresses des buffers de lignes sont alignées sur des DWord, et si la largeur de l'image est impaire, on a des "trous" dans le tableau entre deux lignes si on utilise le format pixel pf24bit (nombre impair d'octets). Il vaut donc travailler sur une image dont la largeur est multiple de 4, ou utiliser le format pf32Bits.
Autrement, contrairement à ce que j'ai écrit plus haut, les coordonnées d'un pixel sont [Rangee, Colonne] et pas l'inverse.
Mille excuses
Ken@vo
5 juil. 2004 à 07:48
A+
Autrement, je n'ai pas moi-même mis de source avec ce code, mais je n'ai pas regardé s'il y en avait !
Ken@vo
3 juil. 2004 à 10:55
En toute logique, si les pixels des rangés sont alignés en mémoire, ceux des colonnes ne le sont pas.
Mais je demande. On ne sait jamais. Les concepteur on peut être fait un truc exotique pour le permettre ...
3 juil. 2004 à 10:48
Mais y'avait déjà une source ou ton truc était expliqué ?
J'aurais bien aimé la trouver plus tôt, ça m'aurait évité d'écrire celle la...
En tout cas, MERCI pour cette remarque constructive.
30 juin 2004 à 11:06
Si tu as vraiment envie de booster tes fonctions de dessin en manipulant les pixels, utilise plutôt la fonction ScanLine, la fonction Pixels est terriblement lente !
Je te propose d'essayer ce code, et d'en comparer les performances avec celui utilisant la fontion Pixels. Pour moi, 2.5 sec avec ton astuce, 90 millisecondes avec scanline ! (image 1164 x 977 pixels)
______________________________________________
type // existent dans Graphics, mais pas publics
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array [Byte] of TRGBTriple;
var
i : INTEGER;
j : INTEGER;
Row: pRGBTripleArray;
Bitmap : TBitMap;
begin
Bitmap := TBitmap.Create; // Nouveau bitmap
try
Bitmap.PixelFormat := pf24bit; // format couleur 24bits
Bitmap.Width := Image1.Width; // dimensionnement
Bitmap.Height := Image1.Height;
for j := 0 to Bitmap.Height-1 do
begin
Row := Bitmap.Scanline[j]; // Pointeur sur ligne de pixels
for i := 0 to Bitmap.Width-1 do
with Row[i] do
begin
rgbtRed := 255- i mod 256; // Modif rouge pixel
rgbtGreen := 255- i mod 256; // Modif vert
rgbtBlue := 255- i mod 256; // Modif bleu
end
end;
// Affichage à l'écran
Image1.Picture.Graphic := Bitmap;
// Image1.Repaint si tu utilises ExtCtrls modifié !!
finally
Bitmap.Free
end;
end;
___________________________________________
Ken@vo