您当前的位置:首页 > 计算机 > 文件格式与编码

bmp格式说明

时间:02-07来源:作者:点击数:

bmp24位位图格式

创建bmp24位位图格式的图片

打开Windows系统自带的画图软件,如下:

在这里插入图片描述

点击“文件 > 另存为”,在弹出的界面中有4种bmp格式可以选择,如下:

在这里插入图片描述

不知道bmp格式是不是只有上面的4种,还是有更多,我也懒得了解,这里我们只了解“24位位图”的bmp格式,因为这个格式比较简单,所以,在保存图片的时候要选择24位位图的bmp格式,不要选错了。

查看bmp24位位图格式的图片数据

重新调整大小,如下:

在这里插入图片描述

Windows 11中调整大小在菜单中:文件 > 图像属性,如下图:

在这里插入图片描述

如上图,设置图片的宽为2,高为3,这样的话我们的bmp图片就只有6个像素,比较少,容易分析。按住Ctrl键然后滚动鼠标滚轮,这样可以放大图片,因为6个像素的图片非常非常的小,所以我们把它放大到最大,如下:

在这里插入图片描述

因为这个图片有6个像素,所以我们可以画6个颜色上去,使用铅笔工具,并且把粗细调到最细,如下:

在这里插入图片描述

在软件的右上位置点击“编辑颜色”,然后在出现的界面中,在红、绿、蓝的位置可以输入颜色值,如下:

在这里插入图片描述

设置好自己需要的颜色,然后用铅笔给图片画上6个颜色,如下:

在这里插入图片描述

最后,把图片保存为24位的bmp格式。下面我把我设置的6个颜色标注如下:

在这里插入图片描述

这6个颜色分别是:黑、白、红、绿、黄、紫。

我把图片保存为“示例.bmp”,查看该文件信息,如下:

在这里插入图片描述

如上图,有3个信息我们可以了解一下,分别是图片的分辨率、位深度、大小。

然后我们查看该文件的二进制,不知道如何查看文件二进制的,可以查看我的这篇文章:https://www.cdsy.xyz/computer/programme/suggest/230207/cd40315.html

"示例.bmp"的二进制内容如下:

在这里插入图片描述

我们再来理解一下这个界面的相关内容,如下:

在这里插入图片描述

如上图,分为3个区域,左边显示的是相应的行的第一个byte元素的索引位置,因为一个文件其实就是一个byte数组,如上图所示,它把一个文件的所有byte数据进行了分行显示,所以最左边的紫色数字显示了相应行第一个byte的索引位置,且是以16进制来表示的,比如第二行的00000018,对应的十进制为24,也就是说第二行的第一个byte是在数组索引24的位置。第三行第一个byte在数组中的索引位置为30(对应十进制为48)。中间区域为具体的文件数据,以16进制显示,每两位为一个字节,比如42是文件第1个字节的数据,4d是文件第2个字节的数据,依次类推。右侧区域应该是显示了每个字节对应的符号,比如42对应的十进制为66,66对应的ASCII符号为B,我们把光标定位在42上,则42高亮显示,右侧的符号B也高亮显示,说明42这个十六进制数据对应的符号为B,如下:

在这里插入图片描述

4d对应的十进制为77,77对应的ASCII符号为M,如下:

在这里插入图片描述

BM就表示这是一个bmp文件,有些数据没有对应符号的就显示为点。

bmp头文件解析

bmp文件由以下3部分组成:

  1. bmp文件头(占14字节):提供文件的格式、大小等普通的文件属性信息。
  2. 位图信息头(占40字节):提供图像数据的尺寸、大小等信息,方便图片软件读取以便知道如何读取文件并显示。
  3. 位图数据,即真正的图片数据。

我们把bmp文件头和位图信息头简称文件头吧,这样文件头就占54字节,剩下的就是图片像素数据了。

回顾我们之前创建的 2 x 3大小的图片,共6个像素,保存了6个颜色,如下:

在这里插入图片描述

此文件的二进制数据如下:

在这里插入图片描述

如上图,有4行数据,每一行解释如下:

在这里插入图片描述

如上图,每行数据我都进行了含义说明,并把每个数据的数组索引标了出来,以16进制方式标了出来。红色为图像像素数据,其它为文件头数据。

关于大端、小端的知识点,可以查看此篇文章,如上图的文件大小为:4e00 0000,它是小端模式的,则4e是最低位的,高位都是0,所以直接把16进制的4e换为十进制即可,4e的十进制为78,说明这个文件的大小为78字节,这跟我们看文件属性上的大小是对应的,如下:

在这里插入图片描述

假设另一个文件大小如下:

在这里插入图片描述

如上图,表示大小的数据为:1675 2800,因为是小端模式,所以,换成高位到低位的顺序为:00 28 75 16,因为它是16进制的,换成十进制,我们使用系统自带的计算机进行计算,如下:

在这里插入图片描述

如上图,可以看到,16进制的287516对应的十进制为2651414,与文件信息上的文件大小也是对应的,如下:

在这里插入图片描述
在这里插入图片描述

bmp头文件中的信息很多,我们只需要了解其中几个即可,比如:

  • 文件大小,由文件头大小(54字节)加上像素数据大小(包含补位)
  • 像素数据大小(包含补位),如上图,每两个颜色后面都有2个字节的补位。可以看到数据显示大小为18,对应的十进制为24,我们的图像是6个像素,需要 6 * 3 = 18字节,为什么需要24字节呢?因为每一行都需要补充2个字节,3行就共需要补6字节,18 + 6 = 24。
  • 像素存储位置值为36,我们看到红色框为图像的像素数据,第一个像素下标正好是36。当我们需要读取图片的像素时,就可以从下标36的位置开始读取了(注意:这是16进制的36,即0x36)。
  • 图像宽度,知道图像宽度很有必要,这样我们才能知道什么时候该换行。如上图中图像宽度为2,因为每个像素由红(8位)、绿(8位)、蓝(8位)组成,所以1个像素需要24位,也就是3个字节,图像宽度为2像素,则宽需要6个字节,但是bmp格式规定宽度必须是4的倍数,6不是4的倍数,所以需要补够4的倍数,6 + 2 = 8,8是4的倍数,从上图我们可以看到,每两个像素后面都有两个字节的补位,就是要补够4的倍数的,从上图我们还发现,它是从图像的最后一行像素开始保存的,而且每个像素在存储的时候是按蓝、绿、红的顺序存储的,比如颜色值为#010203,存储在内存中的顺序为:030201。
  • 图像高度

我个人认为对于初学者不需要了解那么多参数,如想了解所有参数可以查看别人的文章:https://www.cdsy.xyz/computer/fileABC/230208/cd40325.htmlhttps://www.cdsy.xyz/computer/fileABC/230208/cd40326.html

