CropActivity.java revision 6fe165b7d28299d5b2f97deb135b233d84eb300f
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 17package com.android.gallery3d.filtershow.crop; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.app.WallpaperManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.graphics.Bitmap; 26import android.graphics.Bitmap.CompressFormat; 27import android.graphics.BitmapFactory; 28import android.graphics.Point; 29import android.graphics.Rect; 30import android.graphics.RectF; 31import android.net.Uri; 32import android.os.AsyncTask; 33import android.os.Bundle; 34import android.provider.MediaStore; 35import android.util.DisplayMetrics; 36import android.util.Log; 37import android.util.TypedValue; 38import android.view.Display; 39import android.view.View; 40import android.view.View.OnClickListener; 41import android.view.WindowManager; 42import android.widget.Toast; 43 44import com.android.gallery3d.R; 45 46import java.io.FileNotFoundException; 47import java.io.IOException; 48import java.io.OutputStream; 49 50/** 51 * Activity for cropping an image. 52 */ 53public class CropActivity extends Activity { 54 private static final String LOGTAG = "CropActivity"; 55 private CropExtras mCropExtras = null; 56 private LoadBitmapTask mLoadBitmapTask = null; 57 private SaveBitmapTask mSaveBitmapTask = null; 58 private SetWallpaperTask mSetWallpaperTask = null; 59 private Bitmap mOriginalBitmap = null; 60 private CropView mCropView = null; 61 private int mActiveBackgroundIO = 0; 62 private Intent mResultIntent = null; 63 private static final int SELECT_PICTURE = 1; // request code for picker 64 private static final int DEFAULT_DENSITY = 133; 65 private static final int DEFAULT_COMPRESS_QUALITY = 90; 66 public static final int MAX_BMAP_IN_INTENT = 990000; 67 68 @Override 69 public void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 Intent intent = getIntent(); 72 mResultIntent = new Intent(); 73 setResult(RESULT_CANCELED, mResultIntent); 74 mCropExtras = getExtrasFromIntent(intent); 75 if (mCropExtras != null && mCropExtras.getShowWhenLocked()) { 76 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 77 } 78 79 setContentView(R.layout.crop_activity); 80 mCropView = (CropView) findViewById(R.id.cropView); 81 82 if (intent.getData() != null) { 83 startLoadBitmap(intent.getData()); 84 } else { 85 pickImage(); 86 } 87 ActionBar actionBar = getActionBar(); 88 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); 89 actionBar.setCustomView(R.layout.filtershow_actionbar); 90 91 View saveButton = actionBar.getCustomView(); 92 saveButton.setOnClickListener(new OnClickListener() { 93 @Override 94 public void onClick(View view) { 95 startFinishOutput(); 96 } 97 }); 98 } 99 100 @Override 101 protected void onDestroy() { 102 if (mLoadBitmapTask != null) { 103 mLoadBitmapTask.cancel(false); 104 } 105 super.onDestroy(); 106 } 107 108 /** 109 * Opens a selector in Gallery to chose an image for use when none was given 110 * in the CROP intent. 111 */ 112 public void pickImage() { 113 Intent intent = new Intent(); 114 intent.setType("image/*"); 115 intent.setAction(Intent.ACTION_GET_CONTENT); 116 startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)), 117 SELECT_PICTURE); 118 } 119 120 /** 121 * Callback for pickImage(). 122 */ 123 @Override 124 public void onActivityResult(int requestCode, int resultCode, Intent data) { 125 if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) { 126 Uri selectedImageUri = data.getData(); 127 startLoadBitmap(selectedImageUri); 128 } 129 } 130 131 /** 132 * Gets the crop extras from the intent, or null if none exist. 133 */ 134 public static CropExtras getExtrasFromIntent(Intent intent) { 135 Bundle extras = intent.getExtras(); 136 if (extras != null) { 137 return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0), 138 extras.getInt(CropExtras.KEY_OUTPUT_Y, 0), 139 extras.getBoolean(CropExtras.KEY_SCALE, true) && 140 extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false), 141 extras.getInt(CropExtras.KEY_ASPECT_X, 0), 142 extras.getInt(CropExtras.KEY_ASPECT_Y, 0), 143 extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false), 144 extras.getBoolean(CropExtras.KEY_RETURN_DATA, false), 145 (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT), 146 extras.getString(CropExtras.KEY_OUTPUT_FORMAT), 147 extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false), 148 extras.getFloat(CropExtras.KEY_SPOTLIGHT_X), 149 extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y)); 150 } 151 return null; 152 } 153 154 /** 155 * Gets screen size metric. 156 */ 157 private int getScreenImageSize() { 158 DisplayMetrics metrics = new DisplayMetrics(); 159 Display display = getWindowManager().getDefaultDisplay(); 160 Point size = new Point(); 161 display.getSize(size); 162 display.getMetrics(metrics); 163 int msize = Math.min(size.x, size.y); 164 // TODO: WTF 165 return (DEFAULT_DENSITY * msize) / metrics.densityDpi + 512; 166 } 167 168 /** 169 * Method that loads a bitmap in an async task. 170 */ 171 private void startLoadBitmap(Uri uri) { 172 mActiveBackgroundIO++; 173 final View loading = findViewById(R.id.loading); 174 loading.setVisibility(View.VISIBLE); 175 mLoadBitmapTask = new LoadBitmapTask(); 176 mLoadBitmapTask.execute(uri); 177 } 178 179 /** 180 * Method called on UI thread with loaded bitmap. 181 */ 182 private void doneLoadBitmap(Bitmap bitmap) { 183 mActiveBackgroundIO--; 184 final View loading = findViewById(R.id.loading); 185 loading.setVisibility(View.GONE); 186 mOriginalBitmap = bitmap; 187 // TODO: move these to dimens folder 188 if (bitmap != null) { 189 mCropView.setup(bitmap, (int) getPixelsFromDip(55), (int) getPixelsFromDip(25)); 190 } else { 191 Log.w(LOGTAG, "could not load image for cropping"); 192 cannotLoadImage(); 193 setResult(RESULT_CANCELED, mResultIntent); 194 done(); 195 } 196 } 197 198 /** 199 * Display toast for image loading failure. 200 */ 201 private void cannotLoadImage() { 202 CharSequence text = getString(R.string.cannot_load_image); 203 Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT); 204 toast.show(); 205 } 206 207 /** 208 * AsyncTask for loading a bitmap into memory. 209 * 210 * @see #startLoadBitmap(Uri) 211 * @see #doneLoadBitmap(Bitmap) 212 */ 213 private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> { 214 int mBitmapSize; 215 Context mContext; 216 Rect mOriginalBounds; 217 218 public LoadBitmapTask() { 219 mBitmapSize = getScreenImageSize(); 220 Log.v(LOGTAG, "bitmap size: " + mBitmapSize); 221 mContext = getApplicationContext(); 222 mOriginalBounds = new Rect(); 223 } 224 225 @Override 226 protected Bitmap doInBackground(Uri... params) { 227 Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize, 228 mOriginalBounds); 229 return bmap; 230 } 231 232 @Override 233 protected void onPostExecute(Bitmap result) { 234 doneLoadBitmap(result); 235 // super.onPostExecute(result); 236 } 237 } 238 239 private void startSaveBitmap(Bitmap bmap, Uri uri, String format) { 240 if (bmap == null || uri == null) { 241 throw new IllegalArgumentException("bad argument to startSaveBitmap"); 242 } 243 mActiveBackgroundIO++; 244 final View loading = findViewById(R.id.loading); 245 loading.setVisibility(View.VISIBLE); 246 mSaveBitmapTask = new SaveBitmapTask(uri, format); 247 mSaveBitmapTask.execute(bmap); 248 } 249 250 private void doneSaveBitmap(Uri uri) { 251 mActiveBackgroundIO--; 252 final View loading = findViewById(R.id.loading); 253 loading.setVisibility(View.GONE); 254 if (uri == null) { 255 Log.w(LOGTAG, "failed to save bitmap"); 256 setResult(RESULT_CANCELED, mResultIntent); 257 done(); 258 return; 259 } 260 done(); 261 } 262 263 private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> { 264 265 OutputStream mOutStream = null; 266 String mOutputFormat = null; 267 Uri mOutUri = null; 268 269 public SaveBitmapTask(Uri uri, String outputFormat) { 270 mOutputFormat = outputFormat; 271 mOutStream = null; 272 mOutUri = uri; 273 try { 274 mOutStream = getContentResolver().openOutputStream(uri); 275 } catch (FileNotFoundException e) { 276 Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e); 277 } 278 } 279 280 @Override 281 protected Boolean doInBackground(Bitmap... params) { 282 if (mOutStream == null) { 283 return false; 284 } 285 CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); 286 return params[0].compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream); 287 } 288 289 @Override 290 protected void onPostExecute(Boolean result) { 291 if (result.booleanValue() == false) { 292 Log.w(LOGTAG, "could not compress to output: " + mOutUri.toString()); 293 doneSaveBitmap(null); 294 } 295 doneSaveBitmap(mOutUri); 296 } 297 } 298 299 private void startSetWallpaper(Bitmap bmap) { 300 if (bmap == null) { 301 throw new IllegalArgumentException("bad argument to startSetWallpaper"); 302 } 303 mActiveBackgroundIO++; 304 Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show(); 305 mSetWallpaperTask = new SetWallpaperTask(); 306 mSetWallpaperTask.execute(bmap); 307 308 } 309 310 private void doneSetWallpaper() { 311 mActiveBackgroundIO--; 312 done(); 313 } 314 315 private class SetWallpaperTask extends AsyncTask<Bitmap, Void, Boolean> { 316 private final WallpaperManager mWPManager; 317 318 public SetWallpaperTask() { 319 mWPManager = WallpaperManager.getInstance(getApplicationContext()); 320 } 321 322 @Override 323 protected Boolean doInBackground(Bitmap... params) { 324 try { 325 mWPManager.setBitmap(params[0]); 326 } catch (IOException e) { 327 Log.w(LOGTAG, "fail to set wall paper", e); 328 } 329 return true; 330 } 331 332 @Override 333 protected void onPostExecute(Boolean result) { 334 doneSetWallpaper(); 335 } 336 } 337 338 private void startFinishOutput() { 339 if (mOriginalBitmap != null && mCropExtras != null) { 340 Bitmap cropped = null; 341 if (mCropExtras.getExtraOutput() != null) { 342 if (cropped == null) { 343 cropped = getCroppedImage(mOriginalBitmap); 344 } 345 startSaveBitmap(cropped, mCropExtras.getExtraOutput(), 346 mCropExtras.getOutputFormat()); 347 } 348 if (mCropExtras.getSetAsWallpaper()) { 349 if (cropped == null) { 350 cropped = getCroppedImage(mOriginalBitmap); 351 } 352 startSetWallpaper(cropped); 353 } 354 if (mCropExtras.getReturnData()) { 355 if (cropped == null) { 356 cropped = getCroppedImage(mOriginalBitmap); 357 } 358 int bmapSize = cropped.getRowBytes() * cropped.getHeight(); 359 if (bmapSize > MAX_BMAP_IN_INTENT) { 360 Log.w(LOGTAG, "Bitmap too large to be returned via intent"); 361 } else { 362 mResultIntent.putExtra(CropExtras.KEY_DATA, cropped); 363 } 364 } 365 setResult(RESULT_OK, mResultIntent); 366 } else { 367 setResult(RESULT_CANCELED, mResultIntent); 368 } 369 done(); 370 } 371 372 private void done() { 373 if (mActiveBackgroundIO == 0) { 374 finish(); 375 } 376 } 377 378 private Bitmap getCroppedImage(Bitmap image) { 379 RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight()); 380 RectF crop = getBitmapCrop(imageBounds); 381 if (crop == null) { 382 return image; 383 } 384 Rect intCrop = new Rect(); 385 crop.roundOut(intCrop); 386 return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(), 387 intCrop.height()); 388 } 389 390 private RectF getBitmapCrop(RectF imageBounds) { 391 RectF crop = new RectF(); 392 if (!mCropView.getCropBounds(crop, imageBounds)) { 393 Log.w(LOGTAG, "could not get crop"); 394 return null; 395 } 396 return crop; 397 } 398 399 /** 400 * Helper method for unit conversions. 401 */ 402 public float getPixelsFromDip(float value) { 403 Resources r = getResources(); 404 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, 405 r.getDisplayMetrics()); 406 } 407 408 private static CompressFormat convertExtensionToCompressFormat(String extension) { 409 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 410 } 411 412 private static String getFileExtension(String requestFormat) { 413 String outputFormat = (requestFormat == null) 414 ? "jpg" 415 : requestFormat; 416 outputFormat = outputFormat.toLowerCase(); 417 return (outputFormat.equals("png") || outputFormat.equals("gif")) 418 ? "png" // We don't support gif compression. 419 : "jpg"; 420 } 421 422} 423