RÉÉCHANTILLONNAGE BICUBIQUE VS STRETCHBLT

cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 - 4 août 2012 à 14:16
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 - 23 août 2012 à 16:09
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/54486-reechantillonnage-bicubique-vs-stretchblt

Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
23 août 2012 à 16:09
Moi, je pensais que le Timage.Picture.Bitmap était déjà créé mais avec ses Width et Height à zéro...

Sinon, un lien intéressant sur le canevas du TImage :

http://delphi.developpez.com/faq/?page=compopropri#dessinersurcanvas2timage
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
23 août 2012 à 14:45
Ben les filles... ça papote, ça papote, ça papote...
On ne peut pas partir une semaine sans avoir un roman photo au retour...

Je reviens de loin avec le Timage.Canvas et Timage.Picture.Bitmap.Canvas. En regardant (sous D7), c'est la même chose.
Timage.Canvas renvoi le canvas de Timage.Picture.Bitmap si il existe déjà.
Sinon, il crée ce fameux Picture.Bitmap et renvoi ce canvas.

Pour le reste, continuez à optimiser, on va arriver à des temps de calcul négatifs...
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
19 août 2012 à 17:19
« mais j'ai pas de photo plus récente »
Bon. Alors, SURTOUT, laisse tomber.

En théorie, sur une machine 64bits il devrait être possible de doubler les performances. En effet, les couleurs restent codées de la même façon (sur 3 ou 4 bytes), et le processeur permet de traiter 8 bytes à la fois, soit 2 pixels. Or, ce qui prend du temps, c'est d'aller chercher ou de changer les composantes de la couleur des pixels en mémoire. Le faisant à chaque fois pour 2 pixels, ça doit forcément aller plus vite.

Bien sûr, il faudra alors faire deux codes. Un pour les machine 32bits et un autre pour les 64bits. C'est pour cette raison que la compilation conditionnelle m'intéresse beaucoup et je vais y jeter un oeil car j'y connais encore rien.
Merci pour cette MAJ, Cirec !

PS: Ma prochaine machine sera à coup sûr une 64bits car trop impatient de tester tout ça...
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
19 août 2012 à 16:25
"Mais j'serais toi, je changerais quand même d'avatar..."
mais j'ai pas de photo plus récente ^^

Bon, j'ai eu l'occasion de tester et je peux confirmer les propos de Caribensila
"je pense que sur une machine en 64 bits, le Double doit avoir de meilleures performances."

effectivement le Double arrive en tête sur une 64 bits suivi par Extended et loin derrière le Single

La version ASM ne compile pas sous 64bits problème de registre .. ce qui semble normale ...

j'ai donc modifié le code pour permettre de le recompiler rapidement avec le type choisi (Extended, Double ou Single) grace à la compilation conditionnelle ... et il affiche dans la barre de titre la
version du compilateur utilisé (D7, D2009, XE2) le type d'os (32/64 bits) et le type choisi (Extended, Double ou Single) ... j'en avais marre de douter de la version, quand on en teste plusieurs ça aide beaucoup ;)

Maintenant comme le code est devenu plus difficile à lire, à cause de la compilation conditionnelle, et tout le monde ne veux pas forcément ceci, j'ai mis cette version dans un répertoire nommé "Version Custom"

pour changer de type il suffit d'éditer le fichier "Types.inc" et d'activer/désactiver le type voulu
et reconstruire le projet ... simple non !

@++
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
18 août 2012 à 19:23
Franchement, ça m'étonnait aussi beaucoup de ta part.
(Mais j'serais toi, je changerais quand même d'avatar...)

Enfin, on voit bien que t'as rien perdu de ton talent et si je pouvais, je remettrais un nouveau 10/10 pour cet excellent travail.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
18 août 2012 à 19:08
bon sang mais c'est bien sur ....
comment ai-je pu oublier une telle chose ...

Merci à toi de me rafraichir la mémoire ;)
c'est à cause de mon age avancé ... je perd la vue, la mémoire ... et après ? mdr
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
18 août 2012 à 18:38
... En revanche, je pense que sur une machine en 64 bits, le Double doit avoir de meilleures performances. Mais c'est à vérifier car j'ai pas une telle machine. M'enfin, ce serait logique...
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
18 août 2012 à 18:33
Ca n'a rien d'étonnant sur une machine en 32 bits car un Single est codé sur 4 octets, comme l'Integer.

Le Double, lui, fait 8 octets (comme l'Int64), et l'Extended 10 octets !

Et il est inutile de travailler avec une telle précision car tous les calculs seront arrondis en entiers codés sur 8 bits (les canaux d'un TRGBTriple) pour être affiché, et une éventuelle différence de 1 sur un ou plusieurs canaux n'est pas perceptible à l'oeil.

De toute façon, l'Extended est le moins portable de tous, et il est à éviter quand on travaille pour plusieurs plates-formes.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
18 août 2012 à 18:03
re,
"Moi j'ai été surpris que le simple passage du Extended vers le Single apporte une augmentation de vitesse décelable avec GetTickCount !!!."

oui j'ai également été surpris ... surtout que les gains ne sont pas négligeables !!!

au début j'ai utilisé le type Extended avec les résultats que l'on connait !
puis je suis passé au Double (pour coller au code original) ... avec déjà un gain de temps remarquable

et là avec Single c'est encore plus fort ...
à la base je ne pensais même pas observer une différence sur le temps d'exécution
par contre je m'attendais à une différence sur la qualité ou sur le décalage de l'image. (mais rien de décelable à l'oeil nu)

surprenant quand même !!!

en ce qui concerne le décalage de l'image
il semble avoir été légèrement atténué grace à ceci (-0.5 + ...):
i_in := Floor(-0.5 + i_out * nx / nnx);
j_in := Floor(-0.5 + j_out * ny / nny);
cx := -0.5 + i_out * nx / nnx;
cy := -0.5 + j_out * ny / nny;

@++
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
18 août 2012 à 17:19
Re-salut,

"ben si tu redimensionnes à 101% sans la correction ... le code plante"
... OK, je teste en neutralisant temporairement la correction.

"je viens de re-tester et le code plante bien avec le type Extended et donne un résultat surprenant avec le type Single" :
...Moi j'ai été surpris que le simple passage du Extended vers le Single apporte une augmentation de vitesse décelable avec GetTickCount !!!.

"le trunc induit un décalage qui n'a pas grande incidence sur le code mais qui donne un résultat différent entre la taille annoncée et la taille réelle ... du coup la ProgressBar ne se remplit pas jusqu'au bout" :
... Je n'avais pas remarqué car j'avais modifié le code de sorte que la ProgressBar se remette illico à zéro en fin d'exécution.

A+.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
18 août 2012 à 15:42
pardon,
oui je parle bien de ceci
// *** Modification pour éviter un bug Indice Hors limites
Ind1 := Max(Min(Ind1, 16), 1);
Ind2 := Max(Min(Ind2, 16), 1);

ben si tu redimensionnes à 101% sans la correction ... le code plante

je viens de re-tester et le code plante bien avec le type Extended
et donne un résultat surprenant avec le type Single !!!
dans tous les cas la correction est utile
ps: ce qui est bizarre c'est que le message d'erreur ne soit plus le même ... maintenant j'obtiens
Opération en virgule flottante incorrecte. !!!

et le deuxième (mais c'est du chipotage ça):
// WR := trunc(k * BmpS.width); HR := trunc(k * bmpS.Height);
WR := Round(k * BmpS.width);
HR := Round(k * bmpS.Height);

le trunc induit un décalage qui n'a pas grande incidence sur le code mais qui donne un résultat différent entre la taille annoncée et la taille réelle ... du coup la ProgressBar ne se remplit pas jusqu'au bout.

@++
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
18 août 2012 à 14:22
Re-bonjour,

A Cirec :

1) "as-tu remarqué la modification apporté à ton code (StretchBmpAlpha3) ? pour éviter que le code plante en dehors d'un redimensionnement "plein" 200, 300, 400%" :
Si tu veux parler de cette modif :
// *** Modification pour éviter un bug Indice Hors limites
Ind1 := Max(Min(Ind1, 16), 1);
Ind2 := Max(Min(Ind2, 16), 1);
alors celle-ci se trouve dans FastBiCubicScale
car dans StretchBmpAlpha3 je n'ai trouvé que celle-ci :
// WR := trunc(k * BmpS.width); HR := trunc(k * bmpS.Height);
WR := Round(k * BmpS.width);
HR := Round(k * bmpS.Height);
mais je n'ai pas pigé le rapport que la "modif pour éviter un bug Indice Hors limites" avait avec un redimensionnement autre que "plein" ?

2) "...tu sembles affectionner le 400% ": En fait j'utilise toujours le 400% et le même bitMap-source lors des tests comparatifs de vitesse ça permet de mieux s'y retrouver dans le schmilblik des résultats lorsqu'il y des changements dans un code.

A+.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
18 août 2012 à 13:33
re,

voilà les modifications sont faites
j'ai ajouté un bout de code pour tous ceux qui ont plusieurs versions de delphi (comme moi) ... il permet de savoir avec laquelle le code a été compilé. Pour le désactiver il suffit de mettre un point avant la directive comme ceci:
{.$define CompilerVersion}

@Pseudo3:
as-tu remarqué la modification apporté à ton code (StretchBmpAlpha3) ?
pour éviter que le code plante en dehors d'un redimensionnement "plein" 200, 300, 400%

c'est 3 fois rien mais comme tu sembles affectionner le 400% tu es certainement passé à coté de ça ;)

@++
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
18 août 2012 à 09:13
Bonjour,

A Cirec :

1) OK, je vais me contenter des explications de Caribensila.

2) A propos de "ps: j'ai modifié le code" : J'ai lu dans l'historique que tu as supprimé la variable globale TBitmap mais en as tu également profité pour remplacer le type Extended par le type Single des variables locales et de la constante pré-calculée dans FastBiCubicScale vue le gain de vitesse signalé dans mon message du 17/08/2012 10:23:17 ??? Ce serait dommage de ne pas en faire profiter tout le monde.