通过代码创建一个bmp文件

前面我们了解了bmp文件的组成原理,那我们就可以通过代码的方式创建一个bmp图片,这里我们创建一个简单的图片,如下:

在这里插入图片描述

图像宽4,高4,上一半显示红色,一下半显示绿色。4 x 4的图片是非常小的,所以上面的截图是经过放大之后的,使用这么小的宽和高,是为了在写代码时可以打印出所有像素数据,方便观察是否正确,如果图片宽高很大的话,像素数据会非常多,不方便分析。

在开始写代码之前,需要先知道一些数据:

  • 宽4,是4的倍数,所以在保存像素时不需要进行补位的操作。刚开始练习时尽量把宽设置为4的倍数。
  • 高4
  • 文件头大小是54,图像像素大小是4 x 4 x 3 = 48,所以48是像素数据大小,48 + 54 = 102是文件大小
  • 红色:#FF0000
  • 绿色:#00FF00

代码创建bmp文件非常简单,根据前面了解的bmp文件头,我们把宽、高、像素大小、文件大小这4个数据设置到文件头中,其它文件头信息就保持和之前分析的保持一样即可。最后就是把红、绿颜色的像素写到文件头后面即可。

1、直接拼装的方式

import java.io.*

fun main() {
    val width = 4
    val height = 4
    val pixelBytesCount = width * height * 3
    val fileBytesCount = pixelBytesCount + 54
    val red = 0xff0000
    val green = 0x00ff00

    // 424d
    val bmpFileBytes = ByteArray(fileBytesCount)
    bmpFileBytes[0x00] = 0x42
    bmpFileBytes[0x01] = 0x4d

    // 文件大小
    var bytes = getLittleEndianBytes(fileBytesCount)
    bmpFileBytes[0x02] = bytes[0]
    bmpFileBytes[0x03] = bytes[1]
    bmpFileBytes[0x04] = bytes[2]
    bmpFileBytes[0x05] = bytes[3]

    // 保留数据
    bmpFileBytes[0x06] = 0x00
    bmpFileBytes[0x07] = 0x00
    bmpFileBytes[0x08] = 0x00
    bmpFileBytes[0x09] = 0x00

    // 像素存储位置
    bmpFileBytes[0x0a] = 0x36
    bmpFileBytes[0x0b] = 0x00
    bmpFileBytes[0x0c] = 0x00
    bmpFileBytes[0x0d] = 0x00

    // bmp头文件大小
    bmpFileBytes[0x0e] = 0x28
    bmpFileBytes[0x0f] = 0x00
    bmpFileBytes[0x10] = 0x00
    bmpFileBytes[0x11] = 0x00

    // 图像宽度
    bytes = getLittleEndianBytes(width)
    bmpFileBytes[0x12] = bytes[0]
    bmpFileBytes[0x13] = bytes[1]
    bmpFileBytes[0x14] = bytes[2]
    bmpFileBytes[0x15] = bytes[3]

    // 图像高度
    bytes = getLittleEndianBytes(height)
    bmpFileBytes[0x16] = bytes[0]
    bmpFileBytes[0x17] = bytes[1]
    bmpFileBytes[0x18] = bytes[2]
    bmpFileBytes[0x19] = bytes[3]

    // 色彩平面数
    bmpFileBytes[0x1a] = 0x01
    bmpFileBytes[0x1b] = 0x00

    // 像素位数
    bmpFileBytes[0x1c] = 0x18
    bmpFileBytes[0x1d] = 0x00

    // 压缩方式
    bmpFileBytes[0x1e] = 0x00
    bmpFileBytes[0x1f] = 0x00
    bmpFileBytes[0x20] = 0x00
    bmpFileBytes[0x21] = 0x00

    // 像素数据大小
    bytes = getLittleEndianBytes(pixelBytesCount)
    bmpFileBytes[0x22] = bytes[0]
    bmpFileBytes[0x23] = bytes[1]
    bmpFileBytes[0x24] = bytes[2]
    bmpFileBytes[0x25] = bytes[3]

    // 横向分辨率
    bmpFileBytes[0x26] = 0x00
    bmpFileBytes[0x27] = 0x00
    bmpFileBytes[0x28] = 0x00
    bmpFileBytes[0x29] = 0x00

    // 纵向分辨率
    bmpFileBytes[0x2a] = 0x00
    bmpFileBytes[0x2b] = 0x00
    bmpFileBytes[0x2c] = 0x00
    bmpFileBytes[0x2d] = 0x00

    // 调色板颜色数
    bmpFileBytes[0x2e] = 0x00
    bmpFileBytes[0x2f] = 0x00
    bmpFileBytes[0x30] = 0x00
    bmpFileBytes[0x31] = 0x00

    // 重要颜色数
    bmpFileBytes[0x32] = 0x00
    bmpFileBytes[0x33] = 0x00
    bmpFileBytes[0x34] = 0x00
    bmpFileBytes[0x35] = 0x00

    // 图像像素
    val redBytes = getLittleEndianBytes(red)
    val greenBytes = getLittleEndianBytes(green)
    var index = 0x36
    for (rowIndex in 0 until height) {
        // 注意:因为存储时是从图像最后一行开始存的,最后一行是绿色,
        // 所以先存后面那些行的绿色,再存前面那些行的红色
        bytes = if (rowIndex < height / 2) greenBytes else redBytes
        for (columnIndex in 0 until width) {
            bmpFileBytes[index++] = bytes[0]
            bmpFileBytes[index++] = bytes[1]
            bmpFileBytes[index++] = bytes[2]
            // 注意:bytes中是有4个元素的,因为int转为了byte数组,我们知道一个int是占4个字节的
            // 因为使用了小端,所以bytes[3]是int的最高位数据,我们是没有使用到的,
            // 因为#FF0000这种颜色值只需要3个字节
        }
    }

    // 把所有的字节写到文件
    val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
    bmpFile.writeBytes(bmpFileBytes)
}

/** 把int转换为byte数组,默认是大端方式的数组,返回转换为小端方式的数组 */
fun getLittleEndianBytes(number: Int): ByteArray {
    val baos = ByteArrayOutputStream()
    val dos = DataOutputStream(baos)
    dos.writeInt(number)
    val bigEndianBytes = baos.toByteArray()
    val littleEndianBytes = bigEndianBytes.reversedArray()
    return littleEndianBytes 
}

运行这段代码即可得到正确的bmp文件,当然,如果你在写代码的时候某些地方写错了,得不到正确的bmp图片,你可以分两步分析:

  1. 用16进制的方式打开bmp文件,查看文件头信息,认真看文件头信息是否都是正确的
  2. 如果查看了文件头没有错,那就要查看像素数据是否有错了,因为我们使用的图片宽高为 4 x 4,所以数据量很小,非常方便我们分析,在分析像素数据时,我们可以在代码中打印,这样我们就可以按行的方式打印每一行的像素,方便分析数据是否有误,示例如下:
