Skip to content

feat: white-balance mode control (fixes iOS yellow/warm tint)#375

Open
twtpat wants to merge 2 commits into
Cap-go:mainfrom
twtpat:feat/white-balance-mode
Open

feat: white-balance mode control (fixes iOS yellow/warm tint)#375
twtpat wants to merge 2 commits into
Cap-go:mainfrom
twtpat:feat/white-balance-mode

Conversation

@twtpat

@twtpat twtpat commented Jun 24, 2026

Copy link
Copy Markdown

Adds white-balance mode control, mirroring the existing exposure-mode controls. Resolves #374.

Why

On iOS, captured photos can have a warm/yellow cast and there's no way to influence white balance (the plugin exposes flash + exposure only). This adds a WB API and, more importantly, defaults the device to .continuousAutoWhiteBalance on start, which fixes the cast for the common case.

API (mirrors exposure)

type WhiteBalanceMode = 'AUTO' | 'LOCK' | 'CONTINUOUS';
getWhiteBalanceModes(): Promise<{ modes: WhiteBalanceMode[] }>;
getWhiteBalanceMode(): Promise<{ mode: WhiteBalanceMode }>;
setWhiteBalanceMode(options: { mode: WhiteBalanceMode }): Promise<void>;

Changes

  • TS (src/definitions.ts): WhiteBalanceMode type + 3 methods. Web (src/web.ts): unsupported-platform stubs.
  • iOS: CameraController.swift get/get/set via AVCaptureDevice.whiteBalanceMode (.locked / .autoWhiteBalance / .continuousAutoWhiteBalance, guarded by isWhiteBalanceModeSupported); Plugin.swift registrations + bridge; continuous-auto-WB default on camera start (next to the existing continuous-exposure default).
  • Android: CameraXView.java get/get/set via Camera2 CONTROL_AWB_MODE / CONTROL_AWB_LOCK (Camera2CameraControl); CameraPreview.java @PluginMethod bridge.
  • README regenerated via docgen.

Validation

  • tsc compiles clean; docgen regenerated the README; npm run check:wiring passes (methods consistent across TS/iOS/Android).
  • I implemented the native code by mirroring the existing exposure controls but could not run verify:ios / verify:android in my environment — would appreciate a maintainer build/on-device check, and happy to adjust per review.

Thanks for maintaining this plugin!

Summary by CodeRabbit

  • New Features

    • Added white-balance controls to the camera API, including viewing supported modes, checking the current mode, and setting a new mode.
    • Supported modes include Auto, Lock, and Continuous across mobile platforms.
  • Bug Fixes

    • Improved camera readiness checks so white-balance actions only run when the camera is active.
    • Added clearer validation and error handling for invalid white-balance selections.
  • Documentation

    • Updated API docs to reflect the new white-balance methods and supported mode types.

…eMode/setWhiteBalanceMode)

Mirrors the existing exposure-mode controls. Fixes a warm/yellow cast on iOS by
also defaulting the device to .continuousAutoWhiteBalance on start.

- TS: add WhiteBalanceMode ('AUTO' | 'LOCK' | 'CONTINUOUS') + 3 methods (definitions.ts), web stubs (web.ts).
- iOS: AVCaptureDevice.whiteBalanceMode (.locked/.autoWhiteBalance/.continuousAutoWhiteBalance) in CameraController, bridge in Plugin.swift, and continuous-auto-WB default on start.
- Android: Camera2 CONTROL_AWB_MODE / CONTROL_AWB_LOCK in CameraXView, bridge in CameraPreview.java.
- README regenerated via docgen.

Closes Cap-go#374
@github-actions

Copy link
Copy Markdown
Contributor

Beta npm build

This PR comes from a fork, so beta publish is disabled for security.

If you need a beta package, move the branch into this repository first.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@twtpat, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 50 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ddc27bd-763e-4dae-9fd7-cb95091d3e26

📥 Commits

Reviewing files that changed from the base of the PR and between ad67bf9 and 28a7b07.

📒 Files selected for processing (1)
  • android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java
📝 Walkthrough

Walkthrough

Adds white-balance mode control to the camera preview plugin. A WhiteBalanceMode type ('AUTO' | 'LOCK' | 'CONTINUOUS') and three interface methods (getWhiteBalanceModes, getWhiteBalanceMode, setWhiteBalanceMode) are defined in TypeScript, implemented natively on Android via Camera2 CaptureRequestOptions and on iOS via AVCaptureDevice, wired through each platform's Capacitor bridge, stubbed for web, and documented in the README.

