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
17package com.android.photos.views;
18
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
21import android.content.Context;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Matrix;
26import android.graphics.Paint;
27import android.graphics.Paint.Align;
28import android.graphics.RectF;
29import android.opengl.GLSurfaceView;
30import android.opengl.GLSurfaceView.Renderer;
31import android.os.Build;
32import android.util.AttributeSet;
33import android.view.Choreographer;
34import android.view.Choreographer.FrameCallback;
35import android.view.View;
36import android.widget.FrameLayout;
37
38import com.android.gallery3d.glrenderer.BasicTexture;
39import com.android.gallery3d.glrenderer.GLES20Canvas;
40import com.android.photos.views.TiledImageRenderer.TileSource;
41
42import javax.microedition.khronos.egl.EGLConfig;
43import javax.microedition.khronos.opengles.GL10;
44
45/**
46 * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
47 * or {@link BlockingGLTextureView}.
48 */
49public 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
82    public static boolean isTilingSupported() {
83        return IS_SUPPORTED;
84    }
85
86    public TiledImageView(Context context) {
87        this(context, null);
88    }
89
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    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
134    public void onPause() {
135        if (!IS_SUPPORTED) {
136            return;
137        }
138        if (!USE_TEXTURE_VIEW) {
139            mGLSurfaceView.onPause();
140        }
141    }
142
143    public void onResume() {
144        if (!IS_SUPPORTED) {
145            return;
146        }
147        if (!USE_TEXTURE_VIEW) {
148            mGLSurfaceView.onResume();
149        }
150    }
151
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
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
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
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
203    public void setTranslationX(float translationX) {
204        if (!IS_SUPPORTED) {
205            return;
206        }
207        super.setTranslationX(translationX);
208    }
209
210    @Override
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)
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();
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
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
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
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
341        public int getTileSize() {
342            return 256;
343        }
344
345        @Override
346        public int getImageWidth() {
347            return 16384;
348        }
349
350        @Override
351        public int getImageHeight() {
352            return 8192;
353        }
354
355        @Override
356        public int getRotation() {
357            return 0;
358        }
359
360        @Override
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
382        public BasicTexture getPreview() {
383            return null;
384        }
385    }
386}
387