A+.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
17 août 2012 à 17:27
@Pseudo3:
Salut,
certes j'ai traduit le code en Pascal mais je ne prétend pas avoir compris toutes les finesses de cette méthode. J'en ai saisi les grandes lignes mais de grandes zones d'ombres persistent encore ^^
il faudra te contenter des explications de Caribensila ;)

@Caribensila:

Salut,
tu as entièrement raison .. en fait le problème n'est pas d'avoir oublié que le TImage contient déjà un TBitmap mais que ce dernier soit déjà créé ... mais on arrive très vite à des fuites de mémoire en utilisant ces composants, en fonction de ce que l'on fait et surtout comment, et de ce fait je travaille machinalement comme ceci ce qui me garanti une libération dans tous les cas de figure ... puisque c'est moi qui l'a fait :D

quand à ceci "Draw(0,0,UnTGraphic);"
je ne m'étendrai pas sur le sujet puisque les deux ont leurs utilités ^^
chacun voit midi à sa porte :D

ps: j'ai modifié le code ;)
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
17 août 2012 à 15:59
Re-salut,

"Ah! J'ajoute que pour les images d'infographie il existe une méthode qui résout tous ces problèmes... Ce sont les images vectorisées. Elles sont en général plus légères... Mais aussi plus longues à afficher" :
OK, plus légères en mémoire et sur disque, mais pour les afficher il faut à chaque fois tout re-calculer sans oublier que c'est la galère pour les coder.

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
17 août 2012 à 15:49
Re-salut Caribesila,

"Scientifiquement parlant, il n'y a en effet aucune raison qui puisse faire dire qu'un algo sera plus exact qu'un autre puisque dans tous les cas il s'agit d'"inventer" des données dont on ne dispose pas au départ. Mais il faut plutôt voir cela sous un aspect probabiliste" :
Je dirais plutôt qu'un algo sera plus exact qu'un autre si son résultat est probablement plus proche d'une réalité physique qu'un autre.

" ... sur une photo argentique la couleur des "grains" est influencée par la couleur des grains voisins" : OK et idem pour le cas des pixels-voisins du bitMap original dont on peut supposer que les couleurs A,B,C,D ont été influencés par celles de ses voisins et donc ceux qui sont à l'intérieur du carré ABCD agrandi sont à leur tour influencés seulement par les couleurs ABCD et pas par ceux situés à l'extérieur.

"Je le répète donc, l'algo à utiliser dépend beaucoup de l'image source" : Ce qui va bien nous compliquer encore davantage le vie.

"Mais aussi, la démo de Cirec nous apprend que certains algos traitent mieux que d'autres ces différents cas et ceux basés sur l'interpolation sont de ceux-là" :
Bin, ils sont bien tous basés sur une méthode d'interpolation : les uns sur la Bicubique et StretchBmpAlpha3 est basée sur deux interpolations linéaires horizontales entre AB et entre CD suivie d'une troisième interpolation linaire verticale entre les interpolations précédentes.

"TPublicGraphicControl ? J'en ai jamais mangé" : Cela n'interdit pas d'en goûter.

A+.
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
17 août 2012 à 15:36
... Ah! J'ajoute que pour les images d'infographie il existe une méthode qui résout tous ces problèmes... Ce sont les images vectorisées.

Elles sont en générale plus légères...
Mais aussi plus longues à afficher. ( Pfff! Y'a toujours un truc qui flotte dans l'bouillon ! )
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
17 août 2012 à 15:19
Scientifiquement parlant, il n'y a en effet aucune raison qui puisse faire dire qu'un algo sera plus exact qu'un autre puisque dans tous les cas il s'agit d'"inventer" des données dont on ne dispose pas au départ.
Mais il faut plutôt voir cela sous un aspect probabiliste. Le calcul de probabilité est bien une science dont les résultats n'ont rien de scientifique. Après avoir jeté une pièce de monnaie 20 fois en l'air, elle est tombée 20 fois sur "face". Au prochain lancement, les probabilités nous disent que se sera plutôt un "pile" qui sortira. Et pourtant nous savons tous que "face" ou "pile" ont autant de chances l'un que l'autre de sortir au prochain lancement...

En d'autres termes, l'interpolation ne donne pas des résultats exact sur la couleur des pixels, mais donne cependant un résultat proche d'une réalité scientifiquement calculée. Et ce calcul est basé sur le fait que sur une photo argentique la couleur des "grains" est influencée par la couleur des grains voisins. On n'a en effet jamais un grain noir collé à un grain blanc sur une surface argentique. C'est pour cette raison que l'interpolation est plus efficace sur de vraies photos. Dès qu'on veut agrandir une image informatique pixellisée, le problème est différent car on a souvent un pixel noir côtoyant un pixel blanc. Et on peut être sûr que, sur un agrandissement, les pixels "inventés" devront être soit blancs, soit noirs; et certainement pas gris !
Je le répète donc, l'algo à utiliser dépend beaucoup de l'image source.

