Fix gradient transformation (#588)

* Fix with perfect circle

* Actually fix rotated gradient

* Gradient transform & fix on rotated canvas

* Cleanup & remove logging
This commit is contained in:
0HyperCube 2022-04-20 18:14:33 +01:00 committed by Keavon Chambers
parent bbe94e35cb
commit c7e80180c2
4 changed files with 46 additions and 15 deletions

View File

@ -81,10 +81,12 @@ impl Default for GradientToolFsmState {
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space) /// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> DAffine2 { fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> DAffine2 {
let bounds = layer.aabounding_box().unwrap(); let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY).unwrap();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
document.graphene_document.multiply_transforms(&path[..path.len() - 1]).unwrap() * bound_transform let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
multiplied * bound_transform
} }
/// Contains info on the overlays for a single gradient /// Contains info on the overlays for a single gradient
@ -225,7 +227,7 @@ impl SelectedGradient {
self.gradient.end = mouse; self.gradient.end = mouse;
} }
self.gradient.transform = self.transform.inverse(); self.gradient.transform = self.transform;
let fill = Fill::LinearGradient(self.gradient.clone()); let fill = Fill::LinearGradient(self.gradient.clone());
let path = self.path.clone(); let path = self.path.clone();
responses.push_back(Operation::SetLayerFill { path, fill }.into()); responses.push_back(Operation::SetLayerFill { path, fill }.into());

View File

@ -32,6 +32,10 @@ pub struct ShapeLayer {
impl LayerData for ShapeLayer { impl LayerData for ShapeLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) {
let mut path = self.path.clone(); let mut path = self.path.clone();
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let layer_bounds = [(x0, y0).into(), (x1, y1).into()];
let transform = self.transform(transforms, view_mode); let transform = self.transform(transforms, view_mode);
let inverse = transform.inverse(); let inverse = transform.inverse();
if !inverse.is_finite() { if !inverse.is_finite() {
@ -40,12 +44,20 @@ impl LayerData for ShapeLayer {
} }
path.apply_affine(glam_to_kurbo(transform)); path.apply_affine(glam_to_kurbo(transform));
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let transformed_bounds = [(x0, y0).into(), (x1, y1).into()];
let _ = writeln!(svg, r#"<g transform="matrix("#); let _ = writeln!(svg, r#"<g transform="matrix("#);
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| { inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," })); let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
}); });
let _ = svg.write_str(r#")">"#); let _ = svg.write_str(r#")">"#);
let _ = write!(svg, r#"<path d="{}" {} />"#, path.to_svg(), self.style.render(view_mode, svg_defs)); let _ = write!(
svg,
r#"<path d="{}" {} />"#,
path.to_svg(),
self.style.render(view_mode, svg_defs, transform, layer_bounds, transformed_bounds)
);
let _ = svg.write_str("</g>"); let _ = svg.write_str("</g>");
} }

View File

@ -61,7 +61,11 @@ impl Gradient {
} }
/// Adds the gradient def with the uuid specified /// Adds the gradient def with the uuid specified
fn render_defs(&self, svg_defs: &mut String) { fn render_defs(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) {
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
let updated_transform = multiplied_transform * bound_transform;
let positions = self let positions = self
.positions .positions
.iter() .iter()
@ -69,11 +73,13 @@ impl Gradient {
.map(|(position, color)| format!(r##"<stop offset="{}" stop-color="#{}" />"##, position, color.rgba_hex())) .map(|(position, color)| format!(r##"<stop offset="{}" stop-color="#{}" />"##, position, color.rgba_hex()))
.collect::<String>(); .collect::<String>();
let start = self.transform.inverse().transform_point2(self.start); let mod_gradient = transformed_bound_transform.inverse();
let end = self.transform.inverse().transform_point2(self.end); let mod_points = mod_gradient.inverse() * transformed_bound_transform.inverse() * updated_transform;
let transform = self let start = mod_points.transform_point2(self.start);
.transform let end = mod_points.transform_point2(self.end);
let transform = mod_gradient
.to_cols_array() .to_cols_array()
.iter() .iter()
.enumerate() .enumerate()
@ -122,12 +128,12 @@ impl Fill {
} }
/// Renders the fill, adding necessary defs. /// Renders the fill, adding necessary defs.
pub fn render(&self, svg_defs: &mut String) -> String { pub fn render(&self, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) -> String {
match self { match self {
Self::None => r#" fill="none""#.to_string(), Self::None => r#" fill="none""#.to_string(),
Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill", color.a())), Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill", color.a())),
Self::LinearGradient(gradient) => { Self::LinearGradient(gradient) => {
gradient.render_defs(svg_defs); gradient.render_defs(svg_defs, multiplied_transform, bounds, transformed_bounds);
format!(r##" fill="url('#{}')""##, gradient.uuid) format!(r##" fill="url('#{}')""##, gradient.uuid)
} }
} }
@ -430,10 +436,10 @@ impl PathStyle {
self.stroke = None; self.stroke = None;
} }
pub fn render(&self, view_mode: ViewMode, svg_defs: &mut String) -> String { pub fn render(&self, view_mode: ViewMode, svg_defs: &mut String, multiplied_transform: DAffine2, bounds: [DVec2; 2], transformed_bounds: [DVec2; 2]) -> String {
let fill_attribute = match (view_mode, &self.fill) { let fill_attribute = match (view_mode, &self.fill) {
(ViewMode::Outline, _) => Fill::None.render(svg_defs), (ViewMode::Outline, _) => Fill::None.render(svg_defs, multiplied_transform, bounds, transformed_bounds),
(_, fill) => fill.render(svg_defs), (_, fill) => fill.render(svg_defs, multiplied_transform, bounds, transformed_bounds),
}; };
let stroke_attribute = match (view_mode, &self.stroke) { let stroke_attribute = match (view_mode, &self.stroke) {
(ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(), (ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(),

View File

@ -63,9 +63,20 @@ impl LayerData for TextLayer {
} else { } else {
let mut path = self.to_bez_path(); let mut path = self.to_bez_path();
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let bounds = [(x0, y0).into(), (x1, y1).into()];
path.apply_affine(glam_to_kurbo(transform)); path.apply_affine(glam_to_kurbo(transform));
let _ = write!(svg, r#"<path d="{}" {} />"#, path.to_svg(), self.style.render(view_mode, svg_defs)); let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let transformed_bounds = [(x0, y0).into(), (x1, y1).into()];
let _ = write!(
svg,
r#"<path d="{}" {} />"#,
path.to_svg(),
self.style.render(view_mode, svg_defs, transform, bounds, transformed_bounds)
);
} }
let _ = svg.write_str("</g>"); let _ = svg.write_str("</g>");
} }