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?
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.
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.
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:
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 ↘ ↙ :-)
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:
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:
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:
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.
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.
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:
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:
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.
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.
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;
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;