From b189b1db8e07b4ef0146f943863f5ad730c7b019 Mon Sep 17 00:00:00 2001 From: Muhammad Numan Date: Thu, 5 Oct 2023 16:17:10 +0500 Subject: [PATCH] fix: png transparent background --- README.md | 7 +- .../Image/ImageCompressor.kt | 45 +++++++------ .../Image/ImageCompressorOptions.kt | 2 + example/ios/Podfile.lock | 4 +- example/src/Screens/Image/index.tsx | 6 +- ios/Image/ImageCompressor.swift | 67 +++++++------------ ios/Image/ImageCompressorOptions.swift | 3 + src/Image/index.tsx | 4 ++ 8 files changed, 71 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 81b8272..1d472b9 100644 --- a/README.md +++ b/README.md @@ -362,7 +362,12 @@ await clearCache(); // this will clear cache of thumbnails cache directory - ###### `output: OutputType` (default: jpg) - Can be either `jpg` or `png`, defines the output image format. + The quality modifier for the `JPEG` file format, can be specified when output is `PNG` but will be ignored. if you wanna apply quality modifier then you can enable `disablePngTransparency:true`, + **Note:** if you png image have no transparent background then enable `disablePngTransparency:true` modifier is recommended + +- ###### `disablePngTransparency: boolean` (default: false) + + when user add `output:'png'` then by default compressed image will have transparent background, and quality will be ignored, if you wanna apply quality then you have to disablePngTransparency like `disablePngTransparency:true`, it will convert transparent background to white - ###### `returnableOutputType: ReturnableOutputType` (default: uri) Can be either `uri` or `base64`, defines the Returnable output image format. diff --git a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt index ac14f69..519f0ff 100644 --- a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +++ b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt @@ -71,20 +71,20 @@ object ImageCompressor { } fun resize(image: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap { - val size = findActualSize(image, maxWidth, maxHeight) - val scaledImage = Bitmap.createBitmap(size.width, size.height, image.config) - val scaleMatrix = Matrix() - val canvas = Canvas(scaledImage) - val paint = Paint(Paint.FILTER_BITMAP_FLAG) - scaleMatrix.setScale(size.scale, size.scale, 0f, 0f) - paint.isDither = true - paint.isAntiAlias = true - paint.isFilterBitmap = true - canvas.drawBitmap(image, scaleMatrix, paint) - return scaledImage + val size = findActualSize(image, maxWidth, maxHeight) + val scaledImage = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888) + val scaleMatrix = Matrix() + val canvas = Canvas(scaledImage) + val paint = Paint(Paint.FILTER_BITMAP_FLAG) + scaleMatrix.setScale(size.scale, size.scale, 0f, 0f) + paint.isDither = true + paint.isAntiAlias = true + paint.isFilterBitmap = true + canvas.drawBitmap(image, scaleMatrix, paint) + return scaledImage } - fun compress(image: Bitmap?, output: ImageCompressorOptions.OutputType, quality: Float): ByteArrayOutputStream { + fun compress(image: Bitmap?, output: ImageCompressorOptions.OutputType, quality: Float,disablePngTransparency:Boolean): ByteArrayOutputStream { var stream = ByteArrayOutputStream() if (output === ImageCompressorOptions.OutputType.jpg) { @@ -92,12 +92,15 @@ object ImageCompressor { } else { - val pngStream = ByteArrayOutputStream() - image!!.compress(CompressFormat.JPEG, Math.round(100 * quality), stream) - val byteArray: ByteArray = stream.toByteArray() - stream=ByteArrayOutputStream() - val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) - bitmap.compress(CompressFormat.PNG, 100, stream) + var bitmap = image + if(disablePngTransparency) + { + image!!.compress(CompressFormat.JPEG, Math.round(100 * quality), stream) + val byteArray: ByteArray = stream.toByteArray() + stream=ByteArrayOutputStream() + bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + } + bitmap!!.compress(CompressFormat.PNG, 100, stream) } return stream } @@ -105,7 +108,7 @@ object ImageCompressor { fun manualCompressImage(imagePath: String?, options: ImageCompressorOptions, reactContext: ReactApplicationContext?): String? { val image = if (options.input === ImageCompressorOptions.InputType.base64) decodeImage(imagePath) else loadImage(imagePath) val resizedImage = resize(image, options.maxWidth, options.maxHeight) - val imageDataByteArrayOutputStream = compress(resizedImage, options.output, options.quality) + 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) } @@ -152,7 +155,7 @@ object ImageCompressor { exception.printStackTrace() } try { - scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.RGB_565) + scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888) } catch (exception: OutOfMemoryError) { exception.printStackTrace() } @@ -169,7 +172,7 @@ object ImageCompressor { bmp.recycle() } scaledBitmap = correctImageOrientation(scaledBitmap, imagePath) - val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality) + val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality,compressorOptions.disablePngTransparency) return encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(), reactContext) } diff --git a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt index ef3e13f..6aa884c 100644 --- a/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt +++ b/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt @@ -32,6 +32,7 @@ class ImageCompressorOptions { var output = OutputType.jpg var uuid: String? = "" var returnableOutputType = ReturnableOutputType.uri + var disablePngTransparency:Boolean = false companion object { fun fromMap(map: ReadableMap): ImageCompressorOptions { @@ -49,6 +50,7 @@ class ImageCompressorOptions { "output" -> options.output = OutputType.valueOf(map.getString(key)!!) "returnableOutputType" -> options.returnableOutputType = ReturnableOutputType.valueOf(map.getString(key)!!) "uuid" -> options.uuid = map.getString(key) + "disablePngTransparency" -> options.disablePngTransparency = map.getBoolean(key) } } return options diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f7a36e0..9159667 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -881,7 +881,7 @@ PODS: - React-Codegen - React-RCTFabric - ReactCommon/turbomodule/core - - react-native-compressor (1.8.8): + - react-native-compressor (1.8.11): - hermes-engine - NextLevelSessionExporter - RCT-Folly (= 2021.07.22.00) @@ -1381,7 +1381,7 @@ SPEC CHECKSUMS: React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 react-native-cameraroll: 5d9523136a929b58f092fd7f0a9a13367a4b46e3 - react-native-compressor: 8de39845e794d65b089161278ba0d185d269f1eb + react-native-compressor: f807dbe3d5dac8cc2efab2adf8bed7949f70d5a9 react-native-document-picker: c9ac93d7b511413f4a0ed61c92ff6c7b1bcf4f94 react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb react-native-image-picker: 9b4b1d0096500050cbdabf8f4fd00b771065d983 diff --git a/example/src/Screens/Image/index.tsx b/example/src/Screens/Image/index.tsx index 5eef3ba..389a019 100644 --- a/example/src/Screens/Image/index.tsx +++ b/example/src/Screens/Image/index.tsx @@ -40,7 +40,11 @@ const Index = () => { setMimeType(source.type); setOrignalUri(source.uri); } - Image.compress(source.uri) + Image.compress(source.uri, { + output: 'png', + maxHeight: 200, + disablePngTransparency: true, + }) .then(async (compressedFileUri) => { console.log(compressedFileUri, 'compressedFileUri'); setCommpressedUri(compressedFileUri); diff --git a/ios/Image/ImageCompressor.swift b/ios/Image/ImageCompressor.swift index 9567ada..7d8a04a 100644 --- a/ios/Image/ImageCompressor.swift +++ b/ios/Image/ImageCompressor.swift @@ -100,20 +100,25 @@ class ImageCompressor { } return UIImage() } - - static func manualCompress(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool) -> String { + static func writeImage(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool,disablePngTransparency:Bool)-> String{ var data: Data var exception: NSException? - switch OutputType(rawValue: output)! { case .jpg: data = image.jpegData(compressionQuality: CGFloat(quality))! case .png: - data = image.pngData()! - data = image.jpegData(compressionQuality: CGFloat(quality))! - let compressedImage = UIImage(data: data) - data = compressedImage!.pngData()! + if(disablePngTransparency) + { + data = image.jpegData(compressionQuality: CGFloat(quality))! + let compressedImage = UIImage(data: data) + data = compressedImage!.pngData()! + } + else + { + data=image.pngData()! + } + } if isBase64 { @@ -129,11 +134,15 @@ class ImageCompressor { exception?.raise() } } - return "" } + static func manualCompress(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool,disablePngTransparency:Bool) -> String { + return writeImage(image, output: output, quality: quality, outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: disablePngTransparency) + } + + static func scaleAndRotateImage(_ image: UIImage) -> UIImage { if image.imageOrientation == .up { return image @@ -215,7 +224,7 @@ class ImageCompressor { let outputExtension = ImageCompressorOptions.getOutputInString(options.output) let resizedImage = ImageCompressor.manualResize(_image, maxWidth: options.maxWidth, maxHeight: options.maxHeight) let isBase64 = options.returnableOutputType == .rbase64 - return ImageCompressor.manualCompress(resizedImage, output: options.output.rawValue, quality: options.quality, outputExtension: outputExtension, isBase64: isBase64) + return ImageCompressor.manualCompress(resizedImage, output: options.output.rawValue, quality: options.quality, outputExtension: outputExtension, isBase64: isBase64,disablePngTransparency: options.disablePngTransparency) } else { exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil) exception?.raise() @@ -228,11 +237,11 @@ class ImageCompressor { static func autoCompressHandler(_ imagePath: String, options: ImageCompressorOptions) -> String { var exception: NSException? var image = ImageCompressor.loadImage(imagePath) - + if var image = image { image = ImageCompressor.scaleAndRotateImage(image) let outputExtension = ImageCompressorOptions.getOutputInString(options.output) - + var actualHeight = image.size.height var actualWidth = image.size.width let maxHeight: CGFloat = CGFloat(options.maxHeight) @@ -240,7 +249,7 @@ class ImageCompressor { var imgRatio = actualWidth / actualHeight let maxRatio = maxWidth / maxHeight let compressionQuality: CGFloat = CGFloat(options.quality) - + if actualHeight > maxHeight || actualWidth > maxWidth { if imgRatio < maxRatio { imgRatio = maxHeight / actualHeight @@ -255,42 +264,16 @@ class ImageCompressor { actualWidth = maxWidth } } - + let rect = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight) UIGraphicsBeginImageContext(rect.size) image.draw(in: rect) + let isBase64 = options.returnableOutputType == .rbase64 + if let img = UIGraphicsGetImageFromCurrentImageContext() { - var imageData: Data - switch OutputType(rawValue: options.output.rawValue)! { - case .jpg: - imageData = img.jpegData(compressionQuality: compressionQuality)! - case .png: - imageData = img.jpegData(compressionQuality: compressionQuality)! - let compressedImage = UIImage(data: imageData) - imageData = compressedImage!.pngData()! - } - - UIGraphicsEndImageContext() - let isBase64 = options.returnableOutputType == .rbase64 - if isBase64 { - return imageData.base64EncodedString(options: []) - } else { - let filePath = Utils.generateCacheFilePath(outputExtension) - do { - try imageData.write(to: URL(fileURLWithPath: filePath), options: .atomic) - let returnablePath = ImageCompressor.makeValidUri(filePath) - return returnablePath - } catch { - exception = NSException(name: NSExceptionName(rawValue: "file_error"), reason: "Error writing file", userInfo: nil) - exception?.raise() - } - } + return writeImage(img, output: options.output.rawValue, quality: Float(compressionQuality), outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: options.disablePngTransparency) } - } else { - exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil) - exception?.raise() } - return "" } diff --git a/ios/Image/ImageCompressorOptions.swift b/ios/Image/ImageCompressorOptions.swift index f9e8d1a..d45b88d 100644 --- a/ios/Image/ImageCompressorOptions.swift +++ b/ios/Image/ImageCompressorOptions.swift @@ -44,6 +44,8 @@ class ImageCompressorOptions: NSObject { options.parseReturnableOutput(value as? String) case "uuid": options.uuid = value as? String + case "disablePngTransparency": + options.disablePngTransparency = value as? Bool ?? false default: break } @@ -65,6 +67,7 @@ class ImageCompressorOptions: NSObject { var output: OutputType = .jpg var returnableOutputType: ReturnableOutputType = .ruri var uuid: String? + var disablePngTransparency: Bool = false override init() { super.init() diff --git a/src/Image/index.tsx b/src/Image/index.tsx index cd5f677..b1bc4c0 100644 --- a/src/Image/index.tsx +++ b/src/Image/index.tsx @@ -37,6 +37,10 @@ export type CompressorOptions = { * The output image type. */ output?: OutputType; + /*** + * when user add `output:'png'` then by default compressed image will have transparent background, and quality will be ignored, if you wanna apply quality then you have to disablePngTransparency like `disablePngTransparency:true`, it will convert transparent background to white + */ + disablePngTransparency?: boolean; /*** * The output that will return to user. */