-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from Kashif-E/jvm-support
Jvm support
- Loading branch information
Showing
19 changed files
with
819 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
...averPlugin/src/desktopMain/kotlin/com/kashif/imagesaverplugin/ImageSaverPlugin.desktop.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.kashif.imagesaverplugin | ||
|
||
import coil3.PlatformContext | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import java.io.ByteArrayInputStream | ||
import java.io.File | ||
import java.io.FileOutputStream | ||
import java.io.IOException | ||
import javax.imageio.ImageIO | ||
|
||
/** | ||
* jvm-specific implementation of [ImageSaverPlugin]. | ||
* | ||
* @param config The configuration settings for the plugin. | ||
* @param onImageSaved Callback invoked when the image is successfully saved. | ||
* @param onImageSavedFailed Callback invoked when the image saving fails. | ||
*/ | ||
class JVMImageSaverPlugin( | ||
config: ImageSaverConfig, | ||
private val onImageSaved: () -> Unit, | ||
private val onImageSavedFailed: (String) -> Unit | ||
) : ImageSaverPlugin(config) { | ||
|
||
override suspend fun saveImage(byteArray: ByteArray, imageName: String?): String? { | ||
return withContext(Dispatchers.IO) { | ||
try { | ||
val image = ImageIO.read(ByteArrayInputStream(byteArray)) | ||
val fileName = "${imageName ?: "image_${System.currentTimeMillis()}"}.jpg" | ||
val outputDirectory = File(config.directory.name) | ||
val outputFile = File(outputDirectory, fileName) | ||
val outputStream = FileOutputStream(outputFile) | ||
ImageIO.write(image, "jpg", outputStream) | ||
outputStream.close() | ||
outputFile.absolutePath | ||
} catch (e: Exception) { | ||
null | ||
} | ||
} | ||
} | ||
|
||
override fun getByteArrayFrom(path: String): ByteArray { | ||
return try { | ||
File(path).readBytes() | ||
} catch (e: Exception) { | ||
throw IOException("Failed to read image from path: $path", e) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Factory function to create an jvm-specific [ImageSaverPlugin]. | ||
* | ||
* @param config Configuration settings for the plugin. | ||
* @return An instance of [JVMImageSaverPlugin]. | ||
*/ | ||
|
||
actual fun createPlatformImageSaverPlugin( | ||
context: PlatformContext, | ||
config: ImageSaverConfig | ||
): ImageSaverPlugin { | ||
return JVMImageSaverPlugin( | ||
config = config, | ||
onImageSaved = { println("Image saved successfully!") }, | ||
onImageSavedFailed = { errorMessage -> println("Failed to save image: $errorMessage") } | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.window.Window | ||
import androidx.compose.ui.window.application | ||
import androidx.compose.ui.window.rememberWindowState | ||
import org.company.app.App | ||
|
||
fun main()= application { | ||
System.setProperty("compose.interop.blending", "true") | ||
System.setProperty("compose.swing.render.on.layer", "true") | ||
System.setProperty("compose.interop.blending", "true") | ||
|
||
Window( | ||
title = "CameraK", | ||
state = rememberWindowState(width = 1440.dp, height = 1024.dp), | ||
onCloseRequest = ::exitApplication, | ||
) { | ||
App() | ||
} | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
Sample/src/desktopMain/kotlin/org/company/app/theme/Theme.desktop.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.company.app.theme | ||
|
||
import androidx.compose.runtime.Composable | ||
|
||
@Composable | ||
internal actual fun SystemAppearance(isDark: Boolean) { | ||
//not needed | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
cameraK/src/desktopMain/kotlin/com/kashif/cameraK/controller/CameraGrabber.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package com.kashif.cameraK.controller | ||
|
||
|
||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.isActive | ||
import kotlinx.coroutines.launch | ||
import org.bytedeco.javacv.FFmpegFrameGrabber | ||
import java.awt.image.BufferedImage | ||
|
||
|
||
class CameraGrabber( | ||
private val frameChannel: Channel<BufferedImage>, | ||
private val errorHandler: (Throwable) -> Unit | ||
) { | ||
private val converter = FrameConverter() | ||
private var grabber: FFmpegFrameGrabber? = null | ||
private var job: Job? = null | ||
|
||
fun grabCurrentFrame(): BufferedImage? { | ||
val frame = grabber?.grab() | ||
return if (frame?.image != null) { | ||
converter.convert(frame) | ||
} else { | ||
null | ||
} | ||
} | ||
fun start(coroutineScope: CoroutineScope) { | ||
if (job?.isActive == true) return | ||
|
||
grabber = createGrabber().apply { | ||
try { | ||
start() | ||
} catch (e: Exception) { | ||
errorHandler(e) | ||
return | ||
} | ||
} | ||
|
||
job = coroutineScope.launch(Dispatchers.IO) { | ||
var frameCount = 0 | ||
var lastFpsTime = System.currentTimeMillis() | ||
|
||
try { | ||
while (isActive) { | ||
val frame = grabber?.grab() | ||
if (frame?.image != null) { | ||
converter.convert(frame)?.let { image -> | ||
frameChannel.trySend(image) | ||
|
||
frameCount++ | ||
val currentTime = System.currentTimeMillis() | ||
if (currentTime - lastFpsTime >= 1000) { | ||
println("Camera FPS: $frameCount") | ||
frameCount = 0 | ||
lastFpsTime = currentTime | ||
} | ||
} | ||
} | ||
|
||
// Minimal delay to prevent CPU overload | ||
delay(1) | ||
} | ||
} catch (e: Exception) { | ||
errorHandler(e) | ||
} | ||
} | ||
} | ||
|
||
fun stop() { | ||
job?.cancel() | ||
try { | ||
grabber?.stop() | ||
grabber?.release() | ||
converter.release() | ||
} catch (e: Exception) { | ||
errorHandler(e) | ||
} | ||
} | ||
|
||
private fun createGrabber() = when { | ||
System.getProperty("os.name").lowercase().contains("mac") -> { | ||
FFmpegFrameGrabber("default").apply { | ||
format = "avfoundation" | ||
} | ||
} | ||
System.getProperty("os.name").lowercase().contains("windows") -> { | ||
FFmpegFrameGrabber("video=0").apply { | ||
format = "dshow" | ||
} | ||
} | ||
else -> { | ||
FFmpegFrameGrabber("/dev/video0").apply { | ||
format = "v4l2" | ||
} | ||
} | ||
}.apply { | ||
frameRate = 30.0 | ||
imageWidth = 640 | ||
imageHeight = 480 | ||
} | ||
} |
Oops, something went wrong.