diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow/tools/SaveImage.java')
-rw-r--r-- | src/com/android/gallery3d/filtershow/tools/SaveImage.java | 632 |
1 files changed, 0 insertions, 632 deletions
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveImage.java b/src/com/android/gallery3d/filtershow/tools/SaveImage.java deleted file mode 100644 index 83cbd0136..000000000 --- a/src/com/android/gallery3d/filtershow/tools/SaveImage.java +++ /dev/null @@ -1,632 +0,0 @@ -/* - * 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.filtershow.tools; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; -import android.provider.MediaStore.Images.ImageColumns; -import android.util.Log; - -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifInterface; -import com.android.gallery3d.filtershow.FilterShowActivity; -import com.android.gallery3d.filtershow.cache.ImageLoader; -import com.android.gallery3d.filtershow.filters.FiltersManager; -import com.android.gallery3d.filtershow.imageshow.MasterImage; -import com.android.gallery3d.filtershow.pipeline.CachingPipeline; -import com.android.gallery3d.filtershow.pipeline.ImagePreset; -import com.android.gallery3d.filtershow.pipeline.ProcessingService; -import com.android.gallery3d.util.UsageStatistics; -import com.android.gallery3d.util.XmpUtilHelper; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.util.TimeZone; - -/** - * Handles saving edited photo - */ -public class SaveImage { - private static final String LOGTAG = "SaveImage"; - - /** - * Callback for updates - */ - public interface Callback { - void onProgress(int max, int current); - } - - public interface ContentResolverQueryCallback { - void onCursorResult(Cursor cursor); - } - - private static final String TIME_STAMP_NAME = "_yyyyMMdd_HHmmss"; - private static final String PREFIX_PANO = "PANO"; - private static final String PREFIX_IMG = "IMG"; - private static final String POSTFIX_JPG = ".jpg"; - private static final String AUX_DIR_NAME = ".aux"; - - private final Context mContext; - private final Uri mSourceUri; - private final Callback mCallback; - private final File mDestinationFile; - private final Uri mSelectedImageUri; - - private int mCurrentProcessingStep = 1; - - public static final int MAX_PROCESSING_STEPS = 6; - public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; - - // In order to support the new edit-save behavior such that user won't see - // the edited image together with the original image, we are adding a new - // auxiliary directory for the edited image. Basically, the original image - // will be hidden in that directory after edit and user will see the edited - // image only. - // Note that deletion on the edited image will also cause the deletion of - // the original image under auxiliary directory. - // - // There are several situations we need to consider: - // 1. User edit local image local01.jpg. A local02.jpg will be created in the - // same directory, and original image will be moved to auxiliary directory as - // ./.aux/local02.jpg. - // If user edit the local02.jpg, local03.jpg will be created in the local - // directory and ./.aux/local02.jpg will be renamed to ./.aux/local03.jpg - // - // 2. User edit remote image remote01.jpg from picassa or other server. - // remoteSavedLocal01.jpg will be saved under proper local directory. - // In remoteSavedLocal01.jpg, there will be a reference pointing to the - // remote01.jpg. There will be no local copy of remote01.jpg. - // If user edit remoteSavedLocal01.jpg, then a new remoteSavedLocal02.jpg - // will be generated and still pointing to the remote01.jpg - // - // 3. User delete any local image local.jpg. - // Since the filenames are kept consistent in auxiliary directory, every - // time a local.jpg get deleted, the files in auxiliary directory whose - // names starting with "local." will be deleted. - // This pattern will facilitate the multiple images deletion in the auxiliary - // directory. - - /** - * @param context - * @param sourceUri The Uri for the original image, which can be the hidden - * image under the auxiliary directory or the same as selectedImageUri. - * @param selectedImageUri The Uri for the image selected by the user. - * In most cases, it is a content Uri for local image or remote image. - * @param destination Destinaton File, if this is null, a new file will be - * created under the same directory as selectedImageUri. - * @param callback Let the caller know the saving has completed. - * @return the newSourceUri - */ - public SaveImage(Context context, Uri sourceUri, Uri selectedImageUri, - File destination, Callback callback) { - mContext = context; - mSourceUri = sourceUri; - mCallback = callback; - if (destination == null) { - mDestinationFile = getNewFile(context, selectedImageUri); - } else { - mDestinationFile = destination; - } - - mSelectedImageUri = selectedImageUri; - } - - public static File getFinalSaveDirectory(Context context, Uri sourceUri) { - File saveDirectory = SaveImage.getSaveDirectory(context, sourceUri); - if ((saveDirectory == null) || !saveDirectory.canWrite()) { - saveDirectory = new File(Environment.getExternalStorageDirectory(), - SaveImage.DEFAULT_SAVE_DIRECTORY); - } - // Create the directory if it doesn't exist - if (!saveDirectory.exists()) - saveDirectory.mkdirs(); - return saveDirectory; - } - - public static File getNewFile(Context context, Uri sourceUri) { - File saveDirectory = getFinalSaveDirectory(context, sourceUri); - String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date( - System.currentTimeMillis())); - if (hasPanoPrefix(context, sourceUri)) { - return new File(saveDirectory, PREFIX_PANO + filename + POSTFIX_JPG); - } - return new File(saveDirectory, PREFIX_IMG + filename + POSTFIX_JPG); - } - - /** - * Remove the files in the auxiliary directory whose names are the same as - * the source image. - * @param contentResolver The application's contentResolver - * @param srcContentUri The content Uri for the source image. - */ - public static void deleteAuxFiles(ContentResolver contentResolver, - Uri srcContentUri) { - final String[] fullPath = new String[1]; - String[] queryProjection = new String[] { ImageColumns.DATA }; - querySourceFromContentResolver(contentResolver, - srcContentUri, queryProjection, - new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - fullPath[0] = cursor.getString(0); - } - } - ); - if (fullPath[0] != null) { - // Construct the auxiliary directory given the source file's path. - // Then select and delete all the files starting with the same name - // under the auxiliary directory. - File currentFile = new File(fullPath[0]); - - String filename = currentFile.getName(); - int firstDotPos = filename.indexOf("."); - final String filenameNoExt = (firstDotPos == -1) ? filename : - filename.substring(0, firstDotPos); - File auxDir = getLocalAuxDirectory(currentFile); - if (auxDir.exists()) { - FilenameFilter filter = new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - if (name.startsWith(filenameNoExt + ".")) { - return true; - } else { - return false; - } - } - }; - - // Delete all auxiliary files whose name is matching the - // current local image. - File[] auxFiles = auxDir.listFiles(filter); - for (File file : auxFiles) { - file.delete(); - } - } - } - } - - public Object getPanoramaXMPData(Uri source, ImagePreset preset) { - Object xmp = null; - if (preset.isPanoramaSafe()) { - InputStream is = null; - try { - is = mContext.getContentResolver().openInputStream(source); - xmp = XmpUtilHelper.extractXMPMeta(is); - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "Failed to get XMP data from image: ", e); - } finally { - Utils.closeSilently(is); - } - } - return xmp; - } - - public boolean putPanoramaXMPData(File file, Object xmp) { - if (xmp != null) { - return XmpUtilHelper.writeXMPMeta(file.getAbsolutePath(), xmp); - } - return false; - } - - public ExifInterface getExifData(Uri source) { - ExifInterface exif = new ExifInterface(); - String mimeType = mContext.getContentResolver().getType(mSelectedImageUri); - if (mimeType == null) { - mimeType = ImageLoader.getMimeType(mSelectedImageUri); - } - if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) { - InputStream inStream = null; - try { - inStream = mContext.getContentResolver().openInputStream(source); - exif.readExif(inStream); - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "Cannot find file: " + source, e); - } catch (IOException e) { - Log.w(LOGTAG, "Cannot read exif for: " + source, e); - } finally { - Utils.closeSilently(inStream); - } - } - return exif; - } - - public boolean putExifData(File file, ExifInterface exif, Bitmap image, - int jpegCompressQuality) { - boolean ret = false; - OutputStream s = null; - try { - s = exif.getExifWriterStream(file.getAbsolutePath()); - image.compress(Bitmap.CompressFormat.JPEG, - (jpegCompressQuality > 0) ? jpegCompressQuality : 1, s); - s.flush(); - s.close(); - s = null; - ret = true; - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "File not found: " + file.getAbsolutePath(), e); - } catch (IOException e) { - Log.w(LOGTAG, "Could not write exif: ", e); - } finally { - Utils.closeSilently(s); - } - return ret; - } - - private Uri resetToOriginalImageIfNeeded(ImagePreset preset, boolean doAuxBackup) { - Uri uri = null; - if (!preset.hasModifications()) { - // This can happen only when preset has no modification but save - // button is enabled, it means the file is loaded with filters in - // the XMP, then all the filters are removed or restore to default. - // In this case, when mSourceUri exists, rename it to the - // destination file. - File srcFile = getLocalFileFromUri(mContext, mSourceUri); - // If the source is not a local file, then skip this renaming and - // create a local copy as usual. - if (srcFile != null) { - srcFile.renameTo(mDestinationFile); - uri = SaveImage.linkNewFileToUri(mContext, mSelectedImageUri, - mDestinationFile, System.currentTimeMillis(), doAuxBackup); - } - } - return uri; - } - - private void resetProgress() { - mCurrentProcessingStep = 0; - } - - private void updateProgress() { - if (mCallback != null) { - mCallback.onProgress(MAX_PROCESSING_STEPS, ++mCurrentProcessingStep); - } - } - - public Uri processAndSaveImage(ImagePreset preset, boolean doAuxBackup, int quality) { - - Uri uri = resetToOriginalImageIfNeeded(preset, doAuxBackup); - if (uri != null) { - return null; - } - - resetProgress(); - - boolean noBitmap = true; - int num_tries = 0; - int sampleSize = 1; - - // If necessary, move the source file into the auxiliary directory, - // newSourceUri is then pointing to the new location. - // If no file is moved, newSourceUri will be the same as mSourceUri. - Uri newSourceUri = mSourceUri; - if (doAuxBackup) { - newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile); - } - - // Stopgap fix for low-memory devices. - while (noBitmap) { - try { - updateProgress(); - // Try to do bitmap operations, downsample if low-memory - Bitmap bitmap = ImageLoader.loadOrientedBitmapWithBackouts(mContext, newSourceUri, - sampleSize); - if (bitmap == null) { - return null; - } - updateProgress(); - CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), - "Saving"); - - bitmap = pipeline.renderFinalImage(bitmap, preset); - updateProgress(); - - Object xmp = getPanoramaXMPData(newSourceUri, preset); - ExifInterface exif = getExifData(newSourceUri); - - updateProgress(); - // Set tags - long time = System.currentTimeMillis(); - exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, time, - TimeZone.getDefault()); - exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, - ExifInterface.Orientation.TOP_LEFT)); - // Remove old thumbnail - exif.removeCompressedThumbnail(); - - updateProgress(); - - // If we succeed in writing the bitmap as a jpeg, return a uri. - if (putExifData(mDestinationFile, exif, bitmap, quality)) { - putPanoramaXMPData(mDestinationFile, xmp); - // mDestinationFile will save the newSourceUri info in the XMP. - XmpPresets.writeFilterXMP(mContext, newSourceUri, - mDestinationFile, preset); - - // After this call, mSelectedImageUri will be actually - // pointing at the new file mDestinationFile. - uri = SaveImage.linkNewFileToUri(mContext, mSelectedImageUri, - mDestinationFile, time, doAuxBackup); - } - updateProgress(); - - noBitmap = false; - UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, - "SaveComplete", null); - } catch (OutOfMemoryError e) { - // Try 5 times before failing for good. - if (++num_tries >= 5) { - throw e; - } - System.gc(); - sampleSize *= 2; - resetProgress(); - } - } - return uri; - } - - /** - * Move the source file to auxiliary directory if needed and return the Uri - * pointing to this new source file. - * @param srcUri Uri to the source image. - * @param dstFile Providing the destination file info to help to build the - * auxiliary directory and new source file's name. - * @return the newSourceUri pointing to the new source image. - */ - private Uri moveSrcToAuxIfNeeded(Uri srcUri, File dstFile) { - File srcFile = getLocalFileFromUri(mContext, srcUri); - if (srcFile == null) { - Log.d(LOGTAG, "Source file is not a local file, no update."); - return srcUri; - } - - // Get the destination directory and create the auxilliary directory - // if necessary. - File auxDiretory = getLocalAuxDirectory(dstFile); - if (!auxDiretory.exists()) { - auxDiretory.mkdirs(); - } - - // Make sure there is a .nomedia file in the auxiliary directory, such - // that MediaScanner will not report those files under this directory. - File noMedia = new File(auxDiretory, ".nomedia"); - if (!noMedia.exists()) { - try { - noMedia.createNewFile(); - } catch (IOException e) { - Log.e(LOGTAG, "Can't create the nomedia"); - return srcUri; - } - } - // We are using the destination file name such that photos sitting in - // the auxiliary directory are matching the parent directory. - File newSrcFile = new File(auxDiretory, dstFile.getName()); - - if (!newSrcFile.exists()) { - srcFile.renameTo(newSrcFile); - } - - return Uri.fromFile(newSrcFile); - - } - - private static File getLocalAuxDirectory(File dstFile) { - File dstDirectory = dstFile.getParentFile(); - File auxDiretory = new File(dstDirectory + "/" + AUX_DIR_NAME); - return auxDiretory; - } - - public static Uri makeAndInsertUri(Context context, Uri sourceUri) { - long time = System.currentTimeMillis(); - String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time)); - File saveDirectory = getFinalSaveDirectory(context, sourceUri); - File file = new File(saveDirectory, filename + ".JPG"); - return linkNewFileToUri(context, sourceUri, file, time, false); - } - - public static void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, - File destination) { - Uri selectedImageUri = filterShowActivity.getSelectedImageUri(); - Uri sourceImageUri = MasterImage.getImage().getUri(); - - Intent processIntent = ProcessingService.getSaveIntent(filterShowActivity, preset, - destination, selectedImageUri, sourceImageUri, false, 90); - - filterShowActivity.startService(processIntent); - - if (!filterShowActivity.isSimpleEditAction()) { - // terminate for now - filterShowActivity.completeSaveImage(selectedImageUri); - } - } - - public static void querySource(Context context, Uri sourceUri, String[] projection, - ContentResolverQueryCallback callback) { - ContentResolver contentResolver = context.getContentResolver(); - querySourceFromContentResolver(contentResolver, sourceUri, projection, callback); - } - - private static void querySourceFromContentResolver( - ContentResolver contentResolver, Uri sourceUri, String[] projection, - ContentResolverQueryCallback callback) { - Cursor cursor = null; - try { - cursor = contentResolver.query(sourceUri, projection, null, null, - null); - if ((cursor != null) && cursor.moveToNext()) { - callback.onCursorResult(cursor); - } - } catch (Exception e) { - // Ignore error for lacking the data column from the source. - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - private static File getSaveDirectory(Context context, Uri sourceUri) { - File file = getLocalFileFromUri(context, sourceUri); - if (file != null) { - return file.getParentFile(); - } else { - return null; - } - } - - /** - * Construct a File object based on the srcUri. - * @return The file object. Return null if srcUri is invalid or not a local - * file. - */ - private static File getLocalFileFromUri(Context context, Uri srcUri) { - if (srcUri == null) { - Log.e(LOGTAG, "srcUri is null."); - return null; - } - - String scheme = srcUri.getScheme(); - if (scheme == null) { - Log.e(LOGTAG, "scheme is null."); - return null; - } - - final File[] file = new File[1]; - // sourceUri can be a file path or a content Uri, it need to be handled - // differently. - if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { - if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) { - querySource(context, srcUri, new String[] { - ImageColumns.DATA - }, - new ContentResolverQueryCallback() { - - @Override - public void onCursorResult(Cursor cursor) { - file[0] = new File(cursor.getString(0)); - } - }); - } - } else if (scheme.equals(ContentResolver.SCHEME_FILE)) { - file[0] = new File(srcUri.getPath()); - } - return file[0]; - } - - /** - * Gets the actual filename for a Uri from Gallery's ContentProvider. - */ - private static String getTrueFilename(Context context, Uri src) { - if (context == null || src == null) { - return null; - } - final String[] trueName = new String[1]; - querySource(context, src, new String[] { - ImageColumns.DATA - }, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - trueName[0] = new File(cursor.getString(0)).getName(); - } - }); - return trueName[0]; - } - - /** - * Checks whether the true filename has the panorama image prefix. - */ - private static boolean hasPanoPrefix(Context context, Uri src) { - String name = getTrueFilename(context, src); - return name != null && name.startsWith(PREFIX_PANO); - } - - /** - * If the <code>sourceUri</code> is a local content Uri, update the - * <code>sourceUri</code> to point to the <code>file</code>. - * At the same time, the old file <code>sourceUri</code> used to point to - * will be removed if it is local. - * If the <code>sourceUri</code> is not a local content Uri, then the - * <code>file</code> will be inserted as a new content Uri. - * @return the final Uri referring to the <code>file</code>. - */ - public static Uri linkNewFileToUri(Context context, Uri sourceUri, - File file, long time, boolean deleteOriginal) { - File oldSelectedFile = getLocalFileFromUri(context, sourceUri); - final ContentValues values = new ContentValues(); - - time /= 1000; - values.put(Images.Media.TITLE, file.getName()); - values.put(Images.Media.DISPLAY_NAME, file.getName()); - values.put(Images.Media.MIME_TYPE, "image/jpeg"); - values.put(Images.Media.DATE_TAKEN, time); - values.put(Images.Media.DATE_MODIFIED, time); - values.put(Images.Media.DATE_ADDED, time); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, file.getAbsolutePath()); - values.put(Images.Media.SIZE, file.length()); - - final String[] projection = new String[] { - ImageColumns.DATE_TAKEN, - ImageColumns.LATITUDE, ImageColumns.LONGITUDE, - }; - SaveImage.querySource(context, sourceUri, projection, - new SaveImage.ContentResolverQueryCallback() { - - @Override - public void onCursorResult(Cursor cursor) { - values.put(Images.Media.DATE_TAKEN, cursor.getLong(0)); - - double latitude = cursor.getDouble(1); - double longitude = cursor.getDouble(2); - // TODO: Change || to && after the default location - // issue is fixed. - if ((latitude != 0f) || (longitude != 0f)) { - values.put(Images.Media.LATITUDE, latitude); - values.put(Images.Media.LONGITUDE, longitude); - } - } - }); - - Uri result = sourceUri; - if (oldSelectedFile == null || !deleteOriginal) { - result = context.getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } else { - context.getContentResolver().update(sourceUri, values, null, null); - if (oldSelectedFile.exists()) { - oldSelectedFile.delete(); - } - } - - return result; - } - -} |