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.Paint; 24 import android.graphics.Paint.Align; 25 import android.opengl.GLSurfaceView.Renderer; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.ScaleGestureDetector; 29 import android.view.ScaleGestureDetector.OnScaleGestureListener; 30 import android.widget.FrameLayout; 31 import com.android.gallery3d.glrenderer.GLES20Canvas; 32 import com.android.photos.views.TiledImageRenderer.TileSource; 33 34 import javax.microedition.khronos.egl.EGLConfig; 35 import javax.microedition.khronos.opengles.GL10; 36 37 38 public class TiledImageView extends FrameLayout implements OnScaleGestureListener { 39 40 private BlockingGLTextureView mTextureView; 41 private float mLastX, mLastY; 42 43 private static class ImageRendererWrapper { 44 // Guarded by locks 45 float scale; 46 int centerX, centerY; 47 int rotation; 48 TileSource source; 49 50 // GL thread only 51 TiledImageRenderer image; 52 } 53 54 // TODO: left/right paging 55 private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1]; 56 private ImageRendererWrapper mFocusedRenderer; 57 58 // ------------------------- 59 // Guarded by mLock 60 // ------------------------- 61 private Object mLock = new Object(); 62 private ScaleGestureDetector mScaleGestureDetector; 63 64 public TiledImageView(Context context) { 65 this(context, null); 66 } 67 68 public TiledImageView(Context context, AttributeSet attrs) { 69 super(context, attrs); 70 mTextureView = new BlockingGLTextureView(context); 71 addView(mTextureView, new LayoutParams( 72 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 73 mTextureView.setRenderer(new TileRenderer()); 74 setTileSource(new ColoredTiles()); 75 mScaleGestureDetector = new ScaleGestureDetector(context, this); 76 } 77 78 public void destroy() { 79 mTextureView.destroy(); 80 } 81 82 public void setTileSource(TileSource source) { 83 synchronized (mLock) { 84 for (int i = 0; i < mRenderers.length; i++) { 85 ImageRendererWrapper renderer = mRenderers[i]; 86 if (renderer == null) { 87 renderer = mRenderers[i] = new ImageRendererWrapper(); 88 } 89 renderer.source = source; 90 renderer.centerX = renderer.source.getImageWidth() / 2; 91 renderer.centerY = renderer.source.getImageHeight() / 2; 92 renderer.rotation = 0; 93 renderer.scale = 0; 94 renderer.image = new TiledImageRenderer(this); 95 updateScaleIfNecessaryLocked(renderer); 96 } 97 } 98 mFocusedRenderer = mRenderers[0]; 99 invalidate(); 100 } 101 102 @Override 103 public boolean onScaleBegin(ScaleGestureDetector detector) { 104 return true; 105 } 106 107 @Override 108 public boolean onScale(ScaleGestureDetector detector) { 109 // Don't need the lock because this will only fire inside of onTouchEvent 110 mFocusedRenderer.scale *= detector.getScaleFactor(); 111 invalidate(); 112 return true; 113 } 114 115 @Override 116 public void onScaleEnd(ScaleGestureDetector detector) { 117 } 118 119 @Override 120 public boolean onTouchEvent(MotionEvent event) { 121 int action = event.getActionMasked(); 122 final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; 123 final int skipIndex = pointerUp ? event.getActionIndex() : -1; 124 125 // Determine focal point 126 float sumX = 0, sumY = 0; 127 final int count = event.getPointerCount(); 128 for (int i = 0; i < count; i++) { 129 if (skipIndex == i) continue; 130 sumX += event.getX(i); 131 sumY += event.getY(i); 132 } 133 final int div = pointerUp ? count - 1 : count; 134 float x = sumX / div; 135 float y = sumY / div; 136 137 synchronized (mLock) { 138 mScaleGestureDetector.onTouchEvent(event); 139 switch (action) { 140 case MotionEvent.ACTION_MOVE: 141 mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale; 142 mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale; 143 invalidate(); 144 break; 145 } 146 } 147 148 mLastX = x; 149 mLastY = y; 150 return true; 151 } 152 153 @Override 154 protected void onLayout(boolean changed, int left, int top, int right, 155 int bottom) { 156 super.onLayout(changed, left, top, right, bottom); 157 synchronized (mLock) { 158 for (ImageRendererWrapper renderer : mRenderers) { 159 updateScaleIfNecessaryLocked(renderer); 160 } 161 } 162 } 163 164 private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) { 165 if (renderer.scale > 0 || getWidth() == 0) return; 166 renderer.scale = Math.min( 167 (float) getWidth() / (float) renderer.source.getImageWidth(), 168 (float) getHeight() / (float) renderer.source.getImageHeight()); 169 } 170 171 @Override 172 protected void dispatchDraw(Canvas canvas) { 173 mTextureView.render(); 174 super.dispatchDraw(canvas); 175 } 176 177 @Override 178 public void invalidate() { 179 super.invalidate(); 180 mTextureView.invalidate(); 181 } 182 183 private class TileRenderer implements Renderer { 184 185 private GLES20Canvas mCanvas; 186 187 @Override 188 public void onSurfaceCreated(GL10 gl, EGLConfig config) { 189 mCanvas = new GLES20Canvas(); 190 for (ImageRendererWrapper renderer : mRenderers) { 191 renderer.image.setModel(renderer.source, renderer.rotation); 192 } 193 } 194 195 @Override 196 public void onSurfaceChanged(GL10 gl, int width, int height) { 197 mCanvas.setSize(width, height); 198 for (ImageRendererWrapper renderer : mRenderers) { 199 renderer.image.setViewSize(width, height); 200 } 201 } 202 203 @Override 204 public void onDrawFrame(GL10 gl) { 205 mCanvas.clearBuffer(); 206 synchronized (mLock) { 207 for (ImageRendererWrapper renderer : mRenderers) { 208 renderer.image.setModel(renderer.source, renderer.rotation); 209 renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale); 210 } 211 } 212 for (ImageRendererWrapper renderer : mRenderers) { 213 renderer.image.draw(mCanvas); 214 } 215 } 216 217 } 218 219 private static class ColoredTiles implements TileSource { 220 private static int[] COLORS = new int[] { 221 Color.RED, 222 Color.BLUE, 223 Color.YELLOW, 224 Color.GREEN, 225 Color.CYAN, 226 Color.MAGENTA, 227 Color.WHITE, 228 }; 229 230 private Paint mPaint = new Paint(); 231 private Canvas mCanvas = new Canvas(); 232 233 @Override 234 public int getTileSize() { 235 return 256; 236 } 237 238 @Override 239 public int getImageWidth() { 240 return 16384; 241 } 242 243 @Override 244 public int getImageHeight() { 245 return 8192; 246 } 247 248 @Override 249 public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { 250 int tileSize = getTileSize(); 251 if (bitmap == null) { 252 bitmap = Bitmap.createBitmap(tileSize, tileSize, 253 Bitmap.Config.ARGB_8888); 254 } 255 mCanvas.setBitmap(bitmap); 256 mCanvas.drawColor(COLORS[level]); 257 mPaint.setColor(Color.BLACK); 258 mPaint.setTextSize(20); 259 mPaint.setTextAlign(Align.CENTER); 260 mCanvas.drawText(x + "x" + y, 128, 128, mPaint); 261 tileSize <<= level; 262 x /= tileSize; 263 y /= tileSize; 264 mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint); 265 mCanvas.setBitmap(null); 266 return bitmap; 267 } 268 } 269 } 270