ExASIC
分享让工作更轻松

用python生成bmp图片

常见的bmp图片是一种不压缩的24位真彩色图片。不压缩是指每个像素点都存储了888的RGB数据,即红绿蓝都是8bit,一共可以表示16777216(2^24次方)种颜色。

由于这种无损的特征,bmp图片常常在数字验证中作为测试数据。在scoreboard中对比时可以做到零误差,对调试比较有利。

bmp格式介绍

bmp图片由两部分组成:文件头和RGB数据。

文件头的格式如下图:

bmp文件头

其中,对于24位真彩BMP图来说,

以一幅分辨率为1080x1920的FHD的BMP图片为例,手工计算bmp文件头过程如下:

  1. 1080是一行的像素个数,即宽度为00 00 04 38
  2. 1920是高度,即00 00 07 80
  3. 一行的字节数是1080x3,是4的整倍数,不需要补0
  4. RGB数据总大小1080x3x1920,即00 5e ec 00
  5. 文件总大小1080x3x1920+54,即00 5e ec 36
  6. 按照文件头的字段顺序进行拼装

下图是一幅FHD的图片按十六进制显示的截图,可以看到与我们手工计算的结果一致。 bmp fhd 文件头

用python来产生bmp文件头

我们定义一个class bmp来处理bmp文件相关的数据结构和操作。self.w和self.h分别是图片宽度和高度。有了图片分辨率后bmp文件头就完全确定了。需要注意的是如果一行的字节数不是4的整倍数需要补0

class bmp:
    """ bmp data structure """

    def __init__(self, w=1080, h=1920):
        self.w = w
        self.h = h

    def calc_data_size (self):
        if((self.w*3)%4 == 0):
            self.dataSize = self.w * 3 * self.h
        else:
            self.dataSize = (((self.w * 3) // 4 + 1) * 4) * self.h

        self.fileSize = self.dataSize + 54

    def conv2byte(self, l, num, len):
        tmp = num
        for i in range(len):
            l.append(tmp & 0x000000ff)
            tmp >>= 8

    def gen_bmp_header (self):
        self.calc_data_size();
        self.bmp_header = [0x42, 0x4d]
        self.conv2byte(self.bmp_header, self.fileSize, 4) #file size
        self.conv2byte(self.bmp_header, 0, 2)
        self.conv2byte(self.bmp_header, 0, 2)
        self.conv2byte(self.bmp_header, 54, 4) #rgb data offset
        self.conv2byte(self.bmp_header, 40, 4) #info block size
        self.conv2byte(self.bmp_header, self.w, 4)
        self.conv2byte(self.bmp_header, self.h, 4)
        self.conv2byte(self.bmp_header, 1, 2)
        self.conv2byte(self.bmp_header, 24, 2) #888
        self.conv2byte(self.bmp_header, 0, 4)  #no compression
        self.conv2byte(self.bmp_header, self.dataSize, 4) #rgb data size
        self.conv2byte(self.bmp_header, 0, 4)
        self.conv2byte(self.bmp_header, 0, 4)
        self.conv2byte(self.bmp_header, 0, 4)
        self.conv2byte(self.bmp_header, 0, 4)

    def print_bmp_header (self):
        length = len(self.bmp_header)
        for i in range(length):
            print("{:0>2x}".format(self.bmp_header[i]), end=' ')
            if i%16 == 15:
                print('')
        print('')

在图片上画线和矩形

在图片上画线和矩形的操作本质上就是对rgbData数组进行覆盖写操作。

比如画线,给起始点(x1, y1)和结束点(x2, y2),根据两点间的直线方程y = (y2-y1)/(x2-x1)*(x-x1) + y1,来描点。矩形比较简单,就是把行列范围内覆盖掉。

    def paint_bgcolor(self, color=0xffffff):
        self.rgbData = []
        for r in range(self.h):
            self.rgbDataRow = []
            for c in range(self.w):
                self.rgbDataRow.append(color)
            self.rgbData.append(self.rgbDataRow)

    def paint_line(self, x1, y1, x2, y2, color):
        k = (y2 - y1) / (x2 - x1)
        for x in range(x1, x2+1):
            y = int(k * (x - x1) + y1)
            self.rgbData[y][x] = color

    def paint_rect(self, x1, y1, w, h, color):
        for x in range(x1, x1+w):
            for y in range(y1, y1+h):
                self.rgbData[y][x] = color

把bmp图片写到文件

写bmp图片就是写二进制文件,用‘wb’。

列表转bytes数组的方法:array.array('B', list).tobytes(),注意list里的数据需要在0~255之间。

注意在bmp文件里,蓝色在低字节,红色在高字节

    def save_image(self, name="save.bmp"):
        f = open(name, 'wb')

        #write bmp header
        f.write(array.array('B', self.bmp_header).tobytes())

        #write rgb data
        zeroBytes = self.dataSize // self.h - self.w * 3

        for r in range(self.h):
            l = []
            for i in range(len(self.rgbData[r])):
                p = self.rgbData[r][i]
                l.append(p & 0x0000ff)
                p >>= 8
                l.append(p & 0x0000ff)
                p >>= 8
                l.append(p & 0x0000ff)

            f.write(array.array('B', l).tobytes())

            for i in range(zeroBytes):
                f.write(bytes([0x00]))

        #close file
        f.close()

测试程序

新建一幅500x500分辨率的图片,背景填充绿色,画一条线,起点(50,50),终点(450,450),再画一个矩形,起点(100,100),宽100,高200。

if __name__ == '__main__':
    image = bmp(500, 500)
    image.gen_bmp_header()
    image.print_bmp_header()

    image.paint_bgcolor(0x00ff00)

    image.paint_line(50, 50, 450, 450, 0xff0000)
    image.paint_rect(100, 100, 100, 200, 0x0000ff)

    image.save_image("save1.bmp")

最终效果如下图:

bmp 500x500

注:本篇中没有考虑1位、8位、32位或带压缩的bmp图片。
阅读数:
更多文章:文章目录
解惑专区
(支持markdown插入源代码)
欢迎使用ExASIC订阅服务
仅用于ExASIC最新文章通知,方便及时阅读。
友情链接: IC技术圈问答ReCclayCrazyFPGA