// Image combinators: build the declarative AST.
//
// Convenience shape constructors are sugar for cutting a path out of a constant
// colour field, mirroring Vg's "primitives are colour fields, paths cut them".
// ----- smart constructors (the faithful core) -----
///|
/// A constant colour filling the whole plane.
pub fn Image::const_color(color : Color) -> Image {
Primitive(Const(color))
}
///|
/// The empty (fully transparent) image.
pub fn Image::empty() -> Image {
Primitive(Const(@color.transparent()))
}
///|
/// Clip `self` to the inside of `path` (default non-zero winding). Outside the
/// path the result is transparent.
pub fn Image::cut(self : Image, path : Path, area? : Area = Anz) -> Image {
Cut(area, path, self)
}
///|
#deprecated("Use `image.cut(path)` instead")
pub fn cut(path : Path, img : Image, area? : Area = Anz) -> Image {
img.cut(path, area~)
}
///|
/// `self` composited over `bottom`.
pub fn Image::over(self : Image, bottom : Image) -> Image {
Blend(Over, None, self, bottom)
}
///|
/// Blend `self` (the top layer) with `bottom` using an explicit operator and an
/// optional top alpha.
#as_free_fn(blend, deprecated="Use `top.blend(bottom)` instead")
pub fn Image::blend(
self : Image,
bottom : Image,
op? : Blender = Over,
alpha? : Double,
) -> Image {
Blend(op, alpha, self, bottom)
}
// ----- transforms -----
///|
pub fn Image::transform(self : Image, t : Transform) -> Image {
Tr(t, self)
}
///|
pub fn Image::translate_img(self : Image, dx : Double, dy : Double) -> Image {
Tr(@geometry.make_translate(dx, dy), self)
}
///|
pub fn Image::scale(self : Image, sx : Double, sy : Double) -> Image {
Tr(@geometry.make_scale(sx, sy), self)
}
///|
pub fn Image::rotate(self : Image, angle : Double) -> Image {
Tr(@geometry.make_rotate(angle), self)
}
// ----- compositing -----
///|
/// Compose `other` over `self` (the receiver is the base layer), so
/// `base.compose(overlay)` paints the overlay on top.
pub fn Image::compose(self : Image, other : Image) -> Image {
Blend(Over, None, other, self)
}
///|
/// Scale an image's opacity by `opacity`.
pub fn Image::with_opacity(self : Image, opacity : Double) -> Image {
Blend(Over, Some(opacity), self, Image::empty())
}
// ----- convenience shapes (cut a path from a const colour field) -----
///|
pub fn Image::circle(color : Color, radius : Double) -> Image {
Cut(Anz, Path::circle(Point(0.0, 0.0), radius), Primitive(Const(color)))
}
///|
pub fn Image::rectangle(
color : Color,
width : Double,
height : Double,
) -> Image {
Cut(
Anz,
Path::rect(-width / 2.0, -height / 2.0, width, height),
Primitive(Const(color)),
)
}
///|
pub fn Image::ellipse(color : Color, rx : Double, ry : Double) -> Image {
Cut(Anz, Path::ellipse(Point(0.0, 0.0), rx, ry), Primitive(Const(color)))
}
///|
pub fn Image::polygon(color : Color, points : Array[Point]) -> Image {
Cut(Anz, polygon_path(points), Primitive(Const(color)))
}
///|
/// A straight line segment stroked with the given thickness (round caps). A
/// zero-length segment renders as a dot.
pub fn Image::line(
color : Color,
start : Point,
end : Point,
thickness : Double,
) -> Image {
let path = Path::empty().move_to(start).line_to(end)
Cut(Outline(thickness), path, Primitive(Const(color)))
}
///|
fn polygon_path(points : Array[Point]) -> Path {
guard points.length() > 0 else { Path::empty() }
let mut p = Path::empty().move_to(points[0])
for i in 1.. Image {
Primitive(
Axial(
[{ offset: 0.0, color: color1 }, { offset: 1.0, color: color2 }],
start,
end,
),
)
}
///|
pub fn Image::axial_gradient(
color1 : Color,
color2 : Color,
start : Point,
end : Point,
) -> Image {
Image::linear_gradient(color1, color2, start, end)
}
///|
pub fn Image::radial_gradient(
color1 : Color,
color2 : Color,
center : Point,
radius : Double,
) -> Image {
Primitive(
Radial(
[{ offset: 0.0, color: color1 }, { offset: 1.0, color: color2 }],
center,
center,
radius,
),
)
}
// ----- raster sampling (fallback; superseded by vector folds for backends) -----
///|
/// Sample the image on a grid and emit one SVG rect per opaque cell. This is the
/// raster fallback (`eval`-based); vector backends render the AST directly and
/// produce far smaller output.
pub fn Image::render_image_to_svg(
self : Image,
width : Double,
height : Double,
samples : Int,
) -> String {
let mut doc = @svg.new_svg(width, height)
let step_x = width / samples.to_double()
let step_y = height / samples.to_double()
for i in 0.. 0.0 {
doc = doc.render_rectangle(x, y, step_x, step_y, color)
}
}
}
doc.to_string()
}
// ----- procedural images (a Raster colour field sampled by eval) -----
///|
/// An image from an arbitrary point -> colour function.
pub fn Image::of_fn(f : (Point) -> Color) -> Image {
Primitive(Raster(f))
}
///|
/// A checkerboard of two colours with the given cell `size`.
pub fn Image::checkerboard(
color1 : Color,
color2 : Color,
size : Double,
) -> Image {
Image::of_fn(fn(p) {
let x_cell = (p.x / size).floor().to_int()
let y_cell = (p.y / size).floor().to_int()
if (x_cell + y_cell) % 2 == 0 {
color1
} else {
color2
}
})
}
///|
/// Tile `self` into `tile_width` x `tile_height` cells.
pub fn Image::tile(
self : Image,
tile_width : Double,
tile_height : Double,
) -> Image {
Image::of_fn(fn(p) {
let tx = p.x % tile_width
let ty = p.y % tile_height
let nx = if tx < 0.0 { tx + tile_width } else { tx }
let ny = if ty < 0.0 { ty + tile_height } else { ty }
self.eval(Point(nx - tile_width / 2.0, ny - tile_height / 2.0))
})
}
///|
/// An angular (conic) gradient around `center`, starting at `start_angle`.
pub fn Image::conic_gradient(
color1 : Color,
color2 : Color,
center : Point,
start_angle : Double,
) -> Image {
Image::of_fn(fn(p) {
let two_pi = 2.0 * 3.14159265358979
let angle = @math.atan2(p.y - center.y, p.x - center.x)
let normalized = ((angle - start_angle) % two_pi + two_pi) % two_pi
@color.lerp_color(color1, color2, normalized / two_pi)
})
}
// ----- text (draw-only) -----
///|
/// A text label anchored at the origin (centre-aligned). Position it with
/// transforms. Text is draw-only: `eval` is transparent for it, but the
/// SVG/PDF/canvas backends render it.
pub fn Image::text(content : String, size : Double, color : Color) -> Image {
Text(content, size, color)
}