Mais aussi, la démo de Cirec nous apprend que certains algos traitent mieux que d'autres ces différents cas et ceux basés sur l'interpolation sont de ceux-là.

Pour le 2), je ne sais pas.
TPublicGraphicControl ?
- J'en ai jamais mangé... ^ ^
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
17 août 2012 à 14:26
Re-bonjour Caribensila,

1) A propos de l'interpolation :
OK sur le principe dans le cas des courbes continues (au sens mathématique) telles qu'un relevé de température pris dans une variation qui est elle même un phénomène continu (au sens physique).
Par contre mes 4 pixels ABCD peuvent être entourés de bruit avec de brusques variations et je n'ai toujours pas d'explication scientifique qui justifierait le fait qu'il faudrait en tenir compte pour calculer les couleurs situées à l'intérieur du carré.

Tu dis "C'est pour cette raison que les algos adaptatifs donnent généralement de meilleur résultats car ils décèlent ces brusques changement de continuité dans les courbes et les reproduisent à une autre échelle" : OK, mais ça c'est plutôt une astuce corrective d'informaticien et même dans ce cas j'aimerais que cette "reproduction à une autre échelle" suive une loi scientifique. (j'ai un faible pour les sciences exactes)

2) A propos de "TImage dispose en effet de deux Canvas" :
Et il parait que le vrai Canvas du TImage soit celui-ci : With TPublicGraphicControl(Image1).Canvas do ...
(lu sur le site de Developpez).

A+.
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
17 août 2012 à 13:13
... Pendant que j'y suis, avec le TImage...
Eviter de se mélanger les pinceaux avec ses TCanvas. TImage dispose en effet de deux Canvas :
- Image1.Canvas
- Image1.Picture.Bitmap.Canvas

J'ai parfois vu :

image1.Canvas.Draw(0,0,UnTGraphic);

au lieu de

image1.Picture.Bitmap.Canvas.Draw(0,0,UnTGraphic);

C'est particulièrement traitre car les deux actions sont acceptées mais le résultat est différent.
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
17 août 2012 à 12:47
Cirec nous dit très justement qu'il faut toujours libérer les objets que l'on crée.
Mais parfois il n'est même pas nécessaire de les créer, et donc de les libérer.
Une maladresse fréquente, même chez les plus grands ^ ^, est d'oublier que le compos TImage contient déjà un TBitmap dans son TPicture. On peut donc simplifier le code ainsi :

procedure TFrmDemoMain.FormCreate(Sender: TObject);
begin
//aBmp := TBitmap.Create; <--- Inutile.
//imgResized.Picture.Bitmap := aBmp; <--- Inutile.
aBmp := imgResized.Picture.Bitmap; <--- suffisant.
...
end;

procedure TFrmDemoMain.FormDestroy(Sender: TObject);
begin
//aBmp.Free; <--- Inutile. Le TImage libèrera lui-même son TBitmap.
end;

On peut donc même supprimer la variable globale aBmp et utiliser à la place le imgResized.Picture.Bitmap.
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
17 août 2012 à 11:32
Ce n'est pas un problème de colorimétrie ou d'optique. C'est un problème mathématique plus général. C'est en fait le principe de l'interpolation.

Sans pouvoir faire de petits croquis, ça ne va pas être facile à expliquer, mais je vais essayer...

Si on te demande de 'deviner' une valeur qui se situe entre seulement deux valeurs données, en général tu feras la moyenne de ces deux valeurs. En revanche, si tu as deux suites de valeurs qui encadrent la(les) valeur(s) à 'deviner', tu pourras calculer et dessiner la courbe que décrivent ces valeurs.
Ensuite, il te suffira de choisir des points situés entre les deux suites et se trouvant sur cette courbe pour avoir une assez bonne approximation.
On sent bien intuitivement que plus les suites données seront longues, plus la courbe calculée sera exacte.

Dans une image, ces suites sont les valeurs situées à l'extérieur du carré ABCD.

On sent bien aussi intuitivement que cette approche est surtout valable pour des courbes continues (au sens mathématique) comme, par exemple, un relevé de température. Mais en graphisme c'est loin d'être toujours le cas. Dans une image il peut y avoir de brusque changement de colorimétrie. C'est pour cette raison que les algos adaptatifs donnent généralement de meilleur résultats car ils décèlent ces brusques changement de continuité dans les courbes et les reproduisent à une autre échelle.

Voilà. J'espère avoir un peu éclairé les lanternes. ^ ^
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
17 août 2012 à 10:23
Bonjour,

1) "ne jamais utiliser le résultat d'une fonction qui crée un objet sans ...' : Oups j'avais oublié la libération. Merci pour le rappel.

2) Résultats de tests sous D5 lors d'un agrandissement de 400% d'un bitmap-source de 683x1024 qui passe à 2732x4096 :
- StretchBlt & Halftone : 118 ms,
- StretchBmpAlpha3 : 594 ms,
- Bicubique & precalc : 2994 ms (version modifiée utilisant des valeurs de type Single) (*),
- Bicubique & precalc : 4713 ms (version d'origine utilisant des valeurs de type Extended) (*),
- Bicubique ASM : 6169 ms
- Bicubique Original : 8089 ms.
(*) : vu le gain de vitesse autant profiter du type single dont la précision est amplement suffisante sachant que la taille d'un pixel d'un écran de 100 PixelsPerInch fait 0,254 millimètre donc on n'a pas besoin d'une précision microscopique.

3) A Cirec, à propos de "Que le méthode Bicubique soit la plus lente est tout à fait normal ... elle utilise une matrice 4x4 et donc, chaque pixel de l'image finale est calculé avec les 16 pixels les plus proches de l'image originale" :

Ce qui m'intrigue : voici un schéma d'agrandissement où chaque lettre majuscule représente un pixel de couleur connue

AB <- ici 4 pixels contigüs du BiMap-Original dont les centres forment un carré de 1x1 pixel
CD

et ici l'agrandissement où les quelques tirets '-' représentent les pixels de l'image agrandie dont on cherche les couleurs interpolées entre celles de A,B,C,D :

A------B
--------
--------
C------D

