/* * Copyright (C) 2015 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 com.android.messaging.datamodel.media; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import com.android.messaging.ui.OrientedBitmapDrawable; import com.android.messaging.util.Assert; import com.android.messaging.util.Assert.DoesNotRunOnMainThread; import com.android.messaging.util.ImageUtils; import com.android.messaging.util.LogUtil; import com.android.messaging.util.OsUtil; import java.util.List; /** * Container class for holding a bitmap resource used by the MediaResourceManager. This resource * can both be cached (albeit not very storage-efficiently) and directly used by the UI. */ public class DecodedImageResource extends ImageResource { private static final int BITMAP_QUALITY = 100; private static final int COMPRESS_QUALITY = 50; private Bitmap mBitmap; private final int mOrientation; private boolean mCacheable = true; public DecodedImageResource(final String key, final Bitmap bitmap, int orientation) { super(key, orientation); mBitmap = bitmap; mOrientation = orientation; } /** * Gets the contained bitmap. */ @Override public Bitmap getBitmap() { acquireLock(); try { return mBitmap; } finally { releaseLock(); } } /** * Attempt to reuse the bitmap in the image resource and repurpose it for something else. * After this, the image resource will relinquish ownership on the bitmap resource so that * it doesn't try to recycle it when getting closed. */ @Override public Bitmap reuseBitmap() { acquireLock(); try { assertSingularRefCount(); final Bitmap retBitmap = mBitmap; mBitmap = null; return retBitmap; } finally { releaseLock(); } } @Override public boolean supportsBitmapReuse() { return true; } @Override public byte[] getBytes() { acquireLock(); try { return ImageUtils.bitmapToBytes(mBitmap, BITMAP_QUALITY); } catch (final Exception e) { LogUtil.e(LogUtil.BUGLE_TAG, "Error trying to get the bitmap bytes " + e); } finally { releaseLock(); } return null; } /** * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants */ @Override public int getOrientation() { return mOrientation; } @Override public int getMediaSize() { acquireLock(); try { Assert.notNull(mBitmap); if (OsUtil.isAtLeastKLP()) { return mBitmap.getAllocationByteCount(); } else { return mBitmap.getRowBytes() * mBitmap.getHeight(); } } finally { releaseLock(); } } @Override protected void close() { acquireLock(); try { if (mBitmap != null) { mBitmap.recycle(); mBitmap = null; } } finally { releaseLock(); } } @Override public Drawable getDrawable(Resources resources) { acquireLock(); try { Assert.notNull(mBitmap); return OrientedBitmapDrawable.create(getOrientation(), resources, mBitmap); } finally { releaseLock(); } } @Override boolean isCacheable() { return mCacheable; } public void setCacheable(final boolean cacheable) { mCacheable = cacheable; } @SuppressWarnings("unchecked") @Override MediaRequest getMediaEncodingRequest( final MediaRequest originalRequest) { Assert.isFalse(isEncoded()); if (getBitmap().hasAlpha()) { // We can't compress images with alpha, as JPEG encoding doesn't support this. return null; } return new EncodeImageRequest((MediaRequest) originalRequest); } /** * A MediaRequest that encodes the contained image resource. */ private class EncodeImageRequest implements MediaRequest { private final MediaRequest mOriginalImageRequest; public EncodeImageRequest(MediaRequest originalImageRequest) { mOriginalImageRequest = originalImageRequest; // Hold a ref onto the encoded resource before the request finishes. DecodedImageResource.this.addRef(); } @Override public String getKey() { return DecodedImageResource.this.getKey(); } @Override @DoesNotRunOnMainThread public ImageResource loadMediaBlocking(List> chainedRequests) throws Exception { Assert.isNotMainThread(); acquireLock(); Bitmap scaledBitmap = null; try { Bitmap bitmap = getBitmap(); Assert.isFalse(bitmap.hasAlpha()); final int bitmapWidth = bitmap.getWidth(); final int bitmapHeight = bitmap.getHeight(); // The original bitmap was loaded using sub-sampling which was fast in terms of // loading speed, but not optimized for caching, encoding and rendering (since // bitmap resizing to fit the UI image views happens on the UI thread and should // be avoided if possible). Therefore, try to resize the bitmap to the exact desired // size before compressing it. if (bitmapWidth > 0 && bitmapHeight > 0 && mOriginalImageRequest instanceof ImageRequest) { final ImageRequestDescriptor descriptor = ((ImageRequest) mOriginalImageRequest).getDescriptor(); final float targetScale = Math.max( (float) descriptor.desiredWidth / bitmapWidth, (float) descriptor.desiredHeight / bitmapHeight); final int targetWidth = (int) (bitmapWidth * targetScale); final int targetHeight = (int) (bitmapHeight * targetScale); // Only try to scale down the image to the desired size. if (targetScale < 1.0f && targetWidth > 0 && targetHeight > 0 && targetWidth != bitmapWidth && targetHeight != bitmapHeight) { scaledBitmap = bitmap = Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false); } } byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY); return new EncodedImageResource(getKey(), encodedBytes, getOrientation()); } catch (Exception ex) { // Something went wrong during bitmap compression, fall back to just using the // original bitmap. LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "Error compressing bitmap", ex); return DecodedImageResource.this; } finally { if (scaledBitmap != null && scaledBitmap != getBitmap()) { scaledBitmap.recycle(); scaledBitmap = null; } releaseLock(); release(); } } @Override public MediaCache getMediaCache() { return mOriginalImageRequest.getMediaCache(); } @Override public int getCacheId() { return mOriginalImageRequest.getCacheId(); } @Override public int getRequestType() { return REQUEST_ENCODE_MEDIA; } @Override public MediaRequestDescriptor getDescriptor() { return mOriginalImageRequest.getDescriptor(); } } }