///|
/// Layered raster compositing primitives for thumbnails and visual regression.
pub(all) struct RasterLayerPlus {
name : String
raster : Raster
opacity : Double
blend : TextureBlend
offset : Vec2
visible : Bool
} derive(Debug)
///|
pub(all) struct RasterCompositeReport {
width : Int
height : Int
layer_count : Int
visible_layers : Int
source_pixels : Int
output_pixels : Int
checksum : UInt
mean_alpha : Double
} derive(Eq, Debug, ToJson)
///|
pub fn RasterLayerPlus::new(name : String, raster : Raster) -> RasterLayerPlus {
{
name,
raster,
opacity: 1.0,
blend: TextureNormal,
offset: Vec2::zero(),
visible: true,
}
}
///|
pub fn RasterLayerPlus::opacity(
self : RasterLayerPlus,
value : Double,
) -> RasterLayerPlus {
{ ..self, opacity: value.max(0.0).min(1.0) }
}
///|
pub fn RasterLayerPlus::blend_mode(
self : RasterLayerPlus,
mode : TextureBlend,
) -> RasterLayerPlus {
{ ..self, blend: mode }
}
///|
pub fn RasterLayerPlus::translate(
self : RasterLayerPlus,
offset : Vec2,
) -> RasterLayerPlus {
{ ..self, offset, }
}
///|
pub fn RasterLayerPlus::hide(self : RasterLayerPlus) -> RasterLayerPlus {
{ ..self, visible: false }
}
///|
pub fn RasterLayerPlus::show(self : RasterLayerPlus) -> RasterLayerPlus {
{ ..self, visible: true }
}
///|
pub fn raster_plus_pixel(
raster : Raster,
x : Int,
y : Int,
fallback : Color,
) -> Color {
raster.get(x, y).unwrap_or(fallback)
}
///|
pub fn raster_plus_stamp(base : Raster, layer : RasterLayerPlus) -> Raster {
let result = base.copy()
if layer.visible {
for y in 0.. Raster {
let result = Raster::new(width, height, fill=background)
for layer in layers {
let stamped = raster_plus_stamp(result, layer)
for y in 0.. Raster {
let width = bounds.width.max(0.0).to_int()
let height = bounds.height.max(0.0).to_int()
let result = Raster::new(width, height)
for y in 0.. Raster {
let width = raster.width + padding.horizontal().to_int().max(0)
let height = raster.height + padding.vertical().to_int().max(0)
let result = Raster::new(width, height, fill~)
for y in 0.. Raster {
let result = Raster::new(width, height)
if raster.width > 0 && raster.height > 0 {
for y in 0.. Raster {
let result = Raster::new(width, height)
if raster.width > 0 && raster.height > 0 {
for y in 0.. Raster {
let width = left.width.min(right.width)
let height = left.height.min(right.height)
let result = Raster::new(width, height)
for y in 0.. Double {
let width = left.width.min(right.width)
let height = left.height.min(right.height)
let mut total = 0.0
let mut count = 0
for y in 0.. UInt {
let mut hash : UInt = 2166136261
for color in raster.pixels {
hash = (hash ^ (color.r * 255.0).to_int().reinterpret_as_uint()) * 16777619
hash = (hash ^ (color.g * 255.0).to_int().reinterpret_as_uint()) * 16777619
hash = (hash ^ (color.b * 255.0).to_int().reinterpret_as_uint()) * 16777619
}
hash
}
///|
pub fn raster_plus_report(
raster : Raster,
layers : Array[RasterLayerPlus],
) -> RasterCompositeReport {
let mut visible = 0
let mut source = 0
let mut alpha = 0.0
for layer in layers {
source += layer.raster.pixels.length()
if layer.visible {
visible += 1
}
for color in layer.raster.pixels {
alpha += color.a
}
}
let pixel_count = raster.pixels.length()
{
width: raster.width,
height: raster.height,
layer_count: layers.length(),
visible_layers: visible,
source_pixels: source,
output_pixels: pixel_count,
checksum: raster_plus_checksum(raster),
mean_alpha: if source == 0 {
0.0
} else {
alpha / source.to_double()
},
}
}
///|
pub fn raster_plus_report_csv(report : RasterCompositeReport) -> String {
"width,height,layer_count,visible_layers,source_pixels,output_pixels,checksum,mean_alpha\n" +
report.width.to_string() +
"," +
report.height.to_string() +
"," +
report.layer_count.to_string() +
"," +
report.visible_layers.to_string() +
"," +
report.source_pixels.to_string() +
"," +
report.output_pixels.to_string() +
"," +
report.checksum.to_string() +
"," +
report.mean_alpha.to_string() +
"\n"
}
///|
pub fn raster_plus_draw_path(
raster : Raster,
path : Path,
color : Color,
) -> Raster {
let result = raster.copy()
let points = path.flatten(subdivisions=8)
if points.length() > 1 {
for index in 0..<(points.length() - 1) {
let a = points[index]
let b = points[index + 1]
let distance = a.distance(b).max(1.0)
let steps = distance.to_int().max(1)
for step in 0..<=steps {
let amount = step.to_double() / steps.to_double()
let point = a.lerp(b, amount)
let _ = result.blend_pixel(point.x.to_int(), point.y.to_int(), color)
}
}
}
result
}
///|
pub fn raster_plus_draw_motifs(
raster : Raster,
motifs : Array[Motif],
) -> Raster {
let mut result = raster
for motif in motifs {
result = raster_plus_draw_path(
result,
motif.transformed_path(),
Color::rgba(0.0, 0.0, 0.0, a=motif.style.opacity),
)
}
result
}
///|
pub fn raster_plus_outline(
raster : Raster,
width : Int,
color : Color,
) -> Raster {
let result = raster.copy()
let radius = width.max(1)
for y in 0.. 0.0 {
for oy in -radius..<=radius {
for ox in -radius..<=radius {
if ox * ox + oy * oy <= radius * radius &&
result.in_bounds(x + ox, y + oy) {
let _ = result.blend_pixel(x + ox, y + oy, color)
}
}
}
}
}
}
result
}
///|
pub fn raster_plus_alpha_mask(raster : Raster, mask : Raster) -> Raster {
let result = raster.copy()
let width = raster.width.min(mask.width)
let height = raster.height.min(mask.height)
for y in 0.. (Raster, RasterCompositeReport) {
let width = layers.fold(init=0, fn(best, layer) {
best.max(layer.raster.width + layer.offset.x.to_int())
})
let height = layers.fold(init=0, fn(best, layer) {
best.max(layer.raster.height + layer.offset.y.to_int())
})
let result = raster_plus_composite(width, height, background, layers)
(result, raster_plus_report(result, layers))
}