Fix 5223982: Adding one pixel transparent border around the texture.

This avoids the jigged edge around the thumbnails when they are rotated.

Change-Id: Ib2c5a75d1aa69a2f024e003a45111a8af4537f67
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index 4253685..3b36397 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -357,7 +357,7 @@
                 if (mActiveRequestCount == 0) requestNonactiveImages();
             }
             if (bitmap != null) {
-                BitmapTexture texture = new BitmapTexture(bitmap);
+                BitmapTexture texture = new BitmapTexture(bitmap, true);
                 texture.setThrottled(true);
                 updateContent(texture);
                 if (mListener != null) mListener.onContentInvalidated();
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index 5184d1c..5f71434 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -304,7 +304,7 @@
                 if (mActiveRequestCount == 0) requestNonactiveImages();
             }
             if (bitmap != null) {
-                BitmapTexture texture = new BitmapTexture(bitmap);
+                BitmapTexture texture = new BitmapTexture(bitmap, true);
                 texture.setThrottled(true);
                 updateContent(texture);
                 if (mListener != null && isActiveSlot) {
diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java
index e930063..8946036 100644
--- a/src/com/android/gallery3d/ui/BasicTexture.java
+++ b/src/com/android/gallery3d/ui/BasicTexture.java
@@ -43,6 +43,8 @@
     private int mTextureWidth;
     private int mTextureHeight;
 
+    private boolean mHasBorder;
+
     protected WeakReference<GLCanvas> mCanvasRef = null;
     private static WeakHashMap<BasicTexture, Object> sAllTextures
             = new WeakHashMap<BasicTexture, Object>();
@@ -100,6 +102,25 @@
         return mTextureHeight;
     }
 
+    // Returns true if the texture has one pixel transparent border around the
+    // actual content. This is used to avoid jigged edges.
+    //
+    // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
+    // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+    // covered by the texture will use the color of the edge texel. If we add
+    // the transparent border, the color of the edge texel will be mixed with
+    // appropriate amount of transparent.
+    //
+    // Currently our background is black, so we can draw the thumbnails without
+    // enabling blending.
+    public boolean hasBorder() {
+        return mHasBorder;
+    }
+
+    protected void setBorder(boolean hasBorder) {
+        mHasBorder = hasBorder;
+    }
+
     public void draw(GLCanvas canvas, int x, int y) {
         canvas.drawTexture(this, x, y, getWidth(), getHeight());
     }
diff --git a/src/com/android/gallery3d/ui/BitmapTexture.java b/src/com/android/gallery3d/ui/BitmapTexture.java
index 046bda9..f5cb2bd 100644
--- a/src/com/android/gallery3d/ui/BitmapTexture.java
+++ b/src/com/android/gallery3d/ui/BitmapTexture.java
@@ -29,6 +29,11 @@
     protected Bitmap mContentBitmap;
 
     public BitmapTexture(Bitmap bitmap) {
+        this(bitmap, false);
+    }
+
+    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
+        super(hasBorder);
         Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
         mContentBitmap = bitmap;
     }
diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java
index 387743f..ab0d91b 100644
--- a/src/com/android/gallery3d/ui/GLCanvasImpl.java
+++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java
@@ -341,9 +341,17 @@
         // Test whether it has been rotated or flipped, if so, glDrawTexiOES
         // won't work
         if (isMatrixRotatedOrFlipped(mMatrixValues)) {
-            setTextureCoords(0, 0,
-                    (float) texture.getWidth() / texture.getTextureWidth(),
-                    (float) texture.getHeight() / texture.getTextureHeight());
+            if (texture.hasBorder()) {
+                setTextureCoords(
+                        1.0f / texture.getTextureWidth(),
+                        1.0f / texture.getTextureHeight(),
+                        (texture.getWidth() - 1.0f) / texture.getTextureWidth(),
+                        (texture.getHeight() - 1.0f) / texture.getTextureHeight());
+            } else {
+                setTextureCoords(0, 0,
+                        (float) texture.getWidth() / texture.getTextureWidth(),
+                        (float) texture.getHeight() / texture.getTextureHeight());
+            }
             textureRect(x, y, width, height);
         } else {
             // draw the rect from bottom-left to top-right
diff --git a/src/com/android/gallery3d/ui/GridDrawer.java b/src/com/android/gallery3d/ui/GridDrawer.java
index 394a6c7..fbd9e78 100644
--- a/src/com/android/gallery3d/ui/GridDrawer.java
+++ b/src/com/android/gallery3d/ui/GridDrawer.java
@@ -24,7 +24,6 @@
 import android.text.Layout;
 
 public class GridDrawer extends IconDrawer {
-    private final NinePatchTexture mFrameSelected;
     private Texture mImportLabel;
     private int mGridWidth;
     private final SelectionManager mSelectionManager;
@@ -37,7 +36,6 @@
     public GridDrawer(Context context, SelectionManager selectionManager) {
         super(context);
         mContext = context;
-        mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected);
         mSelectionManager = selectionManager;
     }
 
@@ -80,7 +78,7 @@
         if (mSelectionManager.isPressedPath(path)) {
             drawPressedFrame(canvas, x, y, width, height);
         } else if (mSelectionMode && mSelectionManager.isItemSelected(path)) {
-            drawFrame(canvas, mFrameSelected, x, y, width, height);
+            drawSelectedFrame(canvas, x, y, width, height);
         }
     }
 
