Home | History | Annotate | Download | only in views
      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.opengl.GLSurfaceView;
     21 import android.opengl.GLSurfaceView.Renderer;
     22 import android.util.AttributeSet;
     23 import android.view.Choreographer;
     24 import android.view.Choreographer.FrameCallback;
     25 import android.widget.FrameLayout;
     26 
     27 import com.android.gallery3d.glrenderer.BasicTexture;
     28 import com.android.gallery3d.glrenderer.GLES20Canvas;
     29 import com.android.photos.views.TiledImageRenderer.TileSource;
     30 
     31 import javax.microedition.khronos.egl.EGLConfig;
     32 import javax.microedition.khronos.opengles.GL10;
     33 
     34 /**
     35  * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}.
     36  */
     37 public 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