gpt4 book ai didi

delphi - 如何对24位位图使用ScanLine属性?

转载 作者:行者123 更新时间:2023-12-03 14:33:01 25 4
gpt4 key购买 nike

如何使用 ScanLine 属性进行24位位图像素处理?为什么我更喜欢使用它而不是经常使用的 Pixels 属性?

最佳答案

1.简介

在本文中,我将尝试说明 ScanLine 属性仅用于24位位图像素格式以及您是否实际需要使用它。首先看一下使此属性如此重要的原因。

2. ScanLine与否...?

您可以问自己为什么要使用诸如 ScanLine 属性之类的棘手技术,就像您只能使用 Pixels 访问位图的像素一样。答案是,即使在相对较小的像素区域上执行像素修改时,也存在明显的性能差异。

Pixels 属性在内部使用Windows API函数 GetPixel SetPixel 来获取和设置设备上下文颜色值。 Pixels 技术的性能不足之处在于,通常需要在修改像素颜色值之前先获取它们,这在内部意味着同时调用了上述两个Windows API函数。 ScanLine 属性在这场竞赛中胜出,因为它提供了对存储位图像素数据的内存的直接访问。而且直接内存访问比两个Windows API函数调用要快。

但是,这并不意味着 Pixels 属性完全不好,您应该避免在所有情况下都使用它。例如,当您偶尔要修改几个像素(不是很大的区域)时,那么 Pixels 可能就足够了。但是,当您要使用像素区域进行操作时,请勿使用它。

3.像素深处

3.1原始数据

您可以将位图的像素数据想象为一个一维字节数组,其中包含每个像素的颜色分量的强度值序列。位图中的每个像素都由固定的字节数组成,具体取决于所使用的像素格式。

例如,24位像素格式的每个颜色分量都有1个字节-用于红色,绿色和蓝色通道。下图说明了如何想象这种24位位图的原始数据字节数组。此处每个彩色矩形代表一个字节:

3.2案例研究

想象一下,您有一个3x2像素(宽3px;高2px)的24位位图,请牢记在心,因为我将尝试解释一些内部原理,并在其上显示 ScanLine 属性用法的原理。它之所以如此之小,仅仅是因为它需要内部深处的空间(对于那些视野开阔的人来说,这种图像是png格式的绿色示例example↙:-)

3.3像素组成

首先,让我们看一下如何在内部存储位图图像的像素数据。看原始数据。下图显示了原始数据字节数组,您可以在其中看到微小位图的每个字节及其在该数组中的索引。您还会注意到,由3个字节组成的组如何形成各个像素,这些像素位于位图上的坐标是:

相同的另一个 View 提供以下图像。每个方框在那里代表我们虚构位图的一个像素。在每个像素中,您可以从原始数据字节数组中看到其坐标和3个字节的组及其索引:

4.充满色彩

4.1。初始值

众所周知,虚构的24位位图中的像素由3个字节组成-每个颜色通道1个字节。当您以自己的想象力创建了该位图时,所有像素中的所有这些字节都违背了您的意愿,被初始化为最大字节值-255。这意味着所有通道现在都具有最大的颜色强度:

当我们看一下从每个像素的初始通道值中混合出哪种颜色时,我们会看到位图为 entirely white 。因此,当您在Delphi中创建24位位图时,它最初是白色的。好吧,默认情况下,白色将是每种像素格式的位图,但它们的原始原始数据字节值可能有所不同。

5. ScanLine的 secret 生活

通过以上阅读,希望您能理解位图数据如何存储在原始数据字节数组中以及如何从这些数据中形成单个像素。现在转到 ScanLine 属性本身,以及如何在直接原始数据处理中有用。

5.1。 ScanLine目的

这篇文章的主要内容 ScanLine 属性是只读的索引属性,该属性返回指向原始数据字节数组的第一个字节的指针,该数组属于位图中的指定行。换句话说,我们请求访问给定行的原始数据字节数组,并且我们收到的是指向该数组第一个字节的指针。此属性的index参数指定我们要获取这些数据的行的从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;

由于我一直试图宽容2009年以下的Delphi版本,并且因为它使代码更容易理解,因此在下面的示例中,我将不使用指针算法进行迭代,而是使用固定长度的数组及其指针(指针在下面的Delphi 2009中可读性较低)。

因此,我们有一个像素的 TRGBTriple结构,现在我们为该行数组定义了一种类型。这将简化位图行像素的迭代。我刚刚从ShadowWnd.pas单元(无论如何是一个有趣的类的主页)借来的那个。这里是:
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;

如您所见,它的行数限制为4096个像素,对于通常的宽图像来说应该足够了。如果这不足以满足您的需求,请增加上限。

6.实践中的ScanLine

6.1。将第二行变黑

让我们从第一个示例开始。为此,我们使虚构的位图客观化,将其设置为适当的宽度,高度和像素格式(或者,如果需要,可以设置位深度)。然后,我们将 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;

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
  • 关于delphi - 如何对24位位图使用ScanLine属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13583451/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com