Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 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.gallery3d.app; |
| 18 | |
Ray Chen | 16e520e | 2011-10-11 12:49:45 +0800 | [diff] [blame] | 19 | import android.app.ActionBar; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 20 | import android.app.ProgressDialog; |
| 21 | import android.app.WallpaperManager; |
| 22 | import android.content.ContentValues; |
| 23 | import android.content.Intent; |
| 24 | import android.graphics.Bitmap; |
Owen Lin | c1965fc | 2011-10-19 19:52:33 +0800 | [diff] [blame] | 25 | import android.graphics.Bitmap.CompressFormat; |
| 26 | import android.graphics.Bitmap.Config; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 27 | import android.graphics.BitmapFactory; |
| 28 | import android.graphics.BitmapRegionDecoder; |
| 29 | import android.graphics.Canvas; |
| 30 | import android.graphics.Paint; |
| 31 | import android.graphics.Rect; |
| 32 | import android.graphics.RectF; |
Owen Lin | 3190f69 | 2011-09-02 21:15:42 +0800 | [diff] [blame] | 33 | import android.media.ExifInterface; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 34 | import android.net.Uri; |
| 35 | import android.os.Bundle; |
| 36 | import android.os.Environment; |
| 37 | import android.os.Handler; |
| 38 | import android.os.Message; |
| 39 | import android.provider.MediaStore; |
| 40 | import android.provider.MediaStore.Images; |
Chih-Chung Chang | 4e05190 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 41 | import android.util.FloatMath; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 42 | import android.view.Menu; |
| 43 | import android.view.MenuItem; |
| 44 | import android.view.Window; |
| 45 | import android.widget.Toast; |
| 46 | |
Owen Lin | 65e2653 | 2011-09-23 15:24:28 +0800 | [diff] [blame] | 47 | import com.android.gallery3d.R; |
| 48 | import com.android.gallery3d.common.BitmapUtils; |
| 49 | import com.android.gallery3d.common.Utils; |
| 50 | import com.android.gallery3d.data.DataManager; |
| 51 | import com.android.gallery3d.data.LocalImage; |
| 52 | import com.android.gallery3d.data.MediaItem; |
| 53 | import com.android.gallery3d.data.MediaObject; |
| 54 | import com.android.gallery3d.data.Path; |
| 55 | import com.android.gallery3d.picasasource.PicasaSource; |
| 56 | import com.android.gallery3d.ui.BitmapTileProvider; |
| 57 | import com.android.gallery3d.ui.CropView; |
| 58 | import com.android.gallery3d.ui.GLRoot; |
| 59 | import com.android.gallery3d.ui.SynchronizedHandler; |
| 60 | import com.android.gallery3d.ui.TileImageViewAdapter; |
| 61 | import com.android.gallery3d.util.Future; |
| 62 | import com.android.gallery3d.util.FutureListener; |
| 63 | import com.android.gallery3d.util.GalleryUtils; |
| 64 | import com.android.gallery3d.util.InterruptableOutputStream; |
| 65 | import com.android.gallery3d.util.ThreadPool.CancelListener; |
| 66 | import com.android.gallery3d.util.ThreadPool.Job; |
| 67 | import com.android.gallery3d.util.ThreadPool.JobContext; |
| 68 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 69 | import java.io.File; |
| 70 | import java.io.FileNotFoundException; |
| 71 | import java.io.FileOutputStream; |
| 72 | import java.io.IOException; |
| 73 | import java.io.OutputStream; |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 74 | import java.text.SimpleDateFormat; |
| 75 | import java.util.Date; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 76 | |
| 77 | /** |
| 78 | * The activity can crop specific region of interest from an image. |
| 79 | */ |
| 80 | public class CropImage extends AbstractGalleryActivity { |
| 81 | private static final String TAG = "CropImage"; |
| 82 | public static final String ACTION_CROP = "com.android.camera.action.CROP"; |
| 83 | |
| 84 | private static final int MAX_PIXEL_COUNT = 5 * 1000000; // 5M pixels |
| 85 | private static final int MAX_FILE_INDEX = 1000; |
| 86 | private static final int TILE_SIZE = 512; |
| 87 | private static final int BACKUP_PIXEL_COUNT = 480000; // around 800x600 |
| 88 | |
| 89 | private static final int MSG_LARGE_BITMAP = 1; |
| 90 | private static final int MSG_BITMAP = 2; |
| 91 | private static final int MSG_SAVE_COMPLETE = 3; |
Owen Lin | 65e2653 | 2011-09-23 15:24:28 +0800 | [diff] [blame] | 92 | private static final int MSG_SHOW_SAVE_ERROR = 4; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 93 | |
| 94 | private static final int MAX_BACKUP_IMAGE_SIZE = 320; |
| 95 | private static final int DEFAULT_COMPRESS_QUALITY = 90; |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 96 | private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss"; |
| 97 | |
| 98 | // Change these to Images.Media.WIDTH/HEIGHT after they are unhidden. |
| 99 | private static final String WIDTH = "width"; |
| 100 | private static final String HEIGHT = "height"; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 101 | |
| 102 | public static final String KEY_RETURN_DATA = "return-data"; |
| 103 | public static final String KEY_CROPPED_RECT = "cropped-rect"; |
| 104 | public static final String KEY_ASPECT_X = "aspectX"; |
| 105 | public static final String KEY_ASPECT_Y = "aspectY"; |
| 106 | public static final String KEY_SPOTLIGHT_X = "spotlightX"; |
| 107 | public static final String KEY_SPOTLIGHT_Y = "spotlightY"; |
| 108 | public static final String KEY_OUTPUT_X = "outputX"; |
| 109 | public static final String KEY_OUTPUT_Y = "outputY"; |
| 110 | public static final String KEY_SCALE = "scale"; |
| 111 | public static final String KEY_DATA = "data"; |
| 112 | public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded"; |
| 113 | public static final String KEY_OUTPUT_FORMAT = "outputFormat"; |
| 114 | public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper"; |
| 115 | public static final String KEY_NO_FACE_DETECTION = "noFaceDetection"; |
| 116 | |
| 117 | private static final String KEY_STATE = "state"; |
| 118 | |
| 119 | private static final int STATE_INIT = 0; |
| 120 | private static final int STATE_LOADED = 1; |
| 121 | private static final int STATE_SAVING = 2; |
| 122 | |
| 123 | public static final String DOWNLOAD_STRING = "download"; |
| 124 | public static final File DOWNLOAD_BUCKET = new File( |
| 125 | Environment.getExternalStorageDirectory(), DOWNLOAD_STRING); |
| 126 | |
| 127 | public static final String CROP_ACTION = "com.android.camera.action.CROP"; |
| 128 | |
| 129 | private int mState = STATE_INIT; |
| 130 | |
| 131 | private CropView mCropView; |
| 132 | |
| 133 | private boolean mDoFaceDetection = true; |
| 134 | |
| 135 | private Handler mMainHandler; |
| 136 | |
| 137 | // We keep the following members so that we can free them |
| 138 | |
| 139 | // mBitmap is the unrotated bitmap we pass in to mCropView for detect faces. |
| 140 | // mCropView is responsible for rotating it to the way that it is viewed by users. |
| 141 | private Bitmap mBitmap; |
| 142 | private BitmapTileProvider mBitmapTileProvider; |
| 143 | private BitmapRegionDecoder mRegionDecoder; |
| 144 | private Bitmap mBitmapInIntent; |
| 145 | private boolean mUseRegionDecoder = false; |
| 146 | |
| 147 | private ProgressDialog mProgressDialog; |
| 148 | private Future<BitmapRegionDecoder> mLoadTask; |
| 149 | private Future<Bitmap> mLoadBitmapTask; |
| 150 | private Future<Intent> mSaveTask; |
| 151 | |
| 152 | private MediaItem mMediaItem; |
| 153 | |
| 154 | @Override |
| 155 | public void onCreate(Bundle bundle) { |
| 156 | super.onCreate(bundle); |
| 157 | requestWindowFeature(Window.FEATURE_ACTION_BAR); |
| 158 | requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); |
| 159 | |
| 160 | // Initialize UI |
| 161 | setContentView(R.layout.cropimage); |
| 162 | mCropView = new CropView(this); |
| 163 | getGLRoot().setContentPane(mCropView); |
| 164 | |
Ray Chen | 16e520e | 2011-10-11 12:49:45 +0800 | [diff] [blame] | 165 | ActionBar actionBar = getActionBar(); |
| 166 | actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, |
| 167 | ActionBar.DISPLAY_HOME_AS_UP); |
| 168 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 169 | mMainHandler = new SynchronizedHandler(getGLRoot()) { |
| 170 | @Override |
| 171 | public void handleMessage(Message message) { |
| 172 | switch (message.what) { |
| 173 | case MSG_LARGE_BITMAP: { |
| 174 | mProgressDialog.dismiss(); |
| 175 | onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj); |
| 176 | break; |
| 177 | } |
| 178 | case MSG_BITMAP: { |
| 179 | mProgressDialog.dismiss(); |
| 180 | onBitmapAvailable((Bitmap) message.obj); |
| 181 | break; |
| 182 | } |
Owen Lin | 65e2653 | 2011-09-23 15:24:28 +0800 | [diff] [blame] | 183 | case MSG_SHOW_SAVE_ERROR: { |
| 184 | mProgressDialog.dismiss(); |
| 185 | setResult(RESULT_CANCELED); |
| 186 | Toast.makeText(CropImage.this, |
| 187 | CropImage.this.getString(R.string.save_error), |
| 188 | Toast.LENGTH_LONG).show(); |
| 189 | finish(); |
| 190 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 191 | case MSG_SAVE_COMPLETE: { |
| 192 | mProgressDialog.dismiss(); |
| 193 | setResult(RESULT_OK, (Intent) message.obj); |
| 194 | finish(); |
| 195 | break; |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | }; |
| 200 | |
| 201 | setCropParameters(); |
| 202 | } |
| 203 | |
| 204 | @Override |
| 205 | protected void onSaveInstanceState(Bundle saveState) { |
| 206 | saveState.putInt(KEY_STATE, mState); |
| 207 | } |
| 208 | |
| 209 | @Override |
| 210 | public boolean onCreateOptionsMenu(Menu menu) { |
| 211 | super.onCreateOptionsMenu(menu); |
| 212 | getMenuInflater().inflate(R.menu.crop, menu); |
| 213 | return true; |
| 214 | } |
| 215 | |
| 216 | @Override |
| 217 | public boolean onOptionsItemSelected(MenuItem item) { |
| 218 | switch (item.getItemId()) { |
Ray Chen | 16e520e | 2011-10-11 12:49:45 +0800 | [diff] [blame] | 219 | case android.R.id.home: { |
| 220 | finish(); |
| 221 | break; |
| 222 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 223 | case R.id.cancel: { |
| 224 | setResult(RESULT_CANCELED); |
| 225 | finish(); |
| 226 | break; |
| 227 | } |
| 228 | case R.id.save: { |
| 229 | onSaveClicked(); |
| 230 | break; |
| 231 | } |
| 232 | } |
| 233 | return true; |
| 234 | } |
| 235 | |
| 236 | private class SaveOutput implements Job<Intent> { |
Ray Chen | 16e520e | 2011-10-11 12:49:45 +0800 | [diff] [blame] | 237 | private final RectF mCropRect; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 238 | |
| 239 | public SaveOutput(RectF cropRect) { |
| 240 | mCropRect = cropRect; |
| 241 | } |
| 242 | |
| 243 | public Intent run(JobContext jc) { |
| 244 | RectF cropRect = mCropRect; |
| 245 | Bundle extra = getIntent().getExtras(); |
| 246 | |
| 247 | Rect rect = new Rect( |
| 248 | Math.round(cropRect.left), Math.round(cropRect.top), |
| 249 | Math.round(cropRect.right), Math.round(cropRect.bottom)); |
| 250 | |
| 251 | Intent result = new Intent(); |
| 252 | result.putExtra(KEY_CROPPED_RECT, rect); |
| 253 | Bitmap cropped = null; |
| 254 | boolean outputted = false; |
| 255 | if (extra != null) { |
| 256 | Uri uri = (Uri) extra.getParcelable(MediaStore.EXTRA_OUTPUT); |
| 257 | if (uri != null) { |
| 258 | if (jc.isCancelled()) return null; |
| 259 | outputted = true; |
| 260 | cropped = getCroppedImage(rect); |
| 261 | if (!saveBitmapToUri(jc, cropped, uri)) return null; |
| 262 | } |
| 263 | if (extra.getBoolean(KEY_RETURN_DATA, false)) { |
| 264 | if (jc.isCancelled()) return null; |
| 265 | outputted = true; |
| 266 | if (cropped == null) cropped = getCroppedImage(rect); |
| 267 | result.putExtra(KEY_DATA, cropped); |
| 268 | } |
| 269 | if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { |
| 270 | if (jc.isCancelled()) return null; |
| 271 | outputted = true; |
| 272 | if (cropped == null) cropped = getCroppedImage(rect); |
| 273 | if (!setAsWallpaper(jc, cropped)) return null; |
| 274 | } |
| 275 | } |
| 276 | if (!outputted) { |
| 277 | if (jc.isCancelled()) return null; |
| 278 | if (cropped == null) cropped = getCroppedImage(rect); |
| 279 | Uri data = saveToMediaProvider(jc, cropped); |
| 280 | if (data != null) result.setData(data); |
| 281 | } |
| 282 | return result; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | public static String determineCompressFormat(MediaObject obj) { |
| 287 | String compressFormat = "JPEG"; |
| 288 | if (obj instanceof MediaItem) { |
| 289 | String mime = ((MediaItem) obj).getMimeType(); |
| 290 | if (mime.contains("png") || mime.contains("gif")) { |
| 291 | // Set the compress format to PNG for png and gif images |
| 292 | // because they may contain alpha values. |
| 293 | compressFormat = "PNG"; |
| 294 | } |
| 295 | } |
| 296 | return compressFormat; |
| 297 | } |
| 298 | |
| 299 | private boolean setAsWallpaper(JobContext jc, Bitmap wallpaper) { |
| 300 | try { |
| 301 | WallpaperManager.getInstance(this).setBitmap(wallpaper); |
| 302 | } catch (IOException e) { |
| 303 | Log.w(TAG, "fail to set wall paper", e); |
| 304 | } |
| 305 | return true; |
| 306 | } |
| 307 | |
| 308 | private File saveMedia( |
| 309 | JobContext jc, Bitmap cropped, File directory, String filename) { |
| 310 | // Try file-1.jpg, file-2.jpg, ... until we find a filename |
| 311 | // which does not exist yet. |
| 312 | File candidate = null; |
| 313 | String fileExtension = getFileExtension(); |
| 314 | for (int i = 1; i < MAX_FILE_INDEX; ++i) { |
| 315 | candidate = new File(directory, filename + "-" + i + "." |
| 316 | + fileExtension); |
| 317 | try { |
| 318 | if (candidate.createNewFile()) break; |
| 319 | } catch (IOException e) { |
| 320 | Log.e(TAG, "fail to create new file: " |
| 321 | + candidate.getAbsolutePath(), e); |
| 322 | return null; |
| 323 | } |
| 324 | } |
| 325 | if (!candidate.exists() || !candidate.isFile()) { |
| 326 | throw new RuntimeException("cannot create file: " + filename); |
| 327 | } |
| 328 | |
| 329 | candidate.setReadable(true, false); |
| 330 | candidate.setWritable(true, false); |
| 331 | |
| 332 | try { |
| 333 | FileOutputStream fos = new FileOutputStream(candidate); |
| 334 | try { |
| 335 | saveBitmapToOutputStream(jc, cropped, |
| 336 | convertExtensionToCompressFormat(fileExtension), fos); |
| 337 | } finally { |
| 338 | fos.close(); |
| 339 | } |
| 340 | } catch (IOException e) { |
| 341 | Log.e(TAG, "fail to save image: " |
| 342 | + candidate.getAbsolutePath(), e); |
| 343 | candidate.delete(); |
| 344 | return null; |
| 345 | } |
| 346 | |
| 347 | if (jc.isCancelled()) { |
| 348 | candidate.delete(); |
| 349 | return null; |
| 350 | } |
| 351 | |
| 352 | return candidate; |
| 353 | } |
| 354 | |
| 355 | private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) { |
| 356 | if (PicasaSource.isPicasaImage(mMediaItem)) { |
| 357 | return savePicasaImage(jc, cropped); |
| 358 | } else if (mMediaItem instanceof LocalImage) { |
| 359 | return saveLocalImage(jc, cropped); |
| 360 | } else { |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 361 | return saveGenericImage(jc, cropped); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 362 | } |
| 363 | } |
| 364 | |
| 365 | private Uri savePicasaImage(JobContext jc, Bitmap cropped) { |
| 366 | if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { |
| 367 | throw new RuntimeException("cannot create download folder"); |
| 368 | } |
| 369 | |
| 370 | String filename = PicasaSource.getImageTitle(mMediaItem); |
| 371 | int pos = filename.lastIndexOf('.'); |
| 372 | if (pos >= 0) filename = filename.substring(0, pos); |
| 373 | File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename); |
| 374 | if (output == null) return null; |
| 375 | |
Owen Lin | 3190f69 | 2011-09-02 21:15:42 +0800 | [diff] [blame] | 376 | copyExif(mMediaItem, output.getAbsolutePath(), cropped.getWidth(), cropped.getHeight()); |
| 377 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 378 | long now = System.currentTimeMillis() / 1000; |
| 379 | ContentValues values = new ContentValues(); |
| 380 | values.put(Images.Media.TITLE, PicasaSource.getImageTitle(mMediaItem)); |
| 381 | values.put(Images.Media.DISPLAY_NAME, output.getName()); |
| 382 | values.put(Images.Media.DATE_TAKEN, PicasaSource.getDateTaken(mMediaItem)); |
| 383 | values.put(Images.Media.DATE_MODIFIED, now); |
| 384 | values.put(Images.Media.DATE_ADDED, now); |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 385 | values.put(Images.Media.MIME_TYPE, getOutputMimeType()); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 386 | values.put(Images.Media.ORIENTATION, 0); |
| 387 | values.put(Images.Media.DATA, output.getAbsolutePath()); |
| 388 | values.put(Images.Media.SIZE, output.length()); |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 389 | values.put(WIDTH, cropped.getWidth()); |
| 390 | values.put(HEIGHT, cropped.getHeight()); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 391 | |
| 392 | double latitude = PicasaSource.getLatitude(mMediaItem); |
| 393 | double longitude = PicasaSource.getLongitude(mMediaItem); |
| 394 | if (GalleryUtils.isValidLocation(latitude, longitude)) { |
| 395 | values.put(Images.Media.LATITUDE, latitude); |
| 396 | values.put(Images.Media.LONGITUDE, longitude); |
| 397 | } |
| 398 | return getContentResolver().insert( |
| 399 | Images.Media.EXTERNAL_CONTENT_URI, values); |
| 400 | } |
| 401 | |
| 402 | private Uri saveLocalImage(JobContext jc, Bitmap cropped) { |
| 403 | LocalImage localImage = (LocalImage) mMediaItem; |
| 404 | |
| 405 | File oldPath = new File(localImage.filePath); |
| 406 | File directory = new File(oldPath.getParent()); |
| 407 | |
| 408 | String filename = oldPath.getName(); |
| 409 | int pos = filename.lastIndexOf('.'); |
| 410 | if (pos >= 0) filename = filename.substring(0, pos); |
| 411 | File output = saveMedia(jc, cropped, directory, filename); |
| 412 | if (output == null) return null; |
| 413 | |
Owen Lin | 3190f69 | 2011-09-02 21:15:42 +0800 | [diff] [blame] | 414 | copyExif(oldPath.getAbsolutePath(), output.getAbsolutePath(), |
| 415 | cropped.getWidth(), cropped.getHeight()); |
| 416 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 417 | long now = System.currentTimeMillis() / 1000; |
| 418 | ContentValues values = new ContentValues(); |
| 419 | values.put(Images.Media.TITLE, localImage.caption); |
| 420 | values.put(Images.Media.DISPLAY_NAME, output.getName()); |
| 421 | values.put(Images.Media.DATE_TAKEN, localImage.dateTakenInMs); |
| 422 | values.put(Images.Media.DATE_MODIFIED, now); |
| 423 | values.put(Images.Media.DATE_ADDED, now); |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 424 | values.put(Images.Media.MIME_TYPE, getOutputMimeType()); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 425 | values.put(Images.Media.ORIENTATION, 0); |
| 426 | values.put(Images.Media.DATA, output.getAbsolutePath()); |
| 427 | values.put(Images.Media.SIZE, output.length()); |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 428 | values.put(WIDTH, cropped.getWidth()); |
| 429 | values.put(HEIGHT, cropped.getHeight()); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 430 | |
| 431 | if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) { |
| 432 | values.put(Images.Media.LATITUDE, localImage.latitude); |
| 433 | values.put(Images.Media.LONGITUDE, localImage.longitude); |
| 434 | } |
| 435 | return getContentResolver().insert( |
| 436 | Images.Media.EXTERNAL_CONTENT_URI, values); |
| 437 | } |
| 438 | |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 439 | private Uri saveGenericImage(JobContext jc, Bitmap cropped) { |
| 440 | if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { |
| 441 | throw new RuntimeException("cannot create download folder"); |
| 442 | } |
| 443 | |
| 444 | long now = System.currentTimeMillis(); |
| 445 | String filename = new SimpleDateFormat(TIME_STAMP_NAME). |
| 446 | format(new Date(now)); |
| 447 | |
| 448 | File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename); |
| 449 | if (output == null) return null; |
| 450 | |
| 451 | ContentValues values = new ContentValues(); |
| 452 | values.put(Images.Media.TITLE, filename); |
| 453 | values.put(Images.Media.DISPLAY_NAME, output.getName()); |
| 454 | values.put(Images.Media.DATE_TAKEN, now); |
| 455 | values.put(Images.Media.DATE_MODIFIED, now / 1000); |
| 456 | values.put(Images.Media.DATE_ADDED, now / 1000); |
| 457 | values.put(Images.Media.MIME_TYPE, getOutputMimeType()); |
| 458 | values.put(Images.Media.ORIENTATION, 0); |
| 459 | values.put(Images.Media.DATA, output.getAbsolutePath()); |
| 460 | values.put(Images.Media.SIZE, output.length()); |
| 461 | values.put(WIDTH, cropped.getWidth()); |
| 462 | values.put(HEIGHT, cropped.getHeight()); |
| 463 | |
| 464 | return getContentResolver().insert( |
| 465 | Images.Media.EXTERNAL_CONTENT_URI, values); |
| 466 | } |
| 467 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 468 | private boolean saveBitmapToOutputStream( |
| 469 | JobContext jc, Bitmap bitmap, CompressFormat format, OutputStream os) { |
| 470 | // We wrap the OutputStream so that it can be interrupted. |
| 471 | final InterruptableOutputStream ios = new InterruptableOutputStream(os); |
| 472 | jc.setCancelListener(new CancelListener() { |
| 473 | public void onCancel() { |
| 474 | ios.interrupt(); |
| 475 | } |
| 476 | }); |
| 477 | try { |
| 478 | bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os); |
Owen Lin | 65e2653 | 2011-09-23 15:24:28 +0800 | [diff] [blame] | 479 | return !jc.isCancelled(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 480 | } finally { |
| 481 | jc.setCancelListener(null); |
| 482 | Utils.closeSilently(os); |
| 483 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 484 | } |
| 485 | |
| 486 | private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) { |
| 487 | try { |
| 488 | return saveBitmapToOutputStream(jc, bitmap, |
| 489 | convertExtensionToCompressFormat(getFileExtension()), |
| 490 | getContentResolver().openOutputStream(uri)); |
| 491 | } catch (FileNotFoundException e) { |
| 492 | Log.w(TAG, "cannot write output", e); |
| 493 | } |
| 494 | return true; |
| 495 | } |
| 496 | |
| 497 | private CompressFormat convertExtensionToCompressFormat(String extension) { |
| 498 | return extension.equals("png") |
| 499 | ? CompressFormat.PNG |
| 500 | : CompressFormat.JPEG; |
| 501 | } |
| 502 | |
Chih-Chung Chang | 6156a86 | 2011-10-19 15:35:28 +0800 | [diff] [blame] | 503 | private String getOutputMimeType() { |
| 504 | return getFileExtension().equals("png") ? "image/png" : "image/jpeg"; |
| 505 | } |
| 506 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 507 | private String getFileExtension() { |
| 508 | String requestFormat = getIntent().getStringExtra(KEY_OUTPUT_FORMAT); |
| 509 | String outputFormat = (requestFormat == null) |
| 510 | ? determineCompressFormat(mMediaItem) |
| 511 | : requestFormat; |
| 512 | |
| 513 | outputFormat = outputFormat.toLowerCase(); |
| 514 | return (outputFormat.equals("png") || outputFormat.equals("gif")) |
| 515 | ? "png" // We don't support gif compression. |
| 516 | : "jpg"; |
| 517 | } |
| 518 | |
| 519 | private void onSaveClicked() { |
| 520 | Bundle extra = getIntent().getExtras(); |
| 521 | RectF cropRect = mCropView.getCropRectangle(); |
| 522 | if (cropRect == null) return; |
| 523 | mState = STATE_SAVING; |
| 524 | int messageId = extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER) |
| 525 | ? R.string.wallpaper |
| 526 | : R.string.saving_image; |
| 527 | mProgressDialog = ProgressDialog.show( |
| 528 | this, null, getString(messageId), true, false); |
| 529 | mSaveTask = getThreadPool().submit(new SaveOutput(cropRect), |
| 530 | new FutureListener<Intent>() { |
| 531 | public void onFutureDone(Future<Intent> future) { |
| 532 | mSaveTask = null; |
Owen Lin | 65e2653 | 2011-09-23 15:24:28 +0800 | [diff] [blame] | 533 | if (future.isCancelled()) return; |
| 534 | Intent intent = future.get(); |
| 535 | if (intent != null) { |
| 536 | mMainHandler.sendMessage(mMainHandler.obtainMessage( |
| 537 | MSG_SAVE_COMPLETE, intent)); |
| 538 | } else { |
| 539 | mMainHandler.sendEmptyMessage(MSG_SHOW_SAVE_ERROR); |
| 540 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 541 | } |
| 542 | }); |
| 543 | } |
| 544 | |
| 545 | private Bitmap getCroppedImage(Rect rect) { |
| 546 | Utils.assertTrue(rect.width() > 0 && rect.height() > 0); |
| 547 | |
| 548 | Bundle extras = getIntent().getExtras(); |
| 549 | // (outputX, outputY) = the width and height of the returning bitmap. |
| 550 | int outputX = rect.width(); |
| 551 | int outputY = rect.height(); |
| 552 | if (extras != null) { |
| 553 | outputX = extras.getInt(KEY_OUTPUT_X, outputX); |
| 554 | outputY = extras.getInt(KEY_OUTPUT_Y, outputY); |
| 555 | } |
| 556 | |
| 557 | if (outputX * outputY > MAX_PIXEL_COUNT) { |
Chih-Chung Chang | 4e05190 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 558 | float scale = FloatMath.sqrt((float) MAX_PIXEL_COUNT / outputX / outputY); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 559 | Log.w(TAG, "scale down the cropped image: " + scale); |
| 560 | outputX = Math.round(scale * outputX); |
| 561 | outputY = Math.round(scale * outputY); |
| 562 | } |
| 563 | |
| 564 | // (rect.width() * scaleX, rect.height() * scaleY) = |
| 565 | // the size of drawing area in output bitmap |
| 566 | float scaleX = 1; |
| 567 | float scaleY = 1; |
| 568 | Rect dest = new Rect(0, 0, outputX, outputY); |
| 569 | if (extras == null || extras.getBoolean(KEY_SCALE, true)) { |
| 570 | scaleX = (float) outputX / rect.width(); |
| 571 | scaleY = (float) outputY / rect.height(); |
| 572 | if (extras == null || !extras.getBoolean( |
| 573 | KEY_SCALE_UP_IF_NEEDED, false)) { |
| 574 | if (scaleX > 1f) scaleX = 1; |
| 575 | if (scaleY > 1f) scaleY = 1; |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | // Keep the content in the center (or crop the content) |
| 580 | int rectWidth = Math.round(rect.width() * scaleX); |
| 581 | int rectHeight = Math.round(rect.height() * scaleY); |
| 582 | dest.set(Math.round((outputX - rectWidth) / 2f), |
| 583 | Math.round((outputY - rectHeight) / 2f), |
| 584 | Math.round((outputX + rectWidth) / 2f), |
| 585 | Math.round((outputY + rectHeight) / 2f)); |
| 586 | |
| 587 | if (mBitmapInIntent != null) { |
| 588 | Bitmap source = mBitmapInIntent; |
| 589 | Bitmap result = Bitmap.createBitmap( |
| 590 | outputX, outputY, Config.ARGB_8888); |
| 591 | Canvas canvas = new Canvas(result); |
| 592 | canvas.drawBitmap(source, rect, dest, null); |
| 593 | return result; |
| 594 | } |
| 595 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 596 | if (mUseRegionDecoder) { |
Owen Lin | c1965fc | 2011-10-19 19:52:33 +0800 | [diff] [blame] | 597 | int rotation = mMediaItem.getFullImageRotation(); |
| 598 | rotateRectangle(rect, mCropView.getImageWidth(), |
| 599 | mCropView.getImageHeight(), 360 - rotation); |
| 600 | rotateRectangle(dest, outputX, outputY, 360 - rotation); |
| 601 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 602 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 603 | int sample = BitmapUtils.computeSampleSizeLarger( |
| 604 | Math.max(scaleX, scaleY)); |
| 605 | options.inSampleSize = sample; |
| 606 | if ((rect.width() / sample) == dest.width() |
| 607 | && (rect.height() / sample) == dest.height() |
| 608 | && rotation == 0) { |
| 609 | // To prevent concurrent access in GLThread |
| 610 | synchronized (mRegionDecoder) { |
| 611 | return mRegionDecoder.decodeRegion(rect, options); |
| 612 | } |
| 613 | } |
| 614 | Bitmap result = Bitmap.createBitmap( |
| 615 | outputX, outputY, Config.ARGB_8888); |
| 616 | Canvas canvas = new Canvas(result); |
| 617 | rotateCanvas(canvas, outputX, outputY, rotation); |
| 618 | drawInTiles(canvas, mRegionDecoder, rect, dest, sample); |
| 619 | return result; |
| 620 | } else { |
Owen Lin | c1965fc | 2011-10-19 19:52:33 +0800 | [diff] [blame] | 621 | int rotation = mMediaItem.getRotation(); |
| 622 | rotateRectangle(rect, mCropView.getImageWidth(), |
| 623 | mCropView.getImageHeight(), 360 - rotation); |
| 624 | rotateRectangle(dest, outputX, outputY, 360 - rotation); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 625 | Bitmap result = Bitmap.createBitmap(outputX, outputY, Config.ARGB_8888); |
| 626 | Canvas canvas = new Canvas(result); |
| 627 | rotateCanvas(canvas, outputX, outputY, rotation); |
| 628 | canvas.drawBitmap(mBitmap, |
| 629 | rect, dest, new Paint(Paint.FILTER_BITMAP_FLAG)); |
| 630 | return result; |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | private static void rotateCanvas( |
| 635 | Canvas canvas, int width, int height, int rotation) { |
| 636 | canvas.translate(width / 2, height / 2); |
| 637 | canvas.rotate(rotation); |
| 638 | if (((rotation / 90) & 0x01) == 0) { |
| 639 | canvas.translate(-width / 2, -height / 2); |
| 640 | } else { |
| 641 | canvas.translate(-height / 2, -width / 2); |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | private static void rotateRectangle( |
| 646 | Rect rect, int width, int height, int rotation) { |
| 647 | if (rotation == 0 || rotation == 360) return; |
| 648 | |
| 649 | int w = rect.width(); |
| 650 | int h = rect.height(); |
| 651 | switch (rotation) { |
| 652 | case 90: { |
| 653 | rect.top = rect.left; |
| 654 | rect.left = height - rect.bottom; |
| 655 | rect.right = rect.left + h; |
| 656 | rect.bottom = rect.top + w; |
| 657 | return; |
| 658 | } |
| 659 | case 180: { |
| 660 | rect.left = width - rect.right; |
| 661 | rect.top = height - rect.bottom; |
| 662 | rect.right = rect.left + w; |
| 663 | rect.bottom = rect.top + h; |
| 664 | return; |
| 665 | } |
| 666 | case 270: { |
| 667 | rect.left = rect.top; |
| 668 | rect.top = width - rect.right; |
| 669 | rect.right = rect.left + h; |
| 670 | rect.bottom = rect.top + w; |
| 671 | return; |
| 672 | } |
| 673 | default: throw new AssertionError(); |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | private void drawInTiles(Canvas canvas, |
| 678 | BitmapRegionDecoder decoder, Rect rect, Rect dest, int sample) { |
| 679 | int tileSize = TILE_SIZE * sample; |
| 680 | Rect tileRect = new Rect(); |
| 681 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 682 | options.inPreferredConfig = Config.ARGB_8888; |
| 683 | options.inSampleSize = sample; |
| 684 | canvas.translate(dest.left, dest.top); |
| 685 | canvas.scale((float) sample * dest.width() / rect.width(), |
| 686 | (float) sample * dest.height() / rect.height()); |
| 687 | Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); |
| 688 | for (int tx = rect.left, x = 0; |
| 689 | tx < rect.right; tx += tileSize, x += TILE_SIZE) { |
| 690 | for (int ty = rect.top, y = 0; |
| 691 | ty < rect.bottom; ty += tileSize, y += TILE_SIZE) { |
| 692 | tileRect.set(tx, ty, tx + tileSize, ty + tileSize); |
| 693 | if (tileRect.intersect(rect)) { |
| 694 | Bitmap bitmap; |
| 695 | |
| 696 | // To prevent concurrent access in GLThread |
| 697 | synchronized (decoder) { |
| 698 | bitmap = decoder.decodeRegion(tileRect, options); |
| 699 | } |
| 700 | canvas.drawBitmap(bitmap, x, y, paint); |
| 701 | bitmap.recycle(); |
| 702 | } |
| 703 | } |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | private void onBitmapRegionDecoderAvailable( |
| 708 | BitmapRegionDecoder regionDecoder) { |
| 709 | |
| 710 | if (regionDecoder == null) { |
Chih-Chung Chang | 52da9b7 | 2012-02-01 18:40:57 +0800 | [diff] [blame] | 711 | Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 712 | finish(); |
| 713 | return; |
| 714 | } |
| 715 | mRegionDecoder = regionDecoder; |
| 716 | mUseRegionDecoder = true; |
| 717 | mState = STATE_LOADED; |
| 718 | |
| 719 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 720 | int width = regionDecoder.getWidth(); |
| 721 | int height = regionDecoder.getHeight(); |
| 722 | options.inSampleSize = BitmapUtils.computeSampleSize(width, height, |
| 723 | BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT); |
| 724 | mBitmap = regionDecoder.decodeRegion( |
| 725 | new Rect(0, 0, width, height), options); |
| 726 | mCropView.setDataModel(new TileImageViewAdapter( |
Owen Lin | c1965fc | 2011-10-19 19:52:33 +0800 | [diff] [blame] | 727 | mBitmap, regionDecoder), mMediaItem.getFullImageRotation()); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 728 | if (mDoFaceDetection) { |
| 729 | mCropView.detectFaces(mBitmap); |
| 730 | } else { |
| 731 | mCropView.initializeHighlightRectangle(); |
| 732 | } |
| 733 | } |
| 734 | |
| 735 | private void onBitmapAvailable(Bitmap bitmap) { |
| 736 | if (bitmap == null) { |
Chih-Chung Chang | 52da9b7 | 2012-02-01 18:40:57 +0800 | [diff] [blame] | 737 | Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 738 | finish(); |
| 739 | return; |
| 740 | } |
| 741 | mUseRegionDecoder = false; |
| 742 | mState = STATE_LOADED; |
| 743 | |
| 744 | mBitmap = bitmap; |
| 745 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 746 | mCropView.setDataModel(new BitmapTileProvider(bitmap, 512), |
| 747 | mMediaItem.getRotation()); |
| 748 | if (mDoFaceDetection) { |
| 749 | mCropView.detectFaces(bitmap); |
| 750 | } else { |
| 751 | mCropView.initializeHighlightRectangle(); |
| 752 | } |
| 753 | } |
| 754 | |
| 755 | private void setCropParameters() { |
| 756 | Bundle extras = getIntent().getExtras(); |
| 757 | if (extras == null) |
| 758 | return; |
| 759 | int aspectX = extras.getInt(KEY_ASPECT_X, 0); |
| 760 | int aspectY = extras.getInt(KEY_ASPECT_Y, 0); |
| 761 | if (aspectX != 0 && aspectY != 0) { |
| 762 | mCropView.setAspectRatio((float) aspectX / aspectY); |
| 763 | } |
| 764 | |
| 765 | float spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0); |
| 766 | float spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0); |
| 767 | if (spotlightX != 0 && spotlightY != 0) { |
| 768 | mCropView.setSpotlightRatio(spotlightX, spotlightY); |
| 769 | } |
| 770 | } |
| 771 | |
| 772 | private void initializeData() { |
| 773 | Bundle extras = getIntent().getExtras(); |
| 774 | |
| 775 | if (extras != null) { |
| 776 | if (extras.containsKey(KEY_NO_FACE_DETECTION)) { |
| 777 | mDoFaceDetection = !extras.getBoolean(KEY_NO_FACE_DETECTION); |
| 778 | } |
| 779 | |
| 780 | mBitmapInIntent = extras.getParcelable(KEY_DATA); |
| 781 | |
| 782 | if (mBitmapInIntent != null) { |
| 783 | mBitmapTileProvider = |
| 784 | new BitmapTileProvider(mBitmapInIntent, MAX_BACKUP_IMAGE_SIZE); |
| 785 | mCropView.setDataModel(mBitmapTileProvider, 0); |
| 786 | if (mDoFaceDetection) { |
| 787 | mCropView.detectFaces(mBitmapInIntent); |
| 788 | } else { |
| 789 | mCropView.initializeHighlightRectangle(); |
| 790 | } |
| 791 | mState = STATE_LOADED; |
| 792 | return; |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | mProgressDialog = ProgressDialog.show( |
| 797 | this, null, getString(R.string.loading_image), true, false); |
| 798 | |
| 799 | mMediaItem = getMediaItemFromIntentData(); |
| 800 | if (mMediaItem == null) return; |
| 801 | |
| 802 | boolean supportedByBitmapRegionDecoder = |
| 803 | (mMediaItem.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0; |
| 804 | if (supportedByBitmapRegionDecoder) { |
| 805 | mLoadTask = getThreadPool().submit(new LoadDataTask(mMediaItem), |
| 806 | new FutureListener<BitmapRegionDecoder>() { |
| 807 | public void onFutureDone(Future<BitmapRegionDecoder> future) { |
| 808 | mLoadTask = null; |
| 809 | BitmapRegionDecoder decoder = future.get(); |
| 810 | if (future.isCancelled()) { |
| 811 | if (decoder != null) decoder.recycle(); |
| 812 | return; |
| 813 | } |
| 814 | mMainHandler.sendMessage(mMainHandler.obtainMessage( |
| 815 | MSG_LARGE_BITMAP, decoder)); |
| 816 | } |
| 817 | }); |
| 818 | } else { |
| 819 | mLoadBitmapTask = getThreadPool().submit(new LoadBitmapDataTask(mMediaItem), |
| 820 | new FutureListener<Bitmap>() { |
| 821 | public void onFutureDone(Future<Bitmap> future) { |
| 822 | mLoadBitmapTask = null; |
| 823 | Bitmap bitmap = future.get(); |
| 824 | if (future.isCancelled()) { |
| 825 | if (bitmap != null) bitmap.recycle(); |
| 826 | return; |
| 827 | } |
| 828 | mMainHandler.sendMessage(mMainHandler.obtainMessage( |
| 829 | MSG_BITMAP, bitmap)); |
| 830 | } |
| 831 | }); |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | @Override |
| 836 | protected void onResume() { |
| 837 | super.onResume(); |
| 838 | if (mState == STATE_INIT) initializeData(); |
| 839 | if (mState == STATE_SAVING) onSaveClicked(); |
| 840 | |
| 841 | // TODO: consider to do it in GLView system |
| 842 | GLRoot root = getGLRoot(); |
| 843 | root.lockRenderThread(); |
| 844 | try { |
| 845 | mCropView.resume(); |
| 846 | } finally { |
| 847 | root.unlockRenderThread(); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | @Override |
| 852 | protected void onPause() { |
| 853 | super.onPause(); |
| 854 | |
| 855 | Future<BitmapRegionDecoder> loadTask = mLoadTask; |
| 856 | if (loadTask != null && !loadTask.isDone()) { |
| 857 | // load in progress, try to cancel it |
| 858 | loadTask.cancel(); |
| 859 | loadTask.waitDone(); |
| 860 | mProgressDialog.dismiss(); |
| 861 | } |
| 862 | |
| 863 | Future<Bitmap> loadBitmapTask = mLoadBitmapTask; |
| 864 | if (loadBitmapTask != null && !loadBitmapTask.isDone()) { |
| 865 | // load in progress, try to cancel it |
| 866 | loadBitmapTask.cancel(); |
| 867 | loadBitmapTask.waitDone(); |
| 868 | mProgressDialog.dismiss(); |
| 869 | } |
| 870 | |
| 871 | Future<Intent> saveTask = mSaveTask; |
| 872 | if (saveTask != null && !saveTask.isDone()) { |
| 873 | // save in progress, try to cancel it |
| 874 | saveTask.cancel(); |
| 875 | saveTask.waitDone(); |
| 876 | mProgressDialog.dismiss(); |
| 877 | } |
| 878 | GLRoot root = getGLRoot(); |
| 879 | root.lockRenderThread(); |
| 880 | try { |
| 881 | mCropView.pause(); |
| 882 | } finally { |
| 883 | root.unlockRenderThread(); |
| 884 | } |
| 885 | } |
| 886 | |
| 887 | private MediaItem getMediaItemFromIntentData() { |
| 888 | Uri uri = getIntent().getData(); |
| 889 | DataManager manager = getDataManager(); |
| 890 | if (uri == null) { |
| 891 | Log.w(TAG, "no data given"); |
| 892 | return null; |
| 893 | } |
| 894 | Path path = manager.findPathByUri(uri); |
| 895 | if (path == null) { |
| 896 | Log.w(TAG, "cannot get path for: " + uri); |
| 897 | return null; |
| 898 | } |
| 899 | return (MediaItem) manager.getMediaObject(path); |
| 900 | } |
| 901 | |
| 902 | private class LoadDataTask implements Job<BitmapRegionDecoder> { |
| 903 | MediaItem mItem; |
| 904 | |
| 905 | public LoadDataTask(MediaItem item) { |
| 906 | mItem = item; |
| 907 | } |
| 908 | |
| 909 | public BitmapRegionDecoder run(JobContext jc) { |
| 910 | return mItem == null ? null : mItem.requestLargeImage().run(jc); |
| 911 | } |
| 912 | } |
| 913 | |
| 914 | private class LoadBitmapDataTask implements Job<Bitmap> { |
| 915 | MediaItem mItem; |
| 916 | |
| 917 | public LoadBitmapDataTask(MediaItem item) { |
| 918 | mItem = item; |
| 919 | } |
| 920 | public Bitmap run(JobContext jc) { |
| 921 | return mItem == null |
| 922 | ? null |
| 923 | : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc); |
| 924 | } |
| 925 | } |
Owen Lin | 3190f69 | 2011-09-02 21:15:42 +0800 | [diff] [blame] | 926 | |
| 927 | private static final String[] EXIF_TAGS = { |
| 928 | ExifInterface.TAG_DATETIME, |
| 929 | ExifInterface.TAG_MAKE, |
| 930 | ExifInterface.TAG_MODEL, |
| 931 | ExifInterface.TAG_FLASH, |
| 932 | ExifInterface.TAG_GPS_LATITUDE, |
| 933 | ExifInterface.TAG_GPS_LONGITUDE, |
| 934 | ExifInterface.TAG_GPS_LATITUDE_REF, |
| 935 | ExifInterface.TAG_GPS_LONGITUDE_REF, |
| 936 | ExifInterface.TAG_GPS_ALTITUDE, |
| 937 | ExifInterface.TAG_GPS_ALTITUDE_REF, |
| 938 | ExifInterface.TAG_GPS_TIMESTAMP, |
| 939 | ExifInterface.TAG_GPS_DATESTAMP, |
| 940 | ExifInterface.TAG_WHITE_BALANCE, |
| 941 | ExifInterface.TAG_FOCAL_LENGTH, |
| 942 | ExifInterface.TAG_GPS_PROCESSING_METHOD}; |
| 943 | |
| 944 | private static void copyExif(MediaItem item, String destination, int newWidth, int newHeight) { |
| 945 | try { |
| 946 | ExifInterface newExif = new ExifInterface(destination); |
| 947 | PicasaSource.extractExifValues(item, newExif); |
| 948 | newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(newWidth)); |
| 949 | newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(newHeight)); |
| 950 | newExif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(0)); |
| 951 | newExif.saveAttributes(); |
| 952 | } catch (Throwable t) { |
| 953 | Log.w(TAG, "cannot copy exif: " + item, t); |
| 954 | } |
| 955 | } |
| 956 | |
| 957 | private static void copyExif(String source, String destination, int newWidth, int newHeight) { |
| 958 | try { |
| 959 | ExifInterface oldExif = new ExifInterface(source); |
| 960 | ExifInterface newExif = new ExifInterface(destination); |
| 961 | |
| 962 | newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(newWidth)); |
| 963 | newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(newHeight)); |
| 964 | newExif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(0)); |
| 965 | |
| 966 | for (String tag : EXIF_TAGS) { |
| 967 | String value = oldExif.getAttribute(tag); |
| 968 | if (value != null) { |
| 969 | newExif.setAttribute(tag, value); |
| 970 | } |
| 971 | } |
| 972 | |
| 973 | // Handle some special values here |
| 974 | String value = oldExif.getAttribute(ExifInterface.TAG_APERTURE); |
| 975 | if (value != null) { |
| 976 | try { |
| 977 | float aperture = Float.parseFloat(value); |
| 978 | newExif.setAttribute(ExifInterface.TAG_APERTURE, |
| 979 | String.valueOf((int) (aperture * 10 + 0.5f)) + "/10"); |
| 980 | } catch (NumberFormatException e) { |
| 981 | Log.w(TAG, "cannot parse aperture: " + value); |
| 982 | } |
| 983 | } |
| 984 | |
| 985 | // TODO: The code is broken, need to fix the JHEAD lib |
| 986 | /* |
| 987 | value = oldExif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); |
| 988 | if (value != null) { |
| 989 | try { |
| 990 | double exposure = Double.parseDouble(value); |
| 991 | testToRational("test exposure", exposure); |
| 992 | newExif.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, value); |
| 993 | } catch (NumberFormatException e) { |
| 994 | Log.w(TAG, "cannot parse exposure time: " + value); |
| 995 | } |
| 996 | } |
| 997 | |
| 998 | value = oldExif.getAttribute(ExifInterface.TAG_ISO); |
| 999 | if (value != null) { |
| 1000 | try { |
| 1001 | int iso = Integer.parseInt(value); |
| 1002 | newExif.setAttribute(ExifInterface.TAG_ISO, String.valueOf(iso) + "/1"); |
| 1003 | } catch (NumberFormatException e) { |
| 1004 | Log.w(TAG, "cannot parse exposure time: " + value); |
| 1005 | } |
| 1006 | }*/ |
| 1007 | newExif.saveAttributes(); |
| 1008 | } catch (Throwable t) { |
| 1009 | Log.w(TAG, "cannot copy exif: " + source, t); |
| 1010 | } |
| 1011 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1012 | } |