27 votes

Comment utiliser la propriété ScanLine pour les bitmaps 24 bits ?

Mode d'emploi ScanLine pour la manipulation des pixels des bitmaps 24 bits ? Pourquoi devrais-je préférer l'utiliser plutôt que la propriété assez fréquemment utilisée Pixels la propriété ?

74voto

TLama Points 40454

1. Introduction

Dans ce billet, je vais essayer d'expliquer la ScanLine L'utilisation de cette propriété n'est possible que pour le format pixel bitmap 24 bits et si vous avez réellement besoin de l'utiliser. Voyons d'abord ce qui rend cette propriété si importante.

2. ScanLine ou pas... ?

Vous pouvez vous demander pourquoi utiliser une technique aussi délicate que celle qui consiste à utiliser ScanLine est apparemment quand vous pouvez simplement utiliser Pixels pour accéder aux pixels de votre bitmap. La réponse est une grande différence de performance perceptible lorsque vous effectuez des modifications de pixels, même sur une zone de pixels relativement petite.

El Pixels utilise en interne les fonctions de l'API Windows - GetPixel y SetPixel pour obtenir et définir les valeurs de couleur du contexte du dispositif. Le manque de performance à Pixels est que vous devez généralement obtenir les valeurs de couleur des pixels avant de les modifier, ce qui signifie en interne l'appel des deux fonctions API Windows mentionnées. Le site ScanLine remporte cette course parce qu'il offre un accès direct à la mémoire où sont stockées les données des pixels des bitmaps. Et l'accès direct à la mémoire est tout simplement plus rapide que deux appels de fonction de l'API Windows.

Mais, ça ne veut pas dire que Pixels est totalement mauvaise et que vous devez éviter de l'utiliser dans tous les cas. Lorsque vous allez modifier quelques pixels (pas une grande surface) occasionnellement, par exemple, alors Pixels pourrait être suffisant pour vous. Mais ne l'utilisez pas lorsque vous allez manipuler une zone de pixels.

3. Au cœur des pixels

3.1 Données brutes

Les données de pixel d'un bitmap (appelons-les données brutes pour l'instant) que vous pouvez imaginer comme un tableau unidimensionnel d'octets, contenant la séquence des valeurs d'intensité des composantes de couleur pour chaque pixel. Chaque pixel d'un bitmap est constitué d'un nombre fixe d'octets, en fonction du format de pixel utilisé.

Par exemple, le format pixel 24 bits possède un octet pour chacune de ses composantes de couleur - pour les canaux rouge, vert et bleu. L'image suivante illustre comment imaginer données brutes tableau d'octets pour un tel bitmap de 24 bits. Chaque rectangle coloré représente ici un octet :

Raw data example for 24-bit bitmap

3.2 Étude de cas

Imaginez que vous avez un bitmap 24 bits de 3x2 pixels (largeur 3px ; hauteur 2px) et gardez-le à l'esprit parce que je vais essayer d'expliquer certains éléments internes et de montrer un principe d'utilisation de l'application. ScanLine l'utilisation de la propriété sur celle-ci. Il est si petit juste à cause de l'espace nécessaire pour une vue profonde à l'intérieur (pour ceux qui ont une vue claire est un exemple vert d'une telle image au format png ici ↘. enter image description here ↙ :-)

3.3 Composition des pixels

Tout d'abord, regardons comment les données des pixels de notre image bitmap sont stockées en interne ; regardez la balise données brutes . L'image suivante montre le données brutes où vous pouvez voir chaque octet de notre petit bitmap avec son index dans ce tableau. Vous pouvez également remarquer comment les groupes de 3 octets forment les pixels individuels, et à quelles coordonnées ces pixels sont situés sur notre bitmap :

Raw data array for the case study bitmap

Une autre vue de la même donne l'image suivante. Chaque case représente un pixel de notre bitmap imaginaire. Dans chaque pixel vous pouvez voir ses coordonnées et le groupe de 3 octets avec leurs indices à partir de l'image de l'ordinateur. données brutes tableau d'octets :

Raw pixel illustration for the case study bitmap

4. Vivre avec les couleurs

4.1. Valeurs initiales