var pixelCount = 0
for (i in 0x36 until bmpFileBytes.size step 3) {
    val blue  = bmpFileBytes[i + 0]
    val green = bmpFileBytes[i + 1]
    val red   = bmpFileBytes[i + 2]
    print("$blue, $green, $red |")
    if (++pixelCount == width) {
        // 够一行了,输出一个换行
        pixelCount = 0
        println()
    }
}

运行效果如下:

0, -1, 0 |0, -1, 0 |0, -1, 0 |0, -1, 0 |
0, -1, 0 |0, -1, 0 |0, -1, 0 |0, -1, 0 |
0, 0, -1 |0, 0, -1 |0, 0, -1 |0, 0, -1 |
0, 0, -1 |0, 0, -1 |0, 0, -1 |0, 0, -1 |

可以看到,共4行,前面两行是绿色,后两行是红色,因为存储时是先从图像的最后一行开始存储的,而且每个像素是按蓝、绿、红的顺序输出的。-1其实就是255,因为byte的表示范围是 -128 ~ 127,-1其实就是8个比特位都是1,在byte中就是-1,如果在int中就是255,所以我们还可以转为int类型来显示,修改相关代码如下:

val blue  = bmpFileBytes[i + 0].toInt() shl 24 ushr 24
val green = bmpFileBytes[i + 1].toInt() shl 24 ushr 24
val red   = bmpFileBytes[i + 2].toInt() shl 24 ushr 24

运行结果如下:

0, 255, 0 |0, 255, 0 |0, 255, 0 |0, 255, 0 |
0, 255, 0 |0, 255, 0 |0, 255, 0 |0, 255, 0 |
0, 0, 255 |0, 0, 255 |0, 0, 255 |0, 0, 255 |
0, 0, 255 |0, 0, 255 |0, 0, 255 |0, 0, 255 |

我们用16进制软件查看bmp时,出来的数据都是16进制的,所以,有时候我们也喜欢用16进制的方式来查看结果,修改相关代码如下:

val blue  = Integer.toHexString(bmpFileBytes[i + 0].toInt() shl 24 ushr 24)
val green = Integer.toHexString(bmpFileBytes[i + 1].toInt() shl 24 ushr 24)
val red   = Integer.toHexString(bmpFileBytes[i + 2].toInt() shl 24 ushr 24)

运行结果如下:

0, ff, 0 |0, ff, 0 |0, ff, 0 |0, ff, 0 |
0, ff, 0 |0, ff, 0 |0, ff, 0 |0, ff, 0 |
0, 0, ff |0, 0, ff |0, 0, ff |0, 0, ff |
0, 0, ff |0, 0, ff |0, 0, ff |0, 0, ff |

当你写的小Demo没问题了,此时你就可以把宽高改大一些了,因为此时你应该是不用再分析数据是否对错了,只需要看生成的图片效果是否正常即可。比如设置为300 x 200,运行效果如下:

在这里插入图片描述

2、面向对象的方式

我们前面的代码是按着逻辑一点点拼装的数据,另外,我们也可以假设有一个正常的像素矩阵了,需要把它写到bmp文件中,这次我们把相关的实现代码封装到一个叫BmpUtil的类中,以方便进行复用,示例如下:

1、我们先模拟出一个像素矩阵,如下:

object BmpUtil {

    /** 创建Bitmap的示例:创建一个上一半为红色,下一半为绿色的bmp文件 */
    fun createBitmapDemo() {
        val width = 4 // 注意:宽高要设置为4的倍数,以避免需要进行补位的操作
        val height = 4
        val pixelBytes = createPixelBytes(width, height)
        printPixelBytes(pixelBytes)
    }
    
    /** 创建像素矩阵,注意:宽要设置为4的倍数 */
    fun createPixelBytes(width: Int, height: Int) : Array<ByteArray> {
        val redColor   = 0xFF0000
        val greenColor = 0x00FF00
        val redBytes   = getColorBytes(redColor)
        val greenBytes = getColorBytes(greenColor)
        val pixelBytes = Array(height) { ByteArray(width * 3) }
        for (rowIndex in 0 until height) {
            val colorBytes = if (rowIndex < height / 2) redBytes else greenBytes
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = colorBytes[0]
                val green = colorBytes[1]
                val blue  = colorBytes[2]
                oneLineBytes[columnIndex + 0] = red
                oneLineBytes[columnIndex + 1] = green
                oneLineBytes[columnIndex + 2] = blue
            }
        }
        return pixelBytes
    }

    fun getColorBytes(color: Int): ByteArray {
        val red   = (color and 0xFF0000 ushr 16).toByte()
        val green = (color and 0x00FF00 ushr 8).toByte()
        val blue  = (color and 0x0000FF).toByte()
        val colorBytes = byteArrayOf(red, green, blue)
        return colorBytes
    }
    
    /** 打印像素值,可打印rgbBytes,也可以打印yuv444Bytes */
    fun printPixelBytes(pixelBytes: Array<ByteArray>) {
        for (rowIndex in pixelBytes.indices) {
            val oneLine = pixelBytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
                // 获取1个像素的3个颜色通道:R、G、B 或 Y、U、V
                val colorChannel1 =   oneLine[columnIndex + 0]
                val colorChannel2 =  oneLine[columnIndex + 1]
                val colorChannel3 = oneLine[columnIndex + 2]

                // 把byte转为int,再以16进制进行输出
                val colorChannelInt1 = toHexString(byteToInt(colorChannel1))
                val colorChannelInt2 = toHexString(byteToInt(colorChannel2))
                val colorChannelInt3 = toHexString(byteToInt(colorChannel3))

                // 以16进制进行打印
                print("$colorChannelInt1 $colorChannelInt2 $colorChannelInt3| ")
            }
            println()
        }
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)
    
}
fun main() {
    BmpUtil.createBitmapDemo()
}

运行结果如下:

ff 0 0| ff 0 0| ff 0 0| ff 0 0| 
ff 0 0| ff 0 0| ff 0 0| ff 0 0| 
0 ff 0| 0 ff 0| 0 ff 0| 0 ff 0| 
0 ff 0| 0 ff 0| 0 ff 0| 0 ff 0| 

可以看到,这是一个4 x 4的图像,前两行是红色,后两行是绿色。需要注意的是,这里在获取一个颜色值的byte数组时,使用了位操作,这也是比较方便的。如果按照之前的使用输出流来获取一个int的4个字节时需要注意,因为一个颜色值只占3个字节,所以要取低位的3个字节,但是如果不小心从高位开始取就会出错,这是很容易出现的错误。

接下来就是要把这些图像数据写到bmp文件中,如下:

