14

I have a requirement to display somewhat big images on an Android app. Right now I'm using an ImageView with a source Bitmap. I understand openGL has a certain device-independent limitation as to how big the image dimensions can be in order for it to process it.

Is there ANY way to display these images (with fixed width, without cropping) regardless of this limit, other than splitting the image into multiple ImageView elements?

Thank you.

UPDATE 01 Apr 2013 Still no luck so far all suggestions were to reduce image quality. One suggested it might be possible to bypass this limitation by using the CPU to do the processing instead of using the GPU (though might take more time to process). I don't understand, is there really no way to display long images with a fixed width without reducing image quality? I bet there is, I'd love it if anyone would at least point me to the right direction.

Thanks everyone.

woot
  • 3,671
  • 4
  • 26
  • 39

4 Answers4

10

You can use BitmapRegionDecoder to break apart larger bitmaps (requires API level 10). I've wrote a method that will utilize this class and return a single Drawable that can be placed inside an ImageView:

private static final int MAX_SIZE = 1024;

private Drawable createLargeDrawable(int resId) throws IOException {

    InputStream is = getResources().openRawResource(resId);
    BitmapRegionDecoder brd = BitmapRegionDecoder.newInstance(is, true);

    try {
        if (brd.getWidth() <= MAX_SIZE && brd.getHeight() <= MAX_SIZE) {
            return new BitmapDrawable(getResources(), is);
        }

        int rowCount = (int) Math.ceil((float) brd.getHeight() / (float) MAX_SIZE);
        int colCount = (int) Math.ceil((float) brd.getWidth() / (float) MAX_SIZE);

        BitmapDrawable[] drawables = new BitmapDrawable[rowCount * colCount];

        for (int i = 0; i < rowCount; i++) {

            int top = MAX_SIZE * i;
            int bottom = i == rowCount - 1 ? brd.getHeight() : top + MAX_SIZE;

            for (int j = 0; j < colCount; j++) {

                int left = MAX_SIZE * j;
                int right = j == colCount - 1 ? brd.getWidth() : left + MAX_SIZE;

                Bitmap b = brd.decodeRegion(new Rect(left, top, right, bottom), null);
                BitmapDrawable bd = new BitmapDrawable(getResources(), b);
                bd.setGravity(Gravity.TOP | Gravity.LEFT);
                drawables[i * colCount + j] = bd;
            }
        }

        LayerDrawable ld = new LayerDrawable(drawables);
        for (int i = 0; i < rowCount; i++) {
            for (int j = 0; j < colCount; j++) {
                ld.setLayerInset(i * colCount + j, MAX_SIZE * j, MAX_SIZE * i, 0, 0);
            }
        }

        return ld;
    }
    finally {
        brd.recycle();
    }
}

The method will check to see if the drawable resource is smaller than MAX_SIZE (1024) in both axes. If it is, it just returns the drawable. If it's not, it will break the image apart and decode chunks of the image and place them in a LayerDrawable.

I chose 1024 because I believe most available phones will support images at least that large. If you want to find the actual texture size limit for a phone, you have to do some funky stuff through OpenGL, and it's not something I wanted to dive into.

I wasn't sure how you were accessing your images, so I assumed they were in your drawable folder. If that's not the case, it should be fairly easy to refactor the method to take in whatever parameter you need.

Jason Robinson
  • 31,005
  • 19
  • 77
  • 131
  • It should be noted that this implementation only splits it into rows, and not a grid. If the width is larger than the max size, it'll go through the algorithm but the chunks will still contain widths larger than MAX_SIZE. It's a trivial fix to add support for it though, and I would've never thought of using LayerDrawable to show the grid of drawables. Great answer. – David Liu Jun 04 '13 at 00:52
  • Edited in the fix to split into square chunks instead of just rows. – David Liu Jun 04 '13 at 00:58
  • Thanks for your additions @DavidLiu. There were a few errors with your edits that I took care of. Please review my edits and make sure I didn't break the intention of your edit. The method throws `IOException`, which is why the catch isn't needed. – Jason Robinson Jun 04 '13 at 07:29
  • @Jason Robinson Whoops, yeah. I copy pasted in the modified version I used in my app that didn't throw the IOException. Looks good. – David Liu Jun 04 '13 at 22:00
  • This is indeed a very good solution for the GL texture size limit. Thank you. – MobileCushion Apr 01 '14 at 13:53
  • Is it possible to use this method with a bitmap, if yes how? I've put the code for detecting the max gl texture size here: http://pastebin.com/gFTdHPbg – tim687 Aug 10 '15 at 07:39
  • @tim687 this newInstance (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html#newInstance(byte[], int, int, boolean)) method takes in a byte array. Looks like that should work. Convert a Bitmap to a byte array with [this answer](http://stackoverflow.com/a/4989543/291827). – Jason Robinson Aug 10 '15 at 21:12
  • SO won't let me link it without messing up the link. Go to the [BitmapRegionDecoder](http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) javadocs and find the factory method with a byte array. – Jason Robinson Aug 10 '15 at 21:18
  • @JasonRobinson I eventually used the chunk split method, but my bitmaps aren't drawing. I'll figure that out :-P – tim687 Aug 11 '15 at 07:32
3

You can use BitmapFactoryOptions to reduce size of picture.You can use somthing like that :

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 3; //reduce size 3 times
developerCoder
  • 176
  • 5
  • 16
2

Have you seen how your maps working? I had made a renderer for maps once. You can use same trick to display your image.

Divide your image into square tiles (e.g. of 128x128 pixels). Create custom imageView supporting rendering from tiles. Your imageView knows which part of bitmap it should show now and displays only required tiles loading them from your sd card. Using such tile map you can display endless images.

Leonidos
  • 10,482
  • 2
  • 28
  • 37
0

It would help if you gave us the dimensions of your bitmap.

Please understand that OpenGL runs against natural mathematical limits.

For instance, there is a very good reason a texture in OpenGL must be 2 to the power of x. This is really the only way the math of any downscaling can be done cleanly without any remainder.

So if you give us the exact dimensions of the smallest bitmap that's giving you trouble, some of us may be able to tell you what kind of actual limit you're running up against.

Stephan Branczyk
  • 9,363
  • 2
  • 33
  • 49
  • The dimensions of the bitmap are dynamic, width could be as high as 480px, height could be anything between 1px and 10000px. The desired width to be displayed is the width of the screen minus some margins. The image should be scaled to the desired width while maintaining aspect ratio cropping from top left. – woot Apr 02 '13 at 09:04
  • Then, it's not an OpenGL texture. Don't treat it as that. You can have background images in OpenGL, that's fine, as long as you don't treat them as textures, they'll be fine. – Stephan Branczyk Apr 02 '13 at 09:09
  • What do you mean not treat them as textures? I don't use OpenGL directly, all I do is set a source for an ImageView, then the image doesn't get displayed due to the texture size limit of the device. For example: "Bitmap too large to be uploaded into a texture (480x5400, max=4096x4096) on a Nexus 10". – woot Apr 02 '13 at 11:34
  • Your images might be getting auto-scaled before OpenGL even gets to them. Create a drawable-nodpi folder and move your images into that. By the way, are you planning to use pinch-to-zoom? or some other zoom-like feature? For zooming to work, your images do need to be tiled as squares like Leonidas suggests. Could you show us your code please. Showing us your code should help I believe. – Stephan Branczyk Apr 02 '13 at 19:47