diff options
author | John Reck <jreck@google.com> | 2012-10-17 18:47:39 -0700 |
---|---|---|
committer | John Reck <jreck@google.com> | 2012-10-18 17:11:00 -0700 |
commit | f1135ad929aadaf873314cc14216c4a8b0ab5b7c (patch) | |
tree | 5f8a90ee540e44a86a11917ad13b8e69ef52569d /src/com/android/gallery3d | |
parent | e1fb53368803126d6f128b16799f1875705d3562 (diff) | |
download | android_packages_apps_Snap-f1135ad929aadaf873314cc14216c4a8b0ab5b7c.tar.gz android_packages_apps_Snap-f1135ad929aadaf873314cc14216c4a8b0ab5b7c.tar.bz2 android_packages_apps_Snap-f1135ad929aadaf873314cc14216c4a8b0ab5b7c.zip |
Save XMP & Exif data on edited photos
Bug: 7293391
Bug: 7329199
Bug: 7376660
Change-Id: I23b1637a9a494c1dc43b1fc1359cdaf3e75bc23f
Diffstat (limited to 'src/com/android/gallery3d')
4 files changed, 164 insertions, 79 deletions
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 6beaed603..d0cab767e 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2012 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.filtershow.cache; @@ -22,7 +37,6 @@ import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.presets.ImagePreset; -import com.android.gallery3d.filtershow.tools.ProcessedBitmap; import com.android.gallery3d.filtershow.tools.SaveCopyTask; import java.io.Closeable; @@ -39,8 +53,6 @@ public class ImageLoader { private Bitmap mOriginalBitmapSmall = null; private Bitmap mOriginalBitmapLarge = null; private Bitmap mBackgroundBitmap = null; - private Bitmap mFullOriginalBitmap = null; - private Bitmap mSaveCopy = null; private Cache mCache = null; private Cache mHiresCache = null; @@ -74,7 +86,7 @@ public class ImageLoader { public void loadBitmap(Uri uri,int size) { mUri = uri; - mOrientation = getOrientation(uri); + mOrientation = getOrientation(mContext, uri); mOriginalBitmapSmall = loadScaledBitmap(uri, 160); if (mOriginalBitmapSmall == null) { // Couldn't read the bitmap, let's exit @@ -92,14 +104,14 @@ public class ImageLoader { return mOriginalBounds; } - private int getOrientation(Uri uri) { + public static int getOrientation(Context context, Uri uri) { if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return getOrientationFromPath(uri.getPath()); } Cursor cursor = null; try { - cursor = mContext.getContentResolver().query(uri, + cursor = context.getContentResolver().query(uri, new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, @@ -125,7 +137,7 @@ public class ImageLoader { } } - private int getOrientationFromPath(String path) { + static int getOrientationFromPath(String path) { int orientation = -1; try { ExifInterface EXIF = new ExifInterface(path); @@ -139,15 +151,15 @@ public class ImageLoader { private void updateBitmaps() { if (mOrientation > 1) { - mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall,mOrientation); - mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge,mOrientation); + mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation); + mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation); } mCache.setOriginalBitmap(mOriginalBitmapSmall); mHiresCache.setOriginalBitmap(mOriginalBitmapLarge); warnListeners(); } - private Bitmap rotateToPortrait(Bitmap bitmap,int ori) { + public static Bitmap rotateToPortrait(Bitmap bitmap,int ori) { Matrix matrix = new Matrix(); int w = bitmap.getWidth(); int h = bitmap.getHeight(); @@ -344,42 +356,18 @@ public class ImageLoader { mZoomCache.reset(imagePreset); } - public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, + public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, File destination) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inMutable = true; + preset.setIsHighQuality(true); + preset.setScaleFactor(1.0f); + new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() { - if (mFullOriginalBitmap != null) { - mFullOriginalBitmap.recycle(); - } - - InputStream is = null; - Uri saveUri = null; - try { - is = mContext.getContentResolver().openInputStream(mUri); - mFullOriginalBitmap = BitmapFactory.decodeStream(is, null, options); - // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't - // exist) - mFullOriginalBitmap = rotateToPortrait(mFullOriginalBitmap,mOrientation); - mSaveCopy = mFullOriginalBitmap; - preset.setIsHighQuality(true); - preset.setScaleFactor(1.0f); - ProcessedBitmap processedBitmap = new ProcessedBitmap(mSaveCopy, preset); - new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() { - - @Override - public void onComplete(Uri result) { - filterShowActivity.completeSaveImage(result); - } - - }).execute(processedBitmap); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } finally { - closeStream(is); - } + @Override + public void onComplete(Uri result) { + filterShowActivity.completeSaveImage(result); + } - return saveUri; + }).execute(preset); } public void setAdapter(HistoryAdapter adapter) { diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index f303d4c15..0d8fc317e 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -76,6 +76,23 @@ public class ImagePreset { return false; } + public boolean isPanoramaSafe() { + if (mImageBorder != null && !mImageBorder.isNil()) { + return false; + } + if (mGeoData.hasModifications()) { + return false; + } + for (ImageFilter filter : mFilters) { + if (filter.getFilterType() == ImageFilter.TYPE_VIGNETTE + && !filter.isNil()) { + return false; + } + } + return true; + } + + public void setGeometry(GeometryMetadata m) { mGeoData.set(m); } diff --git a/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java b/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java deleted file mode 100644 index 24e38fbb1..000000000 --- a/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.android.gallery3d.filtershow.tools; - -import android.graphics.Bitmap; - -import com.android.gallery3d.filtershow.presets.ImagePreset; - -public class ProcessedBitmap { - private Bitmap mBitmap; - private final ImagePreset mPreset; - public ProcessedBitmap(Bitmap bitmap, ImagePreset preset) { - mBitmap = bitmap; - mPreset = preset; - } - public Bitmap apply() { - mBitmap = mPreset.apply(mBitmap); - return mBitmap; - } -}
\ No newline at end of file diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index 8b3eb530b..9c55623d1 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -21,26 +21,26 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Bitmap.CompressFormat; +import android.media.ExifInterface; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; -import android.view.Gravity; -import android.widget.Toast; +import android.util.Log; -import com.android.camera.R; +import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.presets.ImagePreset; - -//import com.android.gallery3d.R; -//import com.android.gallery3d.util.BucketNames; +import com.android.gallery3d.util.XmpUtilHelper; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.sql.Date; import java.text.SimpleDateFormat; @@ -48,24 +48,29 @@ import java.text.SimpleDateFormat; /** * Asynchronous task for saving edited photo as a new copy. */ -public class SaveCopyTask extends AsyncTask<ProcessedBitmap, Void, Uri> { +public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { + + private static final String LOGTAG = "SaveCopyTask"; private static final int DEFAULT_COMPRESS_QUALITY = 95; private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; /** * Saves the bitmap in the final destination */ - public static void saveBitmap(Bitmap bitmap, File destination) { + public static void saveBitmap(Bitmap bitmap, File destination, Object xmp) { OutputStream os = null; try { os = new FileOutputStream(destination); bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os); } catch (FileNotFoundException e) { - e.printStackTrace(); + Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath()); } finally { closeStream(os); } + if (xmp != null) { + XmpUtilHelper.writeXMPMeta(destination.getAbsolutePath(), xmp); + } } private static void closeStream(Closeable stream) { @@ -132,24 +137,116 @@ public class SaveCopyTask extends AsyncTask<ProcessedBitmap, Void, Uri> { return new File(saveDirectory, filename + ".JPG"); } + private Bitmap loadMutableBitmap() throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't + // exist) + options.inMutable = true; + + InputStream is = context.getContentResolver().openInputStream(sourceUri); + Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + int orientation = ImageLoader.getOrientation(context, sourceUri); + bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); + return bitmap; + } + + private static final String[] COPY_EXIF_ATTRIBUTES = new String[] { + ExifInterface.TAG_APERTURE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_ISO, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_WHITE_BALANCE, + }; + + private static void copyExif(String sourcePath, String destPath) { + try { + ExifInterface source = new ExifInterface(sourcePath); + ExifInterface dest = new ExifInterface(destPath); + boolean needsSave = false; + for (String tag : COPY_EXIF_ATTRIBUTES) { + String value = source.getAttribute(tag); + if (value != null) { + needsSave = true; + dest.setAttribute(tag, value); + } + } + if (needsSave) { + dest.saveAttributes(); + } + } catch (IOException ex) { + Log.w(LOGTAG, "Failed to copy exif metadata", ex); + } + } + + private void copyExif(Uri sourceUri, String destPath) { + if (ContentResolver.SCHEME_FILE.equals(sourceUri.getScheme())) { + copyExif(sourceUri.getPath(), destPath); + return; + } + + final String[] PROJECTION = new String[] { + ImageColumns.DATA + }; + try { + Cursor c = context.getContentResolver().query(sourceUri, PROJECTION, + null, null, null); + if (c.moveToFirst()) { + String path = c.getString(0); + if (new File(path).exists()) { + copyExif(path, destPath); + } + } + c.close(); + } catch (Exception e) { + Log.w(LOGTAG, "Failed to copy exif", e); + } + } + /** * The task should be executed with one given bitmap to be saved. */ @Override - protected Uri doInBackground(ProcessedBitmap... params) { + protected Uri doInBackground(ImagePreset... params) { // TODO: Support larger dimensions for photo saving. if (params[0] == null) { return null; } - ProcessedBitmap processedBitmap = params[0]; + ImagePreset preset = params[0]; - Bitmap bitmap = processedBitmap.apply(); - saveBitmap(bitmap, this.destinationFile); + try { + Bitmap bitmap = preset.apply(loadMutableBitmap()); + + Object xmp = null; + InputStream is = null; + if (preset.isPanoramaSafe()) { + is = context.getContentResolver().openInputStream(sourceUri); + xmp = XmpUtilHelper.extractXMPMeta(is); + } + saveBitmap(bitmap, this.destinationFile, xmp); + copyExif(sourceUri, destinationFile.getAbsolutePath()); + + Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName); + bitmap.recycle(); + return uri; - Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName); - bitmap.recycle(); - return uri; + } catch (FileNotFoundException ex) { + Log.w(LOGTAG, "Failed to save image!", ex); + return null; + } } @Override @@ -210,11 +307,12 @@ public class SaveCopyTask extends AsyncTask<ProcessedBitmap, Void, Uri> { values.put(Images.Media.DATA, file.getAbsolutePath()); values.put(Images.Media.SIZE, file.length()); - String[] projection = new String[] { + final String[] projection = new String[] { ImageColumns.DATE_TAKEN, ImageColumns.LATITUDE, ImageColumns.LONGITUDE, }; - querySource(context, sourceUri, projection, new ContentResolverQueryCallback() { + querySource(context, sourceUri, projection, + new ContentResolverQueryCallback() { @Override public void onCursorResult(Cursor cursor) { |