object BmpUtil {

    /** 创建Bitmap的示例:创建一个上一半为红色,下一半为绿色的bmp文件 */
    fun createBitmapDemo() {
        val width = 4 // 注意:宽高要设置为4的倍数,以避免需要进行补位的操作
        val height = 4
        val pixelBytes = createPixelBytes(width, height)
        //printPixelBytes(pixelBytes)
        val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
        createBmpFile(pixelBytes, bmpFile)
    }
    
    /** 根据给定的像素二维数据,按照bmp文件规范保存到指定的bmp文件中 */
    fun createBmpFile(pixelBytes: Array<ByteArray>, saveFile: File) {
        // 因为一行像素中每个像素是占3个字节的,除以3就得到图像的宽度了
        val pixelWidth = pixelBytes[0].size / 3
        val pixelHeight = pixelBytes.size
        // 每个像素占3个byte,所以要乘以3
        val pixelBytesCount = pixelWidth * pixelHeight * 3
        // 文件总大小为:像素数据大小 + 头文件大小
        val fileBytesCount = pixelBytesCount + 54
        // 创建一个byte数组,用于保存bmp文件的所有byte数据
        val bmpFileBytes = ByteArray(fileBytesCount)
        // 往bmpFileBytes中添加bmp文件头
        addBmpFileHeader(pixelWidth, pixelHeight, bmpFileBytes)
        // 往bmpFileBytes中添加像素数据
        addPixelBytes(pixelBytes, bmpFileBytes)
        // 把所有的字节写到文件
        saveFile.writeBytes(bmpFileBytes)
    }
    
    fun addBmpFileHeader(width: Int, height: Int, bmpFileBytes: ByteArray) {
        val pixelBytesCount = width * height * 3
        val fileBytesCount = pixelBytesCount + 54

        // 424d
        bmpFileBytes[0x00] = 0x42
        bmpFileBytes[0x01] = 0x4d

        // 文件大小
        var bytes = getLittleEndianBytes(fileBytesCount)
        bmpFileBytes[0x02] = bytes[0]
        bmpFileBytes[0x03] = bytes[1]
        bmpFileBytes[0x04] = bytes[2]
        bmpFileBytes[0x05] = bytes[3]

        // 保留数据
        bmpFileBytes[0x06] = 0x00
        bmpFileBytes[0x07] = 0x00
        bmpFileBytes[0x08] = 0x00
        bmpFileBytes[0x09] = 0x00

        // 像素存储位置
        bmpFileBytes[0x0a] = 0x36
        bmpFileBytes[0x0b] = 0x00
        bmpFileBytes[0x0c] = 0x00
        bmpFileBytes[0x0d] = 0x00

        // bmp头文件大小
        bmpFileBytes[0x0e] = 0x28
        bmpFileBytes[0x0f] = 0x00
        bmpFileBytes[0x10] = 0x00
        bmpFileBytes[0x11] = 0x00

        // 图像宽度
        bytes = getLittleEndianBytes(width)
        bmpFileBytes[0x12] = bytes[0]
        bmpFileBytes[0x13] = bytes[1]
        bmpFileBytes[0x14] = bytes[2]
        bmpFileBytes[0x15] = bytes[3]

        // 图像高度
        bytes = getLittleEndianBytes(height)
        bmpFileBytes[0x16] = bytes[0]
        bmpFileBytes[0x17] = bytes[1]
        bmpFileBytes[0x18] = bytes[2]
        bmpFileBytes[0x19] = bytes[3]

        // 色彩平面数
        bmpFileBytes[0x1a] = 0x01
        bmpFileBytes[0x1b] = 0x00

        // 像素位数
        bmpFileBytes[0x1c] = 0x18
        bmpFileBytes[0x1d] = 0x00

        // 压缩方式
        bmpFileBytes[0x1e] = 0x00
        bmpFileBytes[0x1f] = 0x00
        bmpFileBytes[0x20] = 0x00
        bmpFileBytes[0x21] = 0x00

        // 像素数据大小
        bytes = getLittleEndianBytes(pixelBytesCount)
        bmpFileBytes[0x22] = bytes[0]
        bmpFileBytes[0x23] = bytes[1]
        bmpFileBytes[0x24] = bytes[2]
        bmpFileBytes[0x25] = bytes[3]

        // 横向分辨率
        bmpFileBytes[0x26] = 0x00
        bmpFileBytes[0x27] = 0x00
        bmpFileBytes[0x28] = 0x00
        bmpFileBytes[0x29] = 0x00

        // 纵向分辨率
        bmpFileBytes[0x2a] = 0x00
        bmpFileBytes[0x2b] = 0x00
        bmpFileBytes[0x2c] = 0x00
        bmpFileBytes[0x2d] = 0x00

        // 调色板颜色数
        bmpFileBytes[0x2e] = 0x00
        bmpFileBytes[0x2f] = 0x00
        bmpFileBytes[0x30] = 0x00
        bmpFileBytes[0x31] = 0x00

        // 重要颜色数
        bmpFileBytes[0x32] = 0x00
        bmpFileBytes[0x33] = 0x00
        bmpFileBytes[0x34] = 0x00
        bmpFileBytes[0x35] = 0x00
    }

    /** 把指定的像素数据添加到bmp文件数组中 */
    fun addPixelBytes(pixelBytes: Array<ByteArray>, bmpFileBytes: ByteArray) {
        val height = pixelBytes.size
        var index = 0x36

        // 设置像素数据,注意:要从像素的最后一行开始进行存储
        for (rowIndex in height - 1 downTo 0) {
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]

                // 每个像素的三原色按倒序存储
                bmpFileBytes[index++] = blue
                bmpFileBytes[index++] = green
                bmpFileBytes[index++] = red
            }
        }
    }

    /** 把int转换为byte数组,默认是大端方式的数组,返回转换为小端方式的数组 */
    fun getLittleEndianBytes(number: Int): ByteArray {
        val baos = ByteArrayOutputStream()
        val dos = DataOutputStream(baos)
        dos.writeInt(number)
        val bigEndianBytes = baos.toByteArray()
        val littleEndianBytes = bigEndianBytes.reversedArray()
        return littleEndianBytes
    }

}

3、完整代码

为了阅读代码的方便,BmpUtil中之前已经实现的代码就没有添加到上述代码中了,完整代码如下:

Test.kt 文件代码如下:

fun main() {
    BmpUtil.createBitmapDemo()
}

BmpUtil.kt 文件代码如下:

import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.File

object BmpUtil {

    /** 创建Bitmap的示例:创建一个上一半为红色,下一半为绿色的bmp文件 */
    fun createBitmapDemo() {
        val width = 300 // 注意:宽高要设置为4的倍数,以避免需要进行补位的操作
        val height = 200
        val pixelBytes = createPixelBytes(width, height)
        //printPixelBytes(pixelBytes)
        val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
        createBmpFile(pixelBytes, bmpFile)
    }

