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

Commit 69784065 authored by Michael Jurka's avatar Michael Jurka
Browse files

Add EXIF rotation support to system wallpaper cropper

Bug: 11137824
parent edbab7b5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ public class TiledImageView extends FrameLayout {
        // Guarded by locks
        public float scale;
        public int centerX, centerY;
        int rotation;
        public int rotation;
        public TileSource source;
        Runnable isReadyCallback;

+90 −17
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.wallpapercropper;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
@@ -36,10 +38,18 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
    private long mTouchDownTime;
    private float mFirstX, mFirstY;
    private float mLastX, mLastY;
    private float mCenterX, mCenterY;
    private float mMinScale;
    private boolean mTouchEnabled = true;
    private RectF mTempEdges = new RectF();
    private float[] mTempPoint = new float[] { 0, 0 };
    private float[] mTempCoef = new float[] { 0, 0 };
    private float[] mTempAdjustment = new float[] { 0, 0 };
    private float[] mTempImageDims = new float[] { 0, 0 };
    private float[] mTempRendererCenter = new float[] { 0, 0 };
    TouchCallback mTouchCallback;
    Matrix mRotateMatrix;
    Matrix mInverseRotateMatrix;

    public interface TouchCallback {
        void onTouchDown();
@@ -54,17 +64,43 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
    public CropView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        mRotateMatrix = new Matrix();
        mInverseRotateMatrix = new Matrix();
    }

    private float[] getImageDims() {
        final float imageWidth = mRenderer.source.getImageWidth();
        final float imageHeight = mRenderer.source.getImageHeight();
        float[] imageDims = mTempImageDims;
        imageDims[0] = imageWidth;
        imageDims[1] = imageHeight;
        mRotateMatrix.mapPoints(imageDims);
        imageDims[0] = Math.abs(imageDims[0]);
        imageDims[1] = Math.abs(imageDims[1]);
        return imageDims;
    }

    private void getEdgesHelper(RectF edgesOut) {
        final float width = getWidth();
        final float height = getHeight();
        final float imageWidth = mRenderer.source.getImageWidth();
        final float imageHeight = mRenderer.source.getImageHeight();
        final float[] imageDims = getImageDims();
        final float imageWidth = imageDims[0];
        final float imageHeight = imageDims[1];

        float initialCenterX = mRenderer.source.getImageWidth() / 2f;
        float initialCenterY = mRenderer.source.getImageHeight() / 2f;

        float[] rendererCenter = mTempRendererCenter;
        rendererCenter[0] = mCenterX - initialCenterX;
        rendererCenter[1] = mCenterY - initialCenterY;
        mRotateMatrix.mapPoints(rendererCenter);
        rendererCenter[0] += imageWidth / 2;
        rendererCenter[1] += imageHeight / 2;

        final float scale = mRenderer.scale;
        float centerX = (width / 2f - mRenderer.centerX + (imageWidth - width) / 2f)
        float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
                * scale + width / 2f;
        float centerY = (height / 2f - mRenderer.centerY + (imageHeight - height) / 2f)
        float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
                * scale + height / 2f;
        float leftEdge = centerX - imageWidth / 2f * scale;
        float rightEdge = centerX + imageWidth / 2f * scale;
@@ -77,6 +113,10 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
        edgesOut.bottom = bottomEdge;
    }

    public int getImageRotation() {
        return mRenderer.rotation;
    }

    public RectF getCrop() {
        final RectF edges = mTempEdges;
        getEdgesHelper(edges);
@@ -96,6 +136,12 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {

    public void setTileSource(TileSource source, Runnable isReadyCallback) {
        super.setTileSource(source, isReadyCallback);
        mCenterX = mRenderer.centerX;
        mCenterY = mRenderer.centerY;
        mRotateMatrix.reset();
        mRotateMatrix.setRotate(mRenderer.rotation);
        mInverseRotateMatrix.reset();
        mInverseRotateMatrix.setRotate(-mRenderer.rotation);
        updateMinScale(getWidth(), getHeight(), source, true);
    }

@@ -115,8 +161,10 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
                mRenderer.scale = 1;
            }
            if (source != null) {
                mMinScale = Math.max(w / (float) source.getImageWidth(),
                        h / (float) source.getImageHeight());
                final float[] imageDims = getImageDims();
                final float imageWidth = imageDims[0];
                final float imageHeight = imageDims[1];
                mMinScale = Math.max(w / imageWidth, h / imageHeight);
                mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
            }
        }
@@ -154,7 +202,13 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
        final RectF edges = mTempEdges;
        getEdgesHelper(edges);
        final float scale = mRenderer.scale;
        mRenderer.centerX += Math.ceil(edges.left / scale);
        mCenterX += Math.ceil(edges.left / scale);
        updateCenter();
    }

    private void updateCenter() {
        mRenderer.centerX = Math.round(mCenterX);
        mRenderer.centerY = Math.round(mCenterY);
    }

    public void setTouchEnabled(boolean enabled) {
@@ -215,8 +269,13 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
            mScaleGestureDetector.onTouchEvent(event);
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    mRenderer.centerX += (mLastX - x) / mRenderer.scale;
                    mRenderer.centerY += (mLastY - y) / mRenderer.scale;
                    float[] point = mTempPoint;
                    point[0] = (mLastX - x) / mRenderer.scale;
                    point[1] = (mLastY - y) / mRenderer.scale;
                    mInverseRotateMatrix.mapPoints(point);
                    mCenterX += point[0];
                    mCenterY += point[1];
                    updateCenter();
                    invalidate();
                    break;
            }
