bmp格式说明

bmp格式说明bmp24 位位图格式创建 bmp24 位位图格式的图片打开 Windows 系统自带的画面软件 如下 点击 文件 gt 另存为 在弹出的界面中有 4 种 bmp 格式可以选择 如下 不知道 bmp 格式是不是只有上面的 4 种 还是有更多 我也懒得了解 这里我们只了解 24 位位图 的 bmp 格式 因为这个格式比较简单 所以 在保存图片的时候要选择 24 位位图的 bmp 格式 不要选错了 bmp24 位位图格式说明重新调整大小 如下 如上图 点击 重新调整大小 然后在出现的界面中设置大小为 2×3 这样的话我们的

bmp24位位图格式

创建bmp24位位图格式的图片

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

然后我们查看该文件的二进制,不知道如何查看文件二进制的,可以查看我的这篇文章:https://blog.csdn.net/android_cai_niao/article/details/

bmp头文件解析

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

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

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

  • 文件大小,由文件头大小(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://blog.csdn.net/u0/article/details/、https://zhuanlan.zhihu.com/p/

通过代码创建一个bmp文件

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

  • 宽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 | 

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文件,示例代码如下:

Test.kt 文件代码如下:

fun main() { 
    //BmpUtil.createBitmapDemo() BmpUtil.createBitmapDemo2() } 
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")) } } 
// 打印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进制的方式打开自己生成的文件和原始的文件,对比数据查看,因为数据很少,也很容易找出问题所在。

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://blog.csdn.net/android_cai_niao/article/details/,在该文章末尾有YUV与bmp图片互相转换的例子。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211660.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午9:55
下一篇 2026年3月18日 下午9:55


相关推荐

  • SSM框架原理,作用及使用方法

    SSM框架原理,作用及使用方法作用:SSM框架是springMVC,spring和mybatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层使用springMVC负责请求的转发和视图管理spring实现业务对象管理,mybatis作为数据对象的持久化引擎原理:SpringMVC:1.客户端发送请求到DispacherServ

    2022年7月12日
    21
  • 实现div里的img图片水平垂直居中

    实现div里的img图片水平垂直居中body结构<body><div><imgsrc="1.jpg"alt="haha"></div></body>方法一:将display设置成table-cell,然后水平居中设置text-align为center,垂直居中设置vertical-align为middle。<styletype="text/css">*{

    2022年5月5日
    63
  • Socket 编程原理

    Socket 编程原理目录socket编程基本概念协议TCPUDPDNSICMPHTTPHTTPS编程流程socket函数socket编程基本概念socket编程即计算机网络编程,目的是使两台主机能够进行远程连接,既然要使两者产生联系,那么就要有至少一个信息发送端和一个信息接收端,因此形成了现在绝大多数socket编程都会用到的C/S架构(Client[客户端]/Server[服务端]),最典型的应用就是web服务器/客户端。在Unix/Linux中执行任何形式的I/O操作(比如网络连接)时,都是在读取

    2022年10月18日
    6
  • JQuery安装与下载教程

    JQuery安装与下载教程jQuery安装与下载JQuery是一个javaScript库,是一个轻量级的”写的少,做的多”的JavaScript库。jQuery极大地简化javaScript编程–juery相比js优点:jquery的onload加载事件速度更快,并且多个加载并行 【jq绑定事件都是使用的事件函数,不需要加on】; js的onloa…

    2022年6月6日
    70
  • OpenClaw 2026.3.2 重大变更!旧教程全部失效,新配置逻辑在这里

    OpenClaw 2026.3.2 重大变更!旧教程全部失效,新配置逻辑在这里

    2026年3月13日
    2
  • css居中的几种办法,CSS div居中的几种方法

    css居中的几种办法,CSS div居中的几种方法CSS 实现 div 垂直居中的方法有很多 下面 div 居中的几种方法是自己平时写网页中经常用到的 最常见的例子就是登录注册弹出框 方法一 对 div 使用绝对布局 position absolute 并设置 top left right bottom 的值相等 但不一定都等于 0 并且设置 margin auto div 水平垂直都居中方法二 这个方法要知道 div 的宽度和高度 对 div 使用绝对布局 position ab

    2026年3月17日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号