用 Python 优雅的缩放图片——计算机学科与技术导论大作业 2023-01-06 学习,编程,大学 2 条评论 2582 次阅读 # 认识 BMP 格式 ## 简介 BMP取自位图Bitmap的缩写,也称为DIB(与设备无关的位图),是一种独立于显示器的位图数字图像文件格式。常见于微软视窗和OS/2操作系统,Windows GDI API内部使用的DIB数据结构与 BMP 文件格式几乎相同。 ## 文件格式 [](https://picture-1300689095.file.myqcloud.com/2023/01/06/63b835218c49d.png) (点击可以跳转图片看大图) 这个图片中的 padding 就是对齐字节用的,最后每行像素的字节数会对齐到4的整数倍,其他的图片里说的很清楚 ### 通用文件头 前14个字节标识了该文件为位图文件 (start-end 代表字节区间,后面的英文代表后面程序中的变量名) #### 0-1 前两个字节用于标识BMP文件,一般为0x42 0x4D,即ASCII的BM。以下为可能的取值: - BM – Windows 3.1x, 95, NT, ... etc. - BA – OS/2 struct Bitmap Array - CI – OS/2 struct Color Icon - CP – OS/2 const Color Pointer - IC – OS/2 struct Icon - PT – OS/2 Pointer 一般是不会遇到 BM 之外的文件头的,所以直接判断 BM 即可 #### 2-5 fileSize 紧跟着四个字节代表文件的大小 #### 6-9 四个保留位,一般都设置为 0 即可 #### 11-13 dataStart 像素数据的地址偏移 ### 位图文件头 位图文件头有很多个版本,长度也不固定,~~需要动态分析~~ #### 14-17 headerSize 位图文件头占用的总字节数量 #### 18-21 width 位图的宽度 #### 22-25 height 位图的高度 #### 26-27 nbplan 色彩平面数;只有1为有效值 #### 28-29 bpp 每个像素所占位数,即图像的色深。典型值为1、4、8、16、24和32 咱们暂时只考虑了 24 和 32 #### 30-33 compression 所使用的压缩方法,取值有很多,我们取0就好 具体的取值可以去 wiki 查看 #### 34-37 imageSize 图像大小,像素数据的总大小,和文件大小不一样 #### 38-(dataStart-1) 一些暂时不需要考虑的数据和调色板,可以去 wiki 查看详情 ### 像素数据 如果 bpp=24,那么只有 rgb ,每三个字节表示一个像素 如果 bpp=32,那么为 rgba ,每四个字节表示一个像素 不论哪种表示方法,都是倒着排的,最后一行像素在整个像素数据的最前面。 每行像素最后都要补充到 4 的整数倍数字节,例如上面的图片是 $$3\*3=9$$ 字节,最后需要对齐到 12 字节 #### 例子 一个内容为 黑 白 的图像 在像素数据中是以 FF FF FF 00 00 00 00 00 黑色(3) 白色(3) 对齐(2) 的方式存储的 # Python 实现 ## 总体思路 读入图片->读取文件头->读取原像素点->计算新像素点->计算新文件头->计算新图片字节->写出图片 ## 文件读入和输出 我们使用 open 函数来操作文件 其中第一个参数为文件路径,第二个参数为模式 模式为 'rb' 时,可以以二进制读入文件 模式为 'wb' 时,可以将二进制写到文件 ### 读入 使用代码 ```python with open("path/to/img.bmp", 'rb') as f: imgBytes = f.read() ``` 即可将 img.bmp 文件读入到 imgBytes 中,imgBytes 为 bytes 格式 ### 输出 使用代码 ```python with open("path/to/img_new.bmp", 'rb') as f: f.write(imgBytes) ``` 即可将 imgBytes 中的字节写到 img_new.bmp 中 ### bytes 类型 bytes 是可迭代对象,可以使用 `imgBytes[index]` 访问偏移量为 index 的字节,会返回该字节数据对应的十进制值 直接输出一个 bytes 对象会非常鬼畜,他会把很多的值以对应的 ASICII 值的形式输出,导致你看不懂,推荐使用其他软件查看 bmp 图片的字节或者是使用 `for i in bytes` 迭代查看 bytes 中的数据 使用 `x = bytes(Array)` 可以将一个数组转为 bytes 类型的对象,因此可以先将每一个字节计算好,最后一次性转换,然后输出 ## 处理字节存储的 16 进制数字 以 fileSize 为例,四个字节存储着整个文件的大小,用 python 处理起来不太方便 假设 fileSize 的四个字节为 21 10 00 00 在 python 中以 bytes 对象存储时,输出为 33,16,0,0 我们可以把他看作一个倒着排的 256 进制数,计算它大小的方法就是 $$33\*256^0+16\*256^1=4129$$ 在最后保存图片时候也把它当作 256 进制的数字处理即可 ## 图片缩放 ### 像素点对应关系 假设我们要缩放的比例为 $$w$$ 和$$h$$ 对于新的位置 $$(x,y)$$,它对应的原位置为 $$(x/w,y/h)$$,考虑到 $$w,h$$ 和计算出的 $$\frac{x}{w},\frac{y}{h}$$可能为小数, 我们使用 python 中 math 库的函数 **floor** 来取整像素点 (为什么不用round:四舍五入可能导致数组越界,虽然不太可能,但是floor保险一些) ### 对齐处理 由于 bmp 图片存储的格式,必须保证每行像素字节数为 4 的整数倍,所以我们要把每行像素字节数加到 4 的整数倍 ```python while (rowLength % 4 != 0): rowLength += 1 ``` ### 偏移量计算 由于我们读入的图片和输出的图片中,bmp像素行都是倒着的,我们可以不去刻意处理,存储像素点的 pixels 数组和新图片的 newpixels 数组全部都是倒着存的 然后就可以用 `dataStart + currentRow * rowLength + currentCol` 很方便的计算偏移量 currentRow,currentCol 表示当前为第 currentRow 行的 currentCol 像素 rowLength 为考虑过对齐的每行像素点个数 ## 最终代码 https://git.luthics.com/Luthics/bmp-resize-python 请勿抄袭,仅供学习 --- **参考链接:** 1. BMP - 维基百科,自由的百科全书 https://zh.wikipedia.org/wiki/BMP 2. corkami/pics: Posters, drawings... Github https://github.com/corkami/pics **修改日志:** 1. 2023/1/6 文章基础版完成 标签: python, bmp 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
叩谢
芜湖