Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ultra wide angle camera option #68

Merged
merged 21 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/android/CameraPreviewFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.camera2.CameraCharacteristics;
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Size;
import android.view.Display;
Expand All @@ -18,7 +21,9 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
Expand All @@ -36,6 +41,9 @@

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

Expand All @@ -55,6 +63,14 @@ interface HasFlashCallback {
void onResult(boolean result);
}

interface CameraSwitchedCallback {
void onSwitch(boolean result);
}

interface HasUltraWideCameraCallback {
void onResult(boolean result);
}

public class CameraPreviewFragment extends Fragment {

private PreviewView viewFinder;
Expand All @@ -69,6 +85,8 @@ public class CameraPreviewFragment extends Fragment {

private static float ratio = (4 / (float) 3);
private static final String TAG = "SimpleCameraPreview";
private float minZoomRatio;
private float maxZoomRatio;

public CameraPreviewFragment() {

Expand Down Expand Up @@ -157,6 +175,47 @@ public void startCamera() {
}
}

@SuppressLint("RestrictedApi")
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
public void deviceHasUltraWideCamera(HasUltraWideCameraCallback hasUltraWideCameraCallback) {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity());
ProcessCameraProvider cameraProvider = null;

try {
cameraProvider = cameraProviderFuture.get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Error occurred while trying to obtain the camera provider: " + e.getMessage());
e.printStackTrace();
return;
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
}
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();

boolean defaultCamera = false;
boolean ultraWideCamera = false;
List<Camera2CameraInfoImpl> backCameras = new ArrayList<>();
for (CameraInfo cameraInfo : cameraInfos) {
if (cameraInfo instanceof Camera2CameraInfoImpl) {
Camera2CameraInfoImpl camera2CameraInfo = (Camera2CameraInfoImpl) cameraInfo;
if (camera2CameraInfo.getLensFacing() == CameraSelector.LENS_FACING_BACK) {
backCameras.add(camera2CameraInfo);
}
}
}

for (Camera2CameraInfoImpl backCamera : backCameras) {
if (backCamera.getCameraCharacteristicsCompat().get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0] >= 2.4) {
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
defaultCamera = true;
} else if( backCamera.getCameraCharacteristicsCompat().get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0] < 2.4) {
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
ultraWideCamera = true;
}
}

if (defaultCamera == true && ultraWideCamera == true) {
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
dinitri marked this conversation as resolved.
Show resolved Hide resolved
hasUltraWideCameraCallback.onResult(true);
} else {
hasUltraWideCameraCallback.onResult(false);
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
}
}

public static Size calculateResolution(Context context, int targetSize) {
Size calculatedSize;
if (getScreenOrientation(context) == Configuration.ORIENTATION_PORTRAIT) {
Expand Down Expand Up @@ -309,4 +368,92 @@ public void setLocation(Location loc) {
this.location = loc;
}
}


public void switchToUltraWideCamera(String device, CameraSwitchedCallback cameraSwitchedCallback) {
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
Handler mainHandler = new Handler(Looper.getMainLooper());
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
mainHandler.post(new Runnable() {
@SuppressLint("RestrictedApi")
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void run() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity());
ProcessCameraProvider cameraProvider = null;
CameraSelector cameraSelector;
try {
cameraProvider = cameraProviderFuture.get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Error occurred while trying to obtain the camera provider: " + e.getMessage());
e.printStackTrace();
cameraSwitchedCallback.onSwitch(false);
return;
}

if (device.equals("default")) {
cameraSelector = new CameraSelector.Builder()
.requireLensFacing(direction)
.build();
} else {
cameraSelector = new CameraSelector.Builder()
.addCameraFilter(cameraInfos -> {
List<Camera2CameraInfoImpl> backCameras = new ArrayList<>();
for (CameraInfo cameraInfo : cameraInfos) {
if (cameraInfo instanceof Camera2CameraInfoImpl) {
Camera2CameraInfoImpl camera2CameraInfo = (Camera2CameraInfoImpl) cameraInfo;
if (camera2CameraInfo.getLensFacing() == CameraSelector.LENS_FACING_BACK) {
backCameras.add(camera2CameraInfo);
}
}
}

Camera2CameraInfoImpl selectedCamera = Collections.min(backCameras, (o1, o2) -> {
Float focalLength1 = o1.getCameraCharacteristicsCompat().get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0];
Float focalLength2 = o2.getCameraCharacteristicsCompat().get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0];
return Float.compare(focalLength1, focalLength2);
});

if (selectedCamera != null) {
return Collections.singletonList(selectedCamera);
} else {
return cameraInfos;
}
})
.build();
}

Size targetResolution = null;
if (targetSize > 0) {
targetResolution = CameraPreviewFragment.calculateResolution(getContext(), targetSize);
}

preview = new Preview.Builder().build();
imageCapture = new ImageCapture.Builder()
.setTargetResolution(targetResolution)
.build();

cameraProvider.unbindAll();
try {
camera = cameraProvider.bindToLifecycle(
getActivity(),
cameraSelector,
preview,
imageCapture
);
} catch (IllegalArgumentException e) {
// Error with result in capturing image with default resolution
e.printStackTrace();
imageCapture = new ImageCapture.Builder()
.build();
camera = cameraProvider.bindToLifecycle(
getActivity(),
cameraSelector,
preview,
imageCapture
);
}
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved

preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
cameraSwitchedCallback.onSwitch(true);
}
});
}
}
28 changes: 28 additions & 0 deletions src/android/SimpleCameraPreview.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo

case "deviceHasFlash":
return deviceHasFlash(callbackContext);

case "deviceHasUltraWideCamera":
return deviceHasUltraWideCamera(callbackContext);

case "switchToUltraWideCamera":
return switchToUltraWideCamera(args.getString(0), callbackContext);
default:
break;
}
Expand Down Expand Up @@ -272,6 +278,14 @@ private boolean deviceHasFlash(CallbackContext callbackContext) {
return true;
}

private boolean deviceHasUltraWideCamera(CallbackContext callbackContext) {
fragment.deviceHasUltraWideCamera((boolean result) -> {
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
callbackContext.sendPluginResult(pluginResult);
});
return true;
}

private boolean torchSwitch(boolean torchState, CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed, cannot switch " + torchState + " torch");
Expand Down Expand Up @@ -323,6 +337,19 @@ public void run() {
}
}

private boolean switchToUltraWideCamera(String device, CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed, cannot switch camera");
return true;
}

fragment.switchToUltraWideCamera(device, (boolean result) -> {
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
callbackContext.sendPluginResult(pluginResult);
});
return true;
}

public boolean hasAllPermissions() {
for(String p : REQUIRED_PERMISSIONS) {
if(!PermissionHelper.hasPermission(this, p)) {
Expand Down Expand Up @@ -395,6 +422,7 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int
}
}
}


@Override
public void onDestroy() {
Expand Down
2 changes: 2 additions & 0 deletions src/ios/CameraSessionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- (void) setupSession:(NSString *)defaultCamera completion:(void(^)(BOOL started))completion options:(NSDictionary *)options photoSettings:(AVCapturePhotoSettings *)photoSettings;
- (void) setFlashMode:(NSInteger)flashMode photoSettings:(AVCapturePhotoSettings *)photoSettings;
- (void) torchSwitch:(NSInteger)torchState;
- (void) switchToUltraWideCamera:(NSString *)cameraMode completion:(void (^)(BOOL success))completion;
- (BOOL) deviceHasUltraWideCamera;
- (void) updateOrientation:(AVCaptureVideoOrientation)orientation;
- (AVCaptureVideoOrientation) getCurrentOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
+ (AVCaptureSessionPreset) calculateResolution:(NSInteger)targetSize;
Expand Down
80 changes: 77 additions & 3 deletions src/ios/CameraSessionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ - (void) setupSession:(NSString *)defaultCamera completion:(void(^)(BOOL started
self.defaultCamera = AVCaptureDevicePositionBack;
}

AVCaptureDevice *videoDevice = [self cameraWithPosition: self.defaultCamera];
AVCaptureDevice *videoDevice;
videoDevice = [self cameraWithPosition: self.defaultCamera captureDeviceType: AVCaptureDeviceTypeBuiltInWideAngleCamera];
if ([options[@"captureDevice"] isEqual: @"wide-angle"]) {
if ([self deviceHasUltraWideCamera]) {
if (@available(iOS 13.0, *)) {
videoDevice = [self cameraWithPosition: self.defaultCamera captureDeviceType: AVCaptureDeviceTypeBuiltInUltraWideCamera];
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
}
}

}

if ([videoDevice hasFlash]) {
if ([videoDevice lockForConfiguration:&error]) {
Expand Down Expand Up @@ -167,6 +176,71 @@ - (void) torchSwitch:(NSInteger)torchState{
}
}

- (void)switchToUltraWideCamera:(NSString*)cameraMode completion:(void (^)(BOOL success))completion {
if (![self deviceHasUltraWideCamera]) {
if (completion) {
completion(NO);
}
return;
}

dispatch_async(self.sessionQueue, ^{
BOOL cameraSwitched = FALSE;
if (@available(iOS 13.0, *)) {
AVCaptureDevice *ultraWideCamera;
if([cameraMode isEqualToString:@"wide-angle"]) {
ultraWideCamera = [self cameraWithPosition:self.defaultCamera captureDeviceType:AVCaptureDeviceTypeBuiltInUltraWideCamera];
} else {
ultraWideCamera = [self cameraWithPosition:self.defaultCamera captureDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera];
}
if (ultraWideCamera) {
// Remove the current input
[self.session removeInput:self.videoDeviceInput];

// Create a new input with the ultra-wide camera
NSError *error = nil;
AVCaptureDeviceInput *ultraWideVideoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:ultraWideCamera error:&error];

if (!error) {
// Add the new input to the session
if ([self.session canAddInput:ultraWideVideoDeviceInput]) {
[self.session addInput:ultraWideVideoDeviceInput];
self.videoDeviceInput = ultraWideVideoDeviceInput;
__block AVCaptureVideoOrientation orientation;
dispatch_sync(dispatch_get_main_queue(), ^{
orientation = [self getCurrentOrientation];
});
[self updateOrientation:orientation];
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
cameraSwitched = TRUE;
} else {
NSLog(@"Failed to add ultra-wide input to session");
}
} else {
NSLog(@"Error creating ultra-wide device input: %@", error.localizedDescription);
}
} else {
NSLog(@"Ultra-wide camera not found");
}
} else {
// Fallback on earlier versions
}

completion ? completion(cameraSwitched): NULL;
});
}

- (BOOL)deviceHasUltraWideCamera {
NSArray *devices;
if (@available(iOS 13.0, *)) {
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInUltraWideCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
devices = discoverySession.devices;
} else {
// Fallback on earlier versions
parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
}

return devices.count > 0;
}

parveshneedhoo marked this conversation as resolved.
Show resolved Hide resolved
- (void)setFlashMode:(NSInteger)flashMode photoSettings:(AVCapturePhotoSettings *)photoSettings {
NSError *error = nil;
// Let's save the setting even if we can't set it up on this camera.
Expand All @@ -186,8 +260,8 @@ - (void)setFlashMode:(NSInteger)flashMode photoSettings:(AVCapturePhotoSettings
}
}
// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position {
AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:self.defaultCamera];
- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position captureDeviceType:(AVCaptureDeviceType) captureDeviceType {
AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[ captureDeviceType] mediaType:AVMediaTypeVideo position:self.defaultCamera];
NSArray *devices = [captureDeviceDiscoverySession devices];
for (AVCaptureDevice *device in devices){
if ([device position] == position)
Expand Down
2 changes: 2 additions & 0 deletions src/ios/SimpleCameraPreview.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- (void) capture:(CDVInvokedUrlCommand*)command;
- (void) setSize:(CDVInvokedUrlCommand*)command;
- (void) torchSwitch: (CDVInvokedUrlCommand*)command;
- (void) switchToUltraWideCamera: (CDVInvokedUrlCommand*) command;
- (void) deviceHasUltraWideCamera: (CDVInvokedUrlCommand*) command;
- (void) deviceHasFlash: (CDVInvokedUrlCommand*)command;
@property (nonatomic) CameraSessionManager *sessionManager;
@property (nonatomic) CameraRenderController *cameraRenderController;
Expand Down
Loading
Loading