blob: 292fc59c894722a7fcbde2060507d1344fd07d0a [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util.imagepool;
import com.android.tools.layoutlib.annotations.Nullable;
import android.util.imagepool.Bucket.BucketCreationMetaData;
import android.util.imagepool.ImagePool.Image.Orientation;
import android.util.imagepool.ImagePool.ImagePoolPolicy;
import java.awt.image.BufferedImage;
import java.util.Map;
/* private package */ class ImagePoolHelper {
@Nullable
public static BucketCreationMetaData getBucketCreationMetaData(int w, int h, int type, ImagePoolPolicy poolPolicy, ImagePoolStats stats) {
// Find the bucket sizes for both dimensions
int widthBucket = -1;
int heightBucket = -1;
int index = 0;
for (int bucketMinSize : poolPolicy.mBucketSizes) {
if (widthBucket == -1 && w <= bucketMinSize) {
widthBucket = bucketMinSize;
if (heightBucket != -1) {
break;
}
}
if (heightBucket == -1 && h <= bucketMinSize) {
heightBucket = bucketMinSize;
if (widthBucket != -1) {
break;
}
}
++index;
}
stats.recordBucketRequest(w, h);
if (index >= poolPolicy.mNumberOfCopies.length) {
return null;
}
// TODO: Apply orientation
// if (widthBucket < heightBucket) {
// return new BucketCreationMetaData(heightBucket, widthBucket, type, poolPolicy.mNumberOfCopies[index],
// Orientation.CW_90, poolPolicy.mBucketMaxCacheSize);
// }
return new BucketCreationMetaData(widthBucket, heightBucket, type, poolPolicy.mNumberOfCopies[index],
Orientation.NONE, poolPolicy.mBucketMaxCacheSize);
}
@Nullable
public static BufferedImage getBufferedImage(
Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
// strongref is just for gc.
BufferedImage strongRef = populateBucket(bucket, metaData, stats);
// pool is too small to create the requested buffer.
if (bucket.isEmpty()) {
assert strongRef == null;
return null;
}
// Go through the bucket of soft references to find the first buffer that's not null.
// Even if gc is triggered here, strongref should survive.
BufferedImage img = bucket.remove();
while (img == null && !bucket.isEmpty()) {
img = bucket.remove();
}
if (img == null && bucket.isEmpty()) {
// Whole buffer was null. Recurse.
return getBufferedImage(bucket, metaData, stats);
}
return img;
}
/**
* Populate the bucket in greedy manner to avoid fragmentation.
* Behaviour is controlled by {@link ImagePoolPolicy}.
* Returns one strong referenced buffer to avoid getting results gc'd. Null if pool is not large
* enough.
*/
@Nullable
private static BufferedImage populateBucket(
Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
if (!bucket.isEmpty()) {
// If not empty no need to populate.
return null;
}
BufferedImage strongRef = null;
for (int i = 0; i < metaData.mNumberOfCopies; i++) {
if (!stats.fitsMaxCacheSize(
metaData.mWidth, metaData.mHeight, metaData.mMaxCacheSize)) {
break;
}
strongRef =new BufferedImage(metaData.mWidth, metaData.mHeight,
metaData.mType);
bucket.offer(strongRef);
stats.recordBucketCreation(metaData.mWidth, metaData.mHeight);
}
return strongRef;
}
private static String toKey(int w, int h, int type) {
return new StringBuilder()
.append(w)
.append('x')
.append(h)
.append(':')
.append(type)
.toString();
}
public static Bucket getBucket(Map<String, Bucket> map, BucketCreationMetaData metaData, ImagePoolPolicy mPolicy) {
String key = toKey(metaData.mWidth, metaData.mHeight, metaData.mType);
Bucket bucket = map.get(key);
if (bucket == null) {
bucket = new Bucket();
map.put(key, bucket);
}
return bucket;
}
}