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