Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
8afc1ec
add doc
lukin87 Oct 25, 2024
c32b1c7
basic example working
lukin87 Oct 28, 2024
aed0d29
potentially working focus lock
lukin87 Nov 26, 2024
1453021
better failure management + returning focus success
lukin87 Nov 26, 2024
e1fbd61
fix typing
lukin87 Nov 26, 2024
6ee859c
remove log
lukin87 Nov 27, 2024
384ec7d
trying to build a release
lukin87 Nov 27, 2024
604340d
chore: release 4.6.0
lukin87 Nov 27, 2024
c7d92bc
trying rerelease
lukin87 Nov 27, 2024
1aab333
readme
lukin87 Nov 27, 2024
e3951d3
chore: release 4.6.1
lukin87 Nov 27, 2024
193417b
trying to install
lukin87 Nov 27, 2024
aad635a
chore: release 4.6.2
lukin87 Nov 27, 2024
7e34e32
fixing release command
lukin87 Nov 27, 2024
6c5b619
chore: release 4.6.3
lukin87 Nov 27, 2024
9c8de77
attempting to remove libb from .gotignore
lukin87 Nov 27, 2024
8962778
chore: release 4.6.4
lukin87 Nov 27, 2024
1acb8b2
Update CameraSession+Photo.kt for custom image saving
hhff Dec 26, 2024
b655d3a
ImageProxy.use{} for proper cleanup
hhff Dec 26, 2024
662fefb
Merge pull request #2 from sanctuarycomputer/lightos-custom-image-sav…
lukin87 Dec 26, 2024
50b2bd0
chore: release 4.6.5
lukin87 Dec 26, 2024
9b12179
prevent CameraSession from being torn down when photos are still proc…
lukin87 Jan 16, 2025
46ac0c5
chore: release 4.6.6
lukin87 Jan 16, 2025
9b50c56
wrapping new takePhoto to return a promise on completion
lukin87 Jan 20, 2025
ec913f3
chore: release 4.6.7
lukin87 Jan 20, 2025
854c73c
new param to config jpeg quality
lukin87 Jan 21, 2025
f3b23a2
ts map files
lukin87 Jan 21, 2025
c02f7e0
chore: release 4.6.8
lukin87 Jan 21, 2025
0f98474
Merge pull request #3 from sanctuarycomputer/lightos-process-before-t…
lukin87 Jan 28, 2025
41d8280
Merge pull request #5 from sanctuarycomputer/lightos-config-jpeg-quality
lukin87 Jan 28, 2025
0cec658
Merge pull request #6 from sanctuarycomputer/lightos-return-promise-f…
lukin87 Jan 28, 2025
e45bdea
adding option to resolve promise early
lukin87 Jan 28, 2025
941d918
updating intent name
lukin87 Jan 28, 2025
b34f249
auto gen'd types
lukin87 Jan 28, 2025
105894e
chore: release 4.6.9
lukin87 Jan 28, 2025
093ad5d
adding profiling output
lukin87 Jan 30, 2025
7fa87e1
chore: release 4.6.10
lukin87 Jan 30, 2025
d985c29
Merge pull request #8 from sanctuarycomputer/lightos-profiling-perf
lukin87 Mar 3, 2025
fc058ab
chore: release 4.6.11
hhff Mar 14, 2025
82cc52c
returning photo mediastore Uri
lukin87 Mar 19, 2025
c3a9046
chore: release 4.6.12
lukin87 Mar 19, 2025
7223665
Merge pull request #9 from sanctuarycomputer/lightos-return-contenturi
hhff Mar 19, 2025
28adf37
Add exposure_metering setting
hhff Mar 21, 2025
5224fc7
Merge pull request #10 from sanctuarycomputer/lightos-exposure_metering
hhff Mar 21, 2025
1735112
chore: release 4.6.13
hhff Mar 21, 2025
f8afbf3
add EIS config to video stream
lukin87 Mar 27, 2025
f6a5aff
use global setting + apply end_stream for no eis too
lukin87 Mar 28, 2025
dac7a8f
also restore end_stream when errored
lukin87 Mar 28, 2025
2175c2c
add try/catch incase settings perms aren't available
lukin87 Apr 9, 2025
3bbf813
Merge pull request #11 from sanctuarycomputer/lightos-eis
hhff Apr 9, 2025
237d885
chore: release 4.6.14
lukin87 Apr 10, 2025
015c53e
fix lp3.jar import not working for lp2
lukin87 Apr 25, 2025
beb30b5
Merge pull request #12 from sanctuarycomputer/light-fix-lp2
brunoro Apr 25, 2025
73b2050
chore: release 4.6.15
brunoro Apr 25, 2025
7e1f0db
readd shutter callback
lukin87 May 29, 2025
5eb8c7b
Merge pull request #13 from sanctuarycomputer/fix/readd-shutter-callback
lukin87 May 29, 2025
5aff220
chore: release 4.6.16
lukin87 May 29, 2025
7eb6b6a
add in missing import
lukin87 May 29, 2025
e9e0cf4
Merge pull request #14 from sanctuarycomputer/fix/add-missing-import
lukin87 May 29, 2025
8776bb5
chore: release 4.6.17
lukin87 May 29, 2025
273cfd1
Merge branch 'main' into lightos
aconanlai Dec 4, 2025
5b6b8ce
fix build
aconanlai Dec 4, 2025
3a1abb1
fix: update readme
aconanlai Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# LightOS fork for finer focus control