Comme nous le savons déjà, les pixels de notre bitmap imaginaire de 24 bits sont composés de 3 octets - 1 octet pour chaque canal de couleur. Lorsque vous avez créé ce bitmap dans votre imagination, tous ces octets dans tous les pixels ont été contre votre volonté initialisés à la valeur maximale de l'octet - à 255. Cela signifie que tous les canaux ont maintenant l'intensité maximale des couleurs :

Initial channel values

Lorsque nous regardons, quelle couleur est mélangée à partir de ces valeurs initiales de canal pour chaque pixel, nous verrons que notre bitmap est entirely white . Ainsi, lorsque vous créez un bitmap 24 bits dans Delphi, il est initialement blanc. Par défaut, le blanc sera le bitmap dans tous les formats de pixels, mais ils peuvent différer en termes d'initialisation. données brutes valeurs d'octets.

5. La vie secrète de ScanLine

En lisant ce qui précède, j'espère que vous avez compris comment les données bitmap sont stockées dans un fichier de type données brutes et comment les pixels individuels sont formés à partir de ces données. Passez maintenant à la ScanLine propriété elle-même et comment elle peut être utile dans un données brutes traitement.

5.1. Objectif de ScanLine

Un plat principal de ce poste, le ScanLine est une propriété indexée en lecture seule qui renvoie le pointeur vers le premier octet du tableau de l'adresse de l'utilisateur. données brutes octets qui appartiennent à une ligne spécifiée dans un bitmap. En d'autres termes, nous demandons l'accès au tableau de données brutes pour une ligne donnée et ce que nous recevons est un pointeur vers le premier octet de ce tableau. Le paramètre index de cette propriété spécifie l'index basé sur 0 d'une ligne pour laquelle nous voulons obtenir ces données.

L'image suivante illustre notre bitmap imaginaire et les pointeurs que nous obtenons par la fonction ScanLine en utilisant des index de ligne différents :

ScanLine call with different parameters

5.2. L'avantage de ScanLine

Donc, à partir de ce que nous savons, nous pouvons résumer que ScanLine nous donne un pointeur vers un certain tableau d'octets de données de ligne. Et avec ce tableau de rangées de données brutes nous pouvons travailler - nous pouvons lire ou écraser ses octets, mais seulement dans une plage des limites du tableau d'une ligne particulière :

ScanLine row array

Eh bien, nous avons un tableau d'intensités de couleur pour chaque pixel d'une certaine ligne. Si l'on considère l'itération d'un tel tableau, il ne serait pas très confortable de parcourir ce tableau par octet et de n'ajuster qu'une seule des trois portions de couleur d'un pixel. Le mieux serait de boucler à travers les pixels et d'ajuster les 3 octets de couleur en une seule fois à chaque itération - tout comme avec Pixels comme nous avions l'habitude de le faire.

5.3. Sauter à travers les pixels

Pour simplifier une boucle de tableau de lignes, nous avons besoin d'une structure correspondant à nos données de pixels. Heureusement, pour les bitmaps 24 bits, il existe la structure RGBTRIPLE dans Delphi, cela se traduit par TRGBTriple . Cette structure, en bref, ressemble à ceci (chaque membre représente l'intensité d'un canal de couleur) :

type
  TRGBTriple = packed record
    rgbtBlue: Byte;
    rgbtGreen: Byte;
    rgbtRed: Byte;
  end;

Comme j'ai essayé d'être tolérant envers ceux qui ont une version de Delphi inférieure à 2009 et parce que cela rend le code plus compréhensible, je n'utiliserai pas l'arithmétique des pointeurs pour l'itération, mais un tableau de longueur fixe avec un pointeur sur celui-ci dans les exemples suivants (l'arithmétique des pointeurs serait moins lisible dans Delphi 2009).

Donc, nous avons le TRGBTriple pour un pixel et maintenant nous définissons un type pour le tableau de lignes. Cela simplifiera l'itération des pixels de la rangée du bitmap. Celle-ci, je l'ai juste empruntée à l'unité ShadowWnd.pas (où se trouve une classe intéressante, en tout cas). Le voici :

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

