Skip to content

Commit

Permalink
Add resize mode cover/contain (#644)
Browse files Browse the repository at this point in the history
* Add resize mode cover/contain

* Add resize to simulator, change type enum and add listener for resize

* Add resize to camera example

* Add description of resizeMode to readme

* Add description of resizeMode to readme

* Update ios/ReactNativeCameraKit/CameraView.swift

---------

Co-authored-by: Seph Soliman <[email protected]>
  • Loading branch information
lichstam and scarlac authored Apr 30, 2024
1 parent eb96b53 commit 80c517d
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 43 deletions.
59 changes: 30 additions & 29 deletions README.md

Large diffs are not rendered by default.

Binary file added example/images/resize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 59 additions & 9 deletions example/src/CameraExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
const [showImageUri, setShowImageUri] = useState<string>('');
const [zoom, setZoom] = useState<number | undefined>();
const [orientationAnim] = useState(new Animated.Value(3));
const [resize, setResize] = useState<'contain' | 'cover'>('contain');

// iOS will error out if capturing too fast,
// so block capturing until the current capture is done
Expand Down Expand Up @@ -65,6 +66,14 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
setFlashData(flashArray[newPosition]);
};

const onSetResize = () => {
if (resize === 'contain') {
setResize('cover');
} else {
setResize('contain');
}
};

const onSetTorch = () => {
setTorchMode(!torchMode);
};
Expand All @@ -91,13 +100,37 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
console.log('image', image);
};

function CaptureButton({ onPress, children }: { onPress: () => void, children?: React.ReactNode }) {
const w = 80, brdW = 4, spc = 6;
const cInner = 'white', cOuter = 'white';
function CaptureButton({ onPress, children }: { onPress: () => void; children?: React.ReactNode }) {
const w = 80,
brdW = 4,
spc = 6;
const cInner = 'white',
cOuter = 'white';
return (
<TouchableOpacity onPress={onPress} style={{ width: w, height: w }}>
<View style={{ position: 'absolute', left: 0, top: 0, width: w, height: w, borderColor: cOuter, borderWidth: brdW, borderRadius: w / 2 }} />
<View style={{ position: 'absolute', left: brdW + spc, top: brdW + spc, width: w - ((brdW + spc) * 2), height: w - ((brdW + spc) * 2), backgroundColor: cInner, borderRadius: (w - ((brdW + spc) * 2)) / 2 }} />
<View
style={{
position: 'absolute',
left: 0,
top: 0,
width: w,
height: w,
borderColor: cOuter,
borderWidth: brdW,
borderRadius: w / 2,
}}
/>
<View
style={{
position: 'absolute',
left: brdW + spc,
top: brdW + spc,
width: w - (brdW + spc) * 2,
height: w - (brdW + spc) * 2,
backgroundColor: cInner,
borderRadius: (w - (brdW + spc) * 2) / 2,
}}
/>
{children}
</TouchableOpacity>
);
Expand All @@ -111,7 +144,7 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
inputRange: [1, 4],
outputRange: ['180deg', '-90deg'],
});
const uiRotationStyle = rotateUi ? {transform: [{ rotate: uiRotation }]} : undefined;
const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : undefined;

