Home | History | Annotate | Download | only in imageshow
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.filtershow.imageshow;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Canvas;
     23 import android.graphics.Matrix;
     24 import android.graphics.Paint;
     25 import android.graphics.RectF;
     26 import android.graphics.drawable.Drawable;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.view.MotionEvent;
     30 
     31 import com.android.gallery3d.R;
     32 import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
     33 import com.android.gallery3d.filtershow.crop.CropMath;
     34 import com.android.gallery3d.filtershow.crop.CropObject;
     35 import com.android.gallery3d.filtershow.editors.EditorCrop;
     36 import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
     37 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
     38 
     39 public class ImageCrop extends ImageShow {
     40     private static final String TAG = ImageCrop.class.getSimpleName();
     41     private RectF mImageBounds = new RectF();
     42     private RectF mScreenCropBounds = new RectF();
     43     private Paint mPaint = new Paint();
     44     private CropObject mCropObj = null;
     45     private GeometryHolder mGeometry = new GeometryHolder();
     46     private GeometryHolder mUpdateHolder = new GeometryHolder();
     47     private Drawable mCropIndicator;
     48     private int mIndicatorSize;
     49     private boolean mMovingBlock = false;
     50     private Matrix mDisplayMatrix = null;
     51     private Matrix mDisplayCropMatrix = null;
     52     private Matrix mDisplayMatrixInverse = null;
     53     private float mPrevX = 0;
     54     private float mPrevY = 0;
     55     private int mMinSideSize = 90;
     56     private int mTouchTolerance = 40;
     57     private enum Mode {
     58         NONE, MOVE
     59     }
     60     private Mode mState = Mode.NONE;
     61     private boolean mValidDraw = false;
     62     FilterCropRepresentation mLocalRep = new FilterCropRepresentation();
     63     EditorCrop mEditorCrop;
     64 
     65     public ImageCrop(Context context) {
     66         super(context);
     67         setup(context);
     68     }
     69 
     70     public ImageCrop(Context context, AttributeSet attrs) {
     71         super(context, attrs);
     72         setup(context);
     73     }
     74 
     75     public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
     76         super(context, attrs, defStyle);
     77         setup(context);
     78     }
     79 
     80     private void setup(Context context) {
     81         Resources rsc = context.getResources();
     82         mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
     83         mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
     84         mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
     85         mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
     86     }
     87 
     88     public void setFilterCropRepresentation(FilterCropRepresentation crop) {
     89         mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
     90         GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
     91         mValidDraw = true;
     92     }
     93 
     94     public FilterCropRepresentation getFinalRepresentation() {
     95         return mLocalRep;
     96     }
     97 
     98     private void internallyUpdateLocalRep(RectF crop, RectF image) {
     99         FilterCropRepresentation
    100                 .findNormalizedCrop(crop, (int) image.width(), (int) image.height());
    101         mGeometry.crop.set(crop);
    102         mUpdateHolder.set(mGeometry);
    103         mLocalRep.setCrop(crop);
    104     }
    105 
    106     @Override
    107     public boolean onTouchEvent(MotionEvent event) {
    108         float x = event.getX();
    109         float y = event.getY();
    110         if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
    111             return true;
    112         }
    113         float[] touchPoint = {
    114                 x, y
    115         };
    116         mDisplayMatrixInverse.mapPoints(touchPoint);
    117         x = touchPoint[0];
    118         y = touchPoint[1];
    119         switch (event.getActionMasked()) {
    120             case (MotionEvent.ACTION_DOWN):
    121                 if (mState == Mode.NONE) {
    122                     if (!mCropObj.selectEdge(x, y)) {
    123                         mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
    124                     }
    125                     mPrevX = x;
    126                     mPrevY = y;
    127                     mState = Mode.MOVE;
    128                 }
    129                 break;
    130             case (MotionEvent.ACTION_UP):
    131                 if (mState == Mode.MOVE) {
    132                     mCropObj.selectEdge(CropObject.MOVE_NONE);
    133                     mMovingBlock = false;
    134                     mPrevX = x;
    135                     mPrevY = y;
    136                     mState = Mode.NONE;
    137                     internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
    138                 }
    139                 break;
    140             case (MotionEvent.ACTION_MOVE):
    141                 if (mState == Mode.MOVE) {
    142                     float dx = x - mPrevX;
    143                     float dy = y - mPrevY;
    144                     mCropObj.moveCurrentSelection(dx, dy);
    145                     mPrevX = x;
    146                     mPrevY = y;
    147                 }
    148                 break;
    149             default:
    150                 break;
    151         }
    152         invalidate();
    153         return true;
    154     }
    155 
    156     private void clearDisplay() {
    157         mDisplayMatrix = null;
    158         mDisplayMatrixInverse = null;
    159         invalidate();
    160     }
    161 
    162     public void applyFreeAspect() {
    163         mCropObj.unsetAspectRatio();
    164         invalidate();
    165     }
    166 
    167     public void applyOriginalAspect() {
    168         RectF outer = mCropObj.getOuterBounds();
    169         float w = outer.width();
    170         float h = outer.height();
    171         if (w > 0 && h > 0) {
    172             applyAspect(w, h);
    173             mCropObj.resetBoundsTo(outer, outer);
    174             internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
    175         } else {
    176             Log.w(TAG, "failed to set aspect ratio original");
    177         }
    178         invalidate();
    179     }
    180 
    181     public void applyAspect(float x, float y) {
    182         if (x <= 0 || y <= 0) {
    183             throw new IllegalArgumentException("Bad arguments to applyAspect");
    184         }
    185         // If we are rotated by 90 degrees from horizontal, swap x and y
    186         if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
    187             float tmp = x;
    188             x = y;
    189             y = tmp;
    190         }
    191         if (!mCropObj.setInnerAspectRatio(x, y)) {
    192             Log.w(TAG, "failed to set aspect ratio");
    193         }
    194         internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
    195         invalidate();
    196     }
    197 
    198     /**
    199      * Rotates first d bits in integer x to the left some number of times.
    200      */
    201     private int bitCycleLeft(int x, int times, int d) {
    202         int mask = (1 << d) - 1;
    203         int mout = x & mask;
    204         times %= d;
    205         int hi = mout >> (d - times);
    206         int low = (mout << times) & mask;
    207         int ret = x & ~mask;
    208         ret |= low;
    209         ret |= hi;
    210         return ret;
    211     }
    212 
    213     /**
    214      * Find the selected edge or corner in screen coordinates.
    215      */
    216     private int decode(int movingEdges, float rotation) {
    217         int rot = CropMath.constrainedRotation(rotation);
    218         switch (rot) {
    219             case 90:
    220                 return bitCycleLeft(movingEdges, 1, 4);
    221             case 180:
    222                 return bitCycleLeft(movingEdges, 2, 4);
    223             case 270:
    224                 return bitCycleLeft(movingEdges, 3, 4);
    225             default:
    226                 return movingEdges;
    227         }
    228     }
    229 
    230     private void forceStateConsistency() {
    231         MasterImage master = MasterImage.getImage();
    232         Bitmap image = master.getFiltersOnlyImage();
    233         int width = image.getWidth();
    234         int height = image.getHeight();
    235         if (mCropObj == null || !mUpdateHolder.equals(mGeometry)
    236                 || mImageBounds.width() != width || mImageBounds.height() != height
    237                 || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
    238             mImageBounds.set(0, 0, width, height);
    239             mGeometry.set(mUpdateHolder);
    240             mLocalRep.setCrop(mUpdateHolder.crop);
    241             RectF scaledCrop = new RectF(mUpdateHolder.crop);
    242             FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
    243             mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
    244             mState = Mode.NONE;
    245             clearDisplay();
    246         }
    247     }
    248 
    249     @Override
    250     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    251         super.onSizeChanged(w, h, oldw, oldh);
    252         clearDisplay();
    253     }
    254 
    255     @Override
    256     public void onDraw(Canvas canvas) {
    257         Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
    258         if (bitmap == null) {
    259             MasterImage.getImage().invalidateFiltersOnly();
    260         }
    261         if (!mValidDraw || bitmap == null) {
    262             return;
    263         }
    264         forceStateConsistency();
    265         mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
    266         // If display matrix doesn't exist, create it and its dependencies
    267         if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
    268             mCropObj.unsetAspectRatio();
    269             mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
    270                     bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
    271             float straighten = mGeometry.straighten;
    272             mGeometry.straighten = 0;
    273             mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
    274                     bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
    275             mGeometry.straighten = straighten;
    276             mDisplayMatrixInverse = new Matrix();
    277             mDisplayMatrixInverse.reset();
    278             if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
    279                 Log.w(TAG, "could not invert display matrix");
    280                 mDisplayMatrixInverse = null;
    281                 return;
    282             }
    283             // Scale min side and tolerance by display matrix scale factor
    284             mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
    285             mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
    286             // drive Crop engine to clamp to crop bounds
    287             int[] sides = {CropObject.MOVE_TOP,
    288                     CropObject.MOVE_BOTTOM,
    289                     CropObject.MOVE_LEFT,
    290                     CropObject.MOVE_RIGHT};
    291             int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
    292             int[] dy = {delta, -delta, 0, 0};
    293             int[] dx = {0, 0, delta, -delta};
    294 
    295             for (int i = 0; i < sides.length; i++) {
    296                 mCropObj.selectEdge(sides[i]);
    297 
    298                 mCropObj.moveCurrentSelection(dx[i], dy[i]);
    299                 mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
    300             }
    301             mCropObj.selectEdge(CropObject.MOVE_NONE);
    302         }
    303         // Draw actual bitmap
    304         mPaint.reset();
    305         mPaint.setAntiAlias(true);
    306         mPaint.setFilterBitmap(true);
    307         canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
    308         mCropObj.getInnerBounds(mScreenCropBounds);
    309         RectF outer = mCropObj.getOuterBounds();
    310         FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(),
    311                 (int) outer.height());
    312         FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(),
    313                 bitmap.getHeight());
    314         if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
    315             // Draw crop rect and markers
    316             CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
    317             CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
    318             CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
    319             CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
    320                     mScreenCropBounds, mCropObj.isFixedAspect(),
    321                     decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
    322         }
    323     }
    324 
    325     public void setEditor(EditorCrop editorCrop) {
    326         mEditorCrop = editorCrop;
    327     }
    328 }
    329