REMPLISSAGE D'UNE COURBE DE BÉZIER

cs_MAURICIO Messages postés 2106 Date d'inscription mardi 10 décembre 2002 Statut Modérateur Dernière intervention 15 décembre 2014 - 19 mai 2010 à 18:03
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 - 16 juil. 2010 à 12:22
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/51776-remplissage-d-une-courbe-de-bezier

cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
16 juil. 2010 à 12:22
Re-salut,

Complément à mon message précédent :

Tu dis "Si une boucle est grande, on verra les segments. Sauf si on augmente ton "nMax"" :
Non il y a confusion : "nMax" = nombre maxi de points de contrôle de la courbe qui sont tous à l'extérieur de la courbe sauf le 1er et le dernier.

C'est les 360 de Courbe : array [0..360] of tPoint qu'il faut augmenter le cas échéant.

Cordialement, et à +.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
16 juil. 2010 à 11:26
Salut Barbichette,

1) "Je te propose plutôt de créer une nouvelle source avec ta solution" : OK, tu peux le faire.

2) "mais je garderai la mienne ainsi. Pour plusieurs raisons" : Le coeur a ses raisons que la raison n'a pas (lol). Mais je te signale une autre raison : avec ta solution le remplissage se fait "pixel par pixel" donc il suffit d'une légère modif si on veut réaliser un remplissage en mode NotXor car avec ma solution le NotXor n'est applicable qu'à Pen.Mode et Brush.Mode n'existe pas.

3) "Uitliser l'API "polygon" en disant que ça prouve que l'on peut tracer une courbe de Bezier, ce n'est pas vrai (de mon point de vu)" : Pas tout à fait d'accord, car toute courbe fermée, tracée par quelque technique informatique que ce soit forme un ensemble de points que l'on nomme "polygone" lorsqu'il y a des angles ou courbe lorsque les points sont assez rapporchés et disposés en arc ... et dans tous ces cas l'API "polygon" permet non seulement d'en tracer le contour mais également son remplissage. Donc pour des courbes particulières rien n'interdit de prendre 3600 points de contour au lieu des 360 du code... sans oublier que si on tombe sur une zone de courbe où il est techniquement impossible que les points théoriques se matérialisent par des pixels situés côte à côte, zones où la fonction est fortement croissante, et où l'on obligé de faire des raccords linéaires.) OK ?

Cordialement, et à +.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
15 juil. 2010 à 21:23
Salut Pseudo3.

Je te propose plutôt de créer une nouvelle source avec ta solution mais je garderai la mienne ainsi.
Pour plusieurs raisons.
- Elle permet de voir comment tracer une courbe de Bézier et de la remplir, de façon mathématique.
- Utiliser une approximation d'une courbe ne revient pas à tracer une courbe. Si une boucle est grande, on verra les segments. Sauf si on augmente ton "nMax", mais dans ce cas, je ne trouve pas très judicieux de couper en 40 ou 50 une petite courbe.
- Uitliser l'API "polygon" en disant que ça prouve que l'on peut tracer une courbe de Bezier, ce n'est pas vrai (de mon point de vu).
- il y a de nombreux cas où l'on ne veut pas tracer une courbe dans un canvas (ce qui est mon cas) et dans ce cas, les API windows ne nous sont pas d'une grande aide. Donc, mieux vaut savoir ce qu'il y a derrière.

Barbichette
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
15 juil. 2010 à 11:00
Salut Barbichette,

Je pensais qu'avec sa quinzaine de lignes la procedure RemplirBezier ne pourrait plus être simplifiée.

Je vois que t'as pu la ramener à 9 lignes. C'est quand même considérablement plus simple que nos premiers codes.

Juste une remarque, pour ma part je vais la rebaptiser et la modifier légèrement comme suit, vu qu'elle n'est pas réservée aux courbes Bézier :
procedure RemplirPolyCourbe(var bmp: tBitMap; var PolyCourbe : array of tPoint; clRempli : tColor); histoire de se souvenir qu'elle est utilisable pour tout type de polygone ou de courbe fermée.

Ok sur tes explications concernant SetPolyFillMode : On pourrait ajouter aux paramètres d'appel de la procedure un boolean qui fait utiliser soit WINDING, soit ALTERNATE.

SetBkMode : exact je n'avais pas essayé de le supprimer : En fait j'étais parti d'une procedure qui permet de dessiner une fonte bi-colore (ex : jaune bordé de bleu) en y supprimant ce qui visait la fonte et en y ajoutant ce qui concerne les courbes ... et j'étais tellement content de voir que cela marchait avec si peu de lignes que j'en suis resté là.

Concernant ta source : Comme tu dis en 1ière ligne de la Description "Il existe une API pour tracer un polygone rempli. Mais pas pour les courbes de Bézier" et qu'on vient de prouver le contraire vu que cette API peut tracer le remplissage des Bézier il faudrait au moins corriger ceci. Pour le reste à toi de voir si tu veux conserver l'ancien code style "usine à gaz matheuse" ou si tu veux proposer la solution finale simple et concise.

Cordialement et à +.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
14 juil. 2010 à 19:41
La fonction RemplirBezier
peut ressembler à ça :

procedure RemplirBezier(var bmp: tBitMap; var cBezier : TBezier; clRempli : tColor);
begin
with bmp.Canvas do begin
Brush.Color:= clRempli;
SetBkMode(Handle,TRANSPARENT);
SetPolyFillMode(Handle,WINDING);
Polygon(cBezier.Courbe);
end;
end;

Par contre, une petite explication sur SetPolyFillMode :
C'est une fonction qui gère la règle à appliquer pour définir l'intérieur de l'extérieur du dessin
WINDING : règle de l'enroulement (on dessine du premier bord gauche au bord droit sans interruption)
ALTERNATE : règle du pair/impair (on entre dans le dessin au bord gauche, on en ressort au bord suivant, on y rentre au bord suivant.... jusqu'au bord droit)

une petite explication sur SetBkMode :
et bien je n'en vois pas l'utilité ici, mais bon,
OPAQUE : dessine l'arrière plan avec la couleur de fond
TRANSPARENT : ne touche rien à l'arrière plan
Ici, la fonction polygon ne touche rien à l'arrière plan à l'extérieur du polygone... Donc, on peut le supprimer

Barbichette
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
14 juil. 2010 à 16:36
Re-bonjour,

En final l'API signalée par Barbichette permet d'effectuer le remplissage en seulement 15 lignes de codes : voir la procédure RemplirBezier dans le code final ci-après où pour faire plaisir à Barbichette j'ai remis sa palette et ajouté la routine ContourBezierDegrade(Bmp, MaBezier, 2, Palette) pour le tracé final du bord aux couleurs dégradées de l'arc-en-ciel.
(Palette + ContourBezierDegrade coûte 30 lignes ça fait beaucoup)

unit uRemplirBezier; // Remplissage d'une courbe de Béziers avec API Polygon

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Math, ComCtrls, Buttons;

type
TfrmBezier = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormResize(Sender: TObject);
private
public
end;

var
frmBezier: TfrmBezier;

const indexMax = 360;

type TBezier = record PC : array of TPoint; // Points de contrôle de la courbe
nPC : integer; // nb de Points de contrôle
Courbe : array [0..indexMax] of tPoint; //<- contour de la courbe mémorisé en un polygone de points en nombre réduit
end;

var MaBezier : TBezier;
Bmp : tBitMap; //<- BitMap de travail

type tpalette = array[0..indexMax] of longint;
var palette : tpalette;

implementation

{$R *.dfm}

procedure PrecalculPalette; // palette de couleur pour contour dégradé arc-en-ciel
var i:integer;
begin for i:=0 to indexMax do
Case (i div 60) of
0,6:palette[i]:=rgb(255,(i Mod 60)*255 div 60,0);
1: palette[i]:=rgb(255-(i Mod 60)*255 div 60,255,0);
2: palette[i]:=rgb(0,255,(i Mod 60)*255 div 60);
3: palette[i]:=rgb(0,255-(i Mod 60)*255 div 60,255);
4: palette[i]:=rgb((i Mod 60)*255 div 60,0,255);
5: palette[i]:=rgb(255,0,255-(i Mod 60)*255 div 60);
end;
end;

function CombinaisonsCpn(n,p : integer) : integer;
// Result = (n!)/((p!)*(n-p)!) mais sans utiliser les factorielles pour éviter autant que possible l'overflow
var i,eli,num,deno : integer;
begin num:=1; deno:=1; eli:=n-(2*p); if eli<0 then eli:=0;
try for i:=p+1+eli to n do num:=num*i;
for i:=1 to n-p-eli do deno:=deno*i;
if deno=0 then deno:=1; // car convention 0!=1
Result:=num div deno;
except on EOverflow
do begin Result:=-1; showMessage( 'EOverflow dans CombinaisonsCpn pour'+#13#10
+'n = '+intToStr(n)+#13#10
+'p = '+intToStr(p));
end;
end;
end; // CombinaisonsCpn

const nMax = 30; // nombre maxi de points de contrôle de la courbe
type tCoeff = array [0..nMax,0..nMax] of integer;
var kBin : tCoeff;

procedure PreCalculCoeffsBezier;
var i,n : byte;
begin for i:=0 to nMax do for n:=0 to nMax do kBin[i,n]:=CombinaisonsCpn(n,i);
end;

procedure TraceContourBezier(var bmp: tBitMap; var cBezier : TBezier; epTrait : byte; clTrait : tColor);
var iu,i : integer;
xp,yp,binu,xB,yB,u : Single;
begin with cBezier do
begin xp:=0; yp:=0; xB:=0; yB:=0;
with bmp.canvas do
begin pen.width:=epTrait; pen.color:=clTrait; brush.style:=bsClear;
for iu:=0 to indexMax do
begin u:=iu/indexMax;
for i:=0 to npc-1 do
begin binu:=kBin[i,npc-1]*Power(u,i)*Power(1-u,npc-1-i);
if i=0 // xb,yb = Point sur la courbe de Bézier
then begin xb:=PC[i].x*binu; yb:=PC[i].y*binu; end
else begin xb:=xb+PC[i].x*binu; yb:=yb+PC[i].y*binu; end;
end;
if iu = 0 then begin xp:=xb; yp:=yb; end;
Courbe[iu].x:=round(xb); Courbe[iu].y:=round(yb); //<- Polygone de points sur la courbe
MoveTo(round(xp),round(yp)); LineTo(round(xb),round(yb)); // points reliés par LineTo pour fermer la courbe
xp:=xb; yp:=yb;
end;
end;
end;
end; // TraceContourBezier

procedure RemplirBezier(var bmp: tBitMap; var cBezier : TBezier; clRempli : tColor);
var dc : HDC; AncBrush,NouvBrush : HBRUSH;
begin dc := bmp.canvas.Handle;
// initialisation de la couleur de remplissage
NouvBrush := CreateSolidBrush(clRempli);
AncBrush := SelectObject(dc,NouvBrush);
// le contexte doit être transparent
SetBkMode(dc,TRANSPARENT);
SetPolyFillMode(dc,Winding);
Polygon(dc,cBezier.Courbe,length(cBezier.Courbe));
// Restauration objets et libération mémoire
SelectObject(dc,AncBrush);
DeleteObject(NouvBrush);
end;

procedure ContourBezierDegrade(var bmp: tBitMap; var cBezier : TBezier; epTrait : integer; Pal : tpalette);
var iu : integer;
begin with cBezier do
begin with bmp.canvas do
begin pen.width:=epTrait; brush.style:=bsClear;
for iu:=1 to indexMax do
begin pen.color:=Pal[iu];
MoveTo(Courbe[iu-1].x,Courbe[iu-1].y);
LineTo(Courbe[iu].x,Courbe[iu].y);
end;
end;
end;
end; // ContourBezierDegrad

// Utilisation : ---------------------------------------------------------------

// Initialisations :
procedure TfrmBezier.FormCreate(Sender: TObject);
begin bmp:=tbitmap.Create;
with bmp do begin PixelFormat:=pf24Bit; Width:=ClientWidth; Height:=ClientHeight; end;
with MaBezier do begin nPC:=10; SetLength(PC,nPC); end;
PrecalculCoeffsBezier;
PrecalculPalette;
end;

// redimensionnement de la fenêtre => on redimensionne le bitmap temporaire
procedure TfrmBezier.FormResize(Sender: TObject);
begin bmp.Width:=clientwidth; bmp.Height:=clientheight;
end;

// Mouvement de la souris, on affiche la nouvelle courbe de bezier
procedure TfrmBezier.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
begin // on actualise le tableau des points de contrôle de la courbe
with MaBezier do begin
pc[0]:=point(x-15,y-15);
pc[1]:=point(x+50,y-100);
pc[2]:=point(150-y,350);
pc[3]:=point(-250,350+(y div 3));
pc[4]:=point(850-x,350-y);
pc[5]:=point( 100+x div 2,-50+x*y div (x+y));
pc[6]:=point( 50+x div 2,-50+x*y div (x+y));
pc[7]:=point( 15,-50+x*y div (x+y));
pc[8]:=point(x+50,bmp.height-20);
pc[9]:=PC[0];
end;
// on efface le bitmap temporaire de dessin
bmp.Canvas.Brush.Color:=clWhite;
bmp.Canvas.FillRect(clientrect);
// on y trace d'abord une ellipse pour garnir le fond
bmp.canvas.pen.color:=RGB(254,5,5); //<- un rouge numériqement différent de clRed
bmp.canvas.Ellipse( 100,50, 200,300);
// on y dessine la courbe de bézier puis son remplissage
TraceContourBezier(Bmp, MaBezier, 1, clRed);
RemplirBezier(Bmp, MaBezier, clAqua);
// et pour faire un contour dégradé :
ContourBezierDegrade(Bmp, MaBezier, 2, Palette);
// on affiche le résultat
Canvas.Draw(0,0,bmp);
end;

END. ///////////////////////////////////////////////////////////////////////////

Cordialement, et à plus.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
14 juil. 2010 à 12:17
Bonjour,

Je tiens à préciser que mon seul objectif est de trouver une solution qui résoud ce problème en un nombre minimal de lignes ... donc je m'accroche tant que j'ai la conviction que l'on pourrait gratter encore des lignes.

Barbichette, tu dis : "De plus, il n'est pas possible, il me semble de produire des courbes comme la mienne, à savoir avec un dégradé personnalisable, avec les API, et le remplissage ne marche avec floodfill ne marche bien que si le fond est uni ou si le contour est uni, il me semble".

Objection, votre honneur, car :

1) Concernant le dégradé personnalisable : Dans mon code je l'ai viré car c'était hors sujet. Mais comme le contour de la courbe est mémorisé dans le tableau nommé Courbe : array [0..360] of tPoint, rien n'empêcherait de réaliser ce dégradé à la fin du remplissage de la courbe en re-traçant le contour (par dessus-le contour initial uniforme) avec une boucle de 361 MoveTo..LineTo entre lesquels on place un pen.color:=Palette[i] ...

