package info.protonet.imageresizer; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; import android.util.Base64; import android.util.Log; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import org.apache.cordova.camera.FileHelper; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class ImageResizer extends CordovaPlugin { private static final int ARGUMENT_NUMBER = 1; public CallbackContext callbackContext; private String uri; private String folderName; private String fileName; private int quality; private int width; private int height; private boolean base64 = false; private boolean fit = false; private boolean fixRotation = false; public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { try { this.callbackContext = callbackContext; boolean isFileUri = false; if (action.equals("resize")) { checkParameters(args); // get the arguments JSONObject jsonObject = args.getJSONObject(0); uri = jsonObject.getString("uri"); isFileUri = !uri.startsWith("data") ? true : false; folderName = null; if (jsonObject.has("folderName")) { folderName = jsonObject.getString("folderName"); } fileName = null; if (jsonObject.has("fileName")) { fileName = jsonObject.getString("fileName"); } quality = jsonObject.optInt("quality", 85); width = jsonObject.getInt("width"); height = jsonObject.getInt("height"); base64 = jsonObject.optBoolean("base64", false); fit = jsonObject.optBoolean("fit", false); fixRotation = jsonObject.optBoolean("fixRotation",false); Bitmap bitmap; // load the image from uri if (isFileUri) { bitmap = loadScaledBitmapFromUri(uri, width, height); } else { bitmap = this.loadBase64ScaledBitmapFromUri(uri, width, height, fit); } if(fixRotation){ // Get the exif rotation in degrees, create a transformation matrix, and rotate // the bitmap int rotation = getRoationDegrees(getRotation(uri)); Matrix matrix = new Matrix(); if (rotation != 0f) {matrix.preRotate(rotation);} bitmap = Bitmap.createBitmap( bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } if(bitmap == null){ Log.e("Protonet", "There was an error reading the image"); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); return false; } String response; // save the image as jpeg on the device if (!base64) { Uri scaledFile = saveFile(bitmap); response = scaledFile.toString(); if(scaledFile == null){ Log.e("Protonet", "There was an error saving the thumbnail"); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); return false; } } else { response = "data:image/jpeg;base64," + this.getStringImage(bitmap, quality); } bitmap = null; callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, response)); return true; } else { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); return false; } } catch (JSONException e) { Log.e("Protonet", "JSON Exception during the Image Resizer Plugin... :("); } callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); return false; } public String getStringImage(Bitmap bmp, int quality) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); byte[] imageBytes = baos.toByteArray(); String encodedImage = Base64.encodeToString(imageBytes, Base64.NO_WRAP); return encodedImage; } private Bitmap loadBase64ScaledBitmapFromUri(String uriString, int width, int height, boolean fit) { try { String pureBase64Encoded = uriString.substring(uriString.indexOf(",") + 1); byte[] decodedBytes = Base64.decode(pureBase64Encoded, Base64.DEFAULT); Bitmap decodedBitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length); int sourceWidth = decodedBitmap.getWidth(); int sourceHeight = decodedBitmap.getHeight(); float ratio = sourceWidth > sourceHeight ? ((float) width / sourceWidth) : ((float) height / sourceHeight); int execWidth = width; int execHeigth = height; if (fit) { execWidth = Math.round(ratio * sourceWidth); execHeigth = Math.round(ratio * sourceHeight); } Bitmap scaled = Bitmap.createScaledBitmap(decodedBitmap, execWidth, execHeigth, true); decodedBytes = null; decodedBitmap = null; return scaled; } catch (Exception e) { Log.e("Protonet", e.toString()); } return null; } /** * Gets the image rotation from the image EXIF Data * * @param exifOrientation ExifInterface.ORIENTATION_* representation of the rotation * @return the rotation in degrees */ private int getRoationDegrees(int exifOrientation){ if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { return 90; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { return 180; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { return 270; } return 0; } /** * Gets the image rotation from the image EXIF Data * * @param uriString the URI of the image to get the rotation for * @return ExifInterface.ORIENTATION_* representation of the rotation */ private int getRotation(String uriString){ try { ExifInterface exif = new ExifInterface(uriString); return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); } catch (IOException e) { return ExifInterface.ORIENTATION_NORMAL; } } /** * Loads a Bitmap of the given android uri path * * @params uri the URI who points to the image **/ private Bitmap loadScaledBitmapFromUri(String uriString, int width, int height) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(uriString, cordova), null, options); //calc aspect ratio int[] retval = calculateAspectRatio(options.outWidth, options.outHeight); options.inJustDecodeBounds = false; options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, width, height); Bitmap unscaledBitmap = BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(uriString, cordova), null, options); return Bitmap.createScaledBitmap(unscaledBitmap, retval[0], retval[1], true); } catch (FileNotFoundException e) { Log.e("Protonet", "File not found. :("); } catch (IOException e) { Log.e("Protonet", "IO Exception :("); } catch (Exception e) { Log.e("Protonet", e.toString()); } return null; } private Uri saveFile(Bitmap bitmap) { File folder = null; if (folderName == null) { folder = new File(this.getTempDirectoryPath()); } else { if (folderName.contains("/")) { folder = new File(folderName.replace("file://", "")); } else { Context context = this.cordova.getActivity().getApplicationContext(); folder = context.getDir(folderName, context.MODE_PRIVATE); } } boolean success = true; if (!folder.exists()) { success = folder.mkdir(); } if (success) { if (fileName == null) { fileName = System.currentTimeMillis() + ".jpg"; } File file = new File(folder, fileName); if (file.exists()) file.delete(); try { FileOutputStream out = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out); out.flush(); out.close(); } catch (Exception e) { Log.e("Protonet", e.toString()); } return Uri.fromFile(file); } return null; } /** * Figure out what ratio we can load our image into memory at while still being bigger than * our desired width and height * * @param srcWidth * @param srcHeight * @param dstWidth * @param dstHeight * @return */ private 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; } } /** * Maintain the aspect ratio so the resulting image does not look smooshed * * @param origWidth * @param origHeight * @return */ private int[] calculateAspectRatio(int origWidth, int origHeight) { int newWidth = width; int newHeight = height; // If no new width or height were specified return the original bitmap if (newWidth <= 0 && newHeight <= 0) { newWidth = origWidth; newHeight = origHeight; } // 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; } } int[] retval = new int[2]; retval[0] = newWidth; retval[1] = newHeight; return retval; } private boolean checkParameters(JSONArray args) { if (args.length() != ARGUMENT_NUMBER) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION)); return false; } return true; } private String getTempDirectoryPath() { File cache = null; // SD Card Mounted if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/"); } else { // Use internal storage cache = cordova.getActivity().getCacheDir(); } // Create the cache directory if it doesn't exist cache.mkdirs(); return cache.getAbsolutePath(); } }