feat(pen): brush opacity + highlighter multiply
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Honor each brush's opacity/blend so the brushes feel distinct (closes TODO(brush-opacity)). - Shared paint resolver: a stroke's color alpha is multiplied by its brush opacity; ballpoint/pencil opacity is tied to pressure (per-stroke average this increment) so a ballpoint reads lighter than a solid fountain pen. - Highlighter paints with BlendMode.multiply and draws once, so cross-stroke overlap darkens like a real marker while self-overlap doesn't. - Applied across BOTH render paths (PenCanvas static/live painters and the PDF _PageOverlayPainter). Pencil paper-grain texture still deferred (TODO brush-texture); brush kind is not yet serialized (TODO brush-persist — next). analyze clean, tests green.
This commit is contained in:
@@ -65,6 +65,34 @@ Path buildStrokePath(
|
||||
return path;
|
||||
}
|
||||
|
||||
/// Mean point pressure (`pressure ?? 0.5`) of a [PenStroke], for the per-stroke
|
||||
/// opacity resolution (spec §3/§4 tie ballpoint/pencil opacity to pressure).
|
||||
double _avgPressure(PenStroke stroke) {
|
||||
if (stroke.points.isEmpty) return 0.5;
|
||||
var sum = 0.0;
|
||||
for (final p in stroke.points) {
|
||||
sum += p.pressure ?? 0.5;
|
||||
}
|
||||
return sum / stroke.points.length;
|
||||
}
|
||||
|
||||
/// THE single fill [Paint] for a committed/live stroke, with the brush's
|
||||
/// resolved opacity (multiplied into the color's alpha) and blend mode applied
|
||||
/// — closes TODO(brush-opacity). Shared by [StaticInkPainter]/[LiveInkPainter]
|
||||
/// and the PDF overlay painter so both render paths composite identically.
|
||||
Paint paintForStroke(PenStroke stroke) {
|
||||
final resolved = resolveStrokePaint(
|
||||
stroke.brush,
|
||||
stroke.color,
|
||||
pressureAvg: _avgPressure(stroke),
|
||||
);
|
||||
return Paint()
|
||||
..color = resolved.color
|
||||
..blendMode = resolved.blendMode
|
||||
..style = PaintingStyle.fill
|
||||
..isAntiAlias = true;
|
||||
}
|
||||
|
||||
/// Paints all committed strokes for the page. Repaints only when the stroke
|
||||
/// list identity or page size changes (kept behind a RepaintBoundary).
|
||||
class StaticInkPainter extends CustomPainter {
|
||||
@@ -86,13 +114,9 @@ class StaticInkPainter extends CustomPainter {
|
||||
final path =
|
||||
buildStrokePath(stroke, pageSize, isComplete: true, thinning: thinning);
|
||||
if (path.getBounds().isEmpty) continue;
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = Color(stroke.color)
|
||||
..style = PaintingStyle.fill
|
||||
..isAntiAlias = true,
|
||||
);
|
||||
// Single drawPath per stroke ⇒ a highlighter's own self-overlap never
|
||||
// darkens; cross-stroke overlap darkens via BlendMode.multiply (marker).
|
||||
canvas.drawPath(path, paintForStroke(stroke));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,13 +292,7 @@ class LiveInkPainter extends CustomPainter {
|
||||
final path =
|
||||
buildStrokePath(s, pageSize, isComplete: false, thinning: thinning);
|
||||
if (path.getBounds().isEmpty) return;
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = Color(s.color)
|
||||
..style = PaintingStyle.fill
|
||||
..isAntiAlias = true,
|
||||
);
|
||||
canvas.drawPath(path, paintForStroke(s));
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user