ImagePickerKMP
The most complete camera & image picker library for Kotlin Multiplatform. One unified API for Android, iOS, Desktop, Web and WASM.
Camera Capture
Native camera with flash, rotation, zoom, crop and compression on Android & iOS.
Gallery Picker
Single & multiple selection, MIME filtering, selection limit, EXIF extraction.
Crop & Edit
Free, square and circular crop with zoom, rotation and aspect ratio lock.
EXIF Metadata
GPS, altitude, camera model, ISO, aperture, focal length — Android & iOS.
Cloud OCR
Gemini, OpenAI, Claude, Azure, Ollama and custom endpoints. All platforms.
Compression
LOW / MEDIUM / HIGH quality levels with async processing and bitmap recycling.
PDF & Format Support
JPEG, PNG, HEIC, HEIF, WebP, GIF, BMP and PDF across all platforms.
UI Customization
Custom button colors, icons, permission dialogs, confirmation screens and camera callbacks.
Requirements
| Requirement | Minimum Version | Notes |
|---|---|---|
| Kotlin | 2.3.20 | Breaking — ABI incompatible with < 2.3.x |
| Compose Multiplatform | 1.10.3 | Requires Kotlin 2.3.x |
| Android minSdk | 24 | |
| Android compileSdk | 36 | |
| iOS | 12.0+ | |
| JDK (Desktop) | 11+ |
ABI version incompatible. If you need Kotlin 2.1.x, use a previous release.Installation
Kotlin Multiplatform
// commonMain dependencies
implementation("io.github.ismoy:imagepickerkmp:1.0.35")
React / JavaScript (NPM)
npm install imagepickerkmp
ImagePickerLauncher inside a Box(Modifier.fillMaxSize()) — otherwise the camera preview won't render.Correct vs Incorrect Usage
Box(Modifier.fillMaxSize()) {
if (showCamera) {
ImagePickerLauncher(
config = ImagePickerConfig(...)
)
}
}
// No container — preview invisible
if (showCamera) {
ImagePickerLauncher(
config = ImagePickerConfig(...)
)
}
Permissions Setup
<!-- Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Camera access to capture photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access to select images</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save captured photos to your library</string>
<!-- Required when includeExif = true -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location for photo geotagging</string>
<!-- AndroidManifest.xml -->
<!-- No permissions required — the library manages them automatically -->
<!-- Optional: declare CAMERA only if your app directly uses the camera -->
<uses-permission
android:name="android.permission.CAMERA"
android:required="false" />
CAMERA permission is optional — add it only if your app directly accesses the camera outside of this library.Camera Capture
Use ImagePickerLauncher to open the native camera. Control flash, skip confirmation, add crop and set compression.
Basic Camera
var showCamera by remember { mutableStateOf(false) }
var photo by remember { mutableStateOf<PhotoResult?>(null) }
Box(modifier = Modifier.fillMaxSize()) {
if (showCamera) {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
photo = result
showCamera = false
},
onError = { showCamera = false },
onDismiss = { showCamera = false }
)
)
}
Button(onClick = { showCamera = true }) {
Text("Open Camera")
}
}
Camera with Compression & Crop
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* handle result */ },
onDismiss = { showCamera = false },
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.MEDIUM,
skipConfirmation = false,
includeExif = true,
cropConfig = CropConfig(
enabled = true,
circularCrop = true,
squareCrop = true,
freeformCrop = true
)
)
)
)
CameraCaptureConfig Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
compressionLevel | CompressionLevel? | null | Image compression level |
skipConfirmation | Boolean | false | Skip preview/confirm screen |
includeExif | Boolean | false | Extract EXIF metadata |
cropConfig | CropConfig? | null | Crop configuration |
mimeTypes | List<MimeType> | [IMAGE_ALL] | Allowed MIME types |
Gallery Picker
Use GalleryPickerLauncher for single or multiple photo selection from the device's photo library.
Single Selection
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
GalleryPickerLauncher(
config = GalleryPickerConfig(includeExif = true),
onPhotosSelected = { photos ->
showGallery = false
},
onError = { showGallery = false },
onDismiss = { showGallery = false },
allowMultiple = false
)
}
Multiple Selection with MIME Filter
GalleryPickerLauncher(
onPhotosSelected = { photos ->
photos.forEach { photo ->
println("Camera: ${photo.exif?.camera}")
println("GPS: ${photo.exif?.latitude}, ${photo.exif?.longitude}")
println("Size: ${photo.fileSize} bytes")
}
},
onError = { error -> mimeTypeMismatchMessage = error.message },
onDismiss = { showGallery = false },
allowMultiple = true,
mimeTypes = listOf(
MimeType.IMAGE_JPEG,
MimeType.IMAGE_PNG,
MimeType.IMAGE_WEBP
),
mimeTypeMismatchMessage = "Only JPEG, PNG and WebP images are allowed"
)
Images + PDFs
GalleryPickerLauncher(
allowMultiple = true,
mimeTypes = listOf(
MimeType.IMAGE_JPEG,
MimeType.IMAGE_PNG,
MimeType.APPLICATION_PDF
),
onPhotosSelected = { files -> /* handle mixed files */ },
onError = { },
onDismiss = { showGallery = false }
)
Image Crop
Built-in crop UI with free, square and circular modes plus zoom and rotation controls. Works on Android, iOS, Desktop and Web.
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result -> photo = result },
onDismiss = { showCamera = false },
cameraCaptureConfig = CameraCaptureConfig(
cropConfig = CropConfig(
enabled = true,
circularCrop = true, // show circle crop button
squareCrop = true, // show square crop button
freeformCrop = true // show free-form crop button
)
)
)
)
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | false | Enable crop UI after capture |
circularCrop | Boolean | false | Show circular crop option |
squareCrop | Boolean | false | Show square crop option |
freeformCrop | Boolean | true | Show free-form crop option |
Image Compression
Automatic background compression with configurable levels. Works for camera and gallery on Android and iOS.
// Camera with compression
ImagePickerLauncher(
config = ImagePickerConfig(
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.HIGH
)
)
)
// Gallery with compression
GalleryPickerLauncher(
compressionLevel = CompressionLevel.MEDIUM,
onPhotosSelected = { /* compressed photos */ },
onError = { },
onDismiss = { }
)
| Level | JPEG Quality | Max Dimension | Use Case |
|---|---|---|---|
LOW | 85% | 3840 px (4K) | Near-lossless, large files |
MEDIUM | 70% | 1920 px (FHD) | Balanced quality/size |
HIGH | 50% | 1280 px (HD) | Maximum size reduction |
CameraCaptureConfig uses CompressionLevel.MEDIUM by default. Set to null to disable compression entirely and get the original full-quality image.EXIF Metadata
Extract rich metadata from photos on Android and iOS. Requires includeExif = true.
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
result.exif?.let { exif ->
println("GPS: ${exif.latitude}, ${exif.longitude}")
println("Camera: ${exif.cameraModel}")
println("Date: ${exif.dateTaken}")
println("Flash: ${exif.flash}")
println("ISO: ${exif.iso}")
println("Exposure: ${exif.exposureTime}")
println("Size: ${exif.imageWidth} x ${exif.imageHeight} px")
}
},
cameraCaptureConfig = CameraCaptureConfig(includeExif = true)
)
)
// Gallery EXIF (iOS requires photo library permission)
GalleryPickerLauncher(
config = GalleryPickerConfig(includeExif = true),
onPhotosSelected = { photos ->
photos.forEach { photo ->
val gps = "${photo.exif?.latitude}, ${photo.exif?.longitude}"
}
},
onError = { },
onDismiss = { }
)
| Field | Type | Description |
|---|---|---|
| GPS | ||
latitude | Double? | GPS latitude — redacted by default (redactGpsData = true) |
longitude | Double? | GPS longitude — redacted by default |
altitude | Double? | GPS altitude in meters — redacted by default |
| Date & Time | ||
dateTaken | String? | Date and time photo was taken |
dateTime | String? | General date/time (alias of dateTaken) |
digitizedTime | String? | Date image was digitized |
modifiedTime | String? | Last modified date |
| Camera | ||
cameraModel | String? | Camera/device model |
cameraManufacturer | String? | Camera manufacturer |
software | String? | Processing software |
| Capture Settings | ||
flash | String? | Flash status |
iso | String? | ISO sensitivity |
aperture | String? | Aperture f-stop value |
shutterSpeed | String? | Shutter speed (exposure time) |
focalLength | String? | Focal length in mm |
whiteBalance | String? | White balance setting |
exposureBias | String? | Exposure compensation |
meteringMode | String? | Metering mode used |
| Image Properties | ||
imageWidth | Int? | Original width in pixels |
imageHeight | Int? | Original height in pixels |
orientation | String? | Image rotation/orientation |
colorSpace | String? | Color space (sRGB, Adobe RGB, etc.) |
thumbnail | String? | Base64 thumbnail (~5–20 KB). Avoid caching. |
latitude, longitude and altitude are set to null unless you explicitly set redactGpsData = false in CameraCaptureConfig or GalleryConfig. Only disable redaction when the user has been clearly informed.Extension Functions
Process PhotoResult and GalleryPhotoResult with built-in extensions for common operations.
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// For Compose UI
val painter: Painter = result.loadPainter()
// For file operations / upload
val bytes: ByteArray = result.loadBytes()
// For canvas / graphics
val bitmap: ImageBitmap = result.loadImageBitmap()
// For REST APIs
val base64: String = result.loadBase64()
// File info
println("Name: ${result.fileName}")
println("Size: ${result.fileSize} bytes")
println("MIME: ${result.mimeType}")
// Convert to KB
val sizeKB = (result.fileSize ?: 0) / 1024.0
}
)
)
MIME Types
Use MimeType enum values to filter what files can be selected in GalleryPickerLauncher.
| Value | MIME String | Description |
|---|---|---|
MimeType.IMAGE_ALL | image/* | All image formats (default) |
MimeType.IMAGE_JPEG | image/jpeg | JPEG images |
MimeType.IMAGE_PNG | image/png | PNG images |
MimeType.IMAGE_WEBP | image/webp | WebP images |
MimeType.IMAGE_GIF | image/gif | GIF images |
MimeType.IMAGE_BMP | image/bmp | BMP images |
MimeType.IMAGE_HEIC | image/heic | HEIC — iOS native format |
MimeType.IMAGE_HEIF | image/heif | HEIF — iOS native format |
MimeType.APPLICATION_PDF | application/pdf | PDF documents |
Utility Methods
// Convert to string list
val strings = MimeType.toMimeTypeStrings(
MimeType.IMAGE_JPEG,
MimeType.IMAGE_PNG
)
// → ["image/jpeg", "image/png"]
// Parse from string
val mt = MimeType.fromString("image/webp")
// → MimeType.IMAGE_WEBP
// Predefined groups
MimeType.COMMON_IMAGE_TYPES // JPEG, PNG, GIF, WebP
MimeType.ALL_SUPPORTED_TYPES // all enum entries
UI Customization
Customize the camera UI look, lifecycle callbacks, permission dialogs and confirmation screen.
UiConfig — Camera Visual Style
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { /* ... */ },
cameraCaptureConfig = CameraCaptureConfig(
captureButtonSize = 80.dp,
uiConfig = UiConfig(
buttonColor = Color(0xFF0EA5E9), // capture button color
iconColor = Color.White, // flash / switch icons color
buttonSize = 80.dp,
flashIcon = Icons.Default.FlashOn,
switchCameraIcon = Icons.Default.Cameraswitch
)
)
)
)
| Parameter | Type | Description |
|---|---|---|
buttonColor | Color? | Capture button background color |
iconColor | Color? | Icon color inside camera UI |
buttonSize | Dp? | Capture button size |
flashIcon | ImageVector? | Custom flash toggle icon |
switchCameraIcon | ImageVector? | Custom camera switch icon |
galleryIcon | ImageVector? | Custom gallery access icon |
CameraCallbacks — Lifecycle Events
CameraCaptureConfig(
cameraCallbacks = CameraCallbacks(
onCameraReady = {
// Camera preview is ready — hide loading indicator
isLoading = false
},
onCameraSwitch = {
// User switched between front/back camera
},
onPermissionError = { exception ->
// Permission denied or unavailable
showError(exception.message)
},
onGalleryOpened = {
// User navigated to gallery from camera
}
)
)
CapturePhotoPreference — Quality Mode
| Value | Description |
|---|---|
CapturePhotoPreference.FAST | Prioritizes capture speed, lower quality |
CapturePhotoPreference.BALANCED | Default — balanced speed and quality |
CapturePhotoPreference.QUALITY | Best image quality, slower capture |
PermissionAndConfirmationConfig — Permission Dialogs & Confirmation Screen
All permission and confirmation customization lives inside PermissionAndConfirmationConfig, nested in CameraCaptureConfig. The parameters below are all optional — omit any you don't need.
| Parameter | Type | Default | Description |
|---|---|---|---|
skipConfirmation | Boolean | false | Skip confirmation screen — deliver photo directly via onPhotoCaptured |
customConfirmationView | @Composable (PhotoResult, (PhotoResult)->Unit, ()->Unit) -> Unit | null | Replace the built-in post-capture preview & confirm screen |
customDeniedDialog | @Composable ((onRetry: ()->Unit) -> Unit) | null | Dialog shown when camera permission is denied (can retry) |
customSettingsDialog | @Composable ((onOpenSettings: ()->Unit) -> Unit) | null | Dialog shown when permission is permanently denied (open settings) |
cancelButtonTextIOS | String? | "Cancel" | iOS only — label of the cancel button in the permission alert |
onCancelPermissionConfigIOS | (() -> Unit)? | null | iOS only — callback when user taps cancel in permission alert |
customPickerDialog and directCameraLaunch do not exist in ImagePickerConfig — they are OCR-only parameters (ImagePickerOCRConfig). Do not use them with the standard picker.1 — Skip confirmation (deliver photo immediately)
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result -> // called immediately after capture },
onError = { },
cameraCaptureConfig = CameraCaptureConfig(
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
skipConfirmation = true // no review screen shown
)
)
)
)
2 — Custom confirmation screen (Android)
Receives photoResult: PhotoResult, onConfirm: (PhotoResult) -> Unit and onRetry: () -> Unit. Use result.uri to display the preview.
CameraCaptureConfig(
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customConfirmationView = { photoResult, onConfirm, onRetry ->
MyConfirmationView(
result = photoResult,
onConfirm = onConfirm,
onRetry = onRetry
)
}
)
)
// Example implementation:
@Composable
fun MyConfirmationView(
result: PhotoResult,
onConfirm: (PhotoResult) -> Unit,
onRetry: () -> Unit
) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Show preview using result.uri
AsyncImage(
model = result.uri, // ✅ correct — use .uri not .image
contentDescription = "Preview",
modifier = Modifier.fillMaxWidth().weight(1f)
.clip(RoundedCornerShape(16.dp)),
contentScale = ContentScale.Crop
)
// Show file info using correct fields
Text("Size: ${(result.fileSize ?: 0) / 1024} KB") // ✅ fileSize in bytes
Text("Format: ${result.mimeType ?: "unknown"}") // ✅ mimeType (not .format)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
OutlinedButton(onClick = onRetry, modifier = Modifier.weight(1f)) {
Text("Retry")
}
Button(onClick = { onConfirm(result) }, modifier = Modifier.weight(1f)) {
Text("Use Photo")
}
}
}
}
3 — Custom permission denied dialog
Shown when the user denies camera permission. Provides an onRetry lambda to re-launch the system permission prompt.
PermissionAndConfirmationConfig(
customDeniedDialog = { onRetry ->
Dialog(onDismissRequest = {}) {
Card(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Text("Camera permission needed", fontWeight = FontWeight.Bold)
Spacer(Modifier.height(12.dp))
Text("We need camera access to take photos.", color = Color.Gray)
Spacer(Modifier.height(20.dp))
Button(onClick = onRetry, modifier = Modifier.fillMaxWidth()) {
Text("Grant Permission")
}
}
}
}
}
)
4 — Custom settings dialog (permanently denied)
Shown when the user has permanently denied the permission. Provides onOpenSettings to navigate to the system app settings.
PermissionAndConfirmationConfig(
customSettingsDialog = { onOpenSettings ->
Dialog(onDismissRequest = {}) {
Card(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Text("Permission Permanently Denied", fontWeight = FontWeight.Bold)
Spacer(Modifier.height(12.dp))
Text("Go to Settings > Permissions > Camera to enable access.", color = Color.Gray)
Spacer(Modifier.height(20.dp))
Button(onClick = onOpenSettings, modifier = Modifier.fillMaxWidth()) {
Text("Open Settings")
}
}
}
}
}
)
5 — Full combined example
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// result.uri, result.fileSize (bytes), result.mimeType, result.width, result.height
},
onError = { exception -> showError(exception.message) },
onDismiss = { navigateBack() },
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.HIGH,
preference = CapturePhotoPreference.QUALITY,
uiConfig = UiConfig(
buttonColor = Color(0xFF0EA5E9),
iconColor = Color.White
),
cameraCallbacks = CameraCallbacks(
onCameraReady = { isLoading = false },
onPermissionError = { showPermissionBanner() }
),
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
skipConfirmation = false,
customConfirmationView = { photoResult, onConfirm, onRetry ->
MyConfirmationView(photoResult, onConfirm, onRetry)
},
customDeniedDialog = { onRetry ->
MyDeniedDialog(onRetry = onRetry)
},
customSettingsDialog = { onOpenSettings ->
MySettingsDialog(onOpenSettings = onOpenSettings)
}
)
),
enableCrop = false
)
)
result.uri (not .image), result.fileSize in bytes (not KB), result.mimeType (not .format), result.width, result.height.Gallery Options — GalleryConfig
GalleryPickerLauncher(
config = GalleryConfig(
allowMultiple = true,
selectionLimit = 10, // iOS only — max 10 items
includeExif = true,
redactGpsData = false, // expose GPS (inform user!)
mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG)
),
onPhotosSelected = { photos -> /* ... */ },
onError = { },
onDismiss = { }
)
| Parameter | Type | Default | Description |
|---|---|---|---|
allowMultiple | Boolean | false | Enable multi-file selection |
mimeTypes | List<MimeType> | [IMAGE_ALL] | Allowed file types |
selectionLimit | Int | 30 | Max items when allowMultiple (iOS) |
includeExif | Boolean | false | Extract EXIF metadata |
redactGpsData | Boolean | true | Strip GPS from EXIF (privacy default) |
React / Web Integration
ImagePickerKMP is available as an NPM package for JavaScript/TypeScript projects including React, Vue, Angular and Vanilla JS.
Installation
npm install imagepickerkmp
React Component
import { useImagePicker } from 'imagepickerkmp';
function PhotoUploader() {
const { openCamera, openGallery, result } = useImagePicker({
onPhotoCaptured: (photo) => {
console.log('Photo:', photo.fileName, photo.fileSize);
},
onError: (err) => console.error(err)
});
return (
<div>
<button onClick={openCamera}>Open Camera</button>
<button onClick={openGallery}>Open Gallery</button>
{result && <img src={result.uri} alt="captured" />}
</div>
);
}
WebRTC Camera
Browser camera via WebRTC. Works on mobile & desktop.
Drag & Drop
File picker with drag and drop support.
TypeScript
Full type definitions included out of the box.
Cross-Framework
React, Vue, Angular, Vanilla JS.
Cloud OCR Experimental
Extract text from images and PDFs using cloud AI providers. Supports Gemini, OpenAI, Claude, Azure, Ollama and any custom HTTP endpoint.
Gemini OCR
var isOCRActive by remember { mutableStateOf(false) }
var ocrResult by remember { mutableStateOf<OCRResult?>(null) }
@OptIn(ExperimentalOCRApi::class)
if (isOCRActive) {
ImagePickerLauncherOCR(
config = ImagePickerOCRConfig(
scanMode = ScanMode.Cloud(
provider = CloudOCRProvider.Gemini("YOUR_GEMINI_API_KEY")
),
onOCRCompleted = { result ->
ocrResult = result
isOCRActive = false
},
onError = { isOCRActive = false },
onCancel = { isOCRActive = false },
allowedMimeTypes = listOf(MimeType.APPLICATION_PDF, MimeType.IMAGE_ALL),
directCameraLaunch = false
)
)
}
Custom HTTP Endpoint
CloudOCRProvider.Custom(
name = "My OCR Service",
baseUrl = "https://api.mycompany.com/ocr/analyze",
apiKey = "abc123",
headers = mapOf(
"X-API-Version" to "2.1",
"X-Client-ID" to "mobile-app",
"Authorization" to "Bearer $token"
),
requestFormat = RequestFormat.MULTIPART_FORM,
model = "enterprise-model-v3"
)
| Provider | Value |
|---|---|
| Google Gemini | CloudOCRProvider.Gemini("API_KEY") |
| OpenAI | CloudOCRProvider.OpenAI("API_KEY") |
| Anthropic Claude | CloudOCRProvider.Claude("API_KEY") |
| Azure AI | CloudOCRProvider.Azure("ENDPOINT", "KEY") |
| Ollama (local) | CloudOCRProvider.Ollama("http://localhost:11434") |
| Custom | CloudOCRProvider.Custom(...) |
PDF Support
Select PDF documents alongside images. On Android, PDFs automatically open the system file explorer.
GalleryPickerLauncher(
allowMultiple = true,
mimeTypes = listOf(
MimeType.IMAGE_ALL,
MimeType.APPLICATION_PDF
),
onPhotosSelected = { files ->
files.forEach { file ->
when (file.mimeType) {
"application/pdf" -> handlePDF(file)
else -> handleImage(file)
}
}
},
onError = { },
onDismiss = { showGallery = false }
)
Platform Support Matrix
| Feature | Android | iOS | Desktop | JS/Web | WASM |
|---|---|---|---|---|---|
| Camera Capture | |||||
| Gallery Picker | |||||
| Crop UI | |||||
| EXIF Metadata | |||||
| Compression | |||||
| OCR | |||||
| PDF Support | |||||
| Multiple Selection |
API Reference
ImagePickerConfig
| Parameter | Type | Description |
|---|---|---|
onPhotoCaptured | (PhotoResult) -> Unit | Called when photo is captured or selected |
onError | (Exception) -> Unit | Called on any error |
onDismiss | () -> Unit | Called when dismissed without selecting |
cameraCaptureConfig | CameraCaptureConfig | Camera, compression, UI and crop config |
enableCrop | Boolean | Show crop UI after capture (default: false) |
CameraCaptureConfig
| Parameter | Type | Default | Description |
|---|---|---|---|
preference | CapturePhotoPreference | BALANCED | Quality/speed tradeoff |
captureButtonSize | Dp | 72.dp | Shutter button size |
compressionLevel | CompressionLevel? | MEDIUM | Compression — null = no compression |
includeExif | Boolean | false | Extract EXIF metadata |
redactGpsData | Boolean | true | Strip GPS from EXIF (privacy) |
uiConfig | UiConfig | UiConfig() | Camera UI visual customization |
cameraCallbacks | CameraCallbacks | CameraCallbacks() | Camera lifecycle callbacks |
permissionAndConfirmationConfig | PermissionAndConfirmationConfig | …() | Permission dialogs & confirmation screen |
cropConfig | CropConfig | CropConfig() | Crop UI configuration |
GalleryConfig
| Parameter | Type | Default | Description |
|---|---|---|---|
allowMultiple | Boolean | false | Enable multi-file selection |
mimeTypes | List<MimeType> | [IMAGE_ALL] | Allowed MIME types |
selectionLimit | Int | 30 | Max items — iOS only |
includeExif | Boolean | false | Extract EXIF metadata |
redactGpsData | Boolean | true | Strip GPS from EXIF |
CropConfig
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | false | Enable crop UI after capture |
aspectRatioLocked | Boolean | false | Lock aspect ratio during crop |
circularCrop | Boolean | true | Show circular crop option |
squareCrop | Boolean | true | Show square crop option |
freeformCrop | Boolean | false | Show free-form crop option |
PhotoResult / GalleryPhotoResult
GalleryPhotoResult is a typealias for PhotoResult — they are the same model.
| Field | Type | Description |
|---|---|---|
uri | String? | Platform-native URI of the selected file |
fileName | String? | File name (reflects cropped name after crop) |
fileSize | Long? | Size in bytes since v1.0.35 — divide by 1024 for KB |
mimeType | String? | MIME type string (e.g. "image/jpeg", "application/pdf") |
width | Int? | Image width in pixels |
height | Int? | Image height in pixels |
exif | ExifData? | EXIF metadata — populated when includeExif = true |
fileSize returns bytes (previously KB). Migrate: val sizeKB = (result.fileSize ?: 0) / 1024.0ExifData Fields
All fields are nullable. Availability depends on the device, image origin and whether redactGpsData is false.
| Field | Type | Description |
|---|---|---|
| GPS & Location | ||
latitude | Double? | GPS latitude (stripped if redactGpsData = true) |
longitude | Double? | GPS longitude |
altitude | Double? | GPS altitude in metres |
| Date & Time | ||
dateTime | String? | Capture date/time (yyyy:MM:dd HH:mm:ss) |
dateTimeOriginal | String? | Original capture date |
dateTimeDigitized | String? | Digitized date |
| Camera & Lens | ||
make | String? | Camera manufacturer |
model | String? | Camera model name |
lensModel | String? | Lens model |
focalLength | String? | Focal length in mm |
aperture | String? | Aperture (f-number) |
| Capture Settings | ||
iso | String? | ISO sensitivity |
shutterSpeed | String? | Shutter speed (APEX) |
exposureTime | String? | Exposure time in seconds |
exposureBias | String? | Exposure compensation (EV) |
meteringMode | String? | Metering mode (multi, spot, etc.) |
flash | String? | Flash fired / not fired |
whiteBalance | String? | White balance mode |
| Image Properties | ||
imageWidth | String? | Image pixel width (EXIF tag) |
imageHeight | String? | Image pixel height (EXIF tag) |
orientation | String? | EXIF orientation (1–8) |
colorSpace | String? | Color space (sRGB, Adobe RGB, etc.) |
software | String? | Software used to produce the image |
thumbnailOffset | Int? | Byte offset of embedded thumbnail |
thumbnailLength | Int? | Byte length of embedded thumbnail |
Changelog
Recent releases and what changed in each version. Full history on GitHub Releases.
- Breaking:
fileSizenow returns bytes (was KB). Divide by 1024 to get KB. - New: Improved WASM target support with better browser compatibility.
- New:
MimeType.APPLICATION_PDF— select PDFs from gallery. - Fixed crop rotation on iOS in landscape orientation.
- Detekt static analysis integrated into CI pipeline.
- New: Cloud OCR — Gemini, OpenAI, Claude, Azure, Ollama, Custom endpoint.
- New:
@ExperimentalOCRApiannotation for opt-in usage. - New:
directCameraLaunchflag in OCR config. - Improved error handling with typed
ImagePickerException.
- New: Circular, square and freeform crop modes with zoom/rotation.
- New: EXIF metadata extraction — GPS, ISO, exposure, camera model.
- Fix: Memory leak on Android when cancelling gallery picker.
- Kotlin 2.3.x required — ABI incompatible with older versions.
- New: Multiple gallery selection with
allowMultiple = true. - New: MIME type filtering with
mimeTypeMismatchMessage. - Android smart picker: images → gallery, PDFs → file explorer, mixed → file explorer.