关闭

Java实现24位真彩转换为8位灰度图片

1415人阅读 评论(0) 收藏 举报

         Windows下的位图文件即我们通常所熟悉的BMP图片,其存储结构的格式可以在WINGDI.h文件中找到定义。BMP文件大体上分为四个部分:

1.      位图文件头(BITMAPFILEHEADER

2.      位图信息头(BITMAPINFOHEADER

3.      调色板(PALETTE)

4.      位图数据(IMAGEDATA)

根据颜色深度的不同,图像上的一个像素可以用一个或者多个字节表示,它由n/8所确定(n是位深度,1字节包含8个数据位)。这里需要注意的是,对于调色板(也叫颜色查找表LUT(LookUpTable),索引表),不是每一种类型的位图都有的。对于24位的真彩色RGB位图,就是没有调色板的,原因:如果用调色板,总共有2的24次种颜色,所以表示每种颜色的索引也需要24位,和直接用3B来表示RGB数据一样,还得加上一个调色板的容量,完全是吃饱了撑着。所以调色板只是对于16,8,4,1位位深的位图来说的。

首先来看下256阶位图(位深度为8)的前两个部分数据。以下为16进制值:

                                                          图 一

1.      BITMAPFILEHEADER(对应图一的0000H -- 000DH

WINGDI.h中该部分定义如下:

typedef struct tagBITMAPFILEHEADER {
        WORD    bfType;
        DWORD   bfSize;
        WORD    bfReserved1;
        WORD    bfReserved2;
        DWORD   bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

前两个字节(42H,4DH)为ASCII码,代表BM,这是在Windows下BMP文件的标识符,对应结构体中的bfType;

接下来的四个字节(36H,75H,02H,00H )代表的是文件大小,这里高位在后,所以文件大小应该是27536H = 161078字节,对应结构体中的bfSize;

然后为四个字节的保留值,总为0,对应bfReserved1,bfReserved2;

最后四个字节(36H,04H,00H,00H)是图像数据的地址,即文件头+信息头+调色板的长度,这里 436H = 1078字节。我们来算下这是怎么得出的,文件头已经分析过了,为2+4+2+2+4 = 14个字节,文件信息头固定为40个字节,对于8位位图,其调色板为256*4 = 1024个字节。所以1024+40+14 = 1078。

2.      BITMAPINFOHEADER(对应图一的000EH --> 0035H

其结构体定义为:

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, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

各字段的含义如下:

地址(Hex)

描述

对应字段

000EH-0011H

28H,00H,00H,00H

bmp位图信息头大小,28H = 40D,即位图信息头固定为40个字节;

biSize

0012H-0015H

90H,01H,00H,00H

图像宽度(像素),190H = 400D,即该图像宽400个像素;

biWidth

0016H-0019H

90H,01H,00H,00H

图像高度(像素),该图像宽也是400个像素;

biHeight

001AH-001BH

01H,00H

必须是一,不用考虑;

biPlanes

001CH-001DH

08H,00H

图像的位深度,此处为8;

biBitCount

001EH-0021H

全为00H

压缩方式,0代表不压缩;

biCompression

0022H-0025H

00H,71H,02H,00H

实际的位图数据大小,27100H = 160000,即数据大小为400*400 = 160000字节;

biSizeImage

0026H-0029H

全为00H

目标设备的水平分辨率;

biXPelsPerMeter

002AHà002DH

全为00H

目标设备的垂直分辨率;

biYPelsPerMeter

002EH-0031H

全为00H

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

biClrUsed

0032H-0035H

00 01 00 00

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

biClrImportant

3.      调色板(从0036H开始)

当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次方个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:

typedef struct tagRGBQUAD {
        BYTE    rgbBlue;
        BYTE    rgbGreen;
        BYTE    rgbRed;
        BYTE    rgbReserved;
} RGBQUAD;

调色板的一个元素为4个字节,前三个分别为颜色值,后一个为保留值,总是0。

4.      位图数据

对于用到调色板的位图,图象数据就是该像素颜在调色板中的索引值,对于真彩色图,图象数据就是实际的R,G,B值。256色位图,一个字节表示1个像素。而对于真彩色图,三个字节才能表示1个像素。

 

知道bmp位图的格式,就可以实现从24位真彩色到8位灰度图的转换了。我用的是java,首先把24位的真彩转换为24位的灰度值,这个转换很简单,用J2SE的API就可以,也可以自己实现,先读取每个像素的RGB值,再用公式就可以计算出灰度值,再写进每个像素的RGB值中。

不同的RGB空间,灰阶的计算公式有所不同,常见的几种RGB空间的计算灰阶的公式如下:

1、 简化 sRGB IEC61966-2.1 [gamma=2.20]

Gray = (R^2.2 * 0.2126  + G^2.2  * 0.7152  + B^2.2  * 0.0722)^(1/2.2)

2、 Adobe RGB (1998) [gamma=2.20]

Gray = (R^2.2 * 0.2973  + G^2.2  * 0.6274  + B^2.2  * 0.0753)^(1/2.2)

3、 Apple RGB [gamma=1.80]

Gray = (R^1.8 * 0.2446  + G^1.8  * 0.6720  + B^1.8  * 0.0833)^(1/1.8)

4、 ColorMatch RGB [gamma=1.8]

Gray = (R^1.8 * 0.2750  + G^1.8  * 0.6581  + B^1.8  * 0.0670)^(1/1.8)

5、 简化 KODAK DC Series Digital Camera [gamma=2.2]

Gray = (R^2.2 * 0.2229  + G^2.2  * 0.7175  + B^2.2  * 0.0595)^(1/2.2)

 

然后把得到的24位灰度(此时叫伪灰度,因为仅仅是把RGB值设为了一样)图片,转换为8位即256色的灰度图片。我的做法是先构造文件头,信息头,调色板,再把源图片的RGB值只保留一个写进每个像素。Java代码如下:

private void Bit24_to_256Index( )
    {
       // 将图像文件读出,数据保存在Byte数组
       try
       {
           inputImage = ImageIO.read( new File(fileString) );
       }
       catch (IOException e1)
       {
           e1.printStackTrace();
       }
       width = inputImage.getWidth();
       height= inputImage.getHeight();
      
       ByteArrayOutputStream bos = new ByteArrayOutputStream( width*height*4 + 54);
             
       try
       {
           ImageIO.write( inputImage, "BMP", bos);
       } catch (IOException e)
       {
           e.printStackTrace();
       }
       bSrcfile = bos.toByteArray();
      
       // 新文件的长度(b)=数据部分+调色板(1024)+位图信息头+位图文件头
       bDestfile = new byte[ width*height+1078 ];
             
       // 开始构造字节数组
       bDestfile[0] = bSrcfile[0];  // 00H : 42H
       bDestfile[1] = bSrcfile[1];  // 01H : 4DH
       // 文件大小(B)
       int fileLength = width * height + 1078 ;
       byte[] btLen = int2bytes(fileLength);
   
       switch( btLen.length )
       {
       case 1:
           bDestfile[2] = btLen[0];
           break;
       case 2:
           bDestfile[3] = btLen[0];
           bDestfile[2] = btLen[1];
           break;
       case 3:
           bDestfile[4] = btLen[0];
           bDestfile[3] = btLen[1];
           bDestfile[2] = btLen[2];
           break;
       case 4:
           bDestfile[5] = btLen[0];
           bDestfile[4] = btLen[1];
           bDestfile[3] = btLen[2];
           bDestfile[2] = btLen[3];
       }
      
       // 数据的偏移地址固定为1078(436H)
       bDestfile[10] = 54;   // 36H
       bDestfile[11] = 4;    // 04H
       for( int i = 14; i <= 27; i++ )
       {
           bDestfile[i] = bSrcfile[i];  
       }
       bDestfile[28] = 8;  // 2^8 = 256
       // 数据大小字段
       int biSizeImage = width * height;  // 对256色图来讲,数据部分的大小为长*高
       byte[] btSI = int2bytes(biSizeImage);
       switch( btSI.length )
       {
       case 1:
           bDestfile[34] = btSI[0];
           break;
       case 2:
           bDestfile[35] = btSI[0];
           bDestfile[34] = btSI[1];
           break;
       case 3:
           bDestfile[36] = btSI[0];
           bDestfile[35] = btSI[1];
           bDestfile[34] = btSI[2];
           break;
       case 4:
           bDestfile[37] = btSI[0];
           bDestfile[36] = btSI[1];
           bDestfile[35] = btSI[2];
           bDestfile[34] = btSI[3];
       }
      
       for( int i = 38; i <= 53; i++ )
       {
           bDestfile[i] = bSrcfile[i];
       }
       byte bRGB = 0;
       // 写调色板  36H(54) --> 435H(1077)
       for( int i = 54; i <= 1077; i += 4, bRGB ++ )
       {
           bDestfile[i] = bRGB;
           bDestfile[i+1] = bRGB;
           bDestfile[i+2] = bRGB;
           bDestfile[i+3] = 0;   // rgbReserved, 保留值为零
       }
             
       // 转换图像数据部分
       for( int i = 1078, j = 54; i < bDestfile.length; i++, j += 3 )
       {
           bDestfile[i] = bSrcfile[j];
       }
       outputImage = new BufferedImage( width, height, BufferedImage.TYPE_BYTE_GRAY);
       ByteArrayInputStream in = new ByteArrayInputStream( bDestfile );    //将b作为输入流;
       try {
           outputImage = ImageIO.read( in );
       } catch (IOException e) {
           e.printStackTrace();
           }
       try
       {
           ImageIO.write( outputImage, "BMP",new   File( fileString ));
       }
       catch(Exception   ex)
       {
          ex.printStackTrace();
       }
    }

函数int2bytes实现把int型的数据转换为byte型的数组,代码:

    static byte[] int2bytes(int num)
    {
       byte[] b=new byte[4];
       int mask=0xff;
       for(int i=0;i<4;i++)
       {
           b[i]=(byte)(num>>>(24-i*8));
       }
       return b;
    }

经过这两步变换,就可以将真彩的转换为8位灰度了。





0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1788次
    • 积分:11
    • 等级:
    • 排名:千里之外
    • 原创:1篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章存档