You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ma...@apache.org on 2012/06/28 18:33:45 UTC
[2/8] android commit: Using a better scaling algorithm to resize the
image
Using a better scaling algorithm to resize the image
Instead of reading the entire image into a bitmap then scaling we use the
inSampleSize option to get a close to the target width and height as possible
then we scale that smaller image.
Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/commit/e0eadb6b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/tree/e0eadb6b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/diff/e0eadb6b
Branch: refs/heads/master
Commit: e0eadb6b76adb411e2ca30b37e8d4fde81a78b75
Parents: 483e5df
Author: macdonst <si...@gmail.com>
Authored: Tue Jun 26 22:13:39 2012 -0400
Committer: macdonst <si...@gmail.com>
Committed: Thu Jun 28 12:00:19 2012 -0400
----------------------------------------------------------------------
.../src/org/apache/cordova/CameraLauncher.java | 200 +++++++--------
1 files changed, 96 insertions(+), 104 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/e0eadb6b/framework/src/org/apache/cordova/CameraLauncher.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java
index 2ba5a39..04cd1c0 100755
--- a/framework/src/org/apache/cordova/CameraLauncher.java
+++ b/framework/src/org/apache/cordova/CameraLauncher.java
@@ -182,7 +182,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
// Specify file so that large image is captured and returned
- // TODO: What if there isn't any external storage?
File photo = createCaptureFile(encodingType);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
@@ -246,53 +245,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
/**
- * Scales the bitmap according to the requested size.
- *
- * @param bitmap The bitmap to scale.
- * @return Bitmap A new Bitmap object of the same bitmap after scaling.
- */
- public Bitmap scaleBitmap(Bitmap bitmap) {
- int newWidth = this.targetWidth;
- int newHeight = this.targetHeight;
- int origWidth = bitmap.getWidth();
- int origHeight = bitmap.getHeight();
-
- // If no new width or height were specified return the original bitmap
- if (newWidth <= 0 && newHeight <= 0) {
- return bitmap;
- }
- // Only the width was specified
- else if (newWidth > 0 && newHeight <= 0) {
- newHeight = (newWidth * origHeight) / origWidth;
- }
- // only the height was specified
- else if (newWidth <= 0 && newHeight > 0) {
- newWidth = (newHeight * origWidth) / origHeight;
- }
- // If the user specified both a positive width and height
- // (potentially different aspect ratio) then the width or height is
- // scaled so that the image fits while maintaining aspect ratio.
- // Alternatively, the specified width and height could have been
- // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
- // would result in whitespace in the new image.
- else {
- double newRatio = newWidth / (double) newHeight;
- double origRatio = origWidth / (double) origHeight;
-
- if (origRatio > newRatio) {
- newHeight = (newWidth * origHeight) / origWidth;
- } else if (origRatio < newRatio) {
- newWidth = (newHeight * origWidth) / origHeight;
- }
- }
-
- Bitmap retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
- bitmap.recycle();
- System.gc();
- return retval;
- }
-
- /**
* Called when the camera view exits.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
@@ -326,7 +278,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// If sending base64 image back
if (destType == DATA_URL) {
- bitmap = scaleBitmap(getBitmapFromResult(intent));
+ bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
this.processPicture(bitmap);
checkForDuplicateImage(DATA_URL);
@@ -338,42 +290,20 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
if (!this.saveToPhotoAlbum) {
uri = Uri.fromFile(new File("/data/data/" + this.cordova.getActivity().getPackageName() + "/", (new File(FileUtils.stripFileProtocol(this.imageUri.toString()))).getName()));
} else {
- // Create entry in media store for image
- // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
- ContentValues values = new ContentValues();
- values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ uri = getUriFromMediaStore();
+ }
- try {
- uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
- } catch (UnsupportedOperationException e) {
- LOG.d(LOG_TAG, "Can't write to external media storage.");
- try {
- uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
- } catch (UnsupportedOperationException ex) {
- LOG.d(LOG_TAG, "Can't write to internal media storage.");
- this.failPicture("Error capturing image - no media storage found.");
- return;
- }
- }
+ if (uri == null) {
+ this.failPicture("Error capturing image - no media storage found.");
}
// If all this is true we shouldn't compress the image.
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
- FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
- OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
- byte[] buffer = new byte[4096];
- int len;
- while ((len = fis.read(buffer)) != -1) {
- os.write(buffer, 0, len);
- }
- os.flush();
- os.close();
- fis.close();
+ writeUncompressedImage(uri);
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
} else {
-
- bitmap = scaleBitmap(getBitmapFromResult(intent));
+ bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
@@ -435,12 +365,13 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
else {
- // If sending base64 image back
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
+ Bitmap bitmap = getScaledBitmap(imagePath);
+
+ // If sending base64 image back
if (destType == DATA_URL) {
- Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
String[] cols = { MediaStore.Images.Media.ORIENTATION };
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
cols,
@@ -455,11 +386,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
matrix.setRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
- bitmap = scaleBitmap(bitmap);
this.processPicture(bitmap);
- bitmap.recycle();
- bitmap = null;
- System.gc();
}
// If sending filename back
@@ -467,8 +394,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// Do we need to scale the returned file
if (this.targetHeight > 0 && this.targetWidth > 0) {
try {
- Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
- bitmap = scaleBitmap(bitmap);
String fileName = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
OutputStream os = new FileOutputStream(fileName);
@@ -481,13 +406,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
exif.writeExifData();
}
- bitmap.recycle();
- bitmap = null;
-
// The resized image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
- System.gc();
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
@@ -497,6 +418,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
}
+ bitmap.recycle();
+ bitmap = null;
+ System.gc();
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
@@ -508,23 +432,89 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
}
- private Bitmap getBitmapFromResult(Intent intent)
- throws IOException {
- Bitmap bitmap = null;
- //try {
- Log.d(LOG_TAG, "Image URI = " + imageUri.toString());
- String fileName = FileUtils.stripFileProtocol(imageUri.toString());
- bitmap = BitmapFactory.decodeFile(fileName);
- //bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri);
- //} catch (FileNotFoundException e) {
- // Uri uri = intent.getData();
- // android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver();
- // bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
- //}
- return bitmap;
+ /**
+ * In the special case where the default width, height and quality are unchanged
+ * we just write the file out to disk saving the expensive Bitmap.compress function.
+ *
+ * @param uri
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
+ IOException {
+ FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
+ OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ os.flush();
+ os.close();
+ fis.close();
+ }
+
+ /**
+ * Create entry in media store for image
+ *
+ * @return uri
+ */
+ private Uri getUriFromMediaStore() {
+ ContentValues values = new ContentValues();
+ values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ Uri uri;
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ } catch (UnsupportedOperationException e) {
+ LOG.d(LOG_TAG, "Can't write to external media storage.");
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
+ } catch (UnsupportedOperationException ex) {
+ LOG.d(LOG_TAG, "Can't write to internal media storage.");
+ return null;
+ }
+ }
+ return uri;
}
/**
+ * Return a scaled bitmap based on the target width and height
+ *
+ * @param imagePath
+ * @return
+ */
+ private Bitmap getScaledBitmap(String imagePath) {
+ // If no new width or height were specified return the original bitmap
+ if (this.targetWidth <= 0 && this.targetHeight <= 0) {
+ return BitmapFactory.decodeFile(imagePath);
+ }
+
+ Bitmap unscaledBitmap = decodeFile(imagePath,
+ this.targetWidth, this.targetHeight);
+ return Bitmap.createScaledBitmap(unscaledBitmap, this.targetWidth, this.targetHeight, true);
+ }
+
+ public static Bitmap decodeFile(String pathName, int dstWidth, int dstHeight) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(pathName, options);
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth, dstHeight);
+ return BitmapFactory.decodeFile(pathName, options);
+ }
+
+ public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
+ final float srcAspect = (float)srcWidth / (float)srcHeight;
+ final float dstAspect = (float)dstWidth / (float)dstHeight;
+
+ if (srcAspect > dstAspect) {
+ return srcWidth / dstWidth;
+ } else {
+ return srcHeight / dstHeight;
+ }
+ }
+
+ /**
* Creates a cursor that can be used to determine how many images we have.
*
* @return a cursor
@@ -542,7 +532,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
*/
private void cleanup(int imageType, Uri oldImage, Bitmap bitmap) {
- bitmap.recycle();
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
// Clean up initial camera-written image file.
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();