Comme vous pouvez le constater, la limite est de 4096 pixels par ligne, ce qui devrait suffire pour des images généralement larges. Si cela n'est pas suffisant pour vous, augmentez simplement la limite supérieure.

6. ScanLine en pratique

6.1. Rendez la deuxième rangée noire

Commençons par le premier exemple. Nous y objectivons notre bitmap imaginaire, lui définissons une largeur, une hauteur et un format de pixel appropriés (ou, si vous le souhaitez, une profondeur de bit). Ensuite, nous utilisons ScanLine avec le paramètre de rangée 1 pour obtenir un pointeur sur la deuxième rangée données brutes tableau d'octets. Le pointeur que nous obtenons sera assigné à l'objet RowPixels qui pointe vers le tableau de TRGBTriple Donc, depuis ce moment, nous pouvons le considérer comme un tableau de pixels de ligne. Ensuite, nous itérons ce tableau sur toute la largeur de la bitmap et mettons toutes les valeurs de couleur de chaque pixel à 0, ce qui donne une bitmap avec la première ligne blanche (le blanc est par défaut, comme mentionné ci-dessus) et ce qui rend la deuxième ligne noire. Ce bitmap est ensuite enregistré dans un fichier, mais ne soyez pas surpris lorsque vous le verrez, il est vraiment très petit :

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Bitmap: TBitmap;
  Pixels: PRGBTripleArray;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Width := 3;
    Bitmap.Height := 2;
    Bitmap.PixelFormat := pf24bit;
    // get pointer to the second row's raw data
    Pixels := Bitmap.ScanLine[1];
    // iterate our row pixel data array in a whole width
    for I := 0 to Bitmap.Width - 1 do
    begin
      Pixels[I].rgbtBlue := 0;
      Pixels[I].rgbtGreen := 0;
      Pixels[I].rgbtRed := 0;
    end;
    Bitmap.SaveToFile('c:\Image.bmp');
  finally
    Bitmap.Free;
  end;
end;

6.2. Bitmap en niveaux de gris utilisant la luminance

En guise d'exemple significatif, je vais poster ici une procédure de mise à l'échelle des gris des bitmaps en utilisant la luminance. Elle utilise l'itération de toutes les lignes du bitmap de haut en bas. Pour chaque ligne, on obtient un pointeur vers un fichier données brutes et comme précédemment pris comme le tableau de pixels. Pour chaque pixel de cette matrice est alors calculée la valeur de luminance par cette formule :

Luminance = 0.299 R + 0.587 G + 0.114 B

Cette valeur de luminance est ensuite attribuée à chaque composante de couleur du pixel itéré :

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

procedure GrayscaleBitmap(ABitmap: TBitmap);
var
  X: Integer;
  Y: Integer;
  Gray: Byte;
  Pixels: PRGBTripleArray;
begin
  // iterate bitmap from top to bottom to get access to each row's raw data
  for Y := 0 to ABitmap.Height - 1 do
  begin
    // get pointer to the currently iterated row's raw data
    Pixels := ABitmap.ScanLine[Y];
    // iterate the row's pixels from left to right in the whole bitmap width
    for X := 0 to ABitmap.Width - 1 do
    begin
      // calculate luminance for the current pixel by the mentioned formula
      Gray := Round((0.299 * Pixels[X].rgbtRed) +
        (0.587 * Pixels[X].rgbtGreen) + (0.114 * Pixels[X].rgbtBlue));
      // and assign the luminance to each color component of the current pixel
      Pixels[X].rgbtRed := Gray;
      Pixels[X].rgbtGreen := Gray;
      Pixels[X].rgbtBlue := Gray;
    end;
  end;
end;

Et l'utilisation possible de la procédure ci-dessus. Notez que vous ne pouvez utiliser cette procédure que pour des bitmaps de 24 bits :

procedure TForm1.Button1Click(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.LoadFromFile('c:\ColorImage.bmp');
    if Bitmap.PixelFormat <> pf24bit then
      raise Exception.Create('Incorrect bit depth, bitmap must be 24-bit!');
    GrayscaleBitmap(Bitmap);
    Bitmap.SaveToFile('c:\GrayscaleImage.bmp');
  finally
    Bitmap.Free;
  end;
end;

7. Lire aussi

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X