Search code examples
androidbitmaprenderscript

How do I edit and save a large bitmap in Android? Currently writing it out as tiles but it is slow to join them back together


I'm using renderscript on Android to edit photos, currently due to the texture size limit and memory limits on Android the app will crash if I try anything too large eg photos taken with the devices camera.

My first thought to get around this was to use BitmapRegionDecoder and tile the large photo into manageable pieces, edit them through renderscript and save them one at a time, then stitch it all together using PNGJ - a PNG decoding and encoding library that allows writing PNG images to disk in parts so I don't have the full image in memory.

This works fine but stitching it together takes a rather long time - around 1 minute at a guess.

Are there any other solutions I should consider? I can change to JPEG if there is a solution there, but I haven't found it yet. Basically I'm looking for the other side of a BitmapRegionDecoder, a BitmapRegionEncoder.

Just to be clear, I do not want to resize the image.


Solution

    1. Load the image in horizontal stripes using BitmapRegionDecoder. The code below assumes that it is PNG and uses PNGJ to copy the metadata to new image, but adding support for JPEG should not be too difficult.
    2. Process each stripe with Renderscript.
    3. Save it using PNGJ. Do not use high compression or it will slow down to a crawl.

    PNG version of this image (4850x3635px) takes 12 seconds on Nexus 5 with a trivial RS filter (desaturation).

    void processPng(String forig,String fdest) {
        try {
            Allocation inAllocation = null;
            Allocation outAllocation = null;
            final int block_height = 64;
    
            FileInputStream orig = new FileInputStream(forig);
            FileInputStream orig2 = new FileInputStream(forig);
            FileOutputStream dest = new FileOutputStream(fdest);
    
            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(orig, false);
            Rect blockRect = new Rect();
    
            PngReader pngr = new PngReader(orig2);
            PngWriter pngw = new PngWriter(dest, pngr.imgInfo);
            pngw.copyChunksFrom(pngr.getChunksList());
    
            // keep compression quick
            pngw.getPixelsWriter().setDeflaterCompLevel(1);
    
            int channels = 3; // needles to say, this should not be hardcoded
            int width = pngr.imgInfo.samplesPerRow / channels;
            int height = pngr.imgInfo.rows;
    
            pngr.close(); // don't need it anymore
    
            blockRect.left = 0;
            blockRect.right = width;
    
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            Bitmap blockBitmap;
            byte []bytes = new byte[width * block_height * 4];
            byte []byteline = new byte[width * channels];
    
            for (int row = 0; row <= height / block_height; row++) {
                int h;
    
                // are we nearing the end?
                if((row + 1) *  block_height <= height)
                    h = block_height;
                else {
                    h = height - row * block_height;
    
                    // so that new, smaller Allocations are created
                    inAllocation = outAllocation = null;
                }
    
                blockRect.top = row * block_height;
                blockRect.bottom = row * block_height + h;
    
                blockBitmap = decoder.decodeRegion(blockRect, options);
    
                if(inAllocation == null)
                    inAllocation = Allocation.createFromBitmap(mRS, blockBitmap);
    
                if(outAllocation == null)
                {
                    Type.Builder TypeDir = new Type.Builder(mRS, Element.U8_4(mRS));
                    TypeDir.setX(width).setY(h);
    
                    outAllocation = Allocation.createTyped(mRS, TypeDir.create());
                }
    
                inAllocation.copyFrom(blockBitmap);
                mScript.forEach_saturation(inAllocation, outAllocation);
                outAllocation.copyTo(bytes);
    
                int idx = 0;
    
                for(int raster = 0; raster < h; raster++) {
                    for(int m = 0; m < width; m++)
                    {
                        byteline[m * channels] = bytes[idx++];
                        byteline[m * channels + 1] = bytes[idx++];
                        byteline[m * channels + 2] = bytes[idx++];
                        idx++;
                    }
    
                    ImageLineByte line = new ImageLineByte(pngr.imgInfo, byteline);
                    pngw.writeRow(line);
                }
            }
            pngw.end();
        } catch (IOException e)
        {
            Log.d("BIG", "File io problem");
        }
    }