1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.photos.data;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.Point;
21 import android.util.Pools.Pool;
22 import android.util.Pools.SynchronizedPool;
23 
24 import com.android.photos.data.SparseArrayBitmapPool.Node;
25 
26 /**
27  * Pool allowing the efficient reuse of bitmaps in order to avoid long
28  * garbage collection pauses.
29  */
30 public class GalleryBitmapPool {
31 
32     private static final int CAPACITY_BYTES = 20971520;
33 
34     // We found that Gallery uses bitmaps that are either square (for example,
35     // tiles of large images or square thumbnails), match one of the common
36     // photo aspect ratios (4x3, 3x2, or 16x9), or, less commonly, are of some
37     // other aspect ratio. Taking advantage of this information, we use 3
38     // SparseArrayBitmapPool instances to back the GalleryBitmapPool, which affords
39     // O(1) lookups for square bitmaps, and average-case - but *not* asymptotically -
40     // O(1) lookups for common photo aspect ratios and other miscellaneous aspect
41     // ratios. Beware of the pathological case where there are many bitmaps added
42     // to the pool with different non-square aspect ratios but the same width, as
43     // performance will degrade and the average case lookup will approach
44     // O(# of different aspect ratios).
45     private static final int POOL_INDEX_NONE = -1;
46     private static final int POOL_INDEX_SQUARE = 0;
47     private static final int POOL_INDEX_PHOTO = 1;
48     private static final int POOL_INDEX_MISC = 2;
49 
50     private static final Point[] COMMON_PHOTO_ASPECT_RATIOS =
51         { new Point(4, 3), new Point(3, 2), new Point(16, 9) };
52 
53     private int mCapacityBytes;
54     private SparseArrayBitmapPool [] mPools;
55     private Pool<Node> mSharedNodePool = new SynchronizedPool<Node>(128);
56 
GalleryBitmapPool(int capacityBytes)57     private GalleryBitmapPool(int capacityBytes) {
58         mPools = new SparseArrayBitmapPool[3];
59         mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
60         mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
61         mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
62         mCapacityBytes = capacityBytes;
63     }
64 
65     private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
66 
getInstance()67     public static GalleryBitmapPool getInstance() {
68         return sInstance;
69     }
70 
getPoolForDimensions(int width, int height)71     private SparseArrayBitmapPool getPoolForDimensions(int width, int height) {
72         int index = getPoolIndexForDimensions(width, height);
73         if (index == POOL_INDEX_NONE) {
74             return null;
75         } else {
76             return mPools[index];
77         }
78     }
79 
getPoolIndexForDimensions(int width, int height)80     private int getPoolIndexForDimensions(int width, int height) {
81         if (width <= 0 || height <= 0) {
82             return POOL_INDEX_NONE;
83         }
84         if (width == height) {
85             return POOL_INDEX_SQUARE;
86         }
87         int min, max;
88         if (width > height) {
89             min = height;
90             max = width;
91         } else {
92             min = width;
93             max = height;
94         }
95         for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) {
96             if (min * ar.x == max * ar.y) {
97                 return POOL_INDEX_PHOTO;
98             }
99         }
100         return POOL_INDEX_MISC;
101     }
102 
103     /**
104      * @return Capacity of the pool in bytes.
105      */
getCapacity()106     public synchronized int getCapacity() {
107         return mCapacityBytes;
108     }
109 
110     /**
111      * @return Approximate total size in bytes of the bitmaps stored in the pool.
112      */
getSize()113     public int getSize() {
114         // Note that this only returns an approximate size, since multiple threads
115         // might be getting and putting Bitmaps from the pool and we lock at the
116         // sub-pool level to avoid unnecessary blocking.
117         int total = 0;
118         for (SparseArrayBitmapPool p : mPools) {
119             total += p.getSize();
120         }
121         return total;
122     }
123 
124     /**
125      * @return Bitmap from the pool with the desired height/width or null if none available.
126      */
get(int width, int height)127     public Bitmap get(int width, int height) {
128         SparseArrayBitmapPool pool = getPoolForDimensions(width, height);
129         if (pool == null) {
130             return null;
131         } else {
132             return pool.get(width, height);
133         }
134     }
135 
136     /**
137      * Adds the given bitmap to the pool.
138      * @return Whether the bitmap was added to the pool.
139      */
put(Bitmap b)140     public boolean put(Bitmap b) {
141         if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) {
142             return false;
143         }
144         SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight());
145         if (pool == null) {
146             b.recycle();
147             return false;
148         } else {
149             return pool.put(b);
150         }
151     }
152 
153     /**
154      * Empty the pool, recycling all the bitmaps currently in it.
155      */
clear()156     public void clear() {
157         for (SparseArrayBitmapPool p : mPools) {
158             p.clear();
159         }
160     }
161 }
162