All Downloads are FREE. Search and download functionalities are using the official Maven repository.

doodle.image.Image.scala Maven / Gradle / Ivy

There is a newer version: 0.27.0
Show newest version
/*
 * Copyright 2015 Creative Scala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package doodle
package image

import doodle.core.*
import doodle.core.font.{Font as CoreFont}
import doodle.language.Basic

sealed abstract class Image extends Product with Serializable {
  import Image.Elements.*

  // Layout ----------------------------------------------------------

  def beside(right: Image): Image =
    Beside(this, right)

  def on(bottom: Image): Image =
    On(this, bottom)

  def under(top: Image): Image =
    On(top, this)

  def above(bottom: Image): Image =
    Above(this, bottom)

  def below(top: Image): Image =
    Above(top, this)

  def margin(
      top: Double,
      right: Double,
      bottom: Double,
      left: Double
  ): Image =
    Margin(this, top, right, bottom, left)

  def margin(width: Double, height: Double): Image =
    Margin(this, height, width, height, width)

  def margin(width: Double): Image =
    Margin(this, width, width, width, width)

  def size(width: Double, height: Double): Image =
    Size(this, width, height)

  // Context Transform ------------------------------------------------

  def strokeColor(color: Color): Image =
    StrokeColor(this, color)

  // def strokeColorTransform(f: Color => Color): Image =
  //   ContextTransform(_.strokeColorTransform(f), this)

  def strokeWidth(width: Double): Image =
    StrokeWidth(this, width)

  def strokeCap[A](strokeCap: Cap): Image =
    StrokeCap(this, strokeCap)

  def strokeJoin[A](strokeJoin: Join): Image =
    StrokeJoin(this, strokeJoin)

  /** Specify the stroke dash pattern. The pattern gives the length, in local
    * coordinates, of opaque and transparent sections. The first element is the
    * length of an opaque section, the second of a transparent section, and so
    * on.
    */
  def strokeDash[A](pattern: Iterable[Double]): Image =
    StrokeDash(this, pattern)

  def fillColor(color: Color): Image =
    FillColor(this, color)

  def fillGradient(gradient: Gradient): Image =
    FillGradient(this, gradient)

  // def fillColorTransform(f: Color => Color): Image =
  //   ContextTransform(_.fillColorTransform(f), this)

  // def fillGradient(gradient: Gradient): Image =
  //   ContextTransform(_.fillGradient(gradient), this)

  // def fillGradientTransform(f: Gradient => Gradient): Image =
  //   ContextTransform(_.fillGradientTransform(f), this)

  def noStroke: Image =
    NoStroke(this)

  def noFill: Image =
    NoFill(this)

  def font(font: CoreFont): Image =
    Font(this, font)

  // Affine Transform -------------------------------------------------

  def transform(tx: core.Transform): Image =
    Transform(tx, this)

  def rotate(angle: Angle): Image =
    this.transform(core.Transform.rotate(angle))

  def scale(x: Double, y: Double): Image =
    this.transform(core.Transform.scale(x, y))

  def at(landmark: Landmark): Image =
    At(this, landmark)

  def at(vec: Vec): Image =
    // Transform(core.transform.Transform.translate(vec.x, vec.y), this)
    at(vec.x, vec.y)

  def at(pt: Point): Image =
    at(pt.x, pt.y)

  def at(x: Double, y: Double): Image =
    // Transform(core.transform.Transform.translate(x, y), this)
    at(Landmark.point(x, y))

  def at(r: Double, a: Angle): Image = {
    val pt = Point(r, a)
    at(pt.x, pt.y)
  }

  def originAt(landmark: Landmark): Image =
    OriginAt(this, landmark)

  def originAt(vec: Vec): Image =
    originAt(vec.x, vec.y)

  def originAt(pt: Point): Image =
    originAt(pt.x, pt.y)

  def originAt(x: Double, y: Double): Image =
    originAt(Landmark.point(x, y))

  def originAt(r: Double, a: Angle): Image = {
    val pt = Point(r, a)
    originAt(pt.x, pt.y)
  }

  // Debug ------------------------------------------------------------

  def debug(color: Color): Image =
    Debug(this, color)

  def debug: Image =
    Debug(this, Color.crimson)

  // Convert to tagless final format

  def compile[Algebra <: Basic]: doodle.algebra.Picture[Algebra, Unit] =
    Image.compile(this)
}
object Image {

