diff --git a/README.md b/README.md index ff005c1..440e3ab 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,6 @@ cordova-imagePicker Cordova Plugin For Multiple Image Selection - implemented for iOS and Android 4.0 and above. -For iOS this plugin uses the ELCImagePickerController, with slight modifications for the iOS image picker. ELCImagePicker uses the MIT License which can be found in the file LICENSE. - -https://github.com/B-Sides/ELCImagePickerController - -For Android this plugin uses MultiImageChooser, with modifications. MultiImageChooser uses the BSD 2-Clause License which can be found in the file BSD_LICENSE. Some code inside MultImageChooser is licensed under the Apache license which can be found in the file APACHE_LICENSE. - -https://github.com/derosa/MultiImageChooser - -Code(FakeR) was also taken from the phonegap BarCodeScanner plugin. This code uses the MIT license. - -https://github.com/wildabeast/BarcodeScanner - ## Installing the plugin The plugin conforms to the Cordova plugin specification, it can be installed @@ -81,6 +69,26 @@ window.imagePicker.getPictures( The plugin returns images that are stored in a temporary directory. These images will often not be deleted automatically though. The files should be moved or deleted after you get their filepaths in javascript. +## Libraries used + +#### ELCImagePicker + +For iOS this plugin uses the ELCImagePickerController, with slight modifications for the iOS image picker. ELCImagePicker uses the MIT License which can be found in the file LICENSE. + +https://github.com/B-Sides/ELCImagePickerController + +#### MultiImageChooser + +For Android this plugin uses MultiImageChooser, with modifications. MultiImageChooser uses the BSD 2-Clause License which can be found in the file BSD_LICENSE. Some code inside MultImageChooser is licensed under the Apache license which can be found in the file APACHE_LICENSE. + +https://github.com/derosa/MultiImageChooser + +#### FakeR + +Code(FakeR) was also taken from the phonegap BarCodeScanner plugin. This code uses the MIT license. + +https://github.com/wildabeast/BarcodeScanner + ## License The MIT License diff --git a/src/android/Library/src/ImageFetcher.java b/src/android/Library/src/ImageFetcher.java index a2540bf..f2adc9a 100644 --- a/src/android/Library/src/ImageFetcher.java +++ b/src/android/Library/src/ImageFetcher.java @@ -30,6 +30,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.Matrix; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; @@ -62,14 +63,14 @@ public class ImageFetcher { executor = Executors.newCachedThreadPool(); } - public void fetch(Integer id, ImageView imageView, int colWidth) { + public void fetch(Integer id, ImageView imageView, int colWidth, int rotate) { resetPurgeTimer(); this.colWidth = colWidth; this.origId = id; Bitmap bitmap = getBitmapFromCache(id); if (bitmap == null) { - forceDownload(id, imageView); + forceDownload(id, imageView, rotate); } else { cancelPotentialDownload(id, imageView); imageView.setImageBitmap(bitmap); @@ -80,14 +81,14 @@ public class ImageFetcher { * Same as download but the image is always downloaded and the cache is not * used. Kept private at the moment as its interest is not clear. */ - private void forceDownload(Integer position, ImageView imageView) { + private void forceDownload(Integer position, ImageView imageView, int rotate) { if (position == null) { imageView.setImageDrawable(null); return; } if (cancelPotentialDownload(position, imageView)) { - BitmapFetcherTask task = new BitmapFetcherTask(imageView.getContext(), imageView); + BitmapFetcherTask task = new BitmapFetcherTask(imageView.getContext(), imageView, rotate); DownloadedDrawable downloadedDrawable = new DownloadedDrawable(imageView.getContext(), task, origId); imageView.setImageDrawable(downloadedDrawable); imageView.setMinimumHeight(colWidth); @@ -164,10 +165,12 @@ public class ImageFetcher { private Integer position; private final WeakReference imageViewReference; private final Context mContext; + private final int rotate; - public BitmapFetcherTask(Context context, ImageView imageView) { + public BitmapFetcherTask(Context context, ImageView imageView, int rotate) { imageViewReference = new WeakReference(imageView); mContext = context; + this.rotate = rotate; } /** @@ -190,6 +193,11 @@ public class ImageFetcher { if (isCancelled()) { return null; } else { + if (rotate != 0) { + Matrix matrix = new Matrix(); + matrix.setRotate(rotate); + thumb = Bitmap.createBitmap(thumb, 0, 0, thumb.getWidth(), thumb.getHeight(), matrix, true); + } return thumb; } } diff --git a/src/android/Library/src/MultiImageChooserActivity.java b/src/android/Library/src/MultiImageChooserActivity.java index 15b3feb..e694460 100644 --- a/src/android/Library/src/MultiImageChooserActivity.java +++ b/src/android/Library/src/MultiImageChooserActivity.java @@ -36,8 +36,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.synconset.FakeR; @@ -77,13 +79,7 @@ import android.widget.TextView; public class MultiImageChooserActivity extends Activity implements OnItemClickListener, LoaderManager.LoaderCallbacks { - private static final String TAG = "Collage"; - public static final String COL_WIDTH_KEY = "COL_WIDTH"; - public static final String FLURRY_EVENT_ADD_MULTIPLE_IMAGES = "Add multiple images"; - - // El tamaƱo por defecto es 100 porque los thumbnails MICRO_KIND son de - // 96x96 - private static final int DEFAULT_COLUMN_WIDTH = 120; + private static final String TAG = "ImagePicker"; public static final int NOLIMIT = -1; public static final String MAX_IMAGES_KEY = "MAX_IMAGES"; @@ -94,13 +90,13 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi private ImageAdapter ia; private Cursor imagecursor, actualimagecursor; - private int image_column_index, actual_image_column_index; + private int image_column_index, image_column_orientation, actual_image_column_index, orientation_column_index; private int colWidth; private static final int CURSORLOADER_THUMBS = 0; private static final int CURSORLOADER_REAL = 1; - private Set fileNames = new HashSet(); + private Map fileNames = new HashMap(); private SparseBooleanArray checkStatus = new SparseBooleanArray(); @@ -115,7 +111,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi private final ImageFetcher fetcher = new ImageFetcher(); - private int selectedColor = Color.GREEN; + private int selectedColor = 0xff32b2e1; private boolean shouldRequestThumb = true; private FakeR fakeR; @@ -135,8 +131,6 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi quality = getIntent().getIntExtra(QUALITY_KEY, 0); maxImageCount = maxImages; - colWidth = getIntent().getIntExtra(COL_WIDTH_KEY, DEFAULT_COLUMN_WIDTH); - Display display = getWindowManager().getDefaultDisplay(); int width = display.getWidth(); @@ -169,7 +163,6 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } } }); - selectedColor = 0xff32b2e1; ia = new ImageAdapter(this); gridView.setAdapter(ia); @@ -187,6 +180,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi @Override public void onItemClick(AdapterView arg0, View view, int position, long id) { String name = getImageName(position); + int rotation = getImageRotation(position); if (name == null) { return; @@ -207,33 +201,29 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } if (isChecked) { - if (fileNames.add(name)) { - if (maxImageCount == 1) { - this.selectClicked(null); - } else { - maxImages--; - ImageView imageView = (ImageView)view; - if (android.os.Build.VERSION.SDK_INT>=16) { - imageView.setImageAlpha(128); - } else { - imageView.setAlpha(128); - } - view.setBackgroundColor(selectedColor); - } - } - } else { - if (fileNames.remove(name)) { - // Solo incrementa los slots libres si hemos - // "liberado" uno... - maxImages++; + fileNames.put(name, new Integer(rotation)); + if (maxImageCount == 1) { + this.selectClicked(null); + } else { + maxImages--; ImageView imageView = (ImageView)view; if (android.os.Build.VERSION.SDK_INT>=16) { - imageView.setImageAlpha(255); + imageView.setImageAlpha(128); } else { - imageView.setAlpha(255); + imageView.setAlpha(128); } - view.setBackgroundColor(Color.TRANSPARENT); + view.setBackgroundColor(selectedColor); } + } else { + fileNames.remove(name); + maxImages++; + ImageView imageView = (ImageView)view; + if (android.os.Build.VERSION.SDK_INT>=16) { + imageView.setImageAlpha(255); + } else { + imageView.setAlpha(255); + } + view.setBackgroundColor(Color.TRANSPARENT); } checkStatus.put(position, isChecked); @@ -249,9 +239,11 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi case CURSORLOADER_THUMBS: img.add(MediaStore.Images.Media._ID); + img.add(MediaStore.Images.Media.ORIENTATION); break; case CURSORLOADER_REAL: img.add(MediaStore.Images.Thumbnails.DATA); + img.add(MediaStore.Images.Media.ORIENTATION); break; default: break; @@ -270,17 +262,20 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } switch (loader.getId()) { - case CURSORLOADER_THUMBS: - imagecursor = cursor; - image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID); - ia.notifyDataSetChanged(); - break; - case CURSORLOADER_REAL: - actualimagecursor = cursor; - actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - break; - default: - break; + case CURSORLOADER_THUMBS: + imagecursor = cursor; + image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID); + image_column_orientation = imagecursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + ia.notifyDataSetChanged(); + break; + case CURSORLOADER_REAL: + actualimagecursor = cursor; + String[] columns = actualimagecursor.getColumnNames(); + actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + orientation_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.ORIENTATION); + break; + default: + break; } } @@ -293,7 +288,6 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } } - public void cancelClicked(View ignored) { setResult(RESULT_CANCELED); finish(); @@ -309,7 +303,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi progress.dismiss(); finish(); } else { - new ResizeImagesTask().execute(fileNames); + new ResizeImagesTask().execute(fileNames.entrySet()); } } @@ -321,7 +315,6 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview"))) .setEnabled(fileNames.size() != 0); getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(fileNames.size() != 0); - } private void setupHeader() { @@ -360,8 +353,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } }); - // Show the custom action bar view and hide the normal Home icon and - // title. + // Show the custom action bar view and hide the normal Home icon and title. final ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); @@ -380,7 +372,19 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } return name; } + + private int getImageRotation(int position) { + actualimagecursor.moveToPosition(position); + int rotation = 0; + try { + rotation = actualimagecursor.getInt(orientation_column_index); + } catch (Exception e) { + return rotation; + } + return rotation; + } + public boolean isChecked(int position) { boolean ret = checkStatus.get(position); return ret; @@ -453,6 +457,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } final int id = imagecursor.getInt(image_column_index); + final int rotate = imagecursor.getInt(image_column_orientation); if (isChecked(pos)) { if (android.os.Build.VERSION.SDK_INT>=16) { imageView.setImageAlpha(128); @@ -469,7 +474,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi imageView.setBackgroundColor(Color.TRANSPARENT); } if (shouldRequestThumb) { - fetcher.fetch(Integer.valueOf(id), imageView, colWidth); + fetcher.fetch(Integer.valueOf(id), imageView, colWidth, rotate); } return imageView; @@ -477,43 +482,59 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi } - private class ResizeImagesTask extends AsyncTask, Void, ArrayList> { + private class ResizeImagesTask extends AsyncTask>, Void, ArrayList> { @Override - protected ArrayList doInBackground(Set... fileSets) { - Set fileNames = fileSets[0]; + protected ArrayList doInBackground(Set>... fileSets) { + Set> fileNames = fileSets[0]; ArrayList al = new ArrayList(); try { - Iterator i = fileNames.iterator(); + Iterator> i = fileNames.iterator(); + Bitmap bmp; while(i.hasNext()) { - File file = new File(i.next()); - Bitmap bmp = this.getBitmap(file); - int width = bmp.getWidth(); - int height = bmp.getHeight(); - float widthScale = 1.0f; - float heightScale = 1.0f; - float scale = 1.0f; - if (desiredWidth > 0 || desiredHeight > 0) { - if (desiredHeight == 0 && desiredWidth < width) { - scale = (float)desiredWidth/width; - } else if (desiredWidth == 0 && desiredHeight < height) { - scale = (float)desiredHeight/height; - } else { - if (desiredWidth > 0 && desiredWidth < width) { - widthScale = (float)desiredWidth/width; - } - if (desiredHeight > 0 && desiredHeight < height) { - heightScale = (float)desiredHeight/height; - } - if (widthScale < heightScale) { - scale = widthScale; - } else { - scale = heightScale; + Entry imageInfo = i.next(); + File file = new File(imageInfo.getKey()); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 1; + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + int width = options.outWidth; + int height = options.outHeight; + float scale = calculateScale(width, height); + if (scale < 1) { + int finalWidth = (int)(width * scale); + int finalHeight = (int)(height * scale); + int inSampleSize = calculateInSampleSize(options, finalWidth, finalHeight); + options = new BitmapFactory.Options(); + options.inSampleSize = inSampleSize; + bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + if (bmp == null) { + throw new IOException("The image file could not be opened."); + } + scale = calculateScale(options.outWidth, options.outHeight); + bmp = this.getResizedBitmap(bmp, scale); + } else { + try { + bmp = BitmapFactory.decodeFile(file.getAbsolutePath()); + } catch(OutOfMemoryError e) { + options = new BitmapFactory.Options(); + options.inSampleSize = 2; + try { + bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } catch(OutOfMemoryError e2) { + options = new BitmapFactory.Options(); + options.inSampleSize = 4; + bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); } } } - if (scale < 1) { - bmp = this.getResizedBitmap(bmp, scale); + + int rotate = imageInfo.getValue().intValue(); + if (rotate != 0) { + Matrix matrix = new Matrix(); + matrix.setRotate(rotate); + bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true); } + file = this.storeImage(bmp, file.getName()); al.add(Uri.fromFile(file).toString()); } @@ -589,14 +610,52 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false); return resizedBitmap; } + } - private Bitmap getBitmap(File file) throws IOException { - Bitmap bmp; - bmp = BitmapFactory.decodeFile(file.getAbsolutePath()); - if (bmp == null) { - throw new IOException("The image file could not be opened."); + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; } - return bmp; } + + return inSampleSize; + } + + private float calculateScale(int width, int height) { + float widthScale = 1.0f; + float heightScale = 1.0f; + float scale = 1.0f; + if (desiredWidth > 0 || desiredHeight > 0) { + if (desiredHeight == 0 && desiredWidth < width) { + scale = (float)desiredWidth/width; + } else if (desiredWidth == 0 && desiredHeight < height) { + scale = (float)desiredHeight/height; + } else { + if (desiredWidth > 0 && desiredWidth < width) { + widthScale = (float)desiredWidth/width; + } + if (desiredHeight > 0 && desiredHeight < height) { + heightScale = (float)desiredHeight/height; + } + if (widthScale < heightScale) { + scale = widthScale; + } else { + scale = heightScale; + } + } + } + + return scale; } }