mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-27 04:42:51 +08:00
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.
This commit is contained in:
parent
483e5dfbea
commit
e0eadb6b76
@ -182,7 +182,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
|
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
|
||||||
|
|
||||||
// Specify file so that large image is captured and returned
|
// Specify file so that large image is captured and returned
|
||||||
// TODO: What if there isn't any external storage?
|
|
||||||
File photo = createCaptureFile(encodingType);
|
File photo = createCaptureFile(encodingType);
|
||||||
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
|
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
|
||||||
this.imageUri = Uri.fromFile(photo);
|
this.imageUri = Uri.fromFile(photo);
|
||||||
@ -245,53 +244,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.
|
* Called when the camera view exits.
|
||||||
*
|
*
|
||||||
@ -326,7 +278,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
|
|
||||||
// If sending base64 image back
|
// If sending base64 image back
|
||||||
if (destType == DATA_URL) {
|
if (destType == DATA_URL) {
|
||||||
bitmap = scaleBitmap(getBitmapFromResult(intent));
|
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||||
|
|
||||||
this.processPicture(bitmap);
|
this.processPicture(bitmap);
|
||||||
checkForDuplicateImage(DATA_URL);
|
checkForDuplicateImage(DATA_URL);
|
||||||
@ -338,42 +290,20 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
if (!this.saveToPhotoAlbum) {
|
if (!this.saveToPhotoAlbum) {
|
||||||
uri = Uri.fromFile(new File("/data/data/" + this.cordova.getActivity().getPackageName() + "/", (new File(FileUtils.stripFileProtocol(this.imageUri.toString()))).getName()));
|
uri = Uri.fromFile(new File("/data/data/" + this.cordova.getActivity().getPackageName() + "/", (new File(FileUtils.stripFileProtocol(this.imageUri.toString()))).getName()));
|
||||||
} else {
|
} else {
|
||||||
// Create entry in media store for image
|
uri = getUriFromMediaStore();
|
||||||
// (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");
|
|
||||||
|
|
||||||
try {
|
if (uri == null) {
|
||||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
this.failPicture("Error capturing image - no media storage found.");
|
||||||
} 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 all this is true we shouldn't compress the image.
|
// If all this is true we shouldn't compress the image.
|
||||||
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
|
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
|
||||||
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
|
writeUncompressedImage(uri);
|
||||||
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();
|
|
||||||
|
|
||||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||||
} else {
|
} else {
|
||||||
|
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||||
bitmap = scaleBitmap(getBitmapFromResult(intent));
|
|
||||||
|
|
||||||
// Add compressed version of captured image to returned media store Uri
|
// Add compressed version of captured image to returned media store Uri
|
||||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(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);
|
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If sending base64 image back
|
|
||||||
|
|
||||||
// Get the path to the image. Makes loading so much easier.
|
// Get the path to the image. Makes loading so much easier.
|
||||||
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
|
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
|
||||||
|
Bitmap bitmap = getScaledBitmap(imagePath);
|
||||||
|
|
||||||
|
// If sending base64 image back
|
||||||
if (destType == DATA_URL) {
|
if (destType == DATA_URL) {
|
||||||
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
|
|
||||||
String[] cols = { MediaStore.Images.Media.ORIENTATION };
|
String[] cols = { MediaStore.Images.Media.ORIENTATION };
|
||||||
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
|
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
|
||||||
cols,
|
cols,
|
||||||
@ -455,11 +386,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
matrix.setRotate(rotate);
|
matrix.setRotate(rotate);
|
||||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||||
}
|
}
|
||||||
bitmap = scaleBitmap(bitmap);
|
|
||||||
this.processPicture(bitmap);
|
this.processPicture(bitmap);
|
||||||
bitmap.recycle();
|
|
||||||
bitmap = null;
|
|
||||||
System.gc();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If sending filename back
|
// If sending filename back
|
||||||
@ -467,8 +394,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
// Do we need to scale the returned file
|
// Do we need to scale the returned file
|
||||||
if (this.targetHeight > 0 && this.targetWidth > 0) {
|
if (this.targetHeight > 0 && this.targetWidth > 0) {
|
||||||
try {
|
try {
|
||||||
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
|
|
||||||
bitmap = scaleBitmap(bitmap);
|
|
||||||
|
|
||||||
String fileName = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
|
String fileName = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
|
||||||
OutputStream os = new FileOutputStream(fileName);
|
OutputStream os = new FileOutputStream(fileName);
|
||||||
@ -481,13 +406,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
exif.writeExifData();
|
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
|
// 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.
|
// 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);
|
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
|
||||||
System.gc();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
this.failPicture("Error retrieving image.");
|
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);
|
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bitmap.recycle();
|
||||||
|
bitmap = null;
|
||||||
|
System.gc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||||
@ -508,22 +432,88 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getBitmapFromResult(Intent intent)
|
/**
|
||||||
throws IOException {
|
* In the special case where the default width, height and quality are unchanged
|
||||||
Bitmap bitmap = null;
|
* we just write the file out to disk saving the expensive Bitmap.compress function.
|
||||||
//try {
|
*
|
||||||
Log.d(LOG_TAG, "Image URI = " + imageUri.toString());
|
* @param uri
|
||||||
String fileName = FileUtils.stripFileProtocol(imageUri.toString());
|
* @throws FileNotFoundException
|
||||||
bitmap = BitmapFactory.decodeFile(fileName);
|
* @throws IOException
|
||||||
//bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri);
|
*/
|
||||||
//} catch (FileNotFoundException e) {
|
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
|
||||||
// Uri uri = intent.getData();
|
IOException {
|
||||||
// android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver();
|
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||||
// bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
|
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||||
//}
|
byte[] buffer = new byte[4096];
|
||||||
return bitmap;
|
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.
|
* Creates a cursor that can be used to determine how many images we have.
|
||||||
*
|
*
|
||||||
@ -542,7 +532,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
|||||||
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
|
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
|
||||||
*/
|
*/
|
||||||
private void cleanup(int imageType, Uri oldImage, Bitmap bitmap) {
|
private void cleanup(int imageType, Uri oldImage, Bitmap bitmap) {
|
||||||
bitmap.recycle();
|
if (bitmap != null) {
|
||||||
|
bitmap.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up initial camera-written image file.
|
// Clean up initial camera-written image file.
|
||||||
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();
|
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();
|
||||||
|
Loading…
Reference in New Issue
Block a user