一个Java写的用来构建金字塔影像的Bitmap类
cheungmine
2012
下面每个图像都是256x256像素。目的就是把这4幅影像合成一个256x256的图像,即:
Ln+1 = Fn(00, 01, 10, 11);
Ln+1表示第n+1层金字塔图像块。它是在第n层金字塔的基础上创建的。
处理前的第n层金字塔(4个瓦片):
00 | 01
----------
10 | 11
处理后的第n+1层金字塔(1个瓦片):
(1) 最邻近点采样得到的 (2) 4像素取平均值得到的(双线性差值得特例)
我写的Java源代码:
/******************************************************************************
* BitmapDecoder.java
* Read and Write DIB bitmap:
* only 24-bits supported currently.
* Author:
* cheungmine
*****************************************************************************/
//package imagery.bitmap;
import java.io.Closeable;
import java.lang.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// Note: Bitmap is always LittleEndian
// JVM is always BigEndian
public class BitmapDecoder {
private static final int BMPFILE_HEADER_SIZE = 54;
private static final short BMPFILE_TAG_BM = 0x4D42;
// BITMAPFILEHEADER
//
private short tagBM; // 0x4D42
private int bfSize; // bytes size of file
private short bfReserved1; // 0
private short bfReserved2; // 0
private int bfOffBits; // offset from begin to image data
// BITMAPINFOHEADER
//
private int biSize; // size of info block
private int width; // width pixels
private int height; // height pixels
private short biPlanes; // plane == 1
private short biBitCount; // color bits (1, 4, 8, 24, 32)
private int biCompression; // compression mode: 0 - not; 1 - 8bits; 2 - 4bits
private int biSizeImage; // images data size = 4*n
private int biXPelsPerMeter; // horizontal pixels per meter, in DIB is 00H
private int biYPelsPerMeter; // vertical pixels per meter, in DIB is 00H
private int biClrUsed; // 0
private int biClrImportant; // 0
// RGBQUADs: ignored by 24, 32 bits bitmap
// sizeof(RGBQUAD)==4
// biBitCount = 1, 4, 8
// total RGBQUADs size = sizeof(RGBQUAD)*(2^biBitCount). bytes
private byte [] _RGBQUADs = null;
// file handle
//
private FileInputStream _inStream = null;
private int _widthBytes = 0;
private byte [] _header = null;
private static short bytes2short(int _0, int _1) {
return (short)(((_1<<8)&0xff00) | (_0&0xff));
}
private static int bytes2int(int _0, int _1, int _2, int _3) {
return (int)(((_3<<24)&0xff000000)|((_2<<16)&0xff0000)|((_1<<8)&0xff00)|(_0&0xff));
}
private static void closeStream(Closeable stream) {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
throw new ImageryException(e);
} catch (Exception e) {
throw new ImageryException(e);
}
}
private short readShort(byte [] b, int offset) {
return bytes2short(b[offset], b[offset+1]);
}
private int readInt(byte [] b, int offset) {
return bytes2int(b[offset], b[offset+1], b[offset+2], b[offset+3]);
}
private void readBitmapFileHeader(FileInputStream stream) {
try {
byte [] header = new byte[BMPFILE_HEADER_SIZE];
if (BMPFILE_HEADER_SIZE != stream.read(header)) {
throw new ImageryException(new BitmapReadException("Bitmap header not found"));
}
int pos = 0;
tagBM = readShort(header, pos);
pos += 2;
if (tagBM != BMPFILE_TAG_BM) {
throw new ImageryException(new BitmapReadException("BM tag not found"));
}
bfSize = readInt(header, pos);
pos += 4;
bfReserved1 = readShort(header, pos);
pos += 2;
bfReserved2 = readShort(header, pos);
pos += 2;
bfOffBits = readInt(header, pos);
pos += 4;
biSize = readInt(header, pos);
pos += 4;
width = readInt(header, pos);
pos += 4;
height = readInt(header, pos);
pos += 4;
biPlanes = readShort(header, pos);
pos += 2;
biBitCount = readShort(header, pos);
pos += 2;
biCompression = readInt(header, pos);
pos += 4;
biSizeImage = readInt(header, pos);
pos += 4;
biXPelsPerMeter= readInt(header, pos);
pos += 4;
biYPelsPerMeter= readInt(header, pos);
pos += 4;
biClrUsed= readInt(header, pos);
pos += 4;
biClrImportant= readInt(header, pos);
pos += 4;
if (pos != BMPFILE_HEADER_SIZE) {
throw new RuntimeException("Application has error");
}
_widthBytes = calcWidthBytes();
_header = header;
} catch (IOException e) {
throw new ImageryException(e);
}
}
private void readBitmapRGBQUADs(FileInputStream stream) throws BitmapReadException {
if (biClrUsed > 0) {
_RGBQUADs = new byte[4*biClrUsed];
try {
if (4*biClrUsed != stream.read(_RGBQUADs)) {
throw new BitmapReadException("Read RGBQUAD error.");
}
} catch (IOException e) {
throw new ImageryException(e);
}
}
}
private int calcWidthBytes() {
int widthBytes = 0;
switch (biBitCount) {
case 1: // 2-colors
widthBytes = (width + 7)/8;
break;
case 4: // 16-colors
widthBytes = (width+1)/2;
break;
case 8: // 256-colors
widthBytes = width;
break;
case 24: // 24-bits true colors:rgb
widthBytes = width*3;
break;
case 32: // 32 bits true colors: rgba
widthBytes = width*4;
break;
}
return ((widthBytes+3)/4)*4;
}
public BitmapDecoder(String bmpPathfile) {
try {
FileInputStream stream = new FileInputStream(new File(bmpPathfile));
readBitmapFileHeader(stream);
readBitmapRGBQUADs(stream);
_inStream = stream;
} catch (NullPointerException e) {
throw new ImageryException(e);
} catch (FileNotFoundException e) {
throw new ImageryException(e);
} catch (SecurityException e) {
throw new ImageryException(e);
} catch (IOException e) {
throw new ImageryException(e);
} catch (Exception e) {
throw new ImageryException(e);
}
}
public BitmapDecoder(File fileBmp) {
try {
FileInputStream stream = new FileInputStream(fileBmp);
readBitmapFileHeader(stream);
readBitmapRGBQUADs(stream);
_inStream = stream;
} catch (NullPointerException e) {
throw new ImageryException(e);
} catch (FileNotFoundException e) {
throw new ImageryException(e);
} catch (SecurityException e) {
throw new ImageryException(e);
} catch (IOException e) {
throw new ImageryException(e);
} catch (Exception e) {
throw new ImageryException(e);
}
}
public void open(String bmpPathfile) {
close();
try {
FileInputStream stream = new FileInputStream(new File(bmpPathfile));
readBitmapFileHeader(stream);
readBitmapRGBQUADs(stream);
_inStream = stream;
} catch (NullPointerException e) {
throw new ImageryException(e);
} catch (FileNotFoundException e) {
throw new ImageryException(e);
} catch (SecurityException e) {
throw new ImageryException(e);
} catch (IOException e) {
throw new ImageryException(e);
} catch (Exception e) {
throw new ImageryException(e);
}
}
public void close() {
FileInputStream stream = _inStream;
_inStream = null;
closeStream(stream);
}
public FileInputStream getStream() {
return _inStream;
}
public int readBytes(byte [] b, int len) {
try {
return _inStream.read(b, 0, len);
} catch (IOException e) {
throw new ImageryException(e);
}
}
public int readLines(byte [] b, int numLines) {
try {
return _inStream.read(b, 0, _widthBytes*numLines);
} catch (IOException e) {
throw new ImageryException(e);
}
}
public int getSizeImage() {
return biSizeImage;
}
public int getColorBits() {
return biBitCount;
}
public int getWidthPixels() {
return width;
}
public int getHeightPixels() {
return height;
}
public int getWidthBytes() {
return _widthBytes;
}
public byte [] getBmpHeader() {
return _header;
}
public void printBmpHeader() {
// BITMAPINFOHEADER
//
System.out.println("----------- BITMAPFILEHEADER -----------");
System.out.println("file tag: " + tagBM);
System.out.println("bytes size of file: " + bfSize);
System.out.println("bfReserved1: " + bfReserved1);
System.out.println("bfReserved2: " + bfReserved2);
System.out.println("image data offset: " + bfOffBits);
// BITMAPINFOHEADER
System.out.println("----------- BITMAPINFOHEADER -----------");
System.out.println("size of info block: " + biSize);
System.out.println("width pixels: " + width);
System.out.println("height pixels: " + height);
System.out.println("bit plane: " + biPlanes);
System.out.println("color bits: " + biBitCount);
System.out.println("compression mode: " + biCompression);
System.out.println("images data size bytes: "+ biSizeImage);
System.out.println("horizontal pixels per meter: " + biXPelsPerMeter);
System.out.println("vertical pixels per meter: " + biYPelsPerMeter);
System.out.println("colors Used: " + biClrUsed);
System.out.println("colors Important: " + biClrImportant);
// RGBQUAD
if (_RGBQUADs==null) {
System.out.println("----------- RGBQUAD NOT FOUND -----------");
}
else {
System.out.println("----------- RGBQUAD TABLE -----------");
for (int i=0; i<biClrUsed; i++) {
System.out.println(i+":("+
(_RGBQUADs[i*4+0]&0xff)+","+
(_RGBQUADs[i*4+1]&0xff)+","+
(_RGBQUADs[i*4+2]&0xff)+","+
(_RGBQUADs[i*4+3]&0xff)+")");
}
}
}
/**
* ------------
* | 00 | 01 | --------
* ------------ => | |
* | 10 | 11 | --------
* ------------
*/
private static void calcPixel(byte [] dst, int off,
byte [] src, int off_00, int off_01, int off_10, int off_11) {
int b00 = src[off_00+0]&0xff; int b01 = src[off_01+0]&0xff;
int g00 = src[off_00+1]&0xff; int g01 = src[off_01+1]&0xff;
int r00 = src[off_00+2]&0xff; int r01 = src[off_01+2]&0xff;
int b10 = src[off_10+0]&0xff; int b11 = src[off_11+0]&0xff;
int g10 = src[off_10+1]&0xff; int g11 = src[off_11+1]&0xff;
int r10 = src[off_10+2]&0xff; int r11 = src[off_11+2]&0xff;
dst[off+0] = (byte)((b00+b01+b10+b11)/4);
dst[off+1] = (byte)((g00+g01+g10+g11)/4);
dst[off+2] = (byte)((r00+r01+r10+r11)/4);
}
public static void buildPyramid(File outputBmp, int bmpWidthPixels, int bmpHeightPixels,
BitmapDecoder _00, BitmapDecoder _01,
BitmapDecoder _10, BitmapDecoder _11) throws BitmapReadException {
if (_00.getWidthPixels() != _01.getWidthPixels() ||
_10.getWidthPixels() != _11.getWidthPixels() ||
_00.getWidthPixels() != _11.getWidthPixels() ||
_00.getHeightPixels() != _01.getHeightPixels() ||
_10.getHeightPixels() != _11.getHeightPixels() ||
_00.getHeightPixels() != _11.getHeightPixels()) {
throw new BitmapReadException("Bitmaps size not matched.");
}
if (_00.getColorBits() != _01.getColorBits() ||
_10.getColorBits() != _11.getColorBits() ||
_00.getColorBits() != _11.getColorBits()) {
throw new BitmapReadException("Bitmaps colorbits not matched.");
}
if (bmpWidthPixels != bmpHeightPixels) {
//??TODO: Should we support a rectangle one
throw new BitmapReadException("Width not equal with height for output bitmap.");
}
if (bmpWidthPixels != _00.getWidthPixels() ||
bmpHeightPixels != _00.getHeightPixels()) {
throw new BitmapReadException("Bitmap not square.");
}
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(outputBmp);
outStream.write(_00.getBmpHeader());
int el = bmpWidthPixels; // length of edge
int bpp = _00.getColorBits()/8; // Bytes Per Pixel
int wb = _00.getWidthBytes(); // Width Bytes
byte [] rb1 = new byte[wb*2]; // 2-lines buffer
byte [] rb2 = new byte[wb*2]; // 2-lines buffer
byte [] dst = new byte[wb]; // 1-lines buffer
int i, n, r1, r2;
while ((r1=_10.readLines(rb1, 2))==wb*2 && (r2=_11.readLines(rb2, 2))==wb*2) {
for (n=0; n<el/2; n++) {
i = n*2*bpp; // source
calcPixel(dst, n*bpp, rb1, i, i+bpp, wb+i, wb+i+bpp);
}
for (n=el/2; n<el; n++) {
i = (n-el/2)*2*bpp; // source
calcPixel(dst, n*bpp, rb2, i, i+bpp, wb+i, wb+i+bpp);
}
outStream.write(dst);
}
while ((r1=_00.readLines(rb1, 2))==wb*2 && (r2=_01.readLines(rb2, 2))==wb*2) {
for (n=0; n<el/2; n++) {
i = n*2*bpp; // source
calcPixel(dst, n*bpp, rb1, i, i+bpp, wb+i, wb+i+bpp);
}
for (n=el/2; n<el; n++) {
i = (n-el/2)*2*bpp; // source
calcPixel(dst, n*bpp, rb2, i, i+bpp, wb+i, wb+i+bpp);
}
outStream.write(dst);
}
} catch (IOException e) {
throw new ImageryException(e);
} finally {
closeStream(outStream);
}
}
}
/******************************************************************************
* ImageryException.java
*
*****************************************************************************/
//package imagery.bitmap;
import java.io.*;
class ImageryException extends RuntimeException {
private final String _stackTrace;
public Exception originalException;
public ImageryException(Exception e) {
super(e.toString());
originalException = e;
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
_stackTrace = sw.toString();
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(java.io.PrintStream s) {
synchronized(s) {
s.print(getClass().getName() + ": ");
s.print(_stackTrace);
}
}
public void printStackTrace(java.io.PrintWriter s) {
synchronized(s) {
s.print(getClass().getName() + ": ");
s.print(_stackTrace);
}
}
public void rethrow() throws Throwable {
throw originalException;
}
}
/******************************************************************************
* BitmapReadException.java
*
*****************************************************************************/
//package imagery.bitmap;
import java.io.*;
public class BitmapReadException extends Exception {
public BitmapReadException() {
super();
}
public BitmapReadException(String message) {
super(message);
}
public BitmapReadException(String message, Throwable cause) {
super(message, cause);
}
public BitmapReadException(Throwable cause) {
super(cause);
}
}
测试代码:
/*
* ------------
* | 00 | 01 | --------
* ------------ => | |
* | 10 | 11 | --------
* ------------
*/
public void testResampleBMP24() throws Exception {
String srcdir = "C:/nv_workspace/zebra/sandbox/mapred/data/";
BitmapDecoder tile_00 = new BitmapDecoder(srcdir+"hadoop_0-0.bmp");
BitmapDecoder tile_01 = new BitmapDecoder(srcdir+"hadoop_0-1.bmp");
BitmapDecoder tile_10 = new BitmapDecoder(srcdir+"hadoop_1-0.bmp");
BitmapDecoder tile_11 = new BitmapDecoder(srcdir+"hadoop_1-1.bmp");
tile_00.printBmpHeader();
assertEquals(tile_00.getColorBits(), 24);
assertEquals(tile_00.getWidthPixels(), 256);
assertEquals(tile_00.getHeightPixels(), 256);
BitmapDecoder.buildPyramid(new File(srcdir+"hadoop_out.bmp"),
tile_00.getWidthPixels(),
tile_00.getHeightPixels(),
tile_00, tile_01,
tile_10, tile_11);
tile_00.close();
tile_01.close();
tile_10.close();
tile_11.close();
}
结论:
需要注意的是,Java的byte是有符号的,而像素的值是无符号的,需要(见calcPixel)
int iv = bv&0xff;
处理以变成无符号的.这个小小的BUG折腾我一天时间.最后把像素打印出来一个个分析才知道原来Java这么恶心.
同样功能的代码在C中需要100行的话,Java里就需要200行.无语啊!
cheungmine