blob: ababd7107e8a519215e5bf94ac9b2cf0c90d06f5 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.videoeditor.widgets;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
* An image view which can be panned and zoomed.
*/
public class ImageViewTouchBase extends ImageView {
private static final float SCALE_RATE = 1.25F;
// Zoom scale is applied after the transform that fits the image screen,
// so 1.0 is a perfect fit and it doesn't make sense to allow smaller
// values.
private static final float MIN_ZOOM_SCALE = 1.0f;
// This is the base transformation which is used to show the image
// initially. The current computation for this shows the image in
// it's entirety, letterboxing as needed. One could choose to
// show the image as cropped instead.
//
// This matrix is recomputed when we go from the thumbnail image to
// the full size image.
private Matrix mBaseMatrix = new Matrix();
// This is the supplementary transformation which reflects what
// the user has done in terms of zooming and panning.
//
// This matrix remains the same when we go from the thumbnail image
// to the full size image.
private Matrix mSuppMatrix = new Matrix();
// This is the final matrix which is computed as the concatenation
// of the base matrix and the supplementary matrix.
private final Matrix mDisplayMatrix = new Matrix();
// Temporary buffer used for getting the values out of a matrix.
private final float[] mMatrixValues = new float[9];
// The current bitmap being displayed.
private Bitmap mBitmapDisplayed;
// The width and height of the view
private int mThisWidth = -1, mThisHeight = -1;
private boolean mStretch = true;
// The zoom scale
private float mMaxZoom;
private Runnable mOnLayoutRunnable = null;
private ImageTouchEventListener mEventListener;
/**
* Touch interface
*/
public interface ImageTouchEventListener {
public boolean onImageTouchEvent(MotionEvent ev);
}
/**
* Constructor
*
* @param context The context
*/
public ImageViewTouchBase(Context context) {
super(context);
setScaleType(ImageView.ScaleType.MATRIX);
}
/**
* Constructor
*
* @param context The context
* @param attrs The attributes
*/
public ImageViewTouchBase(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ImageView.ScaleType.MATRIX);
}
/**
* Constructor
*
* @param context The context
* @param attrs The attributes
* @param defStyle The default style
*/
public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ImageView.ScaleType.MATRIX);
}
/*
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mThisWidth = right - left;
mThisHeight = bottom - top;
final Runnable r = mOnLayoutRunnable;
if (r != null) {
mOnLayoutRunnable = null;
r.run();
} else {
if (mBitmapDisplayed != null) {
getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
setImageMatrix(getImageViewMatrix());
}
}
}
/*
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mEventListener != null) {
return mEventListener.onImageTouchEvent(ev);
} else {
return false;
}
}
/*
* {@inheritDoc}
*/
@Override
public void setImageBitmap(Bitmap bitmap) {
super.setImageBitmap(bitmap);
final Drawable d = getDrawable();
if (d != null) {
d.setDither(true);
}
mBitmapDisplayed = bitmap;
}
/**
* @param listener The listener
*/
public void setEventListener(ImageTouchEventListener listener) {
mEventListener = listener;
}
/**
* @return The image bitmap
*/
public Bitmap getImageBitmap() {
return mBitmapDisplayed;
}
/**
* If the view has not yet been measured delay the method
*
* @param bitmap The bitmap
* @param resetSupp true to reset the transform matrix
*/
public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
mStretch = true;
final int viewWidth = getWidth();
if (viewWidth <= 0) {
mOnLayoutRunnable = new Runnable() {
@Override
public void run() {
setImageBitmapResetBase(bitmap, resetSupp);
}
};
return;
}
if (bitmap != null) {
getProperBaseMatrix(bitmap, mBaseMatrix);
setImageBitmap(bitmap);
} else {
mBaseMatrix.reset();
setImageBitmap(null);
}
if (resetSupp) {
mSuppMatrix.reset();
}
setImageMatrix(getImageViewMatrix());
mMaxZoom = maxZoom();
}
/**
* Reset the transform of the current image
*/
public void reset() {
if (mBitmapDisplayed != null) {
setImageBitmapResetBase(mBitmapDisplayed, true);
}
}
/**
* Pan
*
* @param dx The horizontal offset
* @param dy The vertical offset
*/
public void postTranslateCenter(float dx, float dy) {
mSuppMatrix.postTranslate(dx, dy);
center(true, true);
}
/**
* Pan by the specified horizontal and vertical amount
*
* @param dx Pan by this horizontal amount
* @param dy Pan by this vertical amount
*/
private void panBy(float dx, float dy) {
mSuppMatrix.postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
/**
* @return The scale
*/
public float getScale() {
return getValue(mSuppMatrix, Matrix.MSCALE_X);
}
/**
* @param rect The input/output rectangle
*/
public void mapRect(RectF rect) {
mSuppMatrix.mapRect(rect);
}
/**
* Setup the base matrix so that the image is centered and scaled properly.
*
* @param bitmap The bitmap
* @param matrix The matrix
*/
private void getProperBaseMatrix(Bitmap bitmap, Matrix matrix) {
final float viewWidth = getWidth();
final float viewHeight = getHeight();
final float w = bitmap.getWidth();
final float h = bitmap.getHeight();
matrix.reset();
if (mStretch) {
// We limit up-scaling to 10x otherwise the result may look bad if
// it's a small icon.
float widthScale = Math.min(viewWidth / w, 10.0f);
float heightScale = Math.min(viewHeight / h, 10.0f);
float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
} else {
matrix.postTranslate((viewWidth - w) / 2F, (viewHeight - h) / 2F);
}
}
/**
* Combine the base matrix and the supp matrix to make the final matrix.
*/
private Matrix getImageViewMatrix() {
// The final matrix is computed as the concatenation of the base matrix
// and the supplementary matrix.
mDisplayMatrix.set(mBaseMatrix);
mDisplayMatrix.postConcat(mSuppMatrix);
return mDisplayMatrix;
}
/**
* @return The maximum zoom
*/
public float getMaxZoom() {
return mMaxZoom;
}
/**
* Sets the maximum zoom, which is a scale relative to the base matrix. It
* is calculated to show the image at 400% zoom regardless of screen or
* image orientation. If in the future we decode the full 3 megapixel
* image, rather than the current 1024x768, this should be changed down
* to 200%.
*/
private float maxZoom() {
if (mBitmapDisplayed == null) {
return 1F;
}
final float fw = (float)mBitmapDisplayed.getWidth() / mThisWidth;
final float fh = (float)mBitmapDisplayed.getHeight() / mThisHeight;
return Math.max(fw, fh) * 4;
}
/**
* Sets the maximum zoom, which is a scale relative to the base matrix. It
* is calculated to show the image at 400% zoom regardless of screen or
* image orientation. If in the future we decode the full 3 megapixel
* image, rather than the current 1024x768, this should be changed down
* to 200%.
*/
public static float maxZoom(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
final float fw = (float)bitmapWidth / viewWidth;
final float fh = (float)bitmapHeight / viewHeight;
return Math.max(fw, fh) * 4;
}
/**
* Ensure the scale factor is within limits
*
* @param scale The scale factor
*
* @return The corrected scaled factor
*/
private float correctedZoomScale(float scale) {
float result = scale;
if (result > mMaxZoom) {
result = mMaxZoom;
} else if (result < MIN_ZOOM_SCALE) {
result = MIN_ZOOM_SCALE;
}
return result;
}
/**
* Zoom to the specified scale factor
*
* @param scale The scale factor
* @param centerX The horizontal center
* @param centerY The vertical center
*/
public void zoomTo(float scale, float centerX, float centerY) {
float correctedScale = correctedZoomScale(scale);
float oldScale = getScale();
float deltaScale = correctedScale / oldScale;
mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
center(true, true);
}
/**
* Zoom to the specified scale factor
*
* @param scale The scale factor
*/
public void zoomTo(float scale) {
final float cx = getWidth() / 2F;
final float cy = getHeight() / 2F;
zoomTo(scale, cx, cy);
}
/**
* Zoom to the specified scale factor and center point
*
* @param scale The scale factor
* @param pointX The horizontal position
* @param pointY The vertical position
*/
public void zoomToPoint(float scale, float pointX, float pointY) {
final float cx = getWidth() / 2F;
final float cy = getHeight() / 2F;
panBy(cx - pointX, cy - pointY);
zoomTo(scale, cx, cy);
}
/**
* Zoom to the specified scale factor and point
*
* @param scale The scale factor
* @param pointX The horizontal position
* @param pointY The vertical position
*/
public void zoomToOffset(float scale, float pointX, float pointY) {
float correctedScale = correctedZoomScale(scale);
float oldScale = getScale();
float deltaScale = correctedScale / oldScale;
mSuppMatrix.postScale(deltaScale, deltaScale);
setImageMatrix(getImageViewMatrix());
panBy(-pointX, -pointY);
}
/**
* Zoom in by a preset scale rate
*/
public void zoomIn() {
zoomIn(SCALE_RATE);
}
/**
* Zoom in by the specified scale rate
*
* @param rate The scale rate
*/
public void zoomIn(float rate) {
if (getScale() < mMaxZoom && mBitmapDisplayed != null) {
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
mSuppMatrix.postScale(rate, rate, cx, cy);
setImageMatrix(getImageViewMatrix());
}
}
/**
* Zoom out by a preset scale rate
*/
public void zoomOut() {
zoomOut(SCALE_RATE);
}
/**
* Zoom out by the specified scale rate
*
* @param rate The scale rate
*/
public void zoomOut(float rate) {
if (getScale() > MIN_ZOOM_SCALE && mBitmapDisplayed != null) {
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
// Zoom out to at most 1x.
Matrix tmp = new Matrix(mSuppMatrix);
tmp.postScale(1F / rate, 1F / rate, cx, cy);
if (getValue(tmp, Matrix.MSCALE_X) < 1F) {
mSuppMatrix.setScale(1F, 1F, cx, cy);
} else {
mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
}
setImageMatrix(getImageViewMatrix());
center(true, true);
}
}
/**
* Center as much as possible in one or both axis. Centering is
* defined as follows: if the image is scaled down below the
* view's dimensions then center it (literally). If the image
* is scaled larger than the view and is translated out of view
* then translate it back into view (i.e. eliminate black bars).
*/
private void center(boolean horizontal, boolean vertical) {
if (mBitmapDisplayed == null) {
return;
}
final Matrix m = getImageViewMatrix();
final RectF rect = new RectF(0, 0, mBitmapDisplayed.getWidth(),
mBitmapDisplayed.getHeight());
m.mapRect(rect);
final float height = rect.height();
final float width = rect.width();
float deltaX = 0, deltaY = 0;
if (vertical) {
int viewHeight = getHeight();
if (height < viewHeight) {
deltaY = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = getHeight() - rect.bottom;
}
}
if (horizontal) {
int viewWidth = getWidth();
if (width < viewWidth) {
deltaX = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
}
mSuppMatrix.postTranslate(deltaX, deltaY);
setImageMatrix(getImageViewMatrix());
}
/**
* Get a matrix transform value
*
* @param matrix The matrix
* @param whichValue Which value
* @return The value
*/
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
}