///|
fn filter_source_alpha(source : Image) -> Image {
let result = Image::new(source.width, source.height)
for y in 0.. 0 {
result.set_pixel(x, y, Color::rgba(0, 0, 0, alpha))
}
}
}
result
}
///|
fn resolve_filter_graph_input(
name : String,
source : Image,
source_alpha : Image,
previous : Image,
named : Map[String, Image],
) -> Image {
match name {
"SourceGraphic" => source
"SourceAlpha" => source_alpha
"" => previous
_ => named.get(name).unwrap_or(Image::new(source.width, source.height))
}
}
///|
fn offset_filter_image(source : Image, dx : Double, dy : Double) -> Image {
let result = Image::new(source.width, source.height)
let offset_x = dx.round().to_int()
let offset_y = dy.round().to_int()
for y in 0.. 0 {
result.set_pixel(x + offset_x, y + offset_y, color)
}
}
}
result
}
///|
fn blur_filter_image_xy(
source : Image,
radius_x : Double,
radius_y : Double,
) -> Image {
let rx = ceil_to_int(if radius_x < 0.0 { 0.0 } else { radius_x })
let ry = ceil_to_int(if radius_y < 0.0 { 0.0 } else { radius_y })
if rx == 0 && ry == 0 {
return source
}
let result = Image::new(source.width, source.height)
let sample_count = (rx * 2 + 1) * (ry * 2 + 1)
for y in 0.. 0 {
result.set_pixel(
x,
y,
Color::rgba(
red_premultiplied / alpha_sum,
green_premultiplied / alpha_sum,
blue_premultiplied / alpha_sum,
alpha_sum / sample_count,
),
)
}
}
}
result
}
///|
fn transfer_filter_value(
function : ComponentTransferFunction,
value : Double,
) -> Double {
match function {
TransferIdentity => value
TransferTable(values) => {
if values.length() == 0 {
return value
}
if values.length() == 1 {
return clamp_filter_unit(values[0])
}
let position = clamp_filter_unit(value) *
(values.length() - 1).to_double()
let lower = position.floor().to_int()
let upper = if lower + 1 < values.length() { lower + 1 } else { lower }
let fraction = position - lower.to_double()
clamp_filter_unit(
values[lower] * (1.0 - fraction) + values[upper] * fraction,
)
}
TransferDiscrete(values) => {
if values.length() == 0 {
return value
}
let mut index = (clamp_filter_unit(value) * values.length().to_double())
.floor()
.to_int()
if index >= values.length() {
index = values.length() - 1
}
clamp_filter_unit(values[index])
}
TransferLinear(slope~, intercept~) =>
clamp_filter_unit(slope * value + intercept)
TransferGamma(amplitude~, exponent~, offset~) =>
clamp_filter_unit(amplitude * @math.pow(value, exponent) + offset)
}
}
///|
fn component_transfer_filter_image(
source : Image,
red : ComponentTransferFunction,
green : ComponentTransferFunction,
blue : ComponentTransferFunction,
alpha : ComponentTransferFunction,
) -> Image {
let result = Image::new(source.width, source.height)
for y in 0.. Image {
let rx = ceil_to_int(if radius_x < 0.0 { 0.0 } else { radius_x })
let ry = ceil_to_int(if radius_y < 0.0 { 0.0 } else { radius_y })
let result = Image::new(source.width, source.height)
for y in 0.. color.r { r } else { color.r }
g = if g > color.g { g } else { color.g }
b = if b > color.b { b } else { color.b }
a = if a > color.a { a } else { color.a }
} else {
r = if r < color.r { r } else { color.r }
g = if g < color.g { g } else { color.g }
b = if b < color.b { b } else { color.b }
a = if a < color.a { a } else { color.a }
}
}
}
result.set_pixel(x, y, Color::rgba(r, g, b, a))
}
}
result
}
///|
fn convolve_sample(
source : Image,
x : Int,
y : Int,
edge_mode : FilterEdgeMode,
) -> Color {
if x >= 0 && x < source.width && y >= 0 && y < source.height {
return source.get_pixel(x, y)
}
match edge_mode {
EdgeNone => Color::transparent()
EdgeDuplicate => {
let sample_x = if x < 0 {
0
} else if x >= source.width {
source.width - 1
} else {
x
}
let sample_y = if y < 0 {
0
} else if y >= source.height {
source.height - 1
} else {
y
}
source.get_pixel(sample_x, sample_y)
}
EdgeWrap => {
let mut sample_x = x % source.width
let mut sample_y = y % source.height
if sample_x < 0 {
sample_x = sample_x + source.width
}
if sample_y < 0 {
sample_y = sample_y + source.height
}
source.get_pixel(sample_x, sample_y)
}
}
}
///|
fn convolve_filter_image(
source : Image,
order_x : Int,
order_y : Int,
kernel : Array[Double],
divisor : Double,
bias : Double,
target_x : Int,
target_y : Int,
edge_mode : FilterEdgeMode,
preserve_alpha : Bool,
) -> Image {
if order_x <= 0 || order_y <= 0 || kernel.length() != order_x * order_y {
return source
}
let safe_divisor = if divisor.abs() <= 0.000001 { 1.0 } else { divisor }
let result = Image::new(source.width, source.height)
for y in 0.. Int {
(clamp_filter_unit(value / safe_divisor / 255.0 + bias) * 255.0)
.round()
.to_int()
}
result.set_pixel(
x,
y,
Color::rgba(
output_channel(red),
output_channel(green),
output_channel(blue),
if preserve_alpha {
source.get_pixel(x, y).a
} else {
output_channel(alpha)
},
),
)
}
}
result
}
///|
fn filter_channel_value(color : Color, channel : FilterChannel) -> Double {
match channel {
ChannelR => color.r.to_double() / 255.0
ChannelG => color.g.to_double() / 255.0
ChannelB => color.b.to_double() / 255.0
ChannelA => color.a.to_double() / 255.0
}
}
///|
fn displacement_filter_image(
source : Image,
displacement : Image,
scale_x : Double,
scale_y : Double,
x_channel : FilterChannel,
y_channel : FilterChannel,
) -> Image {
let result = Image::new(source.width, source.height)
for y in 0.. Double {
value * value * (3.0 - 2.0 * value)
}
///|
fn filter_noise_hash(x : Int, y : Int, seed : Double) -> Double {
let value = @math.sin(
x.to_double() * 12.9898 + y.to_double() * 78.233 + seed * 37.719,
) *
43758.5453
let fraction = value - value.floor()
if fraction < 0.0 {
fraction + 1.0
} else {
fraction
}
}
///|
fn filter_value_noise(x : Double, y : Double, seed : Double) -> Double {
let x0 = x.floor().to_int()
let y0 = y.floor().to_int()
let tx = smooth_noise_fraction(x - x0.to_double())
let ty = smooth_noise_fraction(y - y0.to_double())
let top = filter_noise_hash(x0, y0, seed) * (1.0 - tx) +
filter_noise_hash(x0 + 1, y0, seed) * tx
let bottom = filter_noise_hash(x0, y0 + 1, seed) * (1.0 - tx) +
filter_noise_hash(x0 + 1, y0 + 1, seed) * tx
top * (1.0 - ty) + bottom * ty
}
///|
fn turbulence_filter_image(
width : Int,
height : Int,
base_x : Double,
base_y : Double,
octaves : Int,
seed : Double,
fractal_noise : Bool,
) -> Image {
let result = Image::new(width, height)
let octave_count = if octaves < 1 {
1
} else if octaves > 8 {
8
} else {
octaves
}
for y in 0.. Image {
let mut min_x = source.width
let mut min_y = source.height
let mut max_x = -1
let mut max_y = -1
for y in 0.. 0 {
min_x = if min_x < x { min_x } else { x }
min_y = if min_y < y { min_y } else { y }
max_x = if max_x > x { max_x } else { x }
max_y = if max_y > y { max_y } else { y }
}
}
}
if max_x < min_x || max_y < min_y {
return Image::new(source.width, source.height)
}
let tile_width = max_x - min_x + 1
let tile_height = max_y - min_y + 1
let result = Image::new(source.width, source.height)
for y in 0.. Image?)?,
) -> Image {
let result = Image::new(width, height)
let resource = match resolver {
Some(resolve) => resolve(href)
None => None
}
match resource {
Some(image) => {
let output_width = if target_width > 0.0 {
target_width
} else {
image.width.to_double()
}
let output_height = if target_height > 0.0 {
target_height
} else {
image.height.to_double()
}
let min_x = floor_to_int(x)
let min_y = floor_to_int(y)
let max_x = ceil_to_int(x + output_width)
let max_y = ceil_to_int(y + output_height)
for py in min_y.. ()
}
result
}
///|
fn normalize_filter_vector(
x : Double,
y : Double,
z : Double,
) -> (Double, Double, Double) {
let length = (x * x + y * y + z * z).sqrt()
if length <= 0.000001 {
(0.0, 0.0, 1.0)
} else {
(x / length, y / length, z / length)
}
}
///|
fn filter_light_vector(
light : FilterLight,
x : Double,
y : Double,
z : Double,
) -> (Double, Double, Double, Double) {
match light {
DistantLight(azimuth~, elevation~) => {
let azimuth_radians = degrees_to_radians(azimuth)
let elevation_radians = degrees_to_radians(elevation)
(
@math.cos(azimuth_radians) * @math.cos(elevation_radians),
@math.sin(azimuth_radians) * @math.cos(elevation_radians),
@math.sin(elevation_radians),
1.0,
)
}
PointLight(x=light_x, y=light_y, z=light_z) => {
let vector = normalize_filter_vector(
light_x - x,
light_y - y,
light_z - z,
)
(vector.0, vector.1, vector.2, 1.0)
}
SpotLight(
x=light_x,
y=light_y,
z=light_z,
points_at_x~,
points_at_y~,
points_at_z~,
exponent~,
limiting_cone_angle~
) => {
let vector = normalize_filter_vector(
light_x - x,
light_y - y,
light_z - z,
)
let spot_direction = normalize_filter_vector(
points_at_x - light_x,
points_at_y - light_y,
points_at_z - light_z,
)
let from_light = (-vector.0, -vector.1, -vector.2)
let cosine = max(
0.0,
from_light.0 * spot_direction.0 +
from_light.1 * spot_direction.1 +
from_light.2 * spot_direction.2,
)
let inside_cone = match limiting_cone_angle {
Some(angle) => cosine >= @math.cos(degrees_to_radians(angle))
None => true
}
let attenuation = if inside_cone {
@math.pow(cosine, exponent)
} else {
0.0
}
(vector.0, vector.1, vector.2, attenuation)
}
}
}
///|
fn lighting_filter_image(
source : Image,
surface_scale : Double,
constant : Double,
exponent : Double,
lighting_color : Color,
light : FilterLight,
specular : Bool,
) -> Image {
let result = Image::new(source.width, source.height)
for y in 0.. green { red } else { green }
if red_green > blue {
red_green
} else {
blue
}
} else {
255
}
result.set_pixel(x, y, Color::rgba(red, green, blue, alpha))
}
}
result
}
///|
fn filter_primitive_x(
graph : FilterGraph,
value : Double,
target : BoundingBox,
) -> Double {
match graph.primitive_units {
ObjectBoundingBox => value * target.width()
UserSpaceOnUse => value
}
}
///|
fn filter_primitive_y(
graph : FilterGraph,
value : Double,
target : BoundingBox,
) -> Double {
match graph.primitive_units {
ObjectBoundingBox => value * target.height()
UserSpaceOnUse => value
}
}
///|
fn filter_primitive_position_x(
graph : FilterGraph,
value : Double,
target : BoundingBox,
origin_x : Int,
) -> Double {
match graph.primitive_units {
ObjectBoundingBox => target.min_x + value * target.width()
UserSpaceOnUse => value - origin_x.to_double()
}
}
///|
fn filter_primitive_position_y(
graph : FilterGraph,
value : Double,
target : BoundingBox,
origin_y : Int,
) -> Double {
match graph.primitive_units {
ObjectBoundingBox => target.min_y + value * target.height()
UserSpaceOnUse => value - origin_y.to_double()
}
}
///|
fn resolve_filter_light(
graph : FilterGraph,
light : FilterLight,
target : BoundingBox,
origin_x : Int,
origin_y : Int,
) -> FilterLight {
match light {
DistantLight(..) => light
PointLight(x~, y~, z~) =>
PointLight(
x=filter_primitive_position_x(graph, x, target, origin_x),
y=filter_primitive_position_y(graph, y, target, origin_y),
z=filter_primitive_x(graph, z, target),
)
SpotLight(
x~,
y~,
z~,
points_at_x~,
points_at_y~,
points_at_z~,
exponent~,
limiting_cone_angle~
) =>
SpotLight(
x=filter_primitive_position_x(graph, x, target, origin_x),
y=filter_primitive_position_y(graph, y, target, origin_y),
z=filter_primitive_x(graph, z, target),
points_at_x=filter_primitive_position_x(
graph, points_at_x, target, origin_x,
),
points_at_y=filter_primitive_position_y(
graph, points_at_y, target, origin_y,
),
points_at_z=filter_primitive_x(graph, points_at_z, target),
exponent~,
limiting_cone_angle~,
)
}
}
///|
fn clamp_filter_unit(value : Double) -> Double {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
///|
fn composite_filter_pixel(
source : Color,
backdrop : Color,
operator : FilterCompositeOperator,
k1 : Double,
k2 : Double,
k3 : Double,
k4 : Double,
) -> Color {
let source_alpha = source.a.to_double() / 255.0
let backdrop_alpha = backdrop.a.to_double() / 255.0
let (source_factor, backdrop_factor) = match operator {
CompositeOver => (1.0, 1.0 - source_alpha)
CompositeIn => (backdrop_alpha, 0.0)
CompositeOut => (1.0 - backdrop_alpha, 0.0)
CompositeAtop => (backdrop_alpha, 1.0 - source_alpha)
CompositeXor => (1.0 - backdrop_alpha, 1.0 - source_alpha)
CompositeArithmetic => (0.0, 0.0)
}
let output_alpha = if operator == CompositeArithmetic {
clamp_filter_unit(
k1 * source_alpha * backdrop_alpha +
k2 * source_alpha +
k3 * backdrop_alpha +
k4,
)
} else {
clamp_filter_unit(
source_alpha * source_factor + backdrop_alpha * backdrop_factor,
)
}
if output_alpha <= 0.0 {
return Color::transparent()
}
fn channel(source_channel : Int, backdrop_channel : Int) -> Int {
let source_premultiplied = source_channel.to_double() / 255.0 * source_alpha
let backdrop_premultiplied = backdrop_channel.to_double() /
255.0 *
backdrop_alpha
let output_premultiplied = if operator == CompositeArithmetic {
clamp_filter_unit(
k1 * source_premultiplied * backdrop_premultiplied +
k2 * source_premultiplied +
k3 * backdrop_premultiplied +
k4,
)
} else {
source_premultiplied * source_factor +
backdrop_premultiplied * backdrop_factor
}
(clamp_filter_unit(output_premultiplied / output_alpha) * 255.0)
.round()
.to_int()
}
Color::rgba(
channel(source.r, backdrop.r),
channel(source.g, backdrop.g),
channel(source.b, backdrop.b),
(output_alpha * 255.0).round().to_int(),
)
}
///|
fn composite_filter_images(
source : Image,
backdrop : Image,
operator : FilterCompositeOperator,
k1 : Double,
k2 : Double,
k3 : Double,
k4 : Double,
) -> Image {
let width = if source.width < backdrop.width {
source.width
} else {
backdrop.width
}
let height = if source.height < backdrop.height {
source.height
} else {
backdrop.height
}
let result = Image::new(width, height)
for y in 0.. Image {
let min_x = floor_to_int(bounds.min_x)
let min_y = floor_to_int(bounds.min_y)
let max_x = ceil_to_int(bounds.max_x)
let max_y = ceil_to_int(bounds.max_y)
for y in 0..= max_x || y < min_y || y >= max_y {
result.set_pixel(x, y, Color::transparent())
}
}
}
result
}
///|
fn evaluate_filter_graph(
graph : FilterGraph,
source : Image,
target_bounds : BoundingBox,
image_resolver : ((String) -> Image?)?,
origin_x : Int,
origin_y : Int,
) -> Image {
if graph.primitives.is_empty() {
return Image::new(source.width, source.height)
}
let target_bounds = {
min_x: target_bounds.min_x - origin_x.to_double(),
min_y: target_bounds.min_y - origin_y.to_double(),
max_x: target_bounds.max_x - origin_x.to_double(),
max_y: target_bounds.max_y - origin_y.to_double(),
}
let source_alpha = filter_source_alpha(source)
let named : Map[String, Image] = Map([])
let mut previous = source
for primitive in graph.primitives {
let (output, result_name) = match primitive {
GraphColorMatrix(input~, result~, matrix~) =>
(
apply_filter(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
ColorMatrix(matrix),
),
result,
)
GraphGaussianBlur(input~, result~, radius_x~, radius_y~) =>
(
blur_filter_image_xy(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
filter_primitive_x(graph, radius_x, target_bounds),
filter_primitive_y(graph, radius_y, target_bounds),
),
result,
)
GraphOffset(input~, result~, dx~, dy~) =>
(
offset_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
filter_primitive_x(graph, dx, target_bounds),
filter_primitive_y(graph, dy, target_bounds),
),
result,
)
GraphBlend(input~, input2~, result~, mode~) =>
(
blend_images(
resolve_filter_graph_input(
input2, source, source_alpha, previous, named,
),
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
mode,
),
result,
)
GraphComposite(input~, input2~, result~, operator~, k1~, k2~, k3~, k4~) =>
(
composite_filter_images(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
resolve_filter_graph_input(
input2, source, source_alpha, previous, named,
),
operator,
k1,
k2,
k3,
k4,
),
result,
)
GraphFlood(result~, color~) =>
(Image::filled(source.width, source.height, color), result)
GraphMerge(result~, inputs~) => {
let mut merged = Image::new(source.width, source.height)
for input in inputs {
merged = blend_images(
merged,
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
Normal,
)
}
(merged, result)
}
GraphComponentTransfer(input~, result~, red~, green~, blue~, alpha~) =>
(
component_transfer_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
red,
green,
blue,
alpha,
),
result,
)
GraphMorphology(input~, result~, radius_x~, radius_y~, operator~) =>
(
morphology_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
filter_primitive_x(graph, radius_x, target_bounds),
filter_primitive_y(graph, radius_y, target_bounds),
operator,
),
result,
)
GraphConvolveMatrix(
input~,
result~,
order_x~,
order_y~,
kernel~,
divisor~,
bias~,
target_x~,
target_y~,
edge_mode~,
preserve_alpha~
) =>
(
convolve_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
order_x,
order_y,
kernel,
divisor,
bias,
target_x,
target_y,
edge_mode,
preserve_alpha,
),
result,
)
GraphDisplacementMap(
input~,
input2~,
result~,
scale~,
x_channel~,
y_channel~
) =>
(
displacement_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
resolve_filter_graph_input(
input2, source, source_alpha, previous, named,
),
filter_primitive_x(graph, scale, target_bounds),
filter_primitive_y(graph, scale, target_bounds),
x_channel,
y_channel,
),
result,
)
GraphTurbulence(
result~,
base_x~,
base_y~,
octaves~,
seed~,
stitch=_,
fractal_noise~
) => {
let frequency_x = match graph.primitive_units {
ObjectBoundingBox =>
if target_bounds.width() <= 0.0 {
0.0
} else {
base_x / target_bounds.width()
}
UserSpaceOnUse => base_x
}
let frequency_y = match graph.primitive_units {
ObjectBoundingBox =>
if target_bounds.height() <= 0.0 {
0.0
} else {
base_y / target_bounds.height()
}
UserSpaceOnUse => base_y
}
(
turbulence_filter_image(
source.width,
source.height,
frequency_x,
frequency_y,
octaves,
seed,
fractal_noise,
),
result,
)
}
GraphTile(input~, result~) =>
(
tile_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
),
result,
)
GraphImage(result~, href~, x~, y~, width~, height~) =>
(
filter_image_resource(
source.width,
source.height,
href,
filter_primitive_position_x(graph, x, target_bounds, origin_x),
filter_primitive_position_y(graph, y, target_bounds, origin_y),
filter_primitive_x(graph, width, target_bounds),
filter_primitive_y(graph, height, target_bounds),
image_resolver,
),
result,
)
GraphDiffuseLighting(
input~,
result~,
surface_scale~,
diffuse_constant~,
color~,
light~
) =>
(
lighting_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
filter_primitive_y(graph, surface_scale, target_bounds),
diffuse_constant,
1.0,
color,
resolve_filter_light(
graph, light, target_bounds, origin_x, origin_y,
),
false,
),
result,
)
GraphSpecularLighting(
input~,
result~,
surface_scale~,
specular_constant~,
specular_exponent~,
color~,
light~
) =>
(
lighting_filter_image(
resolve_filter_graph_input(
input, source, source_alpha, previous, named,
),
filter_primitive_y(graph, surface_scale, target_bounds),
specular_constant,
specular_exponent,
color,
resolve_filter_light(
graph, light, target_bounds, origin_x, origin_y,
),
true,
),
result,
)
}
previous = output
if result_name.length() > 0 {
named.set(result_name, output)
}
}
let filter_bounds = match graph.units {
ObjectBoundingBox => graph.get_filter_bounds(target_bounds)
UserSpaceOnUse => {
let device_target = {
min_x: target_bounds.min_x + origin_x.to_double(),
min_y: target_bounds.min_y + origin_y.to_double(),
max_x: target_bounds.max_x + origin_x.to_double(),
max_y: target_bounds.max_y + origin_y.to_double(),
}
let device_bounds = graph.get_filter_bounds(device_target)
{
min_x: device_bounds.min_x - origin_x.to_double(),
min_y: device_bounds.min_y - origin_y.to_double(),
max_x: device_bounds.max_x - origin_x.to_double(),
max_y: device_bounds.max_y - origin_y.to_double(),
}
}
}
clip_filter_result(previous, filter_bounds)
}
///|