Skip to main content

Images

Back on Text, you met Doodle's other drawing primitive with an async loading step. Images play by the same rules — load first, then draw — and they're our last stop in the Rendering section.

Images draw directly to the canvas like any other primitive — transforms and clipping treat them no differently than shapes or text. You load one with ImageLoader

Module Required

Include the ImageModule (Web, Desktop) in your application to use ImageLoader.

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 }

ImageLoader is happy to load from URLs, file paths, or LocalFiles (from drag-and-drop or a FileSelector):

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

Loading can hang — see Handling Timeouts on the Text page for a pattern you can borrow here too.

Once it's loaded, actually drawing it is a single call away. You get to choose which rectangular region of the image gets used (for cropping) and where it lands on the canvas (for scaling) — exactly how you'd zoom, crop, or scale an image as you draw it

Pass a source rectangle, and it crops to that region before drawing; pair that with the image's own ImageLoader and you can build an "aspect-fill" crop that always covers its destination without distorting. Both tricks are the same image call wearing different arguments — see the live demos below for the full worked versions.

package io.nacular.doodle.docs.apps import io.nacular.doodle.application.Application import io.nacular.doodle.core.Display import io.nacular.doodle.core.view import io.nacular.doodle.docs.utils.controlBackgroundColor import io.nacular.doodle.drawing.paint import io.nacular.doodle.image.ImageLoader import io.nacular.doodle.image.aspectRatio import io.nacular.doodle.layout.constraints.constrain import io.nacular.doodle.utils.Resizer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class ImageApp(display: Display, images: ImageLoader, appScope: CoroutineScope): Application { init { appScope.launch { var defaultLayoutPending = true //sampleStart val image = images.load("/doodle/images/photo.jpg") ?: return@launch display += view { render = { image(image, destination = bounds.atOrigin) } Resizer(this) } //sampleEnd display.layout = constrain(display.first()) { if (defaultLayoutPending && !parent.size.value.empty) { defaultLayoutPending = false it.width eq it.height * image.aspectRatio it.height eq parent.height - 80 it.center eq parent.center } else { it.top prefers it.top.value it.left prefers it.left.value it.width prefers it.width.value it.height prefers it.height.value } } display.fill(controlBackgroundColor.paint) } } override fun shutdown() {} }
tip

image also accepts a corner radius and opacity.

Recap​

  • ImageLoader needs ImageModule (Web, Desktop) — load an image before you can draw it.
  • image draws a loaded Image, with source/destination controlling cropping and scaling.
  • Loading can hang, same as font loading — reuse the timeout pattern from the Text page.

That's a wrap on Rendering — Canvas, Paint & Stroke, Shapes & Paths, Text, and Images, all behind you. Next: on to Layout to see how Views get sized and positioned in the first place, before they ever reach render.