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