feat(svm): draw the bitmaps a metafile carries, as png where that is smaller - #786
Merged
Conversation
This was referenced Aug 30, 2026
andiwand
force-pushed
the
feat/svm-text
branch
from
August 30, 2026 09:33
0380eea to
0f787dd
Compare
…smaller Closes #194. A chart whose data area is one big bitmap - `Vyplaty.svm` is 1.59 MB of `BMPEXSCALE` in a 1.63 MB file - rendered as an empty frame with axes and labels around nothing. No pixel decoder was needed. `SvmReader` reads a dib with `ReadDIB(…, bFileHeader=true)`, so the action body holds it *with* its `BITMAPFILEHEADER`: the bytes already are a `.bmp` file, and a browser reads them as one. Only the length has to be worked out, and `bfSize` is no help - it is written from the uncompressed size, so for a compressed dib it lies. The header's own `biSizeImage`, or its width, height and bit count, is the answer. They go out as a png rather than a bmp wherever the pixels can be copied row by row - uncompressed, 1/4/8/24/32 bits, palette expanded, rows turned right way up and bgr swapped. That is re-packing rather than decoding, and it earns its keep: the bitmap above is 1.59 MB as a bmp and 33 KB as a png, so the page it sits on is 158 KB instead of 2.9 MB. Anything compressed still goes out as the bmp it is. The png writing `pdf_image.cpp` already had moves to `util::png::write`, where both callers can reach it; its two tests move with it. A `BMPEX` may carry a transparency mask: a second dib, white where the bitmap does not show. An svg `<mask>` keeps what is white, so the mask image goes through an inverting `feColorMatrix`, written once per document. Still open, and neither occurs in the corpus: `ZCOMPRESS`, whose zlib stream would have to be inflated first, and the `MASK` family, which stencils one colour through a bitmap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CmCr22NW6wPQKiQidk96bq
This was referenced Aug 30, 2026
The bitmap comments that told the story twice: the inverting mask is explained in `AGENTS.md`, `BitmapBox` needs no gloss of its own field names, and `assumed_dpi` only has to say what the file does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQpLmJpJ87qbKoG8B7kbLY
andiwand
force-pushed
the
feat/svm-bitmaps
branch
from
August 30, 2026 09:45
b7d7c6f to
746d43a
Compare
A bitmap action with no size of its own is drawn at 96 dpi, and the hundredths of a millimetre a pixel takes there is 26.458. Truncating that to 26 before multiplying lost 1.7% of the size: sixteen pixels came out 416 wide where they measure 423. The pixel count multiplies first now. The reference output is unchanged - the corpus carries `BMPEXSCALE`, which names its own size, and no unsized action - so the pins stay where they are. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQpLmJpJ87qbKoG8B7kbLY
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.
🤖 Generated with Claude Code
Closes #194. Stage 4 of #772, stacked on #785 → #784 → #779 — review those
first; this branch's base is
feat/svm-text.The empty frame
odr-private/svm/Vyplaty.svmis a 1.63 MB file, and 1.59 MB of it is a singleBMPEXSCALE: the whole data area of the chart is one bitmap. We drew the axes,the grid and 79 labels around nothing. It is the only bitmap in 1125 harvested
metafiles, and it is exactly the file #194 named.
No decoder was needed
SvmReaderreads a dib withReadDIB(…, bFileHeader=true), so the action bodyholds it with its
BITMAPFILEHEADER— the bytes already are a.bmpfileand a browser reads them as one. The only thing to work out is the length, and
bfSizeis no help:ImplWriteDIBFileHeadercomputes it from theuncompressed size, so for a compressed dib it lies (this file's mask claims
522214 bytes and occupies 29478). The header's own
biSizeImage— or width,height and bit count — is the answer.
png, because 2.9 MB per page is not a rendering
The bytes go out as a png rather than a bmp wherever the pixels can be copied
row by row: uncompressed, 1/4/8/24/32 bits, palette expanded, rows turned right
way up, bgr swapped. Re-packing, not decoding.
Vyplaty.ods/sheet1.htmlAnything compressed (RLE, bit fields) still goes out as the bmp it is —
browsers read those, verified in Chrome for the RLE8 mask this very file
carries.
The png writing
pdf_image.cppalready had moves toutil::png::writeso bothcallers can reach it, with its two tests.
The mask
A
BMPEXmay carry a transparency mask: a second dib, white where the bitmapdoes not show (
Bitmap::Bitmap(rBmp, rMask)inverts it into an alphachannel). An svg
<mask>keeps what is white, so the mask image goes throughan inverting
feColorMatrix, written once per document.Still open
Neither occurs in the corpus, both are noted in
svm/PLAN.md:ZCOMPRESS, aLibreOffice-only compression whose zlib stream would have to be inflated first
(miniz is already a dependency), and the
MASKfamily, which stencils onecolour through a bitmap.
Verification
2 new tests, both from inline bytes — a dib built by hand in the test, drawn
where the action puts it and re-packed as a png; and a
BMPEXwhose maskelement, inverting filter and reference all come out.
Rendered in headless Chrome: the chart now matches what LibreOffice's own
--convert-to svgmakes of the same file. The reference output moves for thetwo pages of
ods/Vyplaty.odsand nothing else; the pin is advanced in thisbranch.