mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
New nodes: 'Gradient Type' and 'Spread Method', and add Gradient tool support for controlling these nodes (#4084)
* Use 'Transform', 'Gradient Type', and 'Spread Method' nodes for table gradients * Add gradient widget to the tool's control bar and update where the two swap buttons go * Fix gradient rendering * Format * Code review
This commit is contained in:
@@ -3,16 +3,15 @@ use crate::application_io::PlatformEditorApi;
|
||||
use crate::proto::{Any as DAny, FutureAny};
|
||||
use brush_nodes::brush_cache::BrushCache;
|
||||
use brush_nodes::brush_stroke::BrushStroke;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::table::Table;
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_TRANSFORM, CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
||||
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
||||
use dyn_any::DynAny;
|
||||
pub use dyn_any::StaticType;
|
||||
use glam::{Affine2, Vec2};
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graphic_types::raster_types::{CPU, Image, Raster};
|
||||
use graphic_types::vector_types::gradient::GRADIENT_TABLE_DEFAULT_SCALE;
|
||||
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
||||
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
||||
use graphic_types::{Graphic, Vector};
|
||||
@@ -119,9 +118,7 @@ macro_rules! tagged_value {
|
||||
x if x == TypeId::of::<()>() => TaggedValue::None,
|
||||
// Table-wrapped types need a single-item default with the element's default, not an empty table
|
||||
x if x == TypeId::of::<Table<Color>>() => TaggedValue::Color(Table::new_from_element(Color::default())),
|
||||
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_row(
|
||||
TableRow::new_from_element(GradientStops::default()).with_attribute(ATTR_TRANSFORM, DAffine2::from_scale(DVec2::splat(GRADIENT_TABLE_DEFAULT_SCALE))),
|
||||
)),
|
||||
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())),
|
||||
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
||||
_ => return None,
|
||||
})
|
||||
@@ -256,6 +253,7 @@ tagged_value! {
|
||||
PaintOrder(vector::style::PaintOrder),
|
||||
FillType(vector::style::FillType),
|
||||
GradientType(vector::style::GradientType),
|
||||
GradientSpreadMethod(vector::style::GradientSpreadMethod),
|
||||
ReferencePoint(vector::ReferencePoint),
|
||||
CentroidType(vector::misc::CentroidType),
|
||||
BooleanOperation(vector::misc::BooleanOperation),
|
||||
|
||||
@@ -1705,9 +1705,11 @@ impl Render for Table<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
|
||||
render.leaf_tag("polyline", |attributes| {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
||||
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
||||
// 1e7 stays under that limit while still being far larger than any practical document extent.
|
||||
const MAX: f64 = 1e7;
|
||||
attributes.push("points", format!("{MAX},{MAX} -{MAX},{MAX} -{MAX},-{MAX} {MAX},-{MAX}"));
|
||||
|
||||
attributes.push("fill", format!("#{}", color.to_rgb_hex_srgb_from_gamma()));
|
||||
if color.a() < 1. {
|
||||
@@ -1779,9 +1781,11 @@ impl Render for Table<GradientStops> {
|
||||
attributes.push("width", size.x.to_string());
|
||||
attributes.push("height", size.y.to_string());
|
||||
} else {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
||||
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
||||
// 1e7 stays under that limit while still being far larger than any practical document extent.
|
||||
const MAX: f64 = 1e7;
|
||||
attributes.push("points", format!("{MAX},{MAX} -{MAX},{MAX} -{MAX},-{MAX} {MAX},-{MAX}"));
|
||||
}
|
||||
|
||||
let mut stop_string = String::new();
|
||||
|
||||
@@ -2,10 +2,6 @@ use core_types::{Color, render_complexity::RenderComplexity};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// Default scale applied to a freshly-created `Table<GradientStops>` item's transform.
|
||||
/// Places the unit gradient line (the +X unit vector in local space) inside a 100×100 document-space box.
|
||||
pub const GRADIENT_TABLE_DEFAULT_SCALE: f64 = 100.;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod vector;
|
||||
|
||||
// Re-export commonly used types at the crate root
|
||||
pub use core_types as gcore;
|
||||
pub use gradient::{GradientStop, GradientStops, GradientType};
|
||||
pub use gradient::{GradientSpreadMethod, GradientStop, GradientStops, GradientType};
|
||||
pub use math::{QuadExt, RectExt};
|
||||
pub use subpath::Subpath;
|
||||
pub use vector::Vector;
|
||||
|
||||
@@ -230,11 +230,17 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
|
||||
|
||||
scene.append(child, Some(footprint_transform_vello));
|
||||
|
||||
// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport
|
||||
// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail
|
||||
// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport.
|
||||
// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail.
|
||||
//
|
||||
// `!is_finite()` rather than `== f32::INFINITY`: `scene.append` composes the child's `Affine::scale(INFINITY)` with
|
||||
// the viewport rotation, leaving `matrix[0] = cos(θ) * INFINITY`. In the (90°, 270°) tilt range cos is negative so
|
||||
// the result is `-INFINITY`, which the old equality check missed; Vello then rasterized a unit rect with non-finite
|
||||
// vertices, dropping the gradient and tanking performance. `!is_finite()` also covers NaN as a guard against future
|
||||
// code paths where `matrix[0]` could land on `0 * INFINITY`.
|
||||
let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(physical_resolution.x as f64, physical_resolution.y as f64);
|
||||
for transform in scene.encoding_mut().transforms.iter_mut() {
|
||||
if transform.matrix[0] == f32::INFINITY {
|
||||
if !transform.matrix[0].is_finite() {
|
||||
*transform = vello_encoding::Transform::from_kurbo(&scaled_infinite_transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,6 +863,24 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) ->
|
||||
gradient
|
||||
}
|
||||
|
||||
/// Sets the type (linear or radial) of each gradient in the input table.
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn gradient_type(_: impl Ctx, mut gradient: Table<GradientStops>, gradient_type: vector_types::GradientType) -> Table<GradientStops> {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientType>(core_types::ATTR_GRADIENT_TYPE) {
|
||||
*value = gradient_type;
|
||||
}
|
||||
gradient
|
||||
}
|
||||
|
||||
/// Sets how each gradient in the input table extends past its endpoints: Pad, Reflect, or Repeat.
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn spread_method(_: impl Ctx, mut gradient: Table<GradientStops>, spread_method: vector_types::GradientSpreadMethod) -> Table<GradientStops> {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientSpreadMethod>(core_types::ATTR_SPREAD_METHOD) {
|
||||
*value = spread_method;
|
||||
}
|
||||
gradient
|
||||
}
|
||||
|
||||
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> {
|
||||
|
||||
Reference in New Issue
Block a user