diff --git a/src/com/android/gallery3d/ui/HighlightDrawer.java b/src/com/android/gallery3d/ui/HighlightDrawer.java
index ee37d26..d23a00d 100644
--- a/src/com/android/gallery3d/ui/HighlightDrawer.java
+++ b/src/com/android/gallery3d/ui/HighlightDrawer.java
@@ -21,13 +21,11 @@
 import android.content.Context;
 
 public class HighlightDrawer extends IconDrawer {
-    private final NinePatchTexture mFrameSelected;
     private SelectionManager mSelectionManager;
     private Path mHighlightItem;
 
     public HighlightDrawer(Context context, SelectionManager selectionManager) {
         super(context);
-        mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected);
         mSelectionManager = selectionManager;
     }
 
@@ -64,8 +62,8 @@
 
         if (mSelectionManager.isPressedPath(path)) {
             drawPressedFrame(canvas, x, y, width, height);
-        } else  if (path == mHighlightItem) {
-            drawFrame(canvas, mFrameSelected, x, y, width, height);
+        } else if (path == mHighlightItem) {
+            drawSelectedFrame(canvas, x, y, width, height);
         }
     }
 }
diff --git a/src/com/android/gallery3d/ui/IconDrawer.java b/src/com/android/gallery3d/ui/IconDrawer.java
index 781046c..6ae0157 100644
--- a/src/com/android/gallery3d/ui/IconDrawer.java
+++ b/src/com/android/gallery3d/ui/IconDrawer.java
@@ -29,6 +29,8 @@
     private final ResourceTexture mPicasaIcon;
     private final ResourceTexture mMtpIcon;
     private final NinePatchTexture mFramePressed;
+    private final NinePatchTexture mFrameSelected;
+    private final NinePatchTexture mDarkStrip;
     private final ResourceTexture mPanoramaBorder;
     private final Texture mVideoOverlay;
     private final Texture mVideoPlayIcon;
@@ -50,6 +52,8 @@
         mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_gallery_play);
         mPanoramaBorder = new ResourceTexture(context, R.drawable.ic_pan_thumb);
         mFramePressed = new NinePatchTexture(context, R.drawable.grid_pressed);
+        mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected);
+        mDarkStrip = new NinePatchTexture(context, R.drawable.dark_strip);
         mIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.albumset_icon_size);
     }
@@ -144,7 +148,7 @@
             int drawLabelBackground) {
         int x = -width / 2;
         int y = (height + 1) / 2 - drawLabelBackground;
-        canvas.fillRect(x, y, width, drawLabelBackground, LABEL_BACKGROUND_COLOR);
+        drawFrame(canvas, mDarkStrip, x, y, width, drawLabelBackground);
     }
 
     protected void drawPressedFrame(GLCanvas canvas, int x, int y, int width,
@@ -152,6 +156,11 @@
         drawFrame(canvas, mFramePressed, x, y, width, height);
     }
 
+    protected void drawSelectedFrame(GLCanvas canvas, int x, int y, int width,
+            int height) {
+        drawFrame(canvas, mFrameSelected, x, y, width, height);
+    }
+
     @Override
     public void drawFocus(GLCanvas canvas, int width, int height) {
     }
diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java
index 3eb3f17..3a4de39 100644
--- a/src/com/android/gallery3d/ui/SlotView.java
+++ b/src/com/android/gallery3d/ui/SlotView.java
@@ -238,8 +238,6 @@
 
     @Override
     protected void render(GLCanvas canvas) {
-        canvas.save(GLCanvas.SAVE_FLAG_CLIP);
-        canvas.clipRect(0, 0, getWidth(), getHeight());
         super.render(canvas);
 
         long currentTimeMillis = canvas.currentAnimationTimeMillis();
@@ -296,7 +294,6 @@
             mUIListener.onUserInteractionEnd();
         }
         mMoreAnimation = more;
