feat(thumbnail): on-screen framing + PNG/RLE8 preview, far smaller files

The embedded DWG preview was the whole drawing fit to a canvas, encoded
as a 24-bit uncompressed DIB (~180 KB at 256²) — the bulk of the gap
between an OCS-saved file and AutoCAD's. Three changes:

- Frame it like the screen. `from_scene` now projects every wire vertex
  through the live camera into a viewport-aspect canvas, so the thumbnail
  is the current pan / zoom / rotation and only the visible region, not
  the full extent. Off-screen / clipped segments don't draw (a `None`
  projection or NaN separator breaks the polyline run). Works for any
  view. `stamp_thumbnail` passes the model pane's pixel size and the
  target version.

- PNG for R2013+ (AC1027+). A line drawing on a flat background is almost
  one colour, so PNG collapses to a few KB. PNG previews are only valid
  from R2013, so older targets keep a BMP.

- BMP is now 8-bit palettised, BI_RLE8-compressed (was 24-bit raw). The
  long single-colour runs of a preview compress to a handful of KB; a
  view with >256 colours (rare) falls back to the 24-bit DIB. A full
  256-entry palette is written so the reader's `(1<<bpp)*4` pixel-data
  offset lands correctly.

Net: the preview drops from ~120 KB to ~2-5 KB on every version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Fable 5 committed 2026-07-23 14:09:38 +03:00
1 parent 3326c5f0c8
commit f2ad1cc0c8
2 files changed
+224 -79

No files matched your search

+11 -5
View File
@@ -749,9 +749,14 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
/// Rasterize the current drawing into the document's DWG preview image, so
/// a saved file shows a thumbnail in file browsers and other CAD apps.
/// A degenerate/empty drawing clears any stale preview.
pub(super) fn stamp_thumbnail(&mut self, i: usize) {
/// `target` is the version the file is about to be written as — a PNG
/// preview (few KB vs a ~180 KB DIB) is only valid from R2013 (AC1027) on.
pub(super) fn stamp_thumbnail(&mut self, i: usize, target: acadrust::DxfVersion) {
let png = target >= acadrust::DxfVersion::AC1027;
// Frame the thumbnail exactly as the model pane shows it now (current
// pan/zoom/rotation, visible region only), so pass the pane pixel size.
self.tabs[i].scene.document.preview =
crate::io::thumbnail::from_scene(&self.tabs[i].scene);
crate::io::thumbnail::from_scene(&self.tabs[i].scene, png, self.vp_size);
// The on-disk thumbnail will change — drop the cached Start-page handle
// so it re-reads the updated file on the next refresh.
if let Some(p) = self.tabs[i].current_path.clone() {
@@ -784,7 +789,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
crate::io::write_backup(&path);
}
self.sync_truck_solids_to_acis(i);
self.stamp_thumbnail(i);
let ver = self.tabs[i].scene.document.version;
self.stamp_thumbnail(i, ver);
match crate::io::save(&self.tabs[i].scene.document, &path) {
Ok(()) => {
self.command_line
@@ -890,7 +896,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
sync_annotation_scale_header(&mut self.tabs[i].scene);
self.stamp_header_sysvars(i);
self.sync_truck_solids_to_acis(i);
self.stamp_thumbnail(i);
self.stamp_thumbnail(i, version);
match crate::io::save_to_bytes(&self.tabs[i].scene.document, ext, version) {
Ok(bytes) => {
crate::sys::download_bytes(&filename, &bytes);
@@ -931,7 +937,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
// Persist Ortho / running OSNAP into the header (Save-As path).
self.stamp_header_sysvars(i);
self.sync_truck_solids_to_acis(i);
self.stamp_thumbnail(i);
self.stamp_thumbnail(i, version);
if self.backup_on_save {
crate::io::write_backup(&path);
}