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.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Paint;
     24 import android.graphics.Paint.Align;
     25 import android.opengl.GLSurfaceView.Renderer;
     26 import android.util.AttributeSet;
     27 import android.view.MotionEvent;
     28 import android.view.ScaleGestureDetector;
     29 import android.view.ScaleGestureDetector.OnScaleGestureListener;
     30 import android.widget.FrameLayout;
     31 import com.android.gallery3d.glrenderer.GLES20Canvas;
     32 import com.android.photos.views.TiledImageRenderer.TileSource;
     33 
     34 import javax.microedition.khronos.egl.EGLConfig;
     35 import javax.microedition.khronos.opengles.GL10;
     36 
     37 
     38 public class TiledImageView extends FrameLayout implements OnScaleGestureListener {
     39 
     40     private BlockingGLTextureView mTextureView;
     41     private float mLastX, mLastY;
     42 
     43     private static class ImageRendererWrapper {
     44         // Guarded by locks
     45         float scale;
     46         int centerX, centerY;
     47         int rotation;
     48         TileSource source;
     49 
     50         // GL thread only
     51         TiledImageRenderer image;
     52     }
     53 
     54     // TODO: left/right paging
     55     private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1];
     56     private ImageRendererWrapper mFocusedRenderer;
     57 
     58     // -------------------------
     59     // Guarded by mLock
     60     // -------------------------
     61     private Object mLock = new Object();
     62     private ScaleGestureDetector mScaleGestureDetector;
     63 
     64     public TiledImageView(Context context) {
     65         this(context, null);
     66     }
     67 
     68     public TiledImageView(Context context, AttributeSet attrs) {
     69         super(context, attrs);
     70         mTextureView = new BlockingGLTextureView(context);
     71         addView(mTextureView, new LayoutParams(
     72                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
     73         mTextureView.setRenderer(new TileRenderer());
     74         setTileSource(new ColoredTiles());
     75         mScaleGestureDetector = new ScaleGestureDetector(context, this);
     76     }
     77 
     78     public void destroy() {
     79         mTextureView.destroy();
     80     }
     81 
     82     public void setTileSource(TileSource source) {
     83         synchronized (mLock) {
     84             for (int i = 0; i < mRenderers.length; i++) {
     85                 ImageRendererWrapper renderer = mRenderers[i];
     86                 if (renderer == null) {
     87                     renderer = mRenderers[i] = new ImageRendererWrapper();
     88                 }
     89                 renderer.source = source;
     90                 renderer.centerX = renderer.source.getImageWidth() / 2;
     91                 renderer.centerY = renderer.source.getImageHeight() / 2;
     92                 renderer.rotation = 0;
     93                 renderer.scale = 0;
     94                 renderer.image = new TiledImageRenderer(this);
     95                 updateScaleIfNecessaryLocked(renderer);
     96             }
     97         }
     98         mFocusedRenderer = mRenderers[0];
     99         invalidate();
    100     }
    101 
    102     @Override
    103     public boolean onScaleBegin(ScaleGestureDetector detector) {
    104         return true;
    105     }
    106 
    107     @Override
    108     public boolean onScale(ScaleGestureDetector detector) {
    109         // Don't need the lock because this will only fire inside of onTouchEvent
    110         mFocusedRenderer.scale *= detector.getScaleFactor();
    111         invalidate();
    112         return true;
    113     }
    114 
    115     @Override
    116     public void onScaleEnd(ScaleGestureDetector detector) {
    117     }
    118 
    119     @Override
    120     public boolean onTouchEvent(MotionEvent event) {
    121         int action = event.getActionMasked();
    122         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
    123         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
    124 
    125         // Determine focal point
    126         float sumX = 0, sumY = 0;
    127         final int count = event.getPointerCount();
    128         for (int i = 0; i < count; i++) {
    129             if (skipIndex == i) continue;
    130             sumX += event.getX(i);
    131             sumY += event.getY(i);
    132         }
    133         final int div = pointerUp ? count - 1 : count;
    134         float x = sumX / div;
    135         float y = sumY / div;
    136 
    137         synchronized (mLock) {
    138             mScaleGestureDetector.onTouchEvent(event);
    139             switch (action) {
    140             case MotionEvent.ACTION_MOVE:
    141                 mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale;
    142                 mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale;
    143                 invalidate();
    144                 break;
    145             }
    146         }
    147 
    148         mLastX = x;
    149         mLastY = y;
    150         return true;
    151     }
    152 
    153     @Override
    154     protected void onLayout(boolean changed, int left, int top, int right,
    155             int bottom) {
    156         super.onLayout(changed, left, top, right, bottom);
    157         synchronized (mLock) {
    158             for (ImageRendererWrapper renderer : mRenderers) {
    159                 updateScaleIfNecessaryLocked(renderer);
    160             }
    161         }
    162     }
    163 
    164     private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
    165         if (renderer.scale > 0 || getWidth() == 0) return;
    166         renderer.scale = Math.min(
    167                 (float) getWidth() / (float) renderer.source.getImageWidth(),
    168                 (float) getHeight() / (float) renderer.source.getImageHeight());
    169     }
    170 
    171     @Override
    172     protected void dispatchDraw(Canvas canvas) {
    173         mTextureView.render();
    174         super.dispatchDraw(canvas);
    175     }
    176 
    177     @Override
    178     public void invalidate() {
    179         super.invalidate();
    180         mTextureView.invalidate();
    181     }
    182 
    183     private class TileRenderer implements Renderer {
    184 
    185         private GLES20Canvas mCanvas;
    186 
    187         @Override
    188         public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    189             mCanvas = new GLES20Canvas();
    190             for (ImageRendererWrapper renderer : mRenderers) {
    191                 renderer.image.setModel(renderer.source, renderer.rotation);
    192             }
    193         }
    194 
    195         @Override
    196         public void onSurfaceChanged(GL10 gl, int width, int height) {
    197             mCanvas.setSize(width, height);
    198             for (ImageRendererWrapper renderer : mRenderers) {
    199                 renderer.image.setViewSize(width, height);
    200             }
    201         }
    202 
    203         @Override
    204         public void onDrawFrame(GL10 gl) {
    205             mCanvas.clearBuffer();
    206             synchronized (mLock) {
    207                 for (ImageRendererWrapper renderer : mRenderers) {
    208                     renderer.image.setModel(renderer.source, renderer.rotation);
    209                     renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale);
    210                 }
    211             }
    212             for (ImageRendererWrapper renderer : mRenderers) {
    213                 renderer.image.draw(mCanvas);
    214             }
    215         }
    216 
    217     }
    218 
    219     private static class ColoredTiles implements TileSource {
    220         private static int[] COLORS = new int[] {
    221             Color.RED,
    222             Color.BLUE,
    223             Color.YELLOW,
    224             Color.GREEN,
    225             Color.CYAN,
    226             Color.MAGENTA,
    227             Color.WHITE,
    228         };
    229 
    230         private Paint mPaint = new Paint();
    231         private Canvas mCanvas = new Canvas();
    232 
    233         @Override
    234         public int getTileSize() {
    235             return 256;
    236         }
    237 
    238         @Override
    239         public int getImageWidth() {
    240             return 16384;
    241         }
    242 
    243         @Override
    244         public int getImageHeight() {
    245             return 8192;
    246         }
    247 
    248         @Override
    249         public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
    250             int tileSize = getTileSize();
    251             if (bitmap == null) {
    252                 bitmap = Bitmap.createBitmap(tileSize, tileSize,
    253                         Bitmap.Config.ARGB_8888);
    254             }
    255             mCanvas.setBitmap(bitmap);
    256             mCanvas.drawColor(COLORS[level]);
    257             mPaint.setColor(Color.BLACK);
    258             mPaint.setTextSize(20);
    259             mPaint.setTextAlign(Align.CENTER);
    260             mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
    261             tileSize <<= level;
    262             x /= tileSize;
    263             y /= tileSize;
    264             mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
    265             mCanvas.setBitmap(null);
    266             return bitmap;
    267         }
    268     }
    269 }
    270