Patch for MR1.

Change-Id: I3f4bba2854257008eed95b8eacce598abdca3180
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index 9b1c8c4..d7d1168 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -365,8 +365,9 @@
         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() {
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index a47337f..3d4d4dc 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -65,11 +65,28 @@
         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);
-        return result;
+        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() {
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 5062c0e..aeed577 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -25,12 +25,15 @@
 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.os.SystemClock;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
+import android.view.animation.AccelerateInterpolator;
 
 public class PhotoView extends GLView {
     @SuppressWarnings("unused")
@@ -64,6 +67,14 @@
     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);
@@ -99,6 +110,7 @@
 
     private Path mOpenedItemPath;
     private GalleryActivity mActivity;
+    private Point mImageCenter = new Point();
 
     public PhotoView(GalleryActivity activity) {
         mActivity = activity;
@@ -164,24 +176,49 @@
         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)) {
-            layoutScreenNails();
-        }
+        setTileViewPosition(centerX, centerY, scale);
+        layoutScreenNails();
     }
 
     private void updateScreenNailEntry(int which, ImageData data) {
@@ -217,6 +254,7 @@
             case 0: {
                 // mImageWidth and mImageHeight will get updated
                 mTileView.notifyModelInvalidated();
+                mTileView.setAlpha(1.0f);
 
                 mImageRotation = mModel.getImageRotation();
                 if (((mImageRotation / 90) & 1) == 0) {
@@ -264,6 +302,7 @@
 
         if (mModel == null) {
             mTileView.notifyModelInvalidated();
+            mTileView.setAlpha(1.0f);
             mImageRotation = 0;
             mPositionController.setImageSize(0, 0);
             updateLoadingState();
@@ -341,23 +380,40 @@
     @Override
     protected void render(GLCanvas canvas) {
         PositionController p = mPositionController;
+        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];
+            if (nextNail.mVisible) 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 (prevNail.mVisible) prevNail.draw(canvas);
-            if (nextNail.mVisible) nextNail.draw(canvas);
+        // 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);
         }
 
+        // Draw the previous photo
+        if (drawScreenNail) {
+            ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
+            if (prevNail.mVisible) prevNail.draw(canvas, false);
+        }
+
+        if (mPositionController.advanceAnimation()) invalidate();
+    }
+
+    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.
@@ -366,8 +422,6 @@
         // 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) {
@@ -387,8 +441,6 @@
                 && mLoadingState != LOADING_TIMEOUT) {
             mVideoPlayIcon.draw(canvas, x - s / 2, y - s / 2, s, s);
         }
-
-        if (mPositionController.advanceAnimation()) invalidate();
     }
 
     private void stopCurrentSwipingIfNeeded() {
@@ -731,23 +783,107 @@
             return mEnabled;
         }
 
-        public void draw(GLCanvas canvas) {
-            int x = mOffsetX;
-            int y = getHeight() / 2;
+        public void draw(GLCanvas canvas, boolean applyFadingAnimation) {
+            if (mTexture == null) return;
 
-            if (mTexture != null) {
-                if (mRotation != 0) {
-                    canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
-                    canvas.translate(x, y, 0);
-                    canvas.rotate(mRotation, 0, 0, 1); //mRotation
-                    canvas.translate(-x, -y, 0);
-                }
-                mTexture.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2,
-                        mDrawWidth, mDrawHeight);
-                if (mRotation != 0) {
-                    canvas.restore();
-                }
+            int w = getWidth();
+            int x = applyFadingAnimation ? w / 2 : mOffsetX;
+            int y = getHeight() / 2;
+            int flags = GLCanvas.SAVE_FLAG_MATRIX;
+
+            if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA;
+            canvas.save(flags);
+            canvas.translate(x, y, 0);
+            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, 0);
+            mTexture.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2,
+                    mDrawWidth, mDrawHeight);
+            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));
         }
     }
 
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 980f7b2..5c9f3f4 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -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;
 
@@ -108,6 +109,7 @@
     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();
@@ -125,8 +127,20 @@
         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();
     }
 
@@ -308,6 +322,30 @@
         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;
@@ -320,6 +358,13 @@
         return true;
     }
 
+    public boolean setAlpha(float alpha) {
+        if (mAlpha == alpha) return false;
+        mAlpha = alpha;
+        invalidate();
+        return true;
+    }
+
     public void freeTextures() {
         mIsTextureFreed = true;
 
@@ -365,13 +410,19 @@
 
         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 (rotation != 0) {
-            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
-            int centerX = getWidth() / 2, centerY = getHeight() / 2;
-            canvas.translate(centerX, centerY, 0);
-            canvas.rotate(rotation, 0, 0, 1);
-            canvas.translate(-centerX, -centerY, 0);
+        if (flags != 0) {
+            canvas.save(flags);
+            if (rotation != 0) {
+                int centerX = getWidth() / 2, centerY = getHeight() / 2;
+                canvas.translate(centerX, centerY, 0);
+                canvas.rotate(rotation, 0, 0, 1);
+                canvas.translate(-centerX, -centerY, 0);
+            }
+            if (mAlpha != 1.0f) canvas.multiplyAlpha(mAlpha);
         }
         try {
             if (level != mLevelCount) {
@@ -392,7 +443,7 @@
                         Math.round(mImageHeight * mScale));
             }
         } finally {
-            if (rotation != 0) canvas.restore();
+            if (flags != 0) canvas.restore();
         }
 
         if (mRenderComplete) {
@@ -601,11 +652,9 @@
         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);
             }
@@ -621,6 +670,20 @@
             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;
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 63bb0b2..be255d2 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -34,9 +34,6 @@
     protected int mLevelCount;
     protected boolean mFailedToLoad;
 
-    private final Rect mIntersectRect = new Rect();
-    private final Rect mRegionRect = new Rect();
-
     public TileImageViewAdapter() {
     }
 
@@ -80,16 +77,24 @@
     }
 
     @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();
 
-        // Get the intersected rect of the requested region and the image.
-        Utils.assertTrue(intersectRect.intersect(region));
+        int b = borderSize << level;
+        wantRegion.set(x - b, y - b, x + (tileSize << level) + b,
+                y + (tileSize << level) + b);
+
+        // 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;
@@ -100,25 +105,37 @@
 
         // 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
diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java
index b2b8cd5..1777048 100644
--- a/src/com/android/gallery3d/ui/UploadedTexture.java
+++ b/src/com/android/gallery3d/ui/UploadedTexture.java
@@ -161,6 +161,8 @@
     protected void invalidateContent() {
         if (mBitmap != null) freeBitmap();
         mContentValid = false;
+        mWidth = UNSPECIFIED;
+        mHeight = UNSPECIFIED;
     }
 
     /**