To run the example app:

```
cd package
yarn install
cd example
yarn install
run android
------
run start
```

# Building a new release for LightOS

We have a script in this repo so that the package builds after being installed - see `scripts/prepare-package.js`.

After making changes to this repo, update the version in `package.json` and create a new tag - you can then update the tag in /LightOS/package.json

<a href="https://margelo.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./docs/static/img/banner-dark.png" />
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-vision-camera",
"version": "4.6.19",
"version": "4.6.20",
"description": "A powerful, high-performance React Native Camera library.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
1 change: 0 additions & 1 deletion package/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ yarn-error.log
!.yarn/versions

# TypeScript output
/lib
/example/lib

# Gems
Expand Down
23 changes: 23 additions & 0 deletions package/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ android {
namespace "com.mrousavy.camera"
}

flavorDimensions "version"
productFlavors {
"lp2" {
dimension "version"
}
"lp3" {
dimension "version"
}
}

// Used to override the NDK path/version on internal CI or by allowing
// users to customize the NDK path/version from their root project (e.g. for M1 support)
if (rootProject.hasProperty("ndkPath")) {
Expand Down Expand Up @@ -193,6 +203,9 @@ dependencies {
// Some Coroutines extension functions
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"

// Include light jar files
lp3Implementation files('../../../../framework_all/lp3.jar')

if (enableCodeScanner) {
// User enabled code-scanner, so we bundle the 2.4 MB model in the app.
implementation 'com.google.mlkit:barcode-scanning:17.3.0'
Expand Down Expand Up @@ -242,3 +255,13 @@ tasks.configureEach { task ->
task.dependsOn(deleteCmakeCache)
}
}

gradle.projectsEvaluated {
tasks.withType(JavaCompile) { task ->
def isLp3 = task.toString().indexOf("Lp3") != -1
if (!isLp3) {
return;
}
options.compilerArgs.add("-Xbootclasspath/p:${rootDir}/../../framework_all/lp3.jar")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mrousavy.camera.core

fun CameraSession.setEISMode(newMode: String) {
return
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mrousavy.camera.core

import android.os.SystemProperties
import android.util.Log

fun CameraSession.setEISMode(newMode: String) {
try {
Log.i(CameraSession.TAG, "EIS: changed from: " + SystemProperties.get("persist.vendor.camera.enableEIS"));
SystemProperties.set("persist.vendor.camera.enableEIS", newMode);
Log.i(CameraSession.TAG, "EIS: changed to: " + SystemProperties.get("persist.vendor.camera.enableEIS"));
}
catch (e: Exception) {
Log.e(CameraSession.TAG, "Unable to set EIS mode ${e.message}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ data class CameraConfiguration(
) {
// Output<T> types, those need to be comparable
data class CodeScanner(val codeTypes: List<CodeType>)
data class Photo(val isMirrored: Boolean, val enableHdr: Boolean, val photoQualityBalance: QualityBalance)
data class Photo(val isMirrored: Boolean, val enableHdr: Boolean, val photoQualityBalance: QualityBalance, val jpegCompressionQuality: Int)
data class Video(val isMirrored: Boolean, val enableHdr: Boolean)
data class FrameProcessor(val isMirrored: Boolean, val pixelFormat: PixelFormat)
data class Audio(val nothing: Unit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import androidx.camera.video.VideoCapture
import androidx.lifecycle.Lifecycle
import com.mrousavy.camera.core.extensions.*
import com.mrousavy.camera.core.types.CameraDeviceFormat
import com.mrousavy.camera.core.types.QualityBalance
import com.mrousavy.camera.core.types.Torch
import com.mrousavy.camera.core.types.VideoStabilizationMode
import kotlin.math.roundToInt
import androidx.camera.camera2.interop.Camera2Interop
import android.hardware.camera2.CaptureRequest

private fun assertFormatRequirement(
propName: String,
Expand Down Expand Up @@ -91,8 +94,22 @@ internal fun CameraSession.configureOutputs(configuration: CameraConfiguration)
if (photoConfig != null) {
Log.i(CameraSession.TAG, "Creating Photo output...")
val photo = ImageCapture.Builder().also { photo ->
// Set exposure metering based on SnapdragonCamera
val camera2Extender = Camera2Interop.Extender(photo)
camera2Extender.setCaptureRequestOption(
CaptureRequest.Key<Integer>(
"org.codeaurora.qcamera3.exposure_metering.exposure_metering_mode",
Integer::class.java
),
Integer(1)
)

// Configure Photo Output
photo.setCaptureMode(photoConfig.config.photoQualityBalance.toCaptureMode())
Log.i("LP3_PROFILER", "Mode: ${photoConfig.config.photoQualityBalance}")
Log.i("LP3_PROFILER", "Quality: ${photoConfig.config.jpegCompressionQuality}")
Log.i(LP3_TAG, "Setting jpeg compression to ${photoConfig.config.jpegCompressionQuality}")
photo.setJpegQuality(photoConfig.config.jpegCompressionQuality)
if (format != null) {
Log.i(CameraSession.TAG, "Photo size: ${format.photoSize}")
val resolutionSelector = ResolutionSelector.Builder()
Expand All @@ -107,6 +124,34 @@ internal fun CameraSession.configureOutputs(configuration: CameraConfiguration)
photoOutput = null
}

/*
* LP3: The aim with this was to provide a second Photo UseCase which would be used for when we lock the focus
* This use case is set to aim for fastest shutter speed
* Unfortunately, we can't bind two Photo UseCases at once and so this fails to initialize
* And if we don't bind it, photo taking fails
* Trying to dynamically rebind which UseCase is being used at runtime also seems to be full of pitfalls and complications
* Rebinding takes time and passing in the appropriate
// 2.1 Image Capture with Locked Focus
if (photoConfig != null) {
Log.i(CameraSession.TAG, "Creating Photo output...")
val photo = ImageCapture.Builder().also { photo ->
// Configure Photo Output
photo.setCaptureMode(QualityBalance.BALANCED.toCaptureMode())
if (format != null) {
Log.i(CameraSession.TAG, "Photo size: ${format.photoSize}")
val resolutionSelector = ResolutionSelector.Builder()
.forSize(format.photoSize)
.setAllowedResolutionMode(ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION)
.build()
photo.setResolutionSelector(resolutionSelector)
}
}.build()
photoOutputLockedFocus = photo
} else {
photoOutputLockedFocus = null
}
*/

// 3. Video Capture
if (videoConfig != null) {
Log.i(CameraSession.TAG, "Creating Video output...")
Expand All @@ -129,6 +174,16 @@ internal fun CameraSession.configureOutputs(configuration: CameraConfiguration)
}

val video = VideoCapture.Builder(recorder).also { video ->
// Set exposure metering based on SnapdragonCamera
val camera2Extender = Camera2Interop.Extender(video)
camera2Extender.setCaptureRequestOption(
CaptureRequest.Key<Integer>(
"org.codeaurora.qcamera3.exposure_metering.exposure_metering_mode",
Integer::class.java
),
Integer(1)
)

// Configure Video Output
if (videoConfig.config.isMirrored) {
video.setMirrorMode(MirrorMode.MIRROR_MODE_ON)
Expand Down Expand Up @@ -219,6 +274,7 @@ internal suspend fun CameraSession.configureCamera(provider: ProcessCameraProvid
checkCameraPermission()

// Outputs
// val useCases = listOfNotNull(previewOutput, photoOutput, photoOutputLockedFocus, videoOutput, frameProcessorOutput, codeScannerOutput)
val useCases = listOfNotNull(previewOutput, photoOutput, videoOutput, frameProcessorOutput, codeScannerOutput)
if (useCases.isEmpty()) {
throw NoOutputsError()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.mrousavy.camera.core

import android.annotation.SuppressLint
import android.util.Log
import androidx.camera.core.CameraControl
import android.hardware.camera2.CaptureRequest
import androidx.annotation.OptIn
import androidx.camera.camera2.interop.CaptureRequestOptions
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.MeteringPoint
import com.mrousavy.camera.core.extensions.await
import androidx.camera.camera2.interop.Camera2CameraControl
import androidx.camera.camera2.interop.ExperimentalCamera2Interop

// There are a few different focus control modes available in the docs:
// https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AF_MODE
// The relevant to us are:
// - CONTROL_AF_MODE_AUTO - allows us to manually tell the camera where to focus
// - CONTROL_AF_MODE_CONTINUOUS_PICTURE - tells the camera to continuously autofocus
@OptIn(ExperimentalCamera2Interop::class)
fun CameraSession.setFocusMode(mode: Int) {
val camera = camera ?: throw CameraNotReadyError()

Camera2CameraControl.from(camera.cameraControl).setCaptureRequestOptions(
CaptureRequestOptions.Builder().setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, mode).build()
)
Log.i(CameraSession.TAG, "LP3: set focus mode: $mode")
}

/*
* LP3: Trying to rebind the photoOutput at runtime.
* Pulling up all the necessary context such as the cameraSelector doesn't seem possible without rebuilding it
* This approach also seems vulnerable to race conditions since we can't easily guarantee it being executed before
* a photo request is made as this is async
suspend fun CameraSession.bindNormal() {
val provider = cameraProvider.await(mainExecutor)

provider.unbind(currentlyBoundPhotoOutput)
provider.bindToLifecycle(list, cameraSelector, listOf(photoOutputLockedFocus))
currentlyBoundPhotoOutput = photoOutputLockedFocus
}*/

// For exposure we can just set it to be locked
@OptIn(ExperimentalCamera2Interop::class)
fun CameraSession.setExposureLock(setLocked: Boolean) {
val camera = camera ?: throw CameraNotReadyError()

Camera2CameraControl.from(camera.cameraControl).setCaptureRequestOptions(
CaptureRequestOptions.Builder().setCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK, setLocked).build()
)
}

fun CameraSession.freeFocusAndExposure() {

setExposureLock(false)
setFocusMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}

@SuppressLint("RestrictedApi")
suspend fun CameraSession.lockFocusAndExposureToPoint(meteringPoint: MeteringPoint): Boolean {
val camera = camera ?: throw CameraNotReadyError()

setFocusMode(CaptureRequest.CONTROL_AF_MODE_AUTO)

// So it seems like using, disableAutoCancel might actually be the solution
// Hidden in this obscure SO post
// https://stackoverflow.com/questions/66633338/disable-autofocus-in-android-camerax-camera-2
val action = FocusMeteringAction.Builder(meteringPoint,
FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE).apply {
disableAutoCancel()
}.build()

if (!camera.cameraInfo.isFocusMeteringSupported(action)) {
throw FocusNotSupportedError()
}

try {
Log.i(CameraSession.TAG, "LP3: Focusing to ${action.meteringPointsAf.joinToString { "(${it.x}, ${it.y})" }}...")
val future = camera.cameraControl.startFocusAndMetering(action)
val result = future.await(CameraQueues.cameraExecutor)
if (result.isFocusSuccessful) {
Log.i(CameraSession.TAG, "LP3: Focused successfully")
setExposureLock(true)
return true
} else {
Log.i(CameraSession.TAG, "LP3: Failed to focus")
freeFocusAndExposure()
}
} catch (e: CameraControl.OperationCanceledException) {
throw FocusCanceledError()
}
return false
}
Loading