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