Bmp图片的结构剖析与代码处理实践[Ruby]

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 预览:Paste_Image.png一、BMP文件格式详解(BMP file format)BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式下面以Notepad++为分析工具,结合Windows的位图数据结构对BMP文件格式进行一个深度的剖析。

预览:

img_9cea6a17aee245ea2a790593dd371061.png
Paste_Image.png

一、BMP文件格式详解(BMP file format)

BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式
下面以Notepad++为分析工具,结合Windows的位图数据结构对BMP文件格式进行一个深度的剖析。
BMP文件的数据按照从文件头开始的先后顺序分为四个部分:

bmp文件头(bmp file header):提供文件的格式、大小等信息
位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
调色板(color palette): 可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
Ø位图数据(bitmap data):就是图像数据啦_
下面结合Windows结构体的定义,通过一个表来分析这四个部分。

img_126d7ffd97b7a1dba7a8fa40e306e874.png
BMP文件数据结构

我们一般见到的图像以24位图像为主,即R、G、B三种颜色各用8个bit来表示,这样的图像我们称为真彩色,这种情况下是不需要调色板的,也就是所位图信息头后面紧跟的就是位图数据了。因此,我们常常见到有这样一种说法:位图文件从文件头开始偏移54个字节就是位图数据了,这其实说的是24或32位图的情况。这也就解释了我们按照这种程序写出来的程序为什么对某些位图文件没用了。

BMP文件头数据结构

img_0d4f63094aaf0965df5419622bf65c3a.png
Paste_Image.png

位图信息头数据结构

img_e1a98bb1359deb28013d9ae0baaa5dd9.png
位图信息头数据结构

位图数据

每个像素占一个字节,取得这个字节后,以该字节为索引查询相应的颜色,并显示到相应的显示设备上就可以了。

注意:由于位图信息头中的图像高度是正数,所以位图数据在文件中的排列顺序是从左下角到右上角,以行为主序排列的。

img_40166cabfbdf21ad413d7af4235ef0f8.png

也即我们见到的第一个像素60是图像最左下角的数据,第二个人像素60为图像最后一行第二列的数据,…一直到最后一行的最后一列数据,后面紧接的是倒数第二行的第一列的数据,依此类推。

  • 如果图像是24位或是32位数据的位图的话,位图数据区就不是索引而是实际的像素值了。下面说明一下,此时位图数据区的每个像素的RGB颜色阵列排布:
  • 24位RGB按照BGR的顺序来存储每个像素的各颜色通道的值,一个像素的所有颜色分量值都存完后才存下一个下一个像素,不进行交织存储。
  • 32位数据按照BGRA的顺序存储,其余与24位位图的方式一样。
    像素的排布规则与前述一致。

对齐规则

讲完了像素的排列规则以及各像素的颜色分量的排列规则,最后我们谈谈数据的对齐规则。我们知道Windows默认的扫描的最小单位是4字节,如果数据对齐满足这个值的话对于数据的获取速度等都是有很大的增益的。因此,BMP图像顺应了这个要求,要求每行的数据的长度必须是4的倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。这时,位图数据区的大小就未必是图片宽×每像素字节数×图片高能表示的了,因为每行可能还需要进行比特填充。
填充后的每行的字节数为:

img_95c488283d2ae9f15f4f878783969bb0.png
,其中BPP(Bits Per Pixel)为每像素的比特数。
在程序中,我们可以表示为:
int iLineByteCnt = (((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;
这样,位图数据区的大小为:
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
我们在扫描完一行数据后,也可能接下来的数据并不是下一行的数据,可能需要跳过一段填充数据:
skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;

二、Ruby实现BMP图片的解析

根据上面的BMP图片格式的介绍,我可以写出以下Ruby代码。

    @file = File.open(file, "rb+")
    @bitMapFileHeader = @file.read(14).unpack('a2LS2L')

    @type=@bitMapFileHeader[0] #文件类型,BM:BMP图片

    if @type!="BM"
      puts "不是BMP图片"
      exit
    end

    @size=@bitMapFileHeader[1] #文件大小
    @offBits=@bitMapFileHeader[4] #图像数据的偏移字节
    @bitMapInfoHeader = @file.read(40).unpack('L3S2L6')

    @infoSize=@bitMapInfoHeader[0] #图片信息字段大小
    @width=@bitMapInfoHeader[1] #图片宽度
    @height=@bitMapInfoHeader[2] #图片高度
    @planes=@bitMapInfoHeader[3] #平面数
    @bitCountPerPixel=@bitMapInfoHeader[4] #图片位数
    @compression=@bitMapInfoHeader[5]
    @imageDataSize=@bitMapInfoHeader[6] #图片数据段大小
    @xPelsPerMeter=@bitMapInfoHeader[7]
    @yPelsPerMeter=@bitMapInfoHeader[8]
    @ClrUsed=@bitMapInfoHeader[9]
    @ClrImportant=@bitMapInfoHeader[10]
    @skipByteALine = 4 - ((@width * @bitCountPerPixel)>>3) & 3
    if @bitCountPerPixel == 24

      iLineByteCnt = (((@width * @bitCountPerPixel) + 31) >> 5) << 2
      @file.seek @offBits
      @imageDataArray= @file.read(@imageDataSize).unpack("C*")
    end

file对象中的read(length)是从文件指针开始读出length个字节的数据,数据类型是字符串,通过unpack函数,我们可以通过传入unpack参数解析出bmp图片的数据结构。

@file.read(14).unpack('a2LS2L')为例,根据上面的BMP文件数据结构的分析,read(14)是读出bmp文件的前14个字节的文件头,参数'a2LS2L'可以将14个字节的数据解析为 两个字符(1*2字节)、一个Long型(1*8字节)、两个Short型(2*2字节),分别取出bmp图片的文件类型(“BM”)、文件大小、两个保留字段、 图像数据偏移量(@imageDataSize)

img_8f6fae2231c4cac1e20e1c05afb2cc1a.png
Paste_Image.png

再从文件中读入@imageDataSize个字节的数据,解析为字符数组,里面除了一些因对齐规则以外的数据都是图片的数据;

@imageDataArray= @file.read(@imageDataSize).unpack("C*")

如果是24位BMP图片,那么每个像素占据三个字节,分别为RGB中的B、G、R值
以一个height=2,width=2的图片为例,其图片数据部分转成单字节数组如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
124 254 258 123 212 21 221 56 107 204 251 100 52 100 111 59

其中每一行的无效数据:skipByteALine = 4 - ((2 * 24)>>3) & 3=2

为了更形象地表示图片像素与RGB数据的对应关系,在此我以二维矩阵的方式展示上面的一维数组:

img_9ade8e70d0d0d98f7f5822d47ba05402.png
Paste_Image.png

由于对齐原则,第一行的7 、8,第二行的15、16元素将被丢弃。

基于以上考虑,可以通过以下Ruby代码获取指定像素位置的RGB值

#图片(i,j)位置的RGB,二维坐标到一维坐标的映射,同时考虑到一个像素的位数以及skip量
def getRGB(i, j) 
 linearIndex = (@width*i+j)*(@bitCountPerPixel>>3)+i*@skipByteALine  
RGB.new(@imageDataArray[linearIndex+2], @imageDataArray[linearIndex+1], @imageDataArray[linearIndex])
end

通过以下Ruby代码设置指定像素位置的RGB值

def setRGB(i, j, rgb)
  linearIndex = (@width*i+j)*(@bitCountPerPixel>>3)+i*@skipByteALine  @imageDataArray[linearIndex+2] = rgb.r  
@imageDataArray[linearIndex+1] = rgb.g  
@imageDataArray[linearIndex] = rgb.b
end

三、Ruby处理图片

通过上面介绍的setRGB和getRGB方法来修改bmp的图片数据字节数组,我们就可以对bmp的指定像素进行操作了,下面介绍将图片灰度化:

#灰度化图片,取RGB三色平均值
#灰度化图片
 #取RGB三色平均值
 def self.grey(bmp)
   for i in 0 .. bmp.height - 1
     for j in 0 .. bmp.width - 1
       rgb = bmp.getRGB(i, j)
       grey = rgb.getGreyLevel
       bmp.setRGB(i, j, RGB.new(grey, grey, grey))
     end
   end
 end

上面的代码我取得所有像素的RGB,然后求出R、G、B值的平均值,RGB值三值相同时像素呈现为灰色
上面的操作只会修改图像数据字节数组,修改完毕需要保存到磁盘,保存方法如下:

def save(file)  
@saveFile = File.open(file, "wb")  
@saveFile.write(@bitMapFileHeader.pack('a2LS2L'))  
@saveFile.write(@bitMapInfoHeader.pack('L3S2L6'))  
@saveFile.write(@imageDataArray.pack('C*'))  
@file.close  @saveFile.close
end

原图:

img_dba16461e1b57e2594d7023b14157206.jpe
raw.jpg

处理效果:

img_c4eb80e4275d843faea9fd80f4469587.jpe
out_grey.jpg

下篇文章我将介绍二值化浮雕滤镜底片滤镜等图像处理算法
预览:

img_9cea6a17aee245ea2a790593dd371061.png
Paste_Image.png

项目主页

geekeren/RubyImageProcess

参考文章

http://blog.csdn.net/hzqnju/article/details/5927825
http://www.jianshu.com/p/30fbaab6d0a6
http://blog.csdn.net/hxker/article/details/50013303
http://blog.csdn.net/o_sun_o/article/details/8351037

目录
相关文章
|
算法 测试技术 API
如何用Ruby语言提高代码的可读性和可维护性
在软件开发过程中,代码的可读性和可维护性是非常重要的因素。一旦代码变得难以理解和修改,就会导致开发速度变慢、bug增多以及团队合作效率下降。在本篇博客中,我们将探讨一些使用Ruby编程语言的技巧和最佳实践,以提高代码的可读性和可维护性。
89 1
|
6月前
|
JSON 监控 数据可视化
局域网管理软件的Ruby代码实践
使用Ruby进行局域网管理的实践,包括安装`net-ping`库进行设备扫描、利用`packetgen`监控流量、执行文件备份自动化任务和数据可视化,以及通过HTTP提交数据。示例代码展示了Ruby在局域网管理中的灵活性和效率。
301 5
|
3月前
|
开发者 数据库 虚拟化
Xamarin 应用性能优化策略大揭秘,从代码到界面再到数据访问,全面提升应用性能,快来围观!
【8月更文挑战第31天】在 Xamarin 跨平台移动应用开发中,性能优化至关重要。代码优化是基础,应避免不必要的计算与内存分配,如减少循环中的对象创建及合理使用数据结构。界面设计上需注意简化布局、减少特效并启用虚拟化以提升响应速度。数据访问方面,优化数据库查询和网络请求可显著改善性能。Xamarin Profiler 等工具还可帮助开发者实时监控并优化应用表现,从而打造流畅高效的用户体验。
52 0
|
3月前
|
开发者 UED Ruby
Ruby中的异常处理之谜:如何用异常与rescue让你的代码坚不可摧?
【8月更文挑战第31天】在软件开发中,错误处理对应用稳定性至关重要。作为动态、面向对象的语言,Ruby提供了丰富的错误处理机制,如异常与rescue。本文通过示例代码介绍了Ruby中的异常类型、异常链及rescue与else的使用,展示了如何优雅地处理各种错误情况,增强了程序的健壮性和用户体验。使用这些机制,开发者能更精准地识别并解决运行时问题,提升应用质量。随着Ruby生态的发展,错误处理的重要性将愈发凸显。
45 0
|
3月前
|
开发者 Ruby
神秘编程魔法惊现!Ruby 元编程究竟隐藏着怎样的力量?竟能让代码自我进化!
【8月更文挑战第31天】《Ruby元编程:让代码自我进化》介绍了Ruby元编程的魅力,通过动态修改代码结构和行为,实现代码自我进化。文章通过实例展示了如何使用`class_eval`动态添加属性和方法,以及通过别名修改现有方法。此外,还介绍了利用模块实现代码复用和扩展。元编程为开发者提供了极大的灵活性和创造力,使代码更加动态高效。
28 0
|
3月前
|
Ruby
Ruby游戏开发揭秘:如何用简单的代码构建你的第一个猜数字游戏?
【8月更文挑战第31天】在编程学习之旅中,开发简易游戏是理解基础概念如控制流程、输入输出及数据结构的好方法。本指南将带你从零开始,用 Ruby 构建一个猜数字游戏。游戏设计为让玩家猜一个 1-100 间的随机数。文中详细介绍了所需环境的搭建过程、代码编写步骤,包括初始化游戏、处理猜测、读取输入和启动游戏循环的核心逻辑。完成这些后,你不仅能拥有一个功能完备的游戏,还将掌握 Ruby 的基本语法与特性,为进一步探索编程世界打下坚实的基础。
36 0
|
3月前
|
测试技术 开发者 Ruby
Ruby领域特定语言(DSL)的神秘力量:如何让你的代码飞起来?
【8月更文挑战第31天】在软件开发领域,Ruby以简洁的语法和强大的表达能力著称,但面对复杂业务逻辑时,代码可能变得冗长难维护。此时,领域特定语言(DSL)成为解决问题的有效途径。DSL专为特定领域设计,使代码更贴近业务逻辑,提高可读性和可维护性。本文通过示例展示了如何在Ruby中开发DSL,包括使用解析器和宏功能创建一个简单的Web应用结构描述语言,并提出了定义清晰语法、避免滥用DSL等最佳实践,强调了测试与文档的重要性。
66 0
|
6月前
|
存储 JavaScript 编译器
这款国产中文编程火了!通过文言文编译生成Python、JS、Ruby代码!
这款国产中文编程火了!通过文言文编译生成Python、JS、Ruby代码!
257 0
|
安全 JavaScript 程序员
Ruby 代码清洁之道 (翻译)
Ruby 代码清洁之道 (翻译)
|
Java Linux 应用服务中间件
在 Ruby on Rails 应用程序中重用 Java 代码
在 Ruby on Rails 应用程序中重用 Java 代码