donc si chaque pixel de l'image finale est calculé avec les 16 pixels les plus proches de l'image originale, ces pixels les plus proches sont forcément des pixels situés à l'extérieur du carré ABCD de l'image originale dont les pixels sont contigüs, d'où la petite question qui m'intrigue :
Quelle est la raison scientifique (de colorimétrie, ou d'optique visuelle, ou autre) qui justifierait le fait de tenir compte des couleurs des pixels situés à l'extérieur du carré ABCD pour calculer les couleurs situées à l'intérieur de ce carré ???

A+.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
17 août 2012 à 00:17
Bon ...
la mise à jour est enfin en ligne ..... wouah ^^

les procédures de Pseudo3 et Barbichette sont ajoutées au code et les temps d'exécution sont aussi affichés.

j'ai du apporter quelques petites modifications au code de Pseudo3 afin d'éviter des fuites de mémoire et un débordement de pile.

@Tous:
ne jamais utiliser le résultat d'une fonction qui crée un objet sans garder une trace de ce dernier sous peine de ne pas pouvoir libérer la mémoire qui lui a été alloué.

ainsi cette ligne de code ci-dessous, crée un objet TBitmap et l'assigne au TImage mais ce TBitmap n'est jamais libéré ... et à chaque appel la fuite se répète:
imgResized.Picture.Bitmap.Assign(StretchBmpAlpha3(imgOriginal.Picture.Bitmap, ScaleValue, ProgressBar1.StepIt));

la bonne méthode est:
TmpBMP := StretchBmpAlpha3(imgOriginal.Picture.Bitmap, ScaleValue, ProgressBar1.StepIt);
try
imgResized.Picture.Bitmap.Assign(TmpBMP);
finally
TmpBMP.Free; // <----- ici le Bitmap est libéré correctement
end;

le reste vous le verrez dans le code :D

Par contre j'ai remarque une chose assez étrange:
j'ai compilé le code sous D7 et D2009
sous D7 ma version est plus rapide que la version ASM de Barbichette et quasi aussi rapide que la pré-calculée de Pseudo3 !!!
sous D2009 j'ai le même ordre de rapidité que celui donné par Pseudo3 dans son message du 09/08/2012 11:54:10

comprendra qui pourra ...
sur ce...
@++
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
11 août 2012 à 22:40
ouahhh !!!

'tin les gars vous avez fait très fort ....
merci pour votre implication sur ce source.

Désolé de ne pas encore avoir posté de mise à jour ... pour la petite histoire, un problème musculaire m'a, pour ainsi dire, cloué au lit pour 3 jours ... rien de grave mais c'est très handicapant et douloureux :D.

je pense que d'ici la fin du weekend je posterai le code avec vos changements ;)

et Merci tout particulièrement à Barbichette pour la traduction du code xBR en Pascal. Bravo.
@++
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
11 août 2012 à 12:36
Remarque :
Cette algorithme fonctionnement bien sur des images à fort contrastes (cartoon, dessin...)
Il est un peu moins impressionnant sur des vrais photos.
enfin, d'après moi...
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
11 août 2012 à 12:01
Bonjour Barbichette,

"j'ai converti l'algo du lien de Cirec en pascal. Je fais un bout de code d'exemple avec et je le poste" :
... Excellente idée. Impatient de voir les résultats visuels "stupéfiants".

A+.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
10 août 2012 à 21:46
Bon, pour les fous de redimensionnement, j'ai converti l'algo du lien de Cirec en pascal.
Je fait un bout de code d'exemple avec et je le poste.
A+
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
10 août 2012 à 09:47
Bonjour,

Oups! Prière de ne pas prendre en compte mon code du 09/08/2012 11:54:10 car je viens de me rendre compte que sa constante pré-calculée (BiCubicRPrecal: array[1..16] of Extended) n'est mathématiquement valable que pour des agrandissements d'environ 400% bien qu'on ne le remarque pas visuellement dans le cas d'agrandissements de 200% ou 300% ni dans le cas de réductions ... mais pour des agrandissements s'écartant trop des 400% le résultat n'est plus vraiment du Bicubique : Dommage car le gain de speed obtenu grâce au pré-calcul était intéressant.

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
9 août 2012 à 11:54
Bonjour,

1) Caribensila "Mais ce que je voulais dire c'est qu'il faudra comparer la qualité du résultat plutôt que les temps de traitement." :
... Bin, c'est pas interdit de comparer les deux, et ta remarque "On n'agrandit pas une toile de Mondrian comme celle d'un Monnet" m'incite d'ailleurs à faire ce type de comparaisons.
"L'algo en question ne doit pas être d'un abord facile. De plus, en C, ça n'arrange rien pour moi" : C'est pareil pour moi, tout ce que sais du C c'est qu'il faut traduire les {} par begin et end. (lol).
2) Barbichette : De mon côté je viens de voir que dans BiCubicRAsm(x) comme dans sa cousine BiCubicR(x) les valeurs du paramètre d'entrée x varient entre xmin -1,75 xmax 2 avec un pas constant de 0.25 donc j'ai utilisé les deux d'abord pour comparer leurs résultats (ils sont kif-kif) et ensuite j'ai stocké les résultats dans la constante pré-calculée BiCubicRPrecal: array[1..16] of Extended de la nouvelle procedure BiCubicScale ci-dessous.

Résultats de tests lors d'un agrandissement de 400% d'un bitmap-source de 683x1024 qui passe à 2732x4096 :
- avec la StretchBlt : mis 142 ms,
- avec la StretchBmpAlpha : mis 797 ms,
- avec la BicubiqueAsm : mis 9112 ms contre les 11279 ms de Bicubique sans Asm.
- avec la Bicubique et sa constante BiCubicRPrecal : 3885 ms : 2,34 fois plus rapide qu'en l'absence de pré-calcul.

Et voici le code :

procedure BiCubicScale(const bmSrc: TBitmap; const WS, HS: Integer;
const bmDst: TBitmap; const WD, HD: Integer;
const ProgressCallBack: TObjectProc = nil);

var xd, yd, xs, ys, ii, jj, n, m, index: Integer;
cx, cy, dx, dy, weight, red, green, blue, alpha: Extended;
col: RGBQuad;
PbmSrc, PbmDst: Cardinal; Ind1, Ind2: integer;
const BiCubicRPrecal: array[1..16] of Extended
= (0.00260416666666667, 0.0208333333333333, 0.0703125, 0.166666666666667,
0.315104166666667, 0.479166666666667, 0.611979166666667, 0.666666666666667,
0.611979166666667, 0.479166666666667, 0.315104166666667, 0.166666666666667,
0.0703125, 0.0208333333333333, 0.00260416666666667, 0.0);
begin
bmSrc.PixelFormat := pf32bit;
bmDst.PixelFormat := pf32bit;
PbmSrc := Cardinal(bmSrc.ScanLine[HS - 1]);
PbmDst := Cardinal(bmDst.ScanLine[HD - 1]);

for yd := 0 to HD - 1 do
begin
for xd := 0 to WD - 1 do
begin xs := (xd * WS) div WD;
ys := (yd * HS) div HD;
cx := xd * WS / WD;
cy := yd * HS / HD;
dx := cx - xs;
dy := cy - ys;
red := 0;
green := 0;
blue := 0;
alpha := 0;
for m := -1 to 2 do
for n := -1 to 2 do
begin
ii := xs + m;
jj := ys + n;
if ii < 0 then ii := 0;
if ii >= WS then ii := WS - 1;
if jj < 0 then jj := 0;
if jj >= HS then jj := HS - 1;
index := jj * WS + ii;
Ind1 := round(4 * (m - dx)) + 8; // Indice de correspondance avec la table précalculée
Ind2 := round(4 * (n - dy)) + 8; // Idem
weight := BiCubicRPrecal[ind1] * BiCubicRPrecal[ind2]; // remplace weight := BiCubicRAsm(m - dx) * BiCubicRAsm(n - dy);
with PRGBQuad(index * 4 + PbmSrc)^ do
begin red := red + weight * rgbRed;
green := green + weight * rgbGreen;
blue := blue + weight * rgbBlue;
alpha := alpha + weight * rgbReserved;
end;
end;
col.rgbRed := Trunc(red);
col.rgbGreen := Trunc(green);
col.rgbBlue := Trunc(blue);
col.rgbReserved := Trunc(alpha);
PRGBQuad((yd * WD + xd) * 4 + PbmDst)^ := col;
end;
if Assigned(ProgressCallBack) then ProgressCallBack;
end;
end;

A+.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
8 août 2012 à 20:26
il y a encore une petite optimisation à faire dans le code en assembleur.
Je viens de voir que si x+2<0 alors tous les autres If sont faux aussi (x+1<0, x<0 et x-1<0) donc je vais faire une petite modif et l'envoyer en perso à Cirec afin de sortir de la fonction dès qu'un if est faux.

Bon courage pour l'algo adaptatif...
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
8 août 2012 à 17:15
Salut PSEUDO3,

C'est sûr que ça vaudrait le coup.

Mais ce que je voulais dire c'est qu'il faudra comparer la qualité du résultat plutôt que les temps de traitement.
On ne compare pas un MacDo avec une Poularde demi-deuil. ^ ^

L'algo en question ne doit pas être d'un abord facile. De plus, en C, ça n'arrange rien pour moi... :'(
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
8 août 2012 à 16:37
Re-salut,

Caribensila : "Un algo adaptatif partage l'image en zones ... Et chaque zone est traitées différemment. C'est ce que fait l'algo du lien de Cirec. En revanche, les algos adaptatifs sont meilleurs quand on agrandit une image" :
... Bin on est justement dans le cas d'agrandissements ... donc ça vaudrait bien le coup de pouvoir coder sous Delphi l'algo du lien de Cirec.

A+.
cs_MAURICIO Messages postés 2106 Date d'inscription mardi 10 décembre 2002 Statut Modérateur Dernière intervention 15 décembre 2014 5
8 août 2012 à 15:59
Je vais faire un algo pour savoir si mes critères de notation sont déterminés par l' auteur ou par le type de source ...

En attendant, bonnes vacances!
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
8 août 2012 à 15:41
Salut,

Il est difficile de comparer le rééchantillonnage bicubique et l'algo du lien que propose Cirec. Ce sont deux approches différentes.

Le rééchantillonnage bicubique est un algo non-adaptatif. C'est à dire qu'il traite tous les pixels de la même façon, avec la même formule.

Un algo adaptatif partage l'image en zones différentes selon la texture, les contours, les couleurs, etc... Et chaque zone est traitées différemment. C'est ce que fait l'algo du lien de Cirec.
Autant dire que les possibilités en fonction du choix des zones sont infinies. Ce sont des algos utilisés par les logiciels pour pros (Qimage, PhotoZoom Pro, Genuine Fractals, etc...).

Les algos non-adaptatifs (interpolation bilinéaire, bicubique, au plus proche, splyne, sinc, lanczos, etc...) sont généralement les meilleurs quand on effectue une distorsion ou une rotation de l'image. En revanche, les algos adaptatifs sont meilleurs quand on agrandit une image.

Dans tous les cas, le résultat est fortement dépendant de l'image source. On n'agrandit pas une toile de Mondrian comme celle d'un Monnet. Et le résultat dépendra du choix + ou - pertinent de l'utilisateur, pas de l'algo !

Reste donc encore à coder l'algo qui fera le meilleur choix de traitement en fonction de l'image source... Un algo qui choisirait le meilleur algo, en quelque sorte. ^ ^

PS: Comme je n'avais jamais trouvé cet algo en Delphi sur le Net, je mets un 10/10 sans hésiter et je dis merci Cirec.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
8 août 2012 à 15:37
Re-bonhjour,

"Est-ce que StretchBtlt est encore le plus rapide par exemple ... dans les mêmes proportions?" :
En réduisant à 25% un bitMap de 2732x4096 pour en obtenir un de 683x1024 :
- avec la StretchBlt : mis 78 ms,
- avec la StretchBmpAlpha : mis 63 ms,
- avec la BicubiqueAsm : mis 593 ms.
... mais les 78 et 63 ms c'est environ kif-kif car mon chrono c'est GetTickCount et pour les durées inférieures à 100 ms c'est un peu piffométrique.

A+.
cs_MAURICIO Messages postés 2106 Date d'inscription mardi 10 décembre 2002 Statut Modérateur Dernière intervention 15 décembre 2014 5
8 août 2012 à 15:23
"Quels types de tests ?"
Bem les mêmes!
Est-ce que StretchBtlt est encore le plus rapide par exemple ... dans les mêmes proportions?

A+
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
8 août 2012 à 15:16
Re-bonjour,

1) A Mauricio "Il serait interessant de faire des tests de réduction" :
... Quels types de tests ? En principe les réductions posent moins de problèmes et sont plus rapides que les agrandissements.
"J'utilise StretchBlt ici " :
... Ok, vu. Mais StretchBlt est également utilisé dans le code de Cirec.

2) A Cirec : Vu les liens http://forums.getpaint.net/etc, les résultats visuels sont effectivement stupéfiants mais vu que le seul code que j'y ai trouvé est écrit en C pour ma part je n'y comprends rien. Dommage que l'on n'ait pas accès aux bases mathématiques de son algorithme ou à un pseudo-code.

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
8 août 2012 à 14:02
Bonjour,

1) "Je vais inclure leurs codes au zip afin que tout le monde puisse tester et juger." :

