/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.photos.views; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.opengl.GLSurfaceView.Renderer; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.widget.FrameLayout; import com.android.gallery3d.glrenderer.GLES20Canvas; import com.android.photos.views.TiledImageRenderer.TileSource; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class TiledImageView extends FrameLayout implements OnScaleGestureListener { private BlockingGLTextureView mTextureView; private float mLastX, mLastY; private static class ImageRendererWrapper { // Guarded by locks float scale; int centerX, centerY; int rotation; TileSource source; // GL thread only TiledImageRenderer image; } // TODO: left/right paging private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1]; private ImageRendererWrapper mFocusedRenderer; // ------------------------- // Guarded by mLock // ------------------------- private Object mLock = new Object(); private ScaleGestureDetector mScaleGestureDetector; public TiledImageView(Context context) { this(context, null); } public TiledImageView(Context context, AttributeSet attrs) { super(context, attrs); mTextureView = new BlockingGLTextureView(context); addView(mTextureView, new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); mTextureView.setRenderer(new TileRenderer()); setTileSource(new ColoredTiles()); mScaleGestureDetector = new ScaleGestureDetector(context, this); } public void destroy() { mTextureView.destroy(); } public void setTileSource(TileSource source) { synchronized (mLock) { for (int i = 0; i < mRenderers.length; i++) { ImageRendererWrapper renderer = mRenderers[i]; if (renderer == null) { renderer = mRenderers[i] = new ImageRendererWrapper(); } renderer.source = source; renderer.centerX = renderer.source.getImageWidth() / 2; renderer.centerY = renderer.source.getImageHeight() / 2; renderer.rotation = 0; renderer.scale = 0; renderer.image = new TiledImageRenderer(this); updateScaleIfNecessaryLocked(renderer); } } mFocusedRenderer = mRenderers[0]; invalidate(); } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public boolean onScale(ScaleGestureDetector detector) { // Don't need the lock because this will only fire inside of onTouchEvent mFocusedRenderer.scale *= detector.getScaleFactor(); invalidate(); return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getActionMasked(); final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? event.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; final int count = event.getPointerCount(); for (int i = 0; i < count; i++) { if (skipIndex == i) continue; sumX += event.getX(i); sumY += event.getY(i); } final int div = pointerUp ? count - 1 : count; float x = sumX / div; float y = sumY / div; synchronized (mLock) { mScaleGestureDetector.onTouchEvent(event); switch (action) { case MotionEvent.ACTION_MOVE: mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale; mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale; invalidate(); break; } } mLastX = x; mLastY = y; return true; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); synchronized (mLock) { for (ImageRendererWrapper renderer : mRenderers) { updateScaleIfNecessaryLocked(renderer); } } } private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) { if (renderer.scale > 0 || getWidth() == 0) return; renderer.scale = Math.min( (float) getWidth() / (float) renderer.source.getImageWidth(), (float) getHeight() / (float) renderer.source.getImageHeight()); } @Override protected void dispatchDraw(Canvas canvas) { mTextureView.render(); super.dispatchDraw(canvas); } @Override public void invalidate() { super.invalidate(); mTextureView.invalidate(); } private class TileRenderer implements Renderer { private GLES20Canvas mCanvas; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { mCanvas = new GLES20Canvas(); for (ImageRendererWrapper renderer : mRenderers) { renderer.image.setModel(renderer.source, renderer.rotation); } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mCanvas.setSize(width, height); for (ImageRendererWrapper renderer : mRenderers) { renderer.image.setViewSize(width, height); } } @Override public void onDrawFrame(GL10 gl) { mCanvas.clearBuffer(); synchronized (mLock) { for (ImageRendererWrapper renderer : mRenderers) { renderer.image.setModel(renderer.source, renderer.rotation); renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale); } } for (ImageRendererWrapper renderer : mRenderers) { renderer.image.draw(mCanvas); } } } private static class ColoredTiles implements TileSource { private static int[] COLORS = new int[] { Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.MAGENTA, Color.WHITE, }; private Paint mPaint = new Paint(); private Canvas mCanvas = new Canvas(); @Override public int getTileSize() { return 256; } @Override public int getImageWidth() { return 16384; } @Override public int getImageHeight() { return 8192; } @Override public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { int tileSize = getTileSize(); if (bitmap == null) { bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888); } mCanvas.setBitmap(bitmap); mCanvas.drawColor(COLORS[level]); mPaint.setColor(Color.BLACK); mPaint.setTextSize(20); mPaint.setTextAlign(Align.CENTER); mCanvas.drawText(x + "x" + y, 128, 128, mPaint); tileSize <<= level; x /= tileSize; y /= tileSize; mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint); mCanvas.setBitmap(null); return bitmap; } } }