function rotateUiTo(rotationValue: number) {
Animated.timing(orientationAnim, {
Expand All @@ -128,12 +161,20 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
<SafeAreaView style={styles.topButtons}>
{flashData.image && (
<TouchableOpacity style={styles.topButton} onPress={onSetFlash}>
<Animated.Image source={flashData.image} resizeMode="contain" style={[styles.topButtonImg, uiRotationStyle]} />
<Animated.Image
source={flashData.image}
resizeMode="contain"
style={[styles.topButtonImg, uiRotationStyle]}
/>
</TouchableOpacity>
)}

<TouchableOpacity style={styles.topButton} onPress={onSwitchCameraPressed}>
<Animated.Image source={require('../images/cameraFlipIcon.png')} resizeMode="contain" style={[styles.topButtonImg, uiRotationStyle]} />
<Animated.Image
source={require('../images/cameraFlipIcon.png')}
resizeMode="contain"
style={[styles.topButtonImg, uiRotationStyle]}
/>
</TouchableOpacity>

<TouchableOpacity style={styles.topButton} onPress={() => setZoom(1)}>
Expand All @@ -149,6 +190,14 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
style={[styles.topButtonImg, uiRotationStyle]}
/>
</TouchableOpacity>

<TouchableOpacity style={styles.topButton} onPress={onSetResize}>
<Animated.Image
source={require('../images/resize.png')}
resizeMode="contain"
style={[styles.topButtonImg, uiRotationStyle]}
/>
</TouchableOpacity>
</SafeAreaView>

<View style={styles.cameraContainer}>
Expand All @@ -160,6 +209,7 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
style={styles.cameraPreview}
cameraType={cameraType}
flashMode={flashData?.mode}
resizeMode={resize}
resetFocusWhenMotionDetected
zoom={zoom}
maxZoom={10}
Expand Down Expand Up @@ -265,7 +315,7 @@ const styles = StyleSheet.create({
flex: 1,
},
cameraPreview: {
aspectRatio: 3 / 4,
flex: 1,
width: '100%',
},
bottomButtons: {
Expand Down
1 change: 1 addition & 0 deletions ios/ReactNativeCameraKit/CKCameraManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ @interface RCT_EXTERN_MODULE(CKCameraManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(torchMode, CKTorchMode)
RCT_EXPORT_VIEW_PROPERTY(ratioOverlay, NSString)
RCT_EXPORT_VIEW_PROPERTY(ratioOverlayColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(resizeMode, CKResizeMode)

RCT_EXPORT_VIEW_PROPERTY(scanBarcode, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onReadCode, RCTDirectEventBlock)
Expand Down
5 changes: 5 additions & 0 deletions ios/ReactNativeCameraKit/CKTypes+RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ @implementation RCTConvert (CKTypes)
@"off": @(CKZoomModeOff)
}), CKZoomModeOn, integerValue)

RCT_ENUM_CONVERTER(CKResizeMode, (@{
@"cover": @(CKResizeModeCover),
@"contain": @(CKResizeModeContain)
}), CKResizeModeCover, integerValue)

@end
1 change: 1 addition & 0 deletions ios/ReactNativeCameraKit/CameraProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate {
func update(onZoom: RCTDirectEventBlock?)
func update(zoom: Double?)
func update(maxZoom: Double?)
func update(resizeMode: ResizeMode)

func zoomPinchStart()
func zoomPinchChange(pinchScale: CGFloat)
Expand Down
5 changes: 5 additions & 0 deletions ios/ReactNativeCameraKit/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CameraView: UIView {
// props
// camera settings
@objc var cameraType: CameraType = .back
@objc var resizeMode: ResizeMode = .contain
@objc var flashMode: FlashMode = .auto
@objc var torchMode: TorchMode = .off
// ratio overlay
Expand Down Expand Up @@ -163,6 +164,10 @@ class CameraView: UIView {
if changedProps.contains("onZoom") {
camera.update(onZoom: onZoom)
}

if changedProps.contains("resizeMode") {
camera.update(resizeMode: resizeMode)
}

// Ratio overlay
if changedProps.contains("ratioOverlay") {
Expand Down
12 changes: 12 additions & 0 deletions ios/ReactNativeCameraKit/RealCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
private let photoOutput = AVCapturePhotoOutput()
private let metadataOutput = AVCaptureMetadataOutput()

private var resizeMode: ResizeMode = .contain
private var flashMode: FlashMode = .auto
private var torchMode: TorchMode = .off
private var resetFocus: (() -> Void)?
Expand Down Expand Up @@ -295,6 +296,17 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
}
}

func update(resizeMode: ResizeMode) {
DispatchQueue.main.async {
switch resizeMode {
case .cover:
self.cameraPreview.previewLayer.videoGravity = .resizeAspectFill
case .contain:
self.cameraPreview.previewLayer.videoGravity = .resizeAspect
}
}
}

func capturePicture(onWillCapture: @escaping () -> Void,
onSuccess: @escaping (_ imageData: Data, _ thumbnailData: Data?, _ dimensions: CMVideoDimensions) -> Void,
onError: @escaping (_ message: String) -> Void) {
Expand Down
10 changes: 9 additions & 1 deletion ios/ReactNativeCameraKit/SimulatorCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SimulatorCamera: CameraProtocol {
private var wideAngleZoomFactor: Double = 2.0
private var zoom: Double?
private var maxZoom: Double?
private var resizeMode: ResizeMode = .contain

var previewView: UIView { mockPreview }

Expand Down Expand Up @@ -127,7 +128,6 @@ class SimulatorCamera: CameraProtocol {
func update(cameraType: CameraType) {
DispatchQueue.main.async {
self.mockPreview.cameraTypeLabel.text = "Camera type: \(cameraType)"

self.mockPreview.randomize()
}
}
Expand Down Expand Up @@ -161,6 +161,14 @@ class SimulatorCamera: CameraProtocol {
}
}

func update(resizeMode: ResizeMode) {
DispatchQueue.main.async {
self.mockPreview.resizeModeLabel.text = "Resize mode: \(resizeMode)"
self.mockPreview.randomize()
}
}


func isBarcodeScannerEnabled(_ isEnabled: Bool,
supportedBarcodeTypes: [CodeFormat],
onBarcodeRead: ((_ barcode: String,_ codeFormat:CodeFormat) -> Void)?) {}
Expand Down
3 changes: 2 additions & 1 deletion ios/ReactNativeCameraKit/SimulatorPreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class SimulatorPreviewView: UIView {
let torchModeLabel = UILabel()
let flashModeLabel = UILabel()
let cameraTypeLabel = UILabel()
let resizeModeLabel = UILabel()

var balloonLayer = CALayer()

Expand All @@ -30,7 +31,7 @@ class SimulatorPreviewView: UIView {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
[zoomLabel, focusAtLabel, torchModeLabel, flashModeLabel, cameraTypeLabel].forEach {
[zoomLabel, focusAtLabel, torchModeLabel, flashModeLabel, cameraTypeLabel, resizeModeLabel].forEach {
$0.numberOfLines = 0
stackView.addArrangedSubview($0)
}
Expand Down
13 changes: 13 additions & 0 deletions ios/ReactNativeCameraKit/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ public enum ZoomMode: Int, CustomStringConvertible {
}
}

@objc(CKResizeMode)
public enum ResizeMode: Int, CustomStringConvertible {
case cover
case contain

public var description: String {
switch self {
case .cover: return "cover"
case .contain: return "contain"
}
}
}

@objc(CKSetupResult)
enum SetupResult: Int {
case notStarted
Expand Down
3 changes: 2 additions & 1 deletion src/Camera.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CameraApi, FlashMode, FocusMode, ZoomMode, TorchMode, CameraType, CodeFormat } from './types';
import { CameraApi, FlashMode, FocusMode, ZoomMode, TorchMode, CameraType, CodeFormat, ResizeMode } from './types';
import { Orientation } from './index';

export type OnReadCodeData = {
Expand Down Expand Up @@ -96,6 +96,7 @@ export interface CameraProps {
ratioOverlayColor?: number | string;
resetFocusTimeout?: number;
resetFocusWhenMotionDetected?: boolean;
resizeMode?: ResizeMode;
/** **iOS Only**. Throttle how often the barcode scanner triggers a new scan */
scanThrottleDelay?: number;
/** **Android only**. Play a shutter capture sound when capturing a photo */
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NativeModules } from 'react-native';

import Camera from './Camera';
import { CameraApi, CameraType, CaptureData, FlashMode, FocusMode, TorchMode, ZoomMode } from './types';
import { CameraApi, CameraType, CaptureData, FlashMode, FocusMode, TorchMode, ZoomMode, ResizeMode } from './types';

const { CameraKit } = NativeModules;

Expand All @@ -15,4 +15,4 @@ export const Orientation = {

export default CameraKit;

export { Camera, CameraType, TorchMode, FlashMode, FocusMode, ZoomMode, CameraApi, CaptureData };
export { Camera, CameraType, TorchMode, FlashMode, FocusMode, ZoomMode, CameraApi, CaptureData, ResizeMode };
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type FocusMode = 'on' | 'off';

export type ZoomMode = 'on' | 'off';

export type ResizeMode = 'cover' | 'contain';

export type CaptureData = {
uri: string;
name: string;
Expand Down

0 comments on commit 80c517d

Please sign in to comment.