TLa*_*ama 67
在这篇文章中,我将尝试ScanLine
仅针对24位位图像素格式解释属性用法,如果您确实需要使用它.首先来看看是什么让这个属性如此重要.
您可以问自己为什么要使用这种棘手的技术,比如使用ScanLine
属性,当您可以简单地使用Pixels
访问位图的像素时.答案是即使在相对较小的像素区域上执行像素修改时也会出现明显的性能差异.
该Pixels
属性在内部使用Windows API函数 - GetPixel
以及SetPixel
用于获取和设置设备上下文颜色值.Pixels
技术缺乏的技巧是你通常需要在修改它们之前获得像素颜色值,内部意味着调用两个提到的Windows API函数.该ScanLine
属性赢得了这场比赛,因为它提供了对存储位图像素数据的存储器的直接访问.直接内存访问比两个Windows API函数调用快.
但是,这并不意味着Pixels
财产是完全不好的,你应该避免在所有情况下使用它.如果您偶尔修改几个像素(不是很大的区域),那么Pixels
对您来说可能就足够了.但是当你要用像素区域进行操作时,不要使用它.
位图的像素数据(我们现在将它们称为原始数据)您可以想象为单维的字节数组,包含每个像素的颜色分量的强度值序列.位图中的每个像素都包含固定的字节数,具体取决于使用的像素格式.
例如,24位像素格式的每个颜色分量都有1个字节 - 红色,绿色和蓝色通道.下图说明了如何设想这种24位位图的原始数据字节数组.这里的每个彩色矩形代表一个字节:
想象一下,你有一个24位位图3x2像素(宽度3像素;高度2像素)并保持在你的脑海中,因为我会尝试解释一些内部结构,并ScanLine
在其上显示属性使用原则.它是如此之小,只是因为内部深处需要的空间(对于那些有明亮视线的人来说,这是png格式的图像的绿色例子↘ ↙:-)
首先让我们来看看我们的位图图像的像素数据是如何在内部存储的; 看看原始数据.下图显示了原始数据字节数组,您可以在其中查看我们的小位图的每个字节及其索引在该数组中.您还可以注意到,3个字节的组如何形成单个像素,以及位于我们位图上的这些像素的坐标:
另一个视图提供了以下图像.每个框表示我们想象的位图的一个像素.在每个像素中,您可以看到它的坐标和3个字节的组以及来自原始数据字节数组的索引:
我们已经知道,我们想象的24位位图中的像素由3个字节组成 - 每个颜色通道1个字节.当你在想象中创建了这个位图时,所有像素中的所有这些字节都与您的意志相对应,将其初始化为最大字节值 - 为255.这意味着所有通道现在都具有最大颜色强度:
当我们看一下,从每个像素的这些初始通道值混合哪种颜色时,我们会看到我们的位图是entirely white
.因此,当您在Delphi中创建一个24位位图时,它最初是白色的.嗯,默认情况下,白色将是每种像素格式的位图,但它们在初始原始数据字节值方面可能不同.
从上面的读数中我希望你理解,位图数据如何存储在原始数据字节数组中以及如何从这些数据中形成各个像素.现在转到ScanLine
属性本身,以及如何在直接原始数据处理中发挥作用.
该帖子的主要内容是ScanLine
属性,它是一个只读索引属性,它返回指向属于位图中指定行的原始数据字节数组的第一个字节的指针.换句话说,我们请求访问给定行的原始数据字节数组,我们收到的是指向该数组的第一个字节的指针.此属性的index参数指定我们要获取这些数据的行的基于0的索引.
下图说明了我们想象的位图以及我们ScanLine
使用不同行索引获取的指针:
因此,根据我们所知,我们可以总结ScanLine
出一个指向某个行数据字节数组的指针.使用原始数据的行数组,我们可以工作 - 我们可以读取或覆盖其字节,但只能在特定行的数组范围的范围内:
好吧,我们为某一行的每个像素都有一系列颜色强度.考虑这种数组的迭代; 将这个数组循环一个字节并调整一个像素的3个颜色部分中的一个就不太舒服了.更好的是循环像素并在每次迭代时一次调整所有3个颜色字节 - 就像Pixels
我们以前一样.
为了简化行数组循环,我们需要一个匹配像素数据的结构.幸运的是,对于24位位图,存在RGBTRIPLE
结构; 在Delphi中翻译过来TRGBTriple
.这个结构,简而言之就是这样(每个成员代表一个颜色通道的强度):
type
TRGBTriple = packed record
rgbtBlue: Byte;
rgbtGreen: Byte;
rgbtRed: Byte;
end;
Run Code Online (Sandbox Code Playgroud)
因为我试图容忍那些拥有2009年以下Delphi版本的人,因为它使得代码在某种程度上更容易理解,我不会使用指针算法进行迭代,而是在下面的示例中使用带有指针的固定长度数组(指针)在下面的Delphi 2009中,算术的可读性会降低.
因此,我们有TRGBTriple
一个像素的结构,现在我们为行数组定义一个类型.这将简化位图行像素的迭代.这个我刚刚从ShadowWnd.pas单位借来的(无论如何都是一个有趣的类的家).这里是:
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,它对一行的限制为4096像素,对于通常宽的图像应该足够了.如果这对您来说还不够,只需增加上限即可.
让我们从第一个例子开始.在那里我们将我们想象的位图客观化,设置适当的宽度,高度和像素格式(或者如果你想要的话,有点深度).然后我们使用ScanLine
行参数1来获取指向第二行的原始数据字节数组的指针.我们得到的指针将指向指向RowPixels
数组的变量TRGBTriple
,因此从那时起我们可以将其作为行像素数组.然后我们在位图的整个宽度上迭代这个数组,并将每个像素的所有颜色值设置为0,这导致第一行为白色的位图(默认情况下为白色,如上所述)以及第二行是黑色的是什么.然后将此位图保存到文件中,但是当您看到它时不要感到惊讶,它实际上非常小:
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;
Run Code Online (Sandbox Code Playgroud)
作为一个有意义的例子,我在这里发布了一个使用亮度来对位图进行灰度级处理的过程.它使用从上到下的所有位图行的迭代.然后为每一行获得指向原始数据的指针,并像之前一样作为像素数组.然后,对于该阵列的每个像素,通过以下公式计算亮度值:
Luminance = 0.299 R + 0.587 G + 0.114 B
Run Code Online (Sandbox Code Playgroud)
然后将该亮度值分配给迭代像素的每个颜色分量:
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;
Run Code Online (Sandbox Code Playgroud)
并可能使用上述程序.请注意,您只能将此过程用于24位位图:
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;
Run Code Online (Sandbox Code Playgroud)