Ok, dans ce cas voici la version 3 de StretchBmpAlpha qui est un peu plus rapide que les versions précédentes (j'ai modifié entre-autres la sous fonction rgbQuadDegra4 qui calcule directement les interpolations sans faire appel à une autre sous-fonction): le temps d'exécution pour l'agrandissement de 400% est descendu de 796 ms à 577 ms.
(j'ai aussi viré le ProgressCallBack pensant qu'il mangeait du temps, mais on peut le ré-ajouter car ça ne bouffe que des clopinettes).

function StretchBmpAlpha3(const BmpS: tBitMap; k: single): tBitMap;
// k = coefficient multiplicateur d'échelle
var
// Le BitMap-Source :
WS, HS: integer;
Scan0S: Integer; // Valeur du pointeur d'entrée dans le Bitmap-Source.
ScanS: Integer; // Pointeur temporaire à incrémenter.
MLSS: Integer; // Memory Line Size (en bytes) du Bitmap-Source.
// Le BitMap-result :
WR, HR: integer;
Scan0R: Integer; // Valeur du pointeur d'entrée dans le Bitmap-Result.
ScanR: Integer; // Pointeur temporaire à incrémenter.
MLSR: Integer; // Memory Line Size du Bitmap-Result.
Bpp: Integer; // Bytes par pixel des 2 Bitmap's
c0, c1, c2, c3: tRGBQuad; // Couleurs des pixels des 4 angles
xr, yr, xo1, xo2, yo1, yo2: integer; dxo, dyo: single; // Coordonnées
xrSurk, yrSurk: single; // Rapports

function rgbQuadDegra4: tRGBQuad;
// Renvoie la couleur interpolée entre celle des 4 points d'angle
var unMoinsDxo, DyoDxo: single;
begin unMoinsDxo := 1.0 - dxo; DyoDxo := dyo * dxo;
Result.rgbRed := trunc(unMoinsDxo * (c0.rgbRed - dyo * (c0.rgbRed - c1.rgbRed)) + dxo * c3.rgbRed - DyoDxo * (c3.rgbRed - c2.rgbRed));
Result.rgbGreen := trunc(unMoinsDxo * (c0.rgbGreen - dyo * (c0.rgbGreen - c1.rgbGreen)) + dxo * c3.rgbGreen - DyoDxo * (c3.rgbGreen - c2.rgbGreen));
Result.rgbBlue := trunc(unMoinsDxo * (c0.rgbBlue - dyo * (c0.rgbBlue - c1.rgbBlue)) + dxo * c3.rgbBlue - DyoDxo * (c3.rgbBlue - c2.rgbBlue));
Result.rgbReserved := trunc(unMoinsDxo * (c0.rgbReserved - dyo * (c0.rgbReserved - c1.rgbReserved)) + dxo * c3.rgbReserved - DyoDxo * (c3.rgbReserved - c2.rgbReserved));
end;

begin
k := abs(k);
WS := BmpS.width; HS := bmpS.Height; BmpS.PixelFormat := pf32bit; Bpp := 4;
Scan0S := Integer(BmpS.ScanLine[0]);
MLSS := Integer(BmpS.ScanLine[1]) - Scan0S;

Result := tBitMap.Create;
WR := trunc(k * BmpS.width); HR := trunc(k * bmpS.Height);
with Result do begin PixelFormat := pf32bit; width := WR; height := HR end;
Scan0R := Integer(Result.ScanLine[0]);
MLSR := Integer(Result.ScanLine[1]) - Scan0R;

for yr := 0 to HR - 1 do begin
yrSurk := yr / k; yo1 := trunc(yrSurk); yo2 := trunc((yr + k) / k); dyo := frac(yrSurk);
for xr := 0 to WR - 1 do begin
xrSurk := xr / k; xo1 := trunc(xrSurk); xo2 := trunc((xr + k) / k); dxo := frac(xrSurk);
// Arrivé ici xo1,yo1 et xo2,yo2 sont les coord's des 4 pixels du bmp-source formant un carré de 2x2 (dont les centres forment un carré de 1x1 pixel)
// et à partir desquels on fera un dégradé de couleurs en fonction de dxo,dyo dans le carré de taille k*k correspondant du bmp-result.
if (yo2 < HS) and (xo2 < WS) then begin
// Identification des 4 couleurs de base :
ScanS := Scan0S; Inc(ScanS, yo1 * MLSS + xo1 * Bpp); c0 := PRGBQuad(scanS)^; inc(ScanS, Bpp); c3 := PRGBQuad(scanS)^;
ScanS := Scan0S; Inc(ScanS, yo2 * MLSS + xo1 * Bpp); c1 := PRGBQuad(scanS)^; inc(ScanS, Bpp); c2 := PRGBQuad(scanS)^;
// Le dégradé de couleurs :
ScanR := Scan0R; Inc(ScanR, yr * MLSR + xr * Bpp);
PRGBQuad(scanR)^ := rgbQuadDegra4;
end; // if yo2
end; // for xr
//if Assigned(ProgressCallBack) then ProgressCallBack;
end; // for yr
end; // StretchBmpAlpha3

2) Maintenant si on veut un résultat de qualité ... j'ai trouvé un autre code ... Les résultats visuels sont stupéfiants mais pour l'adaptation c'est pas encore gagné ... si quelqu'un se sent l'envie d'essayer il ne faut pas se gêner"
... Ok, je vais y jeter un coup d'oeil pour soupeser les difficultés d'adaptation.

A+.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
8 août 2012 à 13:43
Salut à tous,

merci de l'intérêt que vous portez à ce code.

<< C'est dans la BiCubicScale (partie sans assembleur) qu'il y a des trucs qui ralentissent. >>
Que le méthode Bicubique soit la plus lente et tout à fait normal ... elle utilise une matrice 4x4 et donc, chaque pixels de l'image finale est calculé avec les 16 pixels les plus proche de l'image originale. Donc plus on agrandit l'image finale plus le temps d'exécution en prend une claque ... par contre pour la réduction les temps sont tout à fait acceptables et plus on réduit moins elle met de temps.

Maintenant si on veut un résultat de qualité ... j'ai trouvé un autre code:
http://forums.getpaint.net/index.php?/topic/23601-2d-image-scaling-algorithms/#entry366643

voir les résultat ici:
http://forums.getpaint.net/index.php?/topic/23601-2d-image-scaling-algorithms/#entry367822

Les résultats visuels sont stupéfiants mais pour l'adaptation c'est pas encore gagné ... si quelqu'un se sent l'envie d'essayer il ne faut pas se gêner ... hein :D

Je veux remercier tout particulièrement Pseudo3 et Barbichette pour leurs contributions ... je vais inclure leurs codes au zip afin que tout le monde puisse tester et juger.

@++
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
8 août 2012 à 09:21
Bonjour,

1) Tests avec utilisation de BiCubicRAsm lors d'un agrandissement de 400% d'un bitmap-source de 683x1024 qui passe à 2732x4096 :
- avec la StretchBlt : mis 141 ms,
- avec la StretchBmpAlpha : mis 796 ms,
- avec la BicubiqueAsm : mis 9111 ms contre les 11279 ms de Bicubique sans Asm.
Conclusions :
C'est dans la BiCubicScale (partie sans assembleur) qu'il y a des trucs qui ralentissent.
Cela m'encourage à chercher des gains de speed avec la StretchBmpAlpha.

2) J'en profite pour rectifier ce que j'ai dit le 06/08/2012 10:52:33 à propos des pourcentages de pixels différents obtenus avec les différentes méthodes : les 2% sont faux car j'avais utilisé sans vérification un code de comptage trouvé sur le net qui était complètement vérolé.
Et dans un deuxième temps j'ai constaté que ce mode de comparaison n'était absolument pas significatif car les BitMap-Result obtenus avec les trois méthodes étaient légèrement décalés les uns par rapport aux autres : Le centre d'un point brillant sur l'oeil du portrait que j'avais utilisé pour l'agrandissement à 400% était situé aux coordonnées :
- 505,1002 sur le bitMap obtenu avec StretchBlt,
- 504,1000 sur celui obtenu avec StretchBmpAlpha,
- 504,1004 avec la Bicubique.

A+.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
7 août 2012 à 20:10
Bon, voilà une petite amélioration en assembleur...
C'est la fonction BiCubicR mais en assembleur.
On vois une légère amélioration sur les grandes images.

function BiCubicRAsm(const x: Extended): Extended;
Const
C2:extended=2;
C4:extended=4;
C6:extended=6;
asm
FLDZ // on charge 0 sur la pile// // xp2
FLD x // charge x
FLD C2 // charge 2
FADDP // addition

FTST // x+2>0 ?
FSTSW AX
FWAIT
SAHF
JBE @xp2_zero

FLD ST //xp2=xp2^3
FMUL ST,ST
FMULP
FADDP // on ajoute au résultat
JMP @suite_xp2

@xp2_zero:
FSTP ST(0) // retire le résultat x+2 de la pile

@suite_xp2:
// // xp1
FLD x // charge x
FLD1 // charge 1
FADDP // addition

FTST // x+1>0 ?
FSTSW AX
FWAIT
SAHF
JBE @xp1_zero

FLD ST //xp1=-4* xp1^3
FMUL ST,ST
FMULP
FLD C4
FMULP

FSUBP // on soustrait au résultat
JMP @suite_xp1

@xp1_zero:
FSTP ST(0) // retire le résultat x+1 de la pile

@suite_xp1:
// // x
FLD x // charge x

FTST // x+1>0 ?
FSTSW AX
FWAIT
SAHF
JBE @x_zero

FLD ST //xx=6* x^3
FMUL ST,ST
FMULP
FLD C6
FMULP

FADDP // on ajoute au résultat
JMP @suite_x

@x_zero:
FSTP ST(0) // retire le résultat x+1 de la pile

@suite_x:
// // xm1
FLD x // charge x
FLD1 // charge 1
FSUBP // addition

FTST // x+1>0 ?
FSTSW AX
FWAIT
SAHF
JBE @xm1_zero

FLD ST //xp1=-4* xp1^3
FMUL ST,ST
FMULP
FLD C4
FMULP

