am 830207ca
: Merge change 25635 into eclair
Merge commit '830207cab4047431d7a38443531a3a912bfecf3a' into eclair-plus-aosp * commit '830207cab4047431d7a38443531a3a912bfecf3a': Add new thumbnail API.
This commit is contained in:
200
api/current.xml
200
api/current.xml
@ -114624,6 +114624,25 @@
|
||||
<parameter name="volumeName" type="java.lang.String">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="getThumbnail"
|
||||
return="android.graphics.Bitmap"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="cr" type="android.content.ContentResolver">
|
||||
</parameter>
|
||||
<parameter name="origId" type="long">
|
||||
</parameter>
|
||||
<parameter name="kind" type="int">
|
||||
</parameter>
|
||||
<parameter name="options" type="android.graphics.BitmapFactory.Options">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="query"
|
||||
return="android.database.Cursor"
|
||||
abstract="false"
|
||||
@ -114787,6 +114806,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="THUMB_DATA"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""thumb_data""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="WIDTH"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
@ -115005,6 +115035,176 @@
|
||||
>
|
||||
</field>
|
||||
</class>
|
||||
<class name="MediaStore.Video.Thumbnails"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<implements name="android.provider.BaseColumns">
|
||||
</implements>
|
||||
<constructor name="MediaStore.Video.Thumbnails"
|
||||
type="android.provider.MediaStore.Video.Thumbnails"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</constructor>
|
||||
<method name="getContentUri"
|
||||
return="android.net.Uri"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="volumeName" type="java.lang.String">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="getThumbnail"
|
||||
return="android.graphics.Bitmap"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="cr" type="android.content.ContentResolver">
|
||||
</parameter>
|
||||
<parameter name="origId" type="long">
|
||||
</parameter>
|
||||
<parameter name="kind" type="int">
|
||||
</parameter>
|
||||
<parameter name="options" type="android.graphics.BitmapFactory.Options">
|
||||
</parameter>
|
||||
</method>
|
||||
<field name="DATA"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""_data""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="DEFAULT_SORT_ORDER"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""video_id ASC""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="EXTERNAL_CONTENT_URI"
|
||||
type="android.net.Uri"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="FULL_SCREEN_KIND"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="2"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="HEIGHT"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""height""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="INTERNAL_CONTENT_URI"
|
||||
type="android.net.Uri"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="KIND"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""kind""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="MICRO_KIND"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="3"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="MINI_KIND"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="1"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="VIDEO_ID"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""video_id""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="WIDTH"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""width""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
</class>
|
||||
<interface name="MediaStore.Video.VideoColumns"
|
||||
abstract="true"
|
||||
static="true"
|
||||
|
@ -23,11 +23,15 @@ import android.content.ContentValues;
|
||||
import android.content.ContentUris;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.MiniThumbFile;
|
||||
import android.media.ThumbnailUtil;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
@ -42,8 +46,7 @@ import java.text.Collator;
|
||||
* The Media provider contains meta data for all available media on both internal
|
||||
* and external storage devices.
|
||||
*/
|
||||
public final class MediaStore
|
||||
{
|
||||
public final class MediaStore {
|
||||
private final static String TAG = "MediaStore";
|
||||
|
||||
public static final String AUTHORITY = "media";
|
||||
@ -226,11 +229,129 @@ public final class MediaStore
|
||||
public static final String MIME_TYPE = "mime_type";
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
|
||||
* to be accessed elsewhere.
|
||||
*/
|
||||
private static class InternalThumbnails implements BaseColumns {
|
||||
private static final int MINI_KIND = 1;
|
||||
private static final int FULL_SCREEN_KIND = 2;
|
||||
private static final int MICRO_KIND = 3;
|
||||
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
|
||||
|
||||
/**
|
||||
* This method ensure thumbnails associated with origId are generated and decode the byte
|
||||
* stream from database (MICRO_KIND) or file (MINI_KIND).
|
||||
*
|
||||
* Special optimization has been done to avoid further IPC communication for MICRO_KIND
|
||||
* thumbnails.
|
||||
*
|
||||
* @param cr ContentResolver
|
||||
* @param origId original image or video id
|
||||
* @param kind could be MINI_KIND or MICRO_KIND
|
||||
* @param options this is only used for MINI_KIND when decoding the Bitmap
|
||||
* @param baseUri the base URI of requested thumbnails
|
||||
* @return Bitmap bitmap of specified thumbnail kind
|
||||
*/
|
||||
static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
|
||||
BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
|
||||
Bitmap bitmap = null;
|
||||
String filePath = null;
|
||||
// some optimization for MICRO_KIND: if the magic is non-zero, we don't bother
|
||||
// querying MediaProvider and simply return thumbnail.
|
||||
if (kind == MICRO_KIND) {
|
||||
MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
|
||||
if (thumbFile.getMagic(origId) != 0) {
|
||||
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
|
||||
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
|
||||
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
|
||||
if (bitmap == null) {
|
||||
Log.w(TAG, "couldn't decode byte array.");
|
||||
}
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
|
||||
.appendQueryParameter("orig_id", String.valueOf(origId)).build();
|
||||
c = cr.query(blockingUri, PROJECTION, null, null, null);
|
||||
// This happens when original image/video doesn't exist.
|
||||
if (c == null) return null;
|
||||
|
||||
// Assuming thumbnail has been generated, at least original image exists.
|
||||
if (kind == MICRO_KIND) {
|
||||
MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
|
||||
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
|
||||
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
|
||||
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
|
||||
if (bitmap == null) {
|
||||
Log.w(TAG, "couldn't decode byte array.");
|
||||
}
|
||||
}
|
||||
} else if (kind == MINI_KIND) {
|
||||
if (c.moveToFirst()) {
|
||||
ParcelFileDescriptor pfdInput;
|
||||
Uri thumbUri = null;
|
||||
try {
|
||||
long thumbId = c.getLong(0);
|
||||
filePath = c.getString(1);
|
||||
thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
|
||||
pfdInput = cr.openFileDescriptor(thumbUri, "r");
|
||||
bitmap = BitmapFactory.decodeFileDescriptor(
|
||||
pfdInput.getFileDescriptor(), null, options);
|
||||
pfdInput.close();
|
||||
} catch (FileNotFoundException ex) {
|
||||
Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
|
||||
} catch (OutOfMemoryError ex) {
|
||||
Log.e(TAG, "failed to allocate memory for thumbnail "
|
||||
+ thumbUri + "; " + ex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported kind: " + kind);
|
||||
}
|
||||
|
||||
// We probably run out of space, so create the thumbnail in memory.
|
||||
if (bitmap == null) {
|
||||
Log.v(TAG, "We probably run out of space, so create the thumbnail in memory.");
|
||||
int targetSize = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE :
|
||||
ThumbnailUtil.MINI_THUMB_TARGET_SIZE;
|
||||
int maxPixelNum = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS :
|
||||
ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS;
|
||||
Uri uri = Uri.parse(
|
||||
baseUri.buildUpon().appendPath(String.valueOf(origId))
|
||||
.toString().replaceFirst("thumbnails", "media"));
|
||||
if (isVideo) {
|
||||
c = cr.query(uri, PROJECTION, null, null, null);
|
||||
if (c != null && c.moveToFirst()) {
|
||||
bitmap = ThumbnailUtil.createVideoThumbnail(c.getString(1));
|
||||
if (kind == MICRO_KIND) {
|
||||
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
|
||||
targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixelNum, uri, cr);
|
||||
}
|
||||
}
|
||||
} catch (SQLiteException ex) {
|
||||
Log.w(TAG, ex);
|
||||
} finally {
|
||||
if (c != null) c.close();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains meta data for all available images.
|
||||
*/
|
||||
public static final class Images
|
||||
{
|
||||
public static final class Images {
|
||||
public interface ImageColumns extends MediaColumns {
|
||||
/**
|
||||
* The description of the image
|
||||
@ -298,21 +419,18 @@ public final class MediaStore
|
||||
}
|
||||
|
||||
public static final class Media implements ImageColumns {
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
|
||||
{
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
|
||||
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
|
||||
String where, String orderBy)
|
||||
{
|
||||
String where, String orderBy) {
|
||||
return cr.query(uri, projection, where,
|
||||
null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
|
||||
}
|
||||
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
|
||||
String selection, String [] selectionArgs, String orderBy)
|
||||
{
|
||||
String selection, String [] selectionArgs, String orderBy) {
|
||||
return cr.query(uri, projection, selection,
|
||||
selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
|
||||
}
|
||||
@ -326,8 +444,7 @@ public final class MediaStore
|
||||
* @throws IOException
|
||||
*/
|
||||
public static final Bitmap getBitmap(ContentResolver cr, Uri url)
|
||||
throws FileNotFoundException, IOException
|
||||
{
|
||||
throws FileNotFoundException, IOException {
|
||||
InputStream input = cr.openInputStream(url);
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(input);
|
||||
input.close();
|
||||
@ -344,9 +461,8 @@ public final class MediaStore
|
||||
* @return The URL to the newly created image
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public static final String insertImage(ContentResolver cr, String imagePath, String name,
|
||||
String description) throws FileNotFoundException
|
||||
{
|
||||
public static final String insertImage(ContentResolver cr, String imagePath,
|
||||
String name, String description) throws FileNotFoundException {
|
||||
// Check if file exists with a FileInputStream
|
||||
FileInputStream stream = new FileInputStream(imagePath);
|
||||
try {
|
||||
@ -415,8 +531,7 @@ public final class MediaStore
|
||||
* for any reason.
|
||||
*/
|
||||
public static final String insertImage(ContentResolver cr, Bitmap source,
|
||||
String title, String description)
|
||||
{
|
||||
String title, String description) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Images.Media.TITLE, title);
|
||||
values.put(Images.Media.DESCRIPTION, description);
|
||||
@ -425,8 +540,7 @@ public final class MediaStore
|
||||
Uri url = null;
|
||||
String stringUrl = null; /* value to be returned */
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
url = cr.insert(EXTERNAL_CONTENT_URI, values);
|
||||
|
||||
if (source != null) {
|
||||
@ -498,25 +612,45 @@ public final class MediaStore
|
||||
public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
public static class Thumbnails implements BaseColumns
|
||||
{
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
|
||||
{
|
||||
/**
|
||||
* This class allows developers to query and get two kinds of thumbnails:
|
||||
* MINI_KIND: 512 x 384 thumbnail
|
||||
* MICRO_KIND: 96 x 96 thumbnail
|
||||
*/
|
||||
public static class Thumbnails implements BaseColumns {
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
|
||||
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)
|
||||
{
|
||||
public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
|
||||
String[] projection) {
|
||||
return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)
|
||||
{
|
||||
public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
|
||||
String[] projection) {
|
||||
return cr.query(EXTERNAL_CONTENT_URI, projection,
|
||||
IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
|
||||
kind, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the thumbnails of the specified image (origId) has been created.
|
||||
* It will be blocked until the thumbnails are generated.
|
||||
*
|
||||
* @param cr ContentResolver used to dispatch queries to MediaProvider.
|
||||
* @param origId Original image id associated with thumbnail of interest.
|
||||
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
|
||||
* @param options this is only used for MINI_KIND when decoding the Bitmap
|
||||
* @return A Bitmap instance. It could be null if the original image
|
||||
* associated with origId doesn't exist or memory is not enough.
|
||||
*/
|
||||
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
|
||||
BitmapFactory.Options options) {
|
||||
return InternalThumbnails.getThumbnail(cr, origId, kind, options,
|
||||
EXTERNAL_CONTENT_URI, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content:// style URI for the image media table on the
|
||||
* given volume.
|
||||
@ -568,6 +702,11 @@ public final class MediaStore
|
||||
public static final int MINI_KIND = 1;
|
||||
public static final int FULL_SCREEN_KIND = 2;
|
||||
public static final int MICRO_KIND = 3;
|
||||
/**
|
||||
* The blob raw data of thumbnail
|
||||
* <P>Type: DATA STREAM</P>
|
||||
*/
|
||||
public static final String THUMB_DATA = "thumb_data";
|
||||
|
||||
/**
|
||||
* The width of the thumbnal
|
||||
@ -1259,8 +1398,7 @@ public final class MediaStore
|
||||
*/
|
||||
public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
|
||||
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
|
||||
{
|
||||
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
|
||||
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
@ -1405,6 +1543,95 @@ public final class MediaStore
|
||||
*/
|
||||
public static final String DEFAULT_SORT_ORDER = TITLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class allows developers to query and get two kinds of thumbnails:
|
||||
* MINI_KIND: 512 x 384 thumbnail
|
||||
* MICRO_KIND: 96 x 96 thumbnail
|
||||
*
|
||||
*/
|
||||
public static class Thumbnails implements BaseColumns {
|
||||
/**
|
||||
* This method checks if the thumbnails of the specified image (origId) has been created.
|
||||
* It will be blocked until the thumbnails are generated.
|
||||
*
|
||||
* @param cr ContentResolver used to dispatch queries to MediaProvider.
|
||||
* @param origId Original image id associated with thumbnail of interest.
|
||||
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
|
||||
* @param options this is only used for MINI_KIND when decoding the Bitmap
|
||||
* @return A Bitmap instance. It could be null if the original image associated with
|
||||
* origId doesn't exist or memory is not enough.
|
||||
*/
|
||||
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
|
||||
BitmapFactory.Options options) {
|
||||
return InternalThumbnails.getThumbnail(cr, origId, kind, options,
|
||||
EXTERNAL_CONTENT_URI, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content:// style URI for the image media table on the
|
||||
* given volume.
|
||||
*
|
||||
* @param volumeName the name of the volume to get the URI for
|
||||
* @return the URI to the image media table on the given volume
|
||||
*/
|
||||
public static Uri getContentUri(String volumeName) {
|
||||
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
|
||||
"/video/thumbnails");
|
||||
}
|
||||
|
||||
/**
|
||||
* The content:// style URI for the internal storage.
|
||||
*/
|
||||
public static final Uri INTERNAL_CONTENT_URI =
|
||||
getContentUri("internal");
|
||||
|
||||
/**
|
||||
* The content:// style URI for the "primary" external storage
|
||||
* volume.
|
||||
*/
|
||||
public static final Uri EXTERNAL_CONTENT_URI =
|
||||
getContentUri("external");
|
||||
|
||||
/**
|
||||
* The default sort order for this table
|
||||
*/
|
||||
public static final String DEFAULT_SORT_ORDER = "video_id ASC";
|
||||
|
||||
/**
|
||||
* The data stream for the thumbnail
|
||||
* <P>Type: DATA STREAM</P>
|
||||
*/
|
||||
public static final String DATA = "_data";
|
||||
|
||||
/**
|
||||
* The original image for the thumbnal
|
||||
* <P>Type: INTEGER (ID from Video table)</P>
|
||||
*/
|
||||
public static final String VIDEO_ID = "video_id";
|
||||
|
||||
/**
|
||||
* The kind of the thumbnail
|
||||
* <P>Type: INTEGER (One of the values below)</P>
|
||||
*/
|
||||
public static final String KIND = "kind";
|
||||
|
||||
public static final int MINI_KIND = 1;
|
||||
public static final int FULL_SCREEN_KIND = 2;
|
||||
public static final int MICRO_KIND = 3;
|
||||
|
||||
/**
|
||||
* The width of the thumbnal
|
||||
* <P>Type: INTEGER (long)</P>
|
||||
*/
|
||||
public static final String WIDTH = "width";
|
||||
|
||||
/**
|
||||
* The height of the thumbnail
|
||||
* <P>Type: INTEGER (long)</P>
|
||||
*/
|
||||
public static final String HEIGHT = "height";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
280
media/java/android/media/MiniThumbFile.java
Normal file
280
media/java/android/media/MiniThumbFile.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
401
media/java/android/media/ThumbnailUtil.java
Normal file
401
media/java/android/media/ThumbnailUtil.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user