Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 1 | page.title=Loading Large Bitmaps Efficiently |
| 2 | parent.title=Displaying Bitmaps Efficiently |
| 3 | parent.link=index.html |
| 4 | |
| 5 | trainingnavtop=true |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 6 | |
| 7 | @jd:body |
| 8 | |
| 9 | <div id="tb-wrapper"> |
| 10 | <div id="tb"> |
| 11 | |
| 12 | <h2>This lesson teaches you to</h2> |
| 13 | <ol> |
| 14 | <li><a href="#read-bitmap">Read Bitmap Dimensions and Type</a></li> |
| 15 | <li><a href="#load-bitmap">Load a Scaled Down Version into Memory</a></li> |
| 16 | </ol> |
| 17 | |
| 18 | <h2>Try it out</h2> |
| 19 | |
| 20 | <div class="download-box"> |
Adam Koch | 21e3372 | 2014-02-26 14:00:20 +1100 | [diff] [blame] | 21 | <a href="{@docRoot}downloads/samples/DisplayingBitmaps.zip" class="button">Download the sample</a> |
| 22 | <p class="filename">DisplayingBitmaps.zip</p> |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 23 | </div> |
| 24 | |
| 25 | </div> |
| 26 | </div> |
| 27 | |
| 28 | <p>Images come in all shapes and sizes. In many cases they are larger than required for a typical |
| 29 | application user interface (UI). For example, the system Gallery application displays photos taken |
| 30 | using your Android devices's camera which are typically much higher resolution than the screen |
| 31 | density of your device.</p> |
| 32 | |
| 33 | <p>Given that you are working with limited memory, ideally you only want to load a lower resolution |
| 34 | version in memory. The lower resolution version should match the size of the UI component that |
| 35 | displays it. An image with a higher resolution does not provide any visible benefit, but still takes |
| 36 | up precious memory and incurs additional performance overhead due to additional on the fly |
| 37 | scaling.</p> |
| 38 | |
| 39 | <p>This lesson walks you through decoding large bitmaps without exceeding the per application |
| 40 | memory limit by loading a smaller subsampled version in memory.</p> |
| 41 | |
| 42 | <h2 id="read-bitmap">Read Bitmap Dimensions and Type</h2> |
| 43 | |
| 44 | <p>The {@link android.graphics.BitmapFactory} class provides several decoding methods ({@link |
| 45 | android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) |
| 46 | decodeByteArray()}, {@link |
| 47 | android.graphics.BitmapFactory#decodeFile(java.lang.String,android.graphics.BitmapFactory.Options) |
| 48 | decodeFile()}, {@link |
| 49 | android.graphics.BitmapFactory#decodeResource(android.content.res.Resources,int,android.graphics.BitmapFactory.Options) |
| 50 | decodeResource()}, etc.) for creating a {@link android.graphics.Bitmap} from various sources. Choose |
| 51 | the most appropriate decode method based on your image data source. These methods attempt to |
| 52 | allocate memory for the constructed bitmap and therefore can easily result in an {@code OutOfMemory} |
| 53 | exception. Each type of decode method has additional signatures that let you specify decoding |
| 54 | options via the {@link android.graphics.BitmapFactory.Options} class. Setting the {@link |
| 55 | android.graphics.BitmapFactory.Options#inJustDecodeBounds} property to {@code true} while decoding |
| 56 | avoids memory allocation, returning {@code null} for the bitmap object but setting {@link |
| 57 | android.graphics.BitmapFactory.Options#outWidth}, {@link |
| 58 | android.graphics.BitmapFactory.Options#outHeight} and {@link |
| 59 | android.graphics.BitmapFactory.Options#outMimeType}. This technique allows you to read the |
| 60 | dimensions and type of the image data prior to construction (and memory allocation) of the |
| 61 | bitmap.</p> |
| 62 | |
| 63 | <pre> |
| 64 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 65 | options.inJustDecodeBounds = true; |
| 66 | BitmapFactory.decodeResource(getResources(), R.id.myimage, options); |
| 67 | int imageHeight = options.outHeight; |
| 68 | int imageWidth = options.outWidth; |
| 69 | String imageType = options.outMimeType; |
| 70 | </pre> |
| 71 | |
| 72 | <p>To avoid {@code java.lang.OutOfMemory} exceptions, check the dimensions of a bitmap before |
| 73 | decoding it, unless you absolutely trust the source to provide you with predictably sized image data |
| 74 | that comfortably fits within the available memory.</p> |
| 75 | |
| 76 | <h2 id="load-bitmap">Load a Scaled Down Version into Memory</h2> |
| 77 | |
| 78 | <p>Now that the image dimensions are known, they can be used to decide if the full image should be |
| 79 | loaded into memory or if a subsampled version should be loaded instead. Here are some factors to |
| 80 | consider:</p> |
| 81 | |
| 82 | <ul> |
| 83 | <li>Estimated memory usage of loading the full image in memory.</li> |
| 84 | <li>Amount of memory you are willing to commit to loading this image given any other memory |
| 85 | requirements of your application.</li> |
| 86 | <li>Dimensions of the target {@link android.widget.ImageView} or UI component that the image |
| 87 | is to be loaded into.</li> |
| 88 | <li>Screen size and density of the current device.</li> |
| 89 | </ul> |
| 90 | |
| 91 | <p>For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be |
| 92 | displayed in a 128x96 pixel thumbnail in an {@link android.widget.ImageView}.</p> |
| 93 | |
| 94 | <p>To tell the decoder to subsample the image, loading a smaller version into memory, set {@link |
| 95 | android.graphics.BitmapFactory.Options#inSampleSize} to {@code true} in your {@link |
| 96 | android.graphics.BitmapFactory.Options} object. For example, an image with resolution 2048x1536 that |
| 97 | is decoded with an {@link android.graphics.BitmapFactory.Options#inSampleSize} of 4 produces a |
| 98 | bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full |
| 99 | image (assuming a bitmap configuration of {@link android.graphics.Bitmap.Config ARGB_8888}). Here’s |
Adam Koch | 3a4a1d7 | 2013-10-25 15:28:41 -0400 | [diff] [blame] | 100 | a method to calculate a sample size value that is a power of two based on a target width and |
| 101 | height:</p> |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 102 | |
| 103 | <pre> |
| 104 | public static int calculateInSampleSize( |
| 105 | BitmapFactory.Options options, int reqWidth, int reqHeight) { |
| 106 | // Raw height and width of image |
| 107 | final int height = options.outHeight; |
| 108 | final int width = options.outWidth; |
| 109 | int inSampleSize = 1; |
| 110 | |
Adam Koch | 3a4a1d7 | 2013-10-25 15:28:41 -0400 | [diff] [blame] | 111 | if (height > reqHeight || width > reqWidth) { |
Adam Koch | 0bb4dad | 2013-01-09 16:58:59 -0500 | [diff] [blame] | 112 | |
Adam Koch | 3a4a1d7 | 2013-10-25 15:28:41 -0400 | [diff] [blame] | 113 | final int halfHeight = height / 2; |
| 114 | final int halfWidth = width / 2; |
Adam Koch | 0bb4dad | 2013-01-09 16:58:59 -0500 | [diff] [blame] | 115 | |
Adam Koch | 3a4a1d7 | 2013-10-25 15:28:41 -0400 | [diff] [blame] | 116 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both |
| 117 | // height and width larger than the requested height and width. |
| 118 | while ((halfHeight / inSampleSize) > reqHeight |
| 119 | && (halfWidth / inSampleSize) > reqWidth) { |
| 120 | inSampleSize *= 2; |
| 121 | } |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 122 | } |
Adam Koch | 0bb4dad | 2013-01-09 16:58:59 -0500 | [diff] [blame] | 123 | |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 124 | return inSampleSize; |
| 125 | } |
| 126 | </pre> |
| 127 | |
Adam Koch | 3a4a1d7 | 2013-10-25 15:28:41 -0400 | [diff] [blame] | 128 | <p class="note"><strong>Note:</strong> A power of two value is calculated because the decoder uses |
| 129 | a final value by rounding down to the nearest power of two, as per the {@link |
| 130 | android.graphics.BitmapFactory.Options#inSampleSize} documentation.</p> |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 131 | |
| 132 | <p>To use this method, first decode with {@link |
| 133 | android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code true}, pass the options |
| 134 | through and then decode again using the new {@link |
| 135 | android.graphics.BitmapFactory.Options#inSampleSize} value and {@link |
| 136 | android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code false}:</p> |
| 137 | |
| 138 | <a name="decodeSampledBitmapFromResource"></a> |
| 139 | <pre> |
| 140 | public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, |
| 141 | int reqWidth, int reqHeight) { |
| 142 | |
| 143 | // First decode with inJustDecodeBounds=true to check dimensions |
| 144 | final BitmapFactory.Options options = new BitmapFactory.Options(); |
| 145 | options.inJustDecodeBounds = true; |
| 146 | BitmapFactory.decodeResource(res, resId, options); |
| 147 | |
| 148 | // Calculate inSampleSize |
| 149 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); |
| 150 | |
| 151 | // Decode bitmap with inSampleSize set |
| 152 | options.inJustDecodeBounds = false; |
| 153 | return BitmapFactory.decodeResource(res, resId, options); |
| 154 | } |
| 155 | </pre> |
| 156 | |
| 157 | <p>This method makes it easy to load a bitmap of arbitrarily large size into an {@link |
| 158 | android.widget.ImageView} that displays a 100x100 pixel thumbnail, as shown in the following example |
| 159 | code:</p> |
| 160 | |
| 161 | <pre> |
| 162 | mImageView.setImageBitmap( |
| 163 | decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); |
| 164 | </pre> |
| 165 | |
| 166 | <p>You can follow a similar process to decode bitmaps from other sources, by substituting the |
| 167 | appropriate {@link |
| 168 | android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) |
kmccormick | 7571542 | 2013-03-04 15:25:40 -0800 | [diff] [blame] | 169 | BitmapFactory.decode*} method as needed.</p> |