FSUBP // on soustrait au résultat

JMP @suite_xm1

@xm1_zero:
FSTP ST(0) // retire le résultat x+1 de la pile

@suite_xm1:

// r /6
FLD C6
FDIV

FWAIT
end;
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
7 août 2012 à 15:06
Bonjour,

Voici une deuxième version de StretchBmpAlpha basée sur le même principe du dégradé de couleurs avec une simplification qui améliore un peu la vitesse et une correction qui améliore la qualité du rendu visuel : le rendu soutient les comparaisons avec la Bicubique et s'obtient environ 13 fois plus rapidement qu'avec la Bicubique lors d'un agrandissement de 400%.

function StretchBmpAlpha2(const BmpS: tBitMap; k: single; const ProgressCallBack: TObjectProc = nil): tBitMap;
// k = coefficient multiplicateur d'échelle
var
// Le BitMap-Source :
WS, HS: integer;
Scan0S: Integer; // Valeur du pointeur d'entrée dans le Bitmap-Source.
ScanS: Integer; // Pointeur temporaire à incrémenter.
MLSS: Integer; // Memory Line Size (en bytes) du Bitmap-Source.
// Le BitMap-result :
WR, HR: integer;
Scan0R: Integer; // Valeur du pointeur d'entrée dans le Bitmap-Result.
ScanR: Integer; // Pointeur temporaire à incrémenter.
MLSR: Integer; // Memory Line Size du Bitmap-Result.
Bpp: Integer; // Bytes par pixel des 2 Bitmap's
cl0, cl1, cl2, cl3: tRGBQuad; // Couleurs des pixels des 4 angles
cl30, cl21: tRGBQuad; // Couleurs intermédiaires interpolées entre cl3,cl0 d'une part et entre cl2,cl1 d'autre part
xr, yr, xo1, xo2, yo1, yo2: integer; dxo, dyo: single; // Coordonnées

function rgbQuadDegra2(cl1, cl2: tRGBQuad; d1, d12: single): tRGBQuad;
// Dégradé entre deux couleurs : Renvoie la couleur dégradée du point distant de d1 par rapport à celui de couleur cl1
// et situé entre les points de couleurs cl1 et cl2 séparés de d12
var k: single;
begin if d12 <> 0 then k := d1 / d12 else k := 0.5;
Result.rgbRed := round((1 - k) * cl1.rgbRed + k * cl2.rgbRed);
Result.rgbGreen := round((1 - k) * cl1.rgbGreen + k * cl2.rgbGreen);
Result.rgbBlue := round((1 - k) * cl1.rgbBlue + k * cl2.rgbBlue);
Result.rgbReserved := round((1 - k) * cl1.rgbReserved + k * cl2.rgbReserved);
end;

function rgbQuadDegra4: tRGBQuad;
// Renvoie la couleur interpolée entre celle des 4 points d'angle
begin cl30 := rgbQuadDegra2(cl0, cl3, dxo, 1);
cl21 := rgbQuadDegra2(cl1, cl2, dxo, 1);
Result := rgbQuadDegra2(cl30, cl21, dyo, 1);
end;

begin
k:=abs(k);
WS := BmpS.width; HS := bmpS.Height; BmpS.PixelFormat := pf32bit; Bpp := 4;
Scan0S := Integer(BmpS.ScanLine[0]);
MLSS := Integer(BmpS.ScanLine[1]) - Scan0S;

Result := tBitMap.Create;
WR := trunc(k * BmpS.width); HR := trunc(k * bmpS.Height);
with Result do begin PixelFormat := pf32bit; width := WR; height := HR end;
Scan0R := Integer(Result.ScanLine[0]);
MLSR := Integer(Result.ScanLine[1]) - Scan0R;

for yr := 0 to HR - 1 do begin
yo1 := trunc(yr / k); yo2 := trunc((yr + k) / k); if yo2 = yo1 then inc(yo2);
dyo := frac(yr / k);
for xr := 0 to WR - 1 do begin
xo1 := trunc(xr / k); xo2 := trunc((xr + k) / k); if xo2 = xo1 then inc(xo2);
dxo := frac(xr / k);
// Arrivé ici xo1,yo1 et xo2,yo2 sont les coord's des 4 pixels du bmp-source formant un carré de 2x2
// et à partir desquels on fera un dégradé de couleurs en fonction de dxo,dyo dans le carré de taille k*k correspondant du bmp-result.
if (yo2 < HS) and (xo2 < WS) then begin
// Identification des 4 couleurs de base :
ScanS := Scan0S; Inc(ScanS, yo1 * MLSS + xo1 * Bpp); cl0 := PRGBQuad(scanS)^;
ScanS := Scan0S; Inc(ScanS, yo1 * MLSS + xo2 * Bpp); cl3 := PRGBQuad(scanS)^;
ScanS := Scan0S; Inc(ScanS, yo2 * MLSS + xo1 * Bpp); cl1 := PRGBQuad(scanS)^;
ScanS := Scan0S; Inc(ScanS, yo2 * MLSS + xo2 * Bpp); cl2 := PRGBQuad(scanS)^;
// Le dégradé de couleurs :
ScanR := Scan0R; Inc(ScanR, yr * MLSR + xr * Bpp);
PRGBQuad(scanR)^ := rgbQuadDegra4;
end; // if yo2
end; // for xr
if Assigned(ProgressCallBack) then ProgressCallBack;
end; // for yr
end; // StretchBmpAlpha2

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
6 août 2012 à 10:52
Bonjour,

1) "Je pense que StretchBlt gagne aussi du temps car c'est codé en partie en assembleur. Pour les maîtres du domaines, il faudrait reprendre l'unité de Cirec pour passer des morceaux en assembleur" : Exact qu'avec de l'Asm on peut gagner en speed, mais pour ma part je suis nul en Asm.

2) Rectification d'une petite erreur dans le code de StretchBmpAlpha du 05/08/2012 14:44:23 : Remplacer la sous-fonction encapsulée rgbQuadDegradee par la suivante :

function rgbQuadDegradee(cl1, cl2: tRGBQuad; d1, d12: single): tRGBQuad;
// Renvoie la couleur dégradée du point distant de d1 par rapport à celui de couleur cl1
// et situé entre les points de couleurs cl1 et cl2 séparés de d12
var k: single;
begin if d12 <> 0 then k := abs(d1/d12) else k := 0.5;
Result.rgbRed := round((1-k)*cl1.rgbRed + k*cl2.rgbRed);
Result.rgbGreen := round((1-k)*cl1.rgbGreen + k*cl2.rgbGreen);
Result.rgbBlue := round((1-k)*cl1.rgbBlue + k*cl2.rgbBlue);
Result.rgbReserved := round((1 - k)*cl1.rgbReserved + k*cl2.rgbReserved);
end;

... j'avais inversé l'affectation des pondérations (1-k) et k des composantes R,G,B

3) Résultats de tests comptant le pourcentage de pixels différents obtenus avec les différentes méthodes : Bicubique/Stretch : 2,28 % et Bicubique/StretchBmpAlpha 2,38 % avec l'agrandissement de 400% d'un bitmap-source de 683x1024 qui passe à 2732x4096 pixels.