@@ -226,18 +285,32 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
                final RectF edges = mTempEdges;
                getEdgesHelper(edges);
                final float scale = mRenderer.scale;

                float[] coef = mTempCoef;
                coef[0] = 1;
                coef[1] = 1;
                mRotateMatrix.mapPoints(coef);
                float[] adjustment = mTempAdjustment;
                mTempAdjustment[0] = 0;
                mTempAdjustment[1] = 0;
                if (edges.left > 0) {
                    mRenderer.centerX += Math.ceil(edges.left / scale);
                }
                if (edges.right < getWidth()) {
                    mRenderer.centerX += (edges.right - getWidth()) / scale;
                    adjustment[0] = edges.left / scale;
                } else if (edges.right < getWidth()) {
                    adjustment[0] = (edges.right - getWidth()) / scale;
                }
                if (edges.top > 0) {
                    mRenderer.centerY += Math.ceil(edges.top / scale);
                    adjustment[1] = FloatMath.ceil(edges.top / scale);
                } else if (edges.bottom < getHeight()) {
                    adjustment[1] = (edges.bottom - getHeight()) / scale;
                }
                if (edges.bottom < getHeight()) {
                    mRenderer.centerY += (edges.bottom - getHeight()) / scale;
                for (int dim = 0; dim <= 1; dim++) {
                    if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
                }

                mInverseRotateMatrix.mapPoints(adjustment);
                mCenterX += adjustment[0];
                mCenterY += adjustment[1];
                updateCenter();
            }
        }

+142 −42
Original line number Diff line number Diff line
@@ -37,12 +37,14 @@ import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.FloatMath;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.photos.BitmapRegionTileSource;

import java.io.BufferedInputStream;
@@ -85,10 +87,17 @@ public class WallpaperCropActivity extends Activity {

        mCropView = (CropView) findViewById(R.id.cropView);

        Intent cropIntent = this.getIntent();
        Intent cropIntent = getIntent();
        final Uri imageUri = cropIntent.getData();

        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, 0), null);
        if (imageUri == null) {
            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
            finish();
            return;
        }

        int rotation = getRotationFromExif(this, imageUri);
        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
        mCropView.setTouchEnabled(true);
        // Action bar
        // Show the custom action bar view
