Home | History | Annotate | Download | only in views
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.photos.views;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.annotation.TargetApi;
     21 import android.content.Context;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.Matrix;
     26 import android.graphics.Paint;
     27 import android.graphics.Paint.Align;
     28 import android.graphics.RectF;
     29 import android.opengl.GLSurfaceView;
     30 import android.opengl.GLSurfaceView.Renderer;
     31 import android.os.Build;
     32 import android.util.AttributeSet;
     33 import android.view.Choreographer;
     34 import android.view.Choreographer.FrameCallback;
     35 import android.view.View;
     36 import android.widget.FrameLayout;
     37 
     38 import com.android.gallery3d.glrenderer.BasicTexture;
     39 import com.android.gallery3d.glrenderer.GLES20Canvas;
     40 import com.android.photos.views.TiledImageRenderer.TileSource;
     41 
     42 import javax.microedition.khronos.egl.EGLConfig;
     43 import javax.microedition.khronos.opengles.GL10;
     44 
     45 /**
     46  * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
     47  * or {@link BlockingGLTextureView}.
     48  */
     49 public class TiledImageView extends FrameLayout {
     50 
     51     private static final boolean USE_TEXTURE_VIEW = false;
     52     private static final boolean IS_SUPPORTED =
     53             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
     54     private static final boolean USE_CHOREOGRAPHER =
     55             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
     56 
     57     private BlockingGLTextureView mTextureView;
     58     private GLSurfaceView mGLSurfaceView;
     59     private boolean mInvalPending = false;
     60     private FrameCallback mFrameCallback;
     61 
     62     protected static class ImageRendererWrapper {
     63         // Guarded by locks
     64         public float scale;
     65         public int centerX, centerY;
     66         public int rotation;
     67         public TileSource source;
     68         Runnable isReadyCallback;
     69 
     70         // GL thread only
     71         TiledImageRenderer image;
     72     }
     73 
     74     private float[] mValues = new float[9];
     75 
     76     // -------------------------
     77     // Guarded by mLock
     78     // -------------------------
     79     protected Object mLock = new Object();
     80     protected ImageRendererWrapper mRenderer;
     81 
     82     public static boolean isTilingSupported() {
     83         return IS_SUPPORTED;
     84     }
     85 
     86     public TiledImageView(Context context) {
     87         this(context, null);
     88     }
     89 
     90     public TiledImageView(Context context, AttributeSet attrs) {
     91         super(context, attrs);
     92         if (!IS_SUPPORTED) {
     93             return;
     94         }
     95 
     96         mRenderer = new ImageRendererWrapper();
     97         mRenderer.image = new TiledImageRenderer(this);
     98         View view;
     99         if (USE_TEXTURE_VIEW) {
    100             mTextureView = new BlockingGLTextureView(context);
    101             mTextureView.setRenderer(new TileRenderer());
    102             view = mTextureView;
    103         } else {
    104             mGLSurfaceView = new GLSurfaceView(context);
    105             mGLSurfaceView.setEGLContextClientVersion(2);
    106             mGLSurfaceView.setRenderer(new TileRenderer());
    107             mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    108             view = mGLSurfaceView;
    109         }
    110         addView(view, new LayoutParams(
    111                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    112         //setTileSource(new ColoredTiles());
    113     }
    114 
    115     @Override
    116     public void setVisibility(int visibility) {
    117         super.setVisibility(visibility);
    118         // need to update inner view's visibility because it seems like we're causing it to draw
    119         // from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
    120         if (USE_TEXTURE_VIEW) {
    121             mTextureView.setVisibility(visibility);
    122         } else {
    123             mGLSurfaceView.setVisibility(visibility);
    124         }
    125     }
    126 
    127     public void destroy() {
    128         if (!IS_SUPPORTED) {
    129             return;
    130         }
    131         if (USE_TEXTURE_VIEW) {
    132             mTextureView.destroy();
    133         } else {
    134             mGLSurfaceView.queueEvent(mFreeTextures);
    135         }
    136     }
    137 
    138     private Runnable mFreeTextures = new Runnable() {
    139 
    140         @Override
    141         public void run() {
    142             mRenderer.image.freeTextures();
    143         }
    144     };
    145 
    146     public void onPause() {
    147         if (!IS_SUPPORTED) {
    148             return;
    149         }
    150         if (!USE_TEXTURE_VIEW) {
    151             mGLSurfaceView.onPause();
    152         }
    153     }
    154 
    155     public void onResume() {
    156         if (!IS_SUPPORTED) {
    157             return;
    158         }
    159         if (!USE_TEXTURE_VIEW) {
    160             mGLSurfaceView.onResume();
    161         }
    162     }
    163 
    164     public void setTileSource(TileSource source, Runnable isReadyCallback) {
    165         if (!IS_SUPPORTED) {
    166             return;
    167         }
    168         synchronized (mLock) {
    169             mRenderer.source = source;
    170             mRenderer.isReadyCallback = isReadyCallback;
    171             mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
    172             mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
    173             mRenderer.rotation = source != null ? source.getRotation() : 0;
    174             mRenderer.scale = 0;
    175             updateScaleIfNecessaryLocked(mRenderer);
    176         }
    177         invalidate();
    178     }
    179 
    180     @Override
    181     protected void onLayout(boolean changed, int left, int top, int right,
    182             int bottom) {
    183         super.onLayout(changed, left, top, right, bottom);
    184         if (!IS_SUPPORTED) {
    185             return;
    186         }
    187         synchronized (mLock) {
    188             updateScaleIfNecessaryLocked(mRenderer);
    189         }
    190     }
    191 
    192     private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
    193         if (renderer == null || renderer.source == null
    194                 || renderer.scale > 0 || getWidth() == 0) {
    195             return;
    196         }
    197         renderer.scale = Math.min(
    198                 (float) getWidth() / (float) renderer.source.getImageWidth(),
    199                 (float) getHeight() / (float) renderer.source.getImageHeight());
    200     }
    201 
    202     @Override
    203     protected void dispatchDraw(Canvas canvas) {
    204         if (!IS_SUPPORTED) {
    205             return;
    206         }
    207         if (USE_TEXTURE_VIEW) {
    208             mTextureView.render();
    209         }
    210         super.dispatchDraw(canvas);
    211     }
    212 
    213     @SuppressLint("NewApi")
    214     @Override
    215     public void setTranslationX(float translationX) {
    216         if (!IS_SUPPORTED) {
    217             return;
    218         }
    219         super.setTranslationX(translationX);
    220     }
    221 
    222     @Override
    223     public void invalidate() {
    224         if (!IS_SUPPORTED) {
    225             return;
    226         }
    227         if (USE_TEXTURE_VIEW) {
    228             super.invalidate();
    229             mTextureView.invalidate();
    230         } else {
    231             if (USE_CHOREOGRAPHER) {
    232                 invalOnVsync();
    233             } else {
    234                 mGLSurfaceView.requestRender();
    235             }
    236         }
    237     }
    238 
    239     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    240     private void invalOnVsync() {
    241         if (!mInvalPending) {
    242             mInvalPending = true;
    243             if (mFrameCallback == null) {
    244                 mFrameCallback = new FrameCallback() {
    245                     @Override
    246                     public void doFrame(long frameTimeNanos) {
    247                         mInvalPending = false;
    248                         mGLSurfaceView.requestRender();
    249                     }
    250                 };
    251             }
    252             Choreographer.getInstance().postFrameCallback(mFrameCallback);
    253         }
    254     }
    255 
    256     private RectF mTempRectF = new RectF();
    257     public void positionFromMatrix(Matrix matrix) {
    258         if (!IS_SUPPORTED) {
    259             return;
    260         }
    261         if (mRenderer.source != null) {
    262             final int rotation = mRenderer.source.getRotation();
    263             final boolean swap = !(rotation % 180 == 0);
    264             final int width = swap ? mRenderer.source.getImageHeight()
    265                     : mRenderer.source.getImageWidth();
    266             final int height = swap ? mRenderer.source.getImageWidth()
    267                     : mRenderer.source.getImageHeight();
    268             mTempRectF.set(0, 0, width, height);
    269             matrix.mapRect(mTempRectF);
    270             matrix.getValues(mValues);
    271             int cx = width / 2;
    272             int cy = height / 2;
    273             float scale = mValues[Matrix.MSCALE_X];
    274             int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
    275             int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
    276             if (rotation == 90 || rotation == 180) {
    277                 cx += (mTempRectF.left / scale) - xoffset;
    278             } else {
    279                 cx -= (mTempRectF.left / scale) - xoffset;
    280             }
    281             if (rotation == 180 || rotation == 270) {
    282                 cy += (mTempRectF.top / scale) - yoffset;
    283             } else {
    284                 cy -= (mTempRectF.top / scale) - yoffset;
    285             }
    286             mRenderer.scale = scale;
    287             mRenderer.centerX = swap ? cy : cx;
    288             mRenderer.centerY = swap ? cx : cy;
    289             invalidate();
    290         }
    291     }
    292 
    293     private class TileRenderer implements Renderer {
    294 
    295         private GLES20Canvas mCanvas;
    296 
    297         @Override
    298         public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    299             mCanvas = new GLES20Canvas();
    300             BasicTexture.invalidateAllTextures();
    301             mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
    302         }
    303 
    304         @Override
    305         public void onSurfaceChanged(GL10 gl, int width, int height) {
    306             mCanvas.setSize(width, height);
    307             mRenderer.image.setViewSize(width, height);
    308         }
    309 
    310         @Override
    311         public void onDrawFrame(GL10 gl) {
    312             mCanvas.clearBuffer();
    313             Runnable readyCallback;
    314             synchronized (mLock) {
    315                 readyCallback = mRenderer.isReadyCallback;
    316                 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
    317                 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
    318                         mRenderer.scale);
    319             }
    320             boolean complete = mRenderer.image.draw(mCanvas);
    321             if (complete && readyCallback != null) {
    322                 synchronized (mLock) {
    323                     // Make sure we don't trample on a newly set callback/source
    324                     // if it changed while we were rendering
    325                     if (mRenderer.isReadyCallback == readyCallback) {
    326                         mRenderer.isReadyCallback = null;
    327                     }
    328                 }
    329                 if (readyCallback != null) {
    330                     post(readyCallback);
    331                 }
    332             }
    333         }
    334 
    335     }
    336 
    337     @SuppressWarnings("unused")
    338     private static class ColoredTiles implements TileSource {
    339         private static final int[] COLORS = new int[] {
    340             Color.RED,
    341             Color.BLUE,
    342             Color.YELLOW,
    343             Color.GREEN,
    344             Color.CYAN,
    345             Color.MAGENTA,
    346             Color.WHITE,
    347         };
    348 
    349         private Paint mPaint = new Paint();
    350         private Canvas mCanvas = new Canvas();
    351 
    352         @Override
    353         public int getTileSize() {
    354             return 256;
    355         }
    356 
    357         @Override
    358         public int getImageWidth() {
    359             return 16384;
    360         }
    361 
    362         @Override
    363         public int getImageHeight() {
    364             return 8192;
    365         }
    366 
    367         @Override
    368         public int getRotation() {
    369             return 0;
    370         }
    371 
    372         @Override
    373         public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
    374             int tileSize = getTileSize();
    375             if (bitmap == null) {
    376                 bitmap = Bitmap.createBitmap(tileSize, tileSize,
    377                         Bitmap.Config.ARGB_8888);
    378             }
    379             mCanvas.setBitmap(bitmap);
    380             mCanvas.drawColor(COLORS[level]);
    381             mPaint.setColor(Color.BLACK);
    382             mPaint.setTextSize(20);
    383             mPaint.setTextAlign(Align.CENTER);
    384             mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
    385             tileSize <<= level;
    386             x /= tileSize;
    387             y /= tileSize;
    388             mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
    389             mCanvas.setBitmap(null);
    390             return bitmap;
    391         }
    392 
    393         @Override
    394         public BasicTexture getPreview() {
    395             return null;
    396         }
    397     }
    398 }
    399