1/* 2 * Copyright (C) 2015 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 */ 16package com.android.messaging.datamodel.media; 17 18import android.content.res.Resources; 19import android.graphics.Bitmap; 20import android.graphics.drawable.Drawable; 21 22import com.android.messaging.ui.OrientedBitmapDrawable; 23import com.android.messaging.util.Assert; 24import com.android.messaging.util.Assert.DoesNotRunOnMainThread; 25import com.android.messaging.util.ImageUtils; 26import com.android.messaging.util.LogUtil; 27import com.android.messaging.util.OsUtil; 28 29import java.util.List; 30 31 32/** 33 * Container class for holding a bitmap resource used by the MediaResourceManager. This resource 34 * can both be cached (albeit not very storage-efficiently) and directly used by the UI. 35 */ 36public class DecodedImageResource extends ImageResource { 37 private static final int BITMAP_QUALITY = 100; 38 private static final int COMPRESS_QUALITY = 50; 39 40 private Bitmap mBitmap; 41 private final int mOrientation; 42 private boolean mCacheable = true; 43 44 public DecodedImageResource(final String key, final Bitmap bitmap, int orientation) { 45 super(key, orientation); 46 mBitmap = bitmap; 47 mOrientation = orientation; 48 } 49 50 /** 51 * Gets the contained bitmap. 52 */ 53 @Override 54 public Bitmap getBitmap() { 55 acquireLock(); 56 try { 57 return mBitmap; 58 } finally { 59 releaseLock(); 60 } 61 } 62 63 /** 64 * Attempt to reuse the bitmap in the image resource and repurpose it for something else. 65 * After this, the image resource will relinquish ownership on the bitmap resource so that 66 * it doesn't try to recycle it when getting closed. 67 */ 68 @Override 69 public Bitmap reuseBitmap() { 70 acquireLock(); 71 try { 72 assertSingularRefCount(); 73 final Bitmap retBitmap = mBitmap; 74 mBitmap = null; 75 return retBitmap; 76 } finally { 77 releaseLock(); 78 } 79 } 80 81 @Override 82 public boolean supportsBitmapReuse() { 83 return true; 84 } 85 86 @Override 87 public byte[] getBytes() { 88 acquireLock(); 89 try { 90 return ImageUtils.bitmapToBytes(mBitmap, BITMAP_QUALITY); 91 } catch (final Exception e) { 92 LogUtil.e(LogUtil.BUGLE_TAG, "Error trying to get the bitmap bytes " + e); 93 } finally { 94 releaseLock(); 95 } 96 return null; 97 } 98 99 /** 100 * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants 101 */ 102 @Override 103 public int getOrientation() { 104 return mOrientation; 105 } 106 107 @Override 108 public int getMediaSize() { 109 acquireLock(); 110 try { 111 Assert.notNull(mBitmap); 112 if (OsUtil.isAtLeastKLP()) { 113 return mBitmap.getAllocationByteCount(); 114 } else { 115 return mBitmap.getRowBytes() * mBitmap.getHeight(); 116 } 117 } finally { 118 releaseLock(); 119 } 120 } 121 122 @Override 123 protected void close() { 124 acquireLock(); 125 try { 126 if (mBitmap != null) { 127 mBitmap.recycle(); 128 mBitmap = null; 129 } 130 } finally { 131 releaseLock(); 132 } 133 } 134 135 @Override 136 public Drawable getDrawable(Resources resources) { 137 acquireLock(); 138 try { 139 Assert.notNull(mBitmap); 140 return OrientedBitmapDrawable.create(getOrientation(), resources, mBitmap); 141 } finally { 142 releaseLock(); 143 } 144 } 145 146 @Override 147 boolean isCacheable() { 148 return mCacheable; 149 } 150 151 public void setCacheable(final boolean cacheable) { 152 mCacheable = cacheable; 153 } 154 155 @SuppressWarnings("unchecked") 156 @Override 157 MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest( 158 final MediaRequest<? extends RefCountedMediaResource> originalRequest) { 159 Assert.isFalse(isEncoded()); 160 if (getBitmap().hasAlpha()) { 161 // We can't compress images with alpha, as JPEG encoding doesn't support this. 162 return null; 163 } 164 return new EncodeImageRequest((MediaRequest<ImageResource>) originalRequest); 165 } 166 167 /** 168 * A MediaRequest that encodes the contained image resource. 169 */ 170 private class EncodeImageRequest implements MediaRequest<ImageResource> { 171 private final MediaRequest<ImageResource> mOriginalImageRequest; 172 173 public EncodeImageRequest(MediaRequest<ImageResource> originalImageRequest) { 174 mOriginalImageRequest = originalImageRequest; 175 // Hold a ref onto the encoded resource before the request finishes. 176 DecodedImageResource.this.addRef(); 177 } 178 179 @Override 180 public String getKey() { 181 return DecodedImageResource.this.getKey(); 182 } 183 184 @Override 185 @DoesNotRunOnMainThread 186 public ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedRequests) 187 throws Exception { 188 Assert.isNotMainThread(); 189 acquireLock(); 190 Bitmap scaledBitmap = null; 191 try { 192 Bitmap bitmap = getBitmap(); 193 Assert.isFalse(bitmap.hasAlpha()); 194 final int bitmapWidth = bitmap.getWidth(); 195 final int bitmapHeight = bitmap.getHeight(); 196 // The original bitmap was loaded using sub-sampling which was fast in terms of 197 // loading speed, but not optimized for caching, encoding and rendering (since 198 // bitmap resizing to fit the UI image views happens on the UI thread and should 199 // be avoided if possible). Therefore, try to resize the bitmap to the exact desired 200 // size before compressing it. 201 if (bitmapWidth > 0 && bitmapHeight > 0 && 202 mOriginalImageRequest instanceof ImageRequest<?>) { 203 final ImageRequestDescriptor descriptor = 204 ((ImageRequest<?>) mOriginalImageRequest).getDescriptor(); 205 final float targetScale = Math.max( 206 (float) descriptor.desiredWidth / bitmapWidth, 207 (float) descriptor.desiredHeight / bitmapHeight); 208 final int targetWidth = (int) (bitmapWidth * targetScale); 209 final int targetHeight = (int) (bitmapHeight * targetScale); 210 // Only try to scale down the image to the desired size. 211 if (targetScale < 1.0f && targetWidth > 0 && targetHeight > 0 && 212 targetWidth != bitmapWidth && targetHeight != bitmapHeight) { 213 scaledBitmap = bitmap = 214 Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false); 215 } 216 } 217 byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY); 218 return new EncodedImageResource(getKey(), encodedBytes, getOrientation()); 219 } catch (Exception ex) { 220 // Something went wrong during bitmap compression, fall back to just using the 221 // original bitmap. 222 LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "Error compressing bitmap", ex); 223 return DecodedImageResource.this; 224 } finally { 225 if (scaledBitmap != null && scaledBitmap != getBitmap()) { 226 scaledBitmap.recycle(); 227 scaledBitmap = null; 228 } 229 releaseLock(); 230 release(); 231 } 232 } 233 234 @Override 235 public MediaCache<ImageResource> getMediaCache() { 236 return mOriginalImageRequest.getMediaCache(); 237 } 238 239 @Override 240 public int getCacheId() { 241 return mOriginalImageRequest.getCacheId(); 242 } 243 244 @Override 245 public int getRequestType() { 246 return REQUEST_ENCODE_MEDIA; 247 } 248 249 @Override 250 public MediaRequestDescriptor<ImageResource> getDescriptor() { 251 return mOriginalImageRequest.getDescriptor(); 252 } 253 } 254} 255