  /** Contains the leaves of the Image algebraic data type. Packaged here so
    * they don't pollute the namespace when importing Image to access to the
    * smart constructors.
    */
  object Elements {
    final case class OpenPath(path: doodle.core.OpenPath) extends Image
    final case class ClosedPath(path: doodle.core.ClosedPath) extends Image
    final case class Text(get: String) extends Image
    final case class Circle(d: Double) extends Image
    final case class Rectangle(w: Double, h: Double) extends Image
    final case class Triangle(w: Double, h: Double) extends Image
    // final case class Draw(w: Double, h: Double, f: Canvas => Unit) extends Image
    final case class Beside(l: Image, r: Image) extends Image
    final case class Above(l: Image, r: Image) extends Image
    final case class On(t: Image, b: Image) extends Image
    final case class At(image: Image, landmark: Landmark) extends Image
    final case class Transform(tx: core.Transform, i: Image) extends Image
    final case class Margin(
        i: Image,
        top: Double,
        right: Double,
        bottom: Double,
        left: Double
    ) extends Image
    final case class Size(image: Image, width: Double, height: Double)
        extends Image
    final case class OriginAt(image: Image, landmark: Landmark) extends Image
    // Style
    final case class StrokeWidth(image: Image, width: Double) extends Image
    final case class StrokeColor(image: Image, color: Color) extends Image
    final case class StrokeCap(image: Image, cap: Cap) extends Image
    final case class StrokeJoin(image: Image, join: Join) extends Image
    final case class StrokeDash(image: Image, pattern: Iterable[Double])
        extends Image

    final case class FillColor(image: Image, color: Color) extends Image
    final case class FillGradient(image: Image, gradient: Gradient)
        extends Image
    final case class NoStroke(image: Image) extends Image
    final case class NoFill(image: Image) extends Image
    final case class Font(image: Image, font: CoreFont) extends Image
    // Debug
    final case class Debug(image: Image, color: Color) extends Image

    case object Empty extends Image
  }
  import Elements.*

  // Smart constructors

  def path(path: doodle.core.ClosedPath): Image =
    ClosedPath(path)

  def path(path: doodle.core.OpenPath): Image =
    OpenPath(path)

  def text(characters: String): Image =
    Text(characters)

  def line(x: Double, y: Double): Image =
    path(doodle.core.OpenPath.line(x, y))

  def arc(diameter: Double, angle: Angle): Image =
    path(doodle.core.OpenPath.arc(0.0, 0.0, diameter, angle))

  def circle(diameter: Double): Image =
    Circle(diameter)

  def pie(diameter: Double, angle: Angle): Image =
    path(doodle.core.ClosedPath.pie(0.0, 0.0, diameter, angle))

  def rectangle(width: Double, height: Double): Image =
    Rectangle(width, height)

  def square(side: Double): Image =
    rectangle(side, side)

  def regularPolygon(sides: Int, radius: Double): Image =
    path(doodle.core.ClosedPath.regularPolygon(sides, radius))

  def star(points: Int, outerRadius: Double, innerRadius: Double): Image =
    path(doodle.core.ClosedPath.star(points, outerRadius, innerRadius))

  def rightArrow(width: Double, height: Double): Image =
    path(doodle.core.ClosedPath.rightArrow(width, height))

  def roundedRectangle(width: Double, height: Double, radius: Double): Image =
    path(doodle.core.ClosedPath.roundedRectangle(width, height, radius))

