• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onImageTouchEvent(MotionEvent ev)78         public boolean onImageTouchEvent(MotionEvent ev);
79     }
80     /**
81      * Constructor
82      *
83      * @param context The context
84      */
ImageViewTouchBase(Context context)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      */
ImageViewTouchBase(Context context, AttributeSet attrs)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      */
ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle)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
onLayout(boolean changed, int left, int top, int right, int bottom)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
dispatchTouchEvent(MotionEvent ev)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
setImageBitmap(Bitmap bitmap)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      */
setEventListener(ImageTouchEventListener listener)164     public void setEventListener(ImageTouchEventListener listener) {
165         mEventListener = listener;
166     }
167 
168     /**
169      * @return The image bitmap
170      */
getImageBitmap()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      */
setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp)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      */
reset()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      */
postTranslateCenter(float dx, float dy)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      */
panBy(float dx, float dy)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      */
getScale()246     public float getScale() {
247         return getValue(mSuppMatrix, Matrix.MSCALE_X);
248     }
249 
250     /**
251      * @param rect The input/output rectangle
252      */
mapRect(RectF rect)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      */
getProperBaseMatrix(Bitmap bitmap, Matrix matrix)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      */
getImageViewMatrix()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      */
getMaxZoom()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      */
maxZoom()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      */
maxZoom(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)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      */
correctedZoomScale(float scale)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      */
zoomTo(float scale, float centerX, float centerY)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      */
zoomTo(float scale)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      */
zoomToPoint(float scale, float pointX, float pointY)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      */
zoomToOffset(float scale, float pointX, float pointY)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      */
zoomIn()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      */
zoomIn(float rate)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      */
zoomOut()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      */
zoomOut(float rate)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      */
center(boolean horizontal, boolean vertical)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      */
getValue(Matrix matrix, int whichValue)526     private float getValue(Matrix matrix, int whichValue) {
527         matrix.getValues(mMatrixValues);
528         return mMatrixValues[whichValue];
529     }
530 }
531