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