diff --git a/src/android/SpectrumManager.java b/src/android/SpectrumManager.java index db233e5..758291e 100644 --- a/src/android/SpectrumManager.java +++ b/src/android/SpectrumManager.java @@ -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() { @@ -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) { diff --git a/src/android/SpoonCameraExif.java b/src/android/SpoonCameraExif.java new file mode 100644 index 0000000..bc7fd50 --- /dev/null +++ b/src/android/SpoonCameraExif.java @@ -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" + }; + +}