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 TiledImageView(Context context)63 public TiledImageView(Context context) { 64 this(context, null); 65 } 66 TiledImageView(Context context, AttributeSet attrs)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 setVisibility(int visibility)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 destroy()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 onPause()99 public void onPause() { 100 mGLSurfaceView.onPause(); 101 } 102 onResume()103 public void onResume() { 104 mGLSurfaceView.onResume(); 105 } 106 setTileSource(TileSource source, Runnable isReadyCallback)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 getTileSource()120 public TileSource getTileSource() { 121 return mRenderer.source; 122 } 123 124 @Override onLayout(boolean changed, int left, int top, int right, int bottom)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 updateScaleIfNecessaryLocked(ImageRendererWrapper renderer)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 invalidate()144 public void invalidate() { 145 invalOnVsync(); 146 } 147 invalOnVsync()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 onSurfaceCreated(GL10 gl, EGLConfig config)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 onSurfaceChanged(GL10 gl, int width, int height)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 onDrawFrame(GL10 gl)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