Skip to content

Commit 1acb8b2

Browse files
committed
Update CameraSession+Photo.kt for custom image saving
1 parent 8962778 commit 1acb8b2

1 file changed

Lines changed: 103 additions & 31 deletions

File tree

package/android/src/main/java/com/mrousavy/camera/core/CameraSession+Photo.kt

Lines changed: 103 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,130 @@
11
package com.mrousavy.camera.core
22

33
import android.media.AudioManager
4-
import android.util.Log
54
import com.mrousavy.camera.core.extensions.takePicture
65
import com.mrousavy.camera.core.types.Flash
76
import com.mrousavy.camera.core.types.Orientation
87
import com.mrousavy.camera.core.types.TakePhotoOptions
98
import com.mrousavy.camera.core.utils.FileUtils
109

10+
import androidx.camera.core.ImageCapture.OnImageCapturedCallback
11+
import androidx.camera.core.ImageCaptureException
12+
import androidx.camera.core.ImageProxy
13+
import android.os.Looper
14+
import android.util.Log
15+
import android.provider.MediaStore
16+
import android.content.ContentValues
17+
import java.io.File
18+
import android.os.Environment
19+
import java.io.FileOutputStream
20+
import androidx.core.content.ContextCompat
21+
import androidx.camera.core.ImageCapture.Metadata
22+
import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
23+
import android.media.MediaActionSound
24+
import androidx.exifinterface.media.ExifInterface
25+
26+
fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
27+
fun ensureBackgroundThread(callback: () -> Unit) {
28+
if (isOnMainThread()) {
29+
Thread {
30+
callback()
31+
}.start()
32+
} else {
33+
callback()
34+
}
35+
}
36+
37+
val TAG = "CameraSession+Photo"
38+
1139
suspend fun CameraSession.takePhoto(options: TakePhotoOptions): Photo {
1240
val camera = camera ?: throw CameraNotReadyError()
1341
val configuration = configuration ?: throw CameraNotReadyError()
1442
val photoConfig = configuration.photo as? CameraConfiguration.Output.Enabled<CameraConfiguration.Photo> ?: throw PhotoNotEnabledError()
1543
val photoOutput = photoOutput ?: throw PhotoNotEnabledError()
1644

17-
// Log.i(CameraSession.TAG, "LP3 is zsl supported ${camera.cameraInfo.isZslSupported()}")
18-
// This returns true
19-
20-
/*
21-
LP3: We can't have two PhotoOutput use cases bound, so this doesn't work
22-
We also can't dynamically update the modes of the photoOutput using camerax
23-
Currently, the added useFastMode option does nothing
24-
if (options.useFastMode) {
25-
Log.i(CameraSession.TAG,"LP3 Using fast photo mode")
26-
photoOutput = photoOutputLockedFocus
27-
}
28-
*/
29-
30-
// Flash
3145
if (options.flash != Flash.OFF && !camera.cameraInfo.hasFlashUnit()) {
3246
throw FlashUnavailableError()
3347
}
3448
photoOutput.flashMode = options.flash.toFlashMode()
35-
// Shutter sound
49+
3650
val enableShutterSound = options.enableShutterSound && !audioManager.isSilent
37-
// isMirrored (EXIF)
51+
val shutterSound = if (enableShutterSound) MediaActionSound() else null
52+
shutterSound?.load(MediaActionSound.SHUTTER_CLICK)
53+
3854
val isMirrored = photoConfig.config.isMirrored
55+
val metadata = Metadata().apply {
56+
isReversedHorizontal = isMirrored
57+
}
3958

40-
// Shoot photo!
41-
val photoFile = photoOutput.takePicture(
42-
options.file.file,
43-
isMirrored,
44-
enableShutterSound,
45-
metadataProvider,
46-
callback,
47-
CameraQueues.cameraExecutor
48-
)
59+
var capturedAt = System.currentTimeMillis();
60+
val filename = "img_${capturedAt}.jpg"
61+
photoOutput.takePicture(CameraQueues.cameraExecutor, object : OnImageCapturedCallback() {
62+
override fun onCaptureSuccess(image: ImageProxy) {
63+
ensureBackgroundThread {
64+
if (enableShutterSound) {
65+
shutterSound?.play(MediaActionSound.SHUTTER_CLICK)
66+
}
67+
68+
val directory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Light")
69+
if (!directory.exists()) {
70+
directory.mkdirs()
71+
}
72+
73+
// Create file with timestamp
74+
val file = File(directory, filename)
4975

50-
// Parse resulting photo (EXIF data)
51-
val size = FileUtils.getImageSize(photoFile.uri.path)
52-
val rotation = photoOutput.targetRotation
53-
val orientation = Orientation.fromSurfaceRotation(rotation)
76+
try {
77+
val buffer = image.planes[0].buffer
78+
val bytes = ByteArray(buffer.remaining()).apply {
79+
buffer.get(this)
80+
}
81+
FileOutputStream(file).use { output ->
82+
output.write(bytes)
83+
}
5484

55-
return Photo(photoFile.uri.path, size.width, size.height, orientation, isMirrored)
85+
// Add the image to MediaStore so it appears in the gallery
86+
val values = ContentValues().apply {
87+
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
88+
put(MediaStore.Images.Media.DATE_ADDED, capturedAt / 1000)
89+
put(MediaStore.Images.Media.DATE_TAKEN, capturedAt)
90+
put(MediaStore.Images.Media.DATA, file.absolutePath)
91+
}
92+
93+
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
94+
Log.i(TAG, "Image saved successfully to: ${file.absolutePath}, height: ${image.height}, width: ${image.width}, format: ${image.format}")
95+
96+
val exif = ExifInterface(file.absolutePath)
97+
// Overwrite the original orientation if the quirk exists.
98+
if (!ExifRotationAvailability().shouldUseExifOrientation(image)) {
99+
exif.rotate(image.imageInfo.rotationDegrees)
100+
}
101+
if (metadata.isReversedHorizontal) {
102+
exif.flipHorizontally()
103+
}
104+
if (metadata.isReversedVertical) {
105+
exif.flipVertically()
106+
}
107+
exif.saveAttributes();
108+
Log.i(TAG, "EXIF data saved")
109+
} catch (e: Exception) {
110+
Log.e(TAG, "Error saving image: ${e.message}")
111+
e.printStackTrace()
112+
}
113+
}
114+
}
115+
116+
override fun onError(exception: ImageCaptureException) {
117+
Log.d(TAG, "onError: ${exception.message}")
118+
}
119+
})
120+
121+
return Photo(
122+
"/storage/emulated/0/Pictures/Light/${filename}",
123+
0,
124+
0,
125+
Orientation.fromSurfaceRotation(photoOutput.targetRotation),
126+
isMirrored
127+
)
56128
}
57129

58130
private val AudioManager.isSilent: Boolean

0 commit comments

Comments
 (0)