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.annotation.SuppressLint; 20 import android.annotation.TargetApi; 21 import android.content.Context; 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Matrix; 26 import android.graphics.Paint; 27 import android.graphics.Paint.Align; 28 import android.graphics.RectF; 29 import android.opengl.GLSurfaceView; 30 import android.opengl.GLSurfaceView.Renderer; 31 import android.os.Build; 32 import android.util.AttributeSet; 33 import android.view.Choreographer; 34 import android.view.Choreographer.FrameCallback; 35 import android.view.View; 36 import android.widget.FrameLayout; 37 38 import com.android.gallery3d.glrenderer.BasicTexture; 39 import com.android.gallery3d.glrenderer.GLES20Canvas; 40 import com.android.photos.views.TiledImageRenderer.TileSource; 41 42 import javax.microedition.khronos.egl.EGLConfig; 43 import javax.microedition.khronos.opengles.GL10; 44 45 /** 46 * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView} 47 * or {@link BlockingGLTextureView}. 48 */ 49 public 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 @Override 116 public void setVisibility(int visibility) { 117 super.setVisibility(visibility); 118 // need to update inner view's visibility because it seems like we're causing it to draw 119 // from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible. 120 if (USE_TEXTURE_VIEW) { 121 mTextureView.setVisibility(visibility); 122 } else { 123 mGLSurfaceView.setVisibility(visibility); 124 } 125 } 126 127 public void destroy() { 128 if (!IS_SUPPORTED) { 129 return; 130 } 131 if (USE_TEXTURE_VIEW) { 132 mTextureView.destroy(); 133 } else { 134 mGLSurfaceView.queueEvent(mFreeTextures); 135 } 136 } 137 138 private Runnable mFreeTextures = new Runnable() { 139 140 @Override 141 public void run() { 142 mRenderer.image.freeTextures(); 143 } 144 }; 145 146 public void onPause() { 147 if (!IS_SUPPORTED) { 148 return; 149 } 150 if (!USE_TEXTURE_VIEW) { 151 mGLSurfaceView.onPause(); 152 } 153 } 154 155 public void onResume() { 156 if (!IS_SUPPORTED) { 157 return; 158 } 159 if (!USE_TEXTURE_VIEW) { 160 mGLSurfaceView.onResume(); 161 } 162 } 163 164 public void setTileSource(TileSource source, Runnable isReadyCallback) { 165 if (!IS_SUPPORTED) { 166 return; 167 } 168 synchronized (mLock) { 169 mRenderer.source = source; 170 mRenderer.isReadyCallback = isReadyCallback; 171 mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0; 172 mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0; 173 mRenderer.rotation = source != null ? source.getRotation() : 0; 174 mRenderer.scale = 0; 175 updateScaleIfNecessaryLocked(mRenderer); 176 } 177 invalidate(); 178 } 179 180 @Override 181 protected void onLayout(boolean changed, int left, int top, int right, 182 int bottom) { 183 super.onLayout(changed, left, top, right, bottom); 184 if (!IS_SUPPORTED) { 185 return; 186 } 187 synchronized (mLock) { 188 updateScaleIfNecessaryLocked(mRenderer); 189 } 190 } 191 192 private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) { 193 if (renderer == null || renderer.source == null 194 || renderer.scale > 0 || getWidth() == 0) { 195 return; 196 } 197 renderer.scale = Math.min( 198 (float) getWidth() / (float) renderer.source.getImageWidth(), 199 (float) getHeight() / (float) renderer.source.getImageHeight()); 200 } 201 202 @Override 203 protected void dispatchDraw(Canvas canvas) { 204 if (!IS_SUPPORTED) { 205 return; 206 } 207 if (USE_TEXTURE_VIEW) { 208 mTextureView.render(); 209 } 210 super.dispatchDraw(canvas); 211 } 212 213 @SuppressLint("NewApi") 214 @Override 215 public void setTranslationX(float translationX) { 216 if (!IS_SUPPORTED) { 217 return; 218 } 219 super.setTranslationX(translationX); 220 } 221 222 @Override 223 public void invalidate() { 224 if (!IS_SUPPORTED) { 225 return; 226 } 227 if (USE_TEXTURE_VIEW) { 228 super.invalidate(); 229 mTextureView.invalidate(); 230 } else { 231 if (USE_CHOREOGRAPHER) { 232 invalOnVsync(); 233 } else { 234 mGLSurfaceView.requestRender(); 235 } 236 } 237 } 238 239 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 240 private void invalOnVsync() { 241 if (!mInvalPending) { 242 mInvalPending = true; 243 if (mFrameCallback == null) { 244 mFrameCallback = new FrameCallback() { 245 @Override 246 public void doFrame(long frameTimeNanos) { 247 mInvalPending = false; 248 mGLSurfaceView.requestRender(); 249 } 250 }; 251 } 252 Choreographer.getInstance().postFrameCallback(mFrameCallback); 253 } 254 } 255 256 private RectF mTempRectF = new RectF(); 257 public void positionFromMatrix(Matrix matrix) { 258 if (!IS_SUPPORTED) { 259 return; 260 } 261 if (mRenderer.source != null) { 262 final int rotation = mRenderer.source.getRotation(); 263 final boolean swap = !(rotation % 180 == 0); 264 final int width = swap ? mRenderer.source.getImageHeight() 265 : mRenderer.source.getImageWidth(); 266 final int height = swap ? mRenderer.source.getImageWidth() 267 : mRenderer.source.getImageHeight(); 268 mTempRectF.set(0, 0, width, height); 269 matrix.mapRect(mTempRectF); 270 matrix.getValues(mValues); 271 int cx = width / 2; 272 int cy = height / 2; 273 float scale = mValues[Matrix.MSCALE_X]; 274 int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale); 275 int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale); 276 if (rotation == 90 || rotation == 180) { 277 cx += (mTempRectF.left / scale) - xoffset; 278 } else { 279 cx -= (mTempRectF.left / scale) - xoffset; 280 } 281 if (rotation == 180 || rotation == 270) { 282 cy += (mTempRectF.top / scale) - yoffset; 283 } else { 284 cy -= (mTempRectF.top / scale) - yoffset; 285 } 286 mRenderer.scale = scale; 287 mRenderer.centerX = swap ? cy : cx; 288 mRenderer.centerY = swap ? cx : cy; 289 invalidate(); 290 } 291 } 292 293 private class TileRenderer implements Renderer { 294 295 private GLES20Canvas mCanvas; 296 297 @Override 298 public void onSurfaceCreated(GL10 gl, EGLConfig config) { 299 mCanvas = new GLES20Canvas(); 300 BasicTexture.invalidateAllTextures(); 301 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation); 302 } 303 304 @Override 305 public void onSurfaceChanged(GL10 gl, int width, int height) { 306 mCanvas.setSize(width, height); 307 mRenderer.image.setViewSize(width, height); 308 } 309 310 @Override 311 public void onDrawFrame(GL10 gl) { 312 mCanvas.clearBuffer(); 313 Runnable readyCallback; 314 synchronized (mLock) { 315 readyCallback = mRenderer.isReadyCallback; 316 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation); 317 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY, 318 mRenderer.scale); 319 } 320 boolean complete = mRenderer.image.draw(mCanvas); 321 if (complete && readyCallback != null) { 322 synchronized (mLock) { 323 // Make sure we don't trample on a newly set callback/source 324 // if it changed while we were rendering 325 if (mRenderer.isReadyCallback == readyCallback) { 326 mRenderer.isReadyCallback = null; 327 } 328 } 329 if (readyCallback != null) { 330 post(readyCallback); 331 } 332 } 333 } 334 335 } 336 337 @SuppressWarnings("unused") 338 private static class ColoredTiles implements TileSource { 339 private static final int[] COLORS = new int[] { 340 Color.RED, 341 Color.BLUE, 342 Color.YELLOW, 343 Color.GREEN, 344 Color.CYAN, 345 Color.MAGENTA, 346 Color.WHITE, 347 }; 348 349 private Paint mPaint = new Paint(); 350 private Canvas mCanvas = new Canvas(); 351 352 @Override 353 public int getTileSize() { 354 return 256; 355 } 356 357 @Override 358 public int getImageWidth() { 359 return 16384; 360 } 361 362 @Override 363 public int getImageHeight() { 364 return 8192; 365 } 366 367 @Override 368 public int getRotation() { 369 return 0; 370 } 371 372 @Override 373 public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { 374 int tileSize = getTileSize(); 375 if (bitmap == null) { 376 bitmap = Bitmap.createBitmap(tileSize, tileSize, 377 Bitmap.Config.ARGB_8888); 378 } 379 mCanvas.setBitmap(bitmap); 380 mCanvas.drawColor(COLORS[level]); 381 mPaint.setColor(Color.BLACK); 382 mPaint.setTextSize(20); 383 mPaint.setTextAlign(Align.CENTER); 384 mCanvas.drawText(x + "x" + y, 128, 128, mPaint); 385 tileSize <<= level; 386 x /= tileSize; 387 y /= tileSize; 388 mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint); 389 mCanvas.setBitmap(null); 390 return bitmap; 391 } 392 393 @Override 394 public BasicTexture getPreview() { 395 return null; 396 } 397 } 398 } 399