Home | History | Annotate | Download | only in camera
      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.camera;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Matrix;
     22 import android.graphics.RectF;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Handler;
     25 import android.util.AttributeSet;
     26 import android.view.KeyEvent;
     27 import android.widget.ImageView;
     28 
     29 abstract class ImageViewTouchBase extends ImageView {
     30 
     31     @SuppressWarnings("unused")
     32     private static final String TAG = "ImageViewTouchBase";
     33 
     34     // This is the base transformation which is used to show the image
     35     // initially.  The current computation for this shows the image in
     36     // it's entirety, letterboxing as needed.  One could choose to
     37     // show the image as cropped instead.
     38     //
     39     // This matrix is recomputed when we go from the thumbnail image to
     40     // the full size image.
     41     protected Matrix mBaseMatrix = new Matrix();
     42 
     43     // This is the supplementary transformation which reflects what
     44     // the user has done in terms of zooming and panning.
     45     //
     46     // This matrix remains the same when we go from the thumbnail image
     47     // to the full size image.
     48     protected Matrix mSuppMatrix = new Matrix();
     49 
     50     // This is the final matrix which is computed as the concatentation
     51     // of the base matrix and the supplementary matrix.
     52     private final Matrix mDisplayMatrix = new Matrix();
     53 
     54     // Temporary buffer used for getting the values out of a matrix.
     55     private final float[] mMatrixValues = new float[9];
     56 
     57     // The current bitmap being displayed.
     58     protected final RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
     59 
     60     int mThisWidth = -1, mThisHeight = -1;
     61 
     62     float mMaxZoom;
     63 
     64     // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
     65     // its use of that Bitmap.
     66     public interface Recycler {
     67         public void recycle(Bitmap b);
     68     }
     69 
     70     public void setRecycler(Recycler r) {
     71         mRecycler = r;
     72     }
     73 
     74     private Recycler mRecycler;
     75 
     76     @Override
     77     protected void onLayout(boolean changed, int left, int top,
     78                             int right, int bottom) {
     79         super.onLayout(changed, left, top, right, bottom);
     80         mThisWidth = right - left;
     81         mThisHeight = bottom - top;
     82         Runnable r = mOnLayoutRunnable;
     83         if (r != null) {
     84             mOnLayoutRunnable = null;
     85             r.run();
     86         }
     87         if (mBitmapDisplayed.getBitmap() != null) {
     88             getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
     89             setImageMatrix(getImageViewMatrix());
     90         }
     91     }
     92 
     93     @Override
     94     public boolean onKeyDown(int keyCode, KeyEvent event) {
     95         if (keyCode == KeyEvent.KEYCODE_BACK
     96                 && event.getRepeatCount() == 0) {
     97             event.startTracking();
     98             return true;
     99         }
    100         return super.onKeyDown(keyCode, event);
    101     }
    102 
    103     @Override
    104     public boolean onKeyUp(int keyCode, KeyEvent event) {
    105         if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
    106                 && !event.isCanceled()) {
    107             if (getScale() > 1.0f) {
    108                 // If we're zoomed in, pressing Back jumps out to show the
    109                 // entire image, otherwise Back returns the user to the gallery.
    110                 zoomTo(1.0f);
    111                 return true;
    112             }
    113         }
    114         return super.onKeyUp(keyCode, event);
    115     }
    116 
    117     protected Handler mHandler = new Handler();
    118 
    119     @Override
    120     public void setImageBitmap(Bitmap bitmap) {
    121         setImageBitmap(bitmap, 0);
    122     }
    123 
    124     private void setImageBitmap(Bitmap bitmap, int rotation) {
    125         super.setImageBitmap(bitmap);
    126         Drawable d = getDrawable();
    127         if (d != null) {
    128             d.setDither(true);
    129         }
    130 
    131         Bitmap old = mBitmapDisplayed.getBitmap();
    132         mBitmapDisplayed.setBitmap(bitmap);
    133         mBitmapDisplayed.setRotation(rotation);
    134 
    135         if (old != null && old != bitmap && mRecycler != null) {
    136             mRecycler.recycle(old);
    137         }
    138     }
    139 
    140     public void clear() {
    141         setImageBitmapResetBase(null, true);
    142     }
    143 
    144     private Runnable mOnLayoutRunnable = null;
    145 
    146     // This function changes bitmap, reset base matrix according to the size
    147     // of the bitmap, and optionally reset the supplementary matrix.
    148     public void setImageBitmapResetBase(final Bitmap bitmap,
    149             final boolean resetSupp) {
    150         setImageRotateBitmapResetBase(new RotateBitmap(bitmap), resetSupp);
    151     }
    152 
    153     public void setImageRotateBitmapResetBase(final RotateBitmap bitmap,
    154             final boolean resetSupp) {
    155         final int viewWidth = getWidth();
    156 
    157         if (viewWidth <= 0)  {
    158             mOnLayoutRunnable = new Runnable() {
    159                 public void run() {
    160                     setImageRotateBitmapResetBase(bitmap, resetSupp);
    161                 }
    162             };
    163             return;
    164         }
    165 
    166         if (bitmap.getBitmap() != null) {
    167             getProperBaseMatrix(bitmap, mBaseMatrix);
    168             setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
    169         } else {
    170             mBaseMatrix.reset();
    171             setImageBitmap(null);
    172         }
    173 
    174         if (resetSupp) {
    175             mSuppMatrix.reset();
    176         }
    177         setImageMatrix(getImageViewMatrix());
    178         mMaxZoom = maxZoom();
    179     }
    180 
    181     // Center as much as possible in one or both axis.  Centering is
    182     // defined as follows:  if the image is scaled down below the
    183     // view's dimensions then center it (literally).  If the image
    184     // is scaled larger than the view and is translated out of view
    185     // then translate it back into view (i.e. eliminate black bars).
    186     protected void center(boolean horizontal, boolean vertical) {
    187         if (mBitmapDisplayed.getBitmap() == null) {
    188             return;
    189         }
    190 
    191         Matrix m = getImageViewMatrix();
    192 
    193         RectF rect = new RectF(0, 0,
    194                 mBitmapDisplayed.getBitmap().getWidth(),
    195                 mBitmapDisplayed.getBitmap().getHeight());
    196 
    197         m.mapRect(rect);
    198 
    199         float height = rect.height();
    200         float width  = rect.width();
    201 
    202         float deltaX = 0, deltaY = 0;
    203 
    204         if (vertical) {
    205             int viewHeight = getHeight();
    206             if (height < viewHeight) {
    207                 deltaY = (viewHeight - height) / 2 - rect.top;
    208             } else if (rect.top > 0) {
    209                 deltaY = -rect.top;
    210             } else if (rect.bottom < viewHeight) {
    211                 deltaY = getHeight() - rect.bottom;
    212             }
    213         }
    214 
    215         if (horizontal) {
    216             int viewWidth = getWidth();
    217             if (width < viewWidth) {
    218                 deltaX = (viewWidth - width) / 2 - rect.left;
    219             } else if (rect.left > 0) {
    220                 deltaX = -rect.left;
    221             } else if (rect.right < viewWidth) {
    222                 deltaX = viewWidth - rect.right;
    223             }
    224         }
    225 
    226         postTranslate(deltaX, deltaY);
    227         setImageMatrix(getImageViewMatrix());
    228     }
    229 
    230     public ImageViewTouchBase(Context context) {
    231         super(context);
    232         init();
    233     }
    234 
    235     public ImageViewTouchBase(Context context, AttributeSet attrs) {
    236         super(context, attrs);
    237         init();
    238     }
    239 
    240     private void init() {
    241         setScaleType(ImageView.ScaleType.MATRIX);
    242     }
    243 
    244     protected float getValue(Matrix matrix, int whichValue) {
    245         matrix.getValues(mMatrixValues);
    246         return mMatrixValues[whichValue];
    247     }
    248 
    249     // Get the scale factor out of the matrix.
    250     protected float getScale(Matrix matrix) {
    251         return getValue(matrix, Matrix.MSCALE_X);
    252     }
    253 
    254     protected float getScale() {
    255         return getScale(mSuppMatrix);
    256     }
    257 
    258     // Setup the base matrix so that the image is centered and scaled properly.
    259     private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) {
    260         float viewWidth = getWidth();
    261         float viewHeight = getHeight();
    262 
    263         float w = bitmap.getWidth();
    264         float h = bitmap.getHeight();
    265         matrix.reset();
    266 
    267         // We limit up-scaling to 3x otherwise the result may look bad if it's
    268         // a small icon.
    269         float widthScale = Math.min(viewWidth / w, 3.0f);
    270         float heightScale = Math.min(viewHeight / h, 3.0f);
    271         float scale = Math.min(widthScale, heightScale);
    272 
    273         matrix.postConcat(bitmap.getRotateMatrix());
    274         matrix.postScale(scale, scale);
    275 
    276         matrix.postTranslate(
    277                 (viewWidth  - w * scale) / 2F,
    278                 (viewHeight - h * scale) / 2F);
    279     }
    280 
    281     // Combine the base matrix and the supp matrix to make the final matrix.
    282     protected Matrix getImageViewMatrix() {
    283         // The final matrix is computed as the concatentation of the base matrix
    284         // and the supplementary matrix.
    285         mDisplayMatrix.set(mBaseMatrix);
    286         mDisplayMatrix.postConcat(mSuppMatrix);
    287         return mDisplayMatrix;
    288     }
    289 
    290     static final float SCALE_RATE = 1.25F;
    291 
    292     // Sets the maximum zoom, which is a scale relative to the base matrix. It
    293     // is calculated to show the image at 400% zoom regardless of screen or
    294     // image orientation. If in the future we decode the full 3 megapixel image,
    295     // rather than the current 1024x768, this should be changed down to 200%.
    296     protected float maxZoom() {
    297         if (mBitmapDisplayed.getBitmap() == null) {
    298             return 1F;
    299         }
    300 
    301         float fw = (float) mBitmapDisplayed.getWidth()  / (float) mThisWidth;
    302         float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
    303         float max = Math.max(fw, fh) * 4;
    304         return max;
    305     }
    306 
    307     protected void zoomTo(float scale, float centerX, float centerY) {
    308         if (scale > mMaxZoom) {
    309             scale = mMaxZoom;
    310         }
    311 
    312         float oldScale = getScale();
    313         float deltaScale = scale / oldScale;
    314 
    315         mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
    316         setImageMatrix(getImageViewMatrix());
    317         center(true, true);
    318     }
    319 
    320     protected void zoomTo(final float scale, final float centerX,
    321                           final float centerY, final float durationMs) {
    322         final float incrementPerMs = (scale - getScale()) / durationMs;
    323         final float oldScale = getScale();
    324         final long startTime = System.currentTimeMillis();
    325 
    326         mHandler.post(new Runnable() {
    327             public void run() {
    328                 long now = System.currentTimeMillis();
    329                 float currentMs = Math.min(durationMs, now - startTime);
    330                 float target = oldScale + (incrementPerMs * currentMs);
    331                 zoomTo(target, centerX, centerY);
    332 
    333                 if (currentMs < durationMs) {
    334                     mHandler.post(this);
    335                 }
    336             }
    337         });
    338     }
    339 
    340     protected void zoomTo(float scale) {
    341         float cx = getWidth() / 2F;
    342         float cy = getHeight() / 2F;
    343 
    344         zoomTo(scale, cx, cy);
    345     }
    346 
    347     protected void zoomToPoint(float scale, float pointX, float pointY) {
    348         float cx = getWidth() / 2F;
    349         float cy = getHeight() / 2F;
    350 
    351         panBy(cx - pointX, cy - pointY);
    352         zoomTo(scale, cx, cy);
    353     }
    354 
    355     protected void zoomIn() {
    356         zoomIn(SCALE_RATE);
    357     }
    358 
    359     protected void zoomOut() {
    360         zoomOut(SCALE_RATE);
    361     }
    362 
    363     protected void zoomIn(float rate) {
    364         if (getScale() >= mMaxZoom) {
    365             return;     // Don't let the user zoom into the molecular level.
    366         }
    367         if (mBitmapDisplayed.getBitmap() == null) {
    368             return;
    369         }
    370 
    371         float cx = getWidth() / 2F;
    372         float cy = getHeight() / 2F;
    373 
    374         mSuppMatrix.postScale(rate, rate, cx, cy);
    375         setImageMatrix(getImageViewMatrix());
    376     }
    377 
    378     protected void zoomOut(float rate) {
    379         if (mBitmapDisplayed.getBitmap() == null) {
    380             return;
    381         }
    382 
    383         float cx = getWidth() / 2F;
    384         float cy = getHeight() / 2F;
    385 
    386         // Zoom out to at most 1x.
    387         Matrix tmp = new Matrix(mSuppMatrix);
    388         tmp.postScale(1F / rate, 1F / rate, cx, cy);
    389 
    390         if (getScale(tmp) < 1F) {
    391             mSuppMatrix.setScale(1F, 1F, cx, cy);
    392         } else {
    393             mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
    394         }
    395         setImageMatrix(getImageViewMatrix());
    396         center(true, true);
    397     }
    398 
    399     protected void postTranslate(float dx, float dy) {
    400         mSuppMatrix.postTranslate(dx, dy);
    401     }
    402 
    403     protected void panBy(float dx, float dy) {
    404         postTranslate(dx, dy);
    405         setImageMatrix(getImageViewMatrix());
    406     }
    407 }
    408