Java读写BMP文件

本文详细介绍了BMP文件格式的组成部分,包括位图文件头和位图信息头,以及Java中如何使用BmpUtil类进行BMP图像的读写操作,着重讲解了BMP文件的结构和处理真彩色图与调色板的方法。
摘要由CSDN通过智能技术生成

1.BMP文件格式

        BMP文件格式组成部分:bmp文件头(14个字节) + 位图信息头(40个字节) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)

位图文件头BITMAPFILEHEADER

位图信息头BITMAPINFOHEADER

调色板Palette(可选)

实际的位图数据ImageDate

         第一部分为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:      

typedef struct tagBITMAPFILEHEADER {
    WORD  bfType;
    DWORD bfSize;
    WORD  bfReserved1;
    WORD  bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

        这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:

bfType

指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”。

bfSize

指定文件大小,包括这14个字节。

bfReserved1

保留字,不用考虑

bfReserved2

保留字,不用考虑

bfOffBits

为从文件头到实际的位图数据的偏移字节数,前三个部分的长度之和。

         第二部分为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:

typedef struct tagBITMAPINFOHEADER{
    DWORD  biSize;
    LONG   biWidth;
    LONG   biHeight;
    WORD   biPlanes;
    WORD   biBitCount
    DWORD  biCompression;
    DWORD  biSizeImage;
    LONG   biXPelsPerMeter;
    LONG   biYPelsPerMeter;
    DWORD  biClrUsed;
    DWORD  biClrImportant;
} BITMAPINFOHEADER;

        这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下:

biSize

指定这个结构的长度,为40。

biWidth

指定图象的宽度,单位是像素。

biHeight

指定图象的高度,单位是像素。

biPlanes

必须是1,不用考虑。

biBitCount

指定表示颜色时要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图), 32(带透明通道的真彩色图)

biCompression

指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。一般只使用第一种不压缩的情况,即biCompression为BI_RGB的情况。

biSizeImage

指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来:

biSizeImage=biWidth’ × biHeight

要注意的是:上述公式中的biWidth’必须是4的整倍数(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数。举个例子,如果biWidth=240,则biWidth’=240;如果biWidth=241,biWidth’=244)。

如果biCompression为BI_RGB,则该项可能为零

biXPelsPerMeter

指定目标设备的水平分辨率,单位是每米的像素个数。

biYPelsPerMeter

指定目标设备的垂直分辨率,单位同上。

biClrUsed

指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次方。

biClrImportant

指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。

        第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。

        调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有 2的biBitCount次方 个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:

typedef struct tagRGBQUAD {
    BYTE    rgbBlue;     //该颜色的蓝色分量
    BYTE    rgbGreen;    //该颜色的绿色分量
    BYTE    rgbRed;      //该颜色的红色分量
    BYTE    rgbReserved; //保留值
} RGBQUAD;

        第四部分就是实际的图像数据了。对于用到调色板的位图,图像数据就是该像素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。