  def equilateralTriangle(width: Double): Image =
    path(doodle.core.ClosedPath.equilateralTriangle(width))

  def triangle(width: Double, height: Double): Image =
    Triangle(width, height)

  /** Construct an open path of bezier curves that intersects all the given
    * points. Defaults to `catmulRom` with the default tension.
    */
  def interpolatingSpline(points: Seq[Point]): Image =
    catmulRom(points)

  /** Interpolate a spline (a curve) that passes through all the given points,
    * using the Catmul Rom formulation (see, e.g.,
    * https://en.wikipedia.org/wiki/Cubic_Hermite_spline)
    *
    * The tension can be changed to control how tightly the curve turns. It
    * defaults to 0.5.
    *
    * The Catmul Rom algorithm requires a point before and after each pair of
    * points that define the curve. To meet this condition for the first and
    * last points in `points`, they are repeated.
    *
    * If `points` has less than two elements an empty `Path` is returned.
    */
  def catmulRom(points: Seq[Point], tension: Double = 0.5): Image =
    path(doodle.core.OpenPath.catmulRom(points, tension))

  val empty: Image =
    Empty

  /** Compile an `Image` to a `doodle.algebra.Picture` */
  def compile[Alg <: Basic](
      image: Image
  ): doodle.algebra.Picture[Alg, Unit] = {
    import cats.instances.unit.*
    import Elements.*

    new doodle.algebra.Picture[Alg, Unit] {
      def apply(implicit algebra: Alg): algebra.Drawing[Unit] =
        image match {
          case OpenPath(path) =>
            algebra.path(path)
          case ClosedPath(path) =>
            algebra.path(path)

          case Text(t) =>
            algebra.text(t)
          case Font(image, f) =>
            algebra.font(compile(image)(algebra), f)

          case Circle(d) =>
            algebra.circle(d)
          case Rectangle(w, h) =>
            algebra.rectangle(w, h)
          case Triangle(w, h) =>
            algebra.triangle(w, h)

          case Beside(l, r) =>
            algebra.beside(compile(l)(algebra), compile(r)(algebra))
          case Above(l, r) =>
            algebra.above(compile(l)(algebra), compile(r)(algebra))
          case On(t, b) =>
            algebra.on(compile(t)(algebra), compile(b)(algebra))
          case At(image, landmark) =>
            algebra.at(compile(image)(algebra), landmark)
          case Margin(image, top, right, bottom, left) =>
            algebra.margin(compile(image)(algebra), top, right, bottom, left)
          case Size(image, width, height) =>
            algebra.size(compile(image)(algebra), width, height)
          case OriginAt(image, landmark) =>
            algebra.originAt(compile(image)(algebra), landmark)

          case Transform(tx, i) =>
            algebra.transform(compile(i)(algebra), tx)

          case StrokeWidth(image, width) =>
            algebra.strokeWidth(compile(image)(algebra), width)
          case StrokeColor(image, color) =>
            algebra.strokeColor(compile(image)(algebra), color)
          case StrokeCap(image, cap) =>
            algebra.strokeCap(compile(image)(algebra), cap)
          case StrokeJoin(image, join) =>
            algebra.strokeJoin(compile(image)(algebra), join)
          case StrokeDash(image, pattern) =>
            algebra.strokeDash(compile(image)(algebra), pattern)
          case FillColor(image, color) =>
            algebra.fillColor(compile(image)(algebra), color)
          case FillGradient(image, gradient) =>
            algebra.fillGradient(compile(image)(algebra), gradient)
          case NoStroke(image) =>
            algebra.noStroke(compile(image)(algebra))
          case NoFill(image) =>
            algebra.noFill(compile(image)(algebra))

          case Debug(image, color) =>
            algebra.debug(compile(image)(algebra), color)

          case Empty =>
            algebra.empty
        }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy