Skip to content

Commit

Permalink
fix: copy exif info of source image into compressed image
Browse files Browse the repository at this point in the history
  • Loading branch information
numandev1 committed Nov 4, 2023
1 parent 7562b32 commit a557f67
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
181 changes: 161 additions & 20 deletions android/src/main/java/com/reactnativecompressor/Utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
38 changes: 36 additions & 2 deletions ios/Image/ImageCompressor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -135,7 +166,10 @@ class ImageCompressor {
}

}


data=copyExifInfo(actualImagePath: actualImagePath, image: image, data: data)


if isBase64 {
return data.base64EncodedString(options: [])
} else {
Expand Down

0 comments on commit a557f67

Please sign in to comment.