blob: 759613d987c0fb7bfdf0cab96281b936354c411b [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.Matrix;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.widget.FrameLayout;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.GLES20Canvas;
import com.android.launcher3.util.Thunk;
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}.
*/
public class TiledImageView extends FrameLayout {
@Thunk GLSurfaceView mGLSurfaceView;
@Thunk boolean mInvalPending = false;
private FrameCallback mFrameCallback;
protected static class ImageRendererWrapper {
// Guarded by locks
public float scale;
public int centerX, centerY;
public int rotation;
public TileSource source;
Runnable isReadyCallback;
// GL thread only
TiledImageRenderer image;
}
private float[] mValues = new float[9];
// -------------------------
// Guarded by mLock
// -------------------------
protected Object mLock = new Object();
protected ImageRendererWrapper mRenderer;
public TiledImageView(Context context) {
this(context, null);
}
public TiledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mRenderer = new ImageRendererWrapper();
mRenderer.image = new TiledImageRenderer(this);
mGLSurfaceView = new GLSurfaceView(context);
mGLSurfaceView.setEGLContextClientVersion(2);
mGLSurfaceView.setRenderer(new TileRenderer());
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
addView(mGLSurfaceView, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
// need to update inner view's visibility because it seems like we're causing it to draw
// from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
mGLSurfaceView.setVisibility(visibility);
}
public void destroy() {
mGLSurfaceView.queueEvent(mFreeTextures);
}
private Runnable mFreeTextures = new Runnable() {
@Override
public void run() {
mRenderer.image.freeTextures();
}
};
public void onPause() {
mGLSurfaceView.onPause();
}
public void onResume() {
mGLSurfaceView.onResume();
}
public void setTileSource(TileSource source, Runnable isReadyCallback) {
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();
}
public TileSource getTileSource() {
return mRenderer.source;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
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
public void invalidate() {
invalOnVsync();
}
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);
}
}
@Thunk 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);
}
}
}
}
}