Revenir au sommaire Tutoriaux



Canvas Couleur Pixel Palette
Programmation des couleurs
Bitmaps
Scanlines
Masques Conclusion Références


Scanlines

A partir de Delphi3, nous disposons d'une méthode efficace pour accéder aux pixels d'un bitmap. Il s'agit de la propriété Scanline.
Un Scanline n'est autre qu'un un pointeur vers le début d'une ligne de pixels d'un bitmap. Mais c'est à nous de définir la structure de la ligne pour y accéder comme à un tableau à une dimension, celle de la largeur du bitmap. La stucture à définir dépend de la résolution pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit et pf32bit.

Nous choisissons ici pf24bit à cause de sa compatibilité avec le format de fichier JPG et la résolution des moniteurs actuels. En effet, pf32bit contient un octet inutile, pf15bit et pf16bit posent des problèmes avec certains moniteurs, et les résolutions inférieures ne permettent pas un traitement correct des dégradés et surtout utilisent une palette vraiment gênante au niveau de la restitution des couleurs. Noter que le format pf32bit peut s'avérer plus performant à cause de son alignement au double mot, mais il est plus gourmand en mémoire.

Pour accéder aux pixels, le découpage de la ligne de bitmap en 3 octets est effectué en définissant la structure de tableau suivante:

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

RGBTriple est découpé en
BYTE rgbtBlue;
BYTE rgbtGreen;
BYTE rgbtRed;

Note :L'utilisation de tableau sans taille [0..0] implique que l'on compile sans vérification des limites ou avec {$R-}


Exemple : Eclaircir le bitmap Bmp0 de 10%

Procedure Tform1.Eclaircir;
var
x, y : integer;
  // colonnes, lignes
Row : Prgbarray;
 // pointeur scanline
R,G,B : integer;
 // les 3 couleurs
begin
  For y := 0 to bmp1.height-1 do
  // attention au -1
  begin
    row := Bmp1.scanline[y];
     // scanline
    for x := 0 to bmp1.width-1 do
// attention au -1
    begin
      R := (Row[x].rgbTred * 10) div 100;
      G := (Row[x].rgbTgreen * 10) div 100;
      B := (Row[x].rgbTblue * 10) div 100;
      if R > 255 then R := 255 else if R < 0 then R := 0;
      if G > 255 then G := 255 else if G < 0 then G := 0;
      if B > 255 then B := 255 else if B < 0 then B := 0;
      row[x].rgbtred := R;
      row[x].rgbtgreen := G;
      row[x].rgbtblue := B;
    end;
  end;
end;

Conseils

  • Attention aux -1 des limites bmp.height-1 et bmp.width-1
  • L'usage d'integer au lieu de byte pour les 3 couleurs évite les dépassements de capacité lors des calculs.Dans l'exemple, on augmente la valeur d'une couleur et on peut dépasser 255. Ainsi 240+20 = 260 se traduirait par 5 avec un byte , ce qui rendrait le pixel plus foncé au lieu de plus clair. C'est l'inverse quand on diminue la valeur d'une couleur 20 - 30 = -10 ce qui donnerait 245 avec un byte.De plus, quand les calculs effectués sur les couleurs risquent de ne pas tenir dans l'intervalle 0..255 il faut les vérifier .

Optimisations

Certains traitements d'image (adoucir ou durcir une image, effectuer une rotation de d degrés) nécessitent de combiner plusieurs pixels voisins se trouvant sur différentes lignes. Dans ce cas, la fonction scanline exécutée plus que nécessaire peut devenir pénalisante. On peut l'optimiser de 2 manières:

  • Créer un tableau des lignes, c'est à dire un tableau de PRGBArray
  • Effectuer une arithmétique sur les pointeurs

Exemple 1

Objectif : Adoucir le bitmap bmp0 avec résultat dans bmp1. Les 2 bitmaps sont pf24bit et de même taille.
Le bmp1 résultat utilise normalement les scanlines (row1) car les lignes sont utilisées les unes après les autres.
Pour bmp0 on crée un tableau de scanlines (scanrows0) car les lignes précédentes et suivantes sont réutilisées pour chaque ligne résultat. La technique de tableau de scanlines permet de traiter le bitmap comme un tableau à 2 dimensions.

Pour adoucir l'image, on prend les valeurs des 8 pixels entourant le pixel traité avec un coefficient de 1 si diagonale et de 2 si orthogonal. Le pixel du centre a un coefficient de 8 et puis on divise le total
1 2 1
2 8 2
1 2 1   
par la somme des coefficients soit 16 pour conserver la pondération des couleurs.

