Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.ui;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Bitmap.Config;
     21 import android.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Paint;
     24 import android.graphics.PorterDuff.Mode;
     25 import android.graphics.PorterDuffXfermode;
     26 import android.graphics.RectF;
     27 import android.os.SystemClock;
     28 
     29 import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
     30 
     31 import java.util.ArrayDeque;
     32 import java.util.ArrayList;
     33 
     34 // This class is similar to BitmapTexture, except the bitmap is
     35 // split into tiles. By doing so, we may increase the time required to
     36 // upload the whole bitmap but we reduce the time of uploading each tile
     37 // so it make the animation more smooth and prevents jank.
     38 public class TiledTexture implements Texture {
     39     private static final int CONTENT_SIZE = 254;
     40     private static final int BORDER_SIZE = 1;
     41     private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
     42     private static final int INIT_CAPACITY = 8;
     43 
     44     // We are targeting at 60fps, so we have 16ms for each frame.
     45     // In this 16ms, we use about 4~8 ms to upload tiles.
     46     private static final long UPLOAD_TILE_LIMIT = 4; // ms
     47 
     48     private static Tile sFreeTileHead = null;
     49     private static final Object sFreeTileLock = new Object();
     50 
     51     private static Bitmap sUploadBitmap;
     52     private static Canvas sCanvas;
     53     private static Paint sBitmapPaint;
     54     private static Paint sPaint;
     55 
     56     private int mUploadIndex = 0;
     57 
     58     private final Tile[] mTiles;
     59     private final int mWidth;
     60     private final int mHeight;
     61     private final RectF mSrcRect = new RectF();
     62     private final RectF mDestRect = new RectF();
     63 
     64     public static class Uploader implements OnGLIdleListener {
     65         private final ArrayDeque<TiledTexture> mTextures =
     66                 new ArrayDeque<TiledTexture>(INIT_CAPACITY);
     67 
     68         private final GLRoot mGlRoot;
     69         private boolean mIsQueued = false;
     70 
     71         public Uploader(GLRoot glRoot) {
     72             mGlRoot = glRoot;
     73         }
     74 
     75         public synchronized void clear() {
     76             mTextures.clear();
     77         }
     78 
     79         public synchronized void addTexture(TiledTexture t) {
     80             if (t.isReady()) return;
     81             mTextures.addLast(t);
     82 
     83             if (mIsQueued) return;
     84             mIsQueued = true;
     85             mGlRoot.addOnGLIdleListener(this);
     86         }
     87 
     88         @Override
     89         public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
     90             ArrayDeque<TiledTexture> deque = mTextures;
     91             synchronized (this) {
     92                 long now = SystemClock.uptimeMillis();
     93                 long dueTime = now + UPLOAD_TILE_LIMIT;
     94                 while(now < dueTime && !deque.isEmpty()) {
     95                     TiledTexture t = deque.peekFirst();
     96                     if (t.uploadNextTile(canvas)) {
     97                         deque.removeFirst();
     98                         mGlRoot.requestRender();
     99                     }
    100                     now = SystemClock.uptimeMillis();
    101                 }
    102                 mIsQueued = !mTextures.isEmpty();
    103 
    104                 // return true to keep this listener in the queue
    105                 return mIsQueued;
    106             }
    107         }
    108     }
    109 
    110     private static class Tile extends UploadedTexture {
    111         public int offsetX;
    112         public int offsetY;
    113         public Bitmap bitmap;
    114         public Tile nextFreeTile;
    115         public int contentWidth;
    116         public int contentHeight;
    117 
    118         @Override
    119         public void setSize(int width, int height) {
    120             contentWidth = width;
    121             contentHeight = height;
    122             mWidth = width + 2 * BORDER_SIZE;
    123             mHeight = height + 2 * BORDER_SIZE;
    124             mTextureWidth = TILE_SIZE;
    125             mTextureHeight = TILE_SIZE;
    126         }
    127 
    128         @Override
    129         protected Bitmap onGetBitmap() {
    130             int x = BORDER_SIZE - offsetX;
    131             int y = BORDER_SIZE - offsetY;
    132             int r = bitmap.getWidth() + x;
    133             int b = bitmap.getHeight() + y ;
    134             sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
    135             bitmap = null;
    136 
    137             // draw borders if need
    138             if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
    139             if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
    140             if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
    141             if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
    142 
    143             return sUploadBitmap;
    144         }
    145 
    146         @Override
    147         protected void onFreeBitmap(Bitmap bitmap) {
    148             // do nothing
    149         }
    150     }
    151 
    152     private static void freeTile(Tile tile) {
    153         tile.invalidateContent();
    154         tile.bitmap = null;
    155         synchronized (sFreeTileLock) {
    156             tile.nextFreeTile = sFreeTileHead;
    157             sFreeTileHead = tile;
    158         }
    159     }
    160 
    161     private static Tile obtainTile() {
    162         synchronized (sFreeTileLock) {
    163             Tile result = sFreeTileHead;
    164             if (result == null) return new Tile();
    165             sFreeTileHead = result.nextFreeTile;
    166             result.nextFreeTile = null;
    167             return result;
    168         }
    169     }
    170 
    171     private boolean uploadNextTile(GLCanvas canvas) {
    172         if (mUploadIndex == mTiles.length) return true;
    173 
    174         Tile next = mTiles[mUploadIndex++];
    175 
    176         // Make sure tile has not already been recycled by the time
    177         // this is called (race condition in onGLIdle)
    178         if (next.bitmap != null) {
    179             boolean hasBeenLoad = next.isLoaded();
    180             next.updateContent(canvas);
    181 
    182             // It will take some time for a texture to be drawn for the first
    183             // time. When scrolling, we need to draw several tiles on the screen
    184             // at the same time. It may cause a UI jank even these textures has
    185             // been uploaded.
    186             if (!hasBeenLoad) next.draw(canvas, 0, 0);
    187         }
    188         return mUploadIndex == mTiles.length;
    189     }
    190 
    191     public TiledTexture(Bitmap bitmap) {
    192         mWidth = bitmap.getWidth();
    193         mHeight = bitmap.getHeight();
    194         ArrayList<Tile> list = new ArrayList<Tile>();
    195 
    196         for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
    197             for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
    198                 Tile tile = obtainTile();
    199                 tile.offsetX = x;
    200                 tile.offsetY = y;
    201                 tile.bitmap = bitmap;
    202                 tile.setSize(
    203                         Math.min(CONTENT_SIZE, mWidth - x),
    204                         Math.min(CONTENT_SIZE, mHeight - y));
    205                 list.add(tile);
    206             }
    207         }
    208         mTiles = list.toArray(new Tile[list.size()]);
    209     }
    210 
    211     public boolean isReady() {
    212         return mUploadIndex == mTiles.length;
    213     }
    214 
    215     public void recycle() {
    216         for (int i = 0, n = mTiles.length; i < n; ++i) {
    217             freeTile(mTiles[i]);
    218         }
    219     }
    220 
    221     public static void freeResources() {
    222         sUploadBitmap = null;
    223         sCanvas = null;
    224         sBitmapPaint = null;
    225         sPaint = null;
    226     }
    227 
    228     public static void prepareResources() {
    229         sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
    230         sCanvas = new Canvas(sUploadBitmap);
    231         sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    232         sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    233         sPaint = new Paint();
    234         sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    235         sPaint.setColor(Color.TRANSPARENT);
    236     }
    237 
    238     // We want to draw the "source" on the "target".
    239     // This method is to find the "output" rectangle which is
    240     // the corresponding area of the "src".
    241     //                                   (x,y)  target
    242     // (x0,y0)  source                     +---------------+
    243     //    +----------+                     |               |
    244     //    | src      |                     | output        |
    245     //    | +--+     |    linear map       | +----+        |
    246     //    | +--+     |    ---------->      | |    |        |
    247     //    |          | by (scaleX, scaleY) | +----+        |
    248     //    +----------+                     |               |
    249     //      Texture                        +---------------+
    250     //                                          Canvas
    251     private static void mapRect(RectF output,
    252             RectF src, float x0, float y0, float x, float y, float scaleX,
    253             float scaleY) {
    254         output.set(x + (src.left - x0) * scaleX,
    255                 y + (src.top - y0) * scaleY,
    256                 x + (src.right - x0) * scaleX,
    257                 y + (src.bottom - y0) * scaleY);
    258     }
    259 
    260     // Draws a mixed color of this texture and a specified color onto the
    261     // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
    262     public void drawMixed(GLCanvas canvas, int color, float ratio,
    263             int x, int y, int width, int height) {
    264         RectF src = mSrcRect;
    265         RectF dest = mDestRect;
    266         float scaleX = (float) width / mWidth ;
    267         float scaleY = (float) height / mHeight;
    268         for (int i = 0, n = mTiles.length; i < n; ++i) {
    269             Tile t = mTiles[i];
    270             src.set(0, 0, t.contentWidth, t.contentHeight);
    271             src.offset(t.offsetX, t.offsetY);
    272             mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    273             src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    274             canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
    275         }
    276     }
    277 
    278     // Draws the texture on to the specified rectangle.
    279     @Override
    280     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
    281         RectF src = mSrcRect;
    282         RectF dest = mDestRect;
    283         float scaleX = (float) width / mWidth ;
    284         float scaleY = (float) height / mHeight;
    285         for (int i = 0, n = mTiles.length; i < n; ++i) {
    286             Tile t = mTiles[i];
    287             src.set(0, 0, t.contentWidth, t.contentHeight);
    288             src.offset(t.offsetX, t.offsetY);
    289             mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    290             src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    291             canvas.drawTexture(t, mSrcRect, mDestRect);
    292         }
    293     }
    294 
    295     // Draws a sub region of this texture on to the specified rectangle.
    296     public void draw(GLCanvas canvas, RectF source, RectF target) {
    297         RectF src = mSrcRect;
    298         RectF dest = mDestRect;
    299         float x0 = source.left;
    300         float y0 = source.top;
    301         float x = target.left;
    302         float y = target.top;
    303         float scaleX = target.width() / source.width();
    304         float scaleY = target.height() / source.height();
    305 
    306         for (int i = 0, n = mTiles.length; i < n; ++i) {
    307             Tile t = mTiles[i];
    308             src.set(0, 0, t.contentWidth, t.contentHeight);
    309             src.offset(t.offsetX, t.offsetY);
    310             if (!src.intersect(source)) continue;
    311             mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
    312             src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    313             canvas.drawTexture(t, src, dest);
    314         }
    315     }
    316 
    317     @Override
    318     public int getWidth() {
    319         return mWidth;
    320     }
    321 
    322     @Override
    323     public int getHeight() {
    324         return mHeight;
    325     }
    326 
    327     @Override
    328     public void draw(GLCanvas canvas, int x, int y) {
    329         draw(canvas, x, y, mWidth, mHeight);
    330     }
    331 
    332     @Override
    333     public boolean isOpaque() {
    334         return false;
    335     }
    336 }
    337