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