Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bitmap compression #14

Merged
merged 13 commits into from
Apr 30, 2024
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</feature>
</config-file>
<source-file src="src/android/SpectrumManager.java" target-dir="src/com/spoon/spectrum" />
<source-file src="src/android/SpoonCameraExif.java" target-dir="src/com/spoon/spectrum" />
</platform>

<!-- ios -->
Expand Down
135 changes: 79 additions & 56 deletions src/android/SpectrumManager.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,26 @@
package com.spoon.spectrum;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.MimeTypeMap;


import com.facebook.spectrum.DefaultPlugins;
import com.facebook.spectrum.EncodedImageSink;
import com.facebook.spectrum.EncodedImageSource;
import com.facebook.spectrum.Spectrum;
import com.facebook.spectrum.SpectrumException;
import com.facebook.spectrum.SpectrumResult;
import com.facebook.spectrum.SpectrumSoLoader;
import com.facebook.spectrum.image.ImageSize;
import com.facebook.spectrum.logging.SpectrumLogcatLogger;
import com.facebook.spectrum.options.TranscodeOptions;
import com.facebook.spectrum.requirements.EncodeRequirement;
import com.facebook.spectrum.requirements.ResizeRequirement;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

import static com.facebook.spectrum.image.EncodedImageFormat.JPEG;
import androidx.exifinterface.media.ExifInterface;

