[英]Merge images into one file without creating the full image in memory
我需要在 Android 中“分块处理”一些任意大小的位图。 我可以轻松地使用 BitmapRegionDecoder 创建较小的块进行处理,但是一旦完成,我需要将它们重新组合成一个文件。 由于最终图像可以是任意大小,因此不能在内存中创建相应的位图并使用画布在其上书写。
我看过这个线程,但我想在没有外部库的情况下做到这一点(我在使用 Android 时受到这些限制)。 理想情况下,我追求某种 BitmapRegionEncoder。 只要是图像(PNG、JPG 甚至 BMP),我就不太关心输出格式。 如果在 Java 中不可能,我也很高兴使用 JNI 在 C 中完成。
一个简单的方法是将块存储在一个表结构中,然后读回它们并将数据写回文件作为图像文件。
这是一个示例表结构。
public class Chunk
{
private int chunkId;
private byte[] chunkdata;
private int nextchunkId;
}
从表中读取块的方法
private Chunk getChunk(int index){
Chunk chunk = null;
if(index == 1){ // this assumes that the chunk id starts from 1
//get and return Chunk where chunkId == 1 from the table
}
else{
// get and return Chunk where nextchunkId == index from the table
}
return chunk
}
现在将块直接写入二进制文件
private void mergeChunksToFile(){
int index = 1; // this assumes that the chunk id starts from 1
// Create a binary file in append mode to store the data, which is the image
Chunk chunk = getChunk(index);
while(chunk != null){
// Here, write chunk.chunkdata to the binary file
index = chunk.nextchunkId;
// get the next chunk
chunk = getChunk(index);
}
}
这可能不是最好的解决方案,但它应该可以帮助您了解如何在不使用任何外部库的情况下进行操作
我最终做了什么:
public class BitmapChunkWriter {
private static final int BMP_WIDTH_OF_TIMES = 4;
private static final int BYTE_PER_PIXEL = 3;
private FileOutputStream fos;
private byte[] dummyBytesPerRow;
private boolean hasDummy;
private int imageSize;
private int fileSize;
private int rowWidthInBytes;
/**
* Android Bitmap Object to Window's v3 24bit Bmp Format File
* @param imageWidth
* @param imageHeight
* @param filePath
* @return file saved result
*/
public void writeHeader(int imageWidth, int imageHeight, String filePath) throws IOException {
//image dummy data size
//reason : the amount of bytes per image row must be a multiple of 4 (requirements of bmp format)
dummyBytesPerRow = null;
hasDummy = false;
rowWidthInBytes = BYTE_PER_PIXEL * imageWidth; //source image width * number of bytes to encode one pixel.
if (rowWidthInBytes % BMP_WIDTH_OF_TIMES > 0) {
hasDummy = true;
//the number of dummy bytes we need to add on each row
dummyBytesPerRow = new byte[(BMP_WIDTH_OF_TIMES - (rowWidthInBytes % BMP_WIDTH_OF_TIMES))];
//just fill an array with the dummy bytes we need to append at the end of each row
for (int i = 0; i < dummyBytesPerRow.length; i++) {
dummyBytesPerRow[i] = (byte) 0xFF;
}
}
//the number of bytes used in the file to store raw image data (excluding file headers)
imageSize = (rowWidthInBytes + (hasDummy ? dummyBytesPerRow.length : 0)) * imageHeight;
//file headers size
int imageDataOffset = 0x36;
//final size of the file
fileSize = imageSize + imageDataOffset;
//ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
ByteBuffer buffer = ByteBuffer.allocate(imageDataOffset);
/**
* BITMAP FILE HEADER Write Start
**/
buffer.put((byte) 0x42);
buffer.put((byte) 0x4D);
//size
buffer.put(writeInt(fileSize));
//reserved
buffer.put(writeShort((short) 0));
buffer.put(writeShort((short) 0));
//image data start offset
buffer.put(writeInt(imageDataOffset));
/** BITMAP FILE HEADER Write End */
//*******************************************
/** BITMAP INFO HEADER Write Start */
//size
buffer.put(writeInt(0x28));
//width, height
//if we add 3 dummy bytes per row : it means we add a pixel (and the image width is modified.
buffer.put(writeInt(imageWidth + (hasDummy ? (dummyBytesPerRow.length == 3 ? 1 : 0) : 0)));
buffer.put(writeInt(imageHeight));
//planes
buffer.put(writeShort((short) 1));
//bit count
buffer.put(writeShort((short) 24));
//bit compression
buffer.put(writeInt(0));
//image data size
buffer.put(writeInt(imageSize));
//horizontal resolution in pixels per meter
buffer.put(writeInt(0));
//vertical resolution in pixels per meter (unreliable)
buffer.put(writeInt(0));
buffer.put(writeInt(0));
buffer.put(writeInt(0));
fos = new FileOutputStream(filePath);
fos.write(buffer.array());
}
public void writeChunk(Bitmap bitmap) throws IOException {
int chunkWidth = bitmap.getWidth();
int chunkHeight = bitmap.getHeight();
//an array to receive the pixels from the source image
int[] pixels = new int[chunkWidth * chunkHeight];
//Android Bitmap Image Data
bitmap.getPixels(pixels, 0, chunkWidth, 0, 0, chunkWidth, chunkHeight);
//the number of bytes used in the file to store raw image data (excluding file headers)
//int imageSize = (rowWidthInBytes + (hasDummy ? dummyBytesPerRow.length : 0)) * height;
int chunkSize = (rowWidthInBytes + (hasDummy ? dummyBytesPerRow.length : 0)) * chunkHeight;
ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
int row = chunkHeight;
int col = chunkWidth;
int startPosition = (row - 1) * col;
int endPosition = row * col;
while( row > 0 ){
for(int i = startPosition; i < endPosition; i++ ){
buffer.put((byte)(pixels[i] & 0x000000FF));
buffer.put((byte)((pixels[i] & 0x0000FF00) >> 8));
buffer.put((byte)((pixels[i] & 0x00FF0000) >> 16));
}
if(hasDummy){
buffer.put(dummyBytesPerRow);
}
row--;
endPosition = startPosition;
startPosition = startPosition - col;
}
fos.write(buffer.array());
}
public void finish() throws IOException {
fos.close();
}
/**
* Write integer to little-endian
* @param value
* @return
* @throws IOException
*/
private static byte[] writeInt(int value) throws IOException {
byte[] b = new byte[4];
b[0] = (byte)(value & 0x000000FF);
b[1] = (byte)((value & 0x0000FF00) >> 8);
b[2] = (byte)((value & 0x00FF0000) >> 16);
b[3] = (byte)((value & 0xFF000000) >> 24);
return b;
}
/**
* Write short to little-endian byte array
* @param value
* @return
* @throws IOException
*/
private static byte[] writeShort(short value) throws IOException {
byte[] b = new byte[2];
b[0] = (byte)(value & 0x00FF);
b[1] = (byte)((value & 0xFF00) >> 8);
return b;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.