    /** 创建像素矩阵,注意:宽要设置为4的倍数 */
    fun createPixelBytes(width: Int, height: Int) : Array<ByteArray> {
        val redColor   = 0xFF0000
        val greenColor = 0x00FF00
        val redBytes   = getColorBytes(redColor)
        val greenBytes = getColorBytes(greenColor)
        val pixelBytes = Array(height) { ByteArray(width * 3) }
        for (rowIndex in 0 until height) {
            val colorBytes = if (rowIndex < height / 2) redBytes else greenBytes
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = colorBytes[0]
                val green = colorBytes[1]
                val blue  = colorBytes[2]
                oneLineBytes[columnIndex + 0] = red
                oneLineBytes[columnIndex + 1] = green
                oneLineBytes[columnIndex + 2] = blue
            }
        }
        return pixelBytes
    }

    fun getColorBytes(color: Int): ByteArray {
        val red   = (color and 0xFF0000 ushr 16).toByte()
        val green = (color and 0x00FF00 ushr 8).toByte()
        val blue  = (color and 0x0000FF).toByte()
        val colorBytes = byteArrayOf(red, green, blue)
        return colorBytes
    }
    
    /** 打印像素值,可打印rgbBytes,也可以打印yuv444Bytes */
    fun printPixelBytes(pixelBytes: Array<ByteArray>) {
        for (rowIndex in pixelBytes.indices) {
            val oneLine = pixelBytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
                // 获取1个像素的3个颜色通道:R、G、B 或 Y、U、V
                val colorChannel1 =   oneLine[columnIndex + 0]
                val colorChannel2 =  oneLine[columnIndex + 1]
                val colorChannel3 = oneLine[columnIndex + 2]

                // 把byte转为int,再以16进制进行输出
                val colorChannelInt1 = toHexString(byteToInt(colorChannel1))
                val colorChannelInt2 = toHexString(byteToInt(colorChannel2))
                val colorChannelInt3 = toHexString(byteToInt(colorChannel3))

                // 以16进制进行打印
                print("$colorChannelInt1 $colorChannelInt2 $colorChannelInt3| ")
            }
            println()
        }
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)

    /** 根据给定的像素二维数据,按照bmp文件规范保存到指定的bmp文件中 */
    fun createBmpFile(pixelBytes: Array<ByteArray>, saveFile: File) {
        // 因为一行像素中每个像素是占3个字节的,除以3就得到图像的宽度了
        val pixelWidth = pixelBytes[0].size / 3
        val pixelHeight = pixelBytes.size
        // 每个像素占3个byte,所以要乘以3
        val pixelBytesCount = pixelWidth * pixelHeight * 3
        // 文件总大小为:像素数据大小 + 头文件大小
        val fileBytesCount = pixelBytesCount + 54
        // 创建一个byte数组,用于保存bmp文件的所有byte数据
        val bmpFileBytes = ByteArray(fileBytesCount)
        // 往bmpFileBytes中添加bmp文件头
        addBmpFileHeader(pixelWidth, pixelHeight, bmpFileBytes)
        // 往bmpFileBytes中添加像素数据
        addPixelBytes(pixelBytes, bmpFileBytes)
        // 把所有的字节写到文件
        saveFile.writeBytes(bmpFileBytes)
    }

    fun addBmpFileHeader(width: Int, height: Int, bmpFileBytes: ByteArray) {
        val pixelBytesCount = width * height * 3
        val fileBytesCount = pixelBytesCount + 54

        // 424d
        bmpFileBytes[0x00] = 0x42
        bmpFileBytes[0x01] = 0x4d

        // 文件大小
        var bytes = getLittleEndianBytes(fileBytesCount)
        bmpFileBytes[0x02] = bytes[0]
        bmpFileBytes[0x03] = bytes[1]
        bmpFileBytes[0x04] = bytes[2]
        bmpFileBytes[0x05] = bytes[3]

        // 保留数据
        bmpFileBytes[0x06] = 0x00
        bmpFileBytes[0x07] = 0x00
        bmpFileBytes[0x08] = 0x00
        bmpFileBytes[0x09] = 0x00

        // 像素存储位置
        bmpFileBytes[0x0a] = 0x36
        bmpFileBytes[0x0b] = 0x00
        bmpFileBytes[0x0c] = 0x00
        bmpFileBytes[0x0d] = 0x00

        // bmp头文件大小
        bmpFileBytes[0x0e] = 0x28
        bmpFileBytes[0x0f] = 0x00
        bmpFileBytes[0x10] = 0x00
        bmpFileBytes[0x11] = 0x00

        // 图像宽度
        bytes = getLittleEndianBytes(width)
        bmpFileBytes[0x12] = bytes[0]
        bmpFileBytes[0x13] = bytes[1]
        bmpFileBytes[0x14] = bytes[2]
        bmpFileBytes[0x15] = bytes[3]

        // 图像高度
        bytes = getLittleEndianBytes(height)
        bmpFileBytes[0x16] = bytes[0]
        bmpFileBytes[0x17] = bytes[1]
        bmpFileBytes[0x18] = bytes[2]
        bmpFileBytes[0x19] = bytes[3]

        // 色彩平面数
        bmpFileBytes[0x1a] = 0x01
        bmpFileBytes[0x1b] = 0x00

        // 像素位数
        bmpFileBytes[0x1c] = 0x18
        bmpFileBytes[0x1d] = 0x00

        // 压缩方式
        bmpFileBytes[0x1e] = 0x00
        bmpFileBytes[0x1f] = 0x00
        bmpFileBytes[0x20] = 0x00
        bmpFileBytes[0x21] = 0x00

        // 像素数据大小
        bytes = getLittleEndianBytes(pixelBytesCount)
        bmpFileBytes[0x22] = bytes[0]
        bmpFileBytes[0x23] = bytes[1]
        bmpFileBytes[0x24] = bytes[2]
        bmpFileBytes[0x25] = bytes[3]

        // 横向分辨率
        bmpFileBytes[0x26] = 0x00
        bmpFileBytes[0x27] = 0x00
        bmpFileBytes[0x28] = 0x00
        bmpFileBytes[0x29] = 0x00

        // 纵向分辨率
        bmpFileBytes[0x2a] = 0x00
        bmpFileBytes[0x2b] = 0x00
        bmpFileBytes[0x2c] = 0x00
        bmpFileBytes[0x2d] = 0x00

        // 调色板颜色数
        bmpFileBytes[0x2e] = 0x00
        bmpFileBytes[0x2f] = 0x00
        bmpFileBytes[0x30] = 0x00
        bmpFileBytes[0x31] = 0x00

        // 重要颜色数
        bmpFileBytes[0x32] = 0x00
        bmpFileBytes[0x33] = 0x00
        bmpFileBytes[0x34] = 0x00
        bmpFileBytes[0x35] = 0x00
    }

    /** 把指定的像素数据添加到bmp文件数组中 */
    fun addPixelBytes(pixelBytes: Array<ByteArray>, bmpFileBytes: ByteArray) {
        val height = pixelBytes.size
        var index = 0x36

        // 设置像素数据,注意:要从像素的最后一行开始进行存储
        for (rowIndex in height - 1 downTo 0) {
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]

                // 每个像素的三原色按倒序存储
                bmpFileBytes[index++] = blue
                bmpFileBytes[index++] = green
                bmpFileBytes[index++] = red
            }
        }
    }

    /** 把int转换为byte数组,默认是大端方式的数组,返回转换为小端方式的数组 */
    fun getLittleEndianBytes(number: Int): ByteArray {
        val baos = ByteArrayOutputStream()
        val dos = DataOutputStream(baos)
        dos.writeInt(number)
        val bigEndianBytes = baos.toByteArray()
        val littleEndianBytes = bigEndianBytes.reversedArray()
        return littleEndianBytes
    }
    
}

