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