-        canvas.restore();
     }
 
     private boolean renderItem(GLCanvas canvas, ItemEntry entry,
diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java
index b063824..b2b8cd5 100644
--- a/src/com/android/gallery3d/ui/UploadedTexture.java
+++ b/src/com/android/gallery3d/ui/UploadedTexture.java
@@ -57,9 +57,18 @@
     private static final int UPLOAD_LIMIT = 100;
 
     protected Bitmap mBitmap;
+    private int mBorder;
 
     protected UploadedTexture() {
+        this(false);
+    }
+
+    protected UploadedTexture(boolean hasBorder) {
         super(null, 0, STATE_UNLOADED);
+        if (hasBorder) {
+            setBorder(true);
+            mBorder = 1;
+        }
     }
 
     private static class BorderKey implements Cloneable {
@@ -114,14 +123,14 @@
     private Bitmap getBitmap() {
         if (mBitmap == null) {
             mBitmap = onGetBitmap();
+            int w = mBitmap.getWidth() + mBorder * 2;
+            int h = mBitmap.getHeight() + mBorder * 2;
             if (mWidth == UNSPECIFIED) {
-                setSize(mBitmap.getWidth(), mBitmap.getHeight());
-            } else if (mWidth != mBitmap.getWidth()
-                    || mHeight != mBitmap.getHeight()) {
+                setSize(w, h);
+            } else if (mWidth != w || mHeight != h) {
                 throw new IllegalStateException(String.format(
                         "cannot change size: this = %s, orig = %sx%s, new = %sx%s",
-                        toString(), mWidth, mHeight, mBitmap.getWidth(),
-                        mBitmap.getHeight()));
+                        toString(), mWidth, mHeight, w, h));
             }
         }
         return mBitmap;
@@ -176,8 +185,8 @@
             int format = GLUtils.getInternalFormat(bitmap);
             int type = GLUtils.getType(bitmap);
             canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId);
-            GLUtils.texSubImage2D(
-                    GL11.GL_TEXTURE_2D, 0, 0, 0, bitmap, format, type);
+            GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder,
+                    bitmap, format, type);
             freeBitmap();
             mContentValid = true;
         }
@@ -200,14 +209,20 @@
         Bitmap bitmap = getBitmap();
         if (bitmap != null) {
             try {
+                int bWidth = bitmap.getWidth();
+                int bHeight = bitmap.getHeight();
+                int width = bWidth + mBorder * 2;
+                int height = bHeight + mBorder * 2;
+                int texWidth = getTextureWidth();
+                int texHeight = getTextureHeight();
                 // Define a vertically flipped crop rectangle for
                 // OES_draw_texture.
-                int width = bitmap.getWidth();
-                int height = bitmap.getHeight();
-                sCropRect[0] = 0;
-                sCropRect[1] = height;
-                sCropRect[2] = width;
-                sCropRect[3] = -height;
+                // The four values in sCropRect are: left, bottom, width, and
+                // height. Negative value of width or height means flip.
+                sCropRect[0] = mBorder;
+                sCropRect[1] = mBorder + bHeight;
+                sCropRect[2] = bWidth;
+                sCropRect[3] = -bHeight;
 
                 // Upload the bitmap to a new texture.
                 gl.glGenTextures(1, sTextureId, 0);
@@ -223,7 +238,7 @@
                 gl.glTexParameterf(GL11.GL_TEXTURE_2D,
                         GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
 
-                if (width == getTextureWidth() && height == getTextureHeight()) {
+                if (bWidth == texWidth && bHeight == texHeight) {
                     GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
                 } else {
                     int format = GLUtils.getInternalFormat(bitmap);
@@ -231,23 +246,35 @@
                     Config config = bitmap.getConfig();
 
                     gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
-                            getTextureWidth(), getTextureHeight(),
-                            0, format, type, null);
-                    GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, bitmap,
-                            format, type);
+                            texWidth, texHeight, 0, format, type, null);
+                    GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                            mBorder, mBorder, bitmap, format, type);
 
-                    if (width != getTextureWidth()) {
-                        Bitmap line = getBorderLine(true, config, getTextureHeight());
-                        GLUtils.texSubImage2D(
-                                GL11.GL_TEXTURE_2D, 0, width, 0, line, format, type);
+                    if (mBorder > 0) {
+                        // Left border
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                                0, 0, line, format, type);
+
+                        // Top border
+                        line = getBorderLine(false, config, texWidth);
+                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                                0, 0, line, format, type);
                     }
 
-                    if (height != getTextureHeight()) {
-                        Bitmap line = getBorderLine(false, config, getTextureWidth());
-                        GLUtils.texSubImage2D(
-                                GL11.GL_TEXTURE_2D, 0, 0, height, line, format, type);
+                    // Right border
+                    if (mBorder + bWidth < texWidth) {
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                                mBorder + bWidth, 0, line, format, type);
                     }
 
+                    // Bottom border
+                    if (mBorder + bHeight < texHeight) {
+                        Bitmap line = getBorderLine(false, config, texWidth);
+                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                                0, mBorder + bHeight, line, format, type);
+                    }
                 }
             } finally {
                 freeBitmap();