Loading src/com/android/gallery3d/app/PhotoDataAdapter.java +3 −2 Original line number Diff line number Diff line Loading @@ -362,8 +362,9 @@ public class PhotoDataAdapter implements PhotoPage.Model { return mTileProvider.getLevelCount(); } public Bitmap getTile(int level, int x, int y, int tileSize) { return mTileProvider.getTile(level, x, y, tileSize); public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { return mTileProvider.getTile(level, x, y, tileSize, borderSize); } public boolean isFailedToLoad() { Loading src/com/android/gallery3d/ui/BitmapTileProvider.java +21 −4 Original line number Diff line number Diff line Loading @@ -65,12 +65,29 @@ public class BitmapTileProvider implements TileImageView.Model { return mMipmaps.length; } public Bitmap getTile(int level, int x, int y, int tileSize) { Bitmap result = Bitmap.createBitmap(tileSize, tileSize, mConfig); public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { x >>= level; y >>= level; int size = tileSize + 2 * borderSize; Bitmap result = Bitmap.createBitmap(size, size, mConfig); Bitmap mipmap = mMipmaps[level]; Canvas canvas = new Canvas(result); canvas.drawBitmap(mMipmaps[level], -(x >> level), -(y >> level), null); int offsetX = -x + borderSize; int offsetY = -y + borderSize; canvas.drawBitmap(mipmap, offsetX, offsetY, null); // If the valid region (covered by mipmap or border) is smaller than the // result bitmap, subset it. int endX = offsetX + mipmap.getWidth() + borderSize; int endY = offsetY + mipmap.getHeight() + borderSize; if (endX < size || endY < size) { return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), Math.min(size, endY)); } else { return result; } } public void recycle() { if (mRecycled) return; Loading src/com/android/gallery3d/ui/PhotoView.java +164 −29 Original line number Diff line number Diff line Loading @@ -19,13 +19,16 @@ package com.android.gallery3d.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.os.Message; import android.view.MotionEvent; import android.view.animation.AccelerateInterpolator; import com.android.gallery3d.R; import com.android.gallery3d.app.GalleryActivity; import com.android.gallery3d.common.Utils; public class PhotoView extends GLView { @SuppressWarnings("unused") Loading Loading @@ -59,6 +62,14 @@ public class PhotoView extends GLView { private static final float SWIPE_THRESHOLD = 300f; private static final float DEFAULT_TEXT_SIZE = 20; private static float TRANSITION_SCALE_FACTOR = 0.74f; // Used to calculate the scaling factor for the fading animation. private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f); // Used to calculate the alpha factor for the fading animation. private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); public interface PhotoTapListener { public void onSingleTapUp(int x, int y); Loading Loading @@ -91,6 +102,7 @@ public class PhotoView extends GLView { private int mImageRotation; private Rect mOpenAnimationRect; private Point mImageCenter = new Point(); public PhotoView(GalleryActivity activity) { mTileView = new TileImageView(activity); Loading Loading @@ -153,25 +165,50 @@ public class PhotoView extends GLView { mPhotoTapListener = listener; } private boolean setTileViewPosition(int centerX, int centerY, float scale) { private void setTileViewPosition(int centerX, int centerY, float scale) { TileImageView t = mTileView; // Calculate the move-out progress value. RectF bounds = mPositionController.getImageBounds(); int left = Math.round(bounds.left); int right = Math.round(bounds.right); int width = getWidth(); float progress = calculateMoveOutProgress(left, right, width); progress = Utils.clamp(progress, -1f, 1f); // We only want to apply the fading animation if the scrolling movement // is to the right. if (progress < 0) { if (right - left < width) { // If the picture is narrower than the view, keep it at the center // of the view. centerX = mPositionController.getImageWidth() / 2; } else { // If the picture is wider than the view (it's zoomed-in), keep // the left edge of the object align the the left edge of the view. centerX = Math.round(width / 2f / scale); } scale *= getScrollScale(progress); t.setAlpha(getScrollAlpha(progress)); } // set the position of the tile view int inverseX = mPositionController.getImageWidth() - centerX; int inverseY = mPositionController.getImageHeight() - centerY; TileImageView t = mTileView; int rotation = mImageRotation; switch (rotation) { case 0: return t.setPosition(centerX, centerY, scale, 0); case 90: return t.setPosition(centerY, inverseX, scale, 90); case 180: return t.setPosition(inverseX, inverseY, scale, 180); case 270: return t.setPosition(inverseY, centerX, scale, 270); case 0: t.setPosition(centerX, centerY, scale, 0); break; case 90: t.setPosition(centerY, inverseX, scale, 90); break; case 180: t.setPosition(inverseX, inverseY, scale, 180); break; case 270: t.setPosition(inverseY, centerX, scale, 270); break; default: throw new IllegalArgumentException(String.valueOf(rotation)); } } public void setPosition(int centerX, int centerY, float scale) { if (setTileViewPosition(centerX, centerY, scale)) { setTileViewPosition(centerX, centerY, scale); layoutScreenNails(); } } private void updateScreenNailEntry(int which, ScreenNail screenNail) { if (mTransitionMode == TRANS_SWITCH_NEXT Loading Loading @@ -202,6 +239,7 @@ public class PhotoView extends GLView { case 0: { // mImageWidth and mImageHeight will get updated mTileView.notifyModelInvalidated(); mTileView.setAlpha(1.0f); mImageRotation = mModel.getImageRotation(); if (((mImageRotation / 90) & 1) == 0) { Loading Loading @@ -251,6 +289,7 @@ public class PhotoView extends GLView { if (mModel == null) { mTileView.notifyModelInvalidated(); mTileView.setAlpha(1.0f); mImageRotation = 0; mPositionController.setImageSize(0, 0); updateLoadingState(); Loading Loading @@ -325,22 +364,38 @@ public class PhotoView extends GLView { @Override protected void render(GLCanvas canvas) { boolean drawScreenNail = (mTransitionMode != TRANS_SLIDE_IN_LEFT && mTransitionMode != TRANS_SLIDE_IN_RIGHT && mTransitionMode != TRANS_OPEN_ANIMATION); // Draw the next photo if (drawScreenNail) { ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; nextNail.draw(canvas, true); } // Draw the current photo if (mLoadingState == LOADING_COMPLETE) { super.render(canvas); } // Draw the previous and the next photo if (mTransitionMode != TRANS_SLIDE_IN_LEFT && mTransitionMode != TRANS_SLIDE_IN_RIGHT && mTransitionMode != TRANS_OPEN_ANIMATION) { ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; // If the photo is loaded, draw the message/icon at the center of it, // otherwise draw the message/icon at the center of the view. if (mLoadingState == LOADING_COMPLETE) { mTileView.getImageCenter(mImageCenter); renderMessage(canvas, mImageCenter.x, mImageCenter.y); } else { renderMessage(canvas, getWidth() / 2, getHeight() / 2); } prevNail.draw(canvas); nextNail.draw(canvas); // Draw the previous photo if (drawScreenNail) { ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; prevNail.draw(canvas, false); } } private void renderMessage(GLCanvas canvas, int x, int y) { // Draw the progress spinner and the text below it // // (x, y) is where we put the center of the spinner. Loading @@ -349,8 +404,6 @@ public class PhotoView extends GLView { // play icon is shown instead of the spinner. int w = getWidth(); int h = getHeight(); int x = Math.round(mPositionController.getImageBounds().centerX()); int y = h / 2; int s = Math.min(getWidth(), getHeight()) / 6; if (mLoadingState == LOADING_TIMEOUT) { Loading Loading @@ -688,30 +741,112 @@ public class PhotoView extends GLView { return mEnabled; } public void draw(GLCanvas canvas) { public void draw(GLCanvas canvas, boolean applyFadingAnimation) { if (mScreenNail == null) return; if (!mVisible) { mScreenNail.disableDraw(); return; } int x = mOffsetX; int w = getWidth(); int x = applyFadingAnimation ? w / 2 : mOffsetX; int y = getHeight() / 2; int flags = GLCanvas.SAVE_FLAG_MATRIX; if (mRotation != 0) { canvas.save(GLCanvas.SAVE_FLAG_MATRIX); if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA; canvas.save(flags); canvas.translate(x, y); if (applyFadingAnimation) { float progress = (float) (x - mOffsetX) / w; float alpha = getScrollAlpha(progress); float scale = getScrollScale(progress); canvas.multiplyAlpha(alpha); canvas.scale(scale, scale, 1); } if (mRotation != 0) { canvas.rotate(mRotation, 0, 0, 1); canvas.translate(-x, -y); } canvas.translate(-x, -y); mScreenNail.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2, mDrawWidth, mDrawHeight); if (mRotation != 0) { canvas.restore(); } } // Returns the scrolling progress value for an object moving out of a // view. The progress value measures how much the object has moving out of // the view. The object currently displays in [left, right), and the view is // at [0, viewWidth]. // // The returned value is negative when the object is moving right, and // positive when the object is moving left. The value goes to -1 or 1 when // the object just moves out of the view completely. The value is 0 if the // object currently fills the view. private static float calculateMoveOutProgress(int left, int right, int viewWidth) { // w = object width // viewWidth = view width int w = right - left; // If the object width is smaller than the view width, // |....view....| // |<-->| progress = -1 when left = viewWidth // |<-->| progress = 1 when left = -w // So progress = 1 - 2 * (left + w) / (viewWidth + w) if (w < viewWidth) { return 1f - 2f * (left + w) / (viewWidth + w); } // If the object width is larger than the view width, // |..view..| // |<--------->| progress = -1 when left = viewWidth // |<--------->| progress = 0 between left = 0 // |<--------->| and right = viewWidth // |<--------->| progress = 1 when right = 0 if (left > 0) { return -left / (float) viewWidth; } if (right < viewWidth) { return (viewWidth - right) / (float) viewWidth; } return 0; } // Maps a scrolling progress value to the alpha factor in the fading // animation. private float getScrollAlpha(float scrollProgress) { return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( 1 - Math.abs(scrollProgress)) : 1.0f; } // Maps a scrolling progress value to the scaling factor in the fading // animation. private float getScrollScale(float scrollProgress) { float interpolatedProgress = mScaleInterpolator.getInterpolation( Math.abs(scrollProgress)); float scale = (1 - interpolatedProgress) + interpolatedProgress * TRANSITION_SCALE_FACTOR; return scale; } // This interpolator emulates the rate at which the perceived scale of an // object changes as its distance from a camera increases. When this // interpolator is applied to a scale animation on a view, it evokes the // sense that the object is shrinking due to moving away from the camera. private static class ZInterpolator { private float focalLength; public ZInterpolator(float foc) { focalLength = foc; } public float getInterpolation(float input) { return (1.0f - focalLength / (focalLength + input)) / (1.0f - focalLength / (focalLength + 1.0f)); } } public void pause() { Loading src/com/android/gallery3d/ui/TileImageView.java +76 −13 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.util.FloatMath; Loading Loading @@ -107,6 +108,7 @@ public class TileImageView extends GLView { protected int mCenterY; protected float mScale; protected int mRotation; protected float mAlpha = 1.0f; // Temp variables to avoid memory allocation private final Rect mTileRange = new Rect(); Loading @@ -124,8 +126,20 @@ public class TileImageView extends GLView { public int getImageWidth(); public int getImageHeight(); // The method would be called in another thread public Bitmap getTile(int level, int x, int y, int tileSize); // The tile returned by this method can be specified this way: Assuming // the image size is (width, height), first take the intersection of (0, // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then // extend this intersection region by borderSize pixels on each side. If // in extending the region, we found some part of the region are outside // the image, those pixels are filled with black. // // If level > 0, it does the same operation on a down-scaled version of // the original image (down-scaled by a factor of 2^level), but (x, y) // still refers to the coordinate on the original image. // // The method would be called in another thread. public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize); public boolean isFailedToLoad(); } Loading Loading @@ -300,6 +314,30 @@ public class TileImageView extends GLView { out.set(left, top, right, bottom); } // Calculate where the center of the image is, in the view coordinates. public void getImageCenter(Point center) { // The width and height of this view. int viewW = getWidth(); int viewH = getHeight(); // The distance between the center of the view to the center of the // bitmap, in bitmap units. (mCenterX and mCenterY are the bitmap // coordinates correspond to the center of view) int distW, distH; if (mRotation % 180 == 0) { distW = mImageWidth / 2 - mCenterX; distH = mImageHeight / 2 - mCenterY; } else { distW = mImageHeight / 2 - mCenterY; distH = mImageWidth / 2 - mCenterX; } // Convert to view coordinates. mScale translates from bitmap units to // view units. center.x = Math.round(viewW / 2f + distW * mScale); center.y = Math.round(viewH / 2f + distH * mScale); } public boolean setPosition(int centerX, int centerY, float scale, int rotation) { if (mCenterX == centerX && mCenterY == centerY && mScale == scale) return false; Loading @@ -312,6 +350,13 @@ public class TileImageView extends GLView { return true; } public boolean setAlpha(float alpha) { if (mAlpha == alpha) return false; mAlpha = alpha; invalidate(); return true; } public void freeTextures() { mIsTextureFreed = true; Loading Loading @@ -359,14 +404,20 @@ public class TileImageView extends GLView { int level = mLevel; int rotation = mRotation; int flags = 0; if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX; if (mAlpha != 1.0f) flags |= GLCanvas.SAVE_FLAG_ALPHA; if (flags != 0) { canvas.save(flags); if (rotation != 0) { canvas.save(GLCanvas.SAVE_FLAG_MATRIX); int centerX = getWidth() / 2, centerY = getHeight() / 2; canvas.translate(centerX, centerY); canvas.rotate(rotation, 0, 0, 1); canvas.translate(-centerX, -centerY); } if (mAlpha != 1.0f) canvas.multiplyAlpha(mAlpha); } try { if (level != mLevelCount) { if (mScreenNail != null) { Loading @@ -390,7 +441,7 @@ public class TileImageView extends GLView { Math.round(mImageHeight * mScale)); } } finally { if (rotation != 0) canvas.restore(); if (flags != 0) canvas.restore(); } if (mRenderComplete) { Loading Loading @@ -600,11 +651,9 @@ public class TileImageView extends GLView { boolean decode() { // Get a tile from the original image. The tile is down-scaled // by (1 << mTilelevel) from a region in the original image. int tileLength = (TILE_SIZE + 2 * TILE_BORDER); int borderLength = TILE_BORDER << mTileLevel; try { mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile( mTileLevel, mX - borderLength, mY - borderLength, tileLength)); mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER)); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); } Loading @@ -620,6 +669,20 @@ public class TileImageView extends GLView { return bitmap; } // We override getTextureWidth() and getTextureHeight() here, so the // texture can be re-used for different tiles regardless of the actual // size of the tile (which may be small because it is a tile at the // boundary). @Override public int getTextureWidth() { return TILE_SIZE + TILE_BORDER * 2; } @Override public int getTextureHeight() { return TILE_SIZE + TILE_BORDER * 2; } public void update(int x, int y, int level) { mX = x; mY = y; Loading src/com/android/gallery3d/ui/TileImageViewAdapter.java +38 −21 Original line number Diff line number Diff line Loading @@ -34,9 +34,6 @@ public class TileImageViewAdapter implements TileImageView.Model { protected int mLevelCount; protected boolean mFailedToLoad; private final Rect mIntersectRect = new Rect(); private final Rect mRegionRect = new Rect(); public TileImageViewAdapter() { } Loading Loading @@ -90,16 +87,24 @@ public class TileImageViewAdapter implements TileImageView.Model { } @Override public synchronized Bitmap getTile(int level, int x, int y, int length) { public synchronized Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { if (mRegionDecoder == null) return null; Rect region = mRegionRect; Rect intersectRect = mIntersectRect; region.set(x, y, x + (length << level), y + (length << level)); intersectRect.set(0, 0, mImageWidth, mImageHeight); // wantRegion is the rectangle on the original image we want. askRegion // is the rectangle on the original image that we will ask from // mRegionDecoder. Both are in the coordinates of the original image, // not the coordinates of the scaled-down images. Rect wantRegion = new Rect(); Rect askRegion = new Rect(); int b = borderSize << level; wantRegion.set(x - b, y - b, x + (tileSize << level) + b, y + (tileSize << level) + b); // Get the intersected rect of the requested region and the image. Utils.assertTrue(intersectRect.intersect(region)); // askRegion is the intersection of wantRegion and the original image. askRegion.set(0, 0, mImageWidth, mImageHeight); Utils.assertTrue(askRegion.intersect(wantRegion)); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.ARGB_8888; Loading @@ -110,25 +115,37 @@ public class TileImageViewAdapter implements TileImageView.Model { // In CropImage, we may call the decodeRegion() concurrently. synchronized (mRegionDecoder) { bitmap = mRegionDecoder.decodeRegion(intersectRect, options); bitmap = mRegionDecoder.decodeRegion(askRegion, options); } // The returned region may not match with the targetLength. // If so, we fill black pixels on it. if (intersectRect.equals(region)) return bitmap; if (bitmap == null) { Log.w(TAG, "fail in decoding region"); return null; } Bitmap tile = Bitmap.createBitmap(length, length, Config.ARGB_8888); Canvas canvas = new Canvas(tile); canvas.drawBitmap(bitmap, (intersectRect.left - region.left) >> level, (intersectRect.top - region.top) >> level, null); if (wantRegion.equals(askRegion)) return bitmap; // Now the wantRegion does not match the askRegion. This means we are at // a boundary tile, and we need to add paddings. Create a new Bitmap // and copy over. int size = tileSize + 2 * borderSize; Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); Canvas canvas = new Canvas(result); int offsetX = (askRegion.left - wantRegion.left) >> level; int offsetY = (askRegion.top - wantRegion.top) >> level; canvas.drawBitmap(bitmap, offsetX, offsetY, null); // If the valid region (covered by bitmap or border) is smaller than the // result bitmap, subset it. int endX = offsetX + bitmap.getWidth() + borderSize; int endY = offsetY + bitmap.getHeight() + borderSize; bitmap.recycle(); return tile; if (endX < size || endY < size) { return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), Math.min(size, endY)); } else { return result; } } @Override Loading Loading
src/com/android/gallery3d/app/PhotoDataAdapter.java +3 −2 Original line number Diff line number Diff line Loading @@ -362,8 +362,9 @@ public class PhotoDataAdapter implements PhotoPage.Model { return mTileProvider.getLevelCount(); } public Bitmap getTile(int level, int x, int y, int tileSize) { return mTileProvider.getTile(level, x, y, tileSize); public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { return mTileProvider.getTile(level, x, y, tileSize, borderSize); } public boolean isFailedToLoad() { Loading
src/com/android/gallery3d/ui/BitmapTileProvider.java +21 −4 Original line number Diff line number Diff line Loading @@ -65,12 +65,29 @@ public class BitmapTileProvider implements TileImageView.Model { return mMipmaps.length; } public Bitmap getTile(int level, int x, int y, int tileSize) { Bitmap result = Bitmap.createBitmap(tileSize, tileSize, mConfig); public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { x >>= level; y >>= level; int size = tileSize + 2 * borderSize; Bitmap result = Bitmap.createBitmap(size, size, mConfig); Bitmap mipmap = mMipmaps[level]; Canvas canvas = new Canvas(result); canvas.drawBitmap(mMipmaps[level], -(x >> level), -(y >> level), null); int offsetX = -x + borderSize; int offsetY = -y + borderSize; canvas.drawBitmap(mipmap, offsetX, offsetY, null); // If the valid region (covered by mipmap or border) is smaller than the // result bitmap, subset it. int endX = offsetX + mipmap.getWidth() + borderSize; int endY = offsetY + mipmap.getHeight() + borderSize; if (endX < size || endY < size) { return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), Math.min(size, endY)); } else { return result; } } public void recycle() { if (mRecycled) return; Loading
src/com/android/gallery3d/ui/PhotoView.java +164 −29 Original line number Diff line number Diff line Loading @@ -19,13 +19,16 @@ package com.android.gallery3d.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.os.Message; import android.view.MotionEvent; import android.view.animation.AccelerateInterpolator; import com.android.gallery3d.R; import com.android.gallery3d.app.GalleryActivity; import com.android.gallery3d.common.Utils; public class PhotoView extends GLView { @SuppressWarnings("unused") Loading Loading @@ -59,6 +62,14 @@ public class PhotoView extends GLView { private static final float SWIPE_THRESHOLD = 300f; private static final float DEFAULT_TEXT_SIZE = 20; private static float TRANSITION_SCALE_FACTOR = 0.74f; // Used to calculate the scaling factor for the fading animation. private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f); // Used to calculate the alpha factor for the fading animation. private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); public interface PhotoTapListener { public void onSingleTapUp(int x, int y); Loading Loading @@ -91,6 +102,7 @@ public class PhotoView extends GLView { private int mImageRotation; private Rect mOpenAnimationRect; private Point mImageCenter = new Point(); public PhotoView(GalleryActivity activity) { mTileView = new TileImageView(activity); Loading Loading @@ -153,25 +165,50 @@ public class PhotoView extends GLView { mPhotoTapListener = listener; } private boolean setTileViewPosition(int centerX, int centerY, float scale) { private void setTileViewPosition(int centerX, int centerY, float scale) { TileImageView t = mTileView; // Calculate the move-out progress value. RectF bounds = mPositionController.getImageBounds(); int left = Math.round(bounds.left); int right = Math.round(bounds.right); int width = getWidth(); float progress = calculateMoveOutProgress(left, right, width); progress = Utils.clamp(progress, -1f, 1f); // We only want to apply the fading animation if the scrolling movement // is to the right. if (progress < 0) { if (right - left < width) { // If the picture is narrower than the view, keep it at the center // of the view. centerX = mPositionController.getImageWidth() / 2; } else { // If the picture is wider than the view (it's zoomed-in), keep // the left edge of the object align the the left edge of the view. centerX = Math.round(width / 2f / scale); } scale *= getScrollScale(progress); t.setAlpha(getScrollAlpha(progress)); } // set the position of the tile view int inverseX = mPositionController.getImageWidth() - centerX; int inverseY = mPositionController.getImageHeight() - centerY; TileImageView t = mTileView; int rotation = mImageRotation; switch (rotation) { case 0: return t.setPosition(centerX, centerY, scale, 0); case 90: return t.setPosition(centerY, inverseX, scale, 90); case 180: return t.setPosition(inverseX, inverseY, scale, 180); case 270: return t.setPosition(inverseY, centerX, scale, 270); case 0: t.setPosition(centerX, centerY, scale, 0); break; case 90: t.setPosition(centerY, inverseX, scale, 90); break; case 180: t.setPosition(inverseX, inverseY, scale, 180); break; case 270: t.setPosition(inverseY, centerX, scale, 270); break; default: throw new IllegalArgumentException(String.valueOf(rotation)); } } public void setPosition(int centerX, int centerY, float scale) { if (setTileViewPosition(centerX, centerY, scale)) { setTileViewPosition(centerX, centerY, scale); layoutScreenNails(); } } private void updateScreenNailEntry(int which, ScreenNail screenNail) { if (mTransitionMode == TRANS_SWITCH_NEXT Loading Loading @@ -202,6 +239,7 @@ public class PhotoView extends GLView { case 0: { // mImageWidth and mImageHeight will get updated mTileView.notifyModelInvalidated(); mTileView.setAlpha(1.0f); mImageRotation = mModel.getImageRotation(); if (((mImageRotation / 90) & 1) == 0) { Loading Loading @@ -251,6 +289,7 @@ public class PhotoView extends GLView { if (mModel == null) { mTileView.notifyModelInvalidated(); mTileView.setAlpha(1.0f); mImageRotation = 0; mPositionController.setImageSize(0, 0); updateLoadingState(); Loading Loading @@ -325,22 +364,38 @@ public class PhotoView extends GLView { @Override protected void render(GLCanvas canvas) { boolean drawScreenNail = (mTransitionMode != TRANS_SLIDE_IN_LEFT && mTransitionMode != TRANS_SLIDE_IN_RIGHT && mTransitionMode != TRANS_OPEN_ANIMATION); // Draw the next photo if (drawScreenNail) { ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; nextNail.draw(canvas, true); } // Draw the current photo if (mLoadingState == LOADING_COMPLETE) { super.render(canvas); } // Draw the previous and the next photo if (mTransitionMode != TRANS_SLIDE_IN_LEFT && mTransitionMode != TRANS_SLIDE_IN_RIGHT && mTransitionMode != TRANS_OPEN_ANIMATION) { ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; // If the photo is loaded, draw the message/icon at the center of it, // otherwise draw the message/icon at the center of the view. if (mLoadingState == LOADING_COMPLETE) { mTileView.getImageCenter(mImageCenter); renderMessage(canvas, mImageCenter.x, mImageCenter.y); } else { renderMessage(canvas, getWidth() / 2, getHeight() / 2); } prevNail.draw(canvas); nextNail.draw(canvas); // Draw the previous photo if (drawScreenNail) { ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; prevNail.draw(canvas, false); } } private void renderMessage(GLCanvas canvas, int x, int y) { // Draw the progress spinner and the text below it // // (x, y) is where we put the center of the spinner. Loading @@ -349,8 +404,6 @@ public class PhotoView extends GLView { // play icon is shown instead of the spinner. int w = getWidth(); int h = getHeight(); int x = Math.round(mPositionController.getImageBounds().centerX()); int y = h / 2; int s = Math.min(getWidth(), getHeight()) / 6; if (mLoadingState == LOADING_TIMEOUT) { Loading Loading @@ -688,30 +741,112 @@ public class PhotoView extends GLView { return mEnabled; } public void draw(GLCanvas canvas) { public void draw(GLCanvas canvas, boolean applyFadingAnimation) { if (mScreenNail == null) return; if (!mVisible) { mScreenNail.disableDraw(); return; } int x = mOffsetX; int w = getWidth(); int x = applyFadingAnimation ? w / 2 : mOffsetX; int y = getHeight() / 2; int flags = GLCanvas.SAVE_FLAG_MATRIX; if (mRotation != 0) { canvas.save(GLCanvas.SAVE_FLAG_MATRIX); if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA; canvas.save(flags); canvas.translate(x, y); if (applyFadingAnimation) { float progress = (float) (x - mOffsetX) / w; float alpha = getScrollAlpha(progress); float scale = getScrollScale(progress); canvas.multiplyAlpha(alpha); canvas.scale(scale, scale, 1); } if (mRotation != 0) { canvas.rotate(mRotation, 0, 0, 1); canvas.translate(-x, -y); } canvas.translate(-x, -y); mScreenNail.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2, mDrawWidth, mDrawHeight); if (mRotation != 0) { canvas.restore(); } } // Returns the scrolling progress value for an object moving out of a // view. The progress value measures how much the object has moving out of // the view. The object currently displays in [left, right), and the view is // at [0, viewWidth]. // // The returned value is negative when the object is moving right, and // positive when the object is moving left. The value goes to -1 or 1 when // the object just moves out of the view completely. The value is 0 if the // object currently fills the view. private static float calculateMoveOutProgress(int left, int right, int viewWidth) { // w = object width // viewWidth = view width int w = right - left; // If the object width is smaller than the view width, // |....view....| // |<-->| progress = -1 when left = viewWidth // |<-->| progress = 1 when left = -w // So progress = 1 - 2 * (left + w) / (viewWidth + w) if (w < viewWidth) { return 1f - 2f * (left + w) / (viewWidth + w); } // If the object width is larger than the view width, // |..view..| // |<--------->| progress = -1 when left = viewWidth // |<--------->| progress = 0 between left = 0 // |<--------->| and right = viewWidth // |<--------->| progress = 1 when right = 0 if (left > 0) { return -left / (float) viewWidth; } if (right < viewWidth) { return (viewWidth - right) / (float) viewWidth; } return 0; } // Maps a scrolling progress value to the alpha factor in the fading // animation. private float getScrollAlpha(float scrollProgress) { return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( 1 - Math.abs(scrollProgress)) : 1.0f; } // Maps a scrolling progress value to the scaling factor in the fading // animation. private float getScrollScale(float scrollProgress) { float interpolatedProgress = mScaleInterpolator.getInterpolation( Math.abs(scrollProgress)); float scale = (1 - interpolatedProgress) + interpolatedProgress * TRANSITION_SCALE_FACTOR; return scale; } // This interpolator emulates the rate at which the perceived scale of an // object changes as its distance from a camera increases. When this // interpolator is applied to a scale animation on a view, it evokes the // sense that the object is shrinking due to moving away from the camera. private static class ZInterpolator { private float focalLength; public ZInterpolator(float foc) { focalLength = foc; } public float getInterpolation(float input) { return (1.0f - focalLength / (focalLength + input)) / (1.0f - focalLength / (focalLength + 1.0f)); } } public void pause() { Loading
src/com/android/gallery3d/ui/TileImageView.java +76 −13 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.util.FloatMath; Loading Loading @@ -107,6 +108,7 @@ public class TileImageView extends GLView { protected int mCenterY; protected float mScale; protected int mRotation; protected float mAlpha = 1.0f; // Temp variables to avoid memory allocation private final Rect mTileRange = new Rect(); Loading @@ -124,8 +126,20 @@ public class TileImageView extends GLView { public int getImageWidth(); public int getImageHeight(); // The method would be called in another thread public Bitmap getTile(int level, int x, int y, int tileSize); // The tile returned by this method can be specified this way: Assuming // the image size is (width, height), first take the intersection of (0, // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then // extend this intersection region by borderSize pixels on each side. If // in extending the region, we found some part of the region are outside // the image, those pixels are filled with black. // // If level > 0, it does the same operation on a down-scaled version of // the original image (down-scaled by a factor of 2^level), but (x, y) // still refers to the coordinate on the original image. // // The method would be called in another thread. public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize); public boolean isFailedToLoad(); } Loading Loading @@ -300,6 +314,30 @@ public class TileImageView extends GLView { out.set(left, top, right, bottom); } // Calculate where the center of the image is, in the view coordinates. public void getImageCenter(Point center) { // The width and height of this view. int viewW = getWidth(); int viewH = getHeight(); // The distance between the center of the view to the center of the // bitmap, in bitmap units. (mCenterX and mCenterY are the bitmap // coordinates correspond to the center of view) int distW, distH; if (mRotation % 180 == 0) { distW = mImageWidth / 2 - mCenterX; distH = mImageHeight / 2 - mCenterY; } else { distW = mImageHeight / 2 - mCenterY; distH = mImageWidth / 2 - mCenterX; } // Convert to view coordinates. mScale translates from bitmap units to // view units. center.x = Math.round(viewW / 2f + distW * mScale); center.y = Math.round(viewH / 2f + distH * mScale); } public boolean setPosition(int centerX, int centerY, float scale, int rotation) { if (mCenterX == centerX && mCenterY == centerY && mScale == scale) return false; Loading @@ -312,6 +350,13 @@ public class TileImageView extends GLView { return true; } public boolean setAlpha(float alpha) { if (mAlpha == alpha) return false; mAlpha = alpha; invalidate(); return true; } public void freeTextures() { mIsTextureFreed = true; Loading Loading @@ -359,14 +404,20 @@ public class TileImageView extends GLView { int level = mLevel; int rotation = mRotation; int flags = 0; if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX; if (mAlpha != 1.0f) flags |= GLCanvas.SAVE_FLAG_ALPHA; if (flags != 0) { canvas.save(flags); if (rotation != 0) { canvas.save(GLCanvas.SAVE_FLAG_MATRIX); int centerX = getWidth() / 2, centerY = getHeight() / 2; canvas.translate(centerX, centerY); canvas.rotate(rotation, 0, 0, 1); canvas.translate(-centerX, -centerY); } if (mAlpha != 1.0f) canvas.multiplyAlpha(mAlpha); } try { if (level != mLevelCount) { if (mScreenNail != null) { Loading @@ -390,7 +441,7 @@ public class TileImageView extends GLView { Math.round(mImageHeight * mScale)); } } finally { if (rotation != 0) canvas.restore(); if (flags != 0) canvas.restore(); } if (mRenderComplete) { Loading Loading @@ -600,11 +651,9 @@ public class TileImageView extends GLView { boolean decode() { // Get a tile from the original image. The tile is down-scaled // by (1 << mTilelevel) from a region in the original image. int tileLength = (TILE_SIZE + 2 * TILE_BORDER); int borderLength = TILE_BORDER << mTileLevel; try { mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile( mTileLevel, mX - borderLength, mY - borderLength, tileLength)); mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER)); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); } Loading @@ -620,6 +669,20 @@ public class TileImageView extends GLView { return bitmap; } // We override getTextureWidth() and getTextureHeight() here, so the // texture can be re-used for different tiles regardless of the actual // size of the tile (which may be small because it is a tile at the // boundary). @Override public int getTextureWidth() { return TILE_SIZE + TILE_BORDER * 2; } @Override public int getTextureHeight() { return TILE_SIZE + TILE_BORDER * 2; } public void update(int x, int y, int level) { mX = x; mY = y; Loading
src/com/android/gallery3d/ui/TileImageViewAdapter.java +38 −21 Original line number Diff line number Diff line Loading @@ -34,9 +34,6 @@ public class TileImageViewAdapter implements TileImageView.Model { protected int mLevelCount; protected boolean mFailedToLoad; private final Rect mIntersectRect = new Rect(); private final Rect mRegionRect = new Rect(); public TileImageViewAdapter() { } Loading Loading @@ -90,16 +87,24 @@ public class TileImageViewAdapter implements TileImageView.Model { } @Override public synchronized Bitmap getTile(int level, int x, int y, int length) { public synchronized Bitmap getTile(int level, int x, int y, int tileSize, int borderSize) { if (mRegionDecoder == null) return null; Rect region = mRegionRect; Rect intersectRect = mIntersectRect; region.set(x, y, x + (length << level), y + (length << level)); intersectRect.set(0, 0, mImageWidth, mImageHeight); // wantRegion is the rectangle on the original image we want. askRegion // is the rectangle on the original image that we will ask from // mRegionDecoder. Both are in the coordinates of the original image, // not the coordinates of the scaled-down images. Rect wantRegion = new Rect(); Rect askRegion = new Rect(); int b = borderSize << level; wantRegion.set(x - b, y - b, x + (tileSize << level) + b, y + (tileSize << level) + b); // Get the intersected rect of the requested region and the image. Utils.assertTrue(intersectRect.intersect(region)); // askRegion is the intersection of wantRegion and the original image. askRegion.set(0, 0, mImageWidth, mImageHeight); Utils.assertTrue(askRegion.intersect(wantRegion)); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.ARGB_8888; Loading @@ -110,25 +115,37 @@ public class TileImageViewAdapter implements TileImageView.Model { // In CropImage, we may call the decodeRegion() concurrently. synchronized (mRegionDecoder) { bitmap = mRegionDecoder.decodeRegion(intersectRect, options); bitmap = mRegionDecoder.decodeRegion(askRegion, options); } // The returned region may not match with the targetLength. // If so, we fill black pixels on it. if (intersectRect.equals(region)) return bitmap; if (bitmap == null) { Log.w(TAG, "fail in decoding region"); return null; } Bitmap tile = Bitmap.createBitmap(length, length, Config.ARGB_8888); Canvas canvas = new Canvas(tile); canvas.drawBitmap(bitmap, (intersectRect.left - region.left) >> level, (intersectRect.top - region.top) >> level, null); if (wantRegion.equals(askRegion)) return bitmap; // Now the wantRegion does not match the askRegion. This means we are at // a boundary tile, and we need to add paddings. Create a new Bitmap // and copy over. int size = tileSize + 2 * borderSize; Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); Canvas canvas = new Canvas(result); int offsetX = (askRegion.left - wantRegion.left) >> level; int offsetY = (askRegion.top - wantRegion.top) >> level; canvas.drawBitmap(bitmap, offsetX, offsetY, null); // If the valid region (covered by bitmap or border) is smaller than the // result bitmap, subset it. int endX = offsetX + bitmap.getWidth() + borderSize; int endY = offsetY + bitmap.getHeight() + borderSize; bitmap.recycle(); return tile; if (endX < size || endY < size) { return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), Math.min(size, endY)); } else { return result; } } @Override Loading