1 /*
2  * Copyright (C) 2018 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 android.util.imagepool;
18 
19 import com.android.tools.layoutlib.annotations.Nullable;
20 
21 import android.util.imagepool.Bucket.BucketCreationMetaData;
22 import android.util.imagepool.ImagePool.Image.Orientation;
23 import android.util.imagepool.ImagePool.ImagePoolPolicy;
24 
25 import java.awt.image.BufferedImage;
26 import java.util.Map;
27 
28 /* private package */ class ImagePoolHelper {
29 
30     @Nullable
getBucketCreationMetaData(int w, int h, int type, ImagePoolPolicy poolPolicy, ImagePoolStats stats)31     public static BucketCreationMetaData getBucketCreationMetaData(int w, int h, int type, ImagePoolPolicy poolPolicy, ImagePoolStats stats) {
32         // Find the bucket sizes for both dimensions
33         int widthBucket = -1;
34         int heightBucket = -1;
35         int index = 0;
36 
37         for (int bucketMinSize : poolPolicy.mBucketSizes) {
38             if (widthBucket == -1 && w <= bucketMinSize) {
39                 widthBucket = bucketMinSize;
40 
41                 if (heightBucket != -1) {
42                     break;
43                 }
44             }
45             if (heightBucket == -1 && h <= bucketMinSize) {
46                 heightBucket = bucketMinSize;
47 
48                 if (widthBucket != -1) {
49                     break;
50                 }
51             }
52             ++index;
53         }
54 
55         stats.recordBucketRequest(w, h);
56 
57         if (index >= poolPolicy.mNumberOfCopies.length) {
58             return null;
59         }
60 
61         // TODO: Apply orientation
62 //        if (widthBucket < heightBucket) {
63 //            return new BucketCreationMetaData(heightBucket, widthBucket, type, poolPolicy.mNumberOfCopies[index],
64 //                    Orientation.CW_90, poolPolicy.mBucketMaxCacheSize);
65 //        }
66         return new BucketCreationMetaData(widthBucket, heightBucket, type, poolPolicy.mNumberOfCopies[index],
67                 Orientation.NONE, poolPolicy.mBucketMaxCacheSize);
68     }
69 
70     @Nullable
getBufferedImage( Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats)71     public static BufferedImage getBufferedImage(
72             Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
73 
74         // strongref is just for gc.
75         BufferedImage strongRef = populateBucket(bucket, metaData, stats);
76 
77         // pool is too small to create the requested buffer.
78         if (bucket.isEmpty()) {
79             assert strongRef == null;
80             return null;
81         }
82 
83         // Go through the bucket of soft references to find the first buffer that's not null.
84         // Even if gc is triggered here, strongref should survive.
85         BufferedImage img = bucket.remove();
86         while (img == null && !bucket.isEmpty()) {
87             img = bucket.remove();
88         }
89 
90         if (img == null && bucket.isEmpty()) {
91             // Whole buffer was null. Recurse.
92             return getBufferedImage(bucket, metaData, stats);
93         }
94         return img;
95     }
96 
97     /**
98      * Populate the bucket in greedy manner to avoid fragmentation.
99      * Behaviour is controlled by {@link ImagePoolPolicy}.
100      * Returns one strong referenced buffer to avoid getting results gc'd. Null if pool is not large
101      * enough.
102      */
103     @Nullable
populateBucket( Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats)104     private static BufferedImage populateBucket(
105             Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
106         if (!bucket.isEmpty()) {
107             // If not empty no need to populate.
108             return null;
109         }
110 
111         BufferedImage strongRef = null;
112         for (int i = 0; i < metaData.mNumberOfCopies; i++) {
113             if (!stats.fitsMaxCacheSize(
114                     metaData.mWidth, metaData.mHeight, metaData.mMaxCacheSize)) {
115                 break;
116             }
117             strongRef =new BufferedImage(metaData.mWidth, metaData.mHeight,
118                     metaData.mType);
119             bucket.offer(strongRef);
120             stats.recordBucketCreation(metaData.mWidth, metaData.mHeight);
121         }
122         return strongRef;
123     }
124 
toKey(int w, int h, int type)125     private static String toKey(int w, int h, int type) {
126         return new StringBuilder()
127                 .append(w)
128                 .append('x')
129                 .append(h)
130                 .append(':')
131                 .append(type)
132                 .toString();
133     }
134 
getBucket(Map<String, Bucket> map, BucketCreationMetaData metaData, ImagePoolPolicy mPolicy)135     public static Bucket getBucket(Map<String, Bucket> map, BucketCreationMetaData metaData, ImagePoolPolicy mPolicy) {
136         String key = toKey(metaData.mWidth, metaData.mHeight, metaData.mType);
137         Bucket bucket = map.get(key);
138         if (bucket == null) {
139             bucket = new Bucket();
140             map.put(key, bucket);
141         }
142         return bucket;
143     }
144 }