读取bmp文件,读取像素,再保存回bmp文件

读出来又保存回去,这似乎是在做无用功啊!其实也是有些用处的,可以加深对bmp文件格式的理解,我们前面的例子比较简单,只有红色和绿色,如何构建一幅复杂颜色的图片呢?纯代码创造那是要搞死人的,所以我们可以用截图软件截个画面出来并保存为bmp,然后读取里面的像素数据,然后再保存为bmp文件,示例代码如下:

为了方便观察数据,我们使用较小的宽高,而且为了预防图片的像素数据没有补位的情况,我们宽要设置为4的倍数,这里我们使用系统自带画图创建一个4 x 2的图像,如下:

在这里插入图片描述

把这个图像保存为24位的bmp文件。上图中8个颜色的颜色值如下:

在这里插入图片描述

接下来就是读取这个图片的像素值,为了方便操作,我们使用二维数组来保存数据,还有,为了方便阅读相关代码,之前已经实现的代码也不会出现在下面的代码中。示例如下:

Test.kt 文件代码如下:

fun main() {
    //BmpUtil.createBitmapDemo()
    BmpUtil.createBitmapDemo2()
}

BmpUtil.kt 文件代码如下:

运行结果如下:

object BmpUtil {

    /** 创建Bitmap的示例:通过读取一个bmp文件的像素,再把这些像素写入一个新的bmp文件 */
    fun createBitmapDemo2() {
        val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\8个颜色.bmp"))
        printPixelBytes(bmpFilePixelBytes)
    }

    fun readBmpFilePixelBytes(bmpFile: File): Array<ByteArray> {
        // 得到bmp文件的所有字节
        val bmpFileBytes = bmpFile.readBytes()

        // 从bmp文件中获取图像的宽和高的字节数组
        val widthLittleEndianBytes = ByteArray(4)
        val heightLittleEndianBytes = ByteArray(4)
        System.arraycopy(bmpFileBytes, 0x12, widthLittleEndianBytes, 0, 4)
        System.arraycopy(bmpFileBytes, 0x16, heightLittleEndianBytes, 0, 4)

        // 把小端的字节数组转换为Int
        val width = bigEndianBytesToInt(widthLittleEndianBytes)
        val height = bigEndianBytesToInt(heightLittleEndianBytes)
        println("读取到bmp图像width = $width, height = $height")
        val pixelBytes = Array(height) { ByteArray(width * 3) }
        var rowIndex = height - 1 // 因为bmp图片是从最后一行开始保存的,读取的时候我们把它往到正确的位置
        var columnIndex = 0
        var oneLineBytes = pixelBytes[rowIndex]
        val oneLineBytesSize = oneLineBytes.size
        // 像素值都是从0x36的位置开始保存的,而且每个像素点3个字节
        for (i in 0x36 until bmpFileBytes.size step 3) {
            if (columnIndex == oneLineBytesSize) {
                // 存满一行了,需要换行保存了。这里--rowIndex是因为原图片是从最后一行向前面行的顺序保存的
                oneLineBytes = pixelBytes[--rowIndex]
                columnIndex = 0
            }

            // 注意:bmp文件的颜色是按蓝、绿、红的顺序保存的
            val blue  = bmpFileBytes[i + 0]
            val green = bmpFileBytes[i + 1]
            val red   = bmpFileBytes[i + 2]

            oneLineBytes[columnIndex++] = red
            oneLineBytes[columnIndex++] = green
            oneLineBytes[columnIndex++] = blue
        }

        return pixelBytes
    }

    /** 把小端的字节数组转换为int */
    private fun bigEndianBytesToInt(littleEndianBytes: ByteArray): Int {
        val bigEndianBytes= littleEndianBytes.reversedArray()
        val bais = ByteArrayInputStream(bigEndianBytes)
        val dis = DataInputStream(bais)
        return dis.readInt()
    }
    
}

运行结果如下:

读取到bmp图像width = 4, height = 2
0 0 0| ff ff ff| ff 0 0| 0 ff 0| 
0 0 ff| ff ff 0| ff 0 ff| 0 ff ff| 

和原图对比一下数据是否正确:

在这里插入图片描述

bmp文件保存的像素数据是从最后一行开始保存的,而且颜色是按蓝、绿、红的顺序存储的,我们取出来的时候把它们都调正了,这是为了方便我们查看结果,从上面打印的结果看,它和图片的每个像素的位置是一至的,而且每个值都是正确的。

Ok,一张bmp的文件的所有像素数据我们都读取到代码中了,接下来就是把它写回到bmp文件中,如下:

object BmpUtil {

    /** 创建Bitmap的示例:通过读取一个bmp文件的像素,再把这些像素写入一个新的bmp文件 */
    fun createBitmapDemo2() {
        val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\8个颜色.bmp"))
        //printPixelBytes(bmpFilePixelBytes)
        createBmpFile(bmpFilePixelBytes, File("C:\\Users\\Even\\Pictures\\demo.bmp"))
    }

}

运行如上代码,将会生成一张demo.bmp的图片,用系统自带的画图软件打开这张图片,并且放大,然后观看效果,如下:

在这里插入图片描述

可以看到,正确还原了!如果发现没有正确还原的话,可以打印一下bmpFileBytes中的数据进行分析,假设怀疑是像素的数据有问题,则可以只打印像素数据的部分,如下:

