blob: 625505f49a480b2559fdaf8b43103fa59ac1c7e1 [file] [log] [blame]
/*
* Copyright (C) 2011 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.gallery3d.ui;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.FloatMath;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.util.GalleryUtils;
class PositionController {
public static final int IMAGE_AT_LEFT_EDGE = 1;
public static final int IMAGE_AT_RIGHT_EDGE = 2;
public static final int IMAGE_AT_TOP_EDGE = 4;
public static final int IMAGE_AT_BOTTOM_EDGE = 8;
private long mAnimationStartTime = NO_ANIMATION;
private static final long NO_ANIMATION = -1;
private static final long LAST_ANIMATION = -2;
private int mAnimationKind;
private float mAnimationDuration;
private final static int ANIM_KIND_SCROLL = 0;
private final static int ANIM_KIND_SCALE = 1;
private final static int ANIM_KIND_SNAPBACK = 2;
private final static int ANIM_KIND_SLIDE = 3;
private final static int ANIM_KIND_ZOOM = 4;
private final static int ANIM_KIND_FLING = 5;
// Animation time in milliseconds. The order must match ANIM_KIND_* above.
private final static int ANIM_TIME[] = {
0, // ANIM_KIND_SCROLL
50, // ANIM_KIND_SCALE
600, // ANIM_KIND_SNAPBACK
400, // ANIM_KIND_SLIDE
300, // ANIM_KIND_ZOOM
0, // ANIM_KIND_FLING (the duration is calculated dynamically)
};
// We try to scale up the image to fill the screen. But in order not to
// scale too much for small icons, we limit the max up-scaling factor here.
private static final float SCALE_LIMIT = 4;
private static final int sHorizontalSlack = GalleryUtils.dpToPixel(12);
private static final float SCALE_MIN_EXTRA = 0.6f;
private static final float SCALE_MAX_EXTRA = 1.4f;
private PhotoView mViewer;
private EdgeView mEdgeView;
private int mImageW, mImageH;
private int mViewW, mViewH;
// The X, Y are the coordinate on bitmap which shows on the center of
// the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
// values used currently.
private int mCurrentX, mFromX, mToX;
private int mCurrentY, mFromY, mToY;
private float mCurrentScale, mFromScale, mToScale;
// The focus point of the scaling gesture (in bitmap coordinates).
private int mFocusBitmapX;
private int mFocusBitmapY;
private boolean mInScale;
// The minimum and maximum scale we allow.
private float mScaleMin, mScaleMax = SCALE_LIMIT;
private boolean mExtraScalingRange = false;
// This is used by the fling animation
private FlingScroller mScroller;
// The bound of the stable region, see the comments above
// calculateStableBound() for details.
private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
// Assume the image size is the same as view size before we know the actual
// size of image.
private boolean mUseViewSize = true;
private RectF mTempRect = new RectF();
private float[] mTempPoints = new float[8];
public PositionController(PhotoView viewer, Context context,
EdgeView edgeView) {
mViewer = viewer;
mEdgeView = edgeView;
mScroller = new FlingScroller();
}
public void setImageSize(int width, int height) {
// If no image available, use view size.
if (width == 0 || height == 0) {
mUseViewSize = true;
mImageW = mViewW;
mImageH = mViewH;
mCurrentX = mImageW / 2;
mCurrentY = mImageH / 2;
mCurrentScale = 1;
mScaleMin = 1;
mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
return;
}
mUseViewSize = false;
float ratio = Math.min(
(float) mImageW / width, (float) mImageH / height);
// See the comment above translate() for details.
mCurrentX = translate(mCurrentX, mImageW, width, ratio);
mCurrentY = translate(mCurrentY, mImageH, height, ratio);
mCurrentScale = mCurrentScale * ratio;
mFromX = translate(mFromX, mImageW, width, ratio);
mFromY = translate(mFromY, mImageH, height, ratio);
mFromScale = mFromScale * ratio;
mToX = translate(mToX, mImageW, width, ratio);
mToY = translate(mToY, mImageH, height, ratio);
mToScale = mToScale * ratio;
mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
mImageW = width;
mImageH = height;
mScaleMin = getMinimalScale(mImageW, mImageH);
// Start animation from the saved rectangle if we have one.
Rect r = mViewer.retrieveOpenAnimationRect();
if (r != null) {
// The animation starts from the specified rectangle; the image
// should be scaled and centered as the thumbnail shown in the
// rectangle to minimize janky opening animation. Note: The below
// implementation depends on how thumbnails are drawn and placed.
float size = MediaItem.getTargetSize(
MediaItem.TYPE_MICROTHUMBNAIL);
float scale = (size / Math.min(width, height)) * Math.min(
r.width() / size, r.height() / size);
mCurrentX = Math.round((mViewW / 2f - r.centerX()) / scale) + mImageW / 2;
mCurrentY = Math.round((mViewH / 2f - r.centerY()) / scale) + mImageH / 2;
mCurrentScale = scale;
mViewer.openAnimationStarted();
startSnapback();
} else if (mAnimationStartTime == NO_ANIMATION) {
mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
}
mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
}
public void zoomIn(float tapX, float tapY, float targetScale) {
if (targetScale > mScaleMax) targetScale = mScaleMax;
// Convert the tap position to image coordinate
int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
calculateStableBound(targetScale);
int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
}
public void resetToFullView() {
startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
}
public float getMinimalScale(int w, int h) {
return Math.min(SCALE_LIMIT,
Math.min((float) mViewW / w, (float) mViewH / h));
}
// Translate a coordinate on bitmap if the bitmap size changes.
// If the aspect ratio doesn't change, it's easy:
//
// r = w / w' (= h / h')
// x' = x / r
// y' = y / r
//
// However the aspect ratio may change. That happens when the user slides
// a image before it's loaded, we don't know the actual aspect ratio, so
// we will assume one. When we receive the actual bitmap size, we need to
// translate the coordinate from the old bitmap into the new bitmap.
//
// What we want to do is center the bitmap at the original position.
//
// ...+--+...
// . | | .
// . | | .
// ...+--+...
//
// First we scale down the new bitmap by a factor r = min(w/w', h/h').
// Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
// to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
// the old bitmap maps to (x', y') in the new bitmap, where
// x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
// y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
private static int translate(int value, int size, int newSize, float ratio) {
return Math.round(newSize / 2f + (value - size / 2f) / ratio);
}
public void setViewSize(int viewW, int viewH) {
boolean needLayout = mViewW == 0 || mViewH == 0;
mViewW = viewW;
mViewH = viewH;
if (mUseViewSize) {
mImageW = viewW;
mImageH = viewH;
mCurrentX = mImageW / 2;
mCurrentY = mImageH / 2;
mCurrentScale = 1;
mScaleMin = 1;
mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
return;
}
// In most cases we want to keep the scaling factor intact when the
// view size changes. The cases we want to reset the scaling factor
// (to fit the view if possible) are (1) the scaling factor is too
// small for the new view size (2) the scaling factor has not been
// changed by the user.
boolean wasMinScale = (mCurrentScale == mScaleMin);
mScaleMin = getMinimalScale(mImageW, mImageH);
if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
mCurrentX = mImageW / 2;
mCurrentY = mImageH / 2;
mCurrentScale = mScaleMin;
mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
}
}
public void stopAnimation() {
mAnimationStartTime = NO_ANIMATION;
}
public void skipAnimation() {
if (mAnimationStartTime == NO_ANIMATION) return;
mAnimationStartTime = NO_ANIMATION;
mCurrentX = mToX;
mCurrentY = mToY;
mCurrentScale = mToScale;
}
public void beginScale(float focusX, float focusY) {
mInScale = true;
mFocusBitmapX = Math.round(mCurrentX +
(focusX - mViewW / 2f) / mCurrentScale);
mFocusBitmapY = Math.round(mCurrentY +
(focusY - mViewH / 2f) / mCurrentScale);
}
// Returns true if the result scale is outside the stable range.
public boolean scaleBy(float s, float focusX, float focusY) {
// We want to keep the focus point (on the bitmap) the same as when
// we begin the scale guesture, that is,
//
// mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
//
s *= getTargetScale();
int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
startAnimation(x, y, s, ANIM_KIND_SCALE);
return (s < mScaleMin || s > mScaleMax);
}
public void endScale() {
mInScale = false;
startSnapbackIfNeeded();
}
public void setExtraScalingRange(boolean enabled) {
mExtraScalingRange = enabled;
if (!enabled) {
startSnapbackIfNeeded();
}
}
public float getCurrentScale() {
return mCurrentScale;
}
public boolean isAtMinimalScale() {
return isAlmostEquals(mCurrentScale, mScaleMin);
}
private static boolean isAlmostEquals(float a, float b) {
float diff = a - b;
return (diff < 0 ? -diff : diff) < 0.02f;
}
public void up() {
startSnapback();
}
// |<--| (1/2) * mImageW
// +-------+-------+-------+
// | | | |
// | | o | |
// | | | |
// +-------+-------+-------+
// |<----------| (3/2) * mImageW
// Slide in the image from left or right.
// Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
// Sliding from left: mCurrentX = (1/2) * mImageW
// right: mCurrentX = (3/2) * mImageW
public void startSlideInAnimation(int direction) {
int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
mImageW / 2 : 3 * mImageW / 2;
mFromX = Math.round(fromX);
mFromY = Math.round(mImageH / 2f);
mCurrentX = mFromX;
mCurrentY = mFromY;
startAnimation(
mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
}
public void startHorizontalSlide(int distance) {
scrollBy(distance, 0, ANIM_KIND_SLIDE);
}
private void scrollBy(float dx, float dy, int type) {
startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
getTargetY() + Math.round(dy / mCurrentScale),
mCurrentScale, type);
}
public void startScroll(float dx, float dy, boolean hasNext,
boolean hasPrev) {
int x = getTargetX() + Math.round(dx / mCurrentScale);
int y = getTargetY() + Math.round(dy / mCurrentScale);
calculateStableBound(mCurrentScale);
// Vertical direction: If we have space to move in the vertical
// direction, we show the edge effect when scrolling reaches the edge.
if (mBoundTop != mBoundBottom) {
if (y < mBoundTop) {
mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
} else if (y > mBoundBottom) {
mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
}
}
y = Utils.clamp(y, mBoundTop, mBoundBottom);
// Horizontal direction: we show the edge effect when the scrolling
// tries to go left of the first image or go right of the last image.
if (!hasPrev && x < mBoundLeft) {
int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
mEdgeView.onPull(pixels, EdgeView.LEFT);
x = mBoundLeft;
} else if (!hasNext && x > mBoundRight) {
int pixels = Math.round((x - mBoundRight) * mCurrentScale);
mEdgeView.onPull(pixels, EdgeView.RIGHT);
x = mBoundRight;
}
startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
}
public boolean fling(float velocityX, float velocityY) {
// We only want to do fling when the picture is zoomed-in.
if (viewWiderThanScaledImage(mCurrentScale) &&
viewHigherThanScaledImage(mCurrentScale)) {
return false;
}
// We only allow flinging in the directions where it won't go over the
// picture.
int edges = getImageAtEdges();
if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
(velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
velocityX = 0;
}
if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
(velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
velocityY = 0;
}
if (isAlmostEquals(velocityX, 0) && isAlmostEquals(velocityY, 0)) {
return false;
}
mScroller.fling(mCurrentX, mCurrentY,
Math.round(-velocityX / mCurrentScale),
Math.round(-velocityY / mCurrentScale),
mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
int targetX = mScroller.getFinalX();
int targetY = mScroller.getFinalY();
mAnimationDuration = mScroller.getDuration();
startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
return true;
}
private void startAnimation(
int targetX, int targetY, float scale, int kind) {
mAnimationKind = kind;
if (targetX == mCurrentX && targetY == mCurrentY
&& scale == mCurrentScale) {
onAnimationComplete();
return;
}
mFromX = mCurrentX;
mFromY = mCurrentY;
mFromScale = mCurrentScale;
mToX = targetX;
mToY = targetY;
mToScale = Utils.clamp(scale, SCALE_MIN_EXTRA * mScaleMin,
SCALE_MAX_EXTRA * mScaleMax);
// If the scaled height is smaller than the view height,
// force it to be in the center.
// (We do for height only, not width, because the user may
// want to scroll to the previous/next image.)
if (!mInScale && viewHigherThanScaledImage(mToScale)) {
mToY = mImageH / 2;
}
mAnimationStartTime = AnimationTime.get();
if (mAnimationKind != ANIM_KIND_FLING) {
mAnimationDuration = ANIM_TIME[mAnimationKind];
}
advanceAnimation();
}
public void advanceAnimation() {
if (mAnimationStartTime == NO_ANIMATION) {
return;
} else if (mAnimationStartTime == LAST_ANIMATION) {
onAnimationComplete();
return;
}
long now = AnimationTime.get();
float progress;
if (mAnimationDuration == 0) {
progress = 1;
} else {
progress = (now - mAnimationStartTime) / mAnimationDuration;
}
if (progress >= 1) {
progress = 1;
mCurrentX = mToX;
mCurrentY = mToY;
mCurrentScale = mToScale;
mAnimationStartTime = LAST_ANIMATION;
} else {
float f = 1 - progress;
switch (mAnimationKind) {
case ANIM_KIND_SCROLL:
case ANIM_KIND_FLING:
progress = 1 - f; // linear
break;
case ANIM_KIND_SCALE:
progress = 1 - f * f; // quadratic
break;
case ANIM_KIND_SNAPBACK:
case ANIM_KIND_ZOOM:
case ANIM_KIND_SLIDE:
progress = 1 - f * f * f * f * f; // x^5
break;
}
if (mAnimationKind == ANIM_KIND_FLING) {
flingInterpolate(progress);
} else {
linearInterpolate(progress);
}
}
mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
mViewer.invalidate();
}
private void onAnimationComplete() {
mAnimationStartTime = NO_ANIMATION;
if (mViewer.isInTransition()) {
mViewer.notifyTransitionComplete();
} else {
if (startSnapbackIfNeeded()) mViewer.invalidate();
}
}
private void flingInterpolate(float progress) {
mScroller.computeScrollOffset(progress);
int oldX = mCurrentX;
int oldY = mCurrentY;
mCurrentX = mScroller.getCurrX();
mCurrentY = mScroller.getCurrY();
// Check if we hit the edges; show edge effects if we do.
if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
mEdgeView.onAbsorb(v, EdgeView.LEFT);
} else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
mEdgeView.onAbsorb(v, EdgeView.RIGHT);
}
if (oldY > mBoundTop && mCurrentY == mBoundTop) {
int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
mEdgeView.onAbsorb(v, EdgeView.TOP);
} else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
}
}
// Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
private void linearInterpolate(float progress) {
// To linearly interpolate the position on view coordinates, we do the
// following steps:
// (1) convert a bitmap position (x, y) to view coordinates:
// from: (x - mFromX) * mFromScale + mViewW / 2
// to: (x - mToX) * mToScale + mViewW / 2
// (2) interpolate between the "from" and "to" coordinates:
// (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
// + mViewW / 2
// should be equal to
// (x - mCurrentX) * mCurrentScale + mViewW / 2
// (3) The x-related terms in the above equation can be removed because
// mFromScale * (1 - p) + ToScale * p = mCurrentScale
// (4) Solve for mCurrentX, we have mCurrentX =
// (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
float fromX = mFromX * mFromScale;
float toX = mToX * mToScale;
float currentX = fromX + progress * (toX - fromX);
float fromY = mFromY * mFromScale;
float toY = mToY * mToScale;
float currentY = fromY + progress * (toY - fromY);
mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
mCurrentX = Math.round(currentX / mCurrentScale);
mCurrentY = Math.round(currentY / mCurrentScale);
}
// Returns true if redraw is needed.
private boolean startSnapbackIfNeeded() {
if (mAnimationStartTime != NO_ANIMATION) return false;
if (mInScale) return false;
if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
return false;
}
return startSnapback();
}
private boolean startSnapback() {
boolean needAnimation = false;
float scale = mCurrentScale;
float scaleMin = mExtraScalingRange ?
mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
float scaleMax = mExtraScalingRange ?
mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
if (mCurrentScale < scaleMin || mCurrentScale > scaleMax) {
needAnimation = true;
scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
}
calculateStableBound(scale, sHorizontalSlack);
int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
needAnimation = true;
}
if (needAnimation) {
startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
}
return needAnimation;
}
// Calculates the stable region of mCurrent{X/Y}, where "stable" means
//
// (1) If the dimension of scaled image >= view dimension, we will not
// see black region outside the image (at that dimension).
// (2) If the dimension of scaled image < view dimension, we will center
// the scaled image.
//
// We might temporarily go out of this stable during user interaction,
// but will "snap back" after user stops interaction.
//
// The results are stored in mBound{Left/Right/Top/Bottom}.
//
// An extra parameter "horizontalSlack" (which has the value of 0 usually)
// is used to extend the stable region by some pixels on each side
// horizontally.
private void calculateStableBound(float scale) {
calculateStableBound(scale, 0f);
}
private void calculateStableBound(float scale, float horizontalSlack) {
// The number of pixels between the center of the view
// and the edge when the edge is aligned.
mBoundLeft = (int) FloatMath.ceil((mViewW - horizontalSlack) / (2 * scale));
mBoundRight = mImageW - mBoundLeft;
mBoundTop = (int) FloatMath.ceil(mViewH / (2 * scale));
mBoundBottom = mImageH - mBoundTop;
// If the scaled height is smaller than the view height,
// force it to be in the center.
if (viewHigherThanScaledImage(scale)) {
mBoundTop = mBoundBottom = mImageH / 2;
}
// Same for width
if (viewWiderThanScaledImage(scale)) {
mBoundLeft = mBoundRight = mImageW / 2;
}
}
private boolean viewHigherThanScaledImage(float scale) {
return FloatMath.floor(mImageH * scale) <= mViewH;
}
private boolean viewWiderThanScaledImage(float scale) {
return FloatMath.floor(mImageW * scale) <= mViewW;
}
private boolean useCurrentValueAsTarget() {
return mAnimationStartTime == NO_ANIMATION ||
mAnimationKind == ANIM_KIND_SNAPBACK ||
mAnimationKind == ANIM_KIND_FLING;
}
private float getTargetScale() {
return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
}
private int getTargetX() {
return useCurrentValueAsTarget() ? mCurrentX : mToX;
}
private int getTargetY() {
return useCurrentValueAsTarget() ? mCurrentY : mToY;
}
public RectF getImageBounds() {
float points[] = mTempPoints;
/*
* (p0,p1)----------(p2,p3)
* | |
* | |
* (p4,p5)----------(p6,p7)
*/
points[0] = points[4] = -mCurrentX;
points[1] = points[3] = -mCurrentY;
points[2] = points[6] = mImageW - mCurrentX;
points[5] = points[7] = mImageH - mCurrentY;
RectF rect = mTempRect;
rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
float scale = mCurrentScale;
float offsetX = mViewW / 2;
float offsetY = mViewH / 2;
for (int i = 0; i < 4; ++i) {
float x = points[i + i] * scale + offsetX;
float y = points[i + i + 1] * scale + offsetY;
if (x < rect.left) rect.left = x;
if (x > rect.right) rect.right = x;
if (y < rect.top) rect.top = y;
if (y > rect.bottom) rect.bottom = y;
}
return rect;
}
public int getImageWidth() {
return mImageW;
}
public int getImageHeight() {
return mImageH;
}
public int getImageAtEdges() {
calculateStableBound(mCurrentScale);
int edges = 0;
if (mCurrentX <= mBoundLeft) {
edges |= IMAGE_AT_LEFT_EDGE;
}
if (mCurrentX >= mBoundRight) {
edges |= IMAGE_AT_RIGHT_EDGE;
}
if (mCurrentY <= mBoundTop) {
edges |= IMAGE_AT_TOP_EDGE;
}
if (mCurrentY >= mBoundBottom) {
edges |= IMAGE_AT_BOTTOM_EDGE;
}
return edges;
}
}