public class SpectrumManager extends CordovaPlugin {

private static Spectrum mSpectrum;

@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
cordova.getThreadPool().execute(new Runnable() {
Expand All @@ -62,57 +44,98 @@ private void sendErrorResultForException(CallbackContext callbackContext, Except
}

private void transcodeImage(String path, int size, CallbackContext callbackContext) {
if (mSpectrum == null) {
SpectrumSoLoader.init(cordova.getActivity());
mSpectrum = Spectrum.make(new SpectrumLogcatLogger(Log.INFO), DefaultPlugins.get());
}
Uri tmpSrc = Uri.parse(path);
final Uri sourceUri = tmpSrc.getScheme() != null ? webView.getResourceApi().remapUri(tmpSrc) : tmpSrc;
final String sourcePath = sourceUri.toString();
File file = new File(sourcePath);
if (!file.exists()) {
callbackContext.error("source file does not exists");
callbackContext.error("source file does not exist");
return;
}
InputStream inputStream;

Bitmap bitmap;
try {
inputStream = new FileInputStream(sourcePath);
} catch (FileNotFoundException e) {
sendErrorResultForException(callbackContext, e);
bitmap = BitmapFactory.decodeFile(sourcePath);
if (bitmap == null) {
callbackContext.error("Could not decode the image");
return;
}
} catch (Exception e) {
callbackContext.error("Failed to load image: " + e.getMessage());
return;
}
final TranscodeOptions transcodeOptions;

// Resize the bitmap if necessary
ImageSize targetSize = getImageSize(path, size);
transcodeOptions = TranscodeOptions.Builder(new EncodeRequirement(JPEG, 80)).resize(ResizeRequirement.Mode.EXACT_OR_SMALLER, targetSize).build();
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
String destinationFileName = UUID.randomUUID().toString() + "_compressed." + fileExtension;
if (bitmap.getWidth() != targetSize.width || bitmap.getHeight() != targetSize.height) {
bitmap = Bitmap.createScaledBitmap(bitmap, targetSize.width, targetSize.height, true);
}

String destinationFileName = UUID.randomUUID().toString() + "_compressed.jpg";
String destinationPath = sourcePath.replace(file.getName(), destinationFileName);
SpectrumResult result;
try {
result = mSpectrum.transcode(
EncodedImageSource.from(inputStream),
EncodedImageSink.from(destinationPath),
transcodeOptions,
"com.spectrum-plugin");
} catch (SpectrumException | FileNotFoundException e) {
sendErrorResultForException(callbackContext, e);
File outputFile = new File(destinationPath);

try (FileOutputStream out = new FileOutputStream(outputFile)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)) {
callbackContext.error("Failed to compress image");
return;
}
}
} catch (Exception e) {
callbackContext.error("Failed to save compressed image: " + e.getMessage());
return;
} finally {
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
}
if (result.isSuccessful()) {
if (!file.delete()) {
callbackContext.error("could not delete source image");
return;

// Initialize ExifInterface for the original and compressed image
ExifInterface originalExif = null;
try {
originalExif = new ExifInterface(sourcePath);
} catch (IOException e) {
Log.d("Can't extract origExifs", e.toString());
}

// Iterate over all EXIF tags in the original file
if (originalExif != null) {
ExifInterface compressedExif = null;
try {
compressedExif = new ExifInterface(destinationPath);
} catch (IOException e) {
Log.d("Can't extract compExifs", e.toString());
}
if (!new File(destinationPath).renameTo(file)) {
callbackContext.error("could not rename image");
return;

for (String attribute : SpoonCameraExif.COMMON_TAGS) {
String value = originalExif.getAttribute(attribute);
if (value != null) {
compressedExif.setAttribute(attribute, value);
}
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
pluginResult.setKeepCallback(true);
callbackContext.sendPluginResult(pluginResult);
if (compressedExif != null) {
try {
compressedExif.saveAttributes();
} catch (IOException e) {
Log.d("Error saving exifs ", e.toString());
}
}
}

// Replace the original file with the compressed one
if (!file.delete()) {
callbackContext.error("could not delete source image");
return;
}
if (!outputFile.renameTo(file)) {
callbackContext.error("could not rename image");
return;
}
callbackContext.error("could not compress image");

PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
pluginResult.setKeepCallback(true);
callbackContext.sendPluginResult(pluginResult);
}

private ImageSize getImageSize(String sourcePath, int defaultSize) {
Expand Down
72 changes: 72 additions & 0 deletions src/android/SpoonCameraExif.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.spoon.spectrum;

import androidx.camera.core.impl.utils.Exif;
import androidx.exifinterface.media.ExifInterface;

public class SpoonCameraExif {
public static final String[] COMMON_TAGS = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of an enum?

ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_APERTURE_VALUE,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_WHITE_BALANCE,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_SOFTWARE,
ExifInterface.TAG_Y_CB_CR_POSITIONING,
ExifInterface.TAG_X_RESOLUTION,
ExifInterface.TAG_Y_RESOLUTION,
ExifInterface.TAG_RESOLUTION_UNIT,
ExifInterface.TAG_F_NUMBER,
ExifInterface.TAG_EXPOSURE_PROGRAM,
ExifInterface.TAG_RW2_ISO,
ExifInterface.TAG_EXIF_VERSION,
ExifInterface.TAG_DATETIME_ORIGINAL,
ExifInterface.TAG_OFFSET_TIME,
ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
ExifInterface.TAG_SHUTTER_SPEED_VALUE,
ExifInterface.TAG_BRIGHTNESS_VALUE,
ExifInterface.TAG_MAX_APERTURE_VALUE,
ExifInterface.TAG_METERING_MODE,
ExifInterface.TAG_FLASHPIX_VERSION,
ExifInterface.TAG_COMPONENTS_CONFIGURATION,
ExifInterface.TAG_SUBSEC_TIME,
ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
ExifInterface.TAG_COLOR_SPACE,
ExifInterface.TAG_SCENE_TYPE,
ExifInterface.TAG_CUSTOM_RENDERED,
ExifInterface.TAG_EXPOSURE_MODE,
ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
"FocalLengthIn35mmFormat",
ExifInterface.TAG_SCENE_CAPTURE_TYPE,
ExifInterface.TAG_CONTRAST,
ExifInterface.TAG_SATURATION,
ExifInterface.TAG_SHARPNESS,
ExifInterface.TAG_IMAGE_UNIQUE_ID,
ExifInterface.TAG_GPS_VERSION_ID,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
"latitude",
"longitude",
"ModifyDate",
"CreateDate",
"ExposureCompensation",
"ExifImageWidth",
"ExifImageHeight"
};

}
Loading