1. 程式人生 > >Java實現24位真彩轉換為8位灰度圖片

Java實現24位真彩轉換為8位灰度圖片

         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位灰度了。