diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java index 0cb7491edb..01fa8a98fb 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java @@ -182,6 +182,7 @@ private CameraSettings getSettings(PluginCall call) { settings.setHeight(call.getInt("height", 0)); settings.setShouldResize(settings.getWidth() > 0 || settings.getHeight() > 0); settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION)); + settings.setPreserveAspectRatio(call.getBoolean("preserveAspectRatio", false)); try { settings.setSource(CameraSource.valueOf(call.getString("source", CameraSource.PROMPT.getSource()))); } catch (IllegalArgumentException ex) { @@ -410,7 +411,12 @@ private Bitmap prepareBitmap(Bitmap bitmap, Uri imageUri) throws IOException { } if (settings.isShouldResize()) { - final Bitmap newBitmap = ImageUtils.resize(bitmap, settings.getWidth(), settings.getHeight()); + final Bitmap newBitmap = ImageUtils.resize( + bitmap, + settings.getWidth(), + settings.getHeight(), + settings.getPreserveAspectRatio() + ); bitmap = replaceBitmap(bitmap, newBitmap); } return bitmap; diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java index 5598ff713b..e239a454a8 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java @@ -15,6 +15,7 @@ public class CameraSettings { private int width = 0; private int height = 0; private CameraSource source = CameraSource.PROMPT; + private boolean preserveAspectRatio = false; public CameraResultType getResultType() { return resultType; @@ -83,4 +84,12 @@ public CameraSource getSource() { public void setSource(CameraSource source) { this.source = source; } + + public void setPreserveAspectRatio(boolean preserveAspectRatio) { + this.preserveAspectRatio = preserveAspectRatio; + } + + public boolean getPreserveAspectRatio() { + return this.preserveAspectRatio; + } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java index da121340b5..a70977b135 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java @@ -17,6 +17,32 @@ public class ImageUtils { + /** + * Resize an image to the given width and height. + * @param bitmap + * @param width + * @param height + * @return a new, scaled Bitmap + */ + public static Bitmap resize(Bitmap bitmap, final int width, final int height) { + return ImageUtils.resize(bitmap, width, height, false); + } + + /** + * Resize an image to the given width and height considering the preserveAspectRatio flag. + * @param bitmap + * @param width + * @param height + * @param preserveAspectRatio + * @return a new, scaled Bitmap + */ + public static Bitmap resize(Bitmap bitmap, final int width, final int height, final boolean preserveAspectRatio) { + if (preserveAspectRatio) { + return ImageUtils.resizePreservingAspectRatio(bitmap, width, height); + } + return ImageUtils.resizeImageWithoutPreservingAspectRatio(bitmap, width, height); + } + /** * Resize an image to the given width and height. Leave one dimension 0 to * perform an aspect-ratio scale on the provided dimension. @@ -25,7 +51,7 @@ public class ImageUtils { * @param height * @return a new, scaled Bitmap */ - public static Bitmap resize(Bitmap bitmap, final int width, final int height) { + private static Bitmap resizeImageWithoutPreservingAspectRatio(Bitmap bitmap, final int width, final int height) { float aspect = bitmap.getWidth() / (float) bitmap.getHeight(); if (width > 0 && height > 0) { return Bitmap.createScaledBitmap(bitmap, width, height, false); @@ -38,6 +64,33 @@ public static Bitmap resize(Bitmap bitmap, final int width, final int height) { return bitmap; } + /** + * Resize an image to the given max width and max height. Constraint can be put + * on one dimension, or both. Resize will always preserve aspect ratio. + * @param bitmap + * @param desiredMaxWidth + * @param desiredMaxHeight + * @return a new, scaled Bitmap + */ + private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + // 0 is treated as 'no restriction' + int maxHeight = desiredMaxHeight == 0 ? height : desiredMaxHeight; + int maxWidth = desiredMaxWidth == 0 ? width : desiredMaxWidth; + + // resize with preserved aspect ratio + float newWidth = Math.min(width, maxWidth); + float newHeight = (height * newWidth) / width; + + if (newHeight > maxHeight) { + newWidth = (width * maxHeight) / height; + newHeight = maxHeight; + } + return Bitmap.createScaledBitmap(bitmap, Math.round(newWidth), Math.round(newHeight), false); + } + /** * Transform an image with the given matrix * @param bitmap diff --git a/core/src/core-plugin-definitions.ts b/core/src/core-plugin-definitions.ts index de8f92ced1..f8a0618538 100644 --- a/core/src/core-plugin-definitions.ts +++ b/core/src/core-plugin-definitions.ts @@ -321,6 +321,18 @@ export interface CameraOptions { * The height of the saved image */ height?: number; + /** + * Whether to preserve the aspect ratio of the image. + * If this flag is true, the width and height will be used as max values + * and the aspect ratio will be preserved. This is only relevant when + * both a width and height are passed. When only width or height is provided + * the aspect ratio is always preserved (and this option is a no-op). + * + * A future major version will change this behavior to be default, + * and may also remove this option altogether. + * Default: false + */ + preserveAspectRatio?: boolean; /** * Whether to automatically rotate the image "up" to correct for orientation * in portrait mode diff --git a/example/src/pages/camera/camera.html b/example/src/pages/camera/camera.html index d55138ad4b..3e2fa75537 100644 --- a/example/src/pages/camera/camera.html +++ b/example/src/pages/camera/camera.html @@ -26,4 +26,5 @@ + diff --git a/example/src/pages/camera/camera.ts b/example/src/pages/camera/camera.ts index cb9b8c99eb..052e00eeec 100644 --- a/example/src/pages/camera/camera.ts +++ b/example/src/pages/camera/camera.ts @@ -187,4 +187,19 @@ export class CameraPage { console.log('Got image back', image.path, image.webPath, image.format, image.exif); this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl)); } + + async takePicturePreservingAspectRatio() { + const image = await Plugins.Camera.getPhoto({ + quality: 80, + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + saveToGallery: true, + correctOrientation: true, + height: 1920, + width: 1920, + preserveAspectRatio: true, + }); + console.log('Got image back', image.path, image.webPath, image.format, image.exif); + this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image.webPath); + } } diff --git a/ios/Capacitor/Capacitor/Plugins/Camera.swift b/ios/Capacitor/Capacitor/Plugins/Camera.swift index 35a9252810..5674f3b0f1 100644 --- a/ios/Capacitor/Capacitor/Plugins/Camera.swift +++ b/ios/Capacitor/Capacitor/Plugins/Camera.swift @@ -29,6 +29,7 @@ struct CameraSettings { var height: Float = 0 var resultType = "base64" var saveToGallery = false + var preserveAspectRatio = false } @objc(CAPCameraPlugin) @@ -72,7 +73,8 @@ public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINav settings.direction = CameraDirection(rawValue: call.getString("direction") ?? DEFAULT_DIRECTION.rawValue) ?? DEFAULT_DIRECTION settings.resultType = call.get("resultType", String.self, "base64")! settings.saveToGallery = call.get("saveToGallery", Bool.self, false)! - + settings.preserveAspectRatio = call.get("preserveAspectRatio", Bool.self, false)! + // Get the new image dimensions if provided settings.width = Float(call.get("width", Int.self, 0)!) settings.height = Float(call.get("height", Int.self, 0)!) @@ -229,7 +231,7 @@ public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINav } if settings.shouldResize { - guard let convertedImage = resizeImage(image!) else { + guard let convertedImage = resizeImage(image!, settings.preserveAspectRatio) else { self.call?.error("Error resizing image") return } @@ -312,7 +314,14 @@ public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINav return meta } - func resizeImage(_ image: UIImage) -> UIImage? { + func resizeImage(_ image: UIImage, _ preserveAspectRatio: Bool) -> UIImage? { + if preserveAspectRatio { + return resizeImagePreservingAspectRatio(image) + } + return resizeImageWithoutPreservingAspectRatio(image) + } + + func resizeImageWithoutPreservingAspectRatio(_ image: UIImage) -> UIImage? { let isAspectScale = settings.width > 0 && settings.height == 0 || settings.height > 0 && settings.width == 0 let aspect = Float(image.size.width / image.size.height); @@ -333,6 +342,31 @@ public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINav return scaledImage } + func resizeImagePreservingAspectRatio(_ image: UIImage) -> UIImage? { + let imageHeight = Float(image.size.height) + let imageWidth = Float(image.size.width) + + // 0 is treated as 'no restriction' + let maxHeight = settings.height == 0 ? imageHeight : settings.height + let maxWidth = settings.width == 0 ? imageWidth : settings.width + + // resize with preserved aspect ratio + var newWidth = min(imageWidth, maxWidth) + var newHeight = (imageHeight * newWidth) / imageWidth + if newHeight > maxHeight { + newWidth = (imageWidth * maxHeight) / imageHeight + newHeight = maxHeight + } + let size = CGSize.init(width: Int(newWidth), height: Int(newHeight)) + + UIGraphicsBeginImageContextWithOptions(size, false, 1.0) + image.draw(in: CGRect(origin: CGPoint.zero, size: size)) + + let scaledImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return scaledImage + } + func makeExif(_ exif: [AnyHashable:Any]?) -> [AnyHashable:Any]? { return exif?["{Exif}"] as? [AnyHashable:Any] }