Skip to main content

Images

Images are rendered directly to the Canvas as primitives just like text and shapes. This means transformations and other rendering capabilities apply to images as well. You load Images into your app using the ImageLoader. This interface provides an async API for fetching images from different sources.

Module Required

You must include the ImageModule in your application in order to use these features.

package rendering import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.ImageModule import io.nacular.doodle.application.application import io.nacular.doodle.core.Display import io.nacular.doodle.image.ImageLoader import org.kodein.di.instance class ImageLoaderApp(display: Display, images: ImageLoader): Application { override fun shutdown() {} } fun imageLoader() { //sampleStart application(modules = listOf(ImageModule)) { ImageLoaderApp(display = instance(), images = instance()) } //sampleEnd }

Doodle uses opt-in modules like this to improve bundle size.

Loading from resource

ImageLoader provides APIs for loading images from various sources, like urls, file-paths, and LocalFiles that are obtained during drag-drop or via a FileSelector.

The following examples shows how loading works. Notice that ImageLoader.load returns Image?, which is null when the image fails to load for some reason. Fetching is also async, so it must be done from a suspend method or a CoroutineScope.

package rendering import io.nacular.doodle.image.Image import io.nacular.doodle.image.ImageLoader import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch fun loadImage(imageLoader: ImageLoader, scope: CoroutineScope) { //sampleStart scope.launch { val image: Image? = imageLoader.load("some_image_path") // won't get here until load resolves image?.let { // ... } } //sampleEnd }
tip

See here for an example of how you might handle time-outs.

Rendering

Images are treated like primitive elements of the rendering pipeline. They are rendered directly to a Canvas like other shapes and text.

You are able to define the rectangular region within an image that will be put onto the Canvas, as well as where on the Canvas that region will be placed. These two values allow you to zoom and scale images as you draw them.

package io.nacular.doodle.docs.apps import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.ImageModule import io.nacular.doodle.application.Modules.Companion.PointerModule import io.nacular.doodle.application.application import io.nacular.doodle.core.Display import io.nacular.doodle.core.center import io.nacular.doodle.core.height import io.nacular.doodle.core.view import io.nacular.doodle.core.width import io.nacular.doodle.docs.utils.controlBackgroundColor import io.nacular.doodle.drawing.paint import io.nacular.doodle.geometry.Rectangle import io.nacular.doodle.geometry.Size import io.nacular.doodle.geometry.centered import io.nacular.doodle.image.ImageLoader import io.nacular.doodle.utils.Resizer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.kodein.di.instance import org.w3c.dom.HTMLElement import kotlin.math.min class ImageApp( display : Display, imageLoader: ImageLoader, appScope : CoroutineScope ): Application { init { appScope.launch { //sampleStart val image = imageLoader.load("/doodle/images/photo.jpg") ?: return@launch display.children += view { render = { image(image, destination = bounds.atOrigin) } Resizer(this) } //sampleEnd display.fill(controlBackgroundColor.paint) when { display.size.empty -> display.sizeChanged += { _,_,_ -> setInitialBounds(display, image.size) } else -> setInitialBounds(display, image.size) } } } private fun setInitialBounds(display: Display, imageSize: Size) { with(display.first()) { if (size.empty && !display.size.empty) { var w = min(display.width / 2, display.height - 40) var h = w * imageSize.height / imageSize.width if (h > display.height - 40) { h = display.height - 40 w = h * imageSize.width / imageSize.height } display.first().bounds = Rectangle(w, h).centered(display.center) } } } override fun shutdown() {} companion object { private val appModules = listOf(PointerModule, ImageModule) operator fun invoke(root: HTMLElement) = application(root, modules = appModules) { ImageApp(instance(), instance(), CoroutineScope(SupervisorJob() + Dispatchers.Default)) } } }
tip

You can also control the corner radius, and opacity of the image being drawn.