Camera Theme Customization
The Paper Receipt camera scanning screen — its capture, retake, finish, confirm, cancel, torch, and hint buttons, the scanning tooltips, the progress bar, and the on-screen guidance hints — is fully themeable through standard Android XML styles. You define a style that extends the SDK's BlinkRecognizerStyle, override only the brand colors and icons you care about, and hand that style to the camera through CameraCharacteristics.
Internally, the SDK reads your XML theme attributes and maps them onto a BlinkRecognizerStyle model that drives the Compose-based camera UI. That model is an SDK-internal type — you never construct it yourself. XML styling is the supported and only customization path.
The camera UI was refreshed in 2.3.1. Themes written for earlier versions still compile and still apply, but the buttons and tooltip now take their colors from their own per-component styles instead of from your theme's Material color roles. If your camera screen looks different after upgrading, see Migrating to Paper Receipt SDK 2.3.1.
Table of Contents
- How It Works
- Default Camera Chrome
- Setup (Host App)
- The
BlinkRecognizerStyleModel - Theme Attribute Reference
- Full Example
- Tips and Pitfalls
- Migrating to Paper Receipt SDK 2.3.1
1. How It Works
Your style feeds the camera through two independent paths: a handful of theme-level color attributes, which may reference ?attr/colorPrimary, and a set of nested component styles, each resolved on its own.
| Stage | What happens |
|---|---|
| Your style | You add a style to res/values/styles.xml that uses BlinkRecognizerStyle as its parent and overrides the attributes you want to brand. |
CameraCharacteristics | You pass the style's resource ID via CameraCharacteristics.Builder().style(...). If you omit it, the SDK uses its default style (R.style.BlinkRecognizerStyle). |
| SDK theming | The SDK applies your style to the camera screen and wraps the Compose context in it. |
| Attribute resolution | The SDK resolves each theme attribute and builds the internal BlinkRecognizerStyle model that the camera UI renders from. The theme-level color attributes may point at ?attr/colorPrimary; the nested component styles are resolved on their own and do not inherit from your Material color roles. |
| Unset attributes | Anything you don't declare falls back to the SDK's own default — never to a blank or transparent value. |
2. Default Camera Chrome
Out of the box, the camera ships with a fixed, high-contrast neutral palette chosen to stay legible over arbitrary camera imagery: a dark scrim behind the overlay controls (torch, hint, cancel, retake), a white capture and confirm button, and a cobalt finish button.
| Role in the camera chrome | Value |
|---|---|
| Overlay control container | #262626 |
| Overlay control pressed state | #3D3D3D |
| Finish (primary action) button | #0062F2 |
| Capture / confirm button | #FFFFFF |
| Tooltip background | #262626 |
Brand colors are applied per component rather than inherited from your app's Material color roles. That keeps the camera chrome readable whatever your brand palette is, and it means you can brand one button without disturbing the others. Step 3 shows the pattern.
3. Setup (Host App)
Step 1 — Define your brand colors
In res/values/colors.xml, declare the colors you want the camera UI to use:
<resources>
<color name="brand_primary">#0B5FFF</color>
<color name="brand_primary_pressed">#0846BF</color>
<color name="brand_on_primary">#FFFFFF</color>
<color name="brand_chrome">#262626</color>
<color name="brand_chrome_pressed">#3D3D3D</color>
<color name="brand_chrome_icon">#FFFFFF</color>
</resources>
Step 2 — Extend BlinkRecognizerStyle
In res/values/styles.xml, create a style whose parent is the SDK's BlinkRecognizerStyle. Use dot-notation, which implies inheritance — BlinkRecognizerStyle.YourBrand automatically inherits everything from BlinkRecognizerStyle, so you only declare the attributes you want to change. Anything you leave out keeps the SDK default.
<style name="BlinkRecognizerStyle.YourBrand">
<!-- Drives the on-screen guidance hint and the scan progress bar -->
<item name="colorPrimary">@color/brand_primary</item>
<item name="colorOnPrimary">@color/brand_on_primary</item>
<item name="guidelineHintViewBackground">?attr/colorPrimary</item>
<item name="progressBarColor">?attr/colorPrimary</item>
</style>
colorPrimary covers the on-screen guidance hint and the scan progress bar. The buttons and the tooltip are branded separately, in Step 3 — brand only the ones you care about and the rest keep the SDK defaults.
Step 3 — Brand the buttons and tooltip
Each button and the tooltip has its own nested style. Point the corresponding attribute at your own style, then override just the colors and icons you want:
<style name="BlinkRecognizerStyle.YourBrand">
<!-- ...theme-level items from Step 2... -->
<item name="finishButtonStyle">@style/Widget.AppCompat.ImageButton.FinishButtonStyle.YourBrand</item>
</style>
<style name="Widget.AppCompat.ImageButton.FinishButtonStyle.YourBrand">
<item name="android:colorActivatedHighlight">@color/brand_primary</item>
<item name="android:colorAccent">@color/brand_primary</item>
<item name="android:colorPressedHighlight">@color/brand_primary_pressed</item>
<item name="android:tint">@color/brand_on_primary</item>
</style>
Nested styles use dot-notation inheritance too. Widget.AppCompat.ImageButton.FinishButtonStyle.YourBrand inherits from the SDK's Widget.AppCompat.ImageButton.FinishButtonStyle, so any attribute you leave out keeps the SDK's default icon and color rather than rendering blank.
See §5 Theme Attribute Reference for every attribute each component accepts, and §6 Full Example for a theme that brands all of them.
Step 4 — Pass the style to the camera
When you launch the camera through CameraRecognizerContract, set your style on the CameraCharacteristics:
import com.microblink.camera.ui.CameraCharacteristics
import com.microblink.camera.ui.CameraRecognizerContract
import com.microblink.camera.ui.CameraRecognizerOptions
import com.microblink.camera.ui.CameraRecognizerResults
val launcher = rememberLauncherForActivityResult(
contract = CameraRecognizerContract(),
) { result ->
when (result) {
is CameraRecognizerResults.Success -> scanResults = result.results
is CameraRecognizerResults.Exception -> Log.e("Scan", "Error: ${result.exception}")
CameraRecognizerResults.Cancelled -> { /* User cancelled */ }
}
}
launcher.launch(
CameraRecognizerOptions.Builder()
.options(scanOptions)
.characteristics(
CameraCharacteristics.Builder()
// Style name "BlinkRecognizerStyle.YourBrand" is referenced
// in code with the dot replaced by an underscore.
.style(R.style.BlinkRecognizerStyle_YourBrand)
.build()
)
.build()
)
A style named BlinkRecognizerStyle.YourBrand in XML is referenced from Kotlin/Java as R.style.BlinkRecognizerStyle_YourBrand — the resource compiler replaces dots with underscores. If you don't call .style(...), the camera falls back to R.style.BlinkRecognizerStyle, the SDK default.
4. The BlinkRecognizerStyle Model
Each theme attribute you set in XML maps to one property of the internal BlinkRecognizerStyle model. You don't interact with this model directly, but understanding it explains what each attribute controls:
| Model property | Controls | Themed by |
|---|---|---|
guidelineHintColor | Background of the on-screen guidance hint shown over the camera. | guidelineHintViewBackground |
shutterSurfaceColor | Surface that animates like a camera shutter on capture. | shutterSurfaceColor |
progressBarColor | Progress bar shown while a scan is being processed. | progressBarColor |
tooltipStyle | Informational tooltips (e.g. "The image is a bit blurry…"). | blinkTooltipStyle |
torchButtonStyle | Flashlight / torch toggle button. | torchButtonStyle |
cancelButtonStyle | Cancel (exit) button. | cancelButtonStyle |
hintButtonStyle | Hint button that opens the on-screen scanning guidance. | hintButtonStyle |
retakeButtonStyle | Retake-capture button. | retakeButtonStyle |
finishButtonStyle | Finish / done button. | finishButtonStyle |
captureButtonStyle | Main camera capture (shutter) button. | captureButtonStyle |
confirmButtonStyle | Confirm / "add another photo" button. | confirmButtonStyle |
5. Theme Attribute Reference
All attributes below are set as <item> entries inside your BlinkRecognizerStyle.YourBrand style (top-level colors) or inside the nested child styles it references (tooltip and buttons). Defaults are the values the SDK's base BlinkRecognizerStyle resolves to as of Paper Receipt SDK 2.3.1.
The attribute names in these tables are the supported theming surface. The SDK's own drawables, colors, and dimensions are internal and can change between releases — always point these attributes at resources in your own app.
Top-level colors
Declared directly on your style:
| Attribute | Format | Controls | Default |
|---|---|---|---|
colorPrimary | color | Primary brand color. Used as the default value for guidelineHintViewBackground and progressBarColor. | #0062F2 |
colorPrimaryContainer | color | Material 3 container color read by the Compose layer. Replaces colorPrimaryVariant, which is no longer read. | White |
colorOnPrimary | color | Foreground color on primary surfaces, read by the Compose layer. The tooltip and buttons set their own text and icon colors. | White |
guidelineHintViewBackground | color | Background of the on-screen guidance hint. | ?attr/colorPrimary |
progressBarColor | color | Color of the scan progress bar. | ?attr/colorPrimary |
shutterSurfaceColor | color | Color of the capture shutter animation surface. | Black |
Tooltip — blinkTooltipStyle
blinkTooltipStyle references a child style with these attributes:
| Attribute | Format | Controls | Default |
|---|---|---|---|
tooltipColor | color | Tooltip background and pointer color. | #262626 |
tooltipTextColor | color | Tooltip text color. | #FFFFFF |
tooltipPointHeight | dimension | Height of the tooltip's triangular pointer. | 8dp |
tooltipBoxRadius | dimension | Corner radius of the tooltip body. | 16dp |
Which tooltips appear, and for how long, is configured separately through TooltipCharacteristics — see Configuring tooltips. Their copy comes from string resources — see Camera UI Customization.
Torch button — torchButtonStyle
| Attribute | Format | Controls | Default |
|---|---|---|---|
android:colorAccent | color | Button container (background) color. | #262626 |
android:colorPressedHighlight | color | Ripple color on press. | #3D3D3D |
android:padding | dimension | Padding around the icon. | 0dp |
torchOnSrc | reference | Drawable for the torch-on (activated) state. | ic_torch_on |
torchOffSrc | reference | Drawable for the torch-off (normal) state. | ic_torch |
torchOnContentDescription | string | Accessibility text for the torch-on state. | Torch On |
torchOffContentDescription | string | Accessibility text for the torch-off state. | Torch Off |
android:tint | color | Icon tint. | White |
The default ic_torch and ic_torch_on artwork was redrawn in 2.3.1. If you set torchOnSrc / torchOffSrc to your own drawables, nothing changes for you.
Hint button — hintButtonStyle
New in 2.3.1. The hint button sits alongside the torch button and opens the on-screen scanning guidance.
| Attribute | Format | Controls | Default |
|---|---|---|---|
android:colorAccent | color | Button container (background) color. | #262626 |
android:colorPressedHighlight | color | Ripple color on press. | #3D3D3D |
android:padding | dimension | Padding around the icon. | 0dp |
android:src | reference | Button icon. | ic_hint |
android:contentDescription | string | Accessibility text. | Hint |
android:tint | color | Icon tint. | White |
Cancel button — cancelButtonStyle
| Attribute | Format | Controls | Default |
|---|---|---|---|
android:colorAccent | color | Button container (background) color. | #262626 |
android:colorPressedHighlight | color | Ripple color on press. | #3D3D3D |
android:padding | dimension | Padding around the icon. | 12dp |
android:src | reference | Button icon. | ic_exit |
android:contentDescription | string | Accessibility text. | @null |
android:tint | color | Icon tint. | White |
Action buttons — retakeButtonStyle, finishButtonStyle, captureButtonStyle, confirmButtonStyle
These four buttons share the same attribute shape — each renders as an inner and outer circle — but they no longer share the same defaults:
| Attribute | Format | Retake | Finish | Capture | Confirm |
|---|---|---|---|---|---|
android:colorActivatedHighlight | color | #3D3D3D | #0062F2 | #FFFFFF | #FFFFFF |
android:colorAccent | color | #262626 | #0062F2 | #FFFFFF | #FFFFFF |
android:colorPressedHighlight | color | #3D3D3D | #0062F2 | #FFFFFF | #FFFFFF |
android:src | reference | ic_retake | ic_confirm | ic_camera_capture | ic_add |
android:padding | dimension | 0dp | 0dp | 0dp | 0dp |
android:tint | color | White | White | Black | Black |
android:contentDescription | string | @null | @null | @null | @null |
android:colorActivatedHighlight is the primary (outer circle) container color, android:colorAccent the secondary (inner circle) container color, and android:colorPressedHighlight the ripple color on press.
These attributes are accepted by the resource compiler but have no effect on the camera UI. Set the listed replacement instead.
| Attribute | Where | Use instead | Ignored since |
|---|---|---|---|
android:background | Any button style | android:colorAccent, android:colorPressedHighlight, android:colorActivatedHighlight | 1.9.10 |
android:src | Torch button only | torchOnSrc and torchOffSrc | 1.9.10 |
android:statusBarColor, android:navigationBarColor, android:enforceStatusBarContrast, android:enforceNavigationBarContrast | Theme level | Nothing — the camera manages its own system bars | 1.9.10 |
colorPrimaryVariant | Theme level | colorPrimaryContainer | 2.3.1 |
colorSecondary, colorSecondaryVariant | Theme level | The per-button android:colorAccent / android:colorPressedHighlight | 2.3.1 |
android:src is read on the hint, cancel, retake, finish, capture, and confirm buttons — the exclusion applies only to the torch, which is a two-state control.
Note the android: prefix on android:tint. The unprefixed tint attribute is a different resource and is not read.
6. Full Example
A complete theme that brands every customizable surface. Copy it into your res/values/styles.xml, rename it to your brand, and point the drawable, color, and string references at resources in your own app.
<resources>
<style name="BlinkRecognizerStyle.YourBrand">
<!-- Theme-level colors -->
<item name="colorPrimary">@color/brand_primary</item>
<item name="colorOnPrimary">@color/brand_on_primary</item>
<item name="guidelineHintViewBackground">?attr/colorPrimary</item>
<item name="progressBarColor">?attr/colorPrimary</item>
<item name="shutterSurfaceColor">@android:color/black</item>
<!-- Per-component styles -->
<item name="blinkTooltipStyle">@style/Tooltip.YourBrand</item>
<item name="torchButtonStyle">@style/Widget.AppCompat.ImageButton.TorchButtonStyle.YourBrand</item>
<item name="hintButtonStyle">@style/Widget.AppCompat.ImageButton.HintButtonStyle.YourBrand</item>
<item name="cancelButtonStyle">@style/Widget.AppCompat.ImageButton.CancelButtonStyle.YourBrand</item>
<item name="retakeButtonStyle">@style/Widget.AppCompat.ImageButton.RetakeButtonStyle.YourBrand</item>
<item name="finishButtonStyle">@style/Widget.AppCompat.ImageButton.FinishButtonStyle.YourBrand</item>
<item name="captureButtonStyle">@style/Widget.AppCompat.ImageButton.CaptureButtonStyle.YourBrand</item>
<item name="confirmButtonStyle">@style/Widget.AppCompat.ImageButton.ConfirmButtonStyle.YourBrand</item>
</style>
<style name="Tooltip.YourBrand">
<item name="tooltipPointHeight">8dp</item>
<item name="tooltipBoxRadius">16dp</item>
<item name="tooltipColor">@color/brand_chrome</item>
<item name="tooltipTextColor">@color/brand_chrome_icon</item>
</style>
<!-- Overlay controls: keep high contrast against the live camera preview -->
<style name="Widget.AppCompat.ImageButton.TorchButtonStyle.YourBrand">
<item name="android:colorAccent">@color/brand_chrome</item>
<item name="android:colorPressedHighlight">@color/brand_chrome_pressed</item>
<item name="android:padding">0dp</item>
<item name="torchOnSrc">@drawable/your_ic_torch_on</item>
<item name="torchOffSrc">@drawable/your_ic_torch_off</item>
<item name="torchOnContentDescription">@string/your_cd_torch_on</item>
<item name="torchOffContentDescription">@string/your_cd_torch_off</item>
<item name="android:tint">@color/brand_chrome_icon</item>
</style>
<style name="Widget.AppCompat.ImageButton.HintButtonStyle.YourBrand">
<item name="android:colorAccent">@color/brand_chrome</item>
<item name="android:colorPressedHighlight">@color/brand_chrome_pressed</item>
<item name="android:padding">0dp</item>
<item name="android:src">@drawable/your_ic_hint</item>
<item name="android:contentDescription">@string/your_cd_hint</item>
<item name="android:tint">@color/brand_chrome_icon</item>
</style>
<style name="Widget.AppCompat.ImageButton.CancelButtonStyle.YourBrand">
<item name="android:colorAccent">@color/brand_chrome</item>
<item name="android:colorPressedHighlight">@color/brand_chrome_pressed</item>
<item name="android:padding">12dp</item>
<item name="android:src">@drawable/your_ic_close</item>
<item name="android:contentDescription">@string/your_cd_close</item>
<item name="android:tint">@color/brand_chrome_icon</item>
</style>
<!-- Review controls -->
<style name="Widget.AppCompat.ImageButton.RetakeButtonStyle.YourBrand">
<item name="android:colorActivatedHighlight">@color/brand_chrome_pressed</item>
<item name="android:colorAccent">@color/brand_chrome</item>
<item name="android:colorPressedHighlight">@color/brand_chrome_pressed</item>
<item name="android:src">@drawable/your_ic_retake</item>
<item name="android:tint">@color/brand_chrome_icon</item>
</style>
<style name="Widget.AppCompat.ImageButton.FinishButtonStyle.YourBrand">
<item name="android:colorActivatedHighlight">@color/brand_primary</item>
<item name="android:colorAccent">@color/brand_primary</item>
<item name="android:colorPressedHighlight">@color/brand_primary_pressed</item>
<item name="android:src">@drawable/your_ic_done</item>
<item name="android:tint">@color/brand_on_primary</item>
</style>
<!-- Capture controls: high-luminance surface, dark icon -->
<style name="Widget.AppCompat.ImageButton.CaptureButtonStyle.YourBrand">
<item name="android:colorActivatedHighlight">@color/brand_capture_surface</item>
<item name="android:colorAccent">@color/brand_capture_surface</item>
<item name="android:colorPressedHighlight">@color/brand_capture_surface</item>
<item name="android:src">@drawable/your_ic_capture</item>
<item name="android:tint">@color/brand_on_capture_surface</item>
</style>
<style name="Widget.AppCompat.ImageButton.ConfirmButtonStyle.YourBrand">
<item name="android:colorActivatedHighlight">@color/brand_capture_surface</item>
<item name="android:colorAccent">@color/brand_capture_surface</item>
<item name="android:colorPressedHighlight">@color/brand_capture_surface</item>
<item name="android:src">@drawable/your_ic_add</item>
<item name="android:tint">@color/brand_on_capture_surface</item>
</style>
</resources>
Every @drawable/your_*, @color/brand_*, and @string/your_* reference above lives in your app. The SDK's own drawables and colors are internal and are not guaranteed to survive an upgrade — don't reference them from your theme.
Each nested button and tooltip style also uses dot-notation inheritance, so it inherits from the SDK's base button styles and only needs to declare the icons and colors you override.
Launch the camera with this theme:
CameraCharacteristics.Builder()
.style(R.style.BlinkRecognizerStyle_YourBrand)
.build()
7. Tips and Pitfalls
Extend the base style. Always use BlinkRecognizerStyle (or one of its Widget.AppCompat.ImageButton.* button styles, including HintButtonStyle, or the Tooltip style) as a parent. Inheritance via dot-notation means unset attributes fall back to SDK defaults, so a missing icon or color never leaves a button blank.
Brand what users notice first. colorPrimary covers the guidance hint and the progress bar. Buttons and the tooltip each have their own style — start with the capture and finish buttons, which carry the most brand weight, and add the rest as needed.
Dots become underscores in code. The XML style BlinkRecognizerStyle.YourBrand is R.style.BlinkRecognizerStyle_YourBrand in Kotlin/Java.
Pass the style every launch. The theme is read from CameraCharacteristics when the camera starts. There is no global "set once" call — include .style(...) on every launch you want branded. Omitting it uses the SDK default.
Some attributes are silently ignored. android:background, android:src on the torch button, colorPrimaryVariant, colorSecondary, and colorSecondaryVariant compile fine but have no effect. Use the replacements in the warning above. Note the android: prefix on android:tint — the unprefixed tint attribute is not read.
Test over a live preview. The camera chrome sits on top of an unpredictable image. Check your colors against both a bright receipt and a dark background before shipping.
8. Migrating to Paper Receipt SDK 2.3.1
Themes written against 2.3.0 and earlier still compile and still apply. What follows is organized by what you'd actually observe after upgrading.
8.1 Your build fails
This only happens if your theme references an SDK-internal resource that was removed in 2.3.1.
Resources removed from the SDK
If your styles.xml references any of these, replace them with equivalents in your own app:
Drawables — default_secondary_button_background, ic_torch_selector, primary_secondary_color_selector, tooltip_secondary_action_button_selector
Colors — see_through_white, blink_blue_tapped, chip_background
Dimensions — secondary_btn_diameter, primary_btn_diameter
8.2 Your build succeeds but your colors are ignored
| Before (2.3.0 and earlier) | After (2.3.1 onwards) |
|---|---|
colorPrimaryVariant | colorPrimaryContainer |
colorSecondary, colorSecondaryVariant | Per-button android:colorAccent / android:colorPressedHighlight |
android:background on a button style | android:colorAccent, android:colorPressedHighlight, android:colorActivatedHighlight |
Unprefixed tint | android:tint |
The full list is in Attributes the SDK does not read.
8.3 Your colors apply but the camera looks different
Expected — this is the refreshed default chrome.
Before 2.3.1, the buttons and tooltip inherited from colorPrimary / colorSecondary. They now carry their own defaults, so a theme that only set the Material color roles will show the SDK's default chrome. Add the per-component styles from §6 Full Example to restore your branding, and see §2 Default Camera Chrome for the new palette.
8.4 New in 2.3.1
- Hint button — a new themeable overlay control. See
hintButtonStyle. TooltipCharacteristics.displayDataMissingTooltip— gates the "No receipt detected" tooltip, which was previously unreachable. See Configuring tooltips.- String resource changes — several camera strings were removed and others reworded. See Camera UI Customization.