MolotovCherry / kmagick

Kotlin ImageMagick bindings to the MagickWand API
MIT License
40 stars 5 forks source link

Is it possible to run kmagic in multiple coroutines? #20

Closed MansonLuo closed 10 months ago

MansonLuo commented 10 months ago

below is my code:

coroutineScope {
    texts.forEachIndexed { index, text ->
        launch {
            withContext(Dispatcher.IO) {
                transformAndSaveToTmpRgb(context, text, index)
            }
        }
    }
}

fun String.transformAndSaveToTmpRgb(
    context: Context,
    rootPath: String,
    nameOfTmpRgb: String
) {
    // generate tmp.rgb file
    Thread {
        context.getMagick().use {
            val wand = MagickWand()
            wand.readImage(this)

            var width = wand.getImageWidth()
            val height = wand.getImageHeight()

            val scaleHeight = 684f / height
            width = ((width / 5f) * scaleHeight).toLong()
            wand.resizeImage(width, 684, FilterType.BartlettFilter)

            val pixel = PixelWand()
            pixel.color = "transparent"
            wand.rotateImage(pixel, 90.00)

            wand.writeImage(rootPath + File.separator + "tmp${nameOfTmpRgb}.rgb")
        }
    }

}

when I run above code, it will raise error, and app crash: --> kmagick::magick_wand: Failed to get handle for MagickWand::writeImage(): Field handle is null

but when I remove launch statement, it will work.

coroutineScope {
    texts.forEachIndexed { index, text ->
        withContext(Dispatcher.IO) {
            transformAndSaveToTmpRgb(context, text, index)
        }
    }
}

it looks very weird.

MolotovCherry commented 10 months ago

I can't say, but as long as your resources are not being closed, it should be fine. ImageMagick is thread-safe as well. Are your resources being closed anywhere? As best as I can tell, this is happening on the kotlin side (and on the native side, accesses are protected by a mutex)

MansonLuo commented 10 months ago

could you please help me check my code on my project? it's very easy. project URL is: https://github.com/MansonLuo/TicketNumberPrintFinnal.

below is a simple explanation if you wish.

this app recognizes texts from image and send these texts to mini portable printer to print them on paper.

The place where I used kmagic is to convert bitmap to temp.rgb file.

Below are several steps and corresponding codes:

  1. user press "take picture" button, it will trigger viewModels.uploadNumbers methods:
    
    # [Main](https://github.com/MansonLuo/TicketNumberPrintFinnal/blob/master/app/src/main/java/com/example/ticketnumberprintfinnal/MainActivity.kt)
    # MainActivity.kt line 100

viewModel.uploadNumbers(context, recognizedNumbers)


below is uploadNumbers method definition:
``` kotlin
//https://github.com/MansonLuo/TicketNumberPrintFinnal/blob/master/app/src/main/java/com/example/ticketnumberprintfinnal/CameraViewModel.kt
at line 198

suspend fun uploadNumbers(
        context: Context,
        numbers: List<String>,
    ) {
        _sendResultList.clear()
        val len = numbers.size

        coroutineScope {
            withContext(Dispatchers.IO) {
                // generate images
                val deffered = numbers.mapIndexed() { index, ticketNumber ->
                    async {
                        ticketNumber.saveGeneratedWhiteJpgTo(rootImgPath, index.toString())
                    }
                }
                val generatedImagePaths = deffered.map {
                    it.await()
                }

                // save tmp.rgb files
                generatedImagePaths.forEachIndexed { index, path ->
                    launch {
                        path.transformAndSaveToTmpRgb(context, rootTmpPath, index.toString())
                    }.join()
                }

                // save to mbd.file
                (0 until len).mapIndexed { index, s ->
                    launch {
                        withContext(Dispatchers.Default) {
                            (context as MainActivity).generateMBDFile(
                                tmpRgbFilePath.replace("#", index.toString()),
                                "$rootMbdPath/${index}.mbd"
                            )
                        }
                    }.join()
                }

                // send mbd files
                (0 until len).map { index ->
                    launch {
                        withContext(Dispatchers.Default) {
                            mbrushRepository.upload(
                                mbdFilePath = "$rootMbdPath/$index.mbd",
                                index
                            )

                            coroutineContext[Job]?.invokeOnCompletion {
                                if (0 == index) {
                                    MediaActionSound().play(MediaActionSound.STOP_VIDEO_RECORDING)
                                }
                                _sendResultList.add("发送结果: OK")
                            }
                        }
                    }
                }.forEach { it.join() }
            }
        }
    }

following method call will use Kmagic from above code:

// save tmp.rgb files
generatedImagePaths.forEachIndexed { index, path ->
    launch {
        path.transformAndSaveToTmpRgb(context, rootTmpPath, index.toString())
     }.join()
}

And its definition:

// https://github.com/MansonLuo/TicketNumberPrintFinnal/blob/master/app/src/main/java/com/example/ticketnumberprintfinnal/extentions/StringExt.kt
// at line 47

fun String.transformAndSaveToTmpRgb(
    context: Context,
    rootPath: String,
    nameOfTmpRgb: String
) {
    // generate tmp.rgb file
    context.getMagick().use {
        val wand = MagickWand()
        wand.readImage(this)

        var width = wand.getImageWidth()
        val height = wand.getImageHeight()

        val scaleHeight = 684f / height
        width = ((width / 5f) * scaleHeight).toLong()
        wand.resizeImage(width, 684, FilterType.BartlettFilter)

        val pixel = PixelWand()
        pixel.color = "transparent"
        wand.rotateImage(pixel, 90.00)

        wand.writeImage(rootPath + File.separator + "tmp${nameOfTmpRgb}.rgb")
    }

}
MansonLuo commented 10 months ago

I'm not sure, each time I use a coroutine to start converting, I' will use context.getMagic.().use { } method to automatecally close magic on end of each coroutine. I'm not sure whether because of this.

MansonLuo commented 10 months ago

Thank you so much for your reply, I finaly found the solution。

Magick.initialize()
generatedImagePaths.mapIndexed { index, path ->
    launch {
        path.transformAndSaveToTmpRgb(context, rootTmpPath, index.toString())
    }
}.forEach { it.join() }
Magick.terminate()

Thank you so much.

MolotovCherry commented 10 months ago

Thank you so much for your reply, I finaly found the solution。

Magick.initialize()
generatedImagePaths.mapIndexed { index, path ->
    launch {
        path.transformAndSaveToTmpRgb(context, rootTmpPath, index.toString())
    }
}.forEach { it.join() }
Magick.terminate()

Thank you so much.

Glad to hear you were able to solve the issue!