Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be94f3b374 | ||
|
|
b92e8fdf61 | ||
|
|
7f020cd892 | ||
|
|
8dd943d0ae | ||
|
|
08d6d3e4ce |
2
.gitignore
vendored
2
.gitignore
vendored
@ -95,3 +95,5 @@ dist/
|
||||
android/generated/ReactCodegen.podspec
|
||||
|
||||
example/.rock/cache/
|
||||
|
||||
.claude/
|
||||
|
||||
175
CLAUDE.md
Normal file
175
CLAUDE.md
Normal file
@ -0,0 +1,175 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
React Native Camera Kit is a high-performance, cross-platform camera library for React Native applications (iOS and Android). It provides:
|
||||
- Photo capture with high performance optimization
|
||||
- Barcode/QR code scanning capabilities
|
||||
- Camera preview support (including iOS simulator)
|
||||
- Extensive camera control options (flash, focus, zoom, torch)
|
||||
- Device orientation detection
|
||||
|
||||
This is a native library that uses React Native Codegen for cross-platform native module bindings.
|
||||
|
||||
## Architecture
|
||||
|
||||
### JavaScript/TypeScript Layer (`src/`)
|
||||
|
||||
The TypeScript codebase provides the React wrapper and type definitions:
|
||||
|
||||
- **Entry point** (`src/index.ts`): Exports the Camera component, types, and Orientation constants
|
||||
- **Camera component** (`src/Camera.tsx`): Platform-agnostic wrapper that lazy-loads platform-specific implementations
|
||||
- `src/Camera.ios.tsx`: iOS-specific camera component
|
||||
- `src/Camera.android.tsx`: Android-specific camera component
|
||||
- **Types and props**:
|
||||
- `src/types.ts`: Core type definitions (CameraType, CodeFormat, TorchMode, FlashMode, FocusMode, ZoomMode, ResizeMode, CameraApi, CaptureData)
|
||||
- `src/CameraProps.ts`: Complete Camera component props interface (both platform-specific and shared)
|
||||
- **Native specs** (`src/specs/`):
|
||||
- `CameraNativeComponent.ts`: React Native Codegen definition for the camera view component (NativeProps)
|
||||
- `NativeCameraKitModule.ts`: TurboModule specification for native camera functions (capture, authorization)
|
||||
|
||||
**Key Pattern**: Optional numeric props are represented as `-1` or `undefined` until React Native Fabric supports optional values. Both platform-specific implementations handle this conversion.
|
||||
|
||||
### Native Layer
|
||||
|
||||
- **iOS** (`ios/ReactNativeCameraKit/`): Swift implementation
|
||||
- `RealCamera.swift` / `SimulatorCamera.swift`: Core camera implementation (real and simulator)
|
||||
- `CameraManager.swift`: Manages camera state and configuration
|
||||
- `PhotoCaptureDelegate.swift`: Handles photo capture logic
|
||||
- `ScannerInterfaceView.swift` / `ScannerFrameView.swift`: Barcode scanning UI
|
||||
- `RatioOverlayView.swift`: Aspect ratio guide overlay
|
||||
|
||||
- **Android** (`android/src/main/java/com/rncamerakit/`): Kotlin implementation
|
||||
- `CKCamera.kt`: Main camera view component
|
||||
- `QRCodeAnalyzer.kt`: Barcode scanning using CameraX
|
||||
- Event classes in `events/`: Handle camera callbacks (zoom, orientation, errors, etc.)
|
||||
- Platform-specific code split between `newarch/` (React Native 0.73+, Fabric) and `oldarch/` (legacy)
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Build and Compilation
|
||||
|
||||
```bash
|
||||
# Build TypeScript to JavaScript (outputs to dist/)
|
||||
yarn build
|
||||
|
||||
# Clean build artifacts
|
||||
yarn clean
|
||||
|
||||
# Run both clean and build
|
||||
yarn clean && yarn build
|
||||
```
|
||||
|
||||
### Linting and Code Quality
|
||||
|
||||
```bash
|
||||
# Run ESLint
|
||||
yarn lint
|
||||
|
||||
# ESLint rules are configured in .eslintrc.js with:
|
||||
# - Max line length: 120 characters
|
||||
# - Required semicolons, proper indentation (2 spaces)
|
||||
# - No console.log or debugger statements allowed
|
||||
# - Strict import resolution checking
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
yarn test
|
||||
|
||||
# Run tests for a specific file
|
||||
yarn test -- src/__tests__/index.test.tsx
|
||||
|
||||
# The project uses Jest with minimal test configuration
|
||||
# Tests should be placed in __tests__ directories
|
||||
```
|
||||
|
||||
### Example Project
|
||||
|
||||
```bash
|
||||
# Bootstrap the example app (installs dependencies and pods)
|
||||
yarn bootstrap
|
||||
|
||||
# For Linux:
|
||||
yarn bootstrap-linux
|
||||
```
|
||||
|
||||
## Key Development Notes
|
||||
|
||||
### Native Component Integration
|
||||
|
||||
The camera component uses **React Native Codegen** to auto-generate native binding code:
|
||||
- Props are defined in `src/specs/CameraNativeComponent.ts` (NativeProps interface)
|
||||
- Changes to props require running: `yarn codegen` (generates `build/` directory)
|
||||
- The codegen config is in `package.json` under `codegenConfig`
|
||||
|
||||
### Platform-Specific Code
|
||||
|
||||
The library uses React Native's platform module for loading platform-specific implementations:
|
||||
```typescript
|
||||
// In src/Camera.tsx
|
||||
const Camera = lazy(() =>
|
||||
Platform.OS === 'ios'
|
||||
? import('./Camera.ios')
|
||||
: import('./Camera.android'),
|
||||
);
|
||||
```
|
||||
|
||||
Both implementations handle color props differently:
|
||||
- **Android**: Uses `processColor()` to convert color values
|
||||
- **iOS**: Passes colors as-is
|
||||
|
||||
### Optional Props Pattern
|
||||
|
||||
React Native Codegen doesn't support optional numeric props, so:
|
||||
- Numeric props default to `-1` to indicate "undefined"
|
||||
- The native layer interprets `-1` as "no value provided"
|
||||
- This affects: `zoom`, `maxZoom`, `scanThrottleDelay`, `resetFocusTimeout`, `shutterAnimationDuration`
|
||||
|
||||
### Type System
|
||||
|
||||
The project uses strict TypeScript (`strict: true` in tsconfig.json):
|
||||
- `@ts-expect-error` comments are used for Codegen type mismatches (see Camera.ios.tsx line 33)
|
||||
- Type definitions must be accurate between user-facing types and native specs
|
||||
- All numeric types from Codegen props must be converted in both platform files
|
||||
|
||||
## Testing and Releases
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run Jest tests
|
||||
yarn test
|
||||
|
||||
# Build TypeScript (validates code)
|
||||
yarn build
|
||||
|
||||
# Test files follow Jest conventions and are excluded from build (tsconfig.json excludes *.test.tsx)
|
||||
```
|
||||
|
||||
### Release Process
|
||||
|
||||
```bash
|
||||
# Standard npm release
|
||||
yarn release
|
||||
|
||||
# Beta release
|
||||
yarn release:beta
|
||||
|
||||
# Local testing (creates and opens tar.gz)
|
||||
yarn release:local
|
||||
```
|
||||
|
||||
The library is published to npm as `react-native-camera-kit` with the `files` array in package.json controlling what gets included in the published bundle.
|
||||
|
||||
## Important Configuration Details
|
||||
|
||||
- **TypeScript**: Strict mode enabled, targets ESNext, outputs to `dist/` with declaration files
|
||||
- **Package managers**: Yarn 1.22.22 required (specified in package.json engines)
|
||||
- **Node**: Requires Node.js >= 18
|
||||
- **React Native**: Uses version 0.79.0, supports both legacy and new architecture (Fabric)
|
||||
- **Import resolution**: ESLint is configured to recognize `.ios.tsx`, `.android.tsx`, and `.js` platform variants
|
||||
47
README.md
47
README.md
@ -1,20 +1,48 @@
|
||||
<h1 align="center">
|
||||
🎈 React Native Camera Kit
|
||||
🎈 React Native Camera Kit (No Google)
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
A <strong>high performance, easy to use, rock solid</strong><br>
|
||||
camera library for React Native apps.
|
||||
camera library for React Native apps.<br>
|
||||
<strong>No Google ML Kit dependency.</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/teslamotors/react-native-camera-kit/blob/master/LICENSE">
|
||||
<a href="https://github.com/limpbrains/react-native-camera-kit-no-google/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="React Native Camera Kit is released under the MIT license." />
|
||||
</a>
|
||||
<a href="https://www.npmjs.org/package/react-native-camera-kit">
|
||||
<img src="https://badge.fury.io/js/react-native-camera-kit.svg" alt="Current npm package version." />
|
||||
<a href="https://www.npmjs.org/package/react-native-camera-kit-no-google">
|
||||
<img src="https://badge.fury.io/js/react-native-camera-kit-no-google.svg" alt="Current npm package version." />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
> **⚠️ Fork Notice**
|
||||
>
|
||||
> This is a fork of [teslamotors/react-native-camera-kit](https://github.com/teslamotors/react-native-camera-kit) with **Google ML Kit removed** from Android.
|
||||
>
|
||||
> **Why?** Google ML Kit is closed source. This fork uses a completely open source pure Kotlin QR decoder instead.
|
||||
|
||||
## Key Differences from Original
|
||||
|
||||
| Feature | Original | This Fork |
|
||||
|---------|----------|-----------|
|
||||
| Android barcode library | Google ML Kit (closed source) | Pure Kotlin ([limpbrains/qr](https://github.com/limpbrains/qr)) |
|
||||
| Source code | Closed source (ML Kit) | Fully open source |
|
||||
| Google Play Services | Required | Not required |
|
||||
| Barcode formats | Multiple formats | **QR codes only** |
|
||||
|
||||
### Limitations
|
||||
|
||||
- **Android barcode scanning supports QR codes only** (no EAN, UPC, Code128, etc.)
|
||||
- iOS barcode scanning is unchanged (uses native AVFoundation, supports all formats)
|
||||
|
||||
### QR Decoder Attribution
|
||||
|
||||
The Android QR decoder is based on:
|
||||
- [limpbrains/qr](https://github.com/limpbrains/qr) - Kotlin QR code reader library
|
||||
- [paulmillr/qr](https://github.com/paulmillr/qr) - Original JavaScript implementation by Paul Miller
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
@ -24,8 +52,9 @@
|
||||
<ul>
|
||||
<li><h3>Cross Platform (iOS and Android)</h3></li>
|
||||
<li><h3>Optimized for performance and high photo capture rate</h3></li>
|
||||
<li><h3>QR / Barcode scanning support</h3></li>
|
||||
<li><h3>QR Code scanning support (QR only on Android)</h3></li>
|
||||
<li><h3>Camera preview support in iOS simulator</h3></li>
|
||||
<li><h3>No Google dependencies</h3></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@ -34,7 +63,7 @@
|
||||
## Installation (RN > 0.60)
|
||||
|
||||
```bash
|
||||
yarn add react-native-camera-kit
|
||||
yarn add react-native-camera-kit-no-google
|
||||
```
|
||||
|
||||
```bash
|
||||
@ -143,7 +172,7 @@ Add the following usage descriptions to your `Info.plist` (usually found at: `io
|
||||
Barebones camera component if you need advanced/customized interface
|
||||
|
||||
```ts
|
||||
import { Camera, CameraType } from 'react-native-camera-kit';
|
||||
import { Camera, CameraType } from 'react-native-camera-kit-no-google';
|
||||
```
|
||||
|
||||
```tsx
|
||||
@ -184,7 +213,7 @@ Additionally, the Camera can be used for barcode scanning
|
||||
| `onZoom` | Function | Callback when user makes a pinch gesture, regardless of what the `zoom` prop was set to. Returned event contains `zoom`. Ex: `onZoom={(e) => console.log(e.nativeEvent.zoom)}`. |
|
||||
| `torchMode` | `'on'`/`'off'` | Toggle flash light when camera is active. Default: `off` |
|
||||
| `cameraType` | CameraType.Back/CameraType.Front | Choose what camera to use. Default: `CameraType.Back` |
|
||||
| `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value |
|
||||
| `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit-no-google'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value |
|
||||
| **Android only** |
|
||||
| `onError` | Function | Android only. Callback when camera fails to initialize. Ex: `onError={(e) => console.log(e.nativeEvent.errorMessage)}`. |
|
||||
| `shutterPhotoSound` | `boolean` | Android only. Enable or disable the shutter sound when capturing a photo. Default: `true` |
|
||||
|
||||
@ -9,10 +9,10 @@ Pod::Spec.new do |s|
|
||||
s.license = "MIT"
|
||||
|
||||
s.authors = "CameraKit"
|
||||
s.homepage = "https://github.com/teslamotors/react-native-camera-kit"
|
||||
s.homepage = "https://github.com/limpbrains/react-native-camera-kit-no-google"
|
||||
s.platform = :ios, "15.0"
|
||||
|
||||
s.source = { :git => "https://github.com/teslamotors/react-native-camera-kit.git", :tag => "v#{s.version}" }
|
||||
s.source = { :git => "https://github.com/limpbrains/react-native-camera-kit-no-google.git", :tag => "v#{s.version}" }
|
||||
s.source_files = [
|
||||
# Exclude .h files as they cause Swift compiler to treat them as C files, but they are C++
|
||||
# See https://github.com/facebook/react-native/issues/45424#issuecomment-2354737063
|
||||
|
||||
@ -66,8 +66,9 @@ dependencies {
|
||||
// If you want to additionally use the CameraX Extensions library
|
||||
// implementation "androidx.camera:camera-extensions:${camerax_version}"
|
||||
|
||||
implementation 'com.google.mlkit:barcode-scanning:17.3.0'
|
||||
implementation 'com.github.limpbrains:qr:v0.0.1'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
@ -44,7 +44,6 @@ import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.util.Size
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.rncamerakit.events.*
|
||||
|
||||
class RectOverlay constructor(context: Context) :
|
||||
@ -330,35 +329,8 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
|
||||
val useCases = mutableListOf(preview, imageCapture)
|
||||
|
||||
if (scanBarcode) {
|
||||
val analyzer = QRCodeAnalyzer(analyzerBlock@{ barcodes, imageSize ->
|
||||
if (barcodes.isEmpty()) {
|
||||
return@analyzerBlock
|
||||
}
|
||||
|
||||
val barcodeFrame = barcodeFrame
|
||||
if (barcodeFrame == null) {
|
||||
onBarcodeRead(barcodes)
|
||||
return@analyzerBlock
|
||||
}
|
||||
|
||||
// Calculate scaling factors (image is always rotated by 90 degrees)
|
||||
val scaleX = viewFinder.width.toFloat() / imageSize.height
|
||||
val scaleY = viewFinder.height.toFloat() / imageSize.width
|
||||
|
||||
val filteredBarcodes = barcodes.filter { barcode ->
|
||||
val barcodeBoundingBox = barcode.boundingBox ?: return@filter false;
|
||||
val scaledBarcodeBoundingBox = Rect(
|
||||
(barcodeBoundingBox.left * scaleX).toInt(),
|
||||
(barcodeBoundingBox.top * scaleY).toInt(),
|
||||
(barcodeBoundingBox.right * scaleX).toInt(),
|
||||
(barcodeBoundingBox.bottom * scaleY).toInt()
|
||||
)
|
||||
barcodeFrame.frameRect.contains(scaledBarcodeBoundingBox)
|
||||
}
|
||||
|
||||
if (filteredBarcodes.isNotEmpty()) {
|
||||
onBarcodeRead(filteredBarcodes)
|
||||
}
|
||||
val analyzer = QRCodeAnalyzer({ decodedValue ->
|
||||
onBarcodeRead(decodedValue)
|
||||
}, scanThrottleDelay)
|
||||
imageAnalyzer!!.setAnalyzer(cameraExecutor, analyzer)
|
||||
useCases.add(imageAnalyzer)
|
||||
@ -509,12 +481,11 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
|
||||
rectOverlay.drawRectBounds(focusRects)
|
||||
}
|
||||
|
||||
private fun onBarcodeRead(barcodes: List<Barcode>) {
|
||||
val codeFormat = CodeFormat.fromBarcodeType(barcodes.first().format);
|
||||
private fun onBarcodeRead(decodedValue: String) {
|
||||
val surfaceId = UIManagerHelper.getSurfaceId(currentContext)
|
||||
UIManagerHelper
|
||||
.getEventDispatcherForReactTag(currentContext, id)
|
||||
?.dispatchEvent(ReadCodeEvent(surfaceId, id, barcodes.first().rawValue, codeFormat.code))
|
||||
?.dispatchEvent(ReadCodeEvent(surfaceId, id, decodedValue, CodeFormat.QR.code))
|
||||
}
|
||||
|
||||
private fun onOrientationChange(orientation: Int) {
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
|
||||
enum class CodeFormat(val code: String) {
|
||||
CODE_128("code-128"),
|
||||
CODE_39("code-39"),
|
||||
@ -16,41 +14,4 @@ enum class CodeFormat(val code: String) {
|
||||
AZTEC("aztec"),
|
||||
DATA_MATRIX("data-matrix"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
fun toBarcodeType(): Int {
|
||||
return when (this) {
|
||||
CODE_128 -> Barcode.FORMAT_CODE_128
|
||||
CODE_39 -> Barcode.FORMAT_CODE_39
|
||||
CODE_93 -> Barcode.FORMAT_CODE_93
|
||||
CODABAR -> Barcode.FORMAT_CODABAR
|
||||
EAN_13 -> Barcode.FORMAT_EAN_13
|
||||
EAN_8 -> Barcode.FORMAT_EAN_8
|
||||
ITF -> Barcode.FORMAT_ITF
|
||||
UPC_E -> Barcode.FORMAT_UPC_E
|
||||
QR -> Barcode.FORMAT_QR_CODE
|
||||
PDF_417 -> Barcode.FORMAT_PDF417
|
||||
AZTEC -> Barcode.FORMAT_AZTEC
|
||||
DATA_MATRIX -> Barcode.FORMAT_DATA_MATRIX
|
||||
UNKNOWN -> -1 // Or any other default value you prefer
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBarcodeType(@Barcode.BarcodeFormat barcodeType: Int): CodeFormat =
|
||||
when (barcodeType) {
|
||||
Barcode.FORMAT_CODE_128 -> CODE_128
|
||||
Barcode.FORMAT_CODE_39 -> CODE_39
|
||||
Barcode.FORMAT_CODE_93 -> CODE_93
|
||||
Barcode.FORMAT_CODABAR -> CODABAR
|
||||
Barcode.FORMAT_EAN_13 -> EAN_13
|
||||
Barcode.FORMAT_EAN_8 -> EAN_8
|
||||
Barcode.FORMAT_ITF -> ITF
|
||||
Barcode.FORMAT_UPC_E -> UPC_E
|
||||
Barcode.FORMAT_QR_CODE -> QR
|
||||
Barcode.FORMAT_PDF417 -> PDF_417
|
||||
Barcode.FORMAT_AZTEC -> AZTEC
|
||||
Barcode.FORMAT_DATA_MATRIX -> DATA_MATRIX
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,48 +1,65 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Size
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import qr.QRDecoder
|
||||
import qr.QRDecodingException
|
||||
|
||||
class QRCodeAnalyzer (
|
||||
private val onQRCodesDetected: (qrCodes: List<Barcode>, imageSize: Size) -> Unit,
|
||||
class QRCodeAnalyzer(
|
||||
private val onQRCodeDetected: (decodedValue: String) -> Unit,
|
||||
private val scanThrottleDelay: Long = 0L
|
||||
) : ImageAnalysis.Analyzer {
|
||||
// Time in milliseconds of the last time we dispatched detected barcodes
|
||||
private var lastBarcodeDetectedTime: Long = 0L
|
||||
@SuppressLint("UnsafeExperimentalUsageError")
|
||||
// Time in milliseconds of the last time we dispatched detected QR code
|
||||
private var lastQRDetectedTime: Long = 0L
|
||||
|
||||
@ExperimentalGetImage
|
||||
override fun analyze(image: ImageProxy) {
|
||||
val mediaImage = image.image ?: return
|
||||
try {
|
||||
val grayscaleData = extractYPlane(image)
|
||||
val decoded = QRDecoder.decode(image.width, image.height, grayscaleData)
|
||||
|
||||
val inputImage = InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees)
|
||||
|
||||
val scanner = BarcodeScanning.getClient()
|
||||
scanner.process(inputImage)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
// Throttle callback invocations based on scanThrottleDelay (ms)
|
||||
val now = System.currentTimeMillis()
|
||||
if (scanThrottleDelay > 0 && (now - lastBarcodeDetectedTime) < scanThrottleDelay) {
|
||||
return@addOnSuccessListener
|
||||
}
|
||||
|
||||
val strBarcodes = mutableListOf<Barcode>()
|
||||
barcodes.forEach { barcode ->
|
||||
strBarcodes.add(barcode ?: return@forEach)
|
||||
}
|
||||
|
||||
if (strBarcodes.isNotEmpty()) {
|
||||
lastBarcodeDetectedTime = now
|
||||
onQRCodesDetected(strBarcodes, Size(image.width, image.height))
|
||||
}
|
||||
// Throttle callback invocations based on scanThrottleDelay (ms)
|
||||
val now = System.currentTimeMillis()
|
||||
if (scanThrottleDelay > 0 && (now - lastQRDetectedTime) < scanThrottleDelay) {
|
||||
return
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
image.close()
|
||||
|
||||
lastQRDetectedTime = now
|
||||
onQRCodeDetected(decoded)
|
||||
} catch (e: QRDecodingException) {
|
||||
// No QR code found or decoding error - this is expected for most frames
|
||||
} finally {
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the Y (luminance) plane from a YUV_420_888 ImageProxy.
|
||||
* The Y plane is already grayscale data (1 byte per pixel).
|
||||
* Handles row stride padding when rowStride > width.
|
||||
*/
|
||||
private fun extractYPlane(image: ImageProxy): ByteArray {
|
||||
val yPlane = image.planes[0]
|
||||
val yBuffer = yPlane.buffer
|
||||
val rowStride = yPlane.rowStride
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
val yBytes = ByteArray(width * height)
|
||||
|
||||
if (rowStride == width) {
|
||||
// Fast path: contiguous data, no padding
|
||||
yBuffer.rewind()
|
||||
yBuffer.get(yBytes, 0, width * height)
|
||||
} else {
|
||||
// Slow path: handle row stride padding
|
||||
yBuffer.rewind()
|
||||
for (row in 0 until height) {
|
||||
yBuffer.position(row * rowStride)
|
||||
yBuffer.get(yBytes, row * width, width)
|
||||
}
|
||||
}
|
||||
|
||||
return yBytes
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,3 +4,4 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autoli
|
||||
rootProject.name = 'CameraKitExample'
|
||||
include ':app'
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
// includeBuild('../../../qr')
|
||||
|
||||
@ -46,6 +46,46 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
|
||||
|
||||
const [cameraType, setCameraType] = useState(CameraType.Back);
|
||||
const [barcode, setBarcode] = useState<string>('');
|
||||
const [fps, setFps] = useState(0);
|
||||
const [scanCount, setScanCount] = useState(0);
|
||||
const [scansPerSec, setScansPerSec] = useState(0);
|
||||
|
||||
// FPS counter using requestAnimationFrame
|
||||
useEffect(() => {
|
||||
let frameCount = 0;
|
||||
let lastTime = performance.now();
|
||||
let animationId: number;
|
||||
|
||||
const measureFps = () => {
|
||||
frameCount++;
|
||||
const now = performance.now();
|
||||
const elapsed = now - lastTime;
|
||||
|
||||
if (elapsed >= 1000) {
|
||||
setFps(Math.round((frameCount * 1000) / elapsed));
|
||||
frameCount = 0;
|
||||
lastTime = now;
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(measureFps);
|
||||
};
|
||||
|
||||
animationId = requestAnimationFrame(measureFps);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Scans per second counter
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setScansPerSec(scanCount);
|
||||
setScanCount(0);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [scanCount]);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => {
|
||||
@ -109,6 +149,11 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.fpsContainer}>
|
||||
<Text style={styles.fpsText}>{fps} FPS</Text>
|
||||
<Text style={styles.fpsText}>{scansPerSec} scans/s</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
<View style={styles.cameraContainer}>
|
||||
@ -119,7 +164,7 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
|
||||
flashMode={flashData?.mode}
|
||||
zoomMode="on"
|
||||
focusMode="on"
|
||||
scanThrottleDelay={2000}
|
||||
scanThrottleDelay={0}
|
||||
torchMode={torchMode ? 'on' : 'off'}
|
||||
onOrientationChange={(e) => {
|
||||
// We recommend locking the camera UI to portrait (using a different library)
|
||||
@ -150,11 +195,8 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
|
||||
showFrame
|
||||
barcodeFrameSize={{ width: 300, height: 150 }}
|
||||
onReadCode={(event) => {
|
||||
Vibration.vibrate(100);
|
||||
setScanCount((prev) => prev + 1);
|
||||
setBarcode(event.nativeEvent.codeStringValue);
|
||||
console.log('barcode', event.nativeEvent.codeStringValue);
|
||||
console.log('codeFormat', event.nativeEvent.codeFormat);
|
||||
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@ -254,4 +296,17 @@ const styles = StyleSheet.create({
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
},
|
||||
fpsContainer: {
|
||||
backgroundColor: '#222',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
fpsText: {
|
||||
color: '#0f0',
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "react-native-camera-kit",
|
||||
"name": "react-native-camera-kit-no-google",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/teslamotors/react-native-camera-kit.git"
|
||||
"url": "https://github.com/limpbrains/react-native-camera-kit-no-google.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user