perf: simplify stroke outlines before PDF export
Each stroke mesh has about 16 points per pen sample, and every one of them went into the PDF as a polygon point: a full handwritten page (1650 strokes) took 2.9 s to export, and PNG export and copying a selection as an image took as long, because both go through the PDF. The outline is now simplified with a closed Douglas-Peucker that measures against segments, at most 0.01 mm from the drawn outline. The same page now takes 0.44 s (PNG 0.55 s), the PDF shrinks from 10.7 MB to 1.4 MB, and the rendered PNG differs only in edge anti-aliasing (max 21/255). INK_NOTES_EXACT_OUTLINES=1 exports every point as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
330c29193f
commit
955eaa4095
4 files changed
+161
-17
No files matched your search
+30
-15
@@ -1222,21 +1222,25 @@ fn render_stroke_polygon(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
|
||||
|
||||
// Geschlossener Pfad um den Ribbon-Rand:
|
||||
// Left[0] -> Left[1] -> ... -> Left[n-1] -> Right[n-1] -> Right[n-2] -> ... -> Right[0]
|
||||
let mut ring = Vec::with_capacity(n * 2);
|
||||
|
||||
// 1. Linke Hüllkurve vorwärts
|
||||
for i in 0..n {
|
||||
let v = stroke.vertices[i * 2];
|
||||
let (x, y) = page_to_pdf_mm(v.position[0], v.position[1]);
|
||||
ring.push((Point::new(Mm(x), Mm(y)), false));
|
||||
}
|
||||
|
||||
// 2. Rechte Hüllkurve rückwärts
|
||||
for i in (0..n).rev() {
|
||||
let v = stroke.vertices[i * 2 + 1];
|
||||
let (x, y) = page_to_pdf_mm(v.position[0], v.position[1]);
|
||||
ring.push((Point::new(Mm(x), Mm(y)), false));
|
||||
}
|
||||
let outline: Vec<glam::Vec2> = (0..n)
|
||||
.map(|i| stroke.vertices[i * 2].position)
|
||||
.chain((0..n).rev().map(|i| stroke.vertices[i * 2 + 1].position))
|
||||
.map(glam::Vec2::from)
|
||||
.collect();
|
||||
// The mesh has about 16 points per pen sample, far more than the eye (or a printer) can
|
||||
// tell apart; fewer points make the PDF, PNG export and copying as an image much faster
|
||||
let outline = if exact_outlines() {
|
||||
outline
|
||||
} else {
|
||||
crate::geometry::simplify_closed_outline(&outline, OUTLINE_TOLERANCE)
|
||||
};
|
||||
let ring: Vec<(Point, bool)> = outline
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let (x, y) = page_to_pdf_mm(p.x, p.y);
|
||||
(Point::new(Mm(x), Mm(y)), false)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fill_color = Color::Rgb(Rgb::new(
|
||||
stroke.color[0],
|
||||
@@ -1254,6 +1258,17 @@ fn render_stroke_polygon(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
|
||||
layer.add_polygon(poly);
|
||||
}
|
||||
|
||||
/// Largest deviation of an exported stroke outline from the drawn one, in page units
|
||||
/// (0.01 mm: a tenth of a pixel at 200 dpi).
|
||||
pub const OUTLINE_TOLERANCE: f32 = 0.1;
|
||||
|
||||
/// `INK_NOTES_EXACT_OUTLINES=1` exports every point of the stroke outlines, as before this change
|
||||
/// (fallback in case the simplified outlines ever look wrong).
|
||||
fn exact_outlines() -> bool {
|
||||
static EXACT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*EXACT.get_or_init(|| std::env::var_os("INK_NOTES_EXACT_OUTLINES").is_some_and(|v| v != "0"))
|
||||
}
|
||||
|
||||
/// Strategie B: Kubische Bézier-Kurven aus den Abtastpunkten via printpdf::Line.
|
||||
fn render_stroke_bezier(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
|
||||
if stroke.points.is_empty() {
|
||||
|
||||
Reference in new issue
Block a user