diff --git a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt index e11ec51..dd31e65 100644 --- a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +++ b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt @@ -11,6 +11,7 @@ import android.net.Uri import android.util.Base64 import com.facebook.react.bridge.ReactApplicationContext import com.reactnativecompressor.Utils.MediaCache +import com.reactnativecompressor.Utils.Utils.exifAttributes import com.reactnativecompressor.Utils.Utils.generateCacheFilePath import com.reactnativecompressor.Utils.Utils.slashifyFilePath import java.io.ByteArrayOutputStream @@ -55,7 +56,28 @@ object ImageCompressor { return BitmapFactory.decodeFile(filePath) } - fun encodeImage(imageDataByteArrayOutputStream: ByteArrayOutputStream, isBase64: Boolean, outputExtension: String?, reactContext: ReactApplicationContext?): String? { + fun copyExifInfo(imagePath:String, outputUri:String){ + try { + // for copy exif info + val sourceExif = ExifInterface(imagePath) + val compressedExif = ExifInterface(outputUri) + for (tag in exifAttributes) { + val compressedValue = compressedExif.getAttribute(tag) + if(compressedValue==null) + { + val sourceValue = sourceExif.getAttribute(tag) + if (sourceValue != null) { + compressedExif.setAttribute(tag, sourceValue) + } + } + } + compressedExif.saveAttributes() + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun encodeImage(imageDataByteArrayOutputStream: ByteArrayOutputStream, isBase64: Boolean, outputExtension: String?,imagePath: String?, reactContext: ReactApplicationContext?): String? { if (isBase64) { val imageData = imageDataByteArrayOutputStream.toByteArray() return Base64.encodeToString(imageData, Base64.DEFAULT) @@ -64,6 +86,9 @@ object ImageCompressor { try { val fos = FileOutputStream(outputUri) imageDataByteArrayOutputStream.writeTo(fos) + + copyExifInfo(imagePath!!, outputUri) + return getRNFileUrl(outputUri) } catch (e: Exception) { e.printStackTrace() @@ -112,7 +137,7 @@ object ImageCompressor { val resizedImage = resize(image, options.maxWidth, options.maxHeight) val imageDataByteArrayOutputStream = compress(resizedImage, options.output, options.quality,options.disablePngTransparency) val isBase64 = options.returnableOutputType === ImageCompressorOptions.ReturnableOutputType.base64 - return encodeImage(imageDataByteArrayOutputStream, isBase64, options.output.toString(), reactContext) + return encodeImage(imageDataByteArrayOutputStream, isBase64, options.output.toString(),imagePath, reactContext) } fun isCompressedSizeLessThanActualFile(sourceFileUrl: String,compressedFileUrl: String?): Boolean { @@ -197,7 +222,7 @@ object ImageCompressor { } scaledBitmap = correctImageOrientation(scaledBitmap, imagePath) val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality,compressorOptions.disablePngTransparency) - val compressedImagePath=encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(), reactContext) + val compressedImagePath=encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(),imagePath, reactContext) if(isCompressedSizeLessThanActualFile(imagePath!!,compressedImagePath)) { return compressedImagePath diff --git a/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt b/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt index ee71e48..4f8aa27 100644 --- a/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +++ b/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt @@ -32,31 +32,31 @@ object Utils { val currentVideoCompression = intArrayOf(0) val videoCompressorClass: VideoCompressorClass? = VideoCompressorClass(reactContext); compressorExports[uuid] = videoCompressorClass - videoCompressorClass?.start(srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(), - listener = object : CompressionListener { - override fun onProgress(index: Int, percent: Float) { - if (percent <= 100) - { - val roundProgress = Math.round(percent) - if (progressDivider==0||(roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) { - EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(),uuid) - currentVideoCompression[0] = roundProgress - } + videoCompressorClass?.start( + srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(), + listener = object : CompressionListener { + override fun onProgress(index: Int, percent: Float) { + if (percent <= 100) { + val roundProgress = Math.round(percent) + if (progressDivider == 0 || (roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) { + EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(), uuid) + currentVideoCompression[0] = roundProgress } } + } - override fun onStart(index: Int) { + override fun onStart(index: Int) { - } + } - override fun onSuccess(index: Int, size: Long, path: String?) { - val fileUrl = "file://$destinationPath" - //convert finish,result(true is success,false is fail) - promise.resolve(fileUrl) - MediaCache.removeCompletedImagePath(fileUrl) - currentVideoCompression[0] = 0 - compressorExports[uuid]=null - } + override fun onSuccess(index: Int, size: Long, path: String?) { + val fileUrl = "file://$destinationPath" + //convert finish,result(true is success,false is fail) + promise.resolve(fileUrl) + MediaCache.removeCompletedImagePath(fileUrl) + currentVideoCompression[0] = 0 + compressorExports[uuid] = null + } override fun onFailure(index: Int, failureMessage: String) { Log.wtf("failureMessage", failureMessage) @@ -155,6 +155,147 @@ object Utils { Log.d(AudioCompressor.TAG, log) } + val exifAttributes = arrayOf( + "FNumber", + "ApertureValue", + "Artist", + "BitsPerSample", + "BrightnessValue", + "CFAPattern", + "ColorSpace", + "ComponentsConfiguration", + "CompressedBitsPerPixel", + "Compression", + "Contrast", + "Copyright", + "CustomRendered", + "DateTime", + "DateTimeDigitized", + "DateTimeOriginal", + "DefaultCropSize", + "DeviceSettingDescription", + "DigitalZoomRatio", + "DNGVersion", + "ExifVersion", + "ExposureBiasValue", + "ExposureIndex", + "ExposureMode", + "ExposureProgram", + "ExposureTime", + "FileSource", + "Flash", + "FlashpixVersion", + "FlashEnergy", + "FocalLength", + "FocalLengthIn35mmFilm", + "FocalPlaneResolutionUnit", + "FocalPlaneXResolution", + "FocalPlaneYResolution", + "FNumber", + "GainControl", + "GPSAltitude", + "GPSAltitudeRef", + "GPSAreaInformation", + "GPSDateStamp", + "GPSDestBearing", + "GPSDestBearingRef", + "GPSDestDistance", + "GPSDestDistanceRef", + "GPSDestLatitude", + "GPSDestLatitudeRef", + "GPSDestLongitude", + "GPSDestLongitudeRef", + "GPSDifferential", + "GPSDOP", + "GPSImgDirection", + "GPSImgDirectionRef", + "GPSLatitude", + "GPSLatitudeRef", + "GPSLongitude", + "GPSLongitudeRef", + "GPSMapDatum", + "GPSMeasureMode", + "GPSProcessingMethod", + "GPSSatellites", + "GPSSpeed", + "GPSSpeedRef", + "GPSStatus", + "GPSTimeStamp", + "GPSTrack", + "GPSTrackRef", + "GPSVersionID", + "ImageDescription", + "ImageLength", + "ImageUniqueID", + "ImageWidth", + "InteroperabilityIndex", + "ISOSpeedRatings", + "ISOSpeedRatings", + "JPEGInterchangeFormat", + "JPEGInterchangeFormatLength", + "LightSource", + "Make", + "MakerNote", + "MaxApertureValue", + "MeteringMode", + "Model", + "NewSubfileType", + "OECF", + "AspectFrame", + "PreviewImageLength", + "PreviewImageStart", + "ThumbnailImage", + "Orientation", + "PhotometricInterpretation", + "PixelXDimension", + "PixelYDimension", + "PlanarConfiguration", + "PrimaryChromaticities", + "ReferenceBlackWhite", + "RelatedSoundFile", + "ResolutionUnit", + "RowsPerStrip", + "ISO", + "JpgFromRaw", + "SensorBottomBorder", + "SensorLeftBorder", + "SensorRightBorder", + "SensorTopBorder", + "SamplesPerPixel", + "Saturation", + "SceneCaptureType", + "SceneType", + "SensingMethod", + "Sharpness", + "ShutterSpeedValue", + "Software", + "SpatialFrequencyResponse", + "SpectralSensitivity", + "StripByteCounts", + "StripOffsets", + "SubfileType", + "SubjectArea", + "SubjectDistance", + "SubjectDistanceRange", + "SubjectLocation", + "SubSecTime", + "SubSecTimeDigitized", + "SubSecTimeDigitized", + "SubSecTimeOriginal", + "SubSecTimeOriginal", + "ThumbnailImageLength", + "ThumbnailImageWidth", + "TransferFunction", + "UserComment", + "WhiteBalance", + "WhitePoint", + "XResolution", + "YCbCrCoefficients", + "YCbCrPositioning", + "YCbCrSubSampling", + "YResolution" + ) + fun getLength(uri: Uri, contentResolver: ContentResolver): Long { var assetFileDescriptor: AssetFileDescriptor? = null try { diff --git a/ios/Image/ImageCompressor.swift b/ios/Image/ImageCompressor.swift index 29cdb2f..06374c0 100644 --- a/ios/Image/ImageCompressor.swift +++ b/ios/Image/ImageCompressor.swift @@ -114,7 +114,38 @@ class ImageCompressor { } return false } - + + static func isPNG(_ data: Data) -> Bool { + return data.starts(with: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) + } + + static func copyExifInfo(actualImagePath:String, image: UIImage, data: Data) -> Data { + let fileURL = URL(string: actualImagePath)! + let filePath = fileURL.path + + let url = URL(fileURLWithPath: filePath) + let source = CGImageSourceCreateWithURL(url as CFURL, nil)! + var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] + + let dataProvider = CGDataProvider(data: data as CFData) + let dataImageSource = CGImageSourceCreateWithDataProvider(dataProvider!, nil)! + let dataMetadata = CGImageSourceCopyPropertiesAtIndex(dataImageSource, 0, nil) as? [CFString: Any] + + // Copy all keys from source metadata to destination metadata if they don't exist + for (key, value) in dataMetadata ?? [:] { + if metadata?[key] == nil { + metadata?[key] = value + } + } + + let outputFormat = isPNG(data) ? kUTTypePNG : kUTTypeJPEG + + let destinationData = NSMutableData() + let destination = CGImageDestinationCreateWithData(destinationData, outputFormat, 1, nil)! + CGImageDestinationAddImage(destination, image.cgImage!, metadata as CFDictionary?) + CGImageDestinationFinalize(destination) + return destinationData as Data + } static func writeImage(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool,disablePngTransparency:Bool,isEnableAutoCompress:Bool,actualImagePath:String)-> String{ var data: Data @@ -135,7 +166,10 @@ class ImageCompressor { } } - + + data=copyExifInfo(actualImagePath: actualImagePath, image: image, data: data) + + if isBase64 { return data.base64EncodedString(options: []) } else {