Refactor TessellationOptions to expose slider for feathering size (#1408)
The epaint tessellator uses "feathering" to accomplish anti-aliasing. This PS allows you to control the feathering size, i.e. how blurry the edges of epaint shapes are. This changes the interface of Tessellator slightly, and renames some options in TessellationOptions.
This commit is contained in:
parent
a9fd03709e
commit
1387d6e9d6
7 changed files with 115 additions and 107 deletions
|
@ -807,14 +807,16 @@ impl Context {
|
||||||
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
||||||
// it takes to tessellate them, so it is not a worth optimization.
|
// it takes to tessellate them, so it is not a worth optimization.
|
||||||
|
|
||||||
let mut tessellation_options = *self.tessellation_options();
|
let pixels_per_point = self.pixels_per_point();
|
||||||
tessellation_options.pixels_per_point = self.pixels_per_point();
|
let tessellation_options = *self.tessellation_options();
|
||||||
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
|
let font_image_size = self.fonts().font_image_size();
|
||||||
|
|
||||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||||
let clipped_primitives = tessellator::tessellate_shapes(
|
let clipped_primitives = tessellator::tessellate_shapes(
|
||||||
shapes,
|
pixels_per_point,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
self.fonts().font_image_size(),
|
shapes,
|
||||||
|
font_image_size,
|
||||||
);
|
);
|
||||||
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
||||||
clipped_primitives
|
clipped_primitives
|
||||||
|
|
|
@ -139,9 +139,8 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
let epaint::TessellationOptions {
|
let epaint::TessellationOptions {
|
||||||
pixels_per_point: _,
|
feathering,
|
||||||
aa_size: _,
|
feathering_size_in_pixels,
|
||||||
anti_alias,
|
|
||||||
coarse_tessellation_culling,
|
coarse_tessellation_culling,
|
||||||
round_text_to_pixels,
|
round_text_to_pixels,
|
||||||
debug_paint_clip_rects,
|
debug_paint_clip_rects,
|
||||||
|
@ -150,8 +149,15 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||||
bezier_tolerance,
|
bezier_tolerance,
|
||||||
epsilon: _,
|
epsilon: _,
|
||||||
} = self;
|
} = self;
|
||||||
ui.checkbox(anti_alias, "Antialias")
|
|
||||||
|
ui.checkbox(feathering, "Feathering (antialias)")
|
||||||
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
||||||
|
let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0)
|
||||||
|
.smallest_positive(0.1)
|
||||||
|
.logarithmic(true)
|
||||||
|
.text("Feathering size in pixels");
|
||||||
|
ui.add_enabled(*feathering, feathering_slider);
|
||||||
|
|
||||||
ui.add(
|
ui.add(
|
||||||
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
|
|
|
@ -123,7 +123,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
});
|
});
|
||||||
|
|
||||||
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
||||||
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
let mut tessellator = egui::epaint::Tessellator::new(1.0, Default::default());
|
||||||
let mut mesh = egui::epaint::Mesh::default();
|
let mut mesh = egui::epaint::Mesh::default();
|
||||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||||
let font_image_size = fonts.font_image_size();
|
let font_image_size = fonts.font_image_size();
|
||||||
|
|
|
@ -5,6 +5,8 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||||
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||||
|
* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||||
|
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -63,11 +63,15 @@ impl Shadow {
|
||||||
|
|
||||||
use crate::tessellator::*;
|
use crate::tessellator::*;
|
||||||
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
||||||
let mut tessellator = Tessellator::from_options(TessellationOptions {
|
let pixels_per_point = 1.0; // doesn't matter here
|
||||||
aa_size: extrusion,
|
let mut tessellator = Tessellator::new(
|
||||||
anti_alias: true,
|
pixels_per_point,
|
||||||
..Default::default()
|
TessellationOptions {
|
||||||
});
|
feathering: true,
|
||||||
|
feathering_size_in_pixels: extrusion * pixels_per_point,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
tessellator.tessellate_rect(&rect, &mut mesh);
|
||||||
mesh
|
mesh
|
||||||
|
|
|
@ -163,23 +163,17 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open-ended.
|
/// Open-ended.
|
||||||
pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||||
stroke_path(&self.0, PathType::Open, stroke, options, out);
|
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A closed path (returning to the first point).
|
/// A closed path (returning to the first point).
|
||||||
pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||||
stroke_path(&self.0, PathType::Closed, stroke, options, out);
|
stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stroke(
|
pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
|
||||||
&self,
|
stroke_path(feathering, &self.0, path_type, stroke, out);
|
||||||
path_type: PathType,
|
|
||||||
stroke: Stroke,
|
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
|
||||||
) {
|
|
||||||
stroke_path(&self.0, path_type, stroke, options, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path is taken to be closed (i.e. returning to the start again).
|
/// The path is taken to be closed (i.e. returning to the start again).
|
||||||
|
@ -187,8 +181,8 @@ impl Path {
|
||||||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||||
///
|
///
|
||||||
/// The preferred winding order is clockwise.
|
/// The preferred winding order is clockwise.
|
||||||
pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
||||||
fill_closed_path(&mut self.0, color, options, out);
|
fill_closed_path(feathering, &mut self.0, color, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,18 +280,23 @@ pub enum PathType {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct TessellationOptions {
|
pub struct TessellationOptions {
|
||||||
/// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries.
|
/// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing.
|
||||||
pub pixels_per_point: f32,
|
///
|
||||||
|
/// Feathering works by making each edge into a thin gradient into transparency.
|
||||||
/// The size of a pixel (in points), used for anti-aliasing (smoothing of edges).
|
/// The size of this edge is controlled by [`Self::feathering_size_in_pixels`].
|
||||||
/// This is normally the inverse of [`Self::pixels_per_point`],
|
///
|
||||||
/// but you can make it larger if you want more blurry edges.
|
/// This makes shapes appear smoother, but requires more triangles and is therefore slower.
|
||||||
pub aa_size: f32,
|
///
|
||||||
|
|
||||||
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
|
|
||||||
/// This setting does not affect text.
|
/// This setting does not affect text.
|
||||||
|
///
|
||||||
/// Default: `true`.
|
/// Default: `true`.
|
||||||
pub anti_alias: bool,
|
pub feathering: bool,
|
||||||
|
|
||||||
|
/// The size of the the feathering, in physical pixels.
|
||||||
|
///
|
||||||
|
/// The default, and suggested, value for this is `1.0`.
|
||||||
|
/// If you use a larger value, edges will appear blurry.
|
||||||
|
pub feathering_size_in_pixels: f32,
|
||||||
|
|
||||||
/// If `true` (default) cull certain primitives before tessellating them.
|
/// If `true` (default) cull certain primitives before tessellating them.
|
||||||
/// This likely makes
|
/// This likely makes
|
||||||
|
@ -326,9 +325,8 @@ pub struct TessellationOptions {
|
||||||
impl Default for TessellationOptions {
|
impl Default for TessellationOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pixels_per_point: 1.0,
|
feathering: true,
|
||||||
aa_size: 1.0,
|
feathering_size_in_pixels: 1.0,
|
||||||
anti_alias: true,
|
|
||||||
coarse_tessellation_culling: true,
|
coarse_tessellation_culling: true,
|
||||||
round_text_to_pixels: true,
|
round_text_to_pixels: true,
|
||||||
debug_paint_text_rects: false,
|
debug_paint_text_rects: false,
|
||||||
|
@ -340,27 +338,6 @@ impl Default for TessellationOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TessellationOptions {
|
|
||||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
pixels_per_point,
|
|
||||||
aa_size: 1.0 / pixels_per_point,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TessellationOptions {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
|
||||||
if self.round_text_to_pixels {
|
|
||||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
|
||||||
} else {
|
|
||||||
point
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
||||||
if let Some(last) = path.last() {
|
if let Some(last) = path.last() {
|
||||||
let mut previous = last.pos;
|
let mut previous = last.pos;
|
||||||
|
@ -380,18 +357,13 @@ fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
||||||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||||
///
|
///
|
||||||
/// The preferred winding order is clockwise.
|
/// The preferred winding order is clockwise.
|
||||||
fn fill_closed_path(
|
fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) {
|
||||||
path: &mut [PathPoint],
|
|
||||||
color: Color32,
|
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
|
||||||
) {
|
|
||||||
if color == Color32::TRANSPARENT {
|
if color == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = path.len() as u32;
|
let n = path.len() as u32;
|
||||||
if options.anti_alias {
|
if feathering > 0.0 {
|
||||||
if cw_signed_area(path) < 0.0 {
|
if cw_signed_area(path) < 0.0 {
|
||||||
// Wrong winding order - fix:
|
// Wrong winding order - fix:
|
||||||
path.reverse();
|
path.reverse();
|
||||||
|
@ -415,7 +387,7 @@ fn fill_closed_path(
|
||||||
let mut i0 = n - 1;
|
let mut i0 = n - 1;
|
||||||
for i1 in 0..n {
|
for i1 in 0..n {
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let dm = 0.5 * options.aa_size * p1.normal;
|
let dm = 0.5 * feathering * p1.normal;
|
||||||
out.colored_vertex(p1.pos - dm, color);
|
out.colored_vertex(p1.pos - dm, color);
|
||||||
out.colored_vertex(p1.pos + dm, color_outer);
|
out.colored_vertex(p1.pos + dm, color_outer);
|
||||||
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
||||||
|
@ -438,10 +410,10 @@ fn fill_closed_path(
|
||||||
|
|
||||||
/// Tessellate the given path as a stroke with thickness.
|
/// Tessellate the given path as a stroke with thickness.
|
||||||
fn stroke_path(
|
fn stroke_path(
|
||||||
|
feathering: f32,
|
||||||
path: &[PathPoint],
|
path: &[PathPoint],
|
||||||
path_type: PathType,
|
path_type: PathType,
|
||||||
stroke: Stroke,
|
stroke: Stroke,
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
) {
|
) {
|
||||||
let n = path.len() as u32;
|
let n = path.len() as u32;
|
||||||
|
@ -452,21 +424,21 @@ fn stroke_path(
|
||||||
|
|
||||||
let idx = out.vertices.len() as u32;
|
let idx = out.vertices.len() as u32;
|
||||||
|
|
||||||
if options.anti_alias {
|
if feathering > 0.0 {
|
||||||
let color_inner = stroke.color;
|
let color_inner = stroke.color;
|
||||||
let color_outer = Color32::TRANSPARENT;
|
let color_outer = Color32::TRANSPARENT;
|
||||||
|
|
||||||
let thin_line = stroke.width <= options.aa_size;
|
let thin_line = stroke.width <= feathering;
|
||||||
if thin_line {
|
if thin_line {
|
||||||
/*
|
/*
|
||||||
We paint the line using three edges: outer, inner, outer.
|
We paint the line using three edges: outer, inner, outer.
|
||||||
|
|
||||||
. o i o outer, inner, outer
|
. o i o outer, inner, outer
|
||||||
. |---| aa_size (pixel width)
|
. |---| feathering (pixel width)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Fade out as it gets thinner:
|
// Fade out as it gets thinner:
|
||||||
let color_inner = mul_color(color_inner, stroke.width / options.aa_size);
|
let color_inner = mul_color(color_inner, stroke.width / feathering);
|
||||||
if color_inner == Color32::TRANSPARENT {
|
if color_inner == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -480,9 +452,9 @@ fn stroke_path(
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
out.colored_vertex(p + n * options.aa_size, color_outer);
|
out.colored_vertex(p + n * feathering, color_outer);
|
||||||
out.colored_vertex(p, color_inner);
|
out.colored_vertex(p, color_inner);
|
||||||
out.colored_vertex(p - n * options.aa_size, color_outer);
|
out.colored_vertex(p - n * feathering, color_outer);
|
||||||
|
|
||||||
if connect_with_previous {
|
if connect_with_previous {
|
||||||
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
||||||
|
@ -500,14 +472,14 @@ fn stroke_path(
|
||||||
We paint the line using four edges: outer, inner, inner, outer
|
We paint the line using four edges: outer, inner, inner, outer
|
||||||
|
|
||||||
. o i p i o outer, inner, point, inner, outer
|
. o i p i o outer, inner, point, inner, outer
|
||||||
. |---| aa_size (pixel width)
|
. |---| feathering (pixel width)
|
||||||
. |--------------| width
|
. |--------------| width
|
||||||
. |---------| outer_rad
|
. |---------| outer_rad
|
||||||
. |-----| inner_rad
|
. |-----| inner_rad
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let inner_rad = 0.5 * (stroke.width - options.aa_size);
|
let inner_rad = 0.5 * (stroke.width - feathering);
|
||||||
let outer_rad = 0.5 * (stroke.width + options.aa_size);
|
let outer_rad = 0.5 * (stroke.width + feathering);
|
||||||
|
|
||||||
match path_type {
|
match path_type {
|
||||||
PathType::Closed => {
|
PathType::Closed => {
|
||||||
|
@ -542,7 +514,7 @@ fn stroke_path(
|
||||||
|
|
||||||
// | aa | | aa |
|
// | aa | | aa |
|
||||||
// _________________ ___
|
// _________________ ___
|
||||||
// | \ added / | aa_size
|
// | \ added / | feathering
|
||||||
// | \ ___p___ / | ___
|
// | \ ___p___ / | ___
|
||||||
// | | | |
|
// | | | |
|
||||||
// | | opa | |
|
// | | opa | |
|
||||||
|
@ -558,7 +530,7 @@ fn stroke_path(
|
||||||
let end = &path[0];
|
let end = &path[0];
|
||||||
let p = end.pos;
|
let p = end.pos;
|
||||||
let n = end.normal;
|
let n = end.normal;
|
||||||
let back_extrude = n.rot90() * options.aa_size;
|
let back_extrude = n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||||
|
@ -595,7 +567,7 @@ fn stroke_path(
|
||||||
let end = &path[i1 as usize];
|
let end = &path[i1 as usize];
|
||||||
let p = end.pos;
|
let p = end.pos;
|
||||||
let n = end.normal;
|
let n = end.normal;
|
||||||
let back_extrude = -n.rot90() * options.aa_size;
|
let back_extrude = -n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||||
|
@ -640,11 +612,11 @@ fn stroke_path(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let thin_line = stroke.width <= options.aa_size;
|
let thin_line = stroke.width <= feathering;
|
||||||
if thin_line {
|
if thin_line {
|
||||||
// Fade out thin lines rather than making them thinner
|
// Fade out thin lines rather than making them thinner
|
||||||
let radius = options.aa_size / 2.0;
|
let radius = feathering / 2.0;
|
||||||
let color = mul_color(stroke.color, stroke.width / options.aa_size);
|
let color = mul_color(stroke.color, stroke.width / feathering);
|
||||||
if color == Color32::TRANSPARENT {
|
if color == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -677,7 +649,10 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
|
||||||
///
|
///
|
||||||
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
||||||
pub struct Tessellator {
|
pub struct Tessellator {
|
||||||
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
|
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
||||||
|
feathering: f32,
|
||||||
/// Only used for culling
|
/// Only used for culling
|
||||||
clip_rect: Rect,
|
clip_rect: Rect,
|
||||||
scratchpad_points: Vec<Pos2>,
|
scratchpad_points: Vec<Pos2>,
|
||||||
|
@ -686,9 +661,17 @@ pub struct Tessellator {
|
||||||
|
|
||||||
impl Tessellator {
|
impl Tessellator {
|
||||||
/// Create a new [`Tessellator`].
|
/// Create a new [`Tessellator`].
|
||||||
pub fn from_options(options: TessellationOptions) -> Self {
|
pub fn new(pixels_per_point: f32, options: TessellationOptions) -> Self {
|
||||||
|
let feathering = if options.feathering {
|
||||||
|
let pixel_size = 1.0 / pixels_per_point;
|
||||||
|
options.feathering_size_in_pixels * pixel_size
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
|
pixels_per_point,
|
||||||
options,
|
options,
|
||||||
|
feathering,
|
||||||
clip_rect: Rect::EVERYTHING,
|
clip_rect: Rect::EVERYTHING,
|
||||||
scratchpad_points: Default::default(),
|
scratchpad_points: Default::default(),
|
||||||
scratchpad_path: Default::default(),
|
scratchpad_path: Default::default(),
|
||||||
|
@ -700,6 +683,15 @@ impl Tessellator {
|
||||||
self.clip_rect = clip_rect;
|
self.clip_rect = clip_rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||||
|
if self.options.round_text_to_pixels {
|
||||||
|
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||||
|
} else {
|
||||||
|
point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
||||||
///
|
///
|
||||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
||||||
|
@ -783,9 +775,9 @@ impl Tessellator {
|
||||||
|
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
self.scratchpad_path.add_circle(center, radius);
|
self.scratchpad_path.add_circle(center, radius);
|
||||||
self.scratchpad_path.fill(fill, &self.options, out);
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke_closed(stroke, &self.options, out);
|
.stroke_closed(self.feathering, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
||||||
|
@ -826,7 +818,8 @@ impl Tessellator {
|
||||||
|
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
self.scratchpad_path.add_line_segment(points);
|
self.scratchpad_path.add_line_segment(points);
|
||||||
self.scratchpad_path.stroke_open(stroke, &self.options, out);
|
self.scratchpad_path
|
||||||
|
.stroke_open(self.feathering, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
||||||
|
@ -863,7 +856,7 @@ impl Tessellator {
|
||||||
closed,
|
closed,
|
||||||
"You asked to fill a path that is not closed. That makes no sense."
|
"You asked to fill a path that is not closed. That makes no sense."
|
||||||
);
|
);
|
||||||
self.scratchpad_path.fill(*fill, &self.options, out);
|
self.scratchpad_path.fill(self.feathering, *fill, out);
|
||||||
}
|
}
|
||||||
let typ = if *closed {
|
let typ = if *closed {
|
||||||
PathType::Closed
|
PathType::Closed
|
||||||
|
@ -871,7 +864,7 @@ impl Tessellator {
|
||||||
PathType::Open
|
PathType::Open
|
||||||
};
|
};
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke(typ, *stroke, &self.options, out);
|
.stroke(self.feathering, typ, *stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
||||||
|
@ -904,8 +897,8 @@ impl Tessellator {
|
||||||
path.clear();
|
path.clear();
|
||||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||||
path.add_line_loop(&self.scratchpad_points);
|
path.add_line_loop(&self.scratchpad_points);
|
||||||
path.fill(fill, &self.options, out);
|
path.fill(self.feathering, fill, out);
|
||||||
path.stroke_closed(stroke, &self.options, out);
|
path.stroke_closed(self.feathering, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||||
|
@ -937,8 +930,8 @@ impl Tessellator {
|
||||||
// The contents of the galley is already snapped to pixel coordinates,
|
// The contents of the galley is already snapped to pixel coordinates,
|
||||||
// but we need to make sure the galley ends up on the start of a physical pixel:
|
// but we need to make sure the galley ends up on the start of a physical pixel:
|
||||||
let galley_pos = pos2(
|
let galley_pos = pos2(
|
||||||
self.options.round_to_pixel(galley_pos.x),
|
self.round_to_pixel(galley_pos.x),
|
||||||
self.options.round_to_pixel(galley_pos.y),
|
self.round_to_pixel(galley_pos.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
|
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
|
||||||
|
@ -1006,7 +999,7 @@ impl Tessellator {
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke_open(*underline, &self.options, out);
|
.stroke_open(self.feathering, *underline, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1086,14 +1079,15 @@ impl Tessellator {
|
||||||
closed,
|
closed,
|
||||||
"You asked to fill a path that is not closed. That makes no sense."
|
"You asked to fill a path that is not closed. That makes no sense."
|
||||||
);
|
);
|
||||||
self.scratchpad_path.fill(fill, &self.options, out);
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
}
|
}
|
||||||
let typ = if closed {
|
let typ = if closed {
|
||||||
PathType::Closed
|
PathType::Closed
|
||||||
} else {
|
} else {
|
||||||
PathType::Open
|
PathType::Open
|
||||||
};
|
};
|
||||||
self.scratchpad_path.stroke(typ, stroke, &self.options, out);
|
self.scratchpad_path
|
||||||
|
.stroke(self.feathering, typ, stroke, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1102,8 +1096,9 @@ impl Tessellator {
|
||||||
/// The given shapes will tessellated in the same order as they are given.
|
/// The given shapes will tessellated in the same order as they are given.
|
||||||
/// They will be batched together by clip rectangle.
|
/// They will be batched together by clip rectangle.
|
||||||
///
|
///
|
||||||
/// * `shapes`: what to tessellate
|
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||||
/// * `options`: tessellation quality
|
/// * `options`: tessellation quality
|
||||||
|
/// * `shapes`: what to tessellate
|
||||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
|
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
|
||||||
///
|
///
|
||||||
/// The implementation uses a [`Tessellator`].
|
/// The implementation uses a [`Tessellator`].
|
||||||
|
@ -1111,11 +1106,12 @@ impl Tessellator {
|
||||||
/// ## Returns
|
/// ## Returns
|
||||||
/// A list of clip rectangles with matching [`Mesh`].
|
/// A list of clip rectangles with matching [`Mesh`].
|
||||||
pub fn tessellate_shapes(
|
pub fn tessellate_shapes(
|
||||||
shapes: Vec<ClippedShape>,
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
|
shapes: Vec<ClippedShape>,
|
||||||
tex_size: [usize; 2],
|
tex_size: [usize; 2],
|
||||||
) -> Vec<ClippedPrimitive> {
|
) -> Vec<ClippedPrimitive> {
|
||||||
let mut tessellator = Tessellator::from_options(options);
|
let mut tessellator = Tessellator::new(pixels_per_point, options);
|
||||||
|
|
||||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||||
|
|
||||||
|
|
|
@ -599,10 +599,8 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
||||||
if antialiased {
|
if antialiased {
|
||||||
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
|
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
|
||||||
path.add_line_segment([start, stop]);
|
path.add_line_segment([start, stop]);
|
||||||
let options = crate::tessellator::TessellationOptions::from_pixels_per_point(
|
let feathering = 1.0 / point_scale.pixels_per_point();
|
||||||
point_scale.pixels_per_point(),
|
path.stroke_open(feathering, stroke, mesh);
|
||||||
);
|
|
||||||
path.stroke_open(stroke, &options, mesh);
|
|
||||||
} else {
|
} else {
|
||||||
// Thin lines often lost, so this is a bad idea
|
// Thin lines often lost, so this is a bad idea
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue