Repository navigation
fix(spf): apply the origin and pitch rules to the writer and the view - #6
Merged
Merged
Conversation
The 741 reconciliation corrected SpfFile.readColorized but left two other places on the old convention, where the size comes from right and bottom and rows advance by the row width. - writeColorized indexed colorData as `y * frame.right + x` for `y < frame.bottom`. For a frame with a non-zero origin that runs past the end of the array, and serializing a frame that had just been read threw "TypeError: Cannot read properties of undefined". 190 of 982 frames in the client have a non-zero origin. - writeColorized advanced startAddress by the source frame's byteCount, which includes row padding, while the payload it writes is tightly packed. Every frame after the first pointed into the wrong place. The writer now sets byteWidth, byteCount and imageByteCount to describe what it emits. - SpfView.get read `t.bottom * t.right` pixels and ignored the pitch. It threw "RangeError: Offset is outside the bounds of the DataView", or returned different pixels than SpfFile for the same bytes. Tests: 8 cases with a non-zero origin and a padded pitch, in spfFile and virtualizedViews. All 8 fail against the previous code. The existing parity test could not catch any of this, because fromColorizedRgbaFrames always produces left = top = 0 and byteWidth = width * 2, which is where the correct and the incorrect conventions agree. Co-Authored-By: Fintan (Claude Opus) <noreply@anthropic.com>
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.
Fixes findings 1–3 from the review of #2.
The 741 reconciliation corrected
SpfFile.readColorized— bounds give the size, rows advance by the frame's own pitch — but left two other places on the old convention, where the size comes fromright/bottomand rows advance by the row width. The reader and its writer, and the eager and lazy parsers, disagreed from the moment #2 landed.What was broken
1.
writeColorizedindexedcolorDatawithright/bottom. The reader sizescolorDataas(right−left) × (bottom−top); the writer loopedy < frame.bottom,x < frame.right. For a frame with a non-zero origin that runs off the end of the array:Serializing a frame the reader had just produced threw. 190 of 982 frames in the client have a non-zero origin.
2.
writeColorizedrecorded a byte count it did not write.startAddressadvanced by the source frame'sbyteCount— which includes row padding — while the payload written is tightly packed. Every frame after the first pointed into the wrong place, silently, with no error. 78 of 982 frames have a pitch that differs from the width.3.
SpfView.getwas never updated. The colorized branch still readt.bottom * t.rightpixels sequentially, ignoringbyteWidth:Where it didn't throw, it returned different pixels than
SpfFilefor the same bytes — on exactly the frames #2 set out to fix.Fixes
writeColorizedsizes from the bounds, like the reader, and normalizesbyteWidth/byteCount/imageByteCountto describe the bytes it actually emits.SpfView.getmirrorsSpfFile.readColorizedexactly: bounds for size,byteWidthfor stride, transparent past the end of the payload.Why the existing tests missed all of it
Every colorized fixture in the suite is built by
fromColorizedRgbaFrames, which always producesleft = top = 0andbyteWidth = width × 2. That is precisely the case where the correct and the incorrect conventions agree. The parity test added in #4 — "slices colors that match the eager parser" — therefore could not fail, no matter how wrongSpfViewwas. Line coverage was already 99.5%: every one of these lines executed, just never with a non-zero origin.The 8 new cases use hand-built fixtures with
left=2, top=1, right=6, bottom=4and 4 bytes of row padding, with a distinct colour per row so a pitch error decodes visibly wrong rather than merely differently.All 8 fail against the previous code (verified by stashing the source fixes and re-running):
Verification
Type check, build, and the full suite in both modes: 537 tests without a client, 539 with. Coverage holds at 99.52% lines / 93.28% branches / 99.74% functions / 99.15% statements.
Note on ordering
This branches off
main, so it does not include #5. Both touchCHANGELOG.mdunder[Unreleased]; whichever merges second may need a trivial rebase.Remaining review findings not addressed here (4–10): the
MetaFileEUC-KR read/write asymmetry,SotpFilealiasing the archive buffer, the semver/breaking-change classification, eagerLftFileallocation,renderTiledouble-pass, and themeasureLftTextsignature.🤖 Generated with Claude Code