web-dev-qa-db-fra.com

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

Comment utiliser la propriété ScanLine pour la manipulation de pixels bitmap 24 bits? Pourquoi devrais-je préférer l'utiliser plutôt que la propriété Pixels assez fréquemment utilisée?

32
TLama

1. Introduction

Dans cet article, je vais essayer d'expliquer l'utilisation de la propriété ScanLine uniquement pour le format de pixel bitmap 24 bits et si vous devez réellement l'utiliser. Regardez 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 comme l'utilisation de la propriété ScanLine est apparemment lorsque vous pouvez simplement utiliser Pixels pour accéder à votre bitmap. pixels. La réponse est une grande différence de performances notable lorsque vous effectuez des modifications de pixels, même sur une zone de pixels relativement petite.

La propriété Pixels utilise en interne les fonctions de l'API Windows - GetPixel et SetPixel , pour obtenir et définir des valeurs de couleur de contexte de périphérique. Le manque de performances à la technique Pixels est que vous devez généralement obtenir des valeurs de couleur de pixel avant de les modifier, ce qui signifie en interne l'appel des deux fonctions API Windows mentionnées. La propriété ScanLine gagne cette course car elle fournit un accès direct à la mémoire où sont stockées les données de pixels bitmap. Et l'accès direct à la mémoire est juste plus rapide que deux appels de fonction API Windows.

Mais cela ne signifie pas que la propriété Pixels est totalement mauvaise et que vous devez éviter de l'utiliser dans tous les cas. Lorsque vous allez modifier quelques pixels (pas de grandes zones) de temps en temps, par exemple, alors Pixels pourrait vous suffire. Mais ne l'utilisez pas lorsque vous allez manipuler avec une zone de pixels.

3. Au fond des pixels

3.1 Données brutes

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

Par exemple, le format de pixels 24 bits a 1 octet pour chacune de ses composantes de couleur - pour le canal rouge, vert et bleu. L'image suivante illustre comment imaginer données brutes tableau d'octets pour un tel bitmap 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 ayez un bitmap 24 bits 3x2 pixels (largeur 3px; hauteur 2px) et gardez-le dans votre esprit car je vais essayer d'expliquer quelques internes et montrer un principe de ScanLine l'utilisation de la propriété sur elle. Il est si petit juste à cause de l'espace nécessaire pour une vue profonde à l'intérieur (pour ceux qui ont une vue lumineuse, voici un exemple vert d'une telle image au format png ici ↘ enter image description here ↙ :-)

3.3 Composition de pixels

Tout d'abord, examinons comment les données de pixels de notre image bitmap sont stockées en interne; regardez les données brutes. L'image suivante montre le tableau d'octets données brutes, où vous pouvez voir chaque octet de notre minuscule image bitmap avec son index dans ce tableau. Vous pouvez également remarquer comment les groupes de 3 octets forment les pixels individuels et sur quelles coordonnées ces pixels sont-ils 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 boîte représente un pixel de notre image bitmap imaginaire. Dans chaque pixel vous pouvez voir ses coordonnées et le groupe de 3 octets avec leurs index dans le tableau données brutes:

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 image bitmap 24 bits imaginaire 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 d'octet max - à 255. Cela signifie que tous les canaux ont maintenant les intensités de couleur maximales:

Initial channel values

Lorsque nous examinons la couleur mélangée à partir de ces valeurs de canal initiales 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. Eh bien, le blanc sera bitmap dans tous les formats de pixels par défaut, mais ils peuvent différer dans les valeurs initiales données brutes octets.

5. La vie secrète de ScanLine

D'après la lecture ci-dessus, j'espère que vous avez compris comment les données bitmap sont stockées dans un tableau données brutes et comment les pixels individuels sont formés à partir de ces données. Passez maintenant à la propriété ScanLine elle-même et comment elle peut être utile dans un traitement direct données brutes.

5.1. Objectif de ScanLine

Un plat principal de cet article, la propriété ScanLine , est une propriété indexée en lecture seule qui renvoie un pointeur sur le premier octet du tableau de 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 octets pour une ligne donnée et ce que nous recevons est un pointeur sur 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 image bitmap imaginaire et les pointeurs que nous obtenons par la propriété ScanLine en utilisant différents index de ligne:

ScanLine call with different parameters

5.2. Avantage ScanLine

Donc, d'après 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 lignes de données brutes nous pouvons travailler - nous pouvons lire ou écraser ses octets, mais uniquement 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. Considérant l'itération d'un tel tableau; il ne serait pas très confortable de parcourir ce tableau d'un octet et d'ajuster une seule des 3 parties de couleur d'un pixel. Mieux vaut parcourir les pixels et ajuster les 3 octets de couleur à la fois à chaque itération - tout comme avec Pixels comme nous le faisions auparavant.

5.3. Saut à 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 ; en Delphi traduit comme 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;

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

Donc, nous avons la structure TRGBTriple pour un pixel et maintenant nous définissons un type pour le tableau de lignes. Cela simplifiera l'itération des pixels de ligne bitmap. Celui-ci que je viens d'emprunter à l'unité ShadowWnd.pas (qui abrite en tout cas une classe intéressante). C'est ici:

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

Comme vous pouvez le voir, il a une limite de 4096 pixels pour une ligne, ce qui devrait être suffisant pour des images généralement larges. Si cela ne vous suffit pas, augmentez simplement la limite supérieure.

6. ScanLine en pratique

6.1. Rendre la deuxième rangée noire

Commençons par le premier exemple. En ce que nous objectivons notre image bitmap imaginaire, définissons la largeur, la hauteur et le format de pixel appropriés (ou, si vous le souhaitez, une profondeur de bits). Ensuite, nous utilisons ScanLine avec le paramètre de ligne 1 pour obtenir un pointeur sur le données brutes de la deuxième ligne tableau d'octets. Le pointeur que nous obtenons sera attribué à la variable RowPixels qui pointe vers le tableau de TRGBTriple, donc depuis ce temps, nous pouvons le prendre comme un tableau de pixels de ligne. Ensuite, nous itérons ce tableau dans toute la largeur du bitmap et définissons toutes les valeurs de couleur de chaque pixel sur 0, ce qui donne un 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 quand vous le voyez, 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

Comme une sorte d'exemple significatif, je publie ici une procédure de mise à l'échelle des images bitmap en utilisant la luminance. Il utilise l'itération de toutes les lignes bitmap de haut en bas. Pour chaque ligne, on obtient alors un pointeur vers a données brutes et comme précédemment pris comme tableau de pixels. Pour chaque pixel de ce tableau est ensuite 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 affectée à chaque composante 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 les bitmaps 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. Lecture connexe

67
TLama