Skip to content

Commit

Permalink
bitmap compress and exif copy
Browse files Browse the repository at this point in the history
  • Loading branch information
dinitri committed Mar 21, 2024
1 parent 821b14a commit 00d739f
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 56 deletions.
137 changes: 81 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,100 @@ 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);
}

// Define the output file with a new filename and .webp extension
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);

// Compress and save the bitmap in WEBP lossless format
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 = {
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"
};

}

0 comments on commit 00d739f

Please sign in to comment.