diff options
Diffstat (limited to 'src/com/android/gallery3d/data/LocalImage.java')
-rw-r--r-- | src/com/android/gallery3d/data/LocalImage.java | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java new file mode 100644 index 000000000..cc70dd457 --- /dev/null +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2010 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.gallery3d.data; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; +import android.provider.MediaStore.MediaColumns; +import android.util.Log; + +import com.android.gallery3d.app.GalleryApp; +import com.android.gallery3d.app.PanoramaMetadataSupport; +import com.android.gallery3d.app.StitchingProgressManager; +import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.exif.ExifInterface; +import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.filtershow.tools.SaveImage; +import com.android.gallery3d.util.GalleryUtils; +import com.android.gallery3d.util.ThreadPool.Job; +import com.android.gallery3d.util.ThreadPool.JobContext; +import com.android.gallery3d.util.UpdateHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +// LocalImage represents an image in the local storage. +public class LocalImage extends LocalMediaItem { + private static final String TAG = "LocalImage"; + + static final Path ITEM_PATH = Path.fromString("/local/image/item"); + + // Must preserve order between these indices and the order of the terms in + // the following PROJECTION array. + private static final int INDEX_ID = 0; + private static final int INDEX_CAPTION = 1; + private static final int INDEX_MIME_TYPE = 2; + private static final int INDEX_LATITUDE = 3; + private static final int INDEX_LONGITUDE = 4; + private static final int INDEX_DATE_TAKEN = 5; + private static final int INDEX_DATE_ADDED = 6; + private static final int INDEX_DATE_MODIFIED = 7; + private static final int INDEX_DATA = 8; + private static final int INDEX_ORIENTATION = 9; + private static final int INDEX_BUCKET_ID = 10; + private static final int INDEX_SIZE = 11; + private static final int INDEX_WIDTH = 12; + private static final int INDEX_HEIGHT = 13; + + static final String[] PROJECTION = { + ImageColumns._ID, // 0 + ImageColumns.TITLE, // 1 + ImageColumns.MIME_TYPE, // 2 + ImageColumns.LATITUDE, // 3 + ImageColumns.LONGITUDE, // 4 + ImageColumns.DATE_TAKEN, // 5 + ImageColumns.DATE_ADDED, // 6 + ImageColumns.DATE_MODIFIED, // 7 + ImageColumns.DATA, // 8 + ImageColumns.ORIENTATION, // 9 + ImageColumns.BUCKET_ID, // 10 + ImageColumns.SIZE, // 11 + "0", // 12 + "0" // 13 + }; + + static { + updateWidthAndHeightProjection(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private static void updateWidthAndHeightProjection() { + if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) { + PROJECTION[INDEX_WIDTH] = MediaColumns.WIDTH; + PROJECTION[INDEX_HEIGHT] = MediaColumns.HEIGHT; + } + } + + private final GalleryApp mApplication; + + public int rotation; + + private PanoramaMetadataSupport mPanoramaMetadata = new PanoramaMetadataSupport(this); + + public LocalImage(Path path, GalleryApp application, Cursor cursor) { + super(path, nextVersionNumber()); + mApplication = application; + loadFromCursor(cursor); + } + + public LocalImage(Path path, GalleryApp application, int id) { + super(path, nextVersionNumber()); + mApplication = application; + ContentResolver resolver = mApplication.getContentResolver(); + Uri uri = Images.Media.EXTERNAL_CONTENT_URI; + Cursor cursor = LocalAlbum.getItemCursor(resolver, uri, PROJECTION, id); + if (cursor == null) { + throw new RuntimeException("cannot get cursor for: " + path); + } + try { + if (cursor.moveToNext()) { + loadFromCursor(cursor); + } else { + throw new RuntimeException("cannot find data for: " + path); + } + } finally { + cursor.close(); + } + } + + private void loadFromCursor(Cursor cursor) { + id = cursor.getInt(INDEX_ID); + caption = cursor.getString(INDEX_CAPTION); + mimeType = cursor.getString(INDEX_MIME_TYPE); + latitude = cursor.getDouble(INDEX_LATITUDE); + longitude = cursor.getDouble(INDEX_LONGITUDE); + dateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN); + dateAddedInSec = cursor.getLong(INDEX_DATE_ADDED); + dateModifiedInSec = cursor.getLong(INDEX_DATE_MODIFIED); + filePath = cursor.getString(INDEX_DATA); + rotation = cursor.getInt(INDEX_ORIENTATION); + bucketId = cursor.getInt(INDEX_BUCKET_ID); + fileSize = cursor.getLong(INDEX_SIZE); + width = cursor.getInt(INDEX_WIDTH); + height = cursor.getInt(INDEX_HEIGHT); + } + + @Override + protected boolean updateFromCursor(Cursor cursor) { + UpdateHelper uh = new UpdateHelper(); + id = uh.update(id, cursor.getInt(INDEX_ID)); + caption = uh.update(caption, cursor.getString(INDEX_CAPTION)); + mimeType = uh.update(mimeType, cursor.getString(INDEX_MIME_TYPE)); + latitude = uh.update(latitude, cursor.getDouble(INDEX_LATITUDE)); + longitude = uh.update(longitude, cursor.getDouble(INDEX_LONGITUDE)); + dateTakenInMs = uh.update( + dateTakenInMs, cursor.getLong(INDEX_DATE_TAKEN)); + dateAddedInSec = uh.update( + dateAddedInSec, cursor.getLong(INDEX_DATE_ADDED)); + dateModifiedInSec = uh.update( + dateModifiedInSec, cursor.getLong(INDEX_DATE_MODIFIED)); + filePath = uh.update(filePath, cursor.getString(INDEX_DATA)); + rotation = uh.update(rotation, cursor.getInt(INDEX_ORIENTATION)); + bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID)); + fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE)); + width = uh.update(width, cursor.getInt(INDEX_WIDTH)); + height = uh.update(height, cursor.getInt(INDEX_HEIGHT)); + return uh.isUpdated(); + } + + @Override + public Job<Bitmap> requestImage(int type) { + return new LocalImageRequest(mApplication, mPath, dateModifiedInSec, + type, filePath); + } + + public static class LocalImageRequest extends ImageCacheRequest { + private String mLocalFilePath; + + LocalImageRequest(GalleryApp application, Path path, long timeModified, + int type, String localFilePath) { + super(application, path, timeModified, type, + MediaItem.getTargetSize(type)); + mLocalFilePath = localFilePath; + } + + @Override + public Bitmap onDecodeOriginal(JobContext jc, final int type) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + int targetSize = MediaItem.getTargetSize(type); + + // try to decode from JPEG EXIF + if (type == MediaItem.TYPE_MICROTHUMBNAIL) { + ExifInterface exif = new ExifInterface(); + byte[] thumbData = null; + try { + exif.readExif(mLocalFilePath); + thumbData = exif.getThumbnail(); + } catch (FileNotFoundException e) { + Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath); + } catch (IOException e) { + Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath); + } + if (thumbData != null) { + Bitmap bitmap = DecodeUtils.decodeIfBigEnough( + jc, thumbData, options, targetSize); + if (bitmap != null) return bitmap; + } + } + + return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type); + } + } + + @Override + public Job<BitmapRegionDecoder> requestLargeImage() { + return new LocalLargeImageRequest(filePath); + } + + public static class LocalLargeImageRequest + implements Job<BitmapRegionDecoder> { + String mLocalFilePath; + + public LocalLargeImageRequest(String localFilePath) { + mLocalFilePath = localFilePath; + } + + @Override + public BitmapRegionDecoder run(JobContext jc) { + return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false); + } + } + + @Override + public int getSupportedOperations() { + StitchingProgressManager progressManager = mApplication.getStitchingProgressManager(); + if (progressManager != null && progressManager.getProgress(getContentUri()) != null) { + return 0; // doesn't support anything while stitching! + } + int operation = SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_CROP + | SUPPORT_SETAS | SUPPORT_EDIT | SUPPORT_INFO; + if (BitmapUtils.isSupportedByRegionDecoder(mimeType)) { + operation |= SUPPORT_FULL_IMAGE; + } + + if (BitmapUtils.isRotationSupported(mimeType)) { + operation |= SUPPORT_ROTATE; + } + + if (GalleryUtils.isValidLocation(latitude, longitude)) { + operation |= SUPPORT_SHOW_ON_MAP; + } + return operation; + } + + @Override + public void getPanoramaSupport(PanoramaSupportCallback callback) { + mPanoramaMetadata.getPanoramaSupport(mApplication, callback); + } + + @Override + public void clearCachedPanoramaSupport() { + mPanoramaMetadata.clearCachedValues(); + } + + @Override + public void delete() { + GalleryUtils.assertNotInRenderThread(); + Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; + ContentResolver contentResolver = mApplication.getContentResolver(); + SaveImage.deleteAuxFiles(contentResolver, getContentUri()); + contentResolver.delete(baseUri, "_id=?", + new String[]{String.valueOf(id)}); + } + + @Override + public void rotate(int degrees) { + GalleryUtils.assertNotInRenderThread(); + Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; + ContentValues values = new ContentValues(); + int rotation = (this.rotation + degrees) % 360; + if (rotation < 0) rotation += 360; + + if (mimeType.equalsIgnoreCase("image/jpeg")) { + ExifInterface exifInterface = new ExifInterface(); + ExifTag tag = exifInterface.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.getOrientationValueForRotation(rotation)); + if(tag != null) { + exifInterface.setTag(tag); + try { + exifInterface.forceRewriteExif(filePath); + fileSize = new File(filePath).length(); + values.put(Images.Media.SIZE, fileSize); + } catch (FileNotFoundException e) { + Log.w(TAG, "cannot find file to set exif: " + filePath); + } catch (IOException e) { + Log.w(TAG, "cannot set exif data: " + filePath); + } + } else { + Log.w(TAG, "Could not build tag: " + ExifInterface.TAG_ORIENTATION); + } + } + + values.put(Images.Media.ORIENTATION, rotation); + mApplication.getContentResolver().update(baseUri, values, "_id=?", + new String[]{String.valueOf(id)}); + } + + @Override + public Uri getContentUri() { + Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; + return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); + } + + @Override + public int getMediaType() { + return MEDIA_TYPE_IMAGE; + } + + @Override + public MediaDetails getDetails() { + MediaDetails details = super.getDetails(); + details.addDetail(MediaDetails.INDEX_ORIENTATION, Integer.valueOf(rotation)); + if (MIME_TYPE_JPEG.equals(mimeType)) { + // ExifInterface returns incorrect values for photos in other format. + // For example, the width and height of an webp images is always '0'. + MediaDetails.extractExifInfo(details, filePath); + } + return details; + } + + @Override + public int getRotation() { + return rotation; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public String getFilePath() { + return filePath; + } +} |