Home | History | Annotate | Download | only in ui
      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.camera.ui;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.BitmapFactory;
     22 import android.graphics.BitmapRegionDecoder;
     23 import android.graphics.Matrix;
     24 import android.graphics.Rect;
     25 import android.graphics.RectF;
     26 import android.net.Uri;
     27 import android.os.AsyncTask;
     28 import android.util.Log;
     29 import android.view.View;
     30 import android.widget.ImageView;
     31 
     32 import java.io.FileNotFoundException;
     33 import java.io.IOException;
     34 import java.io.InputStream;
     35 
     36 public class ZoomView extends ImageView {
     37 
     38     private static final String TAG = "ZoomView";
     39 
     40     private int mViewportWidth = 0;
     41     private int mViewportHeight = 0;
     42 
     43     private int mFullResImageWidth;
     44     private int mFullResImageHeight;
     45 
     46     private BitmapRegionDecoder mRegionDecoder;
     47     private DecodePartialBitmap mPartialDecodingTask;
     48 
     49     private Uri mUri;
     50     private int mOrientation;
     51 
     52     private class DecodePartialBitmap extends AsyncTask<RectF, Void, Bitmap> {
     53 
     54         @Override
     55         protected Bitmap doInBackground(RectF... params) {
     56             RectF endRect = params[0];
     57 
     58             // Calculate the rotation matrix to apply orientation on the original image
     59             // rect.
     60             RectF fullResRect = new RectF(0, 0, mFullResImageWidth - 1, mFullResImageHeight - 1);
     61             Matrix rotationMatrix = new Matrix();
     62             rotationMatrix.setRotate(mOrientation, 0, 0);
     63             rotationMatrix.mapRect(fullResRect);
     64             // Set the translation of the matrix so that after rotation, the top left
     65             // of the image rect is at (0, 0)
     66             rotationMatrix.postTranslate(-fullResRect.left, -fullResRect.top);
     67             rotationMatrix.mapRect(fullResRect, new RectF(0, 0, mFullResImageWidth - 1,
     68                     mFullResImageHeight - 1));
     69 
     70             // Find intersection with the screen
     71             RectF visibleRect = new RectF(endRect);
     72             visibleRect.intersect(0, 0, mViewportWidth - 1, mViewportHeight - 1);
     73             // Calculate the mapping (i.e. transform) between current low res rect
     74             // and full res image rect, and apply the mapping on current visible rect
     75             // to find out the partial region in the full res image that we need
     76             // to decode.
     77             Matrix mapping = new Matrix();
     78             mapping.setRectToRect(endRect, fullResRect, Matrix.ScaleToFit.CENTER);
     79             RectF visibleAfterRotation = new RectF();
     80             mapping.mapRect(visibleAfterRotation, visibleRect);
     81 
     82             // Now the visible region we have is rotated, we need to reverse the
     83             // rotation to find out the region in the original image
     84             RectF visibleInImage = new RectF();
     85             Matrix invertRotation = new Matrix();
     86             rotationMatrix.invert(invertRotation);
     87             invertRotation.mapRect(visibleInImage, visibleAfterRotation);
     88 
     89             // Decode region
     90             Rect region = new Rect();
     91             visibleInImage.round(region);
     92 
     93             // Make sure region to decode is inside the image.
     94             region.intersect(0, 0, mFullResImageWidth - 1, mFullResImageHeight - 1);
     95 
     96             if (region.width() == 0 || region.height() == 0) {
     97                 Log.e(TAG, "Invalid size for partial region. Region: " + region.toString());
     98                 return null;
     99             }
    100 
    101             if (isCancelled()) {
    102                 return null;
    103             }
    104 
    105             BitmapFactory.Options options = new BitmapFactory.Options();
    106 
    107             if ((mOrientation + 360) % 180 == 0) {
    108                 options.inSampleSize = getSampleFactor(region.width(), region.height());
    109             } else {
    110                 // The decoded region will be rotated 90/270 degrees before showing
    111                 // on screen. In other words, the width and height will be swapped.
    112                 // Therefore, sample factor should be calculated using swapped width
    113                 // and height.
    114                 options.inSampleSize = getSampleFactor(region.height(), region.width());
    115             }
    116 
    117             if (mRegionDecoder == null) {
    118                 InputStream is = getInputStream();
    119                 try {
    120                     mRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
    121                     is.close();
    122                 } catch (IOException e) {
    123                     Log.e(TAG, "Failed to instantiate region decoder");
    124                 }
    125             }
    126             if (mRegionDecoder == null) {
    127                 return null;
    128             }
    129             Bitmap b = mRegionDecoder.decodeRegion(region, options);
    130             if (isCancelled()) {
    131                 return null;
    132             }
    133             Matrix rotation = new Matrix();
    134             rotation.setRotate(mOrientation);
    135             return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), rotation, false);
    136         }
    137 
    138         @Override
    139         protected void onPostExecute(Bitmap b) {
    140             if (b == null) {
    141                 return;
    142             }
    143             setImageBitmap(b);
    144             showPartiallyDecodedImage(true);
    145             mPartialDecodingTask = null;
    146         }
    147     }
    148 
    149     public ZoomView(Context context) {
    150         super(context);
    151         setScaleType(ScaleType.FIT_CENTER);
    152         addOnLayoutChangeListener(new OnLayoutChangeListener() {
    153             @Override
    154             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    155                                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
    156                 int w = right - left;
    157                 int h = bottom - top;
    158                 if (mViewportHeight != h || mViewportWidth != w) {
    159                     mViewportWidth = w;
    160                     mViewportHeight = h;
    161                 }
    162             }
    163         });
    164     }
    165 
    166     public void loadBitmap(Uri uri, int orientation, RectF imageRect) {
    167         if (!uri.equals(mUri)) {
    168             mUri = uri;
    169             mOrientation = orientation;
    170             mFullResImageHeight = 0;
    171             mFullResImageWidth = 0;
    172             decodeImageSize();
    173             mRegionDecoder = null;
    174         }
    175         startPartialDecodingTask(imageRect);
    176     }
    177 
    178     private void showPartiallyDecodedImage(boolean show) {
    179         if (show) {
    180             setVisibility(View.VISIBLE);
    181         } else {
    182             setVisibility(View.GONE);
    183         }
    184         mPartialDecodingTask = null;
    185     }
    186 
    187     public void cancelPartialDecodingTask() {
    188         if (mPartialDecodingTask != null && !mPartialDecodingTask.isCancelled()) {
    189             mPartialDecodingTask.cancel(true);
    190             setVisibility(GONE);
    191         }
    192         mPartialDecodingTask = null;
    193     }
    194 
    195     /**
    196      * If the given rect is smaller than viewport on x or y axis, center rect within
    197      * viewport on the corresponding axis. Otherwise, make sure viewport is within
    198      * the bounds of the rect.
    199      */
    200     public static RectF adjustToFitInBounds(RectF rect, int viewportWidth, int viewportHeight) {
    201         float dx = 0, dy = 0;
    202         RectF newRect = new RectF(rect);
    203         if (newRect.width() < viewportWidth) {
    204             dx = viewportWidth / 2 - (newRect.left + newRect.right) / 2;
    205         } else {
    206             if (newRect.left > 0) {
    207                 dx = -newRect.left;
    208             } else if (newRect.right < viewportWidth) {
    209                 dx = viewportWidth - newRect.right;
    210             }
    211         }
    212 
    213         if (newRect.height() < viewportHeight) {
    214             dy = viewportHeight / 2 - (newRect.top + newRect.bottom) / 2;
    215         } else {
    216             if (newRect.top > 0) {
    217                 dy = -newRect.top;
    218             } else if (newRect.bottom < viewportHeight) {
    219                 dy = viewportHeight - newRect.bottom;
    220             }
    221         }
    222 
    223         if (dx != 0 || dy != 0) {
    224             newRect.offset(dx, dy);
    225         }
    226         return newRect;
    227     }
    228 
    229     private void startPartialDecodingTask(RectF endRect) {
    230         // Cancel on-going partial decoding tasks
    231         cancelPartialDecodingTask();
    232         mPartialDecodingTask = new DecodePartialBitmap();
    233         mPartialDecodingTask.execute(endRect);
    234     }
    235 
    236     private void decodeImageSize() {
    237         BitmapFactory.Options option = new BitmapFactory.Options();
    238         option.inJustDecodeBounds = true;
    239         InputStream is = getInputStream();
    240         BitmapFactory.decodeStream(is, null, option);
    241         try {
    242             is.close();
    243         } catch (IOException e) {
    244             Log.e(TAG, "Failed to close input stream");
    245         }
    246         mFullResImageWidth = option.outWidth;
    247         mFullResImageHeight = option.outHeight;
    248     }
    249 
    250     // TODO: Cache the inputstream
    251     private InputStream getInputStream() {
    252         InputStream is = null;
    253         try {
    254             is = getContext().getContentResolver().openInputStream(mUri);
    255         } catch (FileNotFoundException e) {
    256             Log.e(TAG, "File not found at: " + mUri);
    257         }
    258         return is;
    259     }
    260 
    261     /**
    262      * Find closest sample factor that is power of 2, based on the given width and height
    263      *
    264      * @param width width of the partial region to decode
    265      * @param height height of the partial region to decode
    266      * @return sample factor
    267      */
    268     private int getSampleFactor(int width, int height) {
    269 
    270         float fitWidthScale = ((float) mViewportWidth) / ((float) width);
    271         float fitHeightScale = ((float) mViewportHeight) / ((float) height);
    272 
    273         float scale = Math.min(fitHeightScale, fitWidthScale);
    274 
    275         // Find the closest sample factor that is power of 2
    276         int sampleFactor = (int) (1f / scale);
    277         if (sampleFactor <=1) {
    278             return 1;
    279         }
    280         for (int i = 0; i < 32; i++) {
    281             if ((1 << (i + 1)) > sampleFactor) {
    282                 sampleFactor = (1 << i);
    283                 break;
    284             }
    285         }
    286         return sampleFactor;
    287     }
    288 }
    289