WOA-3924 - (Performance) Image uploading in feed post and chat is slo… (#45)
* WOA-3924 - (Performance) Image uploading in feed post and chat is slow - Added resizeImage functionality in Android * Change resolution to be exactly like on iOS
This commit is contained in:
parent
7a71a9c0d7
commit
6295dd6c0f
|
@ -1,18 +1,36 @@
|
|||
package com.wix.RNCameraKit;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public final static String CONTENT_PREFIX = "content://";
|
||||
public final static String FILE_PREFIX = "file://";
|
||||
private static final int MAX_SAMPLE_SIZE = 8;
|
||||
|
||||
|
||||
|
||||
public static String getBase64FromBitmap(Bitmap bitmap) {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
|
||||
|
@ -49,4 +67,156 @@ public class Utils {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static WritableMap resizeImage(Context context, String imageName, String imageUrlString, int maxResolution, int compressionQuality) throws IOException {
|
||||
Bitmap sourceImage = null;
|
||||
sourceImage = Utils.loadBitmapFromFile(context, imageUrlString, maxResolution, maxResolution);
|
||||
|
||||
if (sourceImage == null) {
|
||||
throw new IOException("Unable to load source image from path");
|
||||
}
|
||||
|
||||
Bitmap scaledImage = Utils.resizeImage(sourceImage, maxResolution, maxResolution);
|
||||
if (sourceImage != scaledImage) {
|
||||
sourceImage.recycle();
|
||||
}
|
||||
|
||||
// Save the resulting image
|
||||
File path = context.getCacheDir();
|
||||
String resizedImagePath = Utils.saveImage(scaledImage, path, Long.toString(new Date().getTime()), Bitmap.CompressFormat.JPEG, compressionQuality);
|
||||
|
||||
// Clean up remaining image
|
||||
scaledImage.recycle();
|
||||
|
||||
WritableMap ans = Arguments.createMap();
|
||||
ans.putString("uri", FILE_PREFIX+resizedImagePath);
|
||||
ans.putString("name", imageName);
|
||||
ans.putInt("size", (int)new File(resizedImagePath).length());
|
||||
ans.putInt("width", scaledImage.getWidth());
|
||||
ans.putInt("height", scaledImage.getHeight());
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resize the specified bitmap, keeping its aspect ratio.
|
||||
*/
|
||||
public static Bitmap resizeImage(Bitmap image, int maxWidth, int maxHeight) {
|
||||
Bitmap newImage = null;
|
||||
if (image == null) {
|
||||
return null; // Can't load the image from the given path.
|
||||
}
|
||||
|
||||
if (maxHeight > 0 && maxWidth > 0) {
|
||||
float width = image.getWidth();
|
||||
float height = image.getHeight();
|
||||
|
||||
float ratio = Math.min((float)maxWidth / width, (float)maxHeight / height);
|
||||
|
||||
int finalWidth = (int) (width * ratio);
|
||||
int finalHeight = (int) (height * ratio);
|
||||
newImage = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
|
||||
}
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute the inSampleSize value to use to load a bitmap.
|
||||
* Adapted from https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
|
||||
*/
|
||||
public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
|
||||
|
||||
if (reqHeight == 0 || reqWidth == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
while (inSampleSize <= MAX_SAMPLE_SIZE
|
||||
&& (halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Bitmap loadBitmap(Context context, String imagePath, BitmapFactory.Options options) throws IOException {
|
||||
Bitmap sourceImage = null;
|
||||
if (!imagePath.startsWith(CONTENT_PREFIX)) {
|
||||
try {
|
||||
sourceImage = BitmapFactory.decodeFile(imagePath, options);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new IOException("Error decoding image file");
|
||||
}
|
||||
} else {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
InputStream input = cr.openInputStream(Uri.parse(imagePath));
|
||||
if (input != null) {
|
||||
sourceImage = BitmapFactory.decodeStream(input, null, options);
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
return sourceImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the bitmap resource from the file specified in imagePath.
|
||||
*/
|
||||
public static Bitmap loadBitmapFromFile(Context context, String imagePath, int newWidth,
|
||||
int newHeight) throws IOException {
|
||||
// Decode the image bounds to find the size of the source image.
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
loadBitmap(context, imagePath, options);
|
||||
|
||||
// Set a sample size according to the image size to lower memory usage.
|
||||
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight , newWidth, newHeight);
|
||||
options.inJustDecodeBounds = false;
|
||||
return loadBitmap(context, imagePath, options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the given bitmap in a directory. Extension is automatically generated using the bitmap format.
|
||||
*/
|
||||
public static String saveImage(Bitmap bitmap, File saveDirectory, String fileName,
|
||||
Bitmap.CompressFormat compressFormat, int quality)
|
||||
throws IOException {
|
||||
if (bitmap == null) {
|
||||
throw new IOException("The bitmap couldn't be resized");
|
||||
}
|
||||
|
||||
File newFile = new File(saveDirectory, fileName + "." + compressFormat.name());
|
||||
if(!newFile.createNewFile()) {
|
||||
throw new IOException("The file already exists");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(compressFormat, quality, outputStream);
|
||||
byte[] bitmapData = outputStream.toByteArray();
|
||||
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(newFile);
|
||||
fos.write(bitmapData);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
|
||||
return newFile.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package com.wix.RNCameraKit.gallery;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
|
@ -13,34 +17,59 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.wix.RNCameraKit.SaveImageTask;
|
||||
import com.wix.RNCameraKit.Utils;
|
||||
import com.wix.RNCameraKit.gallery.permission.StoragePermission;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.wix.RNCameraKit.Utils.getStringSafe;
|
||||
|
||||
/**
|
||||
* Created by yedidyak on 29/06/2016.
|
||||
*/
|
||||
public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
||||
|
||||
|
||||
private final String IMAGE_URI_KEY = "uri";
|
||||
private final String IMAGE_NAME_KEY = "name";
|
||||
|
||||
private final int HIGHE_DIMANTION = 1200;
|
||||
private final int MEDIUM_DIMANTION = 800;
|
||||
private final int LOW_DIMANTION = 600;
|
||||
|
||||
|
||||
public static final int MEDIUM_COMPRESSION_QUALITY = 85;
|
||||
public static final int HIGH_COMPRESSION_QUALITY = 92;
|
||||
|
||||
private final String HIGH_QUALITY = "high";
|
||||
private final String MEDIUM_QUALITY = "medium";
|
||||
private final String LOW_QUALITY = "low";
|
||||
|
||||
|
||||
public static final String[] ALBUMS_PROJECTION = new String[]{
|
||||
MediaStore.Images.Media.DATA,
|
||||
MediaStore.Images.Media.BUCKET_DISPLAY_NAME
|
||||
MediaStore.Images.Media.DATA,
|
||||
MediaStore.Images.Media.BUCKET_DISPLAY_NAME
|
||||
};
|
||||
public static final String[] IMAGES_PROJECTION = new String[]{
|
||||
MediaStore.Images.Media. _ID,
|
||||
MediaStore.Images.Media.SIZE,
|
||||
MediaStore.Images.Media.MIME_TYPE,
|
||||
MediaStore.Images.Media.TITLE,
|
||||
MediaStore.Images.Media.WIDTH,
|
||||
MediaStore.Images.Media.HEIGHT,
|
||||
MediaStore.Images.Media.DATA
|
||||
MediaStore.Images.Media._ID,
|
||||
MediaStore.Images.Media.SIZE,
|
||||
MediaStore.Images.Media.MIME_TYPE,
|
||||
MediaStore.Images.Media.TITLE,
|
||||
MediaStore.Images.Media.WIDTH,
|
||||
MediaStore.Images.Media.HEIGHT,
|
||||
MediaStore.Images.Media.DATA
|
||||
};
|
||||
public static final String ALL_PHOTOS = "All Photos";
|
||||
private Promise checkPermissionStatusPromise;
|
||||
|
@ -62,8 +91,7 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
public void addAlbum(String name, String uri) {
|
||||
if (!albums.containsKey(name)) {
|
||||
albums.put(name, new Album(name, uri));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
albums.get(name).count++;
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +113,7 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() {
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
if (checkPermissionStatusPromise != null && getCurrentActivity() != null) {
|
||||
if (checkPermissionStatusPromise != null && getCurrentActivity() != null) {
|
||||
getCurrentActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -142,10 +170,10 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
|
||||
private Bitmap getThumbnail(int thumbId) {
|
||||
return MediaStore.Images.Thumbnails.getThumbnail(
|
||||
getReactApplicationContext().getContentResolver(),
|
||||
thumbId,
|
||||
MediaStore.Images.Thumbnails.MINI_KIND,
|
||||
null);
|
||||
getReactApplicationContext().getContentResolver(),
|
||||
thumbId,
|
||||
MediaStore.Images.Thumbnails.MINI_KIND,
|
||||
null);
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
|
@ -173,7 +201,6 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void getAlbumsWithThumbnails(Promise promise) {
|
||||
|
||||
Cursor imagesCursor = getReactApplicationContext().getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ALBUMS_PROJECTION, null, null, null);
|
||||
AlbumList albums = getAlbumListFromCursor(imagesCursor);
|
||||
|
@ -189,16 +216,55 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
promise.resolve(ret);
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void resizeImage(ReadableMap image, String quality, Promise promise) throws IOException {
|
||||
try {
|
||||
|
||||
String imageName = Utils.getStringSafe(image, IMAGE_NAME_KEY);
|
||||
String imageUrlString = getStringSafe(image, IMAGE_URI_KEY);
|
||||
if (imageUrlString.startsWith(Utils.FILE_PREFIX)) {
|
||||
imageUrlString = imageUrlString.replaceFirst(Utils.FILE_PREFIX, "");
|
||||
}
|
||||
|
||||
// decide what is the wanted compression & resolution
|
||||
int maxResolution;
|
||||
int compressionQuality;
|
||||
switch(quality) {
|
||||
case HIGH_QUALITY:
|
||||
maxResolution = HIGHE_DIMANTION;
|
||||
compressionQuality = MEDIUM_COMPRESSION_QUALITY;
|
||||
break;
|
||||
case MEDIUM_QUALITY:
|
||||
maxResolution = MEDIUM_DIMANTION;
|
||||
compressionQuality = MEDIUM_COMPRESSION_QUALITY;
|
||||
break;
|
||||
case LOW_QUALITY:
|
||||
maxResolution = LOW_DIMANTION;
|
||||
compressionQuality = MEDIUM_COMPRESSION_QUALITY;
|
||||
break;
|
||||
default:
|
||||
maxResolution = HIGHE_DIMANTION;
|
||||
compressionQuality = HIGH_COMPRESSION_QUALITY;
|
||||
}
|
||||
|
||||
WritableMap ans = Utils.resizeImage(getReactApplicationContext(),imageName, imageUrlString, maxResolution, compressionQuality );
|
||||
promise.resolve(ans);
|
||||
} catch (IOException e) {
|
||||
Log.d("","Failed resize image e: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void getImagesForUris(ReadableArray uris, Promise promise) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(MediaStore.Images.Media.DATA + " IN (");
|
||||
for (int i=0; i<uris.size(); i++) {
|
||||
for (int i = 0; i < uris.size(); i++) {
|
||||
builder.append("\"");
|
||||
builder.append(uris.getString(i));
|
||||
builder.append("\"");
|
||||
if(i != uris.size() -1) {
|
||||
if (i != uris.size() - 1) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
|
@ -243,19 +309,19 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void saveImageURLToCameraRoll(String imageUrl, final Promise promise) {
|
||||
new SaveImageTask(imageUrl, getReactApplicationContext(), promise, true).execute();
|
||||
new SaveImageTask(imageUrl, getReactApplicationContext(), promise, true).execute();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void deleteTempImage(String imageUrl, final Promise promise) {
|
||||
boolean success = true;
|
||||
String imagePath = imageUrl.replace("file://","");
|
||||
String imagePath = imageUrl.replace("file://", "");
|
||||
File imageFile = new File(imagePath);
|
||||
if (imageFile.exists()) {
|
||||
success = imageFile.delete();
|
||||
}
|
||||
|
||||
if(promise != null) {
|
||||
if (promise != null) {
|
||||
WritableMap result = Arguments.createMap();
|
||||
result.putBoolean("success", success);
|
||||
promise.resolve(result);
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.widget.LinearLayout;
|
|||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.wix.RNCameraKit.Utils;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
|
@ -24,7 +25,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|||
public class SelectableImage extends FrameLayout {
|
||||
private static final int MINI_THUMB_HEIGHT = 512;
|
||||
private static final int MINI_THUMB_WIDTH = 384;
|
||||
private static final int MAX_SAMPLE_SIZE = 8;
|
||||
|
||||
private static final int DEFAULT_SELECTED_IMAGE_GRAVITY = Gravity.TOP | Gravity.RIGHT;
|
||||
|
||||
public static final int SELECTED_IMAGE_NORMAL_SIZE_DP = 22;
|
||||
|
@ -90,9 +91,10 @@ public class SelectableImage extends FrameLayout {
|
|||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
this.inSampleSize = calculateInSampleSize(w, h);
|
||||
this.inSampleSize = Utils.calculateInSampleSize(MINI_THUMB_WIDTH,MINI_THUMB_HEIGHT,w, h);
|
||||
}
|
||||
|
||||
|
||||
public void setScaleType(ImageView.ScaleType scaleType) {
|
||||
imageView.setScaleType(scaleType);
|
||||
}
|
||||
|
@ -114,7 +116,7 @@ public class SelectableImage extends FrameLayout {
|
|||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
if (inSampleSize == 0) {
|
||||
inSampleSize = calculateInSampleSize(getWidth(), getHeight());
|
||||
inSampleSize = Utils.calculateInSampleSize(MINI_THUMB_WIDTH,MINI_THUMB_HEIGHT, getWidth(), getHeight());
|
||||
}
|
||||
options.inSampleSize = inSampleSize;
|
||||
|
||||
|
@ -157,28 +159,6 @@ public class SelectableImage extends FrameLayout {
|
|||
this.unselectedDrawable = unselectedDrawable;
|
||||
}
|
||||
|
||||
public static int calculateInSampleSize(int reqWidth, int reqHeight) {
|
||||
|
||||
if (reqHeight == 0 || reqWidth == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (MINI_THUMB_HEIGHT > reqHeight || MINI_THUMB_WIDTH > reqWidth) {
|
||||
|
||||
final int halfHeight = MINI_THUMB_HEIGHT / 2;
|
||||
final int halfWidth = MINI_THUMB_WIDTH / 2;
|
||||
|
||||
while (inSampleSize <= MAX_SAMPLE_SIZE
|
||||
&& (halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
private LayoutParams createSelectedImageParams(Integer gravity, Integer sizeDp) {
|
||||
gravity = gravity != null ? gravity : DEFAULT_SELECTED_IMAGE_GRAVITY;
|
||||
|
|
|
@ -51,6 +51,15 @@ async function requestDevicePhotosAuthorization() {
|
|||
return isAuthorized;
|
||||
}
|
||||
|
||||
async function resizeImage(image = {}, quality = 'original') {
|
||||
if (quality === 'original') {
|
||||
return images;
|
||||
}
|
||||
const ans = await NativeGalleryModule.resizeImage(image, quality);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
checkDevicePhotosAuthorizationStatus,
|
||||
requestDevicePhotosAuthorization,
|
||||
|
@ -58,5 +67,6 @@ export default {
|
|||
getImageUriForId,
|
||||
getImagesForIds,
|
||||
getImageForTapEvent,
|
||||
getImagesForCameraEvent
|
||||
getImagesForCameraEvent,
|
||||
resizeImage
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue