如何使用 24 位位图的 ScanLine 属性?

2023-12-09

如何使用ScanLine24 位位图像素操作的属性?为什么我应该更喜欢使用它而不是经常使用Pixels财产?


一、简介

在这篇文章中我将尝试解释ScanLine属性用法仅适用于 24 位位图像素格式以及您是否确实需要使用它。首先来看看是什么让这个属性如此重要。

2. 是否使用 ScanLine...?

你可以问自己为什么要使用这样棘手的技术,比如使用ScanLine财产似乎是当你可以简单地使用Pixels访问您的位图像素。答案是,即使在相对较小的像素区域上执行像素修改,也会出现明显的性能差异。

The Pixels属性内部使用 Windows API 函数 -GetPixel and SetPixel,用于获取和设置设备上下文颜色值。性能不足Pixels技术是你通常需要在修改像素颜色值之前获取它们,这在内部意味着调用两个提到的 Windows API 函数。这ScanLine属性赢得了这场比赛,因为它提供了对存储位图像素数据的内存的直接访问。直接内存访问仅比两次 Windows API 函数调用快。

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

3. 像素深处

3.1 原始数据

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

例如,24 位像素格式的每个颜色分量(红色、绿色和蓝色通道)都有 1 个字节。下图说明了如何想象raw data此类 24 位位图的字节数组。这里每个彩色矩形代表一个字节:

Raw data example for 24-bit bitmap

3.2 案例研究

Imagine you have a 24-bit bitmap 3x2 pixels (width 3px; height 2px) and keep it in your mind because I'll try to explain some internals and show a principle of ScanLine property usage on it. It is so small just because of space needed for a deep view inside (for those having a bright sight is a green example of such image in png format here ↘ enter image description here ↙ :-)

3.3 像素构成

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

Raw data array for the case study bitmap

另一个视图提供了下图。每个框代表我们想象的位图的一个像素。每一个pixel你可以看到它的坐标和 3 个字节组及其索引raw data字节数组:

Raw pixel illustration for the case study bitmap

4. 与色彩共存

4.1.初始值

我们已经知道,假想的 24 位位图中的像素由 3 个字节组成 - 每个颜色通道 1 个字节。当您在想象中创建此位图时,所有像素中的所有字节都已违背您的意愿初始化为最大字节值 - 255。这意味着所有通道现在都具有最大颜色强度:

Initial channel values

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

5. ScanLine的秘密生活

通过上面的阅读,我希望您了解位图数据是如何存储在raw data字节数组以及如何从这些数据形成各个像素。现在继续ScanLine财产本身以及如何直接发挥作用raw data加工。

5.1. ScanLine 目的

这篇文章的主菜是ScanLine属性,是一个只读索引属性,返回指向数组第一个字节的指针raw data属于位图中指定行的字节。换句话说,我们请求访问数组raw data给定行的字节数,我们收到的是指向该数组第一个字节的指针。该属性的索引参数指定我们想要获取这些数据的行的从 0 开始的索引。

下图说明了我们想象的位图和我们通过ScanLine使用不同行索引的属性:

ScanLine call with different parameters

5.2. ScanLine优势

因此,根据我们所知,我们可以总结一下ScanLine给我们一个指向某个行数据字节数组的指针。并用该行数组raw data我们可以工作 - 我们可以读取或覆盖它的字节,但只能在特定行的数组边界范围内:

ScanLine row array

好吧,我们有一个特定行的每个像素的颜色强度数组。考虑这样的数组的迭代;按一个字节循环遍历该数组并仅调整像素的 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 获取指向第二行的指针raw data字节数组。我们得到的指针将分配给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.使用亮度的灰度位图

作为一个有意义的示例,我在这里发布了使用亮度对位图进行灰度化的过程。它使用从上到下的所有位图行的迭代。然后为每一行获得指向raw data和以前一样作为像素数组。然后通过以下公式计算该阵列的每个像素的亮度值:

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:如何使用扫描线
  • Earl F. Glynn:使用 Delphi 的 ScanLine 属性操作像素
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 24 位位图的 ScanLine 属性? 的相关文章

  • MATLAB:图像角坐标和引用元胞数组

    我在比较不同元胞数组中的元素时遇到一些问题 这个问题的背景是我正在使用bwboundariesMATLAB 中的函数可追踪图像的轮廓 该图像是结构横截面 我试图找出整个部分是否具有连续性 即 只有一个轮廓由bwboundaries命令 完成
  • 霍夫变换检测和删除线

    我想使用霍夫变换检测图像中的线条 但是我不想绘制线条 而是想删除原始图像中检测到的每条线条 image imread image jpg image im2bw image BW edge image canny imshow BW fig
  • Blob 的簇生长

    考虑以下来自 Mathworks 的图像 我已经用标签标记了斑点 L num bwlabel I 如何迭代连接所有斑点 即从一个斑点开始 找到离它最近的一个 考虑最左边的两个斑点 可以从一个斑点的许多点绘制许多条线来连接到另一个斑点blob
  • Matlab下降低图像质量

    问候 我正在尝试找到一种简单的方法来处理图像 以便将其质量从 8 位降低到 3 位 实现这一目标的最简单方法是什么 干杯 如果要线性缩放 只需将每个像素值除以 255 7 即 如果原始图像存储在矩阵 I 中 则让低分辨率图像 J I 255
  • 为什么这个基本的 imagejpeg() resizer 返回黑色图像?

    EDIT 感谢您的所有回答 特别是 Mailerdaimon 他注意到我没有在imagecopyresampled功能 我不再得到黑色图像 但我仍然得到一些黑色部分 所以我认为我的比例公式应该更新 如果我上传横向图像 新图像的高度小于 17
  • 从 Delphi 访问 TRAKT API - 承载身份验证问题

    使用 TOauth2Authenticator TRESTClient TRESTRequest TRESTResponseDataSet TRESTResponse TFDmemtable 和 TDataSource 我成功连接到 Tra
  • 我有进程 ID,需要使用 Delphi 5 以编程方式关闭关联进程

    任何人都可以帮我提供一个编码示例 以便在我拥有进程 ID 时关闭关联的进程 我将使用 Delphi 5 在 Windows 2003 服务器上以编程方式执行此操作 如果您有进程 ID 并希望强制终止该进程 可以使用以下代码 function
  • 从不同的形式调用过程

    我正在使用 Lazarus 我有一个名为TForm1单元名称为 Unit 1 在这里我有一个名为mergeDATfile a shortint 这会产生一些东西 顺便说一句 我必须创建另一个名为TForm2里面有按钮 Button1 当它被
  • Vista 中的文本转语音

    我通过在 2000 NT XP 中使用 Delphi 创建 OLE 对象来做到这一点 如下所示 Voice CreateOLEObject SAPI SpVoice Voice speak 但这在 Vista 中不起作用 我怎样才能让我的程
  • 如何使用 IdTCPClient 等待来自服务器的字符串?

    我的 IdTelnet indy 10 1 有问题 我无法以 Unicode 模式从服务器读取数据 现在我想用 IdTCPClient 编写 telnet 终端 服务器有时发送一行 有时发送越来越多的行 但发送之间没有固定的时间 现在我的问
  • Delphi XE5 FireDAC 错误:无法加载供应商库 [libmysql.dll 或 libmysqld.dll]

    我在 Windows 7 64 位上使用 Delphi XE5 只是尝试 FireDAC 组件 我正在使用一个 TFDConnection 组件连接到本地 MySQL 数据库 v5 6 15 我已经将 libmysql dll 32位 v5
  • 如何使用最小生成树方法将边缘连接到图像中的节点

    我正在做我的手写图像图形匹配项目 我想在图形中表示给定的单词图像 我使用下面的算法 Algorithm input Binary image B Grid width w Grid height h Output Graph g V E w
  • 在 tlistbox 中绘制缩略图

    在 DelphiXE 中 我使用 tFileOpenDialog 选择一个文件夹 然后在 tListBox 中列出该文件夹中的所有 jpg 文件 我允许将列表项拖放到列表中进行自定义排序 以便稍后按顺序显示它们 我希望能够在文件名旁边绘制图
  • 使用 IOmniTaskControl/TOmniWorker 时等待 Invoke 完成

    我使用 TOmniWorker 创建了 IOmniTaskControl 以便我可以定期在特定线程上运行代码块 因此 我将根据需要在此 IOmniTaskControl 上调用 Invoke 当我这样做时 有时需要等待与该工作相关的执行完成
  • 使用 IdTCPClient 和 IdTCPServer 发送和接收 TMemoryStream

    我在 XE2 中找到了 Remy Lebeau 的 IdTCP 组件聊天演示 我想玩一下 可以发现 我想使用这些组件发送图片 最好的方法似乎是使用 TMemoryStream 如果我发送字符串 连接工作正常 字符串传输成功 但是当我将其更改
  • delphi专家中的第三方依赖

    我正在编写一个delphi ide Expert 带有一些第三方依赖项 视觉控件 我的问题是当这个专家将安装在目标机器上时 这台电脑是否也需要安装这些第三方组件 或者组件是embeded在生成的 bpl 内部 它们将依赖于您放置在包的 re
  • 如何在Delphi中实现人工神经网络? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想要一个人工神经网络 42 个输入神经元 168 个隐藏神经元 7个输出神经元 这个网络就是玩 连四子 的游戏 每场比赛结束时 网络
  • 比较两个直方图

    对于一个小型项目 我需要将一张图像与另一张图像进行比较 以确定图像是否大致相同 这些图像很小 宽度从 25 到 100 像素不等 这些图像应该具有相同的图片数据 但略有不同 因此简单的像素相等检查不起作用 考虑以下两种可能的情况 博物馆中的
  • 如何将十六进制数组转换为 UIImage?

    有几个与使用 P25mi 动态打印图像相关的未解答问题 没有一个得到公认的答案 下面有几个链接 如何将图像转换为位图代码以便在 iPhone 中进行蓝牙打印 https stackoverflow com questions 1383828
  • 如何指定从 Delphi TStream 读取的组件的所有者?

    我正在从流中读取组件 并且希望能够指定 Owner 属性 var TComponent comp stream Seek 0 soFromBeginning comp stream ReadComponent nil 谁拥有 comp 我该

