Skip to content

Commit

Permalink
feat: add compression like whatsapp by enabling autoCompress props
Browse files Browse the repository at this point in the history
  • Loading branch information
numandev1 committed Jun 24, 2021
1 parent cadcc11 commit 51c5a01
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 56 deletions.
18 changes: 18 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,20 @@ react-native link react-native-compressor

### Image

##### For Whatsapp Image Compression

```js
import { Image } from 'react-native-compressor';

const result = await Image.compress('file://path_of_file/image.jpg', {
autoCompress: true,
});
```

[Here is this package comparison of compression with WhatsApp](https://docs.google.com/spreadsheets/d/13TsnC1c7NOC9aCjzN6wkKurJQPeGRNwDhWsQOkXQskU/edit?usp=sharing)

##### For manual Compression

```js
import { Image } from 'react-native-compressor';

Expand Down Expand Up @@ -108,6 +122,10 @@ const result = await Video.compress(

### CompressorOptions

- ###### `autoCompress: boolean` (default: false)

if you want to compress images like whatsapp then make this prop `true`. by enable this option other option will not effect in compression

- ###### `maxWidth: number` (default: 1024)

The maximum width boundary used as the main boundary in resizing a landscape image.
Expand Down
Expand Up @@ -55,22 +55,22 @@ private void sendEvent(ReactContext reactContext,
//Image
@ReactMethod
public void image_compress(
String value,
String imagePath,
ReadableMap optionMap,
Promise promise) {
try {
final ImageCompressorOptions options = ImageCompressorOptions.fromMap(optionMap);
final Bitmap image = options.input == ImageCompressorOptions.InputType.base64
? ImageCompressor.decodeImage(value)
: ImageCompressor.loadImage(value);

final Bitmap resizedImage = ImageCompressor.resize(image, options.maxWidth, options.maxHeight);
final ByteArrayOutputStream imageDataByteArrayOutputStream = ImageCompressor.compress(resizedImage, options.output, options.quality);
Boolean isBase64=options.returnableOutputType==ImageCompressorOptions.ReturnableOutputType.base64;

final String returnableResult = ImageCompressor.encodeImage(imageDataByteArrayOutputStream,isBase64,image,options.output.toString(),this.reactContext);

promise.resolve(returnableResult);
if(options.autoCompress)
{
String returnableResult=ImageCompressor.autoCompressImage(imagePath,options.output.toString(),reactContext);
promise.resolve(returnableResult);
}
else
{
String returnableResult=ImageCompressor.manualCompressImage(imagePath,options,reactContext);
promise.resolve(returnableResult);
}
} catch (Exception ex) {
promise.reject(ex);
}
Expand Down
Expand Up @@ -5,18 +5,38 @@
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.media.ExifInterface;
import android.net.Uri;
import android.util.Base64;

import com.facebook.react.bridge.ReactApplicationContext;
import com.reactnativecompressor.Image.utils.ImageCompressorOptions;
import com.reactnativecompressor.Image.utils.ImageSize;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;

import static com.reactnativecompressor.Utils.Utils.generateCacheFilePath;


public class ImageCompressor {
private static final float autoCompressMaxHeight = 1280.0f;
private static final float audoCompressMaxWidth = 1280.0f;

public static String getRNFileUrl(String filePath) {
File returnAbleFile= new File(filePath);
try {
filePath = returnAbleFile.toURL().toString();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return filePath;
}

public static ImageSize findActualSize(Bitmap image, int maxWidth, int maxHeight) {
final float width = (float) image.getWidth();
final float height = (float) image.getHeight();
Expand All @@ -40,11 +60,8 @@ public static Bitmap decodeImage(String value) {
}

public static Bitmap loadImage(String value) {
String filePath=value;
if(value.indexOf("file:/")>-1)
{
filePath=value.substring( value.indexOf( ':' ) + 1 );
}
Uri uri= Uri.parse(value);
String filePath = uri.getPath();
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
return bitmap;
}
Expand All @@ -63,7 +80,7 @@ public static String encodeImage(ByteArrayOutputStream imageDataByteArrayOutputS
try {
FileOutputStream fos=new FileOutputStream(outputUri);
imageDataByteArrayOutputStream.writeTo(fos);
return "file:/"+outputUri;
return getRNFileUrl(outputUri);
} catch (Exception e) {
e.printStackTrace();
}
Expand Down Expand Up @@ -99,4 +116,136 @@ public static ByteArrayOutputStream compress(Bitmap image, ImageCompressorOption
image.compress(format, Math.round(100 * quality), stream);
return stream;
}

public static String manualCompressImage(String imagePath,ImageCompressorOptions options, ReactApplicationContext reactContext) {
final Bitmap image = options.input == ImageCompressorOptions.InputType.base64
? ImageCompressor.decodeImage(imagePath)
: ImageCompressor.loadImage(imagePath);

final Bitmap resizedImage = ImageCompressor.resize(image, options.maxWidth, options.maxHeight);
final ByteArrayOutputStream imageDataByteArrayOutputStream = ImageCompressor.compress(resizedImage, options.output, options.quality);
Boolean isBase64=options.returnableOutputType==ImageCompressorOptions.ReturnableOutputType.base64;

String returnableResult = ImageCompressor.encodeImage(imageDataByteArrayOutputStream,isBase64,image,options.output.toString(),reactContext);
return returnableResult;
}


public static String autoCompressImage(String imagePath,String outputExtension, ReactApplicationContext reactContext) {

Uri uri= Uri.parse(imagePath);
imagePath = uri.getPath();
Bitmap scaledBitmap = null;

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(imagePath, options);

int actualHeight = options.outHeight;
int actualWidth = options.outWidth;

float imgRatio = (float) actualWidth / (float) actualHeight;
float maxRatio = audoCompressMaxWidth / autoCompressMaxHeight;

if (actualHeight > autoCompressMaxHeight || actualWidth > audoCompressMaxWidth) {
if (imgRatio < maxRatio) {
imgRatio = autoCompressMaxHeight / actualHeight;
actualWidth = (int) (imgRatio * actualWidth);
actualHeight = (int) autoCompressMaxHeight;
} else if (imgRatio > maxRatio) {
imgRatio = audoCompressMaxWidth / actualWidth;
actualHeight = (int) (imgRatio * actualHeight);
actualWidth = (int) audoCompressMaxWidth;
} else {
actualHeight = (int) autoCompressMaxHeight;
actualWidth = (int) audoCompressMaxWidth;

}
}

options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPurgeable = true;
options.inInputShareable = true;
options.inTempStorage = new byte[16 * 1024];

try {
bmp = BitmapFactory.decodeFile(imagePath, options);
} catch (OutOfMemoryError exception) {
exception.printStackTrace();

}
try {
scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.RGB_565);
} catch (OutOfMemoryError exception) {
exception.printStackTrace();
}

float ratioX = actualWidth / (float) options.outWidth;
float ratioY = actualHeight / (float) options.outHeight;
float middleX = actualWidth / 2.0f;
float middleY = actualHeight / 2.0f;

Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);

Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));

if(bmp!=null)
{
bmp.recycle();
}

ExifInterface exif;
try {
exif = new ExifInterface(imagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
Matrix matrix = new Matrix();
if (orientation == 6) {
matrix.postRotate(90);
} else if (orientation == 3) {
matrix.postRotate(180);
} else if (orientation == 8) {
matrix.postRotate(270);
}
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
} catch (IOException e) {
e.printStackTrace();
}
FileOutputStream out = null;
String filepath = generateCacheFilePath(outputExtension,reactContext);;
try {
out = new FileOutputStream(filepath);

//write the compressed bitmap at the destination specified by filename.
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);

} catch (FileNotFoundException e) {
e.printStackTrace();
}
return getRNFileUrl(filepath);
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
final float totalPixels = width * height;
final float totalReqPixelsCap = reqWidth * reqHeight * 2;

while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}

return inSampleSize;
}
}
Expand Up @@ -12,6 +12,9 @@ public static ImageCompressorOptions fromMap(ReadableMap map) {
final String key = iterator.nextKey();

switch (key) {
case "autoCompress":
options.autoCompress = map.getBoolean(key);
break;
case "maxWidth":
options.maxWidth = map.getInt(key);
break;
Expand Down Expand Up @@ -48,10 +51,11 @@ public enum ReturnableOutputType {
base64, uri
}

public boolean autoCompress = false;
public int maxWidth = 640;
public int maxHeight = 480;
public float quality = 1.0f;
public InputType input = InputType.uri;
public OutputType output = OutputType.jpg;
public ReturnableOutputType returnableOutputType = ReturnableOutputType.uri;
public ReturnableOutputType returnableOutputType = ReturnableOutputType.uri;
}
59 changes: 46 additions & 13 deletions example/src/Screens/Image/index.tsx
@@ -1,11 +1,20 @@
import React, { useState } from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import {
View,
StyleSheet,
Alert,
useWindowDimensions,
Image as RNImage,
} from 'react-native';
import Button from '../../Components/Button';
import Row from '../../Components/Row';
import * as ImagePicker from 'react-native-image-picker';
const prettyBytes = require('pretty-bytes');
import { Image, getFileInfo } from 'react-native-compressor';
const Index = () => {
const dimension = useWindowDimensions();
const [orignalUri, setOrignalUri] = useState<string>();
const [commpressedUri, setCommpressedUri] = useState<string>();
const [fileName, setFileName] = useState<any>('');
const [mimeType, setMimeType] = useState<any>('');
const [orignalSize, setOrignalSize] = useState(0);
Expand All @@ -27,19 +36,16 @@ const Index = () => {

setFileName(source.fileName);
setMimeType(source.type);
setOrignalUri(source.uri);
}

Image.compress(source.uri, {
maxWidth: 100,
input: 'uri',
output: 'jpg',
quality: 0.5,
returnableOutputType: 'uri',
autoCompress: true,
})
.then(async (compressedFileUri) => {
setCommpressedUri(compressedFileUri);
const detail: any = await getFileInfo(compressedFileUri);
setCompressedSize(prettyBytes(parseInt(detail.size)));
console.log(compressedFileUri, 'compressed');
})
.catch((e) => {
console.log(e, 'error');
Expand All @@ -49,14 +55,35 @@ const Index = () => {
);
} catch (err) {}
};

return (
<View style={styles.container}>
<Row label="File Name" value={fileName} />
<Row label="Mime Type" value={mimeType} />
<Row label="Orignal Size" value={orignalSize} />
<Row label="Compressed Size" value={compressedSize} />
<Button onPress={chooseAudioHandler} title="Choose Image" />
<View style={styles.imageContainer}>
{orignalUri && (
<RNImage
resizeMode="contain"
source={{ uri: orignalUri }}
style={{
width: dimension.width / 3,
}}
/>
)}
{commpressedUri && (
<RNImage
resizeMode="contain"
source={{ uri: commpressedUri }}
style={{
width: dimension.width / 3,
}}
/>
)}
</View>
<View style={styles.container}>
<Row label="File Name" value={fileName} />
<Row label="Mime Type" value={mimeType} />
<Row label="Orignal Size" value={orignalSize} />
<Row label="Compressed Size" value={compressedSize} />
<Button onPress={chooseAudioHandler} title="Choose Image" />
</View>
</View>
);
};
Expand All @@ -69,4 +96,10 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
imageContainer: {
flex: 1,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
},
});

0 comments on commit 51c5a01

Please sign in to comment.