Java Tip 43: How to read 8- and 24-bit Microsoft Windows bitmaps in Java applications
A step-by-step guide to loading bitmap files in a Java application
Java Tip 43: How to read 8- and 24-bit Microsoft Windows bitmaps in Java applications
A step-by-step guide to loading bitmap files in a Java application
在当前的Java版本中,官方不支持用Java应用程序中读取Microsoft Windows的位图文件。但是没关系,我们有一种方法可以解决它!本文将向您展示如何完成这个任务--从阅读Microsoft Windows文件格式开始描述基本步骤。
在Windows中DIB(与设备无关的位图)文件格式是相对简单的。DBI格式保存的详细的信息不同于用于存储在RAM中的普通图像的位图格式。问题是,有很多图像格式种类(除了别的之外有 1, 4, 8 和 16 bit)。在这个Java Tip提供的这个解决方案中只有 8 - 和 24 - bit。这两种代表着最广泛的种类。
尽管是Windows DIB子类型,文件格式总是由一个14-byte的文件头和一个40-byte的信息头组成。这两个头文件包含的信息包括存储在文件中的准确信息和该信息的存储顺序。查阅Microsoft Software Development Kit(SDK)头文件中每一项精确的含义。剩下的文件内容取决于标题中的数据信息。
让我们看看我们将要解决的两种子类型。24-bit的非常简单:RGB(Red-Green-Blue)的颜色值(3 bytes,按照BGR的顺序)紧跟在文件信息头之后。然而,每个扫描线都要填充一个四字节边界(即每行像素数是4的倍数,如果不是,则在末尾补齐)这种“填充"是一个优化的窗口位图绘图API(参见微软SDK)。同时,文件扫描的方向是从下到上从左到右的---所以图像相对于通常的图像坐标系是矢量向下和向右的。
8-bit 子类型之所以复杂是因为在调色板信息在文件信息头和像素数据之间。因此,每个像素条目是一个8-bit到24-bitRGB颜色的调色板数组的索引。再次提醒,像素信息会填充每一条的字节数为4的倍数的扫描线。
注意,这里给出的位图载入图像方法不支持压缩的位图图像。事实上,这个例程甚至不找这种可能性!这个例程在遇到一个压缩的Windows DIB格式文件时肯定会产生一个异常。压缩的Windows DIB格式记录在Windows SDK中。
在性能方面,这个例程可以在486-DX2-66MHz Windows 95以上运行,并且10s内就可以读一个24-bit,640 x 480大小的文件(大约920kb),性能可以通过使用BufferedInputStream代替FileInputStream显著增强。
loadbitmap方法在Java Tip 43 坑内会有Bug。24-bit 中计算npad的值时可以为4。当npad的值为4时,应该设置为0,详情在Java Tip 60 的writeBitmap方法中。
下面的程序读取两个文件的格式和生成一个图像对象中的一个。Comprehensive error和Exception handling不包括下面将进行的常规步骤。一个不受支持的Windows DIB子类型型总是可以使用Windows画图程序进行转换。
/** loadbitmap() method converted from Windows C code. Reads only uncompressed 24- and 8-bit images. Tested with images saved using Microsoft Paint in Windows 95. If the image is not a 24- or 8-bit image, the program refuses to even try. I guess one could include 4-bit images by masking the byte by first 1100 and then 0011. I am not really interested in such images. If a compressed image is attempted, the routine will probably fail by generating an IOException. Look for variable ncompression to be different from 0 to indicate compression is present. Arguments: sdir and sfile are the result of the FileDialog() getDirectory() and getFile() methods. Returns: Image Object, be sure to check for (Image)null !!!! */ public Image loadbitmap (String sdir, String sfile) { Image image; System.out.println("loading:"+sdir+sfile); try { FileInputStream fs=new FileInputStream(sdir+sfile); int bflen=14; // 14 byte BITMAPFILEHEADER byte bf[]=new byte[bflen]; fs.read(bf,0,bflen); int bilen=40; // 40-byte BITMAPINFOHEADER byte bi[]=new byte[bilen]; fs.read(bi,0,bilen); // Interperet data. int nsize = (((int)bf[5]&0xff)<<24) | (((int)bf[4]&0xff)<<16) | (((int)bf[3]&0xff)<<8) | (int)bf[2]&0xff; System.out.println("File type is :"+(char)bf[0]+(char)bf[1]); System.out.println("Size of file is :"+nsize); int nbisize = (((int)bi[3]&0xff)<<24) | (((int)bi[2]&0xff)<<16) | (((int)bi[1]&0xff)<<8) | (int)bi[0]&0xff; System.out.println("Size of bitmapinfoheader is :"+nbisize); int nwidth = (((int)bi[7]&0xff)<<24) | (((int)bi[6]&0xff)<<16) | (((int)bi[5]&0xff)<<8) | (int)bi[4]&0xff; System.out.println("Width is :"+nwidth); int nheight = (((int)bi[11]&0xff)<<24) | (((int)bi[10]&0xff)<<16) | (((int)bi[9]&0xff)<<8) | (int)bi[8]&0xff; System.out.println("Height is :"+nheight); int nplanes = (((int)bi[13]&0xff)<<8) | (int)bi[12]&0xff; System.out.println("Planes is :"+nplanes); int nbitcount = (((int)bi[15]&0xff)<<8) | (int)bi[14]&0xff; System.out.println("BitCount is :"+nbitcount); // Look for non-zero values to indicate compression int ncompression = (((int)bi[19])<<24) | (((int)bi[18])<<16) | (((int)bi[17])<<8) | (int)bi[16]; System.out.println("Compression is :"+ncompression); int nsizeimage = (((int)bi[23]&0xff)<<24) | (((int)bi[22]&0xff)<<16) | (((int)bi[21]&0xff)<<8) | (int)bi[20]&0xff; System.out.println("SizeImage is :"+nsizeimage); int nxpm = (((int)bi[27]&0xff)<<24) | (((int)bi[26]&0xff)<<16) | (((int)bi[25]&0xff)<<8) | (int)bi[24]&0xff; System.out.println("X-Pixels per meter is :"+nxpm); int nypm = (((int)bi[31]&0xff)<<24) | (((int)bi[30]&0xff)<<16) | (((int)bi[29]&0xff)<<8) | (int)bi[28]&0xff; System.out.println("Y-Pixels per meter is :"+nypm); int nclrused = (((int)bi[35]&0xff)<<24) | (((int)bi[34]&0xff)<<16) | (((int)bi[33]&0xff)<<8) | (int)bi[32]&0xff; System.out.println("Colors used are :"+nclrused); int nclrimp = (((int)bi[39]&0xff)<<24) | (((int)bi[38]&0xff)<<16) | (((int)bi[37]&0xff)<<8) | (int)bi[36]&0xff; System.out.println("Colors important are :"+nclrimp); if (nbitcount==24) { // No Palatte data for 24-bit format but scan lines are // padded out to even 4-byte boundaries. int npad = (nsizeimage / nheight) - nwidth * 3; int ndata[] = new int [nheight * nwidth]; byte brgb[] = new byte [( nwidth + npad) * 3 * nheight]; fs.read (brgb, 0, (nwidth + npad) * 3 * nheight); int nindex = 0; for (int j = 0; j < nheight; j++) { for (int i = 0; i < nwidth; i++) { ndata [nwidth * (nheight - j - 1) + i] = (255&0xff)<<24 | (((int)brgb[nindex+2]&0xff)<<16) | (((int)brgb[nindex+1]&0xff)<<8) | (int)brgb[nindex]&0xff; // System.out.println("Encoded Color at (" +i+","+j+")is:"+nrgb+" (R,G,B)= (" +((int)(brgb[2]) & 0xff)+"," +((int)brgb[1]&0xff)+"," +((int)brgb[0]&0xff)+")"); nindex += 3; } nindex += npad; } image = createImage ( new MemoryImageSource (nwidth, nheight, ndata, 0, nwidth)); } else if (nbitcount == 8) { // Have to determine the number of colors, the clrsused // parameter is dominant if it is greater than zero. If // zero, calculate colors based on bitsperpixel. int nNumColors = 0; if (nclrused > 0) { nNumColors = nclrused; } else { nNumColors = (1&0xff)<<nbitcount; } System.out.println("The number of Colors is"+nNumColors); // Some bitmaps do not have the sizeimage field calculated // Ferret out these cases and fix 'em. if (nsizeimage == 0) { nsizeimage = ((((nwidth*nbitcount)+31) & 31 ) >> 3); nsizeimage *= nheight; System.out.println("nsizeimage (backup) is"+nsizeimage); } // Read the palatte colors. int npalette[] = new int [nNumColors]; byte bpalette[] = new byte [nNumColors*4]; fs.read (bpalette, 0, nNumColors*4); int nindex8 = 0; for (int n = 0; n < nNumColors; n++) { npalette[n] = (255&0xff)<<24 | (((int)bpalette[nindex8+2]&0xff)<<16) | (((int)bpalette[nindex8+1]&0xff)<<8) | (int)bpalette[nindex8]&0xff; // System.out.println ("Palette Color "+n +" is:"+npalette[n]+" (res,R,G,B)= (" +((int)(bpalette[nindex8+3]) & 0xff)+"," +((int)(bpalette[nindex8+2]) & 0xff)+"," +((int)bpalette[nindex8+1]&0xff)+"," +((int)bpalette[nindex8]&0xff)+")"); nindex8 += 4; } // Read the image data (actually indices into the palette) // Scan lines are still padded out to even 4-byte // boundaries. int npad8 = (nsizeimage / nheight) - nwidth; System.out.println("nPad is:"+npad8); int ndata8[] = new int [nwidth*nheight]; byte bdata[] = new byte [(nwidth+npad8)*nheight]; fs.read (bdata, 0, (nwidth+npad8)*nheight); nindex8 = 0; for (int j8 = 0; j8 < nheight; j8++) { for (int i8 = 0; i8 < nwidth; i8++) { ndata8 [nwidth*(nheight-j8-1)+i8] = npalette [((int)bdata[nindex8]&0xff)]; nindex8++; } nindex8 += npad8; } image = createImage ( new MemoryImageSource (nwidth, nheight, ndata8, 0, nwidth)); } else { System.out.println ("Not a 24-bit or 8-bit Windows Bitmap, aborting..."); image = (Image)null; } fs.close(); return image; } catch (Exception e) { System.out.println("Caught exception in loadbitmap!"); } return (Image) null; }