随机推荐

  • 整数问题 Flex

    我对这段代码有疑问
  • 4xN 多米诺骨牌的组合数量

    我想找到 4 x N 区域 4 个单位宽度和 N 个单位高度 N 1 多米诺骨牌砖的可能不同组合的数量使用动态规划 多米诺骨牌的尺寸为 2x1 例如 对于水平和 对于垂直砖 Now 示例 4x1 两块多米诺骨牌叠在一起 4x2 砖块配置示例
  • 强制横向应用程序遇到困难

    我有两个应用程序 它们都强制用户在横向模式下使用 iPhone 以获得更宽的屏幕 而不是更高的屏幕 我发现的一件事是 我的第一个视图看起来不错 但所有其他视图都会将其子视图 UIButtons UIPicker UIViews 挤压到一侧或
  • 如何覆盖宽度属性?

    我怎样才能覆盖CSSwidth以便竞争widthHTML 属性定义了计算的大小 下面的示例以 500px 的宽度渲染 但我想要 100px 假设 css 规则无法更改 只是被更强的规则掩盖 编辑以澄清 我不想在内联或其他地方设置显式大小 i
  • 如何在 css 中使用@keyframes 进行交叉淡入淡出图像库?

    我有一个fiddle 小提琴A 其中 2 个图像 2 个图块 发生图像交叉淡入淡出图库 这是我使用过的 html css 片段 div class featured block style display flex a href https
  • 普通类型和匿名类型有什么区别?

    C 中的普通类型和匿名类型在编译过程和运行时内存管理方面有什么区别 匿名类型在某些方面是否比普通类型更加低效 From MSDN 匿名类型提供一种方便的方法来封装一组 将只读属性放入单个对象中 而无需首先 明确定义一个类型 类型名称由编译器
  • 使用 jQuery Validate 插件确保至少选中三个复选框之一

    使用 jQuery Validate 插件 我如何确保至少三个复选框之一已被选中 到目前为止 我能做的最好的事情就是使所有三个复选框成为必需的 这不是我想要的 我的代码如下 我尝试使用这个例子在 jQuery Validate 演示页面上
  • httr POST 请求正文中的数组

    这个curl调用可以在Digital Ocean上创建一个新的droplet curl X POST https api digitalocean com v2 droplets d name test3 region nyc2 size
  • mousePressed 中的 glReadPixels

    我试图在用户单击 JOGL 时获取像素的颜色 如果我将以下代码放入显示方法中 来自GLEventListener 效果很好 FloatBuffer buffer FloatBuffer allocate 4 gl glReadBuffer
  • 将 varchar 值“%”转换为数据类型 int 时转换失败

    我创建了一个过程并收到了这条消息 将 varchar 值 转换为数据类型 int 时转换失败 create procedure consultarEquipo id varchar 10 marca varchar 20 year int
  • Spring:获取 ManyToOne 实体时,参考实体 (OneToMany) 未显示在 JSON 中

    当我在 POSTMAN 中发送 GET 请求以获取所有子实体 城镇 时 父实体 省 不会显示在 JSON 响应中 这是我的控制器 RequestMapping value api v1 town method RequestMethod G
  • 从驱动程序创建进程

    有没有办法在Windows NT平台 XP W7 上从内核模式创建用户模式进程 编辑 我必须只安装驱动程序 这是该项目的具体情况 要创建有效的 win32 进程 驱动程序必须与 CSRSS 通信 完全未记录 因此 我最终对用户模式 APC
  • 优化 Postgres 对时间戳范围的查询

    我定义了下表和索引 CREATE TABLE ticket wid bigint NOT NULL DEFAULT nextval tickets id seq regclass eid bigint created timestamp w
  • 在 Clojure 中喜结良缘:没有(显式的、丑陋的)突变的循环引用?

    在我的回答中Clojure 理解示例我有一个处理自己的输出的函数 defn stream seed defn helper slow concat map str first slow seed lazy seq helper rest s
  • Google 地球 KML 中的文本叠加

    我想添加 KML 文件上次更新的日期 时间 以便在 Google 地球中显示为覆盖图 无论如何可以做到这一点吗 我会考虑更新 KML 文件中的文本 然后将其显示在谷歌地球中 非常感谢 一个技巧是使用谷歌图表API从文本动态创建图像并将其用作
  • C# MySQL 错误“列计数与第 1 行的值计数不匹配”

    Query SQL MySqlCommand command1 new MySqlCommand INSERT INTO Equipamento equipamento situacao modelo nr serie avaria est
  • 如何使用 EF6 Code First 将外键属性公开给具有导航属性的现有实体

    我有一个已经与底层数据库一起使用的实体 并且它是使用可选实体 1 0 1 的导航属性创建的 因此 按照默认约定 EF 在数据库中创建了一个可为空的外键列 并根据该约定为其指定了带下划线的 MyProp Id 名称 现在 我希望将该外键公开为
  • 在 bash 脚本中使用 grep 在日志文件上使用 tail -f

    我想创建一个脚本来查找正在写入的日志文件中的特定字符串 我想获取第一个结果并将其放入变量中以供以后使用 这将通过 SSH 连接使用 如下所示 ssh email protected bash s lt usr local bin check
  • 默认情况下使用 uuid 时 Cassandra TimeUUID 泛洪文件描述符

    我有 Cassandra 模型 import uuid from cassandra cqlengine import columns from cassandra cqlengine models import Model class M
  • 如何使用 24 位位图的 ScanLine 属性?

    如何使用ScanLine24 位位图像素操作的属性 为什么我应该更喜欢使用它而不是经常使用Pixels财产 一 简介 在这篇文章中我将尝试解释ScanLine属性用法仅适用于 24 位位图像素格式以及您是否确实需要使用它 首先来看看是什么让