Blend modes (#252)

* Add backend for selecting layer blend mode

* Change dropdown input to support callback on change

* Add debug messages

* Fix canvas update for blend-modes

* Finish up and polish blend modes implementations

* Add changes from code review

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
George Atkinson
2021-07-23 10:21:07 -07:00
committed by GitHub
co-authored by Keavon Chambers Dennis Kobert
parent 63e3b5c604
commit 8f9168bfe5
18 changed files with 348 additions and 69 deletions
+61 -2
View File
@@ -24,12 +24,15 @@ use crate::LayerId;
pub use folder::Folder;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
pub trait LayerData {
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle);
}
// TODO: Rename this `LayerDataType` to not be plural in a separate commit (together with `enum ToolOptions`)
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum LayerDataTypes {
Folder(Folder),
@@ -40,6 +43,48 @@ pub enum LayerDataTypes {
Shape(Shape),
}
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
pub enum BlendMode {
Normal,
Multiply,
Darken,
ColorBurn,
Screen,
Lighten,
ColorDodge,
Overlay,
SoftLight,
HardLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl BlendMode {
fn to_svg_style_name(&self) -> &str {
match self {
BlendMode::Normal => "normal",
BlendMode::Multiply => "multiply",
BlendMode::Darken => "darken",
BlendMode::ColorBurn => "color-burn",
BlendMode::Screen => "screen",
BlendMode::Lighten => "lighten",
BlendMode::ColorDodge => "color-dodge",
BlendMode::Overlay => "overlay",
BlendMode::SoftLight => "soft-light",
BlendMode::HardLight => "hard-light",
BlendMode::Difference => "difference",
BlendMode::Exclusion => "exclusion",
BlendMode::Hue => "hue",
BlendMode::Saturation => "saturation",
BlendMode::Color => "color",
BlendMode::Luminosity => "luminosity",
}
}
}
macro_rules! call_render {
($self:ident.render($svg:ident, $transform:ident, $style:ident) { $($variant:ident),* }) => {
match $self {
@@ -56,7 +101,7 @@ macro_rules! call_kurbo_path {
}
macro_rules! call_intersects_quad {
($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => {
($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => {
match $self {
$(Self::$variant(x) => x.intersects_quad($quad, $path, $intersections, $style)),*
}
@@ -76,6 +121,7 @@ impl LayerDataTypes {
}
}
}
pub fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
call_kurbo_path! {
self.to_kurbo_path(transform, style) {
@@ -125,7 +171,9 @@ pub struct Layer {
pub transform: glam::DAffine2,
pub style: style::PathStyle,
pub cache: String,
pub thumbnail_cache: String,
pub cache_dirty: bool,
pub blend_mode: BlendMode,
}
impl Layer {
@@ -137,7 +185,9 @@ impl Layer {
transform: glam::DAffine2::from_cols_array(&transform),
style,
cache: String::new(),
thumbnail_cache: String::new(),
cache_dirty: true,
blend_mode: BlendMode::Normal,
}
}
@@ -146,8 +196,17 @@ impl Layer {
return "";
}
if self.cache_dirty {
self.thumbnail_cache.clear();
self.data.render(&mut self.thumbnail_cache, self.transform, self.style);
self.cache.clear();
self.data.render(&mut self.cache, self.transform, self.style);
let _ = write!(
self.cache,
r#"<g style="mix-blend-mode: {}">{}</g>"#,
self.blend_mode.to_svg_style_name(),
self.thumbnail_cache.as_str()
);
self.cache_dirty = false;
}
self.cache.as_str()
+3 -3
View File
@@ -1,10 +1,10 @@
use crate::color::Color;
use serde::{Deserialize, Serialize};
const OPACITY_PERCISION: usize = 3;
const OPACITY_PRECISION: usize = 3;
fn format_opacity(name: &str, opacity: f32) -> String {
if (opacity - 1.).abs() > 10f32.powi(-(OPACITY_PERCISION as i32)) {
format!(r#" {}-opacity="{:.percision$}""#, name, opacity, percision = OPACITY_PERCISION)
if (opacity - 1.).abs() > 10f32.powi(-(OPACITY_PRECISION as i32)) {
format!(r#" {}-opacity="{:.precision$}""#, name, opacity, precision = OPACITY_PRECISION)
} else {
String::new()
}