Bitmaps in SVG

The SVG backend provides support for bitmap images through <image> elements. Unlike raster-based backends, SVG doesn't load actual pixel data. Instead, it creates image references that the browser loads and renders.

How SVG Handles Bitmaps

SVG handles bitmaps differently from other backends:

  • No pixel manipulation: SVG creates <image> elements with href attributes pointing to image sources.
  • Browser-handled loading: the browser handles fetching images and respecting CORS policies.
  • Vector context: bitmaps are embedded in a vector graphics context and can have SVG transformations and filters applied.

Loading Bitmap References

In the SVG backend, "loading" a bitmap creates an SvgImageRef which stores the image URL and optional dimensions. The specifier type is String (representing a URL or data URI).

// Load from URL
val imageRef = "https://example.com/image.png".loadBitmap[SvgImageRef]

// Load from data URI
val dataUri = "data:image/png;base64,iVBORw0KGgoAAAANS..."
val dataRef = dataUri.loadBitmap[SvgImageRef]

// Load from relative path
val localRef = "images/logo.svg".loadBitmap[SvgImageRef]

Note* that loadBitmap returns an IO[SvgImageRef]. Use .unsafeRunSync() to extract the value in examples, or handle the IO properly in production code.

Specifying Dimensions

You can optionally specify dimensions when creating image references:

// With both width and height
val withDims = SvgLoadBitmap.withDimensions("image.jpg", width = 200, height = 150)

// With width only (height auto-calculated by browser)
val withWidth = SvgLoadBitmap.withWidth("image.png", width = 300)

// With height only (width auto-calculated by browser)
val withHeight = SvgLoadBitmap.withHeight("image.gif", height = 250)

If dimensions are not specified, the SVG backend uses a default size of 100x100 pixels for bounding box calculations.

Converting to Pictures

An SvgImageRef can be converted to a Picture using the toPicture method:

val imageRefIO = "photo.jpg".loadBitmap[SvgImageRef]
val imageRef = imageRefIO.unsafeRunSync()
val picture = imageRef.toPicture

// Or load and convert in one step
val directPictureIO = "photo.jpg".loadBitmap[SvgImageRef].toPicture
val directPicture = directPictureIO.unsafeRunSync()

Composing with Vector Graphics

Since bitmap references become regular Pictures, they can be composed with vector graphics:

val logoIO = for {
  imageRef <- "logo.png".loadBitmap[SvgImageRef]
  logo = imageRef.toPicture
} yield logo
  .on(circle(150).fillColor(Color.lightBlue))
  .beside(text("Welcome").fillColor(Color.black))

val composition = logoIO.unsafeRunSync()

SVG Filters on Bitmaps

SVG filters can be applied to bitmap images just like any other Picture:

val filteredIO = for {
  imageRef <- "photo.jpg".loadBitmap[SvgImageRef]
  image = imageRef.toPicture
} yield image
  .blur(3.0)
  .dropShadow(4, 4, 2, Color.black.alpha(0.5.normalized))

val filtered = filteredIO.unsafeRunSync()

Complete Example

This example demonstrates loading an image, applying transformations, and composing with vector graphics:

val program = for {
  // Load the image reference
  imageRef <- "https://example.com/logo.png".loadBitmap[SvgImageRef]

  // Convert to Picture
  logo = imageRef.toPicture

  // Create composition
  composition = logo
    .scale(0.75, 0.75)
    .on(square(200).fillColor(Color.lightGray))
    .above(text("Company Name").fillColor(Color.darkGray))

} yield composition

// In production, handle IO properly
// For examples, we can use unsafeRunSync
val result = program.unsafeRunSync()

Important Considerations

CORS Policies

When loading images from external domains, be aware of CORS (Cross-Origin Resource Sharing) policies.

No Pixel Access

Unlike Java2D or Canvas backends, SVG cannot:

  • Access individual pixels.
  • Convert Pictures to bitmap data.
  • Apply pixel-level manipulations.

For these operations, use a raster-based backend.

Copyright © Noel Welsh. Built with 💖