@@ -102,8 +111,6 @@ public class WallpaperCropActivity extends Activity {
                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                    }
                });
        getWindow().addPrivateFlags(
                WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR);
    }

    public boolean enableRotation() {
@@ -170,9 +177,47 @@ public class WallpaperCropActivity extends Activity {
        return new Point(defaultWidth, defaultHeight);
    }

    public static int getRotationFromExif(String path) {
        return getRotationFromExifHelper(path, null, 0, null, null);
    }

    public static int getRotationFromExif(Context context, Uri uri) {
        return getRotationFromExifHelper(null, null, 0, context, uri);
    }

    public static int getRotationFromExif(Resources res, int resId) {
        return getRotationFromExifHelper(null, res, resId, null, null);
    }

    private static int getRotationFromExifHelper(
            String path, Resources res, int resId, Context context, Uri uri) {
        ExifInterface ei = new ExifInterface();
        try {
            if (path != null) {
                ei.readExif(path);
            } else if (uri != null) {
                InputStream is = context.getContentResolver().openInputStream(uri);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
            } else {
                InputStream is = res.openRawResource(resId);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
            }
            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
            if (ori != null) {
                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
            }
        } catch (IOException e) {
            Log.w(LOGTAG, "Getting exif data failed", e);
        }
        return 0;
    }

    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
        BitmapCropTask cropTask = new BitmapCropTask(this,
                filePath, null, 0, 0, true, false, null);
        int rotation = getRotationFromExif(filePath);
        BitmapCropTask cropTask = new BitmapCropTask(
                this, filePath, null, rotation, 0, 0, true, false, null);
        final Point bounds = cropTask.getImageBounds();
        Runnable onEndCrop = new Runnable() {
            public void run() {
@@ -192,6 +237,7 @@ public class WallpaperCropActivity extends Activity {
            Resources res, int resId, final boolean finishActivityWhenDone) {
        // crop this image and scale it down to the default wallpaper size for
        // this device
        int rotation = getRotationFromExif(res, resId);
        Point inSize = mCropView.getSourceDimensions();
        Point outSize = getDefaultWallpaperSize(getResources(),
                getWindowManager());
@@ -209,8 +255,7 @@ public class WallpaperCropActivity extends Activity {
            }
        };
        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
                crop, outSize.x, outSize.y,
                true, false, onEndCrop);
                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
        cropTask.execute();
    }

@@ -222,8 +267,6 @@ public class WallpaperCropActivity extends Activity {
    protected void cropImageAndSetWallpaper(Uri uri,
            OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
        // Get the crop
        Point inSize = mCropView.getSourceDimensions();

        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;

        Point minDims = new Point();
@@ -262,12 +305,21 @@ public class WallpaperCropActivity extends Activity {
        }
        // Get the crop
        RectF cropRect = mCropView.getCrop();
        int cropRotation = mCropView.getImageRotation();
        float cropScale = mCropView.getWidth() / (float) cropRect.width();

        Point inSize = mCropView.getSourceDimensions();
        Matrix rotateMatrix = new Matrix();
        rotateMatrix.setRotate(cropRotation);
        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
        rotateMatrix.mapPoints(rotatedInSize);
        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
        rotatedInSize[1] = Math.abs(rotatedInSize[1]);

        // ADJUST CROP WIDTH
        // Extend the crop all the way to the right, for parallax
        // (or all the way to the left, in RTL)
        float extraSpace = ltr ? inSize.x - cropRect.right : cropRect.left;
        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
        // Cap the amount of extra width
        float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
        extraSpace = Math.min(extraSpace, maxExtraSpace);
@@ -285,7 +337,7 @@ public class WallpaperCropActivity extends Activity {
            float extraPortraitHeight =
                    portraitHeight / cropScale - cropRect.height();
            float expandHeight =
                    Math.min(Math.min(inSize.y - cropRect.bottom, cropRect.top),
                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
                            extraPortraitHeight / 2);
            cropRect.top -= expandHeight;
            cropRect.bottom += expandHeight;
@@ -303,7 +355,7 @@ public class WallpaperCropActivity extends Activity {
            }
        };
        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
                cropRect, outWidth, outHeight, true, false, onEndCrop);
                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
        if (onBitmapCroppedHandler != null) {
            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
        }
@@ -323,7 +375,7 @@ public class WallpaperCropActivity extends Activity {
        InputStream mInStream;
        RectF mCropBounds = null;
        int mOutWidth, mOutHeight;
        int mRotation = 0; // for now
        int mRotation;
        String mOutputFormat = "jpg"; // for now
        boolean mSetWallpaper;
        boolean mSaveCroppedBitmap;
@@ -334,40 +386,45 @@ public class WallpaperCropActivity extends Activity {
        boolean mNoCrop;

        public BitmapCropTask(Context c, String filePath,
                RectF cropBounds, int outWidth, int outHeight,
                RectF cropBounds, int rotation, int outWidth, int outHeight,
                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
            mContext = c;
            mInFilePath = filePath;
            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
            init(cropBounds, rotation,
                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
        }

        public BitmapCropTask(byte[] imageBytes,
                RectF cropBounds, int outWidth, int outHeight,
                RectF cropBounds, int rotation, int outWidth, int outHeight,
                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
            mInImageBytes = imageBytes;
            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
            init(cropBounds, rotation,
                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
        }

        public BitmapCropTask(Context c, Uri inUri,
                RectF cropBounds, int outWidth, int outHeight,
                RectF cropBounds, int rotation, int outWidth, int outHeight,
                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
            mContext = c;
            mInUri = inUri;
            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
            init(cropBounds, rotation,
                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
        }

        public BitmapCropTask(Context c, Resources res, int inResId,
                RectF cropBounds, int outWidth, int outHeight,
                RectF cropBounds, int rotation, int outWidth, int outHeight,
                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
            mContext = c;
            mInResId = inResId;
            mResources = res;
            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
            init(cropBounds, rotation,
                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
        }

        private void init(RectF cropBounds, int outWidth, int outHeight,
        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
            mCropBounds = cropBounds;
            mRotation = rotation;
            mOutWidth = outWidth;
            mOutHeight = outHeight;
            mSetWallpaper = setWallpaper;
@@ -454,6 +511,29 @@ public class WallpaperCropActivity extends Activity {
            if (mInStream != null) {
                // Find crop bounds (scaled to original image size)
                Rect roundedTrueCrop = new Rect();
                Matrix rotateMatrix = new Matrix();
                Matrix inverseRotateMatrix = new Matrix();
                if (mRotation > 0) {
                    rotateMatrix.setRotate(mRotation);
                    inverseRotateMatrix.setRotate(-mRotation);

                    mCropBounds.roundOut(roundedTrueCrop);
                    mCropBounds = new RectF(roundedTrueCrop);

                    Point bounds = getImageBounds();

                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
                    rotateMatrix.mapPoints(rotatedBounds);
                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);

                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
                    inverseRotateMatrix.mapRect(mCropBounds);
                    mCropBounds.offset(bounds.x/2, bounds.y/2);

                    regenerateInputStream();
                }

                mCropBounds.roundOut(roundedTrueCrop);

                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
@@ -497,6 +577,12 @@ public class WallpaperCropActivity extends Activity {
                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
                    }
                    if (fullSize != null) {
                        mCropBounds.left /= scaleDownSampleSize;
                        mCropBounds.top /= scaleDownSampleSize;
                        mCropBounds.bottom /= scaleDownSampleSize;
                        mCropBounds.right /= scaleDownSampleSize;
                        mCropBounds.roundOut(roundedTrueCrop);

                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                                roundedTrueCrop.top, roundedTrueCrop.width(),
                                roundedTrueCrop.height());
@@ -508,16 +594,40 @@ public class WallpaperCropActivity extends Activity {
                    failure = true;
                    return false;
                }
                if (mOutWidth > 0 && mOutHeight > 0) {
                    Matrix m = new Matrix();
                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
                    if (mRotation > 0) {
                        m.setRotate(mRotation);
                        m.mapRect(cropRect);
                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
                    float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
                    rotateMatrix.mapPoints(dimsAfter);
                    dimsAfter[0] = Math.abs(dimsAfter[0]);
                    dimsAfter[1] = Math.abs(dimsAfter[1]);

                    if (!(mOutWidth > 0 && mOutHeight > 0)) {
                        mOutWidth = Math.round(dimsAfter[0]);
                        mOutHeight = Math.round(dimsAfter[1]);
                    }

                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
                    RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);

                    Matrix m = new Matrix();
                    if (mRotation == 0) {
                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
                    m.preRotate(mRotation);
                    } else {
                        Matrix m1 = new Matrix();
                        m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
                        Matrix m2 = new Matrix();
                        m2.setRotate(mRotation);
                        Matrix m3 = new Matrix();
                        m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
                        Matrix m4 = new Matrix();
                        m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);

                        Matrix c1 = new Matrix();
                        c1.setConcat(m2, m1);
                        Matrix c2 = new Matrix();
                        c2.setConcat(m4, m3);
                        m.setConcat(c2, c1);
                    }

                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
                    if (tmp != null) {
@@ -527,14 +637,6 @@ public class WallpaperCropActivity extends Activity {
                        c.drawBitmap(crop, m, p);
                        crop = tmp;
                    }
                } else if (mRotation > 0) {
                    Matrix m = new Matrix();
                    m.setRotate(mRotation);
                    Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
                            crop.getHeight(), m, true);
                    if (tmp != null) {
                        crop = tmp;
                    }
                }

                if (mSaveCroppedBitmap) {
@@ -603,8 +705,7 @@ public class WallpaperCropActivity extends Activity {
            final SharedPreferences sharedPrefs,
            WindowManager windowManager,
            final WallpaperManager wallpaperManager) {
        final Point defaultWallpaperSize =
                WallpaperCropActivity.getDefaultWallpaperSize(res, windowManager);
        final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);

        new Thread("suggestWallpaperDimension") {
            public void run() {
@@ -616,7 +717,6 @@ public class WallpaperCropActivity extends Activity {
        }.start();
    }


    protected static RectF getMaxCropRect(
            int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
        RectF cropRect = new RectF();