Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Adds support for different camera types #21

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Above is a very barebones version of the scanner. Check out a full example in [e
| Prop | Default | Type | Description |
| :-------------------------- | :-----: | :-------: | :--------------------------------------------------------- |
| filterId | `none` | `integer` | The id of the filter to use. [See More](#filters) |
| cameraId | `none` | `integer` | The id of the camera to use. [See More](#cameras) |
| enableTorch | `false` | `bool` | If the flashlight should be turned on |
| capturedQuality | `0.5` | `float` | The jpeg quality of the output images |
| onTorchChanged | `null` | `func` | Called when the system changes the flash state |
Expand Down Expand Up @@ -146,7 +147,19 @@ NOTE: There is no UI changes when you capture an image. No screen flash, only a

**NOTE**: captured images are stored in the app's cache directory under the `CACHE_FOLDER_NAME`. This allows you to clear the cached images when you are done. (This is advised although these may get deleted by the system.)

**NOTE**: on iOS, it will try to correct the rotation of the image. If you are in portrait mode, but the phone is rotated to landscape, it will rotate the captured image automatically.
**NOTE**: on iOS, it will try to correct the rotation of the image. If you are in portrait mode, but the phone is rotated to landscape, it will rotate the captured image automatically.

### Cameras
This packages supports using different camera types. Currently there is support for at least 2 camera types (Front and Back) with the potential to support additional (Telephoto and UltraWide). The default is the generic back camera. The supported camera types will be returned in the `onDeviceSetup({ supportedCameras })` callback. **NOTE**: On iOS you can switch the camera without unmounting the component, on Android, you need to unmount and remount the component(I'm hoping to find a fix for this).

Camera devices. You can import this list via `import { POSSIBLE_CAMERA_TYPES } from 'react-native-rectangle-scanner'` or individually by the names below.

| ID | Name | Import Name |
| -- | ---------- | -------------------------- |
| 1 | Back | `BACK_FACING_CAMERA_TYPE` |
| 2 | Front | `FRONT_FACING_CAMERA_TYPE` |
| 3 | Telephoto | `TELEPHOTO_CAMERA_TYPE` |
| 4 | UltraWide | `ULTRA_WIDE_CAMERA_TYPE` |

### Filters
Instead of allowing you to customize the contrast, saturation, etc of the image, I prebuilt the filters. This is because the filter controls are massively different between platforms and changing those values results in much different image outputs. Below are the avilable filters. Honestly, the color controls where pretty bad on android, so the best ones for android are probably just using the Color and Black & White instead of showing all 4 (they are only slightly better than Greyscale and the original photo).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public void setFilterId(MainView view, int filterId) {
view.setFilterId(filterId);
}

@ReactProp(name = "cameraId", defaultInt = 1)
public void setCameraId(MainView view, int cameraId) {
view.setCameraId(cameraId);
}

// Life cycle Events
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import android.content.res.Configuration;
import android.widget.FrameLayout;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.rectanglescanner.R;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
Expand Down Expand Up @@ -57,10 +60,17 @@ public class CameraDeviceController extends JavaCameraView implements PictureCal
protected boolean isStopped = true;
private WritableMap deviceConfiguration = new WritableNativeMap();
private int captureDevice = -1;
private int cameraId = 1;
private boolean imageProcessorBusy = true;

private static CameraDeviceController mThis;

public int getCameraId() {
return this.cameraId;
}



public CameraDeviceController(Context context, AttributeSet attrs) {
super(context, attrs);
}
Expand All @@ -79,6 +89,13 @@ public CameraDeviceController(Context context, Integer numCam, Activity activity
// Setters
//================================================================================

/**
Sets the currently active camera
*/
public void setCameraId(int cameraId) {
this.cameraId = cameraId;
}

public boolean isFocused() {
return this.mFocused;
}
Expand Down Expand Up @@ -126,8 +143,8 @@ private void refreshCamera() {
Starts the capture session
*/
public void startCamera() {
Log.d(TAG, "Starting preview");
if (this.isStopped) {
Log.d(TAG, "Starting preview");
try {
if (!this.cameraIsSetup) {
setupCameraView();
Expand Down Expand Up @@ -190,7 +207,15 @@ public void setDeviceConfigurationPreviewPercentSize(double heightPercent, doubl
}

/**
Sets the inital device configuration
* Sets the supported camera type ids
* @param supportedCameras
*/
public void setDeviceConfigurationSupportedCameras(ReadableArray supportedCameras) {
this.deviceConfiguration.putArray("supportedCameras", supportedCameras);
}

/**
Sets the initial device configuration
*/
public void resetDeviceConfiguration()
{
Expand All @@ -199,6 +224,7 @@ public void resetDeviceConfiguration()
setDeviceConfigurationPermissionToUseCamera(false);
setDeviceConfigurationHasCamera(false);
setDeviceConfigurationPreviewPercentSize(1.0, 1.0);
setDeviceConfigurationSupportedCameras(Arguments.createArray());
}

/**
Expand All @@ -214,20 +240,57 @@ protected void deviceWasSetup(WritableMap config) {}
// Getters
//================================================================================

private void searchForAvailableCameraTypes() {
WritableArray availableCameras = Arguments.createArray();
boolean foundBackFacing = false;
boolean foundFrontFacing = false;

// Search for the back facing camera
// get the number of cameras
int numberOfCameras = Camera.getNumberOfCameras();
// for every camera check
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);

if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && !foundBackFacing) {
availableCameras.pushInt(1);
foundBackFacing = true;
}

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && !foundFrontFacing) {
availableCameras.pushInt(2);
foundFrontFacing = true;
}

if (foundBackFacing && foundFrontFacing) {
break;
}
}
setDeviceConfigurationSupportedCameras(availableCameras);
}

private int getCameraDevice() {
int cameraId = -1;


// Search for the back facing camera
// get the number of cameras
int numberOfCameras = Camera.getNumberOfCameras();
// for every camera check
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {

if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && this.getCameraId() == 1) {
cameraId = i;
break;
}

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && this.getCameraId() == 2) {
cameraId = i;
break;
}
cameraId = i;
}
return cameraId;
}
Expand Down Expand Up @@ -418,13 +481,15 @@ public boolean setupCaptureDevice() {
this.captureDevice = getCameraDevice();

try {
int cameraId = getCameraDevice();
int cameraId = this.captureDevice;
if (cameraId < 0) return false;
mCamera = Camera.open(cameraId);
} catch (RuntimeException e) {
System.err.println(e);
return false;
}
setDeviceConfigurationHasCamera(true);
searchForAvailableCameraTypes();

PackageManager pm = mActivity.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public void setCapturedQuality(double quality) {
public void setFilterId(int filterId) {
view.setFilterId(filterId);
}
public void setCameraId(int cameraId) {
view.setCameraId(cameraId);
}

public void startCamera() {
view.startCamera();
Expand Down
1 change: 1 addition & 0 deletions ios/CameraDeviceController.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
@property (nonatomic, assign) UIInterfaceOrientation lastInterfaceOrientation;

@property (nonatomic, assign) int filterId;
@property (nonatomic, assign) int cameraId;
@property (nonatomic,strong) EAGLContext *context;
@property (nonatomic, strong) CIContext *_coreImageContext;

Expand Down
89 changes: 88 additions & 1 deletion ios/CameraDeviceController.m
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ - (void)setFilterId:(int)filterId
_filterId = filterId;
}

/*!
Sets the currently active camera
*/
- (void)setCameraId:(int)cameraId
{
_cameraId = cameraId;
if(self._cameraIsSetup) {
self._cameraIsSetup = FALSE;
[self stop];
[self setupCameraView];
[self start];
}
}

/*!
Sets the device configuration flash setting
*/
Expand All @@ -189,6 +203,18 @@ - (void)_setDeviceConfigurationHasCamera: (BOOL) isAvailable{
[_deviceConfiguration setValue:isAvailable ? @TRUE : @FALSE forKey:@"hasCamera"];
}

/*!
Sets the device configuration supported cameras
*/
- (void)_setDeviceConfigurationSupportedCameras {
NSMutableArray *supportedCameras = [NSMutableArray array];
if ([self getDefaultBackFacingCamera]) [supportedCameras addObject:@(1)];
if ([self getFrontFacingCamera]) [supportedCameras addObject:@(2)];
if ([self getTelephotoBackFacingCamera]) [supportedCameras addObject:@(3)];
if ([self getUltraWideAngleBackFacingCamera]) [supportedCameras addObject:@(4)];
[_deviceConfiguration setValue: supportedCameras forKey: @"supportedCameras"];
}

/*!
Sets the inital device configuration
*/
Expand All @@ -200,6 +226,7 @@ - (void)_resetDeviceConfiguration
[self _setDeviceConfigurationHasCamera:NO];
[_deviceConfiguration setValue: @1.0 forKey: @"previewHeightPercent"];
[_deviceConfiguration setValue: @1.0 forKey: @"previewWidthPercent"];
[_deviceConfiguration setValue: @[] forKey: @"supportedCameras"];
}

/*!
Expand Down Expand Up @@ -295,14 +322,73 @@ - (int)getCGImageOrientationForCaptureImage
}
}

/*!
Searches for a generic wide angle camera on the back of the phone
*/
- (AVCaptureDevice *)getDefaultBackFacingCamera{
AVCaptureDevice* possibleDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];

if (possibleDevice) return possibleDevice;
return nil;
}

/*!
Searches for a generic wide angle camera on the front of the phone
*/
- (AVCaptureDevice *)getFrontFacingCamera{
AVCaptureDevice* possibleDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];

if (possibleDevice) return possibleDevice;
return nil;
}

/*!
Searches for the ultra wide angle camera on the back of the phone
@note This may need to be refactored to actually find the desired camera
*/
- (AVCaptureDevice *)getUltraWideAngleBackFacingCamera{
if (@available(iOS 13.0, *)) {
AVCaptureDevice* possibleDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInUltraWideCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];

if (possibleDevice) return possibleDevice;
}
return nil;
}

/*!
Searches for the telephoto camera on the back of the phone
@note This may need to be refactored to actually find the desired camera
*/
- (AVCaptureDevice *)getTelephotoBackFacingCamera{
AVCaptureDevice* possibleDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInTelephotoCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];

if (possibleDevice) return possibleDevice;
return nil;
}

/*!
Gets a hardware camera device.
@return A camera hardware object or nil if not found
*/
- (AVCaptureDevice *)getCameraDevice{
AVCaptureDevice* possibleDevice;
possibleDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];

switch (self.cameraId) {
case 1:
possibleDevice = [self getDefaultBackFacingCamera];
break;
case 2:
possibleDevice = [self getFrontFacingCamera];
break;
case 3:
possibleDevice = [self getTelephotoBackFacingCamera];
break;
case 4:
possibleDevice = [self getUltraWideAngleBackFacingCamera];
break;
default:
possibleDevice = [self getDefaultBackFacingCamera];
}
if (possibleDevice) return possibleDevice;

return nil;
Expand Down Expand Up @@ -389,6 +475,7 @@ - (AVCaptureDevice *)setupCaptureDevice{
if (!self.captureDevice) return nil;
[self _setDeviceConfigurationHasCamera:YES];
[self _setDeviceConfigurationFlashAvailable:([self.captureDevice hasTorch] && [self.captureDevice hasFlash])];
[self _setDeviceConfigurationSupportedCameras];

// Setup camera focus mode
if ([self.captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus])
Expand Down
5 changes: 5 additions & 0 deletions ios/RNRectangleScannerManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Determines what filter id to use (1, 2, 3, or 4)
*/
RCT_EXPORT_VIEW_PROPERTY(filterId, int)

/*!
Determines what camera id to use. Depends on the phone type
*/
RCT_EXPORT_VIEW_PROPERTY(cameraId, int)


// MARK: Life cycle Actions
/*!
Expand Down
Loading