如何使用 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 属性? 的相关文章

  • 在 python lib 中导入和裁剪 jpeg 的快速方法

    我有一个 python 应用程序 可以导入 200k 图像 裁剪它们 并将裁剪后的图像呈现给 pyzbar 来解释条形码 裁剪很有帮助 因为图像上有多个条形码 并且当给定较小的图像时 pyzbar 可能会更快一些 目前我正在使用 Pillo
  • 在运行时按需更改组件类

    我的问题与这里的想法类似 替换delphi中的组件类 https stackoverflow com q 4685863 937125 但我需要改变一个specific按需组件类 这是一些伪演示代码 unit Unit1 TForm1 cl
  • 如何在 Delphi REST 中发布内容类型为“multipart/form-data”的数据?

    我正在尝试使用 REST API 发送请求multipart form data作为内容类型 我总是收到 HTTP 1 1 500 Internal Error 作为响应 我尝试向需要的方法发送请求application x www for
  • 对同色像素块的边界进行着色

    我有一张有 5 种不同颜色的图像 在这种情况下 随机生成 w h 40 27 img Image new RGB w h pixels img load available colors r 255 13 18 b 72 64 255 y
  • Delphi - 自XE8以来如何正确注册图形类?

    我正在编写一个 Delphi 包 它提供了一个新的自定义 TGraphic 对象 允许读取 VCL 组件 如 TImage 中的新图像格式 我最初使用 RAD Studio XE7 开发了这个包 并且运行良好 然而 我最近迁移到了较新的 R
  • 开源 Delphi 包可使用哪些项目选项?

    我写了一些 Delphi 代码 想在 GitHub 上分享 所有代码都根据需要包含在运行时和设计时包中 每个项目有许多项目选项需要设置 输出目录 搜索路径 编译选项等 我设法找到了一些适合我的情况的默认选项 但阅读此处的其他问答很明显有多个
  • 当显示对话框时淡出应用程序的所有其他窗口?

    如何在 Delphi 2009 中调暗 淡出应用程序的所有其他窗口 Form 有一个 AlphaBlend 属性 但它仅控制透明度级别 但如果我们能有这样的东西那就太好了 集中窗口 http www anappaday com downlo
  • 使用 OLE 和 Delphi 提高 Word 文档中搜索替换的性能

    经过一些实验 我最终得到了以下代码来在 MSWord 中执行搜索和替换 此代码在页眉和页脚中也能完美运行 包括首页或奇数 偶数页的页眉和 或页脚不同的情况 问题是我需要打电话MSWordSearchAndReplaceInAllDocume
  • Delphi - 在修复 VCL 错误时,单元 x 是用不同版本的 x 编译的

    我正在使用 Delphi XE6 并在我的项目中使用 Datasnap 和 JSON 我想纠正 VCL 单元 System JSON pas 在 TJSONString ToString 函数中 中的一个错误 它应该转义反斜杠字符和引号 为
  • PyTorch 中的数据增强

    我对 PyTorch 中执行的数据增强有点困惑 现在 据我所知 当我们执行数据增强时 我们保留原始数据集 然后添加它的其他版本 翻转 裁剪 等 但 PyTorch 中似乎并没有发生这种情况 据我从参考文献中了解到 当我们使用data tra
  • 如何确定透视变换后的点在新图像平面中的位置?

    我使用 OpenCV Python Numpy 图像中有三个点 我知道这些点的确切位置 P1 P2 N1 我要将图像转换为另一个视图 例如 我将透视图转换为侧视图 如果这样做 我将无法获得图像平面中这三个点的确切位置 我应该以一种可以获得这
  • Word 2010 自动化:“转到书签”

    我有一个用 Delphi 7 编写的程序 它打开一个基于模板的新 Word 文档 文档打开后 系统会自动跳转到书签 在模板中预定义 并在其中添加一些文本 以下代码在 Word 2003 中工作正常 但会导致invalid variant o
  • 如何识别与我的对象相关的轮廓并找到它们的几何质心

    问题陈述和背景信息 EDIT 约束 法兰上的红色会随着时间的推移而变化 所以我此时不会尝试使用颜色识别来识别我的对象 除非它足够强大 此外 外部照明也可能是一个因素 因为将来这将是在室外区域 我有 RGB 深度相机 有了它 我就能捕捉到这个
  • 图像处理:什么是遮挡?

    我正在开发一个图像处理项目 我遇到了这个词闭塞在许多科学论文中 遮挡在图像处理中意味着什么 字典只是给出了一般的定义 谁能使用图像作为上下文来描述它们 遮挡意味着您想看到某些内容 但由于传感器设置的某些属性或某些事件而无法看到 它到底如何表
  • 当responseText包含有效的Xml时,IXMLHttpRequest.responseXml为空,没有解析错误

    我正在从中获取一些 XML政府网站 http www bankofcanada ca stats assets rates rss noon en all xml http www bankofcanada ca stats assets
  • 解决相关代码的低 FPS 问题以计算图像中的偏移

    我正在尝试使用相关性来跟踪对象 我在较大的图像中逐帧找到较小的补丁 为此 我发现补丁中的变化 并且相关性最大的地方 用新补丁更新补丁 我的代码是 cv Mat im float 2 imagePart out cv Mat im float
  • MATLAB 中的霍夫变换

    有谁知道如何使用霍夫变换来检测二值图像中最强的线 A zeros 7 7 A 6 10 18 24 36 38 41 1 使用 rho theta 格式 其中 theta 以 45 为步长 从 45 到 90 以及如何在 MATLAB 中显
  • 图像处理 - 使用 opencv 进行服装分割

    我正在使用 opencv 进行服装特征识别 第一步 我需要通过从图像中移除脸部和手来分割 T 恤 任何建议表示赞赏 我建议采用以下方法 Use 阿德里安 罗斯布鲁克的用于检测皮肤的皮肤检测算法 谢谢罗莎 格隆奇以获得他的评论 在方差图上使用
  • 如何更改 TPageControl 上标签的方向?

    我是 Delphi 的新手 再次强调 我在 1994 年就使用过 Delphi 我现在有 Delphi 2009 Pro 来自Java 我发现对象继承非常晦涩 我的用户想要选项卡位于左侧的选项卡式页面 但是 TPageControl 不允许
  • 如何调试仅在应用程序关闭时发生的崩溃? (德尔福)

    因此 经过最近的一些更改 我们发现我们最古老的应用程序之一有时会在关闭时崩溃 这会以 运行时错误 216 消息的形式或来自 Windows 错误报告的消息的形式表明应用程序已停止工作 该应用程序已经发出OutputDebugString 每

随机推荐

  • 整数问题 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 位位图像素格式以及您是否确实需要使用它 首先来看看是什么让