fix: graph jitter on load + format

This commit is contained in:
2026-11-09 20:20:27 +01:00
parent d69a6bcafd
commit e49ecabf62
13 changed files with 181 additions and 320 deletions
+47 -53
View File
@@ -383,7 +383,8 @@ Centering detectCentering(QQuickItem* item) {
}
if (ad->usedAnchors & QQuickAnchors::VCenterAnchor) {
c.vertical = true;
c.source += (c.source.isEmpty() ? "" : " ") + QStringLiteral("vCenter");
c.source +=
(c.source.isEmpty() ? "" : " ") + QStringLiteral("vCenter");
}
}
@@ -410,9 +411,9 @@ Centering detectCentering(QQuickItem* item) {
// Parity (mod 2) of an item's whole-device-pixel size; no item = even.
int devParity(const QQuickItem* it, qreal dpr, bool horizontal) {
if (!it)
return 0;
const qint64 dev = std::llround((horizontal ? it->width() : it->height()) * dpr);
if (!it) return 0;
const qint64 dev =
std::llround((horizontal ? it->width() : it->height()) * dpr);
return static_cast<int>(dev % 2);
}
@@ -423,9 +424,9 @@ int devParity(const QQuickItem* it, qreal dpr, bool horizontal) {
// since edges = parentLeft + (parentSize - size) / 2.
qreal snapToParity(qreal value, qreal dpr, int parity, int mode) {
const double exact = value * dpr;
qint64 n = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
: mode < 0 ? std::llround(std::floor(exact + 1e-6))
: std::llround(exact);
qint64 n = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
: mode < 0 ? std::llround(std::floor(exact + 1e-6))
: std::llround(exact);
if (static_cast<int>(n % 2) != parity) {
if (mode > 0) {
++n;
@@ -436,8 +437,7 @@ qreal snapToParity(qreal value, qreal dpr, int parity, int mode) {
n = (exact >= static_cast<double>(n)) ? n + 1 : n - 1;
}
}
if (n < 0)
n = 0;
if (n < 0) n = 0;
return static_cast<qreal>(n) / dpr;
}
@@ -452,16 +452,17 @@ qint64 roundAway(double value) {
// divides exactly), so with the parent's half at a whole device offset the
// child's device size must be even; if the parent's half sits on a half device
// pixel, the child's device size must be odd instead.
qreal snapCenteredSize(qreal value, qreal dpr, qint64 parentHalfRounded, int mode) {
qreal snapCenteredSize(
qreal value, qreal dpr, qint64 parentHalfRounded, int mode) {
const double exact = value * dpr;
const double anchorDev = double(parentHalfRounded) * dpr;
const bool anchorWhole =
std::fabs(anchorDev - std::round(anchorDev)) < 1e-6;
const bool anchorWhole = std::fabs(anchorDev - std::round(anchorDev)) <
1e-6;
const qint64 parity = anchorWhole ? 0 : 1;
qint64 base = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
: mode < 0 ? std::llround(std::floor(exact + 1e-6))
: std::llround(exact);
qint64 base = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
: mode < 0 ? std::llround(std::floor(exact + 1e-6))
: std::llround(exact);
for (qint64 step = 0; step < 8; ++step) {
if (base + step >= 0 && (base + step) % 2 == parity)
return qreal(base + step) / dpr;
@@ -483,7 +484,8 @@ qreal snapCenteredAxis(
int mode) {
QQuickItem* parent = item->parentItem();
if (!centering.alignWhenCentered && parent) {
const qreal parentSize = horizontal ? parent->width() : parent->height();
const qreal parentSize = horizontal ? parent->width()
: parent->height();
return snapCenteredSize(value, dpr, roundAway(parentSize / 2.0), mode);
}
return snapToParity(value, dpr, devParity(parent, dpr, horizontal), mode);
@@ -495,10 +497,8 @@ qreal ZUtils::snap(qreal value, QQuickItem* item) {
const qreal dpr = dprFor(item);
if (item) {
const Centering c = detectCentering(item);
if (c.horizontal)
return snapCenteredAxis(value, item, dpr, true, c, 0);
if (c.vertical)
return snapCenteredAxis(value, item, dpr, false, c, 0);
if (c.horizontal) return snapCenteredAxis(value, item, dpr, true, c, 0);
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, 0);
}
return std::round(value * dpr) / dpr;
}
@@ -507,10 +507,8 @@ qreal ZUtils::snapCeil(qreal value, QQuickItem* item) {
const qreal dpr = dprFor(item);
if (item) {
const Centering c = detectCentering(item);
if (c.horizontal)
return snapCenteredAxis(value, item, dpr, true, c, 1);
if (c.vertical)
return snapCenteredAxis(value, item, dpr, false, c, 1);
if (c.horizontal) return snapCenteredAxis(value, item, dpr, true, c, 1);
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, 1);
}
return std::ceil(value * dpr - 1e-6) / dpr;
}
@@ -521,8 +519,7 @@ qreal ZUtils::snapFloor(qreal value, QQuickItem* item) {
const Centering c = detectCentering(item);
if (c.horizontal)
return snapCenteredAxis(value, item, dpr, true, c, -1);
if (c.vertical)
return snapCenteredAxis(value, item, dpr, false, c, -1);
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, -1);
}
return std::floor(value * dpr + 1e-6) / dpr;
}
@@ -568,15 +565,12 @@ bool ZUtils::logPlacement(QQuickItem* item, const QString& tag) {
// instead of dividing, so the edges are what matter.
const auto centeredAxisOk = [&](double lo, double hi) {
const double size = hi - lo;
if (isOff(size))
return false;
if (!c.alignWhenCentered)
return !isOff(lo) && !isOff(hi);
if (isOff(size)) return false;
if (!c.alignWhenCentered) return !isOff(lo) && !isOff(hi);
const double center = (lo + hi) / 2.0;
const double parity = (std::llround(size) % 2 == 0) ? 0.0 : 0.5;
double err = std::fmod(center - parity, 1.0);
if (err < 0)
err += 1.0;
if (err < 0) err += 1.0;
return std::min(err, 1.0 - err) <= tol;
};
@@ -605,9 +599,7 @@ bool ZUtils::logPlacement(QQuickItem* item, const QString& tag) {
<< "\n device px: left=" << fmt(l) << " top=" << fmt(t)
<< " right=" << fmt(rt) << " bottom=" << fmt(b)
<< (isText ? " (right/bottom ignored: Text)" : "")
<< (!centerSrc.isEmpty()
? " (centered: " + centerSrc + ")"
: QString())
<< (!centerSrc.isEmpty() ? " (centered: " + centerSrc + ")" : QString())
<< (centeredH || centeredV
? QString("\n center: x=%1 y=%2 size=%3 x %4")
.arg(QString::number((l + rt) / 2, 'f', 2))
@@ -642,8 +634,8 @@ bool ZUtils::logCurvePlacement(
constexpr double tol = 1e-3;
const double dpr = dprFor(item);
const int devW = static_cast<int>(
std::max<qint64>(1, std::llround(lineWidth * dpr)));
const int devW =
static_cast<int>(std::max<qint64>(1, std::llround(lineWidth * dpr)));
const double half = devW / 2.0;
qsizetype offCount = 0;
@@ -690,23 +682,25 @@ bool ZUtils::logCurvePlacement(
const qsizetype flat = total - sloped;
const bool aligned = offCount == 0;
qInfo().noquote()
<< (aligned ? "[CURVE ALIGNED] " : "[CURVE UNALIGNED]")
<< (tag.isEmpty() ? QString::fromUtf8(item->metaObject()->className())
: tag)
<< "\n samples: " << total << " points (" << flat << " flat, "
<< sloped << " sloped ignored), dpr=" << dpr
<< ", stroke width=" << lineWidth << " logical = " << devW
<< " device px"
<< "\n edges: " << offCount << " of " << flat * 2
<< " flat stroke edges off the device grid";
qInfo().noquote() << (aligned ? "[CURVE ALIGNED] " : "[CURVE UNALIGNED]")
<< (tag.isEmpty() ? QString::fromUtf8(
item->metaObject()->className())
: tag)
<< "\n samples: " << total << " points (" << flat
<< " flat, " << sloped << " sloped ignored), dpr=" << dpr
<< ", stroke width=" << lineWidth << " logical = " << devW
<< " device px"
<< "\n edges: " << offCount << " of " << flat * 2
<< " flat stroke edges off the device grid";
if (!aligned) {
qInfo().noquote()
<< " worst: deviation=" << QString::number(worst, 'f', 3)
<< " device px at logical x=" << QString::number(worstX, 'f', 2)
<< " y=" << QString::number(worstY, 'f', 2)
<< " (" << (worstSide < 0 ? "top" : "bottom") << " edge device="
<< QString::number(worstEdge, 'f', 3) << ")";
qInfo().noquote() << " worst: deviation="
<< QString::number(worst, 'f', 3)
<< " device px at logical x="
<< QString::number(worstX, 'f', 2)
<< " y=" << QString::number(worstY, 'f', 2) << " ("
<< (worstSide < 0 ? "top" : "bottom")
<< " edge device="
<< QString::number(worstEdge, 'f', 3) << ")";
}
return aligned;