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