Home | History | Annotate | Download | only in widgets
      1 /*
      2  * Copyright (C) 2009 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.videoeditor.widgets;
     18 
     19 
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Matrix;
     23 import android.graphics.RectF;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.AttributeSet;
     26 import android.view.MotionEvent;
     27 import android.widget.ImageView;
     28 
     29 /**
     30  * An image view which can be panned and zoomed.
     31  */
     32 public class ImageViewTouchBase extends ImageView {
     33     private static final float SCALE_RATE = 1.25F;
     34     // Zoom scale is applied after the transform that fits the image screen,
     35     // so 1.0 is a perfect fit and it doesn't make sense to allow smaller
     36     // values.
     37     private static final float MIN_ZOOM_SCALE = 1.0f;
     38 
     39     // This is the base transformation which is used to show the image
     40     // initially.  The current computation for this shows the image in
     41     // it's entirety, letterboxing as needed.  One could choose to
     42     // show the image as cropped instead.
     43     //
     44     // This matrix is recomputed when we go from the thumbnail image to
     45     // the full size image.
     46     private Matrix mBaseMatrix = new Matrix();
     47 
     48     // This is the supplementary transformation which reflects what
     49     // the user has done in terms of zooming and panning.
     50     //
     51     // This matrix remains the same when we go from the thumbnail image
     52     // to the full size image.
     53     private Matrix mSuppMatrix = new Matrix();
     54 
     55     // This is the final matrix which is computed as the concatenation
     56     // of the base matrix and the supplementary matrix.
     57     private final Matrix mDisplayMatrix = new Matrix();
     58 
     59     // Temporary buffer used for getting the values out of a matrix.
     60     private final float[] mMatrixValues = new float[9];
     61 
     62     // The current bitmap being displayed.
     63     private Bitmap mBitmapDisplayed;
     64 
     65     // The width and height of the view
     66     private int mThisWidth = -1, mThisHeight = -1;
     67 
     68     private boolean mStretch = true;
     69     // The zoom scale
     70     private float mMaxZoom;
     71     private Runnable mOnLayoutRunnable = null;
     72     private ImageTouchEventListener mEventListener;
     73 
     74     /**
     75      * Touch interface
     76      */
     77     public interface ImageTouchEventListener {
     78         public boolean onImageTouchEvent(MotionEvent ev);
     79     }
     80     /**
     81      * Constructor
     82      *
     83      * @param context The context
     84      */
     85     public ImageViewTouchBase(Context context) {
     86         super(context);
     87         setScaleType(ImageView.ScaleType.MATRIX);
     88     }
     89 
     90     /**
     91      * Constructor
     92      *
     93      * @param context The context
     94      * @param attrs The attributes
     95      */
     96     public ImageViewTouchBase(Context context, AttributeSet attrs) {
     97         super(context, attrs);
     98         setScaleType(ImageView.ScaleType.MATRIX);
     99     }
    100 
    101     /**
    102      * Constructor
    103      *
    104      * @param context The context
    105      * @param attrs The attributes
    106      * @param defStyle The default style
    107      */
    108     public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
    109         super(context, attrs, defStyle);
    110         setScaleType(ImageView.ScaleType.MATRIX);
    111     }
    112 
    113     /*
    114      * {@inheritDoc}
    115      */
    116     @Override
    117     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    118         super.onLayout(changed, left, top, right, bottom);
    119 
    120         mThisWidth = right - left;
    121         mThisHeight = bottom - top;
    122         final Runnable r = mOnLayoutRunnable;
    123         if (r != null) {
    124             mOnLayoutRunnable = null;
    125             r.run();
    126         } else {
    127             if (mBitmapDisplayed != null) {
    128                 getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
    129                 setImageMatrix(getImageViewMatrix());
    130             }
    131         }
    132     }
    133 
    134     /*
    135      * {@inheritDoc}
    136      */
    137     @Override
    138     public boolean dispatchTouchEvent(MotionEvent ev) {
    139         if (mEventListener != null) {
    140             return mEventListener.onImageTouchEvent(ev);
    141         } else {
    142             return false;
    143         }
    144     }
    145 
    146     /*
    147      * {@inheritDoc}
    148      */
    149     @Override
    150     public void setImageBitmap(Bitmap bitmap) {
    151         super.setImageBitmap(bitmap);
    152 
    153         final Drawable d = getDrawable();
    154         if (d != null) {
    155             d.setDither(true);
    156         }
    157 
    158         mBitmapDisplayed = bitmap;
    159     }
    160 
    161     /**
    162      * @param listener The listener
    163      */
    164     public void setEventListener(ImageTouchEventListener listener) {
    165         mEventListener = listener;
    166     }
    167 
    168     /**
    169      * @return The image bitmap
    170      */
    171     public Bitmap getImageBitmap() {
    172         return mBitmapDisplayed;
    173     }
    174 
    175     /**
    176      * If the view has not yet been measured delay the method
    177      *
    178      * @param bitmap The bitmap
    179      * @param resetSupp true to reset the transform matrix
    180      */
    181     public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
    182         mStretch = true;
    183         final int viewWidth = getWidth();
    184         if (viewWidth <= 0) {
    185             mOnLayoutRunnable = new Runnable() {
    186                 @Override
    187                 public void run() {
    188                     setImageBitmapResetBase(bitmap, resetSupp);
    189                 }
    190             };
    191             return;
    192         }
    193 
    194         if (bitmap != null) {
    195             getProperBaseMatrix(bitmap, mBaseMatrix);
    196             setImageBitmap(bitmap);
    197         } else {
    198             mBaseMatrix.reset();
    199             setImageBitmap(null);
    200         }
    201 
    202         if (resetSupp) {
    203             mSuppMatrix.reset();
    204         }
    205 
    206         setImageMatrix(getImageViewMatrix());
    207         mMaxZoom = maxZoom();
    208     }
    209 
    210     /**
    211      * Reset the transform of the current image
    212      */
    213     public void reset() {
    214         if (mBitmapDisplayed != null) {
    215             setImageBitmapResetBase(mBitmapDisplayed, true);
    216         }
    217     }
    218 
    219     /**
    220      * Pan
    221      *
    222      * @param dx The horizontal offset
    223      * @param dy The vertical offset
    224      */
    225     public void postTranslateCenter(float dx, float dy) {
    226         mSuppMatrix.postTranslate(dx, dy);
    227 
    228         center(true, true);
    229     }
    230 
    231     /**
    232      * Pan by the specified horizontal and vertical amount
    233      *
    234      * @param dx Pan by this horizontal amount
    235      * @param dy Pan by this vertical amount
    236      */
    237     private void panBy(float dx, float dy) {
    238         mSuppMatrix.postTranslate(dx, dy);
    239 
    240         setImageMatrix(getImageViewMatrix());
    241     }
    242 
    243     /**
    244      * @return The scale
    245      */
    246     public float getScale() {
    247         return getValue(mSuppMatrix, Matrix.MSCALE_X);
    248     }
    249 
    250     /**
    251      * @param rect The input/output rectangle
    252      */
    253     public void mapRect(RectF rect) {
    254         mSuppMatrix.mapRect(rect);
    255     }
    256 
    257     /**
    258      * Setup the base matrix so that the image is centered and scaled properly.
    259      *
    260      * @param bitmap The bitmap
    261      * @param matrix The matrix
    262      */
    263     private void getProperBaseMatrix(Bitmap bitmap, Matrix matrix) {
    264         final float viewWidth = getWidth();
    265         final float viewHeight = getHeight();
    266 
    267         final float w = bitmap.getWidth();
    268         final float h = bitmap.getHeight();
    269         matrix.reset();
    270 
    271         if (mStretch) {
    272             // We limit up-scaling to 10x otherwise the result may look bad if
    273             // it's a small icon.
    274             float widthScale = Math.min(viewWidth / w, 10.0f);
    275             float heightScale = Math.min(viewHeight / h, 10.0f);
    276             float scale = Math.min(widthScale, heightScale);
    277             matrix.postScale(scale, scale);
    278             matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
    279         } else {
    280             matrix.postTranslate((viewWidth - w) / 2F, (viewHeight - h) / 2F);
    281         }
    282     }
    283 
    284     /**
    285      * Combine the base matrix and the supp matrix to make the final matrix.
    286      */
    287     private Matrix getImageViewMatrix() {
    288         // The final matrix is computed as the concatenation of the base matrix
    289         // and the supplementary matrix.
    290         mDisplayMatrix.set(mBaseMatrix);
    291         mDisplayMatrix.postConcat(mSuppMatrix);
    292         return mDisplayMatrix;
    293     }
    294 
    295     /**
    296      * @return The maximum zoom
    297      */
    298     public float getMaxZoom() {
    299         return mMaxZoom;
    300     }
    301 
    302     /**
    303      * Sets the maximum zoom, which is a scale relative to the base matrix. It
    304      * is calculated to show the image at 400% zoom regardless of screen or
    305      * image orientation. If in the future we decode the full 3 megapixel
    306      * image, rather than the current 1024x768, this should be changed down
    307      * to 200%.
    308      */
    309     private float maxZoom() {
    310         if (mBitmapDisplayed == null) {
    311             return 1F;
    312         }
    313 
    314         final float fw = (float)mBitmapDisplayed.getWidth() / mThisWidth;
    315         final float fh = (float)mBitmapDisplayed.getHeight() / mThisHeight;
    316 
    317         return Math.max(fw, fh) * 4;
    318     }
    319 
    320     /**
    321      * Sets the maximum zoom, which is a scale relative to the base matrix. It
    322      * is calculated to show the image at 400% zoom regardless of screen or
    323      * image orientation. If in the future we decode the full 3 megapixel
    324      * image, rather than the current 1024x768, this should be changed down
    325      * to 200%.
    326      */
    327     public static float maxZoom(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
    328         final float fw = (float)bitmapWidth / viewWidth;
    329         final float fh = (float)bitmapHeight / viewHeight;
    330 
    331         return Math.max(fw, fh) * 4;
    332     }
    333 
    334     /**
    335      * Ensure the scale factor is within limits
    336      *
    337      * @param scale The scale factor
    338      *
    339      * @return The corrected scaled factor
    340      */
    341     private float correctedZoomScale(float scale) {
    342         float result = scale;
    343         if (result > mMaxZoom) {
    344             result = mMaxZoom;
    345         } else if (result < MIN_ZOOM_SCALE) {
    346             result = MIN_ZOOM_SCALE;
    347         }
    348 
    349         return result;
    350     }
    351 
    352     /**
    353      * Zoom to the specified scale factor
    354      *
    355      * @param scale The scale factor
    356      * @param centerX The horizontal center
    357      * @param centerY The vertical center
    358      */
    359     public void zoomTo(float scale, float centerX, float centerY) {
    360         float correctedScale = correctedZoomScale(scale);
    361 
    362         float oldScale = getScale();
    363         float deltaScale = correctedScale / oldScale;
    364 
    365         mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
    366         setImageMatrix(getImageViewMatrix());
    367         center(true, true);
    368     }
    369 
    370     /**
    371      * Zoom to the specified scale factor
    372      *
    373      * @param scale The scale factor
    374      */
    375     public void zoomTo(float scale) {
    376         final float cx = getWidth() / 2F;
    377         final float cy = getHeight() / 2F;
    378 
    379         zoomTo(scale, cx, cy);
    380     }
    381 
    382     /**
    383      * Zoom to the specified scale factor and center point
    384      *
    385      * @param scale The scale factor
    386      * @param pointX The horizontal position
    387      * @param pointY The vertical position
    388      */
    389     public void zoomToPoint(float scale, float pointX, float pointY) {
    390         final float cx = getWidth() / 2F;
    391         final float cy = getHeight() / 2F;
    392 
    393         panBy(cx - pointX, cy - pointY);
    394         zoomTo(scale, cx, cy);
    395     }
    396 
    397     /**
    398      * Zoom to the specified scale factor and point
    399      *
    400      * @param scale The scale factor
    401      * @param pointX The horizontal position
    402      * @param pointY The vertical position
    403      */
    404     public void zoomToOffset(float scale, float pointX, float pointY) {
    405 
    406         float correctedScale = correctedZoomScale(scale);
    407 
    408         float oldScale = getScale();
    409         float deltaScale = correctedScale / oldScale;
    410 
    411         mSuppMatrix.postScale(deltaScale, deltaScale);
    412         setImageMatrix(getImageViewMatrix());
    413 
    414         panBy(-pointX, -pointY);
    415     }
    416 
    417     /**
    418      * Zoom in by a preset scale rate
    419      */
    420     public void zoomIn() {
    421         zoomIn(SCALE_RATE);
    422     }
    423 
    424     /**
    425      * Zoom in by the specified scale rate
    426      *
    427      * @param rate The scale rate
    428      */
    429     public void zoomIn(float rate) {
    430         if (getScale() < mMaxZoom && mBitmapDisplayed != null) {
    431             float cx = getWidth() / 2F;
    432             float cy = getHeight() / 2F;
    433 
    434             mSuppMatrix.postScale(rate, rate, cx, cy);
    435             setImageMatrix(getImageViewMatrix());
    436         }
    437     }
    438 
    439     /**
    440      * Zoom out by a preset scale rate
    441      */
    442     public void zoomOut() {
    443         zoomOut(SCALE_RATE);
    444     }
    445 
    446     /**
    447      * Zoom out by the specified scale rate
    448      *
    449      * @param rate The scale rate
    450      */
    451     public void zoomOut(float rate) {
    452         if (getScale() > MIN_ZOOM_SCALE && mBitmapDisplayed != null) {
    453             float cx = getWidth() / 2F;
    454             float cy = getHeight() / 2F;
    455 
    456             // Zoom out to at most 1x.
    457             Matrix tmp = new Matrix(mSuppMatrix);
    458             tmp.postScale(1F / rate, 1F / rate, cx, cy);
    459 
    460             if (getValue(tmp, Matrix.MSCALE_X) < 1F) {
    461                 mSuppMatrix.setScale(1F, 1F, cx, cy);
    462             } else {
    463                 mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
    464             }
    465             setImageMatrix(getImageViewMatrix());
    466             center(true, true);
    467         }
    468     }
    469 
    470     /**
    471      * Center as much as possible in one or both axis. Centering is
    472      * defined as follows: if the image is scaled down below the
    473      * view's dimensions then center it (literally). If the image
    474      * is scaled larger than the view and is translated out of view
    475      * then translate it back into view (i.e. eliminate black bars).
    476      */
    477     private void center(boolean horizontal, boolean vertical) {
    478         if (mBitmapDisplayed == null) {
    479             return;
    480         }
    481 
    482         final Matrix m = getImageViewMatrix();
    483         final RectF rect = new RectF(0, 0, mBitmapDisplayed.getWidth(),
    484                 mBitmapDisplayed.getHeight());
    485 
    486         m.mapRect(rect);
    487 
    488         final float height = rect.height();
    489         final float width = rect.width();
    490         float deltaX = 0, deltaY = 0;
    491 
    492         if (vertical) {
    493             int viewHeight = getHeight();
    494             if (height < viewHeight) {
    495                 deltaY = (viewHeight - height) / 2 - rect.top;
    496             } else if (rect.top > 0) {
    497                 deltaY = -rect.top;
    498             } else if (rect.bottom < viewHeight) {
    499                 deltaY = getHeight() - rect.bottom;
    500             }
    501         }
    502 
    503         if (horizontal) {
    504             int viewWidth = getWidth();
    505             if (width < viewWidth) {
    506                 deltaX = (viewWidth - width) / 2 - rect.left;
    507             } else if (rect.left > 0) {
    508                 deltaX = -rect.left;
    509             } else if (rect.right < viewWidth) {
    510                 deltaX = viewWidth - rect.right;
    511             }
    512         }
    513 
    514         mSuppMatrix.postTranslate(deltaX, deltaY);
    515 
    516         setImageMatrix(getImageViewMatrix());
    517     }
    518 
    519     /**
    520      * Get a matrix transform value
    521      *
    522      * @param matrix The matrix
    523      * @param whichValue Which value
    524      * @return The value
    525      */
    526     private float getValue(Matrix matrix, int whichValue) {
    527         matrix.getValues(mMatrixValues);
    528         return mMatrixValues[whichValue];
    529     }
    530 }
    531