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 
destroy()115     public void destroy() {
116         if (!IS_SUPPORTED) {
117             return;
118         }
119         if (USE_TEXTURE_VIEW) {
120             mTextureView.destroy();
121         } else {
122             mGLSurfaceView.queueEvent(mFreeTextures);
123         }
124     }
125 
126     private Runnable mFreeTextures = new Runnable() {
127 
128         @Override
129         public void run() {
130             mRenderer.image.freeTextures();
131         }
132     };
133 
onPause()134     public void onPause() {
135         if (!IS_SUPPORTED) {
136             return;
137         }
138         if (!USE_TEXTURE_VIEW) {
139             mGLSurfaceView.onPause();
140         }
141     }
142 
onResume()143     public void onResume() {
144         if (!IS_SUPPORTED) {
145             return;
146         }
147         if (!USE_TEXTURE_VIEW) {
148             mGLSurfaceView.onResume();
149         }
150     }
151 
setTileSource(TileSource source, Runnable isReadyCallback)152     public void setTileSource(TileSource source, Runnable isReadyCallback) {
153         if (!IS_SUPPORTED) {
154             return;
155         }
156         synchronized (mLock) {
157             mRenderer.source = source;
158             mRenderer.isReadyCallback = isReadyCallback;
159             mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
160             mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
161             mRenderer.rotation = source != null ? source.getRotation() : 0;
162             mRenderer.scale = 0;
163             updateScaleIfNecessaryLocked(mRenderer);
164         }
165         invalidate();
166     }
167 
168     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)169     protected void onLayout(boolean changed, int left, int top, int right,
170             int bottom) {
171         super.onLayout(changed, left, top, right, bottom);
172         if (!IS_SUPPORTED) {
173             return;
174         }
175         synchronized (mLock) {
176             updateScaleIfNecessaryLocked(mRenderer);
177         }
178     }
179 
updateScaleIfNecessaryLocked(ImageRendererWrapper renderer)180     private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
181         if (renderer == null || renderer.source == null
182                 || renderer.scale > 0 || getWidth() == 0) {
183             return;
184         }
185         renderer.scale = Math.min(
186                 (float) getWidth() / (float) renderer.source.getImageWidth(),
187                 (float) getHeight() / (float) renderer.source.getImageHeight());
188     }
189 
190     @Override
dispatchDraw(Canvas canvas)191     protected void dispatchDraw(Canvas canvas) {
192         if (!IS_SUPPORTED) {
193             return;
194         }
195         if (USE_TEXTURE_VIEW) {
196             mTextureView.render();
197         }
198         super.dispatchDraw(canvas);
199     }
200 
201     @SuppressLint("NewApi")
202     @Override
setTranslationX(float translationX)203     public void setTranslationX(float translationX) {
204         if (!IS_SUPPORTED) {
205             return;
206         }
207         super.setTranslationX(translationX);
208     }
209 
210     @Override
invalidate()211     public void invalidate() {
212         if (!IS_SUPPORTED) {
213             return;
214         }
215         if (USE_TEXTURE_VIEW) {
216             super.invalidate();
217             mTextureView.invalidate();
218         } else {
219             if (USE_CHOREOGRAPHER) {
220                 invalOnVsync();
221             } else {
222                 mGLSurfaceView.requestRender();
223             }
224         }
225     }
226 
227     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
invalOnVsync()228     private void invalOnVsync() {
229         if (!mInvalPending) {
230             mInvalPending = true;
231             if (mFrameCallback == null) {
232                 mFrameCallback = new FrameCallback() {
233                     @Override
234                     public void doFrame(long frameTimeNanos) {
235                         mInvalPending = false;
236                         mGLSurfaceView.requestRender();
237                     }
238                 };
239             }
240             Choreographer.getInstance().postFrameCallback(mFrameCallback);
241         }
242     }
243 
244     private RectF mTempRectF = new RectF();
positionFromMatrix(Matrix matrix)245     public void positionFromMatrix(Matrix matrix) {
246         if (!IS_SUPPORTED) {
247             return;
248         }
249         if (mRenderer.source != null) {
250             final int rotation = mRenderer.source.getRotation();
251             final boolean swap = !(rotation % 180 == 0);
252             final int width = swap ? mRenderer.source.getImageHeight()
253                     : mRenderer.source.getImageWidth();
254             final int height = swap ? mRenderer.source.getImageWidth()
255                     : mRenderer.source.getImageHeight();
256             mTempRectF.set(0, 0, width, height);
257             matrix.mapRect(mTempRectF);
258             matrix.getValues(mValues);
259             int cx = width / 2;
260             int cy = height / 2;
261             float scale = mValues[Matrix.MSCALE_X];
262             int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
263             int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
264             if (rotation == 90 || rotation == 180) {
265                 cx += (mTempRectF.left / scale) - xoffset;
266             } else {
267                 cx -= (mTempRectF.left / scale) - xoffset;
268             }
269             if (rotation == 180 || rotation == 270) {
270                 cy += (mTempRectF.top / scale) - yoffset;
271             } else {
272                 cy -= (mTempRectF.top / scale) - yoffset;
273             }
274             mRenderer.scale = scale;
275             mRenderer.centerX = swap ? cy : cx;
276             mRenderer.centerY = swap ? cx : cy;
277             invalidate();
278         }
279     }
280 
281     private class TileRenderer implements Renderer {
282 
283         private GLES20Canvas mCanvas;
284 
285         @Override
onSurfaceCreated(GL10 gl, EGLConfig config)286         public void onSurfaceCreated(GL10 gl, EGLConfig config) {
287             mCanvas = new GLES20Canvas();
288             BasicTexture.invalidateAllTextures();
289             mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
290         }
291 
292         @Override
onSurfaceChanged(GL10 gl, int width, int height)293         public void onSurfaceChanged(GL10 gl, int width, int height) {
294             mCanvas.setSize(width, height);
295             mRenderer.image.setViewSize(width, height);
296         }
297 
298         @Override
onDrawFrame(GL10 gl)299         public void onDrawFrame(GL10 gl) {
300             mCanvas.clearBuffer();
301             Runnable readyCallback;
302             synchronized (mLock) {
303                 readyCallback = mRenderer.isReadyCallback;
304                 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
305                 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
306                         mRenderer.scale);
307             }
308             boolean complete = mRenderer.image.draw(mCanvas);
309             if (complete && readyCallback != null) {
310                 synchronized (mLock) {
311                     // Make sure we don't trample on a newly set callback/source
312                     // if it changed while we were rendering
313                     if (mRenderer.isReadyCallback == readyCallback) {
314                         mRenderer.isReadyCallback = null;
315                     }
316                 }
317                 if (readyCallback != null) {
318                     post(readyCallback);
319                 }
320             }
321         }
322 
323     }
324 
325     @SuppressWarnings("unused")
326     private static class ColoredTiles implements TileSource {
327         private static final int[] COLORS = new int[] {
328             Color.RED,
329             Color.BLUE,
330             Color.YELLOW,
331             Color.GREEN,
332             Color.CYAN,
333             Color.MAGENTA,
334             Color.WHITE,
335         };
336 
337         private Paint mPaint = new Paint();
338         private Canvas mCanvas = new Canvas();
339 
340         @Override
getTileSize()341         public int getTileSize() {
342             return 256;
343         }
344 
345         @Override
getImageWidth()346         public int getImageWidth() {
347             return 16384;
348         }
349 
350         @Override
getImageHeight()351         public int getImageHeight() {
352             return 8192;
353         }
354 
355         @Override
getRotation()356         public int getRotation() {
357             return 0;
358         }
359 
360         @Override
getTile(int level, int x, int y, Bitmap bitmap)361         public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
362             int tileSize = getTileSize();
363             if (bitmap == null) {
364                 bitmap = Bitmap.createBitmap(tileSize, tileSize,
365                         Bitmap.Config.ARGB_8888);
366             }
367             mCanvas.setBitmap(bitmap);
368             mCanvas.drawColor(COLORS[level]);
369             mPaint.setColor(Color.BLACK);
370             mPaint.setTextSize(20);
371             mPaint.setTextAlign(Align.CENTER);
372             mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
373             tileSize <<= level;
374             x /= tileSize;
375             y /= tileSize;
376             mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
377             mCanvas.setBitmap(null);
378             return bitmap;
379         }
380 
381         @Override
getPreview()382         public BasicTexture getPreview() {
383             return null;
384         }
385     }
386 }
387