// 打印bmpFileBytes中保存的像素数据
val bmpFileBytes = File("C:\\Users\\Even\\Pictures\\demo.bmp").readBytes()
var count = 0
val width = 4
for (index in 0x36 until bmpFileBytes.size step 3) {
    val blue = toHexString(byteToInt(bmpFileBytes[index + 0]))
    val green = toHexString(byteToInt(bmpFileBytes[index + 1]))
    val red = toHexString(byteToInt(bmpFileBytes[index + 2]))
    print("$blue $green $red | ")
    count += 3
    if (count == width * 3) {
        // 读够一行了,输出一个换行
        println()
    }
}

运行结果如下:

ff 0 0 | 0 ff ff | ff 0 ff | ff ff 0 | 
0 0 0 | ff ff ff | 0 0 ff | 0 ff 0 | 

分析这个数据是否正确,需要注意的是,它的行是倒着的,每个像素的三原色也是倒着存的,当然了,在打印的时候,你可以调整一下三原色的输出顺序,以方便观察。如果发现像素数据没问题,那就是添加bmp文件头出问题了,就可以打印一下文件头的数据,看哪里出了问题。

还有更简单的检查办法,我们可以以16进制的方式打开自己生成的文件和原始的文件,对比数据查看,因为数据很少,也很容易找出问题所在。

这个Demo我们就完成了,接下来就可以试一些复杂的图片了,比如,使用截图软件截图一张640 * 480的图片,然后保存为24位的bmp文件,如下:

在这里插入图片描述

我们只需要变文件路径即可,如下:

fun createBitmapDemo2() {
    val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\海琴烟.bmp"))
    createBmpFile(bmpFilePixelBytes, File("C:\\Users\\Even\\Pictures\\demo.bmp"))
}

完整代码如下:

import java.io.*

object BmpUtil {

    /** 创建Bitmap的示例:通过读取一个bmp文件的像素,再把这些像素写入一个新的bmp文件 */
    fun createBitmapDemo2() {
        val bmpFilePixelBytes = readBmpFilePixelBytes(File("C:\\Users\\Even\\Pictures\\海琴烟.bmp"))
        //printPixelBytes(bmpFilePixelBytes)
        createBmpFile(bmpFilePixelBytes, File("C:\\Users\\Even\\Pictures\\demo.bmp"))
    }

    /** 创建Bitmap的示例:创建一个上一半为红色,下一半为绿色的bmp文件 */
    fun createBitmapDemo() {
        val width = 300 // 注意:宽高要设置为4的倍数,以避免需要进行补位的操作
        val height = 200
        val pixelBytes = createPixelBytes(width, height)
        //printPixelBytes(pixelBytes)
        val bmpFile = File("C:\\Users\\Even\\Pictures\\demo.bmp")
        createBmpFile(pixelBytes, bmpFile)
    }

    fun readBmpFilePixelBytes(bmpFile: File): Array<ByteArray> {
        // 得到bmp文件的所有字节
        val bmpFileBytes = bmpFile.readBytes()

        // 从bmp文件中获取图像的宽和高的字节数组
        val widthLittleEndianBytes = ByteArray(4)
        val heightLittleEndianBytes = ByteArray(4)
        System.arraycopy(bmpFileBytes, 0x12, widthLittleEndianBytes, 0, 4)
        System.arraycopy(bmpFileBytes, 0x16, heightLittleEndianBytes, 0, 4)

        // 把小端的字节数组转换为Int
        val width = littleEndianBytesToInt(widthLittleEndianBytes)
        val height = littleEndianBytesToInt(heightLittleEndianBytes)
        println("读取到bmp图像width = $width, height = $height")
        val pixelBytes = Array(height) { ByteArray(width * 3) }
        var rowIndex = height - 1 // 因为bmp图片是从最后一行开始保存的,读取的时候我们把它往到正确的位置
        var columnIndex = 0
        var oneLineBytes = pixelBytes[rowIndex]
        val oneLineBytesSize = oneLineBytes.size
        // 像素值都是从0x36的位置开始保存的,而且每个像素点3个字节
        for (i in 0x36 until bmpFileBytes.size step 3) {
            if (columnIndex == oneLineBytesSize) {
                // 存满一行了,需要换行保存了。这里--rowIndex是因为原图片是从最后一行向前面行的顺序保存的
                oneLineBytes = pixelBytes[--rowIndex]
                columnIndex = 0
            }

            // 注意:bmp文件的颜色是按蓝、绿、红的顺序保存的
            val blue  = bmpFileBytes[i + 0]
            val green = bmpFileBytes[i + 1]
            val red   = bmpFileBytes[i + 2]

            oneLineBytes[columnIndex++] = red
            oneLineBytes[columnIndex++] = green
            oneLineBytes[columnIndex++] = blue
        }

        return pixelBytes
    }

    /** 把BigEnding的字节数组转换为int */
    private fun littleEndianBytesToInt(littleEndianBytes: ByteArray): Int {
        val bigEndianBytes = littleEndianBytes.reversedArray()
        val bais = ByteArrayInputStream(bigEndianBytes)
        val dis = DataInputStream(bais)
        return dis.readInt()
    }

