簡體   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