blob: 8d1cb486b7237b7a11be02fef98060b9f7194582 [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.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.View;
import android.widget.FrameLayout;
import com.android.gallery3d.glrenderer.BasicTexture;
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;
/**
* Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
* or {@link BlockingGLTextureView}.
*/
public class TiledImageView extends FrameLayout {
private static final boolean USE_TEXTURE_VIEW = false;
private static final boolean IS_SUPPORTED =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
private static final boolean USE_CHOREOGRAPHER =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
private BlockingGLTextureView mTextureView;
private GLSurfaceView mGLSurfaceView;
private boolean mInvalPending = false;
private FrameCallback mFrameCallback;
private static class ImageRendererWrapper {
// Guarded by locks
float scale;
int centerX, centerY;
int rotation;
TileSource source;
Runnable isReadyCallback;
// GL thread only
TiledImageRenderer image;
}
private float[] mValues = new float[9];
// -------------------------
// Guarded by mLock
// -------------------------
private Object mLock = new Object();
private ImageRendererWrapper mRenderer;
public static boolean isTilingSupported() {
return IS_SUPPORTED;
}
public TiledImageView(Context context) {
this(context, null);
}
public TiledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
if (!IS_SUPPORTED) {
return;
}
mRenderer = new ImageRendererWrapper();
mRenderer.image = new TiledImageRenderer(this);
View view;
if (USE_TEXTURE_VIEW) {
mTextureView = new BlockingGLTextureView(context);
mTextureView.setRenderer(new TileRenderer());
view = mTextureView;
} else {
mGLSurfaceView = new GLSurfaceView(context);
mGLSurfaceView.setEGLContextClientVersion(2);
mGLSurfaceView.setRenderer(new TileRenderer());
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
view = mGLSurfaceView;
}
addView(view, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//setTileSource(new ColoredTiles());
}
public void destroy() {
if (!IS_SUPPORTED) {
return;
}
if (USE_TEXTURE_VIEW) {
mTextureView.destroy();
} else {
mGLSurfaceView.queueEvent(mFreeTextures);
}
}
private Runnable mFreeTextures = new Runnable() {
@Override
public void run() {
mRenderer.image.freeTextures();
}
};
public void onPause() {
if (!IS_SUPPORTED) {
return;
}
if (!USE_TEXTURE_VIEW) {
mGLSurfaceView.onPause();
}
}
public void onResume() {
if (!IS_SUPPORTED) {
return;
}
if (!USE_TEXTURE_VIEW) {
mGLSurfaceView.onResume();
}
}
public void setTileSource(TileSource source, Runnable isReadyCallback) {
if (!IS_SUPPORTED) {
return;
}
synchronized (mLock) {
mRenderer.source = source;
mRenderer.isReadyCallback = isReadyCallback;
mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
mRenderer.rotation = source != null ? source.getRotation() : 0;
mRenderer.scale = 0;
updateScaleIfNecessaryLocked(mRenderer);
}
invalidate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!IS_SUPPORTED) {
return;
}
synchronized (mLock) {
updateScaleIfNecessaryLocked(mRenderer);
}
}
private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
if (renderer == null || renderer.source == null
|| 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) {
if (!IS_SUPPORTED) {
return;
}
if (USE_TEXTURE_VIEW) {
mTextureView.render();
}
super.dispatchDraw(canvas);
}
@SuppressLint("NewApi")
@Override
public void setTranslationX(float translationX) {
if (!IS_SUPPORTED) {
return;
}
super.setTranslationX(translationX);
}
@Override
public void invalidate() {
if (!IS_SUPPORTED) {
return;
}
if (USE_TEXTURE_VIEW) {
super.invalidate();
mTextureView.invalidate();
} else {
if (USE_CHOREOGRAPHER) {
invalOnVsync();
} else {
mGLSurfaceView.requestRender();
}
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void invalOnVsync() {
if (!mInvalPending) {
mInvalPending = true;
if (mFrameCallback == null) {
mFrameCallback = new FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mInvalPending = false;
mGLSurfaceView.requestRender();
}
};
}
Choreographer.getInstance().postFrameCallback(mFrameCallback);
}
}
private RectF mTempRectF = new RectF();
public void positionFromMatrix(Matrix matrix) {
if (!IS_SUPPORTED) {
return;
}
if (mRenderer.source != null) {
final int rotation = mRenderer.source.getRotation();
final boolean swap = !(rotation % 180 == 0);
final int width = swap ? mRenderer.source.getImageHeight()
: mRenderer.source.getImageWidth();
final int height = swap ? mRenderer.source.getImageWidth()
: mRenderer.source.getImageHeight();
mTempRectF.set(0, 0, width, height);
matrix.mapRect(mTempRectF);
matrix.getValues(mValues);
int cx = width / 2;
int cy = height / 2;
float scale = mValues[Matrix.MSCALE_X];
int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
if (rotation == 90 || rotation == 180) {
cx += (mTempRectF.left / scale) - xoffset;
} else {
cx -= (mTempRectF.left / scale) - xoffset;
}
if (rotation == 180 || rotation == 270) {
cy += (mTempRectF.top / scale) - yoffset;
} else {
cy -= (mTempRectF.top / scale) - yoffset;
}
mRenderer.scale = scale;
mRenderer.centerX = swap ? cy : cx;
mRenderer.centerY = swap ? cx : cy;
invalidate();
}
}
private class TileRenderer implements Renderer {
private GLES20Canvas mCanvas;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mCanvas = new GLES20Canvas();
BasicTexture.invalidateAllTextures();
mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mCanvas.setSize(width, height);
mRenderer.image.setViewSize(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
mCanvas.clearBuffer();
Runnable readyCallback;
synchronized (mLock) {
readyCallback = mRenderer.isReadyCallback;
mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
mRenderer.scale);
}
boolean complete = mRenderer.image.draw(mCanvas);
if (complete && readyCallback != null) {
synchronized (mLock) {
// Make sure we don't trample on a newly set callback/source
// if it changed while we were rendering
if (mRenderer.isReadyCallback == readyCallback) {
mRenderer.isReadyCallback = null;
}
}
if (readyCallback != null) {
post(readyCallback);
}
}
}
}
@SuppressWarnings("unused")
private static class ColoredTiles implements TileSource {
private static final 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 int getRotation() {
return 0;
}
@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;
}
@Override
public BasicTexture getPreview() {
return null;
}
}
}