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é ?
Réponse
Trop de publicités?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 :
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 ↘. ↙ :-)
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 :
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 :
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 :
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 :
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 :
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;