mirror of
https://github.com/penpot/penpot.git
synced 2026-09-18 18:06:14 +00:00
🐛 Fix stroke weight visually different with different levels of zoom
This commit is contained in:
parent
b881e36875
commit
6a84215911
@ -27,8 +27,8 @@ fn draw_stroke_on_rect(
|
|||||||
// - The same rect if it's a center stroke
|
// - The same rect if it's a center stroke
|
||||||
// - A bigger rect if it's an outer stroke
|
// - A bigger rect if it's an outer stroke
|
||||||
// - A smaller rect if it's an outer stroke
|
// - A smaller rect if it's an outer stroke
|
||||||
let stroke_rect = stroke.outer_rect(rect);
|
let stroke_rect = stroke.aligned_rect(rect, scale);
|
||||||
let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias);
|
let mut paint = stroke.to_paint(selrect, svg_attrs, antialias);
|
||||||
|
|
||||||
// Apply both blur and shadow filters if present, composing them if necessary.
|
// Apply both blur and shadow filters if present, composing them if necessary.
|
||||||
let filter = compose_filters(blur, shadow);
|
let filter = compose_filters(blur, shadow);
|
||||||
@ -63,8 +63,8 @@ fn draw_stroke_on_circle(
|
|||||||
// - The same oval if it's a center stroke
|
// - The same oval if it's a center stroke
|
||||||
// - A bigger oval if it's an outer stroke
|
// - A bigger oval if it's an outer stroke
|
||||||
// - A smaller oval if it's an outer stroke
|
// - A smaller oval if it's an outer stroke
|
||||||
let stroke_rect = stroke.outer_rect(rect);
|
let stroke_rect = stroke.aligned_rect(rect, scale);
|
||||||
let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias);
|
let mut paint = stroke.to_paint(selrect, svg_attrs, antialias);
|
||||||
|
|
||||||
// Apply both blur and shadow filters if present, composing them if necessary.
|
// Apply both blur and shadow filters if present, composing them if necessary.
|
||||||
let filter = compose_filters(blur, shadow);
|
let filter = compose_filters(blur, shadow);
|
||||||
@ -131,7 +131,6 @@ pub fn draw_stroke_on_path(
|
|||||||
selrect: &Rect,
|
selrect: &Rect,
|
||||||
path_transform: Option<&Matrix>,
|
path_transform: Option<&Matrix>,
|
||||||
svg_attrs: Option<&SvgAttrs>,
|
svg_attrs: Option<&SvgAttrs>,
|
||||||
scale: f32,
|
|
||||||
shadow: Option<&ImageFilter>,
|
shadow: Option<&ImageFilter>,
|
||||||
blur: Option<&ImageFilter>,
|
blur: Option<&ImageFilter>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
@ -142,7 +141,7 @@ pub fn draw_stroke_on_path(
|
|||||||
let is_open = path.is_open();
|
let is_open = path.is_open();
|
||||||
|
|
||||||
let mut paint: skia_safe::Handle<_> =
|
let mut paint: skia_safe::Handle<_> =
|
||||||
stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias);
|
stroke.to_stroked_paint(is_open, selrect, svg_attrs, antialias);
|
||||||
|
|
||||||
let filter = compose_filters(blur, shadow);
|
let filter = compose_filters(blur, shadow);
|
||||||
paint.set_image_filter(filter);
|
paint.set_image_filter(filter);
|
||||||
@ -166,7 +165,6 @@ pub fn draw_stroke_on_path(
|
|||||||
canvas,
|
canvas,
|
||||||
is_open,
|
is_open,
|
||||||
svg_attrs,
|
svg_attrs,
|
||||||
scale,
|
|
||||||
blur,
|
blur,
|
||||||
antialias,
|
antialias,
|
||||||
);
|
);
|
||||||
@ -218,7 +216,6 @@ fn handle_stroke_caps(
|
|||||||
canvas: &skia::Canvas,
|
canvas: &skia::Canvas,
|
||||||
is_open: bool,
|
is_open: bool,
|
||||||
svg_attrs: Option<&SvgAttrs>,
|
svg_attrs: Option<&SvgAttrs>,
|
||||||
scale: f32,
|
|
||||||
blur: Option<&ImageFilter>,
|
blur: Option<&ImageFilter>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
@ -233,8 +230,7 @@ fn handle_stroke_caps(
|
|||||||
let first_point = points.first().unwrap();
|
let first_point = points.first().unwrap();
|
||||||
let last_point = points.last().unwrap();
|
let last_point = points.last().unwrap();
|
||||||
|
|
||||||
let mut paint_stroke =
|
let mut paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, antialias);
|
||||||
stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias);
|
|
||||||
|
|
||||||
if let Some(filter) = blur {
|
if let Some(filter) = blur {
|
||||||
paint_stroke.set_image_filter(filter.clone());
|
paint_stroke.set_image_filter(filter.clone());
|
||||||
@ -405,7 +401,7 @@ fn draw_image_stroke_in_container(
|
|||||||
|
|
||||||
// Draw the stroke based on the shape type, we are using this stroke as
|
// Draw the stroke based on the shape type, we are using this stroke as
|
||||||
// a "selector" of the area of the image we want to show.
|
// a "selector" of the area of the image we want to show.
|
||||||
let outer_rect = stroke.outer_rect(container);
|
let outer_rect = stroke.aligned_rect(container, scale);
|
||||||
|
|
||||||
match &shape.shape_type {
|
match &shape.shape_type {
|
||||||
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
|
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
|
||||||
@ -450,8 +446,7 @@ fn draw_image_stroke_in_container(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let is_open = p.is_open();
|
let is_open = p.is_open();
|
||||||
let mut paint =
|
let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, antialias);
|
||||||
stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, scale, antialias);
|
|
||||||
canvas.draw_path(&path, &paint);
|
canvas.draw_path(&path, &paint);
|
||||||
if stroke.render_kind(is_open) == StrokeKind::Outer {
|
if stroke.render_kind(is_open) == StrokeKind::Outer {
|
||||||
// Small extra inner stroke to overlap with the fill
|
// Small extra inner stroke to overlap with the fill
|
||||||
@ -466,7 +461,6 @@ fn draw_image_stroke_in_container(
|
|||||||
canvas,
|
canvas,
|
||||||
is_open,
|
is_open,
|
||||||
svg_attrs,
|
svg_attrs,
|
||||||
scale,
|
|
||||||
shape.image_filter(1.).as_ref(),
|
shape.image_filter(1.).as_ref(),
|
||||||
antialias,
|
antialias,
|
||||||
);
|
);
|
||||||
@ -662,7 +656,6 @@ fn render_internal(
|
|||||||
&selrect,
|
&selrect,
|
||||||
path_transform.as_ref(),
|
path_transform.as_ref(),
|
||||||
svg_attrs,
|
svg_attrs,
|
||||||
scale,
|
|
||||||
shadow,
|
shadow,
|
||||||
shape.image_filter(1.).as_ref(),
|
shape.image_filter(1.).as_ref(),
|
||||||
antialias,
|
antialias,
|
||||||
@ -685,14 +678,13 @@ pub fn render_text_paths(
|
|||||||
shadow: Option<&ImageFilter>,
|
shadow: Option<&ImageFilter>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
let scale = render_state.get_scale();
|
|
||||||
let canvas = render_state
|
let canvas = render_state
|
||||||
.surfaces
|
.surfaces
|
||||||
.canvas_and_mark_dirty(surface_id.unwrap_or(SurfaceId::Strokes));
|
.canvas_and_mark_dirty(surface_id.unwrap_or(SurfaceId::Strokes));
|
||||||
let selrect = &shape.selrect;
|
let selrect = &shape.selrect;
|
||||||
let svg_attrs = shape.svg_attrs.as_ref();
|
let svg_attrs = shape.svg_attrs.as_ref();
|
||||||
let mut paint: skia_safe::Handle<_> =
|
let mut paint: skia_safe::Handle<_> =
|
||||||
stroke.to_text_stroked_paint(false, selrect, svg_attrs, scale, antialias);
|
stroke.to_text_stroked_paint(false, selrect, svg_attrs, antialias);
|
||||||
|
|
||||||
if let Some(filter) = shadow {
|
if let Some(filter) = shadow {
|
||||||
paint.set_image_filter(filter.clone());
|
paint.set_image_filter(filter.clone());
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::math::is_close_to;
|
||||||
use crate::shapes::fills::{Fill, SolidColor};
|
use crate::shapes::fills::{Fill, SolidColor};
|
||||||
use skia_safe::{self as skia, Rect};
|
use skia_safe::{self as skia, Rect};
|
||||||
|
|
||||||
@ -144,6 +145,15 @@ impl Stroke {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn aligned_rect(&self, rect: &Rect, scale: f32) -> Rect {
|
||||||
|
let stroke_rect = self.outer_rect(rect);
|
||||||
|
if self.kind != StrokeKind::Center {
|
||||||
|
return stroke_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
align_rect_to_half_pixel(&stroke_rect, self.width, scale)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn outer_corners(&self, corners: &Corners) -> Corners {
|
pub fn outer_corners(&self, corners: &Corners) -> Corners {
|
||||||
let offset = match self.kind {
|
let offset = match self.kind {
|
||||||
StrokeKind::Center => 0.0,
|
StrokeKind::Center => 0.0,
|
||||||
@ -162,7 +172,6 @@ impl Stroke {
|
|||||||
&self,
|
&self,
|
||||||
rect: &Rect,
|
rect: &Rect,
|
||||||
svg_attrs: Option<&SvgAttrs>,
|
svg_attrs: Option<&SvgAttrs>,
|
||||||
scale: f32,
|
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) -> skia::Paint {
|
) -> skia::Paint {
|
||||||
let mut paint = self.fill.to_paint(rect, antialias);
|
let mut paint = self.fill.to_paint(rect, antialias);
|
||||||
@ -171,7 +180,7 @@ impl Stroke {
|
|||||||
let width = match self.kind {
|
let width = match self.kind {
|
||||||
StrokeKind::Inner => self.width,
|
StrokeKind::Inner => self.width,
|
||||||
StrokeKind::Center => self.width,
|
StrokeKind::Center => self.width,
|
||||||
StrokeKind::Outer => self.width + (1. / scale),
|
StrokeKind::Outer => self.width,
|
||||||
};
|
};
|
||||||
|
|
||||||
paint.set_stroke_width(width);
|
paint.set_stroke_width(width);
|
||||||
@ -230,10 +239,9 @@ impl Stroke {
|
|||||||
is_open: bool,
|
is_open: bool,
|
||||||
rect: &Rect,
|
rect: &Rect,
|
||||||
svg_attrs: Option<&SvgAttrs>,
|
svg_attrs: Option<&SvgAttrs>,
|
||||||
scale: f32,
|
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) -> skia::Paint {
|
) -> skia::Paint {
|
||||||
let mut paint = self.to_paint(rect, svg_attrs, scale, antialias);
|
let mut paint = self.to_paint(rect, svg_attrs, antialias);
|
||||||
match self.render_kind(is_open) {
|
match self.render_kind(is_open) {
|
||||||
StrokeKind::Inner => {
|
StrokeKind::Inner => {
|
||||||
paint.set_stroke_width(2. * paint.stroke_width());
|
paint.set_stroke_width(2. * paint.stroke_width());
|
||||||
@ -254,10 +262,9 @@ impl Stroke {
|
|||||||
is_open: bool,
|
is_open: bool,
|
||||||
rect: &Rect,
|
rect: &Rect,
|
||||||
svg_attrs: Option<&SvgAttrs>,
|
svg_attrs: Option<&SvgAttrs>,
|
||||||
scale: f32,
|
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) -> skia::Paint {
|
) -> skia::Paint {
|
||||||
let mut paint = self.to_paint(rect, svg_attrs, scale, antialias);
|
let mut paint = self.to_paint(rect, svg_attrs, antialias);
|
||||||
match self.render_kind(is_open) {
|
match self.render_kind(is_open) {
|
||||||
StrokeKind::Inner => {
|
StrokeKind::Inner => {
|
||||||
paint.set_stroke_width(2. * paint.stroke_width());
|
paint.set_stroke_width(2. * paint.stroke_width());
|
||||||
@ -284,6 +291,38 @@ impl Stroke {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn align_rect_to_half_pixel(rect: &Rect, stroke_width: f32, scale: f32) -> Rect {
|
||||||
|
if scale <= 0.0 {
|
||||||
|
return *rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke_pixels = stroke_width * scale;
|
||||||
|
let stroke_pixels_rounded = stroke_pixels.round();
|
||||||
|
if !is_close_to(stroke_pixels, stroke_pixels_rounded) {
|
||||||
|
return *rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stroke_pixels_rounded as i32) % 2 == 0 {
|
||||||
|
return *rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_px = rect.left * scale;
|
||||||
|
let top_px = rect.top * scale;
|
||||||
|
let target_frac = 0.5;
|
||||||
|
let dx_px = target_frac - (left_px - left_px.floor());
|
||||||
|
let dy_px = target_frac - (top_px - top_px.floor());
|
||||||
|
|
||||||
|
if is_close_to(dx_px, 0.0) && is_close_to(dy_px, 0.0) {
|
||||||
|
return *rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect::from_xywh(
|
||||||
|
rect.left + (dx_px / scale),
|
||||||
|
rect.top + (dy_px / scale),
|
||||||
|
rect.width(),
|
||||||
|
rect.height(),
|
||||||
|
)
|
||||||
|
}
|
||||||
fn cap_margin_for_cap(cap: Option<StrokeCap>, width: f32) -> f32 {
|
fn cap_margin_for_cap(cap: Option<StrokeCap>, width: f32) -> f32 {
|
||||||
match cap {
|
match cap {
|
||||||
Some(StrokeCap::LineArrow)
|
Some(StrokeCap::LineArrow)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user