/*
* Copyright 2010 Sony Ericsson Mobile Communications AB
*
* 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 roman10.zoomablegallery;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.Observable;
import java.util.Observer;
/**
* View capable of drawing an image at different zoom state levels
*/
public class ImageZoomView extends View implements Observer {
/** Paint object used when drawing bitmap. */
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
/** Rectangle used (and re-used) for cropping source image. */
private final Rect mRectSrc = new Rect();
/** Rectangle used (and re-used) for specifying drawing area on canvas. */
private final Rect mRectDst = new Rect();
/** Object holding aspect quotient */
private final AspectQuotient mAspectQuotient = new AspectQuotient();
/** The bitmap that we're zooming in, and drawing on the screen. */
private Bitmap mBitmap;
/** State of the zoom. */
private ZoomState mState;
private int mRotate = 0;
public void cleanUp() {
//called when we want to free resources for ImageZoomView
mBitmap.recycle();
mBitmap = null;
}
public void setRotate(int rotate) {
if (rotate!=mRotate) {
mRotate = rotate;
mAccuRotate += mRotate;
mAccuRotate %= 360;
//calculateAspectQuotient();
}
}
public void setZoom(float zoomF) {
mZoomControl.setZoom(mState.getZoom()*zoomF);
if (mState.getZoom() - 1.0f < 0.00001f) {
resetZoomState();
}
}
private static int mAccuRotate = 0;
// Public methods
/**
* Constructor
*/
public ImageZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private int mIndex;
public int getIndex() {
return mIndex;
}
/** Zoom control */
private DynamicZoomControl mZoomControl;
public ImageZoomView(Context context, Integer ori, int _indexId) {
super(context);
mIndex = _indexId;
Log.i("rotate", String.valueOf(ori) + mIndex);
setRotate(ori*90);
/** On touch listener for zoom view */
// LongPressZoomListener mZoomListener;
PinchZoomListener mPinchZoomListener;
// boolean longpressZoom = false;
mZoomControl = new DynamicZoomControl();
setZoomState(mZoomControl.getZoomState());
// mZoomListener = new LongPressZoomListener(mContext.getApplicationContext());
// mZoomListener.setZoomControl(mZoomControl);
mPinchZoomListener = new PinchZoomListener(context.getApplicationContext());
mPinchZoomListener.setZoomControl(mZoomControl);
mZoomControl.setAspectQuotient(getAspectQuotient());
resetZoomState();
setOnTouchListener(mPinchZoomListener);
}
/**
* Reset zoom state and notify observers
*/
public void resetZoomState() {
mZoomControl.getZoomState().setPanX(0.5f);
mZoomControl.getZoomState().setPanY(0.5f);
mZoomControl.getZoomState().setZoom(1f);
mZoomControl.getZoomState().notifyObservers();
}
/**
* Set image bitmap
*
* @param bitmap The bitmap to view and zoom into
*/
public void setImage(Bitmap bitmap) {
mBitmap = bitmap;
calculateAspectQuotient();
invalidate();
}
/**
* Set object holding the zoom state that should be used
*
* @param state The zoom state
*/
public void setZoomState(ZoomState state) {
if (mState != null) {
mState.deleteObserver(this);
}
mState = state;
mState.addObserver(this);
invalidate();
}
/**
* Gets reference to object holding aspect quotient
*
* @return Object holding aspect quotient
*/
public AspectQuotient getAspectQuotient() {
return mAspectQuotient;
}
private void calculateAspectQuotient() {
if (mBitmap != null) {
if ((mAccuRotate == 90) || (mAccuRotate == 270)) {
mAspectQuotient.updateAspectQuotient(getHeight(), getWidth(), mBitmap.getWidth(), mBitmap.getHeight());
// mAspectQuotient = (((float)mBitmap.getWidth()) / mBitmap.getHeight())
// / (((float)getHeight()) / getWidth());
} else {
mAspectQuotient.updateAspectQuotient(getWidth(), getHeight(), mBitmap.getWidth(), mBitmap.getHeight());
// mAspectQuotient = (((float)mBitmap.getWidth()) / mBitmap.getHeight())
// / (((float)getWidth()) / getHeight());
}
}
// Log.e("ImageZoomView-calculateAspectQuotient", getWidth() + ":" + getHeight() + ";" + mBitmap.getWidth() + ":" + mBitmap.getHeight());
mAspectQuotient.notifyObservers();
}
private void calculateDimension() {
final int viewWidth = getWidth();
final int viewHeight = getHeight();
int bitmapWidth = mBitmap.getWidth();
int bitmapHeight = mBitmap.getHeight();
if (mRotate!=0) {
Matrix matrix = new Matrix();
matrix.postRotate(mRotate);
//Bitmap l_bitmap = mBitmap;
Bitmap l_bitmap = mBitmap;
int tryCnt = 0;
int roateBitmapWidth = bitmapWidth;
int rotateBitmapHeight = bitmapHeight;
while (true) {
try {
if (tryCnt >= 5) {
break; //if tried too many times break
}
if (tryCnt > 0) {
//if we failed before, we scale down first
l_bitmap = Bitmap.createScaledBitmap(mBitmap, roateBitmapWidth, rotateBitmapHeight, true);
}
mBitmap = Bitmap.createBitmap(l_bitmap, 0, 0, roateBitmapWidth, rotateBitmapHeight, matrix, true);
if (l_bitmap!=null) {
l_bitmap.recycle();
l_bitmap = null;
}
break;
} catch (OutOfMemoryError e) {
++tryCnt;
roateBitmapWidth /= 2;
rotateBitmapHeight /= 2;
//System.gc();
}
}
bitmapWidth = mBitmap.getWidth();
bitmapHeight = mBitmap.getHeight();
mRotate = 0;
}
calculateAspectQuotient();
final float aspectQuotient = mAspectQuotient.get();
final float panX = mState.getPanX();
final float panY = mState.getPanY();
final float zoomX;
final float zoomY;
if ((mAccuRotate== 90) || (mAccuRotate == 270)) {
zoomX = mState.getZoomX(aspectQuotient) * viewHeight / bitmapWidth;
zoomY = mState.getZoomY(aspectQuotient) * viewWidth / bitmapHeight;
} else {
zoomX = mState.getZoomX(aspectQuotient) * viewWidth / bitmapWidth;
zoomY = mState.getZoomY(aspectQuotient) * viewHeight / bitmapHeight;
}
// Setup source and destination rectangles
mRectSrc.left = (int)(panX * bitmapWidth - viewWidth / (zoomX * 2));
mRectSrc.top = (int)(panY * bitmapHeight - viewHeight / (zoomY * 2));
mRectSrc.right = (int)(mRectSrc.left + viewWidth / zoomX);
mRectSrc.bottom = (int)(mRectSrc.top + viewHeight / zoomY);
mRectDst.left = getLeft();
mRectDst.top = getTop();
mRectDst.right = getRight();
mRectDst.bottom = getBottom();
// Adjust source rectangle so that it fits within the source image.
if (mRectSrc.left < 0) {
mRectDst.left += -mRectSrc.left * zoomX;
mRectSrc.left = 0;
}
if (mRectSrc.right > bitmapWidth) {
mRectDst.right -= (mRectSrc.right - bitmapWidth) * zoomX;
mRectSrc.right = bitmapWidth;
}
if (mRectSrc.top < 0) {
mRectDst.top += -mRectSrc.top * zoomY;
mRectSrc.top = 0;
}
if (mRectSrc.bottom > bitmapHeight) {
mRectDst.bottom -= (mRectSrc.bottom - bitmapHeight) * zoomY;
mRectSrc.bottom = bitmapHeight;
}
// Log.e("ImageZoomView-onDraw", panX + ":" + panY + ";" + mRectSrc.left + ":" + mRectSrc.right + ":" + mRectSrc.top + ":" + mRectSrc.bottom + ";" + mRectDst.left + ":" + mRectDst.right + ":" + mRectDst.top + ":" + mRectDst.bottom);
}
@Override
protected void onDraw(Canvas canvas) {
if (mBitmap != null && mState != null) {
//mId = mId; //for debug
//onMeasure(480,800); //for test
int tryCnt = 0;
while (true) {
try {
if (tryCnt >= 8) {
//if tried too many times break
break;
}
calculateDimension();
canvas.drawBitmap(mBitmap, mRectSrc, mRectDst, mPaint);
break; //if no error, break
} catch (OutOfMemoryError e) {
++tryCnt;
Log.e("ImageZoomView", "onDraw.drawBitmap cause memory error: " + tryCnt);
//System.gc();
}
}
}
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
//mId = mId; //for debug
//calculateDimension();
//setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// if ((mAccuRotate == 90) || (mAccuRotate == 270)) {
// mAspectQuotient.updateAspectQuotient(bottom - top, right - left, mBitmap.getWidth(), mBitmap.getHeight());
// } else {
// mAspectQuotient.updateAspectQuotient(right - left, bottom - top, mBitmap.getWidth(),
// mBitmap.getHeight());
// }
//
// //Log.w("ImageZoomView-onLayout", String.valueOf(right-left) + "," + String.valueOf(bottom-top) + "," + mBitmap.getWidth() + "," + mBitmap.getHeight());
// mAspectQuotient.notifyObservers();
calculateAspectQuotient();
}
// implements Observer
public void update(Observable observable, Object data) {
//onMeasure(mBitmap.getWidth(), mBitmap.getHeight()); //roman10: added
//if (mState.getZoom() > 1.0) {
//requestLayout(); //added by roman10: this works partially
//}
if (mState.getZoom() - 1.0f < 0.00001f) {
mState.setPanX(0.5f);
mState.setPanY(0.5f);
}
invalidate();
}
public ZoomState getZoomState() {
return mState;
}
}