    /** 创建像素矩阵,注意:宽要设置为4的倍数 */
    fun createPixelBytes(width: Int, height: Int) : Array<ByteArray> {
        val redColor   = 0xFF0000
        val greenColor = 0x00FF00
        val redBytes   = getColorBytes(redColor)
        val greenBytes = getColorBytes(greenColor)
        val pixelBytes = Array(height) { ByteArray(width * 3) }
        for (rowIndex in 0 until height) {
            val colorBytes = if (rowIndex < height / 2) redBytes else greenBytes
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = colorBytes[0]
                val green = colorBytes[1]
                val blue  = colorBytes[2]
                oneLineBytes[columnIndex + 0] = red
                oneLineBytes[columnIndex + 1] = green
                oneLineBytes[columnIndex + 2] = blue
            }
        }
        return pixelBytes
    }

    fun getColorBytes(color: Int): ByteArray {
        val red   = (color and 0xFF0000 ushr 16).toByte()
        val green = (color and 0x00FF00 ushr 8).toByte()
        val blue  = (color and 0x0000FF).toByte()
        val colorBytes = byteArrayOf(red, green, blue)
        return colorBytes
    }
    
    /** 打印像素值,可打印rgbBytes,也可以打印yuv444Bytes */
    fun printPixelBytes(pixelBytes: Array<ByteArray>) {
        for (rowIndex in pixelBytes.indices) {
            val oneLine = pixelBytes[rowIndex]
            for (columnIndex in oneLine.indices step 3) {
                // 获取1个像素的3个颜色通道:R、G、B 或 Y、U、V
                val colorChannel1 =   oneLine[columnIndex + 0]
                val colorChannel2 =  oneLine[columnIndex + 1]
                val colorChannel3 = oneLine[columnIndex + 2]

                // 把byte转为int,再以16进制进行输出
                val colorChannelInt1 = toHexString(byteToInt(colorChannel1))
                val colorChannelInt2 = toHexString(byteToInt(colorChannel2))
                val colorChannelInt3 = toHexString(byteToInt(colorChannel3))

                // 以16进制进行打印
                print("$colorChannelInt1 $colorChannelInt2 $colorChannelInt3| ")
            }
            println()
        }
    }

    fun byteToInt(byte: Byte): Int = byte.toInt() shl 24 ushr 24
    fun toHexString(int: Int): String = Integer.toHexString(int)

    /** 根据给定的像素二维数据,按照bmp文件规范保存到指定的bmp文件中 */
    fun createBmpFile(pixelBytes: Array<ByteArray>, saveFile: File) {
        // 因为一行像素中每个像素是占3个字节的,除以3就得到图像的宽度了
        val pixelWidth = pixelBytes[0].size / 3
        val pixelHeight = pixelBytes.size
        // 每个像素占3个byte,所以要乘以3
        val pixelBytesCount = pixelWidth * pixelHeight * 3
        // 文件总大小为:像素数据大小 + 头文件大小
        val fileBytesCount = pixelBytesCount + 54
        // 创建一个byte数组,用于保存bmp文件的所有byte数据
        val bmpFileBytes = ByteArray(fileBytesCount)
        // 往bmpFileBytes中添加bmp文件头
        addBmpFileHeader(pixelWidth, pixelHeight, bmpFileBytes)
        // 往bmpFileBytes中添加像素数据
        addPixelBytes(pixelBytes, bmpFileBytes)
        // 把所有的字节写到文件
        saveFile.writeBytes(bmpFileBytes)
    }

    fun addBmpFileHeader(width: Int, height: Int, bmpFileBytes: ByteArray) {
        val pixelBytesCount = width * height * 3
        val fileBytesCount = pixelBytesCount + 54

        // 424d
        bmpFileBytes[0x00] = 0x42
        bmpFileBytes[0x01] = 0x4d

        // 文件大小
        var bytes = getLittleEndianBytes(fileBytesCount)
        bmpFileBytes[0x02] = bytes[0]
        bmpFileBytes[0x03] = bytes[1]
        bmpFileBytes[0x04] = bytes[2]
        bmpFileBytes[0x05] = bytes[3]

        // 保留数据
        bmpFileBytes[0x06] = 0x00
        bmpFileBytes[0x07] = 0x00
        bmpFileBytes[0x08] = 0x00
        bmpFileBytes[0x09] = 0x00

        // 像素存储位置
        bmpFileBytes[0x0a] = 0x36
        bmpFileBytes[0x0b] = 0x00
        bmpFileBytes[0x0c] = 0x00
        bmpFileBytes[0x0d] = 0x00

        // bmp头文件大小
        bmpFileBytes[0x0e] = 0x28
        bmpFileBytes[0x0f] = 0x00
        bmpFileBytes[0x10] = 0x00
        bmpFileBytes[0x11] = 0x00

        // 图像宽度
        bytes = getLittleEndianBytes(width)
        bmpFileBytes[0x12] = bytes[0]
        bmpFileBytes[0x13] = bytes[1]
        bmpFileBytes[0x14] = bytes[2]
        bmpFileBytes[0x15] = bytes[3]

        // 图像高度
        bytes = getLittleEndianBytes(height)
        bmpFileBytes[0x16] = bytes[0]
        bmpFileBytes[0x17] = bytes[1]
        bmpFileBytes[0x18] = bytes[2]
        bmpFileBytes[0x19] = bytes[3]

        // 色彩平面数
        bmpFileBytes[0x1a] = 0x01
        bmpFileBytes[0x1b] = 0x00

        // 像素位数
        bmpFileBytes[0x1c] = 0x18
        bmpFileBytes[0x1d] = 0x00

        // 压缩方式
        bmpFileBytes[0x1e] = 0x00
        bmpFileBytes[0x1f] = 0x00
        bmpFileBytes[0x20] = 0x00
        bmpFileBytes[0x21] = 0x00

        // 像素数据大小
        bytes = getLittleEndianBytes(pixelBytesCount)
        bmpFileBytes[0x22] = bytes[0]
        bmpFileBytes[0x23] = bytes[1]
        bmpFileBytes[0x24] = bytes[2]
        bmpFileBytes[0x25] = bytes[3]

        // 横向分辨率
        bmpFileBytes[0x26] = 0x00
        bmpFileBytes[0x27] = 0x00
        bmpFileBytes[0x28] = 0x00
        bmpFileBytes[0x29] = 0x00

        // 纵向分辨率
        bmpFileBytes[0x2a] = 0x00
        bmpFileBytes[0x2b] = 0x00
        bmpFileBytes[0x2c] = 0x00
        bmpFileBytes[0x2d] = 0x00

        // 调色板颜色数
        bmpFileBytes[0x2e] = 0x00
        bmpFileBytes[0x2f] = 0x00
        bmpFileBytes[0x30] = 0x00
        bmpFileBytes[0x31] = 0x00

        // 重要颜色数
        bmpFileBytes[0x32] = 0x00
        bmpFileBytes[0x33] = 0x00
        bmpFileBytes[0x34] = 0x00
        bmpFileBytes[0x35] = 0x00
    }

    /** 把指定的像素数据添加到bmp文件数组中 */
    fun addPixelBytes(pixelBytes: Array<ByteArray>, bmpFileBytes: ByteArray) {
        val height = pixelBytes.size
        var index = 0x36

        // 设置像素数据,注意:要从像素的最后一行开始进行存储
        for (rowIndex in height - 1 downTo 0) {
            val oneLineBytes = pixelBytes[rowIndex]
            for (columnIndex in oneLineBytes.indices step 3) {
                val red   = oneLineBytes[columnIndex + 0]
                val green = oneLineBytes[columnIndex + 1]
                val blue  = oneLineBytes[columnIndex + 2]

                // 每个像素的三原色按倒序存储
                bmpFileBytes[index++] = blue
                bmpFileBytes[index++] = green
                bmpFileBytes[index++] = red
            }
        }
    }

    /** 把int转换为byte数组,默认是大端方式的数组,返回转换为小端方式的数组 */
    fun getLittleEndianBytes(number: Int): ByteArray {
        val baos = ByteArrayOutputStream()
        val dos = DataOutputStream(baos)
        dos.writeInt(number)
        val bigEndianBytes = baos.toByteArray()
        val littleEndianBytes = bigEndianBytes.reversedArray()
        return littleEndianBytes
    }
    
}

YUV图片与bmp图片互相转换

这需要用到YUV相关的知识,可参考该文章:https://www.cdsy.xyz/computer/fileABC/230207/cd40322.html,在该文章末尾有YUV与bmp图片互相转换的例子。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门