将图像合并到一个文件中,而无需在内存中创建完整图像
Merge images into one file without creating the full image in memory
我需要在 Android 中 "chunk process" 一些任意大小的位图。我可以轻松地使用 BitmapRegionDecoder 创建较小的块进行处理,但一旦完成,我需要将它们重新组合到一个文件中。因为最终图像可以是任意大小,所以在内存中创建相应的位图并使用 Canvas 写入它不是一个选项。
我看过 this thread 但我想在没有外部库的情况下执行此操作(我使用 Android 时受限于这些)。理想情况下,我正在寻找某种 BitmapRegionEncoder。只要它是图像(PNG、JPG 甚至 BMP),我并不真正关心输出格式。如果在 Java.
中不可能,我也很乐意使用 JNI 在 C 中完成它
一个简单的方法是将块存储在 table 结构中,然后将它们读回并将数据写回文件作为图像文件。
这是一个示例 table 结构。
public class Chunk
{
private int chunkId;
private byte[] chunkdata;
private int nextchunkId;
}
从 table
中读取块的方法
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;
}
}
我需要在 Android 中 "chunk process" 一些任意大小的位图。我可以轻松地使用 BitmapRegionDecoder 创建较小的块进行处理,但一旦完成,我需要将它们重新组合到一个文件中。因为最终图像可以是任意大小,所以在内存中创建相应的位图并使用 Canvas 写入它不是一个选项。
我看过 this thread 但我想在没有外部库的情况下执行此操作(我使用 Android 时受限于这些)。理想情况下,我正在寻找某种 BitmapRegionEncoder。只要它是图像(PNG、JPG 甚至 BMP),我并不真正关心输出格式。如果在 Java.
中不可能,我也很乐意使用 JNI 在 C 中完成它一个简单的方法是将块存储在 table 结构中,然后将它们读回并将数据写回文件作为图像文件。
这是一个示例 table 结构。
public class Chunk
{
private int chunkId;
private byte[] chunkdata;
private int nextchunkId;
}
从 table
中读取块的方法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;
}
}