fix: close Θ-W7-T1 review — scaling guard, opacity claims, two vacuous test fixes
Guards the stroke blend against LICE_EXT_GET_SCALING, tightens the analytic-stroker's boxes and NaN handling, corrects the opaque-core threshold and inner-dial rationale in the docs, and re-derives two review-flagged tautological tests so they actually fail against the bugs they claim to catch.
This commit is contained in:
@@ -73,6 +73,8 @@ static float perpendicularWeight(float angleDeg, float halfWidth) {
|
||||
|
||||
static void testStraightStrokeHasAnOpaqueCore() {
|
||||
// The shipped defect stated numerically: peak alpha must reach full, not 137-192/255.
|
||||
// 2 and 3 px only: below the >= 2 px opaque-core threshold (core/ui/CLAUDE.md), a stroke
|
||||
// does NOT reliably reach full alpha — see testSubOpaqueCoreAtOnePixelWidth below.
|
||||
for (float w : {2.0f, 3.0f}) {
|
||||
for (float deg : {0.0f, 17.0f, 45.0f, 63.0f, 90.0f}) {
|
||||
const float a = deg * kPi / 180.0f;
|
||||
@@ -85,6 +87,25 @@ static void testStraightStrokeHasAnOpaqueCore() {
|
||||
}
|
||||
}
|
||||
|
||||
static void testSubOpaqueCoreAtOnePixelWidth() {
|
||||
// Below the >= 2 px opaque-core threshold: a 1 px stroke (halfWidth = 0.5) has zero slack
|
||||
// against the 0.5 px worst-case pixel-centre distance (core/ui/CLAUDE.md), so peak alpha
|
||||
// tracks the stroke's alignment to the pixel grid instead of reaching 255 everywhere. Pin
|
||||
// both ends of that modulation — this is the knob track arc's and the mini curve-trace's
|
||||
// actual behaviour, not a hypothetical.
|
||||
const StrokePoint onRowCentre[2] = {{20.0f, 100.5f}, {220.0f, 100.5f}}; // centred on row 100
|
||||
StrokeCanvas aligned;
|
||||
strokePolyline(aligned, onRowCentre, 2, 0.5f, kBig);
|
||||
CHECK(aligned.coverageAt(120, 100) >= 0.999f); // aligned to the grid: reaches opaque
|
||||
|
||||
const StrokePoint onRowBoundary[2] = {{20.0f, 100.0f}, {220.0f, 100.0f}}; // on the boundary
|
||||
StrokeCanvas misaligned;
|
||||
strokePolyline(misaligned, onRowBoundary, 2, 0.5f, kBig);
|
||||
CHECK(std::fabs(misaligned.coverageAt(120, 99) - 0.5f) < 1e-4f); // split evenly...
|
||||
CHECK(std::fabs(misaligned.coverageAt(120, 100) - 0.5f) < 1e-4f); // ...across both rows
|
||||
CHECK(peakCoverage(misaligned) < 0.999f); // and never reaches the opaque core here
|
||||
}
|
||||
|
||||
static void testPerpendicularWeightIsAngleIndependent() {
|
||||
// The criterion that killed the ThickFLine option: it dips to wid*cos(theta) at every 45
|
||||
// degrees. An axis-aligned-only sample would pass against it, so sample the diagonals.
|
||||
@@ -214,12 +235,23 @@ static void testRevisitedRowGapReadsZeroNotGarbage() {
|
||||
// A circle touches most rows on BOTH sides, leaving an untouched gap between the two spans.
|
||||
// The row's valid extent grows over that gap, so the gap must be zero-filled, not left at
|
||||
// whatever the reused scratch buffer held.
|
||||
StrokeCanvas c;
|
||||
c.reset(kBig);
|
||||
c.addSegment(30.0f, 100.0f, 30.0f, 140.0f, 1.5f); // dirty the buffer on the left
|
||||
c.reset(kBig);
|
||||
//
|
||||
// The dirtying pass must land at the SAME bounds/stride the arc pass will reuse, or the two
|
||||
// writes address disjoint buffer offsets and the "old" value the gap reads back is just the
|
||||
// scratch buffer's original zero-init — the guard would then have nothing to prove itself
|
||||
// against (verified: deleting extendRow's fill at stroke_aa.cpp:48-51 left this test passing
|
||||
// when the dirtying pass used `kBig` while the arc pass reset to its own tighter bounds).
|
||||
std::vector<StrokePoint> ring;
|
||||
appendArc(ring, 120.0f, 120.0f, 60.0f, 0.0f, 2.0f * kPi);
|
||||
const Rect arcBounds = strokeBounds(ring.data(), ring.size(), 1.5f, kBig);
|
||||
|
||||
StrokeCanvas c;
|
||||
c.reset(arcBounds);
|
||||
// A horizontal segment straight across the row/columns the assertion below checks, so the
|
||||
// buffer genuinely holds nonzero ink there before the arc's own pass reuses the canvas.
|
||||
c.addSegment(100.0f, 120.0f, 140.0f, 120.0f, 1.5f);
|
||||
CHECK(c.coverageAt(120, 120) > 0.9f); // sanity: the dirtying pass actually landed here
|
||||
|
||||
strokePolyline(c, ring.data(), ring.size(), 1.5f, kBig);
|
||||
for (int x = 100; x < 140; ++x) CHECK(c.coverageAt(x, 120) == 0.0f); // hollow middle
|
||||
}
|
||||
@@ -257,6 +289,20 @@ static void testCoverageOutsideTheValidSpanReadsZero() {
|
||||
CHECK(c.coverageAt(1000, 1000) == 0.0f);
|
||||
}
|
||||
|
||||
// --- raster row addressing ----------------------------------------------------
|
||||
|
||||
static void testRasterRowOffsetMatchesUnflippedAndFlippedLayouts() {
|
||||
// Unflipped: row y is just y*rowSpan (LICE's top-down layout).
|
||||
CHECK(rasterRowOffset(0, 100, 240, false) == 0u);
|
||||
CHECK(rasterRowOffset(5, 100, 240, false) == 5u * 240u);
|
||||
CHECK(rasterRowOffset(99, 100, 240, false) == 99u * 240u);
|
||||
// Flipped (bottom-up DIBs): row y is (height-1-y)*rowSpan — LICE_SysBitmap's own pixel
|
||||
// accessor, `(h-1-y)*rowspan + x` (lice.cpp:2262).
|
||||
CHECK(rasterRowOffset(0, 100, 240, true) == 99u * 240u);
|
||||
CHECK(rasterRowOffset(99, 100, 240, true) == 0u);
|
||||
CHECK(rasterRowOffset(40, 100, 240, true) == 59u * 240u);
|
||||
}
|
||||
|
||||
// --- long-segment subdivision ------------------------------------------------
|
||||
|
||||
static void testSubdivisionDoesNotChangeTheRenderedStroke() {
|
||||
@@ -381,6 +427,7 @@ static void testKnobArcIsOpaqueAndEvenAllTheWayRound() {
|
||||
|
||||
int main() {
|
||||
testStraightStrokeHasAnOpaqueCore();
|
||||
testSubOpaqueCoreAtOnePixelWidth();
|
||||
testPerpendicularWeightIsAngleIndependent();
|
||||
testWeightHoldsAtTheExactDiagonal();
|
||||
testZeroLengthSegmentIsARoundDot();
|
||||
@@ -394,6 +441,7 @@ int main() {
|
||||
testBoundsClipToTheClipRectAndCoverTheReach();
|
||||
testStrokeEntirelyOutsideTheClipDrawsNothing();
|
||||
testCoverageOutsideTheValidSpanReadsZero();
|
||||
testRasterRowOffsetMatchesUnflippedAndFlippedLayouts();
|
||||
testSubdivisionDoesNotChangeTheRenderedStroke();
|
||||
testArcPointsLieOnTheCircleAndRespectTheFlatness();
|
||||
testArcDensityGrowsWithRadius();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "../src/core/instrument/engine/velocity_curve.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
@@ -398,12 +399,35 @@ static void testSubpixelMapIsTheIntegerMapBeforeRounding() {
|
||||
const Box boxes[] = {{10, 20, 100, 51}, {0, 0, 127, 101}, {7, 3, 33, 17}};
|
||||
for (const Box& box : boxes) {
|
||||
for (const VelocityCurve& c : {uni(), bip()}) {
|
||||
const double lo = curveYMin(c.domain());
|
||||
for (double v = 0.0; v <= 127.0; v += 1.0) {
|
||||
for (double a = -1.0; a <= 1.0; a += 0.125) {
|
||||
const auto ip = c.pixelFromPoint(box, {v, a});
|
||||
const auto fp = c.subpixelFromPoint(box, {v, a});
|
||||
// Relationship guard: int IS round(subpixel), everywhere — a forward guard
|
||||
// against the trace drifting off its own handles. This alone is a tautology
|
||||
// against the CURRENT formula (int literally computes this expression), so
|
||||
// it cannot catch a regression against the PRE-subpixel formula below.
|
||||
CHECK(ip.x == box.left + static_cast<int>(fp.x - box.left + 0.5));
|
||||
CHECK(ip.y == box.top + static_cast<int>(fp.y - box.top + 0.5));
|
||||
|
||||
// Independent re-derivation of the formula the int map claims to reproduce —
|
||||
// box.left + (int)(frac*w + 0.5) — computed here from scratch rather than
|
||||
// through subpixelFromPoint's box.left-add-then-subtract round trip, so a
|
||||
// flip introduced by that round trip (however unlikely per Sterbenz) has
|
||||
// something to fail against.
|
||||
const double vClamped = (std::min)(kVelMax, (std::max)(kVelMin, v));
|
||||
const double fracX = (vClamped - kVelMin) / (kVelMax - kVelMin);
|
||||
const int oldX =
|
||||
box.left + static_cast<int>(fracX * static_cast<double>(box.width) + 0.5);
|
||||
CHECK(ip.x == oldX);
|
||||
|
||||
const double aClamped = (std::min)(kCurveYMax, (std::max)(lo, a));
|
||||
const double fracY = (aClamped - lo) / (kCurveYMax - lo);
|
||||
const int oldY =
|
||||
box.top + static_cast<int>((1.0 - fracY) *
|
||||
static_cast<double>(box.height - 1) + 0.5);
|
||||
CHECK(ip.y == oldY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user