Librería Multiplataforma de Selección de Imágenes y Cámara (Android, iOS & Desktop)
Construida con Kotlin Multiplatform + Compose Multiplatform + Kotlin/Native
English • GitHub • Maven Central • Paquete NPM • Guía React • Discord
Guías completas y referencias para cada aspecto de ImagePickerKMP
- Guía de Integración - Guía completa de configuración e integración
- Guía de Personalización - Personalización de UI y comportamiento
- Guía de Integración React - Guía completa de integración React/NPM
- Guía de Internacionalización - Guía de soporte multi-idioma
- Guía de Permisos - Detalles de manejo de permisos
- Guía de Cobertura - Cobertura de código y guía de pruebas
- Configuración de Notificaciones - Configuración de notificaciones Discord
- Referencia API - Documentación completa de API
- Ejemplos - Ejemplos de código y casos de uso
| Cámara Android | Recorte Android | Cámara iOS | Recorte iOS |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Desktop | Web | ||
![]() |
![]() |
||
¡ImagePickerKMP ahora está disponible como paquete NPM para desarrollo web!
npm install imagepickerkmpGuía Completa de Integración React →
Características para web:
- Componentes React listos para usar
- Soporte TypeScript con definiciones completas de tipos
- Acceso a Cámara vía WebRTC (móvil y desktop)
- Selector de Archivos con soporte drag & drop
- Procesamiento de Imágenes (Base64, Bytes)
- Cross-Framework (React, Vue, Angular, Vanilla JS)
- Multiplataforma: Funciona perfectamente en Android e iOS
- Integración de Cámara: Acceso directo a la cámara con captura de fotos
- Selección de Galería: Selecciona imágenes de la galería del dispositivo con soporte de compresión
- Recorte Avanzado de Imágenes: Funcionalidad de recorte multiplataforma con gestión automática de contexto
- Metadatos EXIF: Extrae GPS, información de cámara y timestamps (Android/iOS)
- Soporte de PDF: Selección de documentos PDF junto con imágenes
- Compresión Automática de Imágenes: Optimiza el tamaño de imagen manteniendo la calidad
- Niveles de Compresión Configurables: Opciones de compresión BAJA, MEDIA, ALTA
- Procesamiento Asíncrono: UI no bloqueante con integración de Kotlin Coroutines
- Soporte de Múltiples Formatos: JPEG, PNG, HEIC, HEIF, WebP, GIF, BMP, PDF
- Funciones de Extensión: Funciones de extensión integradas para facilitar la visualización y manipulación de imágenes
- UI Personalizable: Diálogos personalizados y vistas de confirmación
- Manejo de Permisos: Gestión inteligente de permisos para ambas plataformas
- Integración Fácil: API simple con Compose Multiplatform
- Experiencia de Usuario Mejorada: Sistema de diseño mejorado con manejo adecuado de zoom y relación de aspecto
- Altamente Configurable: Opciones extensas de personalización
| Requisito | Versión mínima |
|---|---|
| Kotlin | 2.3.20 (cambio incompatible — ver CHANGELOG) |
| Compose Multiplatform | 1.10.3 |
| Ktor | 3.4.1 |
Android minSdk |
24 |
Android compileSdk |
36 |
Nota: Esta librería está compilada con Kotlin 2.3.20. Los proyectos que usen Kotlin < 2.3.x obtendrán un error de incompatibilidad de ABI en tiempo de compilación. Si necesitas soporte para Kotlin 2.1.x, usa una versión anterior de esta librería.
En tu commonMain build.gradle.kts:
dependencies {
implementation("io.github.ismoy:imagepickerkmp:{$lastVersion}")
}Incluso si no estás usando KMP, puedes usar ImagePickerKMP en proyectos Android puros con Jetpack Compose.
implementation("io.github.ismoy:imagepickerkmp:{$lastVersion}")No olvides configurar los permisos específicos de iOS en tu archivo Info.plist:
<key>NSCameraUsageDescription</key>
<string>Necesitamos acceso a la cámara para capturar una foto.</string>Resumen: Usa
rememberImagePickerKMPpara todo código nuevo. Las APIsImagePickerLauncher/GalleryPickerLauncherestán deprecadas y serán eliminadas en una versión futura.
| API Heredada (v1) |
API Nueva (v2) ✅ Recomendada | |
|---|---|---|
| Cámara | ImagePickerLauncher(config = ...) |
picker.launchCamera() |
| Galería | GalleryPickerLauncher(...) |
picker.launchGallery() |
| Resultado | Callbacks (onPhotoCaptured, onDismiss, onError) |
Reactivo: when (picker.result) |
| Estado | Booleans manuales showCamera/showGallery |
Automático via ImagePickerKMPState |
| Overrides por lanzamiento | No soportado | Todos los params opcionales en cada launch*() |
| Reset | Llamar callback onDismiss |
picker.reset() |
| Configuración | ImagePickerConfig + GalleryPickerConfig |
ImagePickerKMPConfig (unificado) |
| Patrón legacy | Equivalente en API nueva |
|---|---|
var showCamera by remember { ... } |
(eliminar — no se necesita) |
showCamera = true |
picker.launchCamera() |
showGallery = true |
picker.launchGallery() |
onPhotoCaptured = { result -> ... } |
is ImagePickerResult.Success -> result.photos |
onDismiss = { showCamera = false } |
is ImagePickerResult.Dismissed -> ... |
onError = { e -> ... } |
is ImagePickerResult.Error -> result.exception |
ImagePickerConfig(cameraCaptureConfig = ...) |
ImagePickerKMPConfig(cameraCaptureConfig = ...) |
GalleryPickerConfig(includeExif = true) |
ImagePickerKMPConfig(galleryConfig = GalleryConfig(includeExif = true)) |
GalleryPickerLauncher(allowMultiple = true, selectionLimit = 5) |
picker.launchGallery(allowMultiple = true, selectionLimit = 5) |
val picker = rememberImagePickerKMP(
config = ImagePickerKMPConfig(
galleryConfig = GalleryConfig(allowMultiple = true, selectionLimit = 10)
)
)
// Botones que lanzan el picker directamente — sin booleans
Button(onClick = { picker.launchCamera() }) { Text("Cámara") }
Button(onClick = { picker.launchGallery() }) { Text("Galería") }
// Override por lanzamiento — sobreescribe el config global solo para este tap
Button(onClick = {
picker.launchGallery(
allowMultiple = true,
selectionLimit = 5,
mimeTypes = listOf(MimeType.IMAGE_JPEG)
)
}) { Text("Galería (solo JPEG, máx 5)") }
// Resultado reactivo
when (val result = picker.result) {
is ImagePickerResult.Loading -> CircularProgressIndicator()
is ImagePickerResult.Success -> result.photos.forEach { photo ->
photo.loadPainter()?.let { Image(it, contentDescription = null) }
}
is ImagePickerResult.Error -> Text("Error: ${result.exception.message}", color = Color.Red)
is ImagePickerResult.Dismissed -> Text("Sin selección", color = Color.Gray)
is ImagePickerResult.Idle -> Text("Listo", color = Color.Gray)
}El compilador mostrará una advertencia de deprecación para guiar la migración. El código sigue compilando y ejecutándose sin cambios.
var showCamera by remember { mutableStateOf(false) }
var capturedPhoto by remember { mutableStateOf<PhotoResult?>(null) }if (showCamera) {
ImagePickerLauncher( // ⚠️ Deprecado — migrar a rememberImagePickerKMP
config = ImagePickerConfig(
enableCrop = false, // Establecer a true si quieres la opción de Recorte
onPhotoCaptured = { result ->
capturedPhoto = result
println("Tamaño de foto de cámara: ${result.fileSize} bytes")
showCamera = false
},
onError = {
showCamera = false
},
onDismiss = {
showCamera = false
},
directCameraLaunch = false, // Solo iOS: lanzar cámara directamente
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.HIGH,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
skipConfirmation = true
)
)
)
)
}var showGallery by remember { mutableStateOf(false) }
var selectedImages by remember { mutableStateOf<List<GalleryPhotoHandler.PhotoResult>>(emptyList()) }if (showGallery) {
GalleryPickerLauncher(
onPhotosSelected = { photos ->
selectedImages = photos
showGallery = false
// ✅ Acceder a datos EXIF si está habilitado
photos.forEach { photo ->
photo.exif?.let { exif ->
println("Ubicación GPS: ${exif.latitude}, ${exif.longitude}")
println("Cámara: ${exif.camera}")
println("Fecha/Hora: ${exif.dateTime}")
}
}
},
onError = { error ->
showGallery = false
},
onDismiss = {
println("Usuario canceló o cerró el selector")
showGallery = false // Restablecer estado cuando el usuario no selecciona nada
},
enableCrop = false, // Establecer a true si quieres la opción de Recorte
allowMultiple = true, // False para selección única
mimeTypes = listOf(MimeType.IMAGE_PNG), // Opcional: filtrar por tipo
includeExif = true, // ✅ IMPORTANTE: Habilita extracción de metadatos EXIF
// Para incluir PDFs: listOf(MimeType.IMAGE_PNG, MimeType.APPLICATION_PDF)
)
}
Button(onClick = { showGallery = true }) {
Text("Elegir de la Galería")
}Para más personalización (vistas de confirmación, filtrado MIME, etc.), consulta la guía de integración para KMP.
ImagePickerKMP incluye funciones de extensión integradas que simplifican el manejo y visualización de imágenes. Estas extensiones funcionan perfectamente tanto con ImagePickerLauncher como con GalleryPickerLauncher, proporcionando una API unificada para el procesamiento de imágenes multiplataforma.
La librería proporciona cuatro funciones de extensión principales para la conversión fácil de imágenes:
loadBytes()- Devuelve la imagen como ByteArray para operaciones de archivo y almacenamientoloadPainter()- Devuelve la imagen como Painter para mostrar directamente en UI de ComposeloadImageBitmap()- Devuelve la imagen como ImageBitmap para operaciones gráficas de ComposeloadBase64()- Devuelve la imagen como string Base64 para llamadas API y transmisión de red
- Integración Simplificada: No necesitas lógica compleja de conversión de imágenes
- Soporte de Múltiples Formatos: Obtén imágenes en diferentes formatos con llamadas de función únicas
- Optimizado para Rendimiento: Conversión directa sin pasos de procesamiento intermedios
- Consistencia Multiplataforma: La misma API funciona idénticamente en Android e iOS
- Eficiente en Memoria: Uso optimizado de memoria durante la conversión de imágenes
// Variables de estado
var showCamera by remember { mutableStateOf(false) }
var showGallery by remember { mutableStateOf(false) }
var capturedImage by remember { mutableStateOf<Painter?>(null) }
var selectedPhotos by remember { mutableStateOf<List<GalleryPhotoHandler.PhotoResult>>(emptyList()) }
// Cámara con funciones de extensión
if (showCamera) {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// Usar funciones de extensión para obtener diferentes formatos
val imageBytes = result.loadBytes() // Para operaciones de archivo
val imagePainter = result.loadPainter() // Para mostrar en UI
val imageBitmap = result.loadImageBitmap() // Para operaciones gráficas
val imageBase64 = result.loadBase64() // Para llamadas API
// Operaciones con el sistema de archivos (kotlinx-io)
val absolutePath = result.absolutePath // String - ruta absoluta del archivo
val path = result.asPath() // Objeto Path para operaciones de archivo
val exists = result.exists() // Comprobar si el archivo existe
val rawSource = result.asRawSource() // RawSource para lectura de bajo nivel
val source = result.asSource() // Buffered Source para lectura eficiente
// Copiar la foto a otra ubicación
val sink = SystemFileSystem
.sink(Path("copy.jpg"))
result.transferToSink(sink) // Transferir contenido a RawSink
// Guardar el painter para mostrar después
capturedImage = imagePainter
showCamera = false
},
onError = { showCamera = false },
onDismiss = { showCamera = false },
directCameraLaunch = true
)
)
}
// Galería con funciones de extensión
if (showGallery) {
GalleryPickerLauncher(
onPhotosSelected = { photos ->
selectedPhotos = photos
showGallery = false
},
onError = { showGallery = false },
onDismiss = { showGallery = false },
allowMultiple = true
)
}
// Mostrar imagen capturada
capturedImage?.let { painter ->
Image(
painter = painter,
contentDescription = "Foto capturada",
modifier = Modifier.size(200.dp)
)
}
// Mostrar fotos seleccionadas
LazyColumn {
items(selectedPhotos) { photo ->
photo.loadPainter()?.let { painter ->
Image(
painter = painter,
contentDescription = "Foto seleccionada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
}
}
}Optimiza automáticamente el tamaño de imagen manteniendo la calidad con niveles de compresión configurables.
- BAJA: 95% de calidad, dimensión máxima 2560px - Mejor calidad, archivos más grandes
- MEDIA: 75% de calidad, dimensión máxima 1920px - Calidad/tamaño equilibrado
- ALTA: 50% de calidad, dimensión máxima 1280px - Archivos más pequeños, bueno para almacenamiento
ImagePickerLauncher(
config = ImagePickerConfig(
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.MEDIUM,
skipConfirmation = true
)
)
)Extrae información GPS, detalles de cámara y timestamps de las fotos (Android/iOS).
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
result.exif?.let { exif ->
println("📍 Ubicación: ${exif.latitude}, ${exif.longitude}")
println("📷 Cámara: ${exif.cameraModel}")
println("📅 Fecha: ${exif.dateTaken}")
println("⚙️ ISO: ${exif.iso}, Apertura: f/${exif.aperture}")
}
},
cameraCaptureConfig = CameraCaptureConfig(
includeExif = true // Solo Android/iOS
)
)
)ImagePickerKMP ahora detecta automáticamente qué tipo de selector usar en Android:
| Tipo de Contenido | Comportamiento | Resultado |
|---|---|---|
Solo Imágenes (image/*) |
Abre la galería nativa de Android | ✅ Mejor UX para fotos |
PDFs (application/pdf) |
Abre el explorador de archivos | ✅ Acceso completo a documentos |
| Tipos Mixtos | Abre el explorador de archivos | ✅ Máxima compatibilidad |
// ✅ Abre GALERÍA automáticamente
GalleryPickerLauncher(
onPhotosSelected = { photos -> },
mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG)
)
### Extracción de Datos EXIF
Para habilitar datos EXIF (metadatos de ubicación, cámara, etc.):
```kotlin
GalleryPickerLauncher(
onPhotosSelected = { photos ->
photos.forEach { photo ->
photo.exif?.let { exif ->
println("Ubicación GPS: ${exif.latitude}, ${exif.longitude}")
println("Cámara: ${exif.camera}")
println("Fecha: ${exif.dateTime}")
}
}
},
includeExif = true // ✅ IMPORTANTE: Habilita extracción EXIF
)Nota: Por defecto
includeExif = falsepara optimizar rendimiento. Especificatruesi necesitas metadatos.
Compatibilidad multiplataforma con gestión inteligente de contexto y funcionalidad de recorte mejorada
- Android: La librería gestiona automáticamente el contexto usando
LocalContext.current. No necesitas pasar el contexto manualmente. - iOS: El contexto no es requerido ya que la librería usa APIs nativas de iOS. Los cálculos mejorados de coordenadas de recorte aseguran comportamiento consistente con Android.
- Recorte Multiplataforma: Función
applyCropunificada con gestión automática de contexto y cálculos de coordenadas consistentes entre plataformas.
- ** Gestión Automática de Contexto**: La función
applyCropahora es@Composabley maneja el contexto de Android automáticamente - ** Precisión Mejorada de Recorte iOS**: Cálculos de coordenas corregidos para recorte preciso de imágenes en iOS
- ** Sistema de Diseño Mejorado**: Resueltos conflictos de z-index y problemas de superposición de zoom para mejor experiencia de usuario
- ** Mejor Soporte de Relación de Aspecto**: Manejo mejorado de relaciones de aspecto verticales (como 9:16) con gestión espacial mejorada
| Plataforma | Versión Mínima | Estado |
|---|---|---|
| Android | API 21+ | ✅ |
| iOS | iOS 12.0+ | ✅ |
| Compose Multiplatform | 1.5.0+ | ✅ |
| Desktop (JVM) | JDK 11+ | ✅ |
El selector de imágenes más completo y amigable para desarrolladores en Kotlin Multiplatform
| Característica | ImagePickerKMP |
|---|---|
| Soporte Compose Multiplatform | ✅ Nativo |
| Personalización de UI | ✅ Control completo |
| Permisos Unificados | ✅ Manejo inteligente |
| Manejo de Errores | ✅ Completo |
| Integración de Cámara | ✅ Acceso directo |
| Soporte de Galería | ✅ Selección múltiple |
| API Multiplataforma | ✅ Base de código única |
- ** Nativo de Compose Multiplatform**: Construido específicamente para Compose Multiplatform, asegurando comportamiento consistente entre plataformas
- ** Personalización Completa de UI**: Control total sobre diálogos, vistas de confirmación, y UI de cámara
- ** Gestión Inteligente de Permisos**: Manejo unificado de permisos con respaldos inteligentes
- ** Optimizado para Rendimiento**: Procesamiento eficiente de imágenes y gestión de memoria
- ** Amigable para Desarrolladores**: API simple con manejo completo de errores
- SDK Mínimo: 21
- Kotlin 1.8+
- Compose Multiplatform
- iOS 12.0+
- Xcode 14+
- Kotlin Multiplatform
- JVM JDK 11+
¡Damos la bienvenida a las contribuciones de la comunidad!
Consulta nuestra Guía de Contribución para obtener detalles.
Obtén ayuda, reporta problemas o únete a nuestra comunidad
📧 Email • 🐛 Problemas • 📖 Wiki • 💬 Discord
Hecho con ❤️ para la comunidad Kotlin Multiplatform
¡Dale una estrella ⭐ a este repo si te ayudó!