Changes

White Balance Mode Control

Layer / File(s) Summary
TypeScript contract and web stubs
src/definitions.ts, src/web.ts
Exports WhiteBalanceMode = 'AUTO' | 'LOCK' | 'CONTINUOUS' and adds getWhiteBalanceModes, getWhiteBalanceMode, setWhiteBalanceMode to CameraPreviewPlugin; CameraPreviewWeb stubs all three as unsupported errors.
Android native implementation and plugin bridge
android/.../CameraXView.java, android/.../CameraPreview.java
CameraXView tracks currentWhiteBalanceMode (default "CONTINUOUS") and applies CONTROL_AWB_LOCK/CONTROL_AWB_MODE via Camera2 CaptureRequestOptions; CameraPreview exposes three @PluginMethod handlers that guard on camera-running state and validate the mode parameter.
iOS native implementation and plugin bridge
ios/.../CameraController.swift, ios/.../Plugin.swift
CameraController initializes continuousAutoWhiteBalance on device configuration and implements the three white-balance methods using AVCaptureDevice.whiteBalanceMode; Plugin.swift registers the methods in pluginMethods and adds bridge handlers with initialization checks and mode string validation.
README API documentation
README.md
Adds index links, full method doc sections (including options.mode: WhiteBalanceMode parameter contract), and the WhiteBalanceMode type alias entry to the generated API reference.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds white-balance APIs, but it omits the linked issue's CUSTOM mode and does not expose the requested getSupportedWhiteBalanceModes name. Add CUSTOM support where platform-relevant and align the API surface with getSupportedWhiteBalanceModes, or document any intentional deviation.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: white-balance mode control for the camera preview.
Out of Scope Changes check ✅ Passed The changes stay focused on white-balance controls, platform wiring, and regenerated docs with no unrelated additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`:
- Line 194: The cached white balance state in CameraXView is drifting from the
actual camera after rebinds, so getWhiteBalanceMode() can return CONTINUOUS even
when the newly bound Camera2CameraControl has reset to its default. Update the
binding/rebind flow in bindCameraUseCases() (and any related camera setup paths)
to reapply the last requested white balance mode to the newly created camera
control, and keep currentWhiteBalanceMode in sync only after the camera request
is successfully applied. Make sure setWhiteBalanceMode() stores the desired mode
for later rebinding, and that getWhiteBalanceMode() reflects the active camera
state after camera switches or startup.
- Around line 3316-3356: The white-balance API is using a fixed capability list
and a cached value that can get out of sync after camera rebinds; update
CameraXView so getWhiteBalanceModes() derives supported modes from the active
camera’s actual Camera2 white-balance capabilities, and make
setWhiteBalanceMode() persist/apply the real native state instead of only
updating currentWhiteBalanceMode. Reapply the selected mode when the camera is
rebound or switched, and ensure AUTO and CONTINUOUS are represented by distinct
behavior only if the camera supports them, otherwise expose only the modes the
camera can actually use.

In `@src/definitions.ts`:
- Around line 496-497: The public WhiteBalanceMode contract is missing the
requested CUSTOM option, so update the type in WhiteBalanceMode in
src/definitions.ts to include CUSTOM alongside the existing modes. Make sure any
related white-balance declarations that mirror this contract elsewhere in the
same definitions area are kept in sync so the API surface matches the intended
exposure-mode pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: defe9e83-65b7-4279-aec8-f3e3d44b59fa

📥 Commits

Reviewing files that changed from the base of the PR and between 9ea7086 and ad67bf9.

📒 Files selected for processing (7)
  • README.md
  • android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java
  • android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java
  • ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift
  • ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift
  • src/definitions.ts
  • src/web.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

private Runnable pendingFrameRateBindSuccess;
private java.util.function.Consumer<String> pendingFrameRateBindError;
private String currentExposureMode = "CONTINUOUS"; // Default behavior
private String currentWhiteBalanceMode = "CONTINUOUS"; // Default behavior

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rebinds can leave getWhiteBalanceMode() lying about the real camera state.

Line 194 introduces a cached default of CONTINUOUS, and getWhiteBalanceMode() always returns that cache. But setWhiteBalanceMode() only pushes the request to the current Camera2CameraControl; later bindCameraUseCases() paths replace camera after unbindAll() without reapplying white balance. After a camera switch/rebind/start, JS can still read the old cached mode while the newly bound camera is back on its default AWB behavior.

Also applies to: 3320-3353

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java` at
line 194, The cached white balance state in CameraXView is drifting from the
actual camera after rebinds, so getWhiteBalanceMode() can return CONTINUOUS even
when the newly bound Camera2CameraControl has reset to its default. Update the
binding/rebind flow in bindCameraUseCases() (and any related camera setup paths)
to reapply the last requested white balance mode to the newly created camera
control, and keep currentWhiteBalanceMode in sync only after the camera request
is successfully applied. Make sure setWhiteBalanceMode() stores the desired mode
for later rebinding, and that getWhiteBalanceMode() reflects the active camera
state after camera switches or startup.

