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.content.Context;
20import android.opengl.GLSurfaceView;
21import android.opengl.GLSurfaceView.Renderer;
22import android.util.AttributeSet;
23import android.view.Choreographer;
24import android.view.Choreographer.FrameCallback;
25import android.widget.FrameLayout;
26
27import com.android.gallery3d.glrenderer.BasicTexture;
28import com.android.gallery3d.glrenderer.GLES20Canvas;
29import com.android.photos.views.TiledImageRenderer.TileSource;
30
31import javax.microedition.khronos.egl.EGLConfig;
32import javax.microedition.khronos.opengles.GL10;
33
34/**
35 * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}.
36 */
37public class TiledImageView extends FrameLayout {
38
39    private GLSurfaceView mGLSurfaceView;
40    private boolean mInvalPending = false;
41    private FrameCallback mFrameCallback;
42
43    protected static class ImageRendererWrapper {
44        // Guarded by locks
45        public float scale;
46        public int centerX, centerY;
47        public int rotation;
48        public TileSource source;
49        Runnable isReadyCallback;
50
51        // GL thread only
52        TiledImageRenderer image;
53    }
54
55    private float[] mValues = new float[9];
56
57    // -------------------------
58    // Guarded by mLock
59    // -------------------------
60    protected Object mLock = new Object();
61    protected ImageRendererWrapper mRenderer;
62
63    public TiledImageView(Context context) {
64        this(context, null);
65    }
66
67    public TiledImageView(Context context, AttributeSet attrs) {
68        super(context, attrs);
69        mRenderer = new ImageRendererWrapper();
70        mRenderer.image = new TiledImageRenderer(this);
71        mGLSurfaceView = new GLSurfaceView(context);
72        mGLSurfaceView.setEGLContextClientVersion(2);
73        mGLSurfaceView.setRenderer(new TileRenderer());
74        mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
75        addView(mGLSurfaceView, new LayoutParams(
76                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
77    }
78
79    @Override
80    public void setVisibility(int visibility) {
81        super.setVisibility(visibility);
82        // need to update inner view's visibility because it seems like we're causing it to draw
83        // from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
84        mGLSurfaceView.setVisibility(visibility);
85    }
86
87    public void destroy() {
88        mGLSurfaceView.queueEvent(mFreeTextures);
89    }
90
91    private Runnable mFreeTextures = new Runnable() {
92
93        @Override
94        public void run() {
95            mRenderer.image.freeTextures();
96        }
97    };
98
99    public void onPause() {
100        mGLSurfaceView.onPause();
101    }
102
103    public void onResume() {
104        mGLSurfaceView.onResume();
105    }
106
107    public void setTileSource(TileSource source, Runnable isReadyCallback) {
108        synchronized (mLock) {
109            mRenderer.source = source;
110            mRenderer.isReadyCallback = isReadyCallback;
111            mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
112            mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
113            mRenderer.rotation = source != null ? source.getRotation() : 0;
114            mRenderer.scale = 0;
115            updateScaleIfNecessaryLocked(mRenderer);
116        }
117        invalidate();
118    }
119
120    public TileSource getTileSource() {
121        return mRenderer.source;
122    }
123
124    @Override
125    protected void onLayout(boolean changed, int left, int top, int right,
126            int bottom) {
127        super.onLayout(changed, left, top, right, bottom);
128        synchronized (mLock) {
129            updateScaleIfNecessaryLocked(mRenderer);
130        }
131    }
132
133    private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
134        if (renderer == null || renderer.source == null
135                || renderer.scale > 0 || getWidth() == 0) {
136            return;
137        }
138        renderer.scale = Math.min(
139                (float) getWidth() / (float) renderer.source.getImageWidth(),
140                (float) getHeight() / (float) renderer.source.getImageHeight());
141    }
142
143    @Override
144    public void invalidate() {
145        invalOnVsync();
146    }
147
148    private void invalOnVsync() {
149        if (!mInvalPending) {
150            mInvalPending = true;
151            if (mFrameCallback == null) {
152                mFrameCallback = new FrameCallback() {
153                    @Override
154                    public void doFrame(long frameTimeNanos) {
155                        mInvalPending = false;
156                        mGLSurfaceView.requestRender();
157                    }
158                };
159            }
160            Choreographer.getInstance().postFrameCallback(mFrameCallback);
161        }
162    }
163
164    private class TileRenderer implements Renderer {
165
166        private GLES20Canvas mCanvas;
167
168        @Override
169        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
170            mCanvas = new GLES20Canvas();
171            BasicTexture.invalidateAllTextures();
172            mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
173        }
174
175        @Override
176        public void onSurfaceChanged(GL10 gl, int width, int height) {
177            mCanvas.setSize(width, height);
178            mRenderer.image.setViewSize(width, height);
179        }
180
181        @Override
182        public void onDrawFrame(GL10 gl) {
183            mCanvas.clearBuffer();
184            Runnable readyCallback;
185            synchronized (mLock) {
186                readyCallback = mRenderer.isReadyCallback;
187                mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
188                mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
189                        mRenderer.scale);
190            }
191            boolean complete = mRenderer.image.draw(mCanvas);
192            if (complete && readyCallback != null) {
193                synchronized (mLock) {
194                    // Make sure we don't trample on a newly set callback/source
195                    // if it changed while we were rendering
196                    if (mRenderer.isReadyCallback == readyCallback) {
197                        mRenderer.isReadyCallback = null;
198                    }
199                }
200                if (readyCallback != null) {
201                    post(readyCallback);
202                }
203            }
204        }
205
206    }
207}
208