bmp24位位图格式
创建bmp24位位图格式的图片
查看bmp24位位图格式的图片数据
然后我们查看该文件的二进制,不知道如何查看文件二进制的,可以查看我的这篇文章:https://blog.csdn.net/android_cai_niao/article/details/
bmp头文件解析
bmp文件由以下3部分组成:
- bmp文件头(占14字节):提供文件的格式、大小等普通的文件属性信息。
- 位图信息头(占40字节):提供图像数据的尺寸、大小等信息,方便图片软件读取以便知道如何读取文件并显示。
- 位图数据,即真正的图片数据。
我们把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图片,你可以分两步分析:
- 用16进制的方式打开bmp文件,查看文件头信息,认真看文件头信息是否都是正确的
- 如果查看了文件头没有错,那就要查看像素数据是否有错了,因为我们使用的图片宽高为 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