2) Concernant le "floodfill ne marche bien que si le fond est uni" : Pour éviter ceci j'ai utilisé le FlodFill du style fsBorder et j'ai fait un test de bon fonctionnement en traçant des ellipses sur le bitMap avant la courbe de Bézier et il suffit que les couleurs des contours des diverses courbes présentes sur le même Bmp soient numériquement différentes (un rouge RGB(255,1,1) est différent d'un rouge RGB(254,2,2) mais visuellement ils sont kif-kif) pour éviter les embrouilles.

Ceci étant je suis persuadé que l'on devrait pouvoir simplifier encore davantage en utilisant l'API cité dans la première ligne de la "Description" et qui dit "Il existe une API pour tracer un polygone rempli. Mais pas pour les courbes de Bézier" ... or dans mon cas la courbe de Bézier n'est rien d'autre qu'un polygone de 361 points.

Cordialement, et à +.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
13 juil. 2010 à 18:35
Bonjour,

Merci pour cette simplification, mais je préciserai que mon but n'était pas d'utiliser au maximum les API Windows mais bien de montrer le mécanisme qui se cache derrière.
Je rappelle qu'en utilisant de façon astucieuse scanline au lieu de pixels, mon code tourne presque aussi rapidement que le fait photoshop.
De plus, il n'est pas possible, il me semble de produire des courbes comme la mienne, à savoir avec un dégradé personnalisable, avec les API, et le remplissage ne marche avec floodfill ne marche bien que si le fond est uni ou si le contour est uni, il me semble.

Mais, toutes les idées sont bonne à prendre et je pense qu'un crack en mathématique pourrai trouver une solution en simplifiant les équations du 3° degrés par des calcules plus simple...

Barbichette
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
13 juil. 2010 à 12:25
Bonjour,

Voici une version corrigée du code de mon message précédent qui foirait dans certains cas particuliers de courbes qui rebouclaient fortement sur elles-mêmes.
Le code ci-après ne fait que 220 lignes soit environ deux fois moins que celui de Barbichette ... et il est peut-être possible de le simplifier encore davantage.

unit uFloodFillBezier; // Remplissage d'une courbe de Béziers avec FloodFill(x,y,clBord,fsBorder)
// Les coordonnées x,y du point d'amorçage de FloddFill sont ici obtenues
interface // simplement, graphiquement et par utilisation de ScanLine pour la vitesse.

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Math, ComCtrls, Buttons;

type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormResize(Sender: TObject);
private
public
end;

var
Form1: TForm1;

type TBezier = record PC : array of TPoint; // Points de contrôle de la courbe
nPC : integer; // nb de Points de contrôle
Courbe : array [0..360] of tPoint; //<- contour de la courbe mémorisé en un polygone de points en nombre réduit
end;

var MaBezier : TBezier;
Bmp : tBitMap; //<- BitMap de travail

implementation

{$R *.dfm}

function CombinaisonsCpn(n,p : integer) : integer;
// Result = (n!)/((p!)*(n-p)!) mais sans utiliser les factorielles pour éviter autant que possible l'overflow
var i,eli,num,deno : integer;
begin num:=1; deno:=1; eli:=n-(2*p); if eli<0 then eli:=0;
try for i:=p+1+eli to n do num:=num*i;
for i:=1 to n-p-eli do deno:=deno*i;
if deno=0 then deno:=1; // car convention 0!=1
Result:=num div deno;
except on EOverflow
do begin Result:=-1; showMessage( 'EOverflow dans CombinaisonsCpn pour'+#13#10
+'n = '+intToStr(n)+#13#10
+'p = '+intToStr(p));
end;
end;
end; // CombinaisonsCpn

const nMax = 30; // nombre maxi de points de contrôle de la courbe
type tCoeff = array [0..nMax,0..nMax] of integer;
var kBin : tCoeff;

procedure PreCalculCoeffsBezier;
var i,n : byte;
begin for i:=0 to nMax do for n:=0 to nMax do kBin[i,n]:=CombinaisonsCpn(n,i);
end;

procedure TraceContourBezier(var bmp: tBitMap; var cBezier : TBezier; epTrait : byte; clTrait : tColor);
var iu,iuMax,i : integer;
xp,yp,binu,xB,yB,u : Single;
begin with cBezier do
begin xp:=0; yp:=0; xB:=0; yB:=0; iuMax:=High(cBezier.Courbe);
with bmp.canvas do
begin pen.width:=epTrait; pen.color:=clTrait; brush.style:=bsClear;
for iu:=0 to iuMax do
begin u:=iu/iuMax;
for i:=0 to npc-1 do
begin binu:=kBin[i,npc-1]*Power(u,i)*Power(1-u,npc-1-i);
if i=0 // xb,yb = Point sur la courbe de Bézier
then begin xb:=PC[i].x*binu; yb:=PC[i].y*binu; end
else begin xb:=xb+PC[i].x*binu; yb:=yb+PC[i].y*binu; end;
end;
if iu = 0 then begin xp:=xb; yp:=yb; end;
Courbe[iu].x:=round(xb); Courbe[iu].y:=round(yb); //<- Polygone de points sur la courbe
MoveTo(round(xp),round(yp)); LineTo(round(xb),round(yb)); // points reliés par LineTo pour fermer la courbe
xp:=xb; yp:=yb;
end;
end;
end;
end; // TraceContourBezier

function PointDansPolygone(const x,y : integer; Polygone : array of TPoint): boolean;
// Renvoie True si le point x,y est à l'intérieur du Polygone de points
var Handle : HRGN;
begin Handle := CreatePolygonRgn(Polygone[0],length(Polygone),Winding);
Result := PtInRegion(Handle,X,Y);
DeleteObject(Handle);
end;

type TRGBArray = ARRAY[0..0] OF TRGBTriple; // Elément de bitmap (API windows)
pRGBArray = ^TRGBArray; // Type pointeur vers tableau 3 octets 24 bits

procedure FloodFillBezier(const bmp : TBitMap; var cBezier : TBezier; clBord, clFill : tColor);
label Saut;
var Scans : array[0..2047] of pRGBArray; //< Tableau de ScanLines pour BitMap's d'au plus 2048 lignes
i,h,w,x,y,xMin,yMin,xMax,yMax,ipi,ipiMax,TotNbInter,numBord : integer;
nbInterSect : array of Longint; cl1,cl2 : tColor;
PointsDAmo : array of tPoint;

function cl(xi,yi : integer) : tColor;
begin Result:=RGB(Scans[yi,xi].rgbtRed,Scans[yi,xi].rgbtGreen,Scans[yi,xi].rgbtBlue);
end;

function EstPointEnclave(ix,iy : integer) : boolean; // Point interne entouré de clBord
var clxy,clN,clO,clS,clE : tColor;
begin Result:=false;
if (ix-1<0) or (iy-1<0) or (ix+1>w-1) or (iy+1>h-1) then EXIT;
clxy:=cl(ix,iy);
clN:=cl(ix ,iy-1); // Nord
clO:=cl(ix-1,iy); // Ouest
clS:=cl(ix ,iy+1); // Sud
clE:=cl(ix+1,iy); // Est
Result:=(clxy<>clBord) and (clN=clBord) and (clO=clBord)
and (clS=clBord) and (clE=clBord);
end;

begin W:=bmp.width; H:=bmp.height;
for y := 0 to h-1 do Scans[y]:=bmp.ScanLine[y];
SetLength(nbInterSect,h);
// Parcours de reconnaissance du BitMap :
// a) Limitation de la zone à scanner au rectangle [xMin,yMin .. xMax,yMax] entourant la courbe
xMin:=MaxInt; yMin:=MaxInt; xMax:=0; yMax:=0;
for i:=0 to High(cBezier.Courbe) do begin
xMin:=min(xMin,cBezier.Courbe[i].x); yMin:=min(yMin,cBezier.Courbe[i].y);
xMax:=max(xMax,cBezier.Courbe[i].x); yMax:=max(yMax,cBezier.Courbe[i].y);
end;
if xMax>W-1 then xMax:=W-1; if yMax>H-1 then yMax:=H-1;
if xMin<0 then xMin:=0; if yMin<0 then yMin:=0;
TotNbInter:=0;
// b) Comptage du nombre d'intersections de la courbe avec chaque horizontale :
y:=yMin-1;
Saut :
repeat inc(y);
nbInterSect[y]:=0;
for x:=xMin+1 to xMax do begin
cl1:=cl(x-1,y); cl2:=cl(x,y);
if (cl1=clBord) and (cl2<>clBord) then // nombre d'intersections sauf lignes à cas de points enclavés
begin if Not EstPointEnclave(x,y)
then begin inc(nbInterSect[y]); inc(TotNbInter); end
else begin nbInterSect[y]:=1; //<- Nb impair : marque les lignes à éviter par la suite
Goto Saut;
end;
end;
end;
until y = yMax;
// c) Récup des positions des points d'amorçage utiles à FloodFill :
SetLength(PointsDAmo,TotNbInter); ipi:=0; ipiMax:=0;
for y:=yMin to yMax do begin
numBord:=1;
if Odd(nbInterSect[y]) then Continue; // on évite les lignes à nombre impair d'intersections
if (nbInterSect[y]=nbInterSect[y-1]) then Continue; // on évite aussi les doublons
for x:=xMin+1 to xMax do begin
cl1:=cl(x-1,y); cl2:=cl(x,y);
// Sur les lignes horizontales comportant un nombre pair d'intersections avec la courbe
// le point d'amorçage sélectionné pour FloodFill est le point situé immédiatement à droite
// de chaque intersection de numéro impair vu que la numérotation commence ici par numBord:=1
if (cl1=clBord) and (cl2<>clBord) then begin
if Odd(numBord) then begin
PointsDAmo[ipi].x:=x; PointsDAmo[ipi].y:=y;
ipiMax:=ipi;
inc(ipi);
end;
inc(numBord);
end;
end;
end;
// d) Utilisation des points d'amorçage par FlooFill
with bmp.canvas do begin
brush.Color:=clFill;
for ipi:=0 to ipiMax do begin
x:=PointsDAmo[ipi].x; y:=PointsDAmo[ipi].y;
if PointDansPolygone(x,y, cBezier.Courbe) then begin
FloodFill(x,y,clBord,fsBorder);
end;
end;
end;
end; // FloodFillBezier

// Utilisation : ---------------------------------------------------------------

// Initialisations :
procedure TForm1.FormCreate(Sender: TObject);
begin bmp:=tbitmap.Create;
with bmp do begin PixelFormat:=pf24Bit; Width:=ClientWidth; Height:=ClientHeight; end;
with MaBezier do begin nPC:=10; SetLength(PC,nPC); end;
PrecalculCoeffsBezier;
end;

// redimensionnement de la fenêtre => on redimensionne le bitmap temporaire
procedure TForm1.FormResize(Sender: TObject);
begin bmp.Width:=clientwidth; bmp.Height:=clientheight;
end;

// Mouvement de la souris, on affiche la nouvelle courbe de bezier
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
begin // on actualise le tableau des points de contrôle de la courbe
with MaBezier do begin
pc[0]:=point(x-15,y-15);
pc[1]:=point(x+50,y-100);
pc[2]:=point(150-y,350);
pc[3]:=point(-250,350+(y div 3));
pc[4]:=point(850-x,350-y);
pc[5]:=point( 100+x div 2,-50+x*y div (x+y));
pc[6]:=point( 50+x div 2,-50+x*y div (x+y));
pc[7]:=point( 15,-50+x*y div (x+y));
pc[8]:=point(x+50,bmp.height-20);
pc[9]:=PC[0];
end;
// on efface le bitmap temporaire de dessin
bmp.Canvas.Brush.Color:=clWhite;
bmp.Canvas.FillRect(clientrect);
// on y trace la courbe puis son remplissage
TraceContourBezier(Bmp, MaBezier, 1, clRed);
FloodFillBezier(Bmp, MaBezier, clRed, clAqua); //<- la couleur du contour ici clRed doit être identique à celle utilisée par TraceContourBezier sinon Violation d'accès
// on affiche le résultat
Canvas.Draw(0,0,bmp);
end;

END. ///////////////////////////////////////////////////////////////////////////

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 juil. 2010 à 13:18
Bonjour,

Finalement j'ai abandonné la piste évoquée dans mon msg précédent et voici le code d'une solution hyper-simple utilisant FloodFill :

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Math, ComCtrls;

type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FormResize(Sender: TObject);
private
public
end;

var
Form1: TForm1;

type TBezier = record
PC : array of TPoint; // Points de contrôle
nPC : integer; // nb de Points de contrôle
R : tRect; //Rectangle incluant enveloppe convexe des points de contrôle
end;

var MaBeziers : TBezier;
Bmp : tBitMap; //<- BitMap de travail

implementation

{$R *.dfm}

function CreeBezier( iPC : array of TPoint) : TBezier;
var i : integer;
begin with Result do
begin nPC:=length(iPC);
SetLength(PC,nPC);
for i:=low(PC) to High(PC) do PC[i]:=iPC[i];
end;
end;

procedure TraceContourBezier(var bmp : TBitMap; var cBezier : TBezier; epTrait : byte; clTrait : tColor);
var i : integer;
begin with bmp.canvas do begin
pen.width:=epTrait;
pen.color:=clTrait;
brush.Style:=bsClear;
PolyBezier(cBezier.PC);
end;
// Rectangle délimitant l'enveloppe convexe des points de contrôle et incluant la courbe.
with cBezier do begin
R:=Rect(MaxInt,MaxInt,0,0);
for i:=low(PC) to High(PC) do begin
R.Left:=min(R.Left,PC[i].x);
R.Top :=min(R.Top,PC[i].y);
R.Right:=max(R.Right,PC[i].x);
R.Bottom:=max(R.Bottom,PC[i].y);
end;
// Recadrage si R déborde du BitMap
if R.Left<0 then R.Left:=0; if R.Top<0 then R.Top:=0;
if R.Right>bmp.Width-1 then R.Right:=bmp.Width-1;
if R.Bottom>bmp.Height-1 then R.Bottom:=bmp.Height-1;
end;
end; // TraceContourBezier

type pLigScan = ^tLigScan;
tLigScan = array[Word] of TRGBTriple;

procedure FloodFillBezier(var bmpBez : TBitMap; cBezier : TBezier; clBord, clFill : tColor);
var x,y,yDeb,ipi,ipiMax,TotNbInter,numBord,R,G,B : integer;
nbInterSect : array of Longint;
Lig : pLigScan; SurBord : boolean;
PointsF : array of tPoint;
begin SetLength(nbInterSect,bmpBez.Height);
// Parcours de Reconnaissance du BitMap dans la zone du Rectangle de délimitation :
R:=GetRValue(clBord);
G:=GetGValue(clBord);
B:=GetBValue(clBord);
y:=-1;
repeat inc(y); x:=-1; nbInterSect[y]:=0; // zone de lignes sans intersection
Lig:=bmpBez.ScanLine[y];
repeat inc(x);
SurBord:=(Lig[x].rgbtRed=R) and (Lig[x].rgbtGreen=G) and (Lig[x].rgbtBlue=B);
until (x=cBezier.R.Right) or SurBord;
until (y=cBezier.R.Bottom) or SurBord;
yDeb:=y; TotNbInter:=0;

with bmpBez.canvas do begin
// Comptage du nombre d'intersections de la courbe avec chaque horizontale
for y:=yDeb to cBezier.R.Bottom do begin
nbInterSect[y]:=0;
for x:=1 to bmpBez.width-1 do begin
if (Pixels[x-1,y]=clBord) and (Pixels[x,y]<>clBord) then
begin inc(nbInterSect[y]); inc(TotNbInter); end;
end;
end;
// Récup des positions des points d'amorçage :
SetLength(PointsF,TotNbInter); ipi:=0; ipiMax:=0;
for y:=yDeb to cBezier.R.Bottom do begin
numBord:=1;
if Odd(nbInterSect[y]) then Continue; // on évite les lignes à nombre impair d'intersections
if (nbInterSect[y]=nbInterSect[y-1]) then Continue; // on évite aussi les doublons
for x:=1 to cBezier.R.Right do begin
// Sur les lignes horizontales comportant un nombre pair d'intersections avec la courbe
// le point d'amorçage de FloodFill est le point situé immédiatement à droite
// de chaque intersection de numéro impair
if (Pixels[x-1,y]=clBord) and (Pixels[x,y]<>clBord) then begin
if Odd(numBord) then begin
PointsF[ipi].x:=x; PointsF[ipi].y:=y;
ipiMax:=ipi;
inc(ipi);
end;
inc(numBord);
end;
end;
end;
brush.Color:=clFill;
pen.color:=clBlue;
for ipi:=0 to ipiMax do begin
x:=PointsF[ipi].x; y:=PointsF[ipi].y;
FloodFill(x,y,clBord,fsBorder);
// Marquage avec une croix du point d'amorçage de FloodFill :
// MoveTo(x-3,y); LineTo(x+3,y);
// MoveTo(x,y-3); LineTo(x, y+3);
end;
end;
end; // FloodFillBezier

procedure BezierFermeeInit; // Initialisation de la Courbe avec le nombre voulu de points
// de contrôle et de valeurs initiales quelconques :
var pt : Array of TPoint;
begin setlength(pt,10);
pt[0]:=point(10,10); pt[1]:=point(50,50); pt[2]:=point(35,35);
pt[3]:=point(45,35); pt[4]:=point(55,35); pt[5]:=point(18,42);
pt[6]:=point(14,75); pt[7]:=point( 2,50); pt[8]:=point(41,56);
pt[9]:=pt[0]; //<- fermeture boucle
MaBeziers:=CreeBezier( Pt );
end;

procedure TForm1.FormCreate(Sender: TObject);
begin bmp:=tbitmap.Create;
with bmp do begin
PixelFormat:=pf24bit;
Width:=ClientWidth;
Height:=ClientHeight;
end;
BezierFermeeInit;
end;

// Traitement des messages
//==============================================================================
// mouvement de la souris, on affiche la courbe de bezier
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
begin // on actualise le tableau des points de contrôle de la courbe
with MaBeziers do begin
PC[0]:=point(x-15,y-15);
PC[1]:=point(x-50,y-100);
PC[2]:=point(35 + x,35 + y);
PC[3]:=point(45 + y,35 + x);
PC[4]:=point(width div 2,height div 2);
PC[5]:=point( 100+x div 2,50+x*y div (x+y));
PC[6]:=point( 50+x div 2,50+x*y div (x+y));
PC[7]:=point( x div 2,50+x*y div (x+y));
PC[8]:=point(x+50,y+100);
PC[9]:=PC[0]; // <- Courbe refermée
end;
// on efface le bitmap temporaire de dessin
bmp.Canvas.Brush.Color:=clWhite;
bmp.Canvas.FillRect(clientrect);
// on y trace la courbe et son remplissage
TraceContourBezier(Bmp, MaBeziers, 1, clRed);
FloodFillBezier(Bmp, MaBeziers, clRed, clAqua);

// on affiche le résultat
canvas.Draw(0,0,bmp);
end;

// redimensionnement de la fenêtre => on redimensionne le bitmap temporaire
procedure TForm1.FormResize(Sender: TObject);
begin bmp.Width:=clientwidth;
bmp.Height:=clientheight;
end;

END. ///////////////////////////////////////////////////////////////////////////

A+.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
30 juin 2010 à 16:55
Bonjour,

La remarque de Barbichette disant en introduction "Parce que je vais ensuite saucissonner ma patate en tranches de 1 pixel de hauteur" m'a donné une idée qui permet de simplifier énormément la partie mathématique du problème au point de pouvoir se passer entièrement de la résolution d'une éqquation du 3ième degré à chaque tour de boucle.

Voici l'idée dans ses grandes lignes :
- 1ière étape : On calcule les coordonnées de tous les points du contour de la courbe et on les sauve dans un tableau.
- 2ième étape : On trie ce tableau de façon à en classer les points en ordre décroissant des Y et des X comme dans l'exemple çi-après où les Y sont formés par le premier nombre :
0041:0056 <-- ici Y=41 et X=56
0041:0057

0042:0055 <-- ici pour Y = 42 on a Xmin=55 et Xmax=57
0042:0056
0042:0057 <-- Xmax=57
N.B : ce tri-double a été obtenu par utilisation d'une StringList.
- 3ième étape : Pour chaque valeur de Y on trace un trait de remplissage reliant horizontalement Xmin à Xmax.
Testé : ça marche sauf qu'il faut modifier la 3ième étape pour ajouter des traits de remplissage intermédiaires par interpolation entre deux rayures disjointes lorsque dans le tableau des Y,X on saute d'un Y vers un Y+DeltaDifférendDeUn.

Mon test a révélé qu'en partant de 360 points répartis sur le contour de la courbe il reste des rayures blanches du background à combler, mais je pense quand même qu'une simple interpolation ça doit être un peu plus simple que de résoudre une équa du 3ième degré. Je verrai cela dans les jours qui viennent.

A+.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
29 juin 2010 à 18:33
Merci Caribensila, tu as été plus rapide que moi.

Barbichette
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
29 juin 2010 à 13:41
Re-bonjour,

Merci Caribensila : j'ai donc créé la fonction sign et du coup le code marche, je vais donc pouvoir le tester.

A+.
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 juin 2010 à 12:36
Bonjour,

Sign,fonction
Indique si une valeur numérique est positive,négative ou nulle.

Unité
Math

Description
Utilisez Sign pour tester le signe d'une valeur numérique.
Sign renvoie :
0 si AValue vaut zéro.
1 si AValue est supérieure àzéro.
-1 si AValue est inférieure àzéro.
cs_pseudo3 Messages postés 268 Date d'inscription mardi 24 juillet 2007 Statut Membre Dernière intervention 2 février 2021 1
29 juin 2010 à 12:21
Bonjour,

Je m'apprêtais à tester le code mais comme j'utilise Delphi-5 je ne dispose pas de la function 'sign' utilisée pour le calcul de mode:=(sign(p2.x-p1.x)+1)+(sign(p2.y-p1.y)+1)*3;

Quel est le calcul effectué par sign ou par quoi puis-je remplacer sign. S.V.P. Merci

A+
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
6 juin 2010 à 20:22
Salut Cari,
Polybezier ne remplit effectivement pas la courbe, et le problème de FloodFill, c'est de trouver où lancer le remplissage car les courbes de Béziers on la fâcheuse tendance à faire des boucles, voir des courbes qui se frôlent de façon tangentielle.

Il est en effet acceptable de faire le remplissage à ma façon et le contour avec PolyBezier, mais je ne suis pas sûr à 100% que les deux sont bien superposable.
De plus, ma source propose les deux (contour+remplissage). Libre aux gens d'en prendre que la moitié...

Enfin, il n'est pas toujours intéressant de dessiner directement dans un Tcanvas et il est parfois nécessaire de tout réécrire soit même.
L'exemple le plus flagrant étant un logiciel de dessin que je développe actuellement et qui ne permet pas, en l'occurrence de dessiner dans un tcanvas, surtout si l'on veux des bords perso (comme des pointillées de différente tailles). Ce qui n'est d'ailleurs pas possible avec les api, même avec une simple ligne droite.

Barbichette
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
4 juin 2010 à 23:03
Salut Barbichette,

Etait-ce bien la peine de dessiner une 'Bézier' avec les équations paramétrées du 3ème degré sans utiliser les APIs ou méthodes de TCanvas comme 'PolyBezier', par exemple ?
Ces méthodes, et surtout la méthode 'FloodFill' de TCanvas n'étaient-elles pas suffisantes et même plus souples ?

Ceci dit, ce code-source est super intéressant pour ceux qui veulent entrer dans les arcanes du dessin d'une 'Bézier', qui est nativement plutôt assez pauvre je trouve.
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
27 mai 2010 à 16:47
Pour l'histoire de la rapidité, j'ai déjà fait des essais en utilisant "Scanline" au lieu de "Pixels", je ne vois plus de ralentissement (sur ma machine de compétition...)

Barbichette
cs_barbichette Messages postés 220 Date d'inscription lundi 30 octobre 2000 Statut Membre Dernière intervention 15 juillet 2013
24 mai 2010 à 17:31
merci pour ces tuyaux. Et merci pour ces éloges,...
Je suis honnête, je n'ai pas cherché à optimiser le programme, mais à tester sont fonctionnement et voir si ça tournait...
Et en plus, j'avais besoin de ce code pour le remplissage et surtout pour le tracé des contours dans le bon ordre.
Pour réduire la courbe à 4 points, c'était ma première piste. Piste que j'ai mis en pratique dans un logiciel "commercial" mais où je ne suis pas très content.
Par contre, lorsque la courbe fait des grandes circonvolutions, les segments apparaissent clairement. Pour limiter la chose, je mesure les distances entre les 4 points de la courbe (le départ, l'arrivée et les 2 poignées), ce qui me donne une grossière approximation de la longueur de la courbe, et je peux ainsi diviser non pas en 4 mais en un nombre de point plus important qui dépends de la longueur de la courbe. Mais même avec cette solution, le résultat n'est pas top top...

Enfin, si j'ai le courage, j'ai la même chose pour tracer des ellipses en travers, des camemberts,...
Mais il faut que je réécrive certains passages...

Barbichette
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
24 mai 2010 à 12:27
Oui voilà c'est ce que je proposais.

"Bref, peut-être est-ce trop compliqué pour quelques chose qui tourne très bien sur un PC de moins de 2 ans."
Mon ordinateur est un ordinateur portable de moins d'un an, plutôt décent niveau puissance de calcul. Seulement, je maintiens, essaye de faire glisser la souris vers l'extrémité bas/gauche de la fiche, tu verras (à moins que ton ordinateur soit en effet un bolide) un petit ralentissement. Donc oui, ça tourne bien, mais pas "très bien" pour des grandes surfaces et je pense qu'il est quand même possible d'améliorer un petit peu. Faire simplement un polygone qui économiserait déjà 50% des calculs serait déjà un gain énorme. Non ?

Cordialement, Bacterius !
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
24 mai 2010 à 12:21
Peut-être en calculant 4 points sur la courbe pour peindre un polygone dont 2 seraient les extremités...
Ensuite, on a plus qu' à tester les points restants entre la courbe et le polygone.

Bref, peut-être est-ce trop compliqué pour quelques chose qui tourne très bien sur un PC de moins de 2 ans.

Mauricio
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
24 mai 2010 à 12:14
Désolé, j'ai oublié la note ...

Cordialement, Bacterius !
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
24 mai 2010 à 12:14
Salut Barbichette,
très bonne source, c'est rare en ces temps difficiles :(

Le rendu est joli chez moi, seulement il est un peu long quand l'aire devient trop importante.
Il y a des optimisations simples à faire (et d'autres plus complexes) : par exemple, as-tu besoin d'une précision de plus de 20 chiffres décimaux ? Tu peux alors laisser de côté le Extended (assez gourmand en temps de calcul) et opter pour un Single qui offre une précision décente (bien suffisante pour nos besoins humains) à un coût relativement faible par rapport à celui de l'Extended.

Une autre optimisation plus efficace serait de véritablement tester les points qui sont susceptibles d'être "à la limite" de la courbe de Bézier. Par exemple, tu peux te fixer une borne autour de la courbe de quelques pixels, et traiter uniquement ces pixels avec les formules mathématiques. Le reste, qui est certain de faire partie du remplissage, peut se remplir rapidement avec une fonction standard au prix de quelques calculs bien moins importants. Le gain y sera alors gigantesque car tu ne seras pas obligé de repasser sur les calculs coûteux pour chaque pixel du remplissage.

Evidemment, ça me semble nettement plus difficile à coder :/

Néanmoins, un 10 pour une source innovante (jamais vu de remplissage de courbes de Bézier !) et fonctionnelle.

Cordialement, Bacterius !
Rejoignez-nous