Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)

* Implement experimental Context struct and traits

* Add Ctx super trait

* Checkpoint

* Return Any instead of DynAny

* Fix send implementation for inputs with lifetimes

* Port more nodes

* Uncomment nodes

* Port more nodes

* Port vector nodes

* Partial progress (the stuff I'm more sure about)

* Partial progress (the stuff that's not compiling and I'm not sure about)

* Fix more errors

* First pass of fixing errors introduced by rebase

* Port wasm application io

* Fix brush node types

* Add type annotation

* Fix warnings and wasm compilation

* Change types for Document Node definitions

* Improve debugging for footprint not found errors

* Forward context in append artboard node

* Fix thumbnails

* Fix loading most demo artwork

* Wrap output type of all nodes in future

* Encode futures as part of the type

* Fix document node definitions for future types

* Remove Clippy warnings

* Fix more things

* Fix opening demo art with manual composition upgrading

* Set correct type for manual composition

* Fix brush

* Fix tests

* Update docs for deps

* Fix up some node signature issues

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Dennis Kobert
2025-03-01 23:54:52 +01:00
committed by Keavon Chambers
parent 0c1e96b9c6
commit 4ff2bdb04f
43 changed files with 1338 additions and 1545 deletions

View File

@@ -6,9 +6,9 @@ use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::{Channel, Color, Pixel};
use crate::registry::types::{Angle, Percentage, SignedPercentage};
use crate::transform::Footprint;
use crate::vector::style::GradientStops;
use crate::vector::VectorDataTable;
use crate::{Ctx, Node};
use crate::{GraphicElement, GraphicGroupTable};
use dyn_any::DynAny;
@@ -284,26 +284,16 @@ impl From<BlendMode> for vello::peniko::Mix {
}
#[node_macro::node(category("Raster: Adjustment"))]
async fn luminance<F: 'n + Send, T: Adjust<Color>>(
fn luminance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
luminance_calc: LuminanceCalculation,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let luminance = match luminance_calc {
LuminanceCalculation::SRGB => color.luminance_srgb(),
@@ -318,26 +308,16 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
}
#[node_macro::node(category("Raster"))]
async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
fn extract_channel<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
channel: RedGreenBlueAlpha,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let extracted_value = match channel {
RedGreenBlueAlpha::Red => color.r(),
@@ -351,25 +331,15 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
}
#[node_macro::node(category("Raster"))]
async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
fn make_opaque<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
if color.a() == 0. {
return color.with_alpha(1.);
@@ -385,31 +355,21 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
// Algorithm from:
// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels
#[node_macro::node(category("Raster: Adjustment"))]
async fn levels<F: 'n + Send, T: Adjust<Color>>(
fn levels<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
#[default(0.)] shadows: Percentage,
#[default(50.)] midtones: Percentage,
#[default(100.)] highlights: Percentage,
#[default(0.)] output_minimums: Percentage,
#[default(100.)] output_maximums: Percentage,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let color = color.to_gamma_srgb();
// Input Range (Range: 0-1)
@@ -451,7 +411,7 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
color.to_linear_srgb()
});
input
image
}
// Aims for interoperable compatibility with:
@@ -462,23 +422,14 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
// https://stackoverflow.com/a/55233732/775283
// Works the same for gamma and linear color
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
async fn black_and_white<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
#[default(Color::BLACK)] tint: Color,
#[default(40.)]
#[range((-200., 300.))]
@@ -499,8 +450,7 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
#[range((-200., 300.))]
magentas: Percentage,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let color = color.to_gamma_srgb();
let reds = reds as f32 / 100.;
@@ -537,35 +487,25 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
color.to_linear_srgb()
});
input
image
}
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
async fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
hue_shift: Angle,
saturation_shift: SignedPercentage,
lightness_shift: SignedPercentage,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let color = color.to_gamma_srgb();
@@ -588,25 +528,15 @@ async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize
#[node_macro::node(category("Raster: Adjustment"))]
async fn invert<F: 'n + Send, T: Adjust<Color>>(
async fn invert<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let color = color.to_gamma_srgb();
@@ -620,29 +550,19 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient
#[node_macro::node(category("Raster: Adjustment"))]
async fn threshold<F: 'n + Send, T: Adjust<Color>>(
async fn threshold<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
#[default(50.)] min_luminance: Percentage,
#[default(100.)] max_luminance: Percentage,
luminance_calc: LuminanceCalculation,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.);
let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.);
@@ -660,7 +580,7 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
Color::BLACK
}
});
input
image
}
trait Blend<P: Pixel> {
@@ -723,44 +643,35 @@ impl Blend<Color> for GradientStops {
}
#[node_macro::node(category("Raster"))]
async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
async fn blend<T: Blend<Color> + Send>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
over: impl Node<F, Output = T>,
over: T,
#[expose]
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
under: impl Node<F, Output = T>,
under: T,
blend_mode: BlendMode,
#[default(100.)] opacity: Percentage,
) -> T {
let over = over.eval(footprint).await;
let under = under.eval(footprint).await;
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
}
#[node_macro::node(category(""))]
fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color {
#[node_macro::node(category(""), skip_impl)]
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
where
BlendModeNode: Node<'n, (), Output = BlendMode> + 'n,
OpacityNode: Node<'n, (), Output = Percentage> + 'n,
{
let blend_mode = blend_mode.eval(());
let opacity = opacity.eval(());
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
}
@@ -857,35 +768,24 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
#[node_macro::node(category("Raster: Adjustment"))]
async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
gradient: GradientStops,
reverse: bool,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let intensity = color.luminance_srgb();
let intensity = if reverse { 1. - intensity } else { intensity };
gradient.evalute(intensity as f64)
});
input
image
}
// Aims for interoperable compatibility with:
@@ -896,27 +796,17 @@ async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters
// The results of this implementation are very close to correct, but not quite perfect
#[node_macro::node(category("Raster: Adjustment"))]
async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
async fn vibrance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
vibrance: SignedPercentage,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let vibrance = vibrance as f32 / 100.;
// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.
// So this scales the 0% to -50% range to 0% to -100%.
@@ -963,7 +853,7 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)
}
});
input
image
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -1196,23 +1086,14 @@ impl DomainWarpType {
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
async fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
monochrome: bool,
#[default(40.)]
@@ -1270,8 +1151,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
// Display-only properties (not used within the node)
_output_channel: RedGreenBlue,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let color = color.to_gamma_srgb();
let (r, g, b, a) = color.components();
@@ -1296,7 +1176,7 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
color.to_linear_srgb()
});
input
image
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -1356,24 +1236,15 @@ impl core::fmt::Display for SelectiveColorChoice {
//
// Algorithm based on:
// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html
#[node_macro::node(category("Raster: Adjustment"))]
async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))]
async fn selective_color<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
mode: RelativeAbsolute,
#[name("(Reds) Cyan")] r_c: f64,
#[name("(Reds) Magenta")] r_m: f64,
@@ -1413,8 +1284,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
#[name("(Blacks) Black")] k_k: f64,
_colors: SelectiveColorChoice,
) -> T {
let mut input = image.eval(footprint).await;
input.adjust(|color| {
image.adjust(|color| {
let color = color.to_gamma_srgb();
let (r, g, b, a) = color.components();
@@ -1488,7 +1358,7 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
color.to_linear_srgb()
});
input
image
}
pub(super) trait MultiplyAlpha {
@@ -1534,28 +1404,18 @@ where
// https://www.axiomx.com/posterize.htm
// This algorithm produces fully accurate output in relation to the industry standard.
#[node_macro::node(category("Raster: Adjustment"))]
async fn posterize<F: 'n + Send, T: Adjust<Color>>(
async fn posterize<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
#[default(4)]
#[min(2.)]
levels: u32,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let color = color.to_gamma_srgb();
@@ -1577,30 +1437,20 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
// Algorithm based on:
// https://geraldbakker.nl/psnumbers/exposure.html
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
async fn exposure<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
mut input: T,
exposure: f64,
offset: f64,
#[default(1.)]
#[range((0.01, 10.))]
gamma_correction: f64,
) -> T {
let mut input = input.eval(footprint).await;
input.adjust(|color| {
let adjusted = color
// Exposure
@@ -1619,7 +1469,7 @@ const WINDOW_SIZE: usize = 1024;
#[cfg(feature = "alloc")]
#[node_macro::node(category(""))]
fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
fn generate_curves<C: Channel + super::Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
use bezier_rs::{Bezier, TValue};
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
@@ -1660,31 +1510,21 @@ fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementa
#[cfg(feature = "alloc")]
#[node_macro::node(category("Raster: Adjustment"))]
async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
fn color_overlay<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
(),
(),
(),
Footprint,
Color,
ImageFrameTable<Color>,
GradientStops,
)]
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
mut image: T,
#[default(Color::BLACK)] color: Color,
blend_mode: BlendMode,
#[default(100.)] opacity: Percentage,
) -> T {
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
let mut input = image.eval(footprint).await;
input.adjust(|pixel| {
image.adjust(|pixel| {
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
@@ -1693,7 +1533,7 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
});
input
image
}
// #[cfg(feature = "alloc")]
@@ -1702,10 +1542,11 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
// #[cfg(feature = "alloc")]
// mod index_node {
// use crate::raster::{Color, ImageFrame};
// use crate::Ctx;
// #[node_macro::node(category(""))]
// pub fn index<T: Default + Clone>(
// _: (),
// _: impl Ctx,
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
// #[widget(ParsedWidgetOverride::Hidden)]
// input: Vec<T>,
@@ -1752,7 +1593,7 @@ mod test {
// 100% of the output should come from the multiplied value
let opacity = 100_f64;
let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await;
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
let result = result.one_item();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)

View File

@@ -1,5 +1,5 @@
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
use super::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
use dyn_any::DynAny;
#[cfg(feature = "serde")]
@@ -257,6 +257,11 @@ impl RGBMut for Color {
self.blue = blue;
}
}
impl AlphaMut for Color {
fn set_alpha(&mut self, value: Self::AlphaChannel) {
self.alpha = value;
}
}
impl Pixel for Color {
#[cfg(not(target_arch = "spirv"))]