creativescala / doodle

Compositional vector graphics in Scala / Scala.JS
https://creativescala.org/doodle/
Apache License 2.0
327 stars 75 forks source link

Immediate Mode in Doodle Canvas #168

Open VOSID8 opened 2 months ago

VOSID8 commented 2 months ago

Theory

Retained Mode: The Doodle library supports retained mode, operating on a declarative principle. In this mode, the application constructs images using graphics primitives like shapes and lines, which are then stored as a model in memory. Users interact with this model to add, remove, or modify elements, while the library handles the redrawing. Retained mode maintains a hierarchy where each element’s position is relative to others. In Doodle, relationships between elements are managed through the Shape algebra, allowing operations like on, beside, and similar commands to organize elements.

The Need for Immediate Mode: While retained mode provides a robust solution for constructing and managing complex images, some tasks require more flexibility and direct control. Retained mode can be restrictive for dynamic image manipulation, generative art, and real-time graphics, where the scene model may become cumbersome or resource-intensive. To address these needs, immediate mode has been introduced in Doodle Canvas, enabling developers to bypass the stored scene model and directly issue drawing commands.

Immediate Mode: Doodle now includes an immediate mode, offering a procedural approach to graphics rendering. In immediate mode, the application directly issues image manipulation commands for each frame or operation, without retaining a model in memory. This approach is particularly effective for tasks requiring real-time rendering, interactive graphics, or dynamic transformations. The Immediate object serves as the core interface for this mode, providing methods for drawing and manipulating images directly, giving developers more control and flexibility in image processing.

Integrating Immediate Mode with Retained Mode: With the introduction of immediate mode, Doodle now combines the strengths of both paradigms. A new algebra Raster allows users to embed immediate mode drawing within the retained mode structure. This feature lets users create a Picture that can be positioned relative to other Pictures using operations like above or on. The contents of this Picture are generated either through Retained mode or dynamically through a user-defined function that has access to a Immediate. Drawing operations within this function are relative to the origin of the Picture, offering procedural flexibility. The resulting Picture can then be integrated into the retained mode hierarchy, enabling seamless interaction between both modes. This hybrid approach allows for the dynamic rendering and fine control of immediate mode while maintaining the organizational clarity of retained mode.

Implementation

Existing Retained Mode

val retained =
  Picture.circle(10).at(100, 100)
    .on(Picture.circle(10).at(0, 0))

We can rewrite the above with Immediate Mode as

val immediate =
    (gc: Immediate) => {
      gc.circle(100, 100, 10)
      gc.circle(0, 0, 10)

Both will return the same output as image

Raster Algebra Implemented

trait Raster[A] extends Algebra {
  def raster(width: Int, height: Int)(f: A => Unit): Drawing[Unit]
}

User can use Raster as following to combine Immediate with Retained Mode

val twoCircles =
  (gc: Immediate) => {
      gc.circle(100, 100, 10)
      gc.fill(Color.blue)
      gc.circle(0, 0, 10)
      gc.fill(Color.pink)
  }

val redSquare = Picture.square(80).fillColor(Color.red)

val masterpiece = raster(100, 100)(twoCircles).beside(redSquare)

Which would give output as

image

Immediate object implements 39 methods covering a wide range of drawing functionalities. These include creating shapes (like circles, rectangles, polygons), path operations (like arc, line, and curves), transformations, clipping, coloring and text rendering.

Example

image

The following image is create using Immediate mode along with Retained Mode. Retained Mode is used to draw trees and put everything together in a compositional manner whereas buildings and the road is drawn using Doodle Canvas Immediate Mode. The code for this picture can be found within CanvasImmediateMode.scala file within this PR

Note: This PR is part of Google Summer of Code'24 (Doodle Canvas)