解决方法
在这篇文章中,我将尝试解释ScanLine
属性的使用只为24位位图像素格式,如果你真的需要使用它。首先看看什么使这个属性如此重要。
2. ScanLine是否…?
你可以问自己为什么使用这样棘手的技术,如使用ScanLine
属性貌似是当你可以简单地使用Pixels
访问你的位图的像素。答案是,即使在相对较小的像素区域上执行像素修改时,性能差异也很大。
Pixels
属性内部使用Windows API函数 – GetPixel
和SetPixel
,用于获取和设置设备上下文颜色值。 Pixels
技术的性能缺乏,你通常需要在修改像素颜色值之前,在内部意味着调用这两个提到的Windows API函数。 ScanLine
属性赢得了这场比赛,因为提供了对存储位图像素数据的内存的直接访问。和直接内存访问只是比两个Windows API函数调用。
但是,这并不意味着Pixels
属性是完全坏,你应该避免使用它在所有情况下。当你要修改只是几个像素(不是一个大的区域)偶尔,例如,然后Pixels
可能就足够了。但是,当你要使用像素区域操作时,不要使用它。
深入像素内
3.1原始数据
位图的像素数据(现在我们称之为原始数据),你可以想象为一个一维的字节数组,包含每个像素的颜色分量的强度值序列。位图中的每个像素由根据所使用的像素格式的固定计数的字节组成。
例如,24位像素格式对于其每个颜色分量(红色,绿色和蓝色通道)具有1个字节。下图说明了如何想象这样的24位位图的原始数据字节数组。这里的每个彩色矩形代表一个字节:
3.2案例研究
想象一下,你有一个24位的位图3×2像素(宽度3px;高度2px),并保持在你的头脑,因为我会尝试解释一些内部,并显示一个原则的ScanLine
属性使用它。它是如此之小,只是因为需要一个深层视图内部的空间(对于那些有明亮的视线是一个绿色的例子这样的图像在这里png格式这里↘↙:-)
3.3像素组成
首先让我们来看看我们的位图图像的像素数据是如何在内部存储的;看看原始数据。下图显示了原始数据字节数组,其中可以看到我们的微小位图的每个字节及其在该数组中的索引。您还可以注意到,3个字节的组如何形成各个像素,以及这些像素位于我们的位图上的坐标:
其另一视图提供以下图像。每个框表示我们的虚拟位图的一个像素。在每个像素中,您可以从原始数据字节数组中查看其坐标和3个字节及其索引的组:
生活与颜色
4.1。初始值
正如我们已经知道的,我们虚构的24位位图中的像素由3个字节组成 – 每个颜色通道1个字节。当你在想象中创建了这个位图时,所有像素中的所有字节都会被初始化为最大字节值 – 255。这意味着所有通道现在都具有最大颜色强度:
当我们看一看,每个像素的这些初始通道值混合了哪种颜色,我们将看到我们的位图是entirely white
.因此,当您在Delphi中创建一个24位位图时,它最初是白色的。好的,白色将在每个像素格式的位图默认情况下,但他们可能不同的初始原始数据字节值。
ScanLine的秘密生活
从上面的读取我希望你理解,位图数据如何存储在原始数据字节数组和如何从这些数据形成单个像素。现在转到ScanLine
属性本身,以及如何在直接原始数据处理中有用。
5.1。 ScanLine用途
这个帖子的主菜单,ScanLine
属性,是一个只读的索引属性,它返回指向属于位图中指定行的原始数据字节数组的第一个字节的指针。换句话说,我们请求访问给定行的原始数据字节数组,我们收到的是指向该数组的第一个字节的指针。此属性的索引参数指定要为其获取这些数据的行的基于0的索引。
下图说明了我们的虚拟位图和我们使用不同行索引的ScanLine
属性获得的指针:
5.2。 ScanLine优势
所以,从我们所知道的,我们可以总结,ScanLine
给我们一个指向某一行数据字节数组的指针。使用原始数据的行数组,我们可以工作 – 我们可以读取或覆盖其字节,但只能在特定行的数组边界范围内:
嗯,我们有一个行的每个像素的颜色强度的数组。考虑这种阵列的迭代;它将不会舒服的循环通过这个数组一个字节,并只调整像素的3个颜色部分之一。更好的是循环通过像素,并与每次迭代一次调整所有3个颜色字节 – 就像我们以前做的Pixels
。
5.3。跳过像素
为了简化行数组循环,我们需要一个与我们的像素数据匹配的结构。幸运的是,对于24位位图,有RGBTRIPLE
结构;在Delphi中翻译成TRGBTriple。这个结构,总之看起来像这样(每个成员表示一个颜色通道的强度):
type TRGBTriple = packed record rgbtBlue: Byte; rgbtGreen: Byte; rgbtRed: Byte; end;
因为我试图容忍那些有Delphi版本低于2009年,因为它使代码以某种方式更易于理解我不会使用指针算术迭代,但固定长度的数组与指针在下面的例子(指针算术在下面的Delphi 2009中将不太可读)。
因此,我们有一个像素的TRGBTriple结构,现在我们为行数组定义一个类型。这将简化位图行像素的迭代。这一个我只是从ShadowWnd.pas单位(一个有趣的类的家,反正)。这里是:
type PRGBTripleArray = ^TRGBTripleArray; TRGBTripleArray = array[0..4095] of TRGBTriple;
正如你所看到的,一行的限制为4096像素,对于通常宽的图像应该足够了。如果这不足以为你,只是增加上限。
6. ScanLine在实践中
6.1。使第二行为黑色
让我们从第一个例子开始。在这里我们对象化我们的虚拟位图,设置它适当的宽度,高度和像素格式(或如果你想,一个位深度)。然后我们使用ScanLine
与行参数1获取指向第二行的原始数据字节数组的指针。我们得到的指针,我们将分配给指向TRGBTriple数组的RowPixels变量,所以从那以后我们可以把它作为一个行像素数组。然后我们在位图的整个宽度中迭代该数组,并将每个像素的所有颜色值设置为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;
6.2。使用亮度的灰度位图
作为一个有意义的例子,我在这里发布使用亮度灰度化位图的过程。它使用从顶部到底部的所有位图行的迭代。对于每一行,然后获得指向原始数据的指针,并且像以前一样作为像素阵列。然后,该阵列的每个像素通过以下公式计算亮度值:
Luminance = 0.299 R + 0.587 G + 0.114 B
然后将该亮度值分配给迭代像素的每个颜色分量:
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;
以及上述程序的可能用法。请注意,您只能对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;
7.相关阅读
> Leonel Togniolli: How to Use Scanlines
> Earl F. Glynn: Manipulating Pixels With Delphi’s ScanLine Property