繁体   English   中英

将图像合并到一个文件中,而无需在内存中创建完整图像

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM