• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.photos.views;
18 
19 import android.annotation.SuppressLint;
20 import android.annotation.TargetApi;
21 import android.content.Context;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Matrix;
26 import android.graphics.Paint;
27 import android.graphics.Paint.Align;
28 import android.graphics.RectF;
29 import android.opengl.GLSurfaceView;
30 import android.opengl.GLSurfaceView.Renderer;
31 import android.os.Build;
32 import android.util.AttributeSet;
33 import android.view.Choreographer;
34 import android.view.Choreographer.FrameCallback;
35 import android.view.View;
36 import android.widget.FrameLayout;
37 
38 import com.android.gallery3d.glrenderer.BasicTexture;
39 import com.android.gallery3d.glrenderer.GLES20Canvas;
40 import com.android.photos.views.TiledImageRenderer.TileSource;
41 
42 import javax.microedition.khronos.egl.EGLConfig;
43 import javax.microedition.khronos.opengles.GL10;
44 
45 /**
46  * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
47  * or {@link BlockingGLTextureView}.
48  */
49 public class TiledImageView extends FrameLayout {
50 
51     private static final boolean USE_TEXTURE_VIEW = false;
52     private static final boolean IS_SUPPORTED =
53             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
54     private static final boolean USE_CHOREOGRAPHER =
55             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
56 
57     private BlockingGLTextureView mTextureView;
58     private GLSurfaceView mGLSurfaceView;
59     private boolean mInvalPending = false;
60     private FrameCallback mFrameCallback;
61 
62     protected static class ImageRendererWrapper {
63         // Guarded by locks
64         public float scale;
65         public int centerX, centerY;
66         public int rotation;
67         public TileSource source;
68         Runnable isReadyCallback;
69 
70         // GL thread only
71         TiledImageRenderer image;
72     }
73 
74     private float[] mValues = new float[9];
75 
76     // -------------------------
77     // Guarded by mLock
78     // -------------------------
79     protected Object mLock = new Object();
80     protected ImageRendererWrapper mRenderer;
81 
isTilingSupported()82     public static boolean isTilingSupported() {
83         return IS_SUPPORTED;
84     }
85 
TiledImageView(Context context)86     public TiledImageView(Context context) {
87         this(context, null);
88     }
89 
TiledImageView(Context context, AttributeSet attrs)90     public TiledImageView(Context context, AttributeSet attrs) {
91         super(context, attrs);
92         if (!IS_SUPPORTED) {
93             return;
94         }
95 
96         mRenderer = new ImageRendererWrapper();
97         mRenderer.image = new TiledImageRenderer(this);
98         View view;
99         if (USE_TEXTURE_VIEW) {
100             mTextureView = new BlockingGLTextureView(context);
101             mTextureView.setRenderer(new TileRenderer());
102             view = mTextureView;
103         } else {
104             mGLSurfaceView = new GLSurfaceView(context);
105             mGLSurfaceView.setEGLContextClientVersion(2);
106             mGLSurfaceView.setRenderer(new TileRenderer());
107             mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
108             view = mGLSurfaceView;
109         }
110         addView(view, new LayoutParams(
111                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
112         //setTileSource(new ColoredTiles());
113     }
114 
115     @Override
setVisibility(int visibility)116     public void setVisibility(int visibility) {
117         super.setVisibility(visibility);
118         // need to update inner view's visibility because it seems like we're causing it to draw
119         // from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
120         if (USE_TEXTURE_VIEW) {
121             mTextureView.setVisibility(visibility);
122         } else {
123             mGLSurfaceView.setVisibility(visibility);
124         }
125     }
126 
destroy()127     public void destroy() {
128         if (!IS_SUPPORTED) {
129             return;
130         }
131         if (USE_TEXTURE_VIEW) {
132             mTextureView.destroy();
133         } else {
134             mGLSurfaceView.queueEvent(mFreeTextures);
135         }
136     }
137 
138     private Runnable mFreeTextures = new Runnable() {
139 
140         @Override
141         public void run() {
142             mRenderer.image.freeTextures();
143         }
144     };
145 
onPause()146     public void onPause() {
147         if (!IS_SUPPORTED) {
148             return;
149         }
150         if (!USE_TEXTURE_VIEW) {
151             mGLSurfaceView.onPause();
152         }
153     }
154 
onResume()155     public void onResume() {
156         if (!IS_SUPPORTED) {
157             return;
158         }
159         if (!USE_TEXTURE_VIEW) {
160             mGLSurfaceView.onResume();
161         }
162     }
163 
setTileSource(TileSource source, Runnable isReadyCallback)164     public void setTileSource(TileSource source, Runnable isReadyCallback) {
165         if (!IS_SUPPORTED) {
166             return;
167         }
168         synchronized (mLock) {
169             mRenderer.source = source;
170             mRenderer.isReadyCallback = isReadyCallback;
171             mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
172             mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
173             mRenderer.rotation = source != null ? source.getRotation() : 0;
174             mRenderer.scale = 0;
175             updateScaleIfNecessaryLocked(mRenderer);
176         }
177         invalidate();
178     }
179 
180     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)181     protected void onLayout(boolean changed, int left, int top, int right,
182             int bottom) {
183         super.onLayout(changed, left, top, right, bottom);
184         if (!IS_SUPPORTED) {
185             return;
186         }
187         synchronized (mLock) {
188             updateScaleIfNecessaryLocked(mRenderer);
189         }
190     }
191 
updateScaleIfNecessaryLocked(ImageRendererWrapper renderer)192     private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
193         if (renderer == null || renderer.source == null
194                 || renderer.scale > 0 || getWidth() == 0) {
195             return;
196         }
197         renderer.scale = Math.min(
198                 (float) getWidth() / (float) renderer.source.getImageWidth(),
199                 (float) getHeight() / (float) renderer.source.getImageHeight());
200     }
201 
202     @Override
dispatchDraw(Canvas canvas)203     protected void dispatchDraw(Canvas canvas) {
204         if (!IS_SUPPORTED) {
205             return;
206         }
207         if (USE_TEXTURE_VIEW) {
208             mTextureView.render();
209         }
210         super.dispatchDraw(canvas);
211     }
212 
213     @SuppressLint("NewApi")
214     @Override
setTranslationX(float translationX)215     public void setTranslationX(float translationX) {
216         if (!IS_SUPPORTED) {
217             return;
218         }
219         super.setTranslationX(translationX);
220     }
221 
222     @Override
invalidate()223     public void invalidate() {
224         if (!IS_SUPPORTED) {
225             return;
226         }
227         if (USE_TEXTURE_VIEW) {
228             super.invalidate();
229             mTextureView.invalidate();
230         } else {
231             if (USE_CHOREOGRAPHER) {
232                 invalOnVsync();
233             } else {
234                 mGLSurfaceView.requestRender();
235             }
236         }
237     }
238 
239     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
invalOnVsync()240     private void invalOnVsync() {
241         if (!mInvalPending) {
242             mInvalPending = true;
243             if (mFrameCallback == null) {
244                 mFrameCallback = new FrameCallback() {
245                     @Override
246                     public void doFrame(long frameTimeNanos) {
247                         mInvalPending = false;
248                         mGLSurfaceView.requestRender();
249                     }
250                 };
251             }
252             Choreographer.getInstance().postFrameCallback(mFrameCallback);
253         }
254     }
255 
256     private RectF mTempRectF = new RectF();
positionFromMatrix(Matrix matrix)257     public void positionFromMatrix(Matrix matrix) {
258         if (!IS_SUPPORTED) {
259             return;
260         }
261         if (mRenderer.source != null) {
262             final int rotation = mRenderer.source.getRotation();
263             final boolean swap = !(rotation % 180 == 0);
264             final int width = swap ? mRenderer.source.getImageHeight()
265                     : mRenderer.source.getImageWidth();
266             final int height = swap ? mRenderer.source.getImageWidth()
267                     : mRenderer.source.getImageHeight();
268             mTempRectF.set(0, 0, width, height);
269             matrix.mapRect(mTempRectF);
270             matrix.getValues(mValues);
271             int cx = width / 2;
272             int cy = height / 2;
273             float scale = mValues[Matrix.MSCALE_X];
274             int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
275             int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
276             if (rotation == 90 || rotation == 180) {
277                 cx += (mTempRectF.left / scale) - xoffset;
278             } else {
279                 cx -= (mTempRectF.left / scale) - xoffset;
280             }
281             if (rotation == 180 || rotation == 270) {
282                 cy += (mTempRectF.top / scale) - yoffset;
283             } else {
284                 cy -= (mTempRectF.top / scale) - yoffset;
285             }
286             mRenderer.scale = scale;
287             mRenderer.centerX = swap ? cy : cx;
288             mRenderer.centerY = swap ? cx : cy;
289             invalidate();
290         }
291     }
292 
293     private class TileRenderer implements Renderer {
294 
295         private GLES20Canvas mCanvas;
296 
297         @Override
onSurfaceCreated(GL10 gl, EGLConfig config)298         public void onSurfaceCreated(GL10 gl, EGLConfig config) {
299             mCanvas = new GLES20Canvas();
300             BasicTexture.invalidateAllTextures();
301             mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
302         }
303 
304         @Override
onSurfaceChanged(GL10 gl, int width, int height)305         public void onSurfaceChanged(GL10 gl, int width, int height) {
306             mCanvas.setSize(width, height);
307             mRenderer.image.setViewSize(width, height);
308         }
309 
310         @Override
onDrawFrame(GL10 gl)311         public void onDrawFrame(GL10 gl) {
312             mCanvas.clearBuffer();
313             Runnable readyCallback;
314             synchronized (mLock) {
315                 readyCallback = mRenderer.isReadyCallback;
316                 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
317                 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
318                         mRenderer.scale);
319             }
320             boolean complete = mRenderer.image.draw(mCanvas);
321             if (complete && readyCallback != null) {
322                 synchronized (mLock) {
323                     // Make sure we don't trample on a newly set callback/source
324                     // if it changed while we were rendering
325                     if (mRenderer.isReadyCallback == readyCallback) {
326                         mRenderer.isReadyCallback = null;
327                     }
328                 }
329                 if (readyCallback != null) {
330                     post(readyCallback);
331                 }
332             }
333         }
334 
335     }
336 
337     @SuppressWarnings("unused")
338     private static class ColoredTiles implements TileSource {
339         private static final int[] COLORS = new int[] {
340             Color.RED,
341             Color.BLUE,
342             Color.YELLOW,
343             Color.GREEN,
344             Color.CYAN,
345             Color.MAGENTA,
346             Color.WHITE,
347         };
348 
349         private Paint mPaint = new Paint();
350         private Canvas mCanvas = new Canvas();
351 
352         @Override
getTileSize()353         public int getTileSize() {
354             return 256;
355         }
356 
357         @Override
getImageWidth()358         public int getImageWidth() {
359             return 16384;
360         }
361 
362         @Override
getImageHeight()363         public int getImageHeight() {
364             return 8192;
365         }
366 
367         @Override
getRotation()368         public int getRotation() {
369             return 0;
370         }
371 
372         @Override
getTile(int level, int x, int y, Bitmap bitmap)373         public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
374             int tileSize = getTileSize();
375             if (bitmap == null) {
376                 bitmap = Bitmap.createBitmap(tileSize, tileSize,
377                         Bitmap.Config.ARGB_8888);
378             }
379             mCanvas.setBitmap(bitmap);
380             mCanvas.drawColor(COLORS[level]);
381             mPaint.setColor(Color.BLACK);
382             mPaint.setTextSize(20);
383             mPaint.setTextAlign(Align.CENTER);
384             mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
385             tileSize <<= level;
386             x /= tileSize;
387             y /= tileSize;
388             mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
389             mCanvas.setBitmap(null);
390             return bitmap;
391         }
392 
393         @Override
getPreview()394         public BasicTexture getPreview() {
395             return null;
396         }
397     }
398 }
399