Is there any solution to generate images on the se...
# compose
n
Is there any solution to generate images on the server-side using Compose Multiplatform? Like rendering on a canvas and exporting it to an image. Thanks
c
This is possible on Android. I guess it is possible to adopt this method to server-side.
👍🏻 1
m
This works on Compose desktop too, which is a bit closer to a server than Android. So, if your server is supporting a full Java JDK with AWT support, then this should work.
👍🏻 1
c
@Michael Paus i do not think the referenced code snippet works as is on Desktop. It uses the
android.graphics.Picture
class.
m
Of course not. I was not referring to your Android link. But with Compose desktop you can create a Canvas based on an ImageBitmap and draw into it. Then you can save that image via Java ImageIO.
👍 1
Here is the code:
Copy code
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import java.io.File
import javax.imageio.ImageIO
import kotlin.test.Test
import kotlin.test.assertTrue

class ImageCanvasTest {

    @Test
    fun test() {
        val image = ImageBitmap(400, 400)
        val canvas = Canvas(image)
        canvas.drawLine(Offset.Zero, Offset(image.width.toFloat(), image.height.toFloat()), Paint().apply { color = Color.Red })
        val file = File("canvasimage.png")
        if (file.exists()) file.delete()
        ImageIO.write(image.toAwtImage(), "png", file)
        assertTrue(file.exists())
    }

}
🙏 1
m
Makes me wonder, could you run headless Compose UI if you wanted to only use it for image rendering? I’m imagining for example using
Box { Image(); Text("Watermark")... }
to use a Compose layout to add a watermark to an image (not that it’s a great use case, but a simple example). Not using Canvas, just composable layouts that would then be rendered to an image, instead of to a screen.
👍 2
m
Hey, running Compose on server to generate images is certainly interesting use-case. Has anyone actually had any success with this? I’m interested in trying this myself, but not sure where to start, considering there is not much information around this particular use-case. I’m basically looking to build a ktor backend service that exposes an endpoint, which renders and returns an image created using Compose. I guess this could potentially be implemented using some Docker image with Java AWT support? (am I thinking about this correctly?)
m
What is a practical use case for not just using the canvas?
m
I have an e-ink display that periodically fetches data from my backend and renders them using static layout I had programmed into the MCU. I thought it would be cool to render entire image on backend using Compose and have e-ink just display the fetched image. This way I could very easily customize what gets displayed without pushing a firmware update.
s
It should (in theory) be possible to do, but I'm not sure if Compose plays well with AWT's headless mode.
As for actually capturing the image, Compose Desktop has tue
ImageCaptureScene
API to do just that (it doesn't create a window or call any code that would raise an AWT
HeadlessException
, so it's perfect for this usecase).
I just tried it out and it works flawlessly in headless mode!
Copy code
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier

fun main() {
    System.setProperty("java.awt.headless", "true")
    val scene = ImageComposeScene(
        width = 1000,
        height = 1000
    ) {
        MaterialTheme {
            Surface(color = MaterialTheme.colors.background) {
                Box(
                    contentAlignment = Alignment.Center,
                    modifier = Modifier.fillMaxSize()
                ) {
                    Text("Hello, World!")
                }
            }
        }
    }
    val image = scene.render(0)
    // do something with the image;
    //  e.g. send back as a response through Ktor
}
🙌🏻 1
Aside from generating Images, you can use
ImageComposeScene
to generate video as well (e.g from an animated Composable) by moving time forward and rendering multiple images.
👍🏻 1
m
Neat, this could be useful
m
Whoa, I did not know about this API or AWT headless mode. This looks promising, cannot wait to try out!
s
Just a few minutes ago I learnt that there's yet another equivalent API (unlike this one, it's multiplatform!) 😛 Not sure if this one works on headless mode, I'll give it a shot and see. https://kotlinlang.slack.com/archives/C01F2HV7868/p1722273132883949?thread_ts=1722272483.625119&cid=C01F2HV7868
😮 1
Seems to work as well, but
ImageComposeScene
is much simpler to use. (
SingleLayerComposeScene
is an internal API as well).
👍 1
👍🏻 1
🙏 1
m
I managed to build a ktor backend service to generate images using the ImageComposeScene API. I also somehow managed to dockerize the application 🤯. Sample here: https://github.com/matejsemancik/ktor-compose.
❤️ 1
K 2
😮 1
s
What is ImageComposeScene? 👀👀 I get no hits here https://cs.android.com/search?q=ImageComposeScene&sq=
m
s
Yeah, the same seems to apply to
SingleLayerComposeScene
(or
MultiLayerComposeScene
), they're only on skikoMain. So Android is excluded from this unfortunately. One of its biggest benefits is the ability to control time, and I'm not sure if there's a way to replicate that using the classic
GraphicsLayer.toBitmap()
solution on Android or some other way.
@matsem Looking at your code, you probably don't need
System.setProperty("java.awt.headless", "true")
, since
ImageComposeScene
skips AWT completely and renders the image through Skia and returns the rendered result. It's apparently only useful if you're creating an actual AWT window (that includes Compose's
Window
and maybe
application
too), since it causes AWT to not try contacting the OS to get a display/keyboard/mouse device when it's initialized, and causes all functionalities that rely on them to throw
HeadlessException
if they're used. I'm not sure Compose's
Window
supports this mode at all, but it doesn't matter when using
ImageComposeScene
AFAIK. More details about headless mode: https://www.oracle.com/technical-resources/articles/javase/headless.html
🙏 1
m
Yes, that makes sense, thanks
s
Honestly this is such a fun use case, I never would've thought this should be possible 😅
267 Views