fix: align raster frame with matplotlib pixel rows#2018
Merged
Conversation
The raster pixel blend converted device coordinate N to the 1-based array element N, placing every feature one display pixel inward of matplotlib's position. The plot-area layout already produces matplotlib-exact edges (top spine y=58, bottom y=427, left x=80, right x=576 for the default 640x480 figure), but the bias rendered them at 57/426/79/575. Map device coordinate N to display pixel N (1-based array element N+1) in blend_pixel, the single sub-pixel write chokepoint. This shifts spines, ticks, the data curve, and markers together, so their mutual registration is preserved while the whole frame lands on matplotlib's rows and columns. Recalibrate two tests that hard-coded the old inward-biased scan rows: - test_dashdot_rendering scanned display row 24 for a line drawn at device y=25; the line now renders on row 25, so the scan row moves with it. - test_suptitle counted the subplot top spine in a 1-based top band whose end excluded the now-correctly-registered spine by one pixel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The raster axes frame rendered one display pixel inward of matplotlib. The
plot-area layout already computes matplotlib-exact edges, but the pixel blend
converted device coordinate N to 1-based array element N, shifting every drawn
feature one display pixel toward the origin.
blend_pixelnow maps device coordinate N to display pixel N (1-based arrayelement N+1). It is the single sub-pixel write chokepoint, so spines, ticks,
the data curve, and markers all shift together: their mutual registration is
preserved while the frame lands on matplotlib's exact rows and columns.
Before -> after vs matplotlib (default 640x480 figure, simple_plot)
Spine positions (0-indexed display rows/cols), measured against the matplotlib
reference render:
Footprint now matches matplotlib pixel-for-pixel: full black on the spine
pixel with symmetric light-gray antialiasing on both neighbours (e.g. top
spine col 300: row 57 gray, row 58 black, row 59 gray).
multi_line (800x600) spines likewise align exactly: top/bottom rows 72/534,
left/right cols 100/720 -- identical to matplotlib.
Major tick marks register with the corrected frame (interior ticks match
matplotlib columns exactly, e.g. 174, 246, 461, 533). The data curve moves the
same direction, improving curve-to-frame registration as well.
Recalibrated tests
Two tests hard-coded the old inward-biased pixel positions and are updated to
the matplotlib-correct values (not weakened):
test_dashdot_rendering: scanned display row 24 for a line drawn at devicey=25. The line now renders on display row 25, so all three scan loops move to
that row. Without the move the scan hit an empty row and the dash-dot vs
dotted comparison saw two blank rows as identical.
test_suptitle: counted the subplot top spine inside a 1-based top band whoseend (
int(0.12*h)) excluded the now-correctly-registered spine by one pixel.Band end extended to
int(0.12*h) + 1to cover the matplotlib 12% region in1-based array coordinates.
Verification
Commands run:
fpm build-- compiled successfully.fpm run --example basic_plots-- regenerated simple_plot/multi_line PNG+PDF.make verify-artifacts-- "Artifact verification passed." (ylabel-leftstripe gates mean ~1.0 vs 0.91 threshold; quiver geometry gate green).
make test-- "ALL TESTS PASSED (fpm test)".make test-ci-- "CI essential test suite completed successfully".Failing-before / passing-after (full
make test):test_dashdot_rendering-> "FAIL: dash-dot and dotted patterns areidentical";
test_suptitle-> "FAIL: 1x3 suptitle is not clearly above thesubplot grid".
PASS: patterns differ; dotted dark=169 dash-dot dark=281,All dash-dot rendering tests passed.;PASS: 1x3 suptitle renders in the top band,All suptitle tests PASSED!.PDF backend is unchanged (vector path does not use the raster blend); pdftotext
on simple_plot.pdf still yields the title, axis labels, and tick labels
("Simple Sine Wave ... sin(x) ... 0 2 4 6 8 x 10 12").