summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorRay Chen <raychen@google.com>2009-08-28 14:12:15 -0700
committerRay Chen <raychen@google.com>2009-09-23 11:23:45 -0700
commit00c575a3fccb9d3065e913f1b8fcf93e18d44eaf (patch)
tree56ca36895b1d2f9b95b3d1cf3bd673a04dbfb2f5 /media
parent9d12fdb1b55500d69df5a1bdc2fcba57a2f1876c (diff)
downloadframeworks_base-00c575a3fccb9d3065e913f1b8fcf93e18d44eaf.zip
frameworks_base-00c575a3fccb9d3065e913f1b8fcf93e18d44eaf.tar.gz
frameworks_base-00c575a3fccb9d3065e913f1b8fcf93e18d44eaf.tar.bz2
Add new thumbnail API.
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/MediaScanner.java2
-rw-r--r--media/java/android/media/MiniThumbFile.java280
-rw-r--r--media/java/android/media/ThumbnailUtil.java401
3 files changed, 683 insertions, 0 deletions
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 3ac5df5..21ad82b 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -486,6 +486,8 @@ public class MediaScanner
}
public void scanFile(String path, long lastModified, long fileSize) {
+ // This is the callback funtion from native codes.
+ // Log.v(TAG, "scanFile: "+path);
doScanFile(path, null, lastModified, fileSize, false);
}
diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java
new file mode 100644
index 0000000..c607218
--- /dev/null
+++ b/media/java/android/media/MiniThumbFile.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2009 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.media;
+
+import android.graphics.Bitmap;
+import android.media.ThumbnailUtil;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.Hashtable;
+
+/**
+ * This class handles the mini-thumb file. A mini-thumb file consists
+ * of blocks, indexed by id. Each block has BYTES_PER_MINTHUMB bytes in the
+ * following format:
+ *
+ * 1 byte status (0 = empty, 1 = mini-thumb available)
+ * 8 bytes magic (a magic number to match what's in the database)
+ * 4 bytes data length (LEN)
+ * LEN bytes jpeg data
+ * (the remaining bytes are unused)
+ *
+ * @hide This file is shared between MediaStore and MediaProvider and should remained internal use
+ * only.
+ */
+public class MiniThumbFile {
+ public static final int THUMBNAIL_TARGET_SIZE = 320;
+ public static final int MINI_THUMB_TARGET_SIZE = 96;
+ public static final int THUMBNAIL_MAX_NUM_PIXELS = 512 * 384;
+ public static final int MINI_THUMB_MAX_NUM_PIXELS = 128 * 128;
+ public static final int UNCONSTRAINED = -1;
+
+ private static final String TAG = "MiniThumbFile";
+ private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
+ public static final int BYTES_PER_MINTHUMB = 10000;
+ private static final int HEADER_SIZE = 1 + 8 + 4;
+ private Uri mUri;
+ private RandomAccessFile mMiniThumbFile;
+ private FileChannel mChannel;
+ private static Hashtable<String, MiniThumbFile> sThumbFiles =
+ new Hashtable<String, MiniThumbFile>();
+
+ /**
+ * We store different types of thumbnails in different files. To remain backward compatibility,
+ * we should hashcode of content://media/external/images/media remains the same.
+ */
+ public static synchronized void reset() {
+ sThumbFiles.clear();
+ }
+
+ public static synchronized MiniThumbFile instance(Uri uri) {
+ String type = uri.getPathSegments().get(1);
+ MiniThumbFile file = sThumbFiles.get(type);
+ // Log.v(TAG, "get minithumbfile for type: "+type);
+ if (file == null) {
+ file = new MiniThumbFile(
+ Uri.parse("content://media/external/" + type + "/media"));
+ sThumbFiles.put(type, file);
+ }
+
+ return file;
+ }
+
+ private String randomAccessFilePath(int version) {
+ String directoryName =
+ Environment.getExternalStorageDirectory().toString()
+ + "/DCIM/.thumbnails";
+ return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
+ }
+
+ private void removeOldFile() {
+ String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
+ File oldFile = new File(oldPath);
+ if (oldFile.exists()) {
+ try {
+ oldFile.delete();
+ } catch (SecurityException ex) {
+ // ignore
+ }
+ }
+ }
+
+ private RandomAccessFile miniThumbDataFile() {
+ if (mMiniThumbFile == null) {
+ removeOldFile();
+ String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
+ File directory = new File(path).getParentFile();
+ if (!directory.isDirectory()) {
+ if (!directory.mkdirs()) {
+ Log.e(TAG, "Unable to create .thumbnails directory "
+ + directory.toString());
+ }
+ }
+ File f = new File(path);
+ try {
+ mMiniThumbFile = new RandomAccessFile(f, "rw");
+ } catch (IOException ex) {
+ // Open as read-only so we can at least read the existing
+ // thumbnails.
+ try {
+ mMiniThumbFile = new RandomAccessFile(f, "r");
+ } catch (IOException ex2) {
+ // ignore exception
+ }
+ }
+ mChannel = mMiniThumbFile.getChannel();
+ }
+ return mMiniThumbFile;
+ }
+
+ public MiniThumbFile(Uri uri) {
+ mUri = uri;
+ }
+
+ public synchronized void deactivate() {
+ if (mMiniThumbFile != null) {
+ try {
+ mMiniThumbFile.close();
+ mMiniThumbFile = null;
+ } catch (IOException ex) {
+ // ignore exception
+ }
+ }
+ }
+
+ // Get the magic number for the specified id in the mini-thumb file.
+ // Returns 0 if the magic is not available.
+ public long getMagic(long id) {
+ // check the mini thumb file for the right data. Right is
+ // defined as having the right magic number at the offset
+ // reserved for this "id".
+ RandomAccessFile r = miniThumbDataFile();
+ if (r != null) {
+ long pos = id * BYTES_PER_MINTHUMB;
+ FileLock lock = null;
+ try {
+ lock = mChannel.lock();
+ // check that we can read the following 9 bytes
+ // (1 for the "status" and 8 for the long)
+ if (r.length() >= pos + 1 + 8) {
+ r.seek(pos);
+ if (r.readByte() == 1) {
+ long fileMagic = r.readLong();
+ return fileMagic;
+ }
+ }
+ } catch (IOException ex) {
+ Log.v(TAG, "Got exception checking file magic: ", ex);
+ } catch (RuntimeException ex) {
+ // Other NIO related exception like disk full, read only channel..etc
+ Log.e(TAG, "Got exception when reading magic, id = " + id +
+ ", disk full or mount read-only? " + ex.getClass());
+ } finally {
+ try {
+ if (lock != null) lock.release();
+ }
+ catch (IOException ex) {
+ // ignore it.
+ }
+ }
+ }
+ return 0;
+ }
+
+ public void saveMiniThumbToFile(Bitmap bitmap, long id, long magic)
+ throws IOException {
+ byte[] data = ThumbnailUtil.miniThumbData(bitmap);
+ saveMiniThumbToFile(data, id, magic);
+ }
+
+ public void saveMiniThumbToFile(byte[] data, long id, long magic)
+ throws IOException {
+ RandomAccessFile r = miniThumbDataFile();
+ if (r == null) return;
+
+ long pos = id * BYTES_PER_MINTHUMB;
+ FileLock lock = null;
+ try {
+ lock = mChannel.lock();
+ if (data != null) {
+ if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) {
+ // not enough space to store it.
+ return;
+ }
+ r.seek(pos);
+ r.writeByte(0); // we have no data in this slot
+
+ // if magic is 0 then leave it alone
+ if (magic == 0) {
+ r.skipBytes(8);
+ } else {
+ r.writeLong(magic);
+ }
+ r.writeInt(data.length);
+ r.write(data);
+ r.seek(pos);
+ r.writeByte(1); // we have data in this slot
+ mChannel.force(true);
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "couldn't save mini thumbnail data for "
+ + id + "; ", ex);
+ throw ex;
+ } catch (RuntimeException ex) {
+ // Other NIO related exception like disk full, read only channel..etc
+ Log.e(TAG, "couldn't save mini thumbnail data for "
+ + id + "; disk full or mount read-only? " + ex.getClass());
+ } finally {
+ try {
+ if (lock != null) lock.release();
+ }
+ catch (IOException ex) {
+ // ignore it.
+ }
+ }
+ }
+
+ /**
+ * Gallery app can use this method to retrieve mini-thumbnail. Full size
+ * images share the same IDs with their corresponding thumbnails.
+ *
+ * @param id the ID of the image (same of full size image).
+ * @param data the buffer to store mini-thumbnail.
+ */
+ public byte [] getMiniThumbFromFile(long id, byte [] data) {
+ RandomAccessFile r = miniThumbDataFile();
+ if (r == null) return null;
+
+ long pos = id * BYTES_PER_MINTHUMB;
+ FileLock lock = null;
+ try {
+ lock = mChannel.lock();
+ r.seek(pos);
+ if (r.readByte() == 1) {
+ long magic = r.readLong();
+ int length = r.readInt();
+ int got = r.read(data, 0, length);
+ if (got != length) return null;
+ return data;
+ } else {
+ return null;
+ }
+ } catch (IOException ex) {
+ Log.w(TAG, "got exception when reading thumbnail: " + ex);
+ return null;
+ } catch (RuntimeException ex) {
+ // Other NIO related exception like disk full, read only channel..etc
+ Log.e(TAG, "Got exception when reading thumbnail, id = " + id +
+ ", disk full or mount read-only? " + ex.getClass());
+ } finally {
+ try {
+ if (lock != null) lock.release();
+ }
+ catch (IOException ex) {
+ // ignore it.
+ }
+ }
+ return null;
+ }
+}
diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java
new file mode 100644
index 0000000..3db10b8
--- /dev/null
+++ b/media/java/android/media/ThumbnailUtil.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2009 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.media;
+
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.media.MediaMetadataRetriever;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Thumbnail generation routines for media provider. This class should only be used internaly.
+ * {@hide} THIS IS NOT FOR PUBLIC API.
+ */
+
+public class ThumbnailUtil {
+ private static final String TAG = "ThumbnailUtil";
+ //Whether we should recycle the input (unless the output is the input).
+ public static final boolean RECYCLE_INPUT = true;
+ public static final boolean NO_RECYCLE_INPUT = false;
+ public static final boolean ROTATE_AS_NEEDED = true;
+ public static final boolean NO_ROTATE = false;
+ public static final boolean USE_NATIVE = true;
+ public static final boolean NO_NATIVE = false;
+
+ public static final int THUMBNAIL_TARGET_SIZE = 320;
+ public static final int MINI_THUMB_TARGET_SIZE = 96;
+ public static final int THUMBNAIL_MAX_NUM_PIXELS = 512 * 384;
+ public static final int MINI_THUMB_MAX_NUM_PIXELS = 128 * 128;
+ public static final int UNCONSTRAINED = -1;
+
+ // Returns Options that set the native alloc flag for Bitmap decode.
+ public static BitmapFactory.Options createNativeAllocOptions() {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inNativeAlloc = true;
+ return options;
+ }
+ /**
+ * Make a bitmap from a given Uri.
+ *
+ * @param uri
+ */
+ public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+ Uri uri, ContentResolver cr) {
+ return makeBitmap(minSideLength, maxNumOfPixels, uri, cr,
+ NO_NATIVE);
+ }
+
+ /*
+ * Compute the sample size as a function of minSideLength
+ * and maxNumOfPixels.
+ * minSideLength is used to specify that minimal width or height of a
+ * bitmap.
+ * maxNumOfPixels is used to specify the maximal size in pixels that is
+ * tolerable in terms of memory usage.
+ *
+ * The function returns a sample size based on the constraints.
+ * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
+ * which indicates no care of the corresponding constraint.
+ * The functions prefers returning a sample size that
+ * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
+ *
+ * Also, the function rounds up the sample size to a power of 2 or multiple
+ * of 8 because BitmapFactory only honors sample size this way.
+ * For example, BitmapFactory downsamples an image by 2 even though the
+ * request is 3. So we round up the sample size to avoid OOM.
+ */
+ public static int computeSampleSize(BitmapFactory.Options options,
+ int minSideLength, int maxNumOfPixels) {
+ int initialSize = computeInitialSampleSize(options, minSideLength,
+ maxNumOfPixels);
+
+ int roundedSize;
+ if (initialSize <= 8 ) {
+ roundedSize = 1;
+ while (roundedSize < initialSize) {
+ roundedSize <<= 1;
+ }
+ } else {
+ roundedSize = (initialSize + 7) / 8 * 8;
+ }
+
+ return roundedSize;
+ }
+
+ private static int computeInitialSampleSize(BitmapFactory.Options options,
+ int minSideLength, int maxNumOfPixels) {
+ double w = options.outWidth;
+ double h = options.outHeight;
+
+ int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
+ (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
+ int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
+ (int) Math.min(Math.floor(w / minSideLength),
+ Math.floor(h / minSideLength));
+
+ if (upperBound < lowerBound) {
+ // return the larger one when there is no overlapping zone.
+ return lowerBound;
+ }
+
+ if ((maxNumOfPixels == UNCONSTRAINED) &&
+ (minSideLength == UNCONSTRAINED)) {
+ return 1;
+ } else if (minSideLength == UNCONSTRAINED) {
+ return lowerBound;
+ } else {
+ return upperBound;
+ }
+ }
+
+ public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+ Uri uri, ContentResolver cr, boolean useNative) {
+ ParcelFileDescriptor input = null;
+ try {
+ input = cr.openFileDescriptor(uri, "r");
+ BitmapFactory.Options options = null;
+ if (useNative) {
+ options = createNativeAllocOptions();
+ }
+ return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input,
+ options);
+ } catch (IOException ex) {
+ Log.e(TAG, "", ex);
+ return null;
+ } finally {
+ closeSilently(input);
+ }
+ }
+
+ // Rotates the bitmap by the specified degree.
+ // If a new bitmap is created, the original bitmap is recycled.
+ public static Bitmap rotate(Bitmap b, int degrees) {
+ if (degrees != 0 && b != null) {
+ Matrix m = new Matrix();
+ m.setRotate(degrees,
+ (float) b.getWidth() / 2, (float) b.getHeight() / 2);
+ try {
+ Bitmap b2 = Bitmap.createBitmap(
+ b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+ if (b != b2) {
+ b.recycle();
+ b = b2;
+ }
+ } catch (OutOfMemoryError ex) {
+ // We have no memory to rotate. Return the original bitmap.
+ }
+ }
+ return b;
+ }
+
+ private static void closeSilently(ParcelFileDescriptor c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (Throwable t) {
+ // do nothing
+ }
+ }
+
+ private static ParcelFileDescriptor makeInputStream(
+ Uri uri, ContentResolver cr) {
+ try {
+ return cr.openFileDescriptor(uri, "r");
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+ Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
+ BitmapFactory.Options options) {
+ Bitmap b = null;
+ try {
+ if (pfd == null) pfd = makeInputStream(uri, cr);
+ if (pfd == null) return null;
+ if (options == null) options = new BitmapFactory.Options();
+
+ FileDescriptor fd = pfd.getFileDescriptor();
+ options.inSampleSize = 1;
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(fd, null, options);
+ if (options.mCancel || options.outWidth == -1
+ || options.outHeight == -1) {
+ return null;
+ }
+ options.inSampleSize = computeSampleSize(
+ options, minSideLength, maxNumOfPixels);
+ options.inJustDecodeBounds = false;
+
+ options.inDither = false;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ b = BitmapFactory.decodeFileDescriptor(fd, null, options);
+ } catch (OutOfMemoryError ex) {
+ Log.e(TAG, "Got oom exception ", ex);
+ return null;
+ } finally {
+ closeSilently(pfd);
+ }
+ return b;
+ }
+
+ /**
+ * Creates a centered bitmap of the desired size.
+ * @param source
+ * @param recycle whether we want to recycle the input
+ */
+ public static Bitmap extractMiniThumb(
+ Bitmap source, int width, int height, boolean recycle) {
+ if (source == null) {
+ return null;
+ }
+
+ float scale;
+ if (source.getWidth() < source.getHeight()) {
+ scale = width / (float) source.getWidth();
+ } else {
+ scale = height / (float) source.getHeight();
+ }
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ Bitmap miniThumbnail = transform(matrix, source, width, height, false, recycle);
+ return miniThumbnail;
+ }
+
+ /**
+ * Creates a byte[] for a given bitmap of the desired size. Recycles the
+ * input bitmap.
+ */
+ public static byte[] miniThumbData(Bitmap source) {
+ if (source == null) return null;
+
+ Bitmap miniThumbnail = extractMiniThumb(
+ source, MINI_THUMB_TARGET_SIZE,
+ MINI_THUMB_TARGET_SIZE,
+ RECYCLE_INPUT);
+
+ ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
+ miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
+ miniThumbnail.recycle();
+
+ try {
+ miniOutStream.close();
+ byte [] data = miniOutStream.toByteArray();
+ return data;
+ } catch (java.io.IOException ex) {
+ Log.e(TAG, "got exception ex " + ex);
+ }
+ return null;
+ }
+
+ /**
+ * Create a video thumbnail for a video. May return null if the video is
+ * corrupt.
+ *
+ * @param filePath
+ */
+ public static Bitmap createVideoThumbnail(String filePath) {
+ Bitmap bitmap = null;
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ try {
+ retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
+ retriever.setDataSource(filePath);
+ bitmap = retriever.captureFrame();
+ } catch (IllegalArgumentException ex) {
+ // Assume this is a corrupt video file
+ } catch (RuntimeException ex) {
+ // Assume this is a corrupt video file.
+ } finally {
+ try {
+ retriever.release();
+ } catch (RuntimeException ex) {
+ // Ignore failures while cleaning up.
+ }
+ }
+ return bitmap;
+ }
+
+ public static Bitmap transform(Matrix scaler,
+ Bitmap source,
+ int targetWidth,
+ int targetHeight,
+ boolean scaleUp,
+ boolean recycle) {
+
+ int deltaX = source.getWidth() - targetWidth;
+ int deltaY = source.getHeight() - targetHeight;
+ if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
+ /*
+ * In this case the bitmap is smaller, at least in one dimension,
+ * than the target. Transform it by placing as much of the image
+ * as possible into the target and leaving the top/bottom or
+ * left/right (or both) black.
+ */
+ Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b2);
+
+ int deltaXHalf = Math.max(0, deltaX / 2);
+ int deltaYHalf = Math.max(0, deltaY / 2);
+ Rect src = new Rect(
+ deltaXHalf,
+ deltaYHalf,
+ deltaXHalf + Math.min(targetWidth, source.getWidth()),
+ deltaYHalf + Math.min(targetHeight, source.getHeight()));
+ int dstX = (targetWidth - src.width()) / 2;
+ int dstY = (targetHeight - src.height()) / 2;
+ Rect dst = new Rect(
+ dstX,
+ dstY,
+ targetWidth - dstX,
+ targetHeight - dstY);
+ c.drawBitmap(source, src, dst, null);
+ if (recycle) {
+ source.recycle();
+ }
+ return b2;
+ }
+ float bitmapWidthF = source.getWidth();
+ float bitmapHeightF = source.getHeight();
+
+ float bitmapAspect = bitmapWidthF / bitmapHeightF;
+ float viewAspect = (float) targetWidth / targetHeight;
+
+ if (bitmapAspect > viewAspect) {
+ float scale = targetHeight / bitmapHeightF;
+ if (scale < .9F || scale > 1F) {
+ scaler.setScale(scale, scale);
+ } else {
+ scaler = null;
+ }
+ } else {
+ float scale = targetWidth / bitmapWidthF;
+ if (scale < .9F || scale > 1F) {
+ scaler.setScale(scale, scale);
+ } else {
+ scaler = null;
+ }
+ }
+
+ Bitmap b1;
+ if (scaler != null) {
+ // this is used for minithumb and crop, so we want to filter here.
+ b1 = Bitmap.createBitmap(source, 0, 0,
+ source.getWidth(), source.getHeight(), scaler, true);
+ } else {
+ b1 = source;
+ }
+
+ if (recycle && b1 != source) {
+ source.recycle();
+ }
+
+ int dx1 = Math.max(0, b1.getWidth() - targetWidth);
+ int dy1 = Math.max(0, b1.getHeight() - targetHeight);
+
+ Bitmap b2 = Bitmap.createBitmap(
+ b1,
+ dx1 / 2,
+ dy1 / 2,
+ targetWidth,
+ targetHeight);
+
+ if (b2 != b1) {
+ if (recycle || b1 != source) {
+ b1.recycle();
+ }
+ }
+
+ return b2;
+ }
+
+
+
+}