Comment on lines +3316 to +3356
public java.util.List<String> getWhiteBalanceModes() {
return Arrays.asList("AUTO", "LOCK", "CONTINUOUS");
}

public String getWhiteBalanceMode() {
return currentWhiteBalanceMode;
}

@OptIn(markerClass = ExperimentalCamera2Interop.class)
public void setWhiteBalanceMode(String mode) throws Exception {
if (camera == null) {
throw new Exception("Camera not initialized");
}
if (mode == null) {
throw new Exception("mode is required");
}
String normalized = mode.toUpperCase(Locale.US);

Camera2CameraControl c2 = Camera2CameraControl.from(camera.getCameraControl());
switch (normalized) {
case "LOCK": {
CaptureRequestOptions opts = new CaptureRequestOptions.Builder()
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_LOCK, true)
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
.build();
mainExecutor.execute(() -> c2.setCaptureRequestOptions(opts));
currentWhiteBalanceMode = "LOCK";
break;
}
case "AUTO":
case "CONTINUOUS": {
CaptureRequestOptions opts = new CaptureRequestOptions.Builder()
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_LOCK, false)
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
.build();
mainExecutor.execute(() -> c2.setCaptureRequestOptions(opts));
currentWhiteBalanceMode = normalized;
break;
}
default:
throw new Exception("Unsupported white balance mode: " + mode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant Android implementation and nearby capability checks
git ls-files android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java
ast-grep outline android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java --view expanded | sed -n '1,220p'

printf '\n--- search white balance / camera capability usages ---\n'
rg -n "getWhiteBalanceModes|getWhiteBalanceMode|setWhiteBalanceMode|AWB_LOCK|CONTROL_AWB_MODE|Camera2CameraControl|supported.*white|white balance" android/src/main/java/app/capgo/capacitor/camera/preview -S

printf '\n--- nearby lines around implementation ---\n'
sed -n '190,220p;3300,3375p' android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java

Repository: Cap-go/capacitor-camera-preview

Length of output: 18863


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether white balance state is reapplied during camera rebind or startup
rg -n "bindCameraUseCases|unbindAll|setWhiteBalanceMode|currentWhiteBalanceMode|whiteBalance" android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java -S

printf '\n--- bindCameraUseCases area ---\n'
sed -n '1180,1295p' android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java

printf '\n--- related camera control/state methods ---\n'
sed -n '3260,3395p' android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java

Repository: Cap-go/capacitor-camera-preview

Length of output: 13349


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect iOS/JS contract for white balance to compare behavior
rg -n "white balance|getWhiteBalanceModes|getWhiteBalanceMode|setWhiteBalanceMode|supported mode" -S .

# If there are platform-specific files, show the relevant snippets
fd -a ".*(white|White|balance|Balance).*" .

Repository: Cap-go/capacitor-camera-preview

Length of output: 5975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the exact implementation and any surrounding camera rebind/state code.
sed -n '180,220p;1180,1295p;3300,3375p' android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java

printf '\n--- occurrences of white balance / capabilities across repo ---\n'
rg -n "currentWhiteBalanceMode|getWhiteBalanceModes|getWhiteBalanceMode|setWhiteBalanceMode|CONTROL_AWB_LOCK|CONTROL_AWB_MODE|CameraCharacteristics|INFO_SUPPORTED_HARDWARE_LEVEL" android/src/main/java -S

Repository: Cap-go/capacitor-camera-preview

Length of output: 18167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the documented contract and iOS behavior for white balance
sed -n '1370,1425p' README.md
printf '\n--- iOS white balance implementation ---\n'
sed -n '2590,2665p' ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift

Repository: Cap-go/capacitor-camera-preview

Length of output: 4082


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the documented contract and iOS behavior for white balance
sed -n '1370,1425p' README.md
printf '\n--- iOS white balance implementation ---\n'
sed -n '2590,2665p' ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift

Repository: Cap-go/capacitor-camera-preview

Length of output: 4082


Derive white-balance support from the active camera and reapply it on rebind.

getWhiteBalanceModes() is hard-coded to AUTO, LOCK, and CONTINUOUS, but the Android path never checks the active camera’s white-balance capabilities. setWhiteBalanceMode() also only updates currentWhiteBalanceMode; after a camera rebind or switch, the cached value survives while the native state resets. AUTO and CONTINUOUS currently map to the same Camera2 request, so the exposed mode can drift from what the camera is actually using.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`
around lines 3316 - 3356, The white-balance API is using a fixed capability list
and a cached value that can get out of sync after camera rebinds; update
CameraXView so getWhiteBalanceModes() derives supported modes from the active
camera’s actual Camera2 white-balance capabilities, and make
setWhiteBalanceMode() persist/apply the real native state instead of only
updating currentWhiteBalanceMode. Reapply the selected mode when the camera is
rebound or switched, and ensure AUTO and CONTINUOUS are represented by distinct
behavior only if the camera supports them, otherwise expose only the modes the
camera can actually use.

Comment thread src/definitions.ts
Comment on lines +496 to +497
/** Reusable white-balance mode type for cross-platform support. */
export type WhiteBalanceMode = 'AUTO' | 'LOCK' | 'CONTINUOUS';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Include CUSTOM in the public white-balance contract.

The new WhiteBalanceMode only allows 'AUTO' | 'LOCK' | 'CONTINUOUS', so callers cannot express the linked issue's requested CUSTOM mode and this API no longer fully mirrors the exposure-mode surface it was modeled after.

As per coding guidelines, src/definitions.ts: "TypeScript interfaces and types must be defined in src/definitions.ts as the source of truth for API documentation."

Also applies to: 1102-1121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/definitions.ts` around lines 496 - 497, The public WhiteBalanceMode
contract is missing the requested CUSTOM option, so update the type in
WhiteBalanceMode in src/definitions.ts to include CUSTOM alongside the existing
modes. Make sure any related white-balance declarations that mirror this
contract elsewhere in the same definitions area are kept in sync so the API
surface matches the intended exposure-mode pattern.

Source: Coding guidelines

Camera2 maps AUTO and CONTINUOUS to the same CONTROL_AWB_MODE_AUTO, so
getWhiteBalanceModes() shouldn't list AUTO as separate (matches getExposureModes).
Addresses review feedback on PR Cap-go#375.
@twtpat

twtpat commented Jun 24, 2026

Copy link
Copy Markdown
Author

Thanks for the review! Addressed:

1. Android WB mode list (CONTROL_AWB): Fixed in 28a7b07getWhiteBalanceModes() now returns only ["LOCK", "CONTINUOUS"]. On Camera2, AUTO and CONTINUOUS both map to CONTROL_AWB_MODE_AUTO, so advertising both was misleading; this now matches how getExposureModes() reports modes. (Deriving the full list from CONTROL_AWB_AVAILABLE_MODES would go beyond the existing exposure pattern — happy to add it if you'd prefer it for both APIs.)

2. WhiteBalanceMode missing CUSTOM: Intentionally omitted. AVCaptureDevice.WhiteBalanceMode only has .locked / .autoWhiteBalance / .continuousAutoWhiteBalance — there is no .custom mode (manual WB is a separate gains API, setWhiteBalanceModeGains, not a mode value). ExposureMode includes CUSTOM because AVCaptureDevice.ExposureMode.custom exists; WB has no equivalent, so adding CUSTOM would be a no-op that rejects/falls back. Left it out to keep the contract honest.

3. Android cache drift on rebind: Acknowledged — currentWhiteBalanceMode isn't reapplied when the camera is rebound/switched. This mirrors the existing exposure controls exactly (currentExposureMode has the same behavior). I'd suggest handling reapply-on-rebind as a separate change covering both exposure and white balance so they stay consistent, rather than diverging here — but glad to fold it in if you'd like it in this PR.

For our use case the key fix is the continuous-auto-WB default on start (iOS), which resolves the yellow cast on a fresh session. tsc + docgen + check:wiring pass; I just can't run verify:ios/verify:android locally, so a maintainer build/on-device check would be appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: white-balance mode control (iOS captured photos have a yellow/warm tint)

1 participant