Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 014a111f authored by John Reck's avatar John Reck
Browse files

Save XMP & Exif data on edited photos

 Bug: 7293391
 Bug: 7329199
 Bug: 7376660

Change-Id: I23b1637a9a494c1dc43b1fc1359cdaf3e75bc23f
parent d7899c56
Loading
Loading
Loading
Loading
+31 −43
Original line number Diff line number Diff line
/*
 * 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);
@@ -147,7 +159,7 @@ public class ImageLoader {
        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,27 +356,10 @@ 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;

        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
@@ -372,14 +367,7 @@ public class ImageLoader {
                filterShowActivity.completeSaveImage(result);
            }

            }).execute(processedBitmap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            closeStream(is);
        }

        return saveUri;
        }).execute(preset);
    }

    public void setAdapter(HistoryAdapter adapter) {
+17 −0
Original line number Diff line number Diff line
@@ -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);
    }
+0 −18
Original line number Diff line number Diff line
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
+116 −18
Original line number Diff line number Diff line
@@ -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];

        try {
            Bitmap bitmap = preset.apply(loadMutableBitmap());

        Bitmap bitmap = processedBitmap.apply();
        saveBitmap(bitmap, this.destinationFile);
            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;

        } 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) {
+31 −0
Original line number Diff line number Diff line
/*
 * 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.util;

import java.io.InputStream;

public class XmpUtilHelper {

    public static Object extractXMPMeta(InputStream is) {
        return null;
    }

    public static boolean writeXMPMeta(String filename, Object meta) {
        return false;
    }

}