Caribensila
Messages postés2527Date d'inscriptionjeudi 15 janvier 2004StatutMembreDernière intervention16 octobre 2019
-
29 mai 2008 à 17:46
florenth
Messages postés1023Date d'inscriptiondimanche 1 août 2004StatutMembreDernière intervention17 août 2008
-
2 juin 2008 à 17:00
Bonjour M'sieurs'Dames,
Je suis en train d'essayer de combler une de mes nombreuses lacunes en prog' en tentant de dompter les pointeurs sous Delphi.
Pour cela, je fais des tests dans tous les sens.
Voici un de ces tests concernant ScanLine.
Ca marche, mais mon problème est que je ne comprends pas pourquoi.
Ce test ne fait que transformer une image couleur en niveaux de gris :
Je ne comprends pas pourquoi ma 2ème méthode est environ 25% plus rapide que la méthode ScanLine classique. En effet, dans les 2 méthodes, on parcourt toutes les lignes du Bmp pour les scanner une à une.
Pourquoi l'accès à chaque pixel est plus rapide en passant par un tableau?
'doit y avoir une histoire de gestion de la mémoire la-dessous, non?
Si une âme charitable pouvait m'allumer la lampe à Eugène ce serait sympa car je sens confusément que je n'ai encore rien compris à ces foutus pointeurs.
procedure Max(const p : pByte; const A,B,C : byte);
begin
if (A > B) and (A > C) then
p^ := A
else
if (B > C) then
p^ := B
else
p^ := C;
end;
procedure Min(const p : pByte; const A,B,C : byte);
begin
if (A < B) and (A < C) then
p^ := A
else
if (B < C) then
p^ := B
else
p^ := C;
end;
procedure Clamp(const p : pByte; const Val,aMin, aMax : integer);
begin
if Val < aMin then p^ := aMin else
if Val > aMax then p^ := aMax else
p^ := Val;
end;
procedure GrayByMinMax(src, dest : TBitmap; const pCycles: pInt64 = nil);
var X,Y : integer;
PCS,PCE : int64;
pScan : pScanLine;
Pix : ByteQuad;
Bo,Bm,Bx : byte;
begin
QueryPerformanceCounter(PCS);
Src.PixelFormat := pf32Bit;
Dest.PixelFormat := pf32Bit;
Dest.Assign(Src);
for Y := 0 to Dest.Height-1 do begin
pScan := Dest.ScanLine[Y];
for X := 0 to Dest.Width-1 do begin
Pix := ByteQuad(pScan[X]);
Max(@Bx,Pix[0],Pix[1],Pix[2]);
Min(@Bm,Pix[0],Pix[1],Pix[2]);
Bo := (Bx + Bm) shr 1;
pScan[X] := (Bo shl 16) or (Bo shl 8) or (Bo);
end;
end;
QueryPerformanceCounter(PCE);
if pCycles <> nil then
pCycles^ := PCE-PCS;
end;
{ ------- }
procedure GrayByRGBAverage(src, dest : TBitmap; const pCycles: pInt64 = nil);
var X,Y : integer;
PCS,PCE : int64;
pScan : pScanLine;
Pix : ByteQuad;
Bo : byte;
begin
QueryPerformanceCounter(PCS);
Src.PixelFormat := pf32Bit;
Dest.PixelFormat := pf32Bit;
Dest.Assign(Src);
for Y := 0 to Dest.Height-1 do begin
pScan := Dest.ScanLine[Y];
for X := 0 to Dest.Width-1 do begin
Pix := ByteQuad(pScan[X]);
Bo := (Pix[0] + Pix[1] + Pix[2]) div 3;
pScan[X] := (Bo shl 16) or (Bo shl 8) or (Bo);
end;
end;
QueryPerformanceCounter(PCE);
if pCycles <> nil then
function Lum(const color : integer) : integer;
var
LC : integer;
begin
LC := round( (byte(Color)*LumRed) +
(byte(Color shr 8)*LumGreen) +
(byte(Color shr 16)*LumBlue)
);
if LC > 255 then
LC := 255
else
if LC < 0 then
LC := 0;
result := integer(LC or (LC shl 8) or (LC shl 16));
end;
{ ------- }
procedure TrueGrayScale(Src : TBitmap; Dest : TBitmap; const pCycles: pInt64 = nil);
var
PCS,PCE : int64;
X,Y : integer;
pScan : pScanLine;
begin
QueryPerformanceCounter(PCS);
Src.PixelFormat := pf32bit;
Dest.Assign(Src);
for Y := 0 to Dest.Height-1 do
begin
pScan := Dest.ScanLine[Y];
for X := 0 to Dest.Width-1 do
pScan[X] := Lum(pScan[X]);
end;
QueryPerformanceCounter(PCE);
if pCycles <> nil then
florenth
Messages postés1023Date d'inscriptiondimanche 1 août 2004StatutMembreDernière intervention17 août 20083 29 mai 2008 à 21:02
Salut !
As-tu vu mon code source sur le traitement archi-extra-giga-bref-rapide des bitmaps via scanline ?
Le problème du Row[2X], c'est que chaque emploi demande au CPU de faire l'addition (@Row + X * SizeOf(TRGBTriple)) et de renvoyer le résultat. Et tu l'emploie 6 fois par pixel !
Alors qu'en faisant :
var
Pix: PRGBTriple;
begin
Pix := Bmp.ScanLine[239];
for X := 0 to 76799 do
begin
Grey := (Pix^.rgbtRed + Pix^.rgbtGreen + Pix^.rgbtBlue) div 3;
Pix^.rgbtRed := Grey;
Pix^.rgbtGreen := Grey;
Pix^.rgbtBlue := Grey;
Inc(Pix);
end;
end;
Là, tu as une addition par pixel, au lieu de 6, puisque l'usage d'un pointeur direct sur la mémoire à changer permet d'économiser le temps d'accès que Delphi génère avec ses foutus pseudos-pointeurs-de-tableaux infinis (le array[0..0] est supra lent).
ThWilliam
Messages postés418Date d'inscriptionmardi 3 janvier 2006StatutMembreDernière intervention26 novembre 20134 30 mai 2008 à 11:40
Salut.
Tests faits sur gros bitmaps avec le code de Florent en n'appelant qu'1 x Scanline et en incrémentant Pix.
C'est environ 2x plus rapide que la méthode classique (appel à Scanline pour chaque ligne du bitmap).
De plus, pour peindre un bitmap dans une couleur uniforme, c'est tout aussi rapide que Bmp.Canvas.FillRect !
Dès lors, n'a-ton pas atteint l'optimisation maximale sous Delphi ?
Vous n’avez pas trouvé la réponse que vous recherchez ?
cs_MAURICIO
Messages postés2106Date d'inscriptionmardi 10 décembre 2002StatutModérateurDernière intervention15 décembre 20145 30 mai 2008 à 15:51
Salut Cari,
j' ai suivi la conversation de loin car je ne voulais pas me mouiller ^^,
mais vu qu' il pleut et que l' on est tous mouillé ...
Je t' annonce tout de suite qu' à ta questions, je ne sais que répondre: bem les pointeurs c' est plus rapide car ça pointe directement dans la mémoire vive (aussi apelée RAM biensûr) oú les données (les pixels donc) de l' image sont stockées.
J' ai moi même une source ou 2 sur le site utilisant scanLine, notamment pour le redimensionnement.
Par contre, je ne connaissait pas la méthode optimisée que Florenth propose ici.
C' est très interessant et ça mériterait une source (Ça c' est pour Florenth) avec comparaison des diverses méthodes.
Moi ce qui m' embête un peu c' est que les méthodes présentes sur le site ne fonctionnent qu' avec un pixel format défini (Toi aussi vu que tu utilises TRGBTriple, donc 16 millions de couleurs
): ça nous oblige à convertir l' image (ou alors la propre procédure le fait) vers le pixelFormat utilisé par la fonction.
Pour illustrer tout ça, prenons la fonction de conversion en niveau de gris que tu proposes: si tu as une image à traiter en 16 couleurs, 256, 16 millions ou 24 millions tu vas devoir convertir l' image en 16 millions de couleurs pour faire la conversion: Une image en 24 millions de couleurs va donc perdre en qualité et une image de 256 couleurs aurait pu être convertie 10 fois plus vite si une fonction était écrite pour ce format là (même si le résultat avec ta fonction sera de meilleure qualité je te l' accorde vu que l' on passe l' image en 16 millions de couleurs avant la conversion).
Sinon je suis d' accord avec toi: les pointeurs c' est bien mais on y comprend rien !!!
Entre @xxx, xxx^, Pointer(xxx), pRGBArray = ^TRGBArray,
TRGBArray = ARRAY[0..0] OF TRGBTriple, Row : pRGBArray ça manque cruellement d' un bon tuto pour clouer le clou comme on dit.
Tu as le mérite de vouloir mettre les points sous les "i", quitte à passer pour ... une personne humble!
Je suis sûr que ton topic en va interesser plus d' un! Moi je suis déjà en train d' attendre l' opinions des autres membres ...
cs_MAURICIO
Messages postés2106Date d'inscriptionmardi 10 décembre 2002StatutModérateurDernière intervention15 décembre 20145 30 mai 2008 à 16:48
Un boulet? plutôt un ballon de boderuche!
Il reste encore des topics et sources interessants! Ça nous change de "j' en ai besoin tout de suite, c' est urgent car c' est pour pomper un max d' argent avec un programme de merde et pour montrer que je suis le roi des programmeurs".
Je viens de moins en moins et vous me manquiez les gars! Y en a pas un qui passerait ses vacances au Portugal par hasard pour se faire une petite bouffe entre Delphistes? Faudrait organiser une réunions anuelle entre membres un de ces 4 ...
cs_Kenavo
Messages postés702Date d'inscriptionvendredi 21 mars 2003StatutMembreDernière intervention 1 octobre 20095 30 mai 2008 à 19:32
Il est sûr que gérer les formats des bitmaps est <strike>un sacré merdier</strike> drôlement compliqué.
Par exemple pf24bit et pf32bit sont des formats de couleurs codées RVB (RGB if you préfères !) alors que les formats pf4bit, pf8bit renvoient un numéro de couleur qu'il faut s'en aller quérir derechef dans une palette associée !
Et puis <strike>faire gaffe</strike> user de grandes précautions dans l'utilisation d'un unique ScanLine sur la dernière ligne du bitmap, sachant que chaque début de ligne est "aligné" sur 4 octets (ou 8), et cela peut <strike>foutre le bordel</strike> causer de grandes contrariétés dans le cas d'image au nombre de pixels horizontaux non divisibles par 4 (impair en particulier) !
Sur ce commentaire quelque peu hors sujet - mais il y a des choses bonnes à dire - je vous souhaite une agréable soirée
ThWilliam
Messages postés418Date d'inscriptionmardi 3 janvier 2006StatutMembreDernière intervention26 novembre 20134 31 mai 2008 à 12:42
@Kenavo : tu as parfaitement raison avec les bitmap de largeur non divisible par 4.
Il faut donc faire un seul appel à Scanline mais sur la 1° ligne. Voir le code de l' OptimizedBitmap de Florent (cfr le lien renseigné par lui).
florenth
Messages postés1023Date d'inscriptiondimanche 1 août 2004StatutMembreDernière intervention17 août 20083 31 mai 2008 à 16:30
Ouais, il faut utiliser packed pour lire des structures bien définies en terme de taille.
La mise en garde de l'aide est légitime mais pour d'autres raisons. En fait si tu déclares
type
TRec = record
E: Extended;
I: Integer;
B: Byte;
I2: Integer;
end;
Alors SizeOf(TRec) ne sera pas la même que s'il était packed.
Tout ça parce que Delphi rajoute des zéros pour compléter les trous et aligner les débuts de variables sur des dword (directive {$A4} il me semble), ce qui rend l'accès plus rapide sur les processeurs 32 bits (puisque un dword 4 octets 32 bits)
Mais dons notre cas, on ne peut pas faire comme ça, puisque la structure interne du format bitmap est telle que les données sont alignées sur 3 octets pour pf24bit, voire moins pour les autres (pf16bit, pf15bit, pf8bit).
Mais dans tous les cas, ici il n'y a pas de baisse de perf parce que leur remarque n'est valable que pour des types dont la taille est un multiple de 4 octets.
Sinon, il y a de toutes façons une opération interne du CPU pour récupérer la partie qui t'intéresse.
Et en effet, quand on utilise des pointeurs, il vaut mieux être sûr de la taille de que qu'on manipule, donc utiliser packed de toutes façons.
NB: à noter que, dans mon exemple précédent, j'aurais pu remplacer tous les P^.R par P.R car cette syntaxe est supportée par Delphi. Cela dit, ce n'est pas conseillé (mais utile pour relire certains codes), car on oublie qu'on manipule un pointeur et puis on risque de faire la même boulette en faisant Pixel1 := Pixel2 (copie d'adresse) qui lui n'est pas identique à Pixel1^ := Pixel2^ (copie de données)
NB2: la différence entre P et P^ est la même en assembleur.
Si P est stocké dans EAX, alors P^ correspond à [EAX], ce qui est sympa à savoir pour comprendre le code asm généré que l'on peut voir par Ctrl-Alt-C lorsqu'on est sur un point d'arrêt.
Et donc, mov ebx,[eax] correspond à un déférencement de eax: le Integer qui se situe à l'adresse eax sera placé dans ebx.
Caribensila
Messages postés2527Date d'inscriptionjeudi 15 janvier 2004StatutMembreDernière intervention16 octobre 201918 29 mai 2008 à 19:23
Merci pour les algo, f0xi. Ca va me donner du grain à moudre...
Cependant, je crois qu'il y a quand même un truc qui nous échappe, là...
Tu dis : « Normal, l'accès a Scanline du Bitmap est assez long... donc acceder a scanline a chaque boucle est long. »
Mais si je fais :
NB: l'image fait 320x240 pixels pour éviter les ajustements de fin de ligne !!!
Row := Bmp.ScanLine[239]; // 1 seul appel à ScanLine au début du Bmp (coin inf. gauche). for X : 0 to 76799 do begin // 76799 320 x 240 - 1
R := Row[X].rgbtRed;
G := Row[X].rgbtGreen;
B := Row[X].rgbtBlue;
Grey := (R+G+B) div 3;
Row[X].rgbtRed := Grey;
Row[X].rgbtGreen := Grey;
Row[X].rgbtBlue := Grey;
end;
Ca donne les perfs les plus décevantes!
Il n'y a qu'1 seul appel à ScanLine[] et c'est la plus lente de toutes les méthodes ! ! !
Caribensila
Messages postés2527Date d'inscriptionjeudi 15 janvier 2004StatutMembreDernière intervention16 octobre 201918 30 mai 2008 à 13:52
Salut,
« Dès lors, n'a-ton pas atteint l'optimisation maximale sous Delphi ? »
Oui, si on considère que Bmp.Canvas.FillRect est le nec plus ultra en matière d'optimisation.
J'entrevois cependant une posibilité d'encore diminuer le temps de traitement par deux (au moins théoriquement) :
A la condition de disposer d'un Multi Core, c'est de faire une moitié du traitement dans un Thread et l'autre moitié dans un autre Thread.
La difficulté étant de partager un Bmp entre 2 Threads. Mais ça doit être jouable.
En tout cas, ça vaut le coup d'explorer cette voie car cette technique du multi-threading dans un environnement MultiCore est applicable à tout traitement long.
Mais tout cela m'éloigne de ma préoccupation actuelle que sont les pointeurs et ça ne répond toujours pas à ma première question : « Pourquoi l'accès à chaque pixel est plus rapide en passant par un tableau? ».
Caribensila
Messages postés2527Date d'inscriptionjeudi 15 janvier 2004StatutMembreDernière intervention16 octobre 201918 31 mai 2008 à 15:27
Un grand merci à Florent qui a pris le temps de nous éclaircir le truc.
Pour généraliser, j'ajouterais que dans le cas d'un tableau ou d'un type structuré, un pointeur contient l'adresse du premier élément de la structure.
Et donc, si j'ai bien compris, l'utilisation de Packed est obligatoire lorsqu'on veut utiliser des pointeurs vers une structure quelconque. C'est bien ça?
Donc, la mise en garde de l'Aide Delphi est discutable quand on utilise des pointeurs, à savoir :
« l'utilisation de packed ralentit l'accès aux données... »
Bon, petit à petit, je commence à y voir plus clair je crois...
Encore merci, Flo :)