要注意两点:

        (1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。

        (2) 一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图像最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推 ,最后得到的是最上面一行的最右一个象素。

2.Java读写BMP代码

package com.miaxis.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class BmpUtil {
    /**
     * @author   mickey
     * @category 读取BMP图像
     * @param    strFileName - [in]BMP文件路径
     *           ucImgBuf    - [out]图象数据
     *           iWidth      - [out]图像宽度
     *           iHeight     - [out]图像高度
     *           iChannels   - [out]图像通道数
     * @return    0   - 成功
     *           其他  - 失败
     * @notes    暂时只支持灰度BMP文件格式
     * */
    public static int ReadBMP(String strFileName,byte[] ucImgBuf,int[] iWidth,int[] iHeight,int[] iChannels)
    {
        byte[] bBmp = ReadData(strFileName);
        if(bBmp==null)
            return -1;
        return Bmp2Raw(bBmp,ucImgBuf,iWidth,iHeight,iChannels);
    }

    /**
     * @author   mickey
     * @category 保存BMP图像
     * @param    strFileName - [in]BMP文件路径
     *           ucImgBuf    - [in]]图象数据
     *           iWidth      - [in]图像宽度
     *           iHeight     - [in]图像高度
     *           iChannels   - [in]图像通道数
     * @return    0   - 成功
     *           其他  - 失败
     * @notes    暂时只支持灰度BMP文件格式
     * */
    public static int SaveBMP(String strFileName,byte[] ucImgBuf,int iWidth,int iHeight,int iChannels)
    {
        int iNewWidth = (iChannels*iWidth+3)/4*4;
        byte[] bBmp = new byte[iNewWidth*iHeight + 1078];
        int[] iBmpLen = new int[1];
        Raw2Bmp(bBmp, iBmpLen,ucImgBuf, iWidth, iHeight,iChannels);
        return SaveData(strFileName, bBmp, iBmpLen[0]);
    }

    public static int JUnsigned(int x)
    {
        if (x>=0)
            return x;
        else
            return (x+256);
    }

    /**
     * @author   mickey
     * @category BMP格式图像数据 -> 图像裸数据
     * @param    pBmp       - [in]BMP格式图像数据
     *                          pRaw       - [out]图像裸数据
     *                          iWidth     - [out]图像宽度
     *                          iHeight        - [out]图像高度
     *                          iChannels  - [out]图像通道数
     * @return     0    - 成功
     *            其他   - 失败
     * */
    public static int Bmp2Raw(byte[] pBmp,byte[] pRaw,int[] iWidth,int[] iHeight,int[] iChannels)
    {
        int i,X,Y,channels;
        byte[] head = new byte[1078];
        System.arraycopy(pBmp, 0, head, 0, 54);
        if(head[0]!=0x42 || head[1]!=0x4D)
            return -1;
        int head18=(int)head[18];
        int head19=(int)(head[19]<<8);
        int head20=(int)(head[20]<<8);
        int head21=(int)(head[21]<<8);

        int head22=(int)head[22];
        int head23=(int)(head[23]<<8);
        int head24=(int)(head[24]<<8);
        int head25=(int)(head[25]<<8);
        X=JUnsigned(head18) + JUnsigned(head19) + JUnsigned(head20) + JUnsigned(head21);
        Y=JUnsigned(head22) + JUnsigned(head23) + JUnsigned(head24) + JUnsigned(head25);

        int head28=(int)(head[28]);
        int head29=(int)(head[29]<<8);
        channels = JUnsigned(head28) + JUnsigned(head29);
        channels = channels/8;
        //System.out.println("channels="+channels);
        iWidth[0]  = X;
        iHeight[0] = Y;
        iChannels[0] = channels;
        if(channels == 4)
            iChannels[0] = 3;
        int iNewWidth = (channels * X + 3) / 4 * 4;
        if (channels==1){
            for( i=0;i<Y; i++ ) {
                System.arraycopy(pBmp,1078+(Y-1-i)*iNewWidth, pRaw,i*X, X);
            }
        }else if(channels==3) {
                        for( i=0;i<Y; i++ ) {
                                System.arraycopy(pBmp,54+(Y-1-i)*iNewWidth, pRaw,i*3*X, 3*X);
                        }
        }else if(channels==4) {
            for (i = 0; i < Y; i++) {
                for (int j = 0; j < X; j++) {
                    pRaw[i * 3 * X + 3 * j]     = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j];
                    pRaw[i * 3 * X + 3 * j + 1] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 1];
                    pRaw[i * 3 * X + 3 * j + 2] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 2];
                }
            }
        }
        else{
            return -2;
        }
        return 0;
    }

    /**
     * @author   mickey
     * @category 读取文件数据到byte数组
     * @param    filepath - [in]文件路径
     * @return   byte数组
     * */
    public static byte[] ReadData(String filepath){
        File f = new File(filepath);
        if(!f.exists()){
            System.out.println("filepath=" + null);
            return null;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length());
        BufferedInputStream in = null;
        try{
            in = new BufferedInputStream(new FileInputStream(f));
            int buf_size = 1024;
            byte[] buffer = new byte[buf_size];
            int len = 0;
            while(-1 != (len = in.read(buffer,0,buf_size))){
                bos.write(buffer,0,len);
            }
            return bos.toByteArray();
        }catch (IOException e) {
            e.printStackTrace();
        }finally{
            try{
                in.close();
            }catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * @author   mickey
     * @category 图像裸数据-> BMP格式图像数据
     * @param    pBmp    - [out]BMP格式图像数据
     *           iBMPLen - [out]BMP格式图像数据长度
     *                          pRaw    - [in]图像裸数据
     *                          iImgX   - [in]图像宽度
     *                          iImgY         - [in]图像高度
     *                          iChannels   - [in]图像通道数
     * @return     0    - 成功
     *            其他   - 失败
     * */
    public static int Raw2Bmp(byte[] pBmp,int[] iBMPLen, byte[] pRaw, int X, int Y,int iChannels) {
        int num;
        int i, j;
        byte[] head = new byte[1078];

        byte[] temp = { 0x42, 0x4d, // file header
                0x00, 0x00, 0x00, 0x00, // file size***
                0x00, 0x00, // reserved
                0x00, 0x00,// reserved
                0x36, 0x4, 0x00, 0x00,// head byte***
                0x28, 0x00, 0x00, 0x00,// struct size
                0x00, 0x00, 0x00, 0x00,// map width***
                0x00, 0x00, 0x00, 0x00,// map height***
                0x01, 0x00,// must be 1
                0x08, 0x00,// color count***
                0x00, 0x00, 0x00, 0x00, // compression
                0x00, 0x00, 0x00, 0x00,// data size***
                0x00, 0x00, 0x00, 0x00, // dpix
                0x00, 0x00, 0x00, 0x00, // dpiy
                0x00, 0x00, 0x00, 0x00,// color used
                0x00, 0x00, 0x00, 0x00,// color important
        };
        System.arraycopy(temp, 0, head, 0, temp.length);

        int iNewWidth = (iChannels*X+3)/4*4;
        int iHeadLen = 54;

        if (iChannels == 1)
        {
            iHeadLen = 1078;
        }
        int iFileSize = iHeadLen + iNewWidth*Y;
        num=iFileSize;
        head[2] = (byte) (num & 0xFF);
        num = num >> 8;
        head[3] = (byte) (num & 0xFF);
        num = num >> 8;
        head[4] = (byte) (num & 0xFF);
        num = num >> 8;
        head[5] = (byte) (num & 0xFF);

        num = iHeadLen;
        head[10] = (byte) (num & 0xFF);
        num = num >> 8;
        head[11] = (byte) (num & 0xFF);
        num = num >> 8;
        head[12] = (byte) (num & 0xFF);
        num = num >> 8;
        head[13] = (byte) (num & 0xFF);

        // 确定图象宽度数值
        num = X;
        head[18] = (byte) (num & 0xFF);
        num = num >> 8;
        head[19] = (byte) (num & 0xFF);
        num = num >> 8;
        head[20] = (byte) (num & 0xFF);
        num = num >> 8;
        head[21] = (byte) (num & 0xFF);
        // 确定图象高度数值
        num = Y;
        head[22] = (byte) (num & 0xFF);
        num = num >> 8;
        head[23] = (byte) (num & 0xFF);
        num = num >> 8;
        head[24] = (byte) (num & 0xFF);
        num = num >> 8;
        head[25] = (byte) (num & 0xFF);
        //BitCount
        num = iChannels*8;
        head[28] = (byte) (num & 0xFF);
        num = num >> 8;
        head[29] = (byte) (num & 0xFF);
        //Data size
        num = Y*iNewWidth;
        head[34] = (byte) (num & 0xFF);
        num = num >> 8;
        head[35] = (byte) (num & 0xFF);
        num = num >> 8;
        head[36] = (byte) (num & 0xFF);
        num = num >> 8;
        head[37] = (byte) (num & 0xFF);

        // 确定调色板数值
        j = 0;
        for (i = 54; i < 1078; i = i + 4) {
            head[i] = head[i + 1] = head[i + 2] = (byte) j;
            head[i + 3] = 0;
            j++;
        }
        // 写入文件头
        System.arraycopy(head, 0, pBmp, 0, iHeadLen);
        // 写入图象数据
        for (i = 0; i < Y; i++) {
            System.arraycopy(pRaw, i * iChannels * X, pBmp,
                    iHeadLen + (Y - 1 - i) * iNewWidth, iChannels * X);
        }
        //输出文件大小
            iBMPLen[0] = iFileSize;
        return 0;
    }

    /**
     * @author   mickey
     * @category 保存数据为文件
     * @param    filepath - [in]文件路径
     *                          buffer   - [in]数据缓存
     *                          size     - [in]数据长度
     * @return     0      - 成功
     *            其他     - 失败
     * */
    public static int SaveData(String filepath, byte[] buffer, int size) {
        File f = new File(filepath);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(f);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return -1;
        }
        try {
            fos.write(buffer, 0, size);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            fos.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return -2;
        }
        return 0;
    }
}

3. Java测试工程源代码

编译器版本: IntelliJ IDEA 2020.3.2 x64

JDK版本:    java 1.8.0_111

https://download.csdn.net/download/mickey2007/89017485

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值