Cette matrice glissant sur tous les pixels de l'image se nomme un filtre. Noter les contrôles sur y et x pour éviter les effets de bord et maintenir les valeurs dans les intervalles 0..Bmp0.height - 1  et  0..Bmp0.width - 1.

Procedure Tform1.adoucir;
const
  a : array[0..2, 0..2] of integer = ((1,2,1),(2,8,2),(1,2,1));
var
  x1, y1 : integer;    
// colonnes, lignes bitmap1
  x0, y0 : integer;   
// colonnes, lignes bitmap0
  scanrows0 : array[0..4000] of pRGBArray;
  row1 : Prgbarray;
  j, i : integer;
  R, G, B : integer;
  somme : integer;
begin
  if bmp1.height > 4000 then exit;
// bitmap trop grand
 
// initialisation du tableau de scanlines pour bmp0
  for y0 := 0 to Bmp1.height-1 do scanrows0[y0] := bmp0.scanline[y0];
 
// traitement
  For y1 := 0 to bmp1.height-1 do
  begin
    row1 := bmp1.scanline[y1];
    for x1 := 0 to bmp1.width-1 do
    begin
      R := 0; G := 0; B := 0;
// pour chaque pixel
      For j := -1 to 1 do
// les 3 lignes autour du pixel
      For i := -1 to 1 do
// les 3 colonnes
      begin
        y0 := y1+j;
        if y0 < 0 then y0 := 0;
        if y0 > bmp0.heigh-1 then y0 := bmp0.height-1;
        x0 := x1+i;
        if x0 < 0 then x0 := 0;
        if x0 > bmp0.width-1 then y0 := bmp0.width-1;
        R := R + scanrows0[y0,x0].rgbtred;
        G := G + scanrows0[y0,x0].rgbtgreen;
        B := B + scanrows0[y0,x0].rgbtblue;
      end;
      row1[x].rgbtred := R div 16;
// pas de test couleurs 0..255

      row1[x].rgbtgreen := G div 16;
      row1[x].rgbtblue := B div 16;
    end;
  end;
end;

Exemple 2

La seconde technique d'optimisation des scanlines consiste à effectuer une arithmétique sur les pointeurs. Il suffit pour cela de calculer la différence entre le scanline de la ligne 0 et de la ligne 1

Procedure Tform1.adoucir;
const
  a : array[0..2, 0..2] of integer = ((1,2,1),(2,8,2),(1,2,1));
var
  x1, y1 : integer;
// colonnes, lignes bitmap1
 
x0, y0 : integer;
// colonne, lignes bitmap0
 
row0 : pRGBArray;
  baserow0 : pRGBarray;
  DeltaScan: INTEGER;
  row1 : Prgbarray;

  j, i : integer;
  R, G, B : integer;
  somme : integer;
begin

  // initialisation du calcul de scanlines pour bmp0

 
baserow0 := Bmp0.Scanline[0];
  DeltaScan := Integer(Bmp0.Scanline[1]) - Integer(baserow0);

  // traitement

 
For y1 := 0 to bmp1.height-1 do
  begin
    row1 := bmp1.scanline[y1];
    for x1 := 0 to bmp1.width-1 do
    begin
      R := 0; G := 0; B := 0;
// pour chaque pixel
     
For j := -1 to 1 do
// les 3 lignes autour du pixel
     
For i := -1 to 1 do
// les 3 colonnes
     
begin
        y0 := y1+j;
// un des 9 pixels
       
if y0 < 0 then y0 := 0;
// tests limites lignes
       
if y0 > bmp0.heigh-1 then y0 := bmp0.height-1;
        x0 := x1+i;
// un des 9 pixels
       
if x0 < 0 then x0 := 0;
// tests limites colonnes
       
if x0 > bmp0.width-1 then y0 := bmp0.width-1;
        integer(row0) := integer(baserow0) + Deltascan*y0;
        R := R + row0[x0].rgbtred;
        G := G + row0[x0].rgbtgreen;
        B := B + row0[x0].rgbtblue;
      end;

     // pas besoin des tests couleurs comprises dans intervalle 0..255

      row1[x].rgbtred := R div 16;
      row1[x].rgbtgreen := G div 16;
      row1[x].rgbtblue := B div 16;
    end;
  end;
end;

 

Retour  Suite

Revenir au sommaire Tutoriaux