fix: stroke arcs and splines analytically — opaque core, angle-independent weight
LICE_Arc never reaches opacity and ThickFLine's width is minor-axis. One distance-to-polyline coverage mask, blended once, replaces both.
This commit is contained in:
@@ -25,18 +25,30 @@ double valuePerPixel(const VelocityCurve::Box& box, CurveDomain d) {
|
||||
if (h <= 1) return 0.0;
|
||||
return (kCurveYMax - curveYMin(d)) / static_cast<double>(h - 1);
|
||||
}
|
||||
int velToX(const VelocityCurve::Box& box, double velocity) {
|
||||
// The integer maps are these rounded — ONE mapping, so a sub-pixel trace and an integer hit-test
|
||||
// cannot drift. Rounding the OFFSET (not the absolute coordinate) keeps the int results identical
|
||||
// to what they were before the sub-pixel form existed: the offset is non-negative, so truncation
|
||||
// is floor regardless of where the box sits.
|
||||
double velToXf(const VelocityCurve::Box& box, double velocity) {
|
||||
const int w = std::max(0, box.width);
|
||||
if (w <= 0) return box.left;
|
||||
if (w <= 0) return static_cast<double>(box.left);
|
||||
const double frac = (clampVelocity(velocity) - kVelMin) / (kVelMax - kVelMin);
|
||||
return box.left + static_cast<int>(frac * static_cast<double>(w) + 0.5);
|
||||
return static_cast<double>(box.left) + frac * static_cast<double>(w);
|
||||
}
|
||||
int valueToY(const VelocityCurve::Box& box, double value, CurveDomain d) {
|
||||
double valueToYf(const VelocityCurve::Box& box, double value, CurveDomain d) {
|
||||
const int h = std::max(0, box.height);
|
||||
if (h <= 1) return box.top;
|
||||
if (h <= 1) return static_cast<double>(box.top);
|
||||
const double lo = curveYMin(d);
|
||||
const double frac = (clampValue(value, d) - lo) / (kCurveYMax - lo);
|
||||
return box.top + static_cast<int>((1.0 - frac) * static_cast<double>(h - 1) + 0.5);
|
||||
return static_cast<double>(box.top) + (1.0 - frac) * static_cast<double>(h - 1);
|
||||
}
|
||||
int velToX(const VelocityCurve::Box& box, double velocity) {
|
||||
return box.left +
|
||||
static_cast<int>(velToXf(box, velocity) - static_cast<double>(box.left) + 0.5);
|
||||
}
|
||||
int valueToY(const VelocityCurve::Box& box, double value, CurveDomain d) {
|
||||
return box.top +
|
||||
static_cast<int>(valueToYf(box, value, d) - static_cast<double>(box.top) + 0.5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -187,6 +199,11 @@ VelocityCurve::CurvePixel VelocityCurve::pixelFromPoint(const Box& box,
|
||||
return CurvePixel{velToX(box, p.velocity), valueToY(box, p.value, domain_)};
|
||||
}
|
||||
|
||||
VelocityCurve::CurvePixelF VelocityCurve::subpixelFromPoint(const Box& box,
|
||||
const VelocityPoint& p) const {
|
||||
return CurvePixelF{velToXf(box, p.velocity), valueToYf(box, p.value, domain_)};
|
||||
}
|
||||
|
||||
VelocityPoint VelocityCurve::pointFromPixel(const Box& box, int x, int y) const {
|
||||
// Exact inverse of velToX/valueToY (within one pixel); degenerate dims collapse the same way.
|
||||
VelocityPoint p;
|
||||
|
||||
@@ -182,6 +182,16 @@ public:
|
||||
};
|
||||
CurvePixel pixelFromPoint(const Box& box, const VelocityPoint& p) const;
|
||||
|
||||
// The SAME mapping before rounding: pixelFromPoint IS this, rounded, so a sub-pixel trace and
|
||||
// an integer hit-test cannot drift. An antialiased stroke needs the fraction — quantizing y to
|
||||
// a whole pixel forces the slope into alternating 1/2-px steps, and that beat-frequency
|
||||
// staircase is what read as a dotted line where a contour steepened.
|
||||
struct CurvePixelF {
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
};
|
||||
CurvePixelF subpixelFromPoint(const Box& box, const VelocityPoint& p) const;
|
||||
|
||||
// Exact inverse of pixelFromPoint (within the one-pixel quantum) — where an empty-space
|
||||
// click lands as a new point. Degenerate box: zero-width reads velocity 0; height <= 1
|
||||
// reads the domain's max (the top row is what a collapsed box draws).
|
||||
|
||||
Reference in New Issue
Block a user