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     public void destroy() {
    116         if (!IS_SUPPORTED) {
    117             return;
    118         }
    119         if (USE_TEXTURE_VIEW) {
    120             mTextureView.destroy();
    121         } else {
    122             mGLSurfaceView.queueEvent(mFreeTextures);
    123         }
    124     }
    125 
    126     private Runnable mFreeTextures = new Runnable() {
    127 
    128         @Override
    129         public void run() {
    130             mRenderer.image.freeTextures();
    131         }
    132     };
    133 
    134     public void onPause() {
    135         if (!IS_SUPPORTED) {
    136             return;
    137         }
    138         if (!USE_TEXTURE_VIEW) {
    139             mGLSurfaceView.onPause();
    140         }
    141     }
    142 
    143     public void onResume() {
    144         if (!IS_SUPPORTED) {
    145             return;
    146         }
    147         if (!USE_TEXTURE_VIEW) {
    148             mGLSurfaceView.onResume();
    149         }
    150     }
    151 
    152     public void setTileSource(TileSource source, Runnable isReadyCallback) {
    153         if (!IS_SUPPORTED) {
    154             return;
    155         }
    156         synchronized (mLock) {
    157             mRenderer.source = source;
    158             mRenderer.isReadyCallback = isReadyCallback;
    159             mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
    160             mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
    161             mRenderer.rotation = source != null ? source.getRotation() : 0;
    162             mRenderer.scale = 0;
    163             updateScaleIfNecessaryLocked(mRenderer);
    164         }
    165         invalidate();
    166     }
    167 
    168     @Override
    169     protected void onLayout(boolean changed, int left, int top, int right,
    170             int bottom) {
    171         super.onLayout(changed, left, top, right, bottom);
    172         if (!IS_SUPPORTED) {
    173             return;
    174         }
    175         synchronized (mLock) {
    176             updateScaleIfNecessaryLocked(mRenderer);
    177         }
    178     }
    179 
    180     private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
    181         if (renderer == null || renderer.source == null
    182                 || renderer.scale > 0 || getWidth() == 0) {
    183             return;
    184         }
    185         renderer.scale = Math.min(
    186                 (float) getWidth() / (float) renderer.source.getImageWidth(),
    187                 (float) getHeight() / (float) renderer.source.getImageHeight());
    188     }
    189 
    190     @Override
    191     protected void dispatchDraw(Canvas canvas) {
    192         if (!IS_SUPPORTED) {
    193             return;
    194         }
    195         if (USE_TEXTURE_VIEW) {
    196             mTextureView.render();
    197         }
    198         super.dispatchDraw(canvas);
    199     }
    200 
    201     @SuppressLint("NewApi")
    202     @Override
    203     public void setTranslationX(float translationX) {
    204         if (!IS_SUPPORTED) {
    205             return;
    206         }
    207         super.setTranslationX(translationX);
    208     }
    209 
    210     @Override
    211     public void invalidate() {
    212         if (!IS_SUPPORTED) {
    213             return;
    214         }
    215         if (USE_TEXTURE_VIEW) {
    216             super.invalidate();
    217             mTextureView.invalidate();
    218         } else {
    219             if (USE_CHOREOGRAPHER) {
    220                 invalOnVsync();
    221             } else {
    222                 mGLSurfaceView.requestRender();
    223             }
    224         }
    225     }
    226 
    227     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    228     private void invalOnVsync() {
    229         if (!mInvalPending) {
    230             mInvalPending = true;
    231             if (mFrameCallback == null) {
    232                 mFrameCallback = new FrameCallback() {
    233                     @Override
    234                     public void doFrame(long frameTimeNanos) {
    235                         mInvalPending = false;
    236                         mGLSurfaceView.requestRender();
    237                     }
    238                 };
    239             }
    240             Choreographer.getInstance().postFrameCallback(mFrameCallback);
    241         }
    242     }
    243 
    244     private RectF mTempRectF = new RectF();
    245     public void positionFromMatrix(Matrix matrix) {
    246         if (!IS_SUPPORTED) {
    247             return;
    248         }
    249         if (mRenderer.source != null) {
    250             final int rotation = mRenderer.source.getRotation();
    251             final boolean swap = !(rotation % 180 == 0);
    252             final int width = swap ? mRenderer.source.getImageHeight()
    253                     : mRenderer.source.getImageWidth();
    254             final int height = swap ? mRenderer.source.getImageWidth()
    255                     : mRenderer.source.getImageHeight();
    256             mTempRectF.set(0, 0, width, height);
    257             matrix.mapRect(mTempRectF);
    258             matrix.getValues(mValues);
    259             int cx = width / 2;
    260             int cy = height / 2;
    261             float scale = mValues[Matrix.MSCALE_X];
    262             int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
    263             int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
    264             if (rotation == 90 || rotation == 180) {
    265                 cx += (mTempRectF.left / scale) - xoffset;
    266             } else {
    267                 cx -= (mTempRectF.left / scale) - xoffset;
    268             }
    269             if (rotation == 180 || rotation == 270) {
    270                 cy += (mTempRectF.top / scale) - yoffset;
    271             } else {
    272                 cy -= (mTempRectF.top / scale) - yoffset;
    273             }
    274             mRenderer.scale = scale;
    275             mRenderer.centerX = swap ? cy : cx;
    276             mRenderer.centerY = swap ? cx : cy;
    277             invalidate();
    278         }
    279     }
    280 
    281     private class TileRenderer implements Renderer {
    282 
    283         private GLES20Canvas mCanvas;
    284 
    285         @Override
    286         public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    287             mCanvas = new GLES20Canvas();
    288             BasicTexture.invalidateAllTextures();
    289             mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
    290         }
    291 
    292         @Override
    293         public void onSurfaceChanged(GL10 gl, int width, int height) {
    294             mCanvas.setSize(width, height);
    295             mRenderer.image.setViewSize(width, height);
    296         }
    297 
    298         @Override
    299         public void onDrawFrame(GL10 gl) {
    300             mCanvas.clearBuffer();
    301             Runnable readyCallback;
    302             synchronized (mLock) {
    303                 readyCallback = mRenderer.isReadyCallback;
    304                 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
    305                 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
    306                         mRenderer.scale);
    307             }
    308             boolean complete = mRenderer.image.draw(mCanvas);
    309             if (complete && readyCallback != null) {
    310                 synchronized (mLock) {
    311                     // Make sure we don't trample on a newly set callback/source
    312                     // if it changed while we were rendering
    313                     if (mRenderer.isReadyCallback == readyCallback) {
    314                         mRenderer.isReadyCallback = null;
    315                     }
    316                 }
    317                 if (readyCallback != null) {
    318                     post(readyCallback);
    319                 }
    320             }
    321         }
    322 
    323     }
    324 
    325     @SuppressWarnings("unused")
    326     private static class ColoredTiles implements TileSource {
    327         private static final int[] COLORS = new int[] {
    328             Color.RED,
    329             Color.BLUE,
    330             Color.YELLOW,
    331             Color.GREEN,
    332             Color.CYAN,
    333             Color.MAGENTA,
    334             Color.WHITE,
    335         };
    336 
    337         private Paint mPaint = new Paint();
    338         private Canvas mCanvas = new Canvas();
    339 
    340         @Override
    341         public int getTileSize() {
    342             return 256;
    343         }
    344 
    345         @Override
    346         public int getImageWidth() {
    347             return 16384;
    348         }
    349 
    350         @Override
    351         public int getImageHeight() {
    352             return 8192;
    353         }
    354 
    355         @Override
    356         public int getRotation() {
    357             return 0;
    358         }
    359 
    360         @Override
    361         public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
    362             int tileSize = getTileSize();
    363             if (bitmap == null) {
    364                 bitmap = Bitmap.createBitmap(tileSize, tileSize,
    365                         Bitmap.Config.ARGB_8888);
    366             }
    367             mCanvas.setBitmap(bitmap);
    368             mCanvas.drawColor(COLORS[level]);
    369             mPaint.setColor(Color.BLACK);
    370             mPaint.setTextSize(20);
    371             mPaint.setTextAlign(Align.CENTER);
    372             mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
    373             tileSize <<= level;
    374             x /= tileSize;
    375             y /= tileSize;
    376             mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
    377             mCanvas.setBitmap(null);
    378             return bitmap;
    379         }
    380 
    381         @Override
    382         public BasicTexture getPreview() {
    383             return null;
    384         }
    385     }
    386 }
    387