blob: 6fe030dae00a4f7b3bea3ef4d9799618044942e0 [file] [log] [blame]
/*
* 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;
}
}
}