A+.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
6 août 2012 à 07:46
Salut,
Je pense que StretchBlt gagne aussi du temps car c'est codé en parti en assembleur.
Pour les maîtres du domaines, il faudrait reprendre l'unité de Cirec pour passer des morceaux en assembleur. Il me semble que certains calculs peuvent être envoyé au coproc mathématique pour améliorer les choses.
J'aime bien réfléchir à ce genre d'optimisation. Je vais donc essayer de trouver du temps pour y jeter un coup d’œil...
Et puis 10/10, c'est quand même pas mal...

Barbichette
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
5 août 2012 à 14:44
Bonjour,

Comme l'extrême lenteur de la Bicubique m'a intrigué et que d'autre part la StretchBlt ignore totalement le canal Alpha j'ai tricoté le code suivant qui gère le canal Alpha et dans lequel l'interpolation Bicubique est remplacée par un dégradé de couleurs entre les 4 couleurs-source encadrant la maille, et voici la fonction StretchBmpAlpha qui pourrait remplacer la StretchBlt si on a besoin du canal Alpha et d'éviter la lenteur de la Bicubique :

function StretchBmpAlpha(const BmpS: tBitMap; k: single; const ProgressCallBack: TObjectProc = nil): tBitMap;
var
// Le BitMap-Source :
WS, HS: integer;
Scan0S: Integer; //Valeur du pointeur d'entrée dans le Bitmap-Source.
ScanS: Integer; //Pointeur temporaire destiné à être incrémenté.
MLSS: Integer; //Memory Line Size (en bytes) du Bitmap-Source.
// Le BitMap-result :
WR, HR: integer;
Scan0R: Integer; //Valeur du pointeur d'entrée dans le Bitmap-Source.
ScanR: Integer; //Pointeur temporaire destiné à être incrémenté.
MLSR: Integer; //Memory Line Size (en bytes) du Bitmap-Source.
Bpp: Integer; //Byte per pixel des Bitmap's
cl0, cl1, cl2, cl3, cl30, cl21: tRGBQuad;

xr, yr, xo1, xo2, yo1, yo2: integer;

function rgbQuadDegradee(cl1, cl2: tRGBQuad; d1, d12: single): tRGBQuad;
// Renvoie la couleur dégradée du point distant de d1 par rapport à celui de couleur cl1
// et situé entre les points de couleurs cl1 et cl2 séparés de d12
var k: single;
begin if d12 <> 0 then k := abs(d1 / d12) else k := 0.5;
Result.rgbRed := round(k * cl1.rgbRed + (1 - k) * cl2.rgbRed);
Result.rgbGreen := round(k * cl1.rgbGreen + (1 - k) * cl2.rgbGreen);
Result.rgbBlue := round(k * cl1.rgbBlue + (1 - k) * cl2.rgbBlue);
Result.rgbReserved := round(k * cl1.rgbReserved + (1 - k) * cl2.rgbReserved);
end;

function rgbQuadDegra4: tRGBQuad;
// Renvoie la couleur interpolée entre celle des 4 points
begin cl30 := rgbQuadDegradee(cl0, cl3, xr - k * xo1, k);
cl21 := rgbQuadDegradee(cl1, cl2, xr - k * xo1, k);
Result := rgbQuadDegradee(cl30, cl21, yr - k * yo1, k);
end;

begin WS := BmpS.width; HS := bmpS.Height; BmpS.PixelFormat := pf32bit; Bpp := 4;
Scan0S := Integer(BmpS.ScanLine[0]);
MLSS := Integer(BmpS.ScanLine[1]) - Scan0S;

Result := tBitMap.Create;
WR := trunc(k * BmpS.width); HR := trunc(k * bmpS.Height);
with Result do begin PixelFormat := pf32bit; width := WR; height := HR end;
Scan0R := Integer(Result.ScanLine[0]);
MLSR := Integer(Result.ScanLine[1]) - Scan0R;

for yr := 0 to HR - 1 do begin
yo1 := round(yr / k); yo2 := round((yr + k) / k); if yo2 = yo1 then inc(yo2);
for xr := 0 to WR - 1 do begin
xo1 := round(xr / k); xo2 := round((xr + k) / k); if xo2 = xo1 then inc(xo2);
if (yo2 < HS) and (xo2 < WS) then begin
ScanS := Scan0S; Inc(ScanS, yo1 * MLSS + xo1 * Bpp);
with cl0 do begin
rgbReserved := PRGBQuad(scanS)^.rgbReserved;
rgbRed := PRGBQuad(scanS)^.rgbRed;
rgbGreen := PRGBQuad(scanS)^.rgbGreen;
rgbBlue := PRGBQuad(scanS)^.rgbBlue;
end;
ScanS := Scan0S; Inc(ScanS, yo1 * MLSS + xo2 * Bpp);
with cl3 do begin
rgbReserved := PRGBQuad(scanS)^.rgbReserved;
rgbRed := PRGBQuad(scanS)^.rgbRed;
rgbGreen := PRGBQuad(scanS)^.rgbGreen;
rgbBlue := PRGBQuad(scanS)^.rgbBlue;
end;
ScanS := Scan0S; Inc(ScanS, yo2 * MLSS + xo1 * Bpp);
with cl1 do begin
rgbReserved := PRGBQuad(scanS)^.rgbReserved;
rgbRed := PRGBQuad(scanS)^.rgbRed;
rgbGreen := PRGBQuad(scanS)^.rgbGreen;
rgbBlue := PRGBQuad(scanS)^.rgbBlue;
end;
ScanS := Scan0S; Inc(ScanS, yo2 * MLSS + xo2 * Bpp);
with cl2 do begin
rgbReserved := PRGBQuad(scanS)^.rgbReserved;
rgbRed := PRGBQuad(scanS)^.rgbRed;
rgbGreen := PRGBQuad(scanS)^.rgbGreen;
rgbBlue := PRGBQuad(scanS)^.rgbBlue;
end;
ScanR := Scan0R; Inc(ScanR, yr * MLSR + xr * Bpp);
PRGBQuad(scanR)^ := rgbQuadDegra4;
end; // if yo2
end; // for xr
if Assigned(ProgressCallBack) then ProgressCallBack;
end; // for yr
end; // StretchBmpAlpha

... et voici pour son utilisation :

procedure TFrmDemoMain.btnStretchBmpAlphaClick(Sender: TObject);
begin LabMis.caption := ''; LabMis.Update;
GTC := GetTickCount;
imgResized.Picture.Bitmap.Assign(StretchBmpAlpha(imgOriginal.Picture.Bitmap, ScaleValue, ProgressBar2.StepIt));
LabMis.caption := 'StretchBmpAlpha Mis : ' + intToStr(GetTickCount - GTC) + ' ms';
imgResized.Invalidate;
ProgressBar2.Position := 0;
end;

Résultats de tests comparatifs de la vitesse pour un agrandissement de 400% d'un bitmap-source de 683x1024 qui passe donc à 2732x4096 :
- avec la StretchBlt : mis 140 ms,
- avec la StretchBmpAlpha : mis 1030 ms,
- avec la Bicubique : mis 11279 ms.

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
4 août 2012 à 14:16
Bonjour,

"Les résultats montrent que StretchBlt est quasi toujours la meilleur méthode." :
Elle est effectivement bigrement plus rapide que la Bicubique et en cas de très fort agrandissement qui laisse apparaître une qualité visuelle légèrement moins bonne que celle du résultat obtenu avec la bicubique il suffit (vérification faite) d'y appliquer ensuite un filtre de flou horizontal + vertical avec une trainée de 2 pixels et la StretchBlt + le flou restent ensemble toujours plus rapide que la bicubique avec une qualité visuelle équivalente.

+.
Rejoignez-nous