Home | History | Annotate | Download | only in imageshow
      1 /*
      2  * Copyright (C) 2012 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.graphics.Bitmap;
     21 import android.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Matrix;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.Style;
     26 import android.graphics.Path;
     27 import android.graphics.RectF;
     28 import android.util.AttributeSet;
     29 import android.view.MotionEvent;
     30 import android.view.View;
     31 
     32 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
     33 import com.android.gallery3d.filtershow.presets.ImagePreset;
     34 
     35 public abstract class ImageGeometry extends ImageShow {
     36     protected boolean mVisibilityGained = false;
     37     private boolean mHasDrawn = false;
     38 
     39     protected static final float MAX_STRAIGHTEN_ANGLE = 45;
     40     protected static final float MIN_STRAIGHTEN_ANGLE = -45;
     41 
     42     protected float mCenterX;
     43     protected float mCenterY;
     44 
     45     protected float mCurrentX;
     46     protected float mCurrentY;
     47     protected float mTouchCenterX;
     48     protected float mTouchCenterY;
     49 
     50     // Local geometry data
     51     private GeometryMetadata mLocalGeometry = null;
     52     private RectF mLocalDisplayBounds = null;
     53     protected float mXOffset = 0;
     54     protected float mYOffset = 0;
     55 
     56     protected enum MODES {
     57         NONE, DOWN, UP, MOVE
     58     }
     59 
     60     protected MODES mMode = MODES.NONE;
     61 
     62     private static final String LOGTAG = "ImageGeometry";
     63 
     64     public ImageGeometry(Context context, AttributeSet attrs) {
     65         super(context, attrs);
     66     }
     67 
     68     public ImageGeometry(Context context) {
     69         super(context);
     70     }
     71 
     72     private void setupLocalDisplayBounds(RectF b) {
     73         mLocalDisplayBounds = b;
     74         calculateLocalScalingFactorAndOffset();
     75     }
     76 
     77     protected static float angleFor(float dx, float dy) {
     78         return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
     79     }
     80 
     81     protected static int snappedAngle(float angle) {
     82         float remainder = angle % 90;
     83         int current = (int) (angle / 90); // truncates
     84         if (remainder < -45) {
     85             --current;
     86         } else if (remainder > 45) {
     87             ++current;
     88         }
     89         return current * 90;
     90     }
     91 
     92     protected float getCurrentTouchAngle() {
     93         if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
     94             return 0;
     95         }
     96         float dX1 = mTouchCenterX - mCenterX;
     97         float dY1 = mTouchCenterY - mCenterY;
     98         float dX2 = mCurrentX - mCenterX;
     99         float dY2 = mCurrentY - mCenterY;
    100 
    101         float angleA = angleFor(dX1, dY1);
    102         float angleB = angleFor(dX2, dY2);
    103         return (angleB - angleA) % 360;
    104     }
    105 
    106     protected float computeScale(float width, float height) {
    107         float imageWidth = mLocalGeometry.getPhotoBounds().width();
    108         float imageHeight = mLocalGeometry.getPhotoBounds().height();
    109         return GeometryMath.scale(imageWidth, imageHeight, width, height);
    110     }
    111 
    112     private void calculateLocalScalingFactorAndOffset() {
    113         if (mLocalGeometry == null || mLocalDisplayBounds == null)
    114             return;
    115         RectF imageBounds = mLocalGeometry.getPhotoBounds();
    116         float imageWidth = imageBounds.width();
    117         float imageHeight = imageBounds.height();
    118         float displayWidth = mLocalDisplayBounds.width();
    119         float displayHeight = mLocalDisplayBounds.height();
    120 
    121         mCenterX = displayWidth / 2;
    122         mCenterY = displayHeight / 2;
    123         mYOffset = (displayHeight - imageHeight) / 2.0f;
    124         mXOffset = (displayWidth - imageWidth) / 2.0f;
    125         updateScale();
    126     }
    127 
    128     @Override
    129     public void resetParameter() {
    130         super.resetParameter();
    131         setLocalRotation(0);
    132         setLocalStraighten(0);
    133         setLocalCropBounds(getLocalPhotoBounds());
    134         setLocalFlip(FLIP.NONE);
    135         saveAndSetPreset();
    136         invalidate();
    137     }
    138 
    139     // Overwrites local with master
    140     public void syncLocalToMasterGeometry() {
    141         mLocalGeometry = getGeometry();
    142         calculateLocalScalingFactorAndOffset();
    143     }
    144 
    145     protected RectF getLocalPhotoBounds() {
    146         return mLocalGeometry.getPhotoBounds();
    147     }
    148 
    149     protected RectF getLocalCropBounds() {
    150         return mLocalGeometry.getPreviewCropBounds();
    151     }
    152 
    153     protected RectF getLocalDisplayBounds() {
    154         return new RectF(mLocalDisplayBounds);
    155     }
    156 
    157     protected float getLocalScale() {
    158         return mLocalGeometry.getScaleFactor();
    159     }
    160 
    161     protected float getLocalRotation() {
    162         return mLocalGeometry.getRotation();
    163     }
    164 
    165     protected float getLocalStraighten() {
    166         return mLocalGeometry.getStraightenRotation();
    167     }
    168 
    169     protected void setLocalScale(float s) {
    170         mLocalGeometry.setScaleFactor(s);
    171     }
    172 
    173     protected void updateScale() {
    174         RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
    175                 getLocalStraighten());
    176         float zoom = computeScale(bounds.width(), bounds.height());
    177         setLocalScale(zoom);
    178     }
    179 
    180     protected void setLocalRotation(float r) {
    181         mLocalGeometry.setRotation(r);
    182         updateScale();
    183     }
    184 
    185     /**
    186      * Constrains rotation to be in [0, 90, 180, 270].
    187      */
    188     protected int constrainedRotation(float rotation) {
    189         int r = (int) ((rotation % 360) / 90);
    190         r = (r < 0) ? (r + 4) : r;
    191         return r * 90;
    192     }
    193 
    194     protected boolean isHeightWidthSwapped() {
    195         return ((int) (getLocalRotation() / 90)) % 2 != 0;
    196     }
    197 
    198     protected void setLocalStraighten(float r) {
    199         mLocalGeometry.setStraightenRotation(r);
    200         updateScale();
    201     }
    202 
    203     protected void setLocalCropBounds(RectF c) {
    204         mLocalGeometry.setCropBounds(c);
    205         updateScale();
    206     }
    207 
    208     protected FLIP getLocalFlip() {
    209         return mLocalGeometry.getFlipType();
    210     }
    211 
    212     protected void setLocalFlip(FLIP flip) {
    213         mLocalGeometry.setFlipType(flip);
    214     }
    215 
    216     protected float getTotalLocalRotation() {
    217         return getLocalRotation() + getLocalStraighten();
    218     }
    219 
    220     protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
    221         Path crop = new Path();
    222         crop.moveTo(points[0], points[1]);
    223         crop.lineTo(points[2], points[3]);
    224         crop.lineTo(points[4], points[5]);
    225         crop.lineTo(points[6], points[7]);
    226         crop.close();
    227         canvas.drawPath(crop, paint);
    228         return crop;
    229     }
    230 
    231     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
    232         return width * h / w;
    233     }
    234 
    235     protected static float getNewWidthForHeightAspect(float height, float w, float h) {
    236         return height * w / h;
    237     }
    238 
    239     @Override
    240     protected void onVisibilityChanged(View changedView, int visibility) {
    241         super.onVisibilityChanged(changedView, visibility);
    242         if (visibility == View.VISIBLE) {
    243             mVisibilityGained = true;
    244             MasterImage.getImage().invalidateFiltersOnly();
    245             syncLocalToMasterGeometry();
    246             updateScale();
    247             gainedVisibility();
    248         } else {
    249             if (mVisibilityGained == true && mHasDrawn == true) {
    250                 lostVisibility();
    251             }
    252             mVisibilityGained = false;
    253             mHasDrawn = false;
    254         }
    255     }
    256 
    257     protected void gainedVisibility() {
    258         // Override this stub.
    259     }
    260 
    261     protected void lostVisibility() {
    262         // Override this stub.
    263     }
    264 
    265     @Override
    266     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    267         super.onSizeChanged(w, h, oldw, oldh);
    268         setupLocalDisplayBounds(new RectF(0, 0, w, h));
    269     }
    270 
    271     @Override
    272     public boolean onTouchEvent(MotionEvent event) {
    273         switch (event.getActionMasked()) {
    274             case (MotionEvent.ACTION_DOWN):
    275                 setActionDown(event.getX(), event.getY());
    276                 break;
    277             case (MotionEvent.ACTION_UP):
    278                 setActionUp();
    279                 saveAndSetPreset();
    280                 break;
    281             case (MotionEvent.ACTION_MOVE):
    282                 setActionMove(event.getX(), event.getY());
    283                 break;
    284             default:
    285                 setNoAction();
    286         }
    287         invalidate();
    288         return true;
    289     }
    290 
    291     protected int getLocalValue() {
    292         return 0; // Override this
    293     }
    294 
    295     protected void setActionDown(float x, float y) {
    296         mTouchCenterX = x;
    297         mTouchCenterY = y;
    298         mCurrentX = x;
    299         mCurrentY = y;
    300         mMode = MODES.DOWN;
    301     }
    302 
    303     protected void setActionMove(float x, float y) {
    304         mCurrentX = x;
    305         mCurrentY = y;
    306         mMode = MODES.MOVE;
    307     }
    308 
    309     protected void setActionUp() {
    310         mMode = MODES.UP;
    311     }
    312 
    313     protected void setNoAction() {
    314         mMode = MODES.NONE;
    315     }
    316 
    317     @Override
    318     public boolean showTitle() {
    319         return false;
    320     }
    321 
    322     public String getName() {
    323         return "Geometry";
    324     }
    325 
    326     public void saveAndSetPreset() {
    327         ImagePreset lastHistoryItem = MasterImage.getImage().getHistory().getLast();
    328         if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
    329             getImagePreset().setGeometry(mLocalGeometry);
    330             resetImageCaches(this);
    331         } else {
    332             if (mLocalGeometry.hasModifications()) {
    333                 ImagePreset copy = new ImagePreset(getImagePreset());
    334                 copy.setGeometry(mLocalGeometry);
    335                 copy.setHistoryName(getName());
    336                 copy.setIsFx(false);
    337                 MasterImage.getImage().setPreset(copy, true);
    338             }
    339         }
    340         invalidate();
    341     }
    342 
    343     public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
    344         float deg = straightenAngle;
    345         if (deg < 0) {
    346             deg = -deg;
    347         }
    348         double a = Math.toRadians(deg);
    349         double sina = Math.sin(a);
    350         double cosa = Math.cos(a);
    351 
    352         double rw = imageRect.width();
    353         double rh = imageRect.height();
    354         double h1 = rh * rh / (rw * sina + rh * cosa);
    355         double h2 = rh * rw / (rw * cosa + rh * sina);
    356         double hh = Math.min(h1, h2);
    357         double ww = hh * rw / rh;
    358 
    359         float left = (float) ((rw - ww) * 0.5f);
    360         float top = (float) ((rh - hh) * 0.5f);
    361         float right = (float) (left + ww);
    362         float bottom = (float) (top + hh);
    363 
    364         return new RectF(left, top, right, bottom);
    365     }
    366 
    367     protected RectF straightenBounds() {
    368         RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
    369                 getLocalStraighten());
    370         float scale = computeScale(getWidth(), getHeight());
    371         bounds = GeometryMath.scaleRect(bounds, scale);
    372         float dx = (getWidth() / 2) - bounds.centerX();
    373         float dy = (getHeight() / 2) - bounds.centerY();
    374         bounds.offset(dx, dy);
    375         return bounds;
    376     }
    377 
    378     protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds,
    379             RectF outerBounds,
    380             float rotation, float centerX, float centerY) {
    381         canvas.save();
    382         canvas.rotate(rotation, centerX, centerY);
    383 
    384         float x = (outerBounds.left - outerBounds.right);
    385         float y = (outerBounds.top - outerBounds.bottom);
    386         float longest = (float) Math.sqrt(x * x + y * y) / 2;
    387         float minX = centerX - longest;
    388         float maxX = centerX + longest;
    389         float minY = centerY - longest;
    390         float maxY = centerY + longest;
    391         canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p);
    392         canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p);
    393         canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY,
    394                 p);
    395         canvas.drawRect(innerBounds.right, minY, maxX,
    396                 innerBounds.bottom, p);
    397         canvas.rotate(-rotation, centerX, centerY);
    398         canvas.restore();
    399     }
    400 
    401     protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
    402         float w = getWidth();
    403         float h = getHeight();
    404         canvas.drawRect(0f, 0f, w, innerBounds.top, p);
    405         canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p);
    406         canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p);
    407         canvas.drawRect(0f, innerBounds.bottom, w, h, p);
    408     }
    409 
    410     @Override
    411     public void onDraw(Canvas canvas) {
    412         if (getDirtyGeometryFlag()) {
    413             syncLocalToMasterGeometry();
    414             clearDirtyGeometryFlag();
    415         }
    416         Bitmap image = getFiltersOnlyImage();
    417         if (image == null) {
    418             invalidate();
    419             return;
    420         }
    421         mHasDrawn = true;
    422 
    423         drawShape(canvas, image);
    424     }
    425 
    426     protected void drawShape(Canvas canvas, Bitmap image) {
    427         // TODO: Override this stub.
    428     }
    429 
    430     /**
    431      * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
    432      * and returns the scale factor.
    433      */
    434     protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) {
    435         RectF photoBounds = getLocalPhotoBounds();
    436         RectF cropBounds = getLocalCropBounds();
    437         float scale = computeScale(getWidth(), getHeight());
    438         // checks if local rotation is an odd multiple of 90.
    439         if (isHeightWidthSwapped()) {
    440             scale = computeScale(getHeight(), getWidth());
    441         }
    442         // put in screen coordinates
    443         if (crop != null) {
    444             crop.set(GeometryMath.scaleRect(cropBounds, scale));
    445         }
    446         if (photo != null) {
    447             photo.set(GeometryMath.scaleRect(photoBounds, scale));
    448         }
    449         if (displayCenter != null && displayCenter.length >= 2) {
    450             displayCenter[0] = getWidth() / 2f;
    451             displayCenter[1] = getHeight() / 2f;
    452         }
    453         return scale;
    454     }
    455 
    456     protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) {
    457         p.setARGB(255, 0, 0, 0);
    458         float[] displayCenter = new float[2];
    459         RectF scaledCrop = new RectF();
    460         RectF scaledPhoto = new RectF();
    461         float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
    462         Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
    463                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
    464 
    465         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
    466                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
    467         m1.mapRect(scaledCrop);
    468         Path path = new Path();
    469         scaledCrop.offset(-offset[0], -offset[1]);
    470         path.addRect(scaledCrop, Path.Direction.CCW);
    471 
    472         m.preScale(scale, scale);
    473         m.postTranslate(-offset[0], -offset[1]);
    474         canvas.save();
    475         canvas.drawBitmap(photo, m, p);
    476         canvas.restore();
    477 
    478         p.setColor(Color.WHITE);
    479         p.setStyle(Style.STROKE);
    480         p.setStrokeWidth(2);
    481         canvas.drawPath(path, p);
    482 
    483         p.setColor(getDefaultBackgroundColor());
    484         p.setAlpha(128);
    485         p.setStyle(Paint.Style.FILL);
    486         drawShadows(canvas, p, scaledCrop);
    487         return scaledCrop;
    488     }
    489 
    490     protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p) {
    491         RectF photoBounds = getLocalPhotoBounds();
    492         RectF cropBounds = getLocalCropBounds();
    493         float imageWidth = cropBounds.width();
    494         float imageHeight = cropBounds.height();
    495         float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
    496         // checks if local rotation is an odd multiple of 90.
    497         if (isHeightWidthSwapped()) {
    498             scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
    499         }
    500         // put in screen coordinates
    501         RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
    502         RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
    503         float[] displayCenter = {
    504                 getWidth() / 2f, getHeight() / 2f
    505         };
    506         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
    507                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
    508         float[] cropCenter = {
    509                 scaledCrop.centerX(), scaledCrop.centerY()
    510         };
    511         m1.mapPoints(cropCenter);
    512         GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
    513         m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
    514         m1.preScale(scale, scale);
    515 
    516         p.setARGB(255, 0, 0, 0);
    517         canvas.save();
    518         canvas.drawBitmap(photo, m1, p);
    519         canvas.restore();
    520 
    521         p.setColor(getDefaultBackgroundColor());
    522         p.setStyle(Paint.Style.FILL);
    523         scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
    524                 - scaledCrop.centerY());
    525         RectF display = new RectF(0, 0, getWidth(), getHeight());
    526         drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2,
    527                 getHeight() / 2);
    528     }
    529 }
    530