|
1 | 1 | #![allow(clippy::too_many_arguments)] |
2 | 2 |
|
| 3 | +use crate::adjust::Adjust; |
3 | 4 | use crate::cubic_spline::CubicSplines; |
4 | 5 | use dyn_any::DynAny; |
5 | | -use graphene_core::Node; |
6 | | -use graphene_core::blending::BlendMode; |
7 | 6 | use graphene_core::color::Color; |
8 | | -use graphene_core::color::Pixel; |
9 | 7 | use graphene_core::context::Ctx; |
10 | 8 | use graphene_core::gradient::GradientStops; |
11 | | -use graphene_core::raster::image::Image; |
12 | | -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; |
| 9 | +use graphene_core::raster_types::{CPU, RasterDataTable}; |
13 | 10 | use graphene_core::registry::types::{Angle, Percentage, SignedPercentage}; |
14 | | -use std::cmp::Ordering; |
15 | 11 | use std::fmt::Debug; |
16 | 12 |
|
17 | 13 | // TODO: Implement the following: |
@@ -137,10 +133,10 @@ fn make_opaque<T: Adjust<Color>>( |
137 | 133 | fn brightness_contrast<T: Adjust<Color>>( |
138 | 134 | _: impl Ctx, |
139 | 135 | #[implementations( |
140 | | - Color, |
141 | | - RasterDataTable<CPU>, |
142 | | - GradientStops, |
143 | | -)] |
| 136 | + Color, |
| 137 | + RasterDataTable<CPU>, |
| 138 | + GradientStops, |
| 139 | + )] |
144 | 140 | mut input: T, |
145 | 141 | brightness: SignedPercentage, |
146 | 142 | contrast: SignedPercentage, |
@@ -447,178 +443,6 @@ async fn threshold<T: Adjust<Color>>( |
447 | 443 | image |
448 | 444 | } |
449 | 445 |
|
450 | | -trait Blend<P: Pixel> { |
451 | | - fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; |
452 | | -} |
453 | | -impl Blend<Color> for Color { |
454 | | - fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { |
455 | | - blend_fn(*self, *under) |
456 | | - } |
457 | | -} |
458 | | -impl Blend<Color> for Option<Color> { |
459 | | - fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { |
460 | | - match (self, under) { |
461 | | - (Some(a), Some(b)) => Some(blend_fn(*a, *b)), |
462 | | - (a, None) => *a, |
463 | | - (None, b) => *b, |
464 | | - } |
465 | | - } |
466 | | -} |
467 | | -impl Blend<Color> for RasterDataTable<CPU> { |
468 | | - fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { |
469 | | - let mut result_table = self.clone(); |
470 | | - |
471 | | - for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) { |
472 | | - let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); |
473 | | - |
474 | | - *over.instance = Raster::new_cpu(Image { |
475 | | - data, |
476 | | - width: over.instance.width, |
477 | | - height: over.instance.height, |
478 | | - base64_string: None, |
479 | | - }); |
480 | | - } |
481 | | - |
482 | | - result_table |
483 | | - } |
484 | | -} |
485 | | -impl Blend<Color> for GradientStops { |
486 | | - fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { |
487 | | - let mut combined_stops = self.iter().map(|(position, _)| position).chain(under.iter().map(|(position, _)| position)).collect::<Vec<_>>(); |
488 | | - combined_stops.dedup_by(|&mut a, &mut b| (a - b).abs() < 1e-6); |
489 | | - combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); |
490 | | - |
491 | | - let stops = combined_stops |
492 | | - .into_iter() |
493 | | - .map(|&position| { |
494 | | - let over_color = self.evaluate(position); |
495 | | - let under_color = under.evaluate(position); |
496 | | - let color = blend_fn(over_color, under_color); |
497 | | - (position, color) |
498 | | - }) |
499 | | - .collect::<Vec<_>>(); |
500 | | - |
501 | | - GradientStops::new(stops) |
502 | | - } |
503 | | -} |
504 | | - |
505 | | -#[node_macro::node(category("Raster"))] |
506 | | -async fn blend<T: Blend<Color> + Send>( |
507 | | - _: impl Ctx, |
508 | | - #[implementations( |
509 | | - Color, |
510 | | - RasterDataTable<CPU>, |
511 | | - GradientStops, |
512 | | - )] |
513 | | - over: T, |
514 | | - #[expose] |
515 | | - #[implementations( |
516 | | - Color, |
517 | | - RasterDataTable<CPU>, |
518 | | - GradientStops, |
519 | | - )] |
520 | | - under: T, |
521 | | - blend_mode: BlendMode, |
522 | | - #[default(100.)] opacity: Percentage, |
523 | | -) -> T { |
524 | | - over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) |
525 | | -} |
526 | | - |
527 | | -#[node_macro::node(category(""), skip_impl)] |
528 | | -fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color |
529 | | -where |
530 | | - BlendModeNode: Node<'n, (), Output = BlendMode> + 'n, |
531 | | - OpacityNode: Node<'n, (), Output = Percentage> + 'n, |
532 | | -{ |
533 | | - let blend_mode = blend_mode.eval(()); |
534 | | - let opacity = opacity.eval(()); |
535 | | - blend_colors(input.0, input.1, blend_mode, opacity / 100.) |
536 | | -} |
537 | | - |
538 | | -pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendMode) -> Color { |
539 | | - match blend_mode { |
540 | | - // Normal group |
541 | | - BlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal), |
542 | | - // Darken group |
543 | | - BlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken), |
544 | | - BlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply), |
545 | | - BlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn), |
546 | | - BlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn), |
547 | | - BlendMode::DarkerColor => background.blend_darker_color(foreground), |
548 | | - // Lighten group |
549 | | - BlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten), |
550 | | - BlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen), |
551 | | - BlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge), |
552 | | - BlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge), |
553 | | - BlendMode::LighterColor => background.blend_lighter_color(foreground), |
554 | | - // Contrast group |
555 | | - BlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight), |
556 | | - BlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight), |
557 | | - BlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight), |
558 | | - BlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light), |
559 | | - BlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light), |
560 | | - BlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light), |
561 | | - BlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix), |
562 | | - // Inversion group |
563 | | - BlendMode::Difference => background.blend_rgb(foreground, Color::blend_difference), |
564 | | - BlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion), |
565 | | - BlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract), |
566 | | - BlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide), |
567 | | - // Component group |
568 | | - BlendMode::Hue => background.blend_hue(foreground), |
569 | | - BlendMode::Saturation => background.blend_saturation(foreground), |
570 | | - BlendMode::Color => background.blend_color(foreground), |
571 | | - BlendMode::Luminosity => background.blend_luminosity(foreground), |
572 | | - // Other utility blend modes (hidden from the normal list) - do not have alpha blend |
573 | | - _ => panic!("Used blend mode without alpha blend"), |
574 | | - } |
575 | | -} |
576 | | - |
577 | | -trait Adjust<P> { |
578 | | - fn adjust(&mut self, map_fn: impl Fn(&P) -> P); |
579 | | -} |
580 | | -impl Adjust<Color> for Color { |
581 | | - fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { |
582 | | - *self = map_fn(self); |
583 | | - } |
584 | | -} |
585 | | -impl Adjust<Color> for Option<Color> { |
586 | | - fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { |
587 | | - if let Some(v) = self { |
588 | | - *v = map_fn(v) |
589 | | - } |
590 | | - } |
591 | | -} |
592 | | -impl Adjust<Color> for GradientStops { |
593 | | - fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { |
594 | | - for (_pos, c) in self.iter_mut() { |
595 | | - *c = map_fn(c); |
596 | | - } |
597 | | - } |
598 | | -} |
599 | | -impl Adjust<Color> for RasterDataTable<CPU> { |
600 | | - fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { |
601 | | - for instance in self.instance_mut_iter() { |
602 | | - for c in instance.instance.data_mut().data.iter_mut() { |
603 | | - *c = map_fn(c); |
604 | | - } |
605 | | - } |
606 | | - } |
607 | | -} |
608 | | - |
609 | | -#[inline(always)] |
610 | | -pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f64) -> Color { |
611 | | - let target_color = match blend_mode { |
612 | | - // Other utility blend modes (hidden from the normal list) - do not have alpha blend |
613 | | - BlendMode::Erase => return background.alpha_subtract(foreground), |
614 | | - BlendMode::Restore => return background.alpha_add(foreground), |
615 | | - BlendMode::MultiplyAlpha => return background.alpha_multiply(foreground), |
616 | | - blend_mode => apply_blend_mode(foreground, background, blend_mode), |
617 | | - }; |
618 | | - |
619 | | - background.alpha_blend(target_color.to_associated_alpha(opacity as f32)) |
620 | | -} |
621 | | - |
622 | 446 | // Aims for interoperable compatibility with: |
623 | 447 | // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map |
624 | 448 | // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) |
@@ -1146,56 +970,3 @@ async fn exposure<T: Adjust<Color>>( |
1146 | 970 | }); |
1147 | 971 | input |
1148 | 972 | } |
1149 | | - |
1150 | | -#[node_macro::node(category("Raster: Adjustment"))] |
1151 | | -fn color_overlay<T: Adjust<Color>>( |
1152 | | - _: impl Ctx, |
1153 | | - #[implementations( |
1154 | | - Color, |
1155 | | - RasterDataTable<CPU>, |
1156 | | - GradientStops, |
1157 | | - )] |
1158 | | - mut image: T, |
1159 | | - #[default(Color::BLACK)] color: Color, |
1160 | | - blend_mode: BlendMode, |
1161 | | - #[default(100.)] opacity: Percentage, |
1162 | | -) -> T { |
1163 | | - let opacity = (opacity as f32 / 100.).clamp(0., 1.); |
1164 | | - |
1165 | | - image.adjust(|pixel| { |
1166 | | - let image = pixel.map_rgb(|channel| channel * (1. - opacity)); |
1167 | | - |
1168 | | - // The apply blend mode function divides rgb by the alpha channel for the background. This undoes that. |
1169 | | - let associated_pixel = Color::from_rgbaf32_unchecked(pixel.r() * pixel.a(), pixel.g() * pixel.a(), pixel.b() * pixel.a(), pixel.a()); |
1170 | | - let overlay = apply_blend_mode(color, associated_pixel, blend_mode).map_rgb(|channel| channel * opacity); |
1171 | | - |
1172 | | - Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a()) |
1173 | | - }); |
1174 | | - image |
1175 | | -} |
1176 | | - |
1177 | | -#[cfg(test)] |
1178 | | -mod test { |
1179 | | - use graphene_core::blending::BlendMode; |
1180 | | - use graphene_core::color::Color; |
1181 | | - use graphene_core::raster::image::Image; |
1182 | | - use graphene_core::raster_types::{Raster, RasterDataTable}; |
1183 | | - |
1184 | | - #[tokio::test] |
1185 | | - async fn color_overlay_multiply() { |
1186 | | - let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4); |
1187 | | - let image = Image::new(1, 1, image_color); |
1188 | | - |
1189 | | - // Color { red: 0., green: 1., blue: 0., alpha: 1. } |
1190 | | - let overlay_color = Color::GREEN; |
1191 | | - |
1192 | | - // 100% of the output should come from the multiplied value |
1193 | | - let opacity = 100_f64; |
1194 | | - |
1195 | | - let result = super::color_overlay((), RasterDataTable::new(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); |
1196 | | - let result = result.instance_ref_iter().next().unwrap().instance; |
1197 | | - |
1198 | | - // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) |
1199 | | - assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); |
1200 | | - } |
1201 | | -} |
0 commit comments