| /* |
| * 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); |
| } |
| } |
| } |
| |
| } |
| } |