From 2166cbb3519cbf462c4e42446d7073ef309f02da Mon Sep 17 00:00:00 2001 From: Pascal Garber Date: Fri, 12 Jun 2026 15:58:09 +0200 Subject: [PATCH] feat(engine): support map background colour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2014 zmap format fills each room with a header background colour before painting tiles — the ported maps only stored the tiles, so sparse maps (Field is ~19% tiles) rendered as void in the editor. - MapData.backgroundColor (optional #rrggbb, below all layers) - engine: map colour -> GL clear colour on loadMap (a colour Actor would raster through the 2D-canvas path; clear colour is pure GL and matches the original game's fill-the-screen semantic) - MapPreview: fill the map bounds in the widget paint + the bake - games/oot2d-2014: backgroundColor injected into all 19 maps from the zmap headers (exporter: PixelRPG/oot-2d e3ea382) --- games/oot2d-2014/maps/bombchu.json | 1 + games/oot2d-2014/maps/castle.json | 6 +++++- games/oot2d-2014/maps/deku-0.json | 1 + games/oot2d-2014/maps/end.json | 6 +++++- games/oot2d-2014/maps/field.json | 6 +++++- games/oot2d-2014/maps/guards.json | 6 +++++- games/oot2d-2014/maps/houses-kokiri.json | 6 +++++- games/oot2d-2014/maps/houses-ranch.json | 6 +++++- games/oot2d-2014/maps/houses.json | 6 +++++- games/oot2d-2014/maps/kakariko.json | 1 + games/oot2d-2014/maps/kokiri-forest.json | 6 +++++- games/oot2d-2014/maps/lost-woods.json | 6 +++++- games/oot2d-2014/maps/market-central.json | 6 +++++- games/oot2d-2014/maps/market-enter.json | 6 +++++- games/oot2d-2014/maps/pots.json | 6 +++++- games/oot2d-2014/maps/ranch.json | 6 +++++- games/oot2d-2014/maps/shooting-gallery.json | 6 +++++- games/oot2d-2014/maps/smb.json | 6 +++++- games/oot2d-2014/maps/windmill.json | 6 +++++- packages/engine/src/engine.ts | 10 ++++++++++ packages/engine/src/types/data/MapData.ts | 9 +++++++++ .../gjs/src/widgets/editor/map-preview.ts | 20 ++++++++++++++++--- 22 files changed, 119 insertions(+), 19 deletions(-) diff --git a/games/oot2d-2014/maps/bombchu.json b/games/oot2d-2014/maps/bombchu.json index 11d148e5..db291a9f 100644 --- a/games/oot2d-2014/maps/bombchu.json +++ b/games/oot2d-2014/maps/bombchu.json @@ -2,6 +2,7 @@ "id": "bombchu", "name": "Bombchu", "version": "1.0.0", + "backgroundColor": "#303030", "tileWidth": 8, "tileHeight": 8, "columns": 30, diff --git a/games/oot2d-2014/maps/castle.json b/games/oot2d-2014/maps/castle.json index c31e3375..4425cddb 100644 --- a/games/oot2d-2014/maps/castle.json +++ b/games/oot2d-2014/maps/castle.json @@ -2,6 +2,7 @@ "id": "castle", "name": "Castle", "version": "1.0.0", + "backgroundColor": "#409740", "tileWidth": 8, "tileHeight": 8, "columns": 123, @@ -58254,5 +58255,8 @@ "source": "Maps/Castle.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 433, + "atlasY": 62 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/deku-0.json b/games/oot2d-2014/maps/deku-0.json index caf66bb7..3478e815 100644 --- a/games/oot2d-2014/maps/deku-0.json +++ b/games/oot2d-2014/maps/deku-0.json @@ -2,6 +2,7 @@ "id": "deku-0", "name": "Deku 0", "version": "1.0.0", + "backgroundColor": "#544429", "tileWidth": 8, "tileHeight": 8, "columns": 325, diff --git a/games/oot2d-2014/maps/end.json b/games/oot2d-2014/maps/end.json index b6a175ae..91ad9101 100644 --- a/games/oot2d-2014/maps/end.json +++ b/games/oot2d-2014/maps/end.json @@ -2,6 +2,7 @@ "id": "end", "name": "End", "version": "1.0.0", + "backgroundColor": "#2C2C2C", "tileWidth": 8, "tileHeight": 8, "columns": 33, @@ -821,5 +822,8 @@ "source": "Maps/End.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 214, + "atlasY": 163 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/field.json b/games/oot2d-2014/maps/field.json index 1f0f0d3f..bb52865a 100644 --- a/games/oot2d-2014/maps/field.json +++ b/games/oot2d-2014/maps/field.json @@ -2,6 +2,7 @@ "id": "field", "name": "Field", "version": "1.0.0", + "backgroundColor": "#409740", "tileWidth": 8, "tileHeight": 8, "columns": 372, @@ -162354,5 +162355,8 @@ "source": "Maps/Field.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 390, + "atlasY": 470 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/guards.json b/games/oot2d-2014/maps/guards.json index 89f2fda6..68a040c4 100644 --- a/games/oot2d-2014/maps/guards.json +++ b/games/oot2d-2014/maps/guards.json @@ -2,6 +2,7 @@ "id": "guards", "name": "Guards", "version": "1.0.0", + "backgroundColor": "#409740", "tileWidth": 8, "tileHeight": 8, "columns": 204, @@ -39750,5 +39751,8 @@ "source": "Maps/Guards.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1060, + "atlasY": 331 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/houses-kokiri.json b/games/oot2d-2014/maps/houses-kokiri.json index 9f828056..5374c470 100644 --- a/games/oot2d-2014/maps/houses-kokiri.json +++ b/games/oot2d-2014/maps/houses-kokiri.json @@ -2,6 +2,7 @@ "id": "houses-kokiri", "name": "Houses Kokiri", "version": "1.0.0", + "backgroundColor": "#623E3B", "tileWidth": 8, "tileHeight": 8, "columns": 194, @@ -27732,5 +27733,8 @@ "source": "Maps/Houses_Kokiri.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1145, + "atlasY": 532 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/houses-ranch.json b/games/oot2d-2014/maps/houses-ranch.json index 3bbbaf20..8d65c8ff 100644 --- a/games/oot2d-2014/maps/houses-ranch.json +++ b/games/oot2d-2014/maps/houses-ranch.json @@ -2,6 +2,7 @@ "id": "houses-ranch", "name": "Houses Ranch", "version": "1.0.0", + "backgroundColor": "#382828", "tileWidth": 8, "tileHeight": 8, "columns": 102, @@ -25722,5 +25723,8 @@ "source": "Maps/Houses_Ranch.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1664, + "atlasY": 426 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/houses.json b/games/oot2d-2014/maps/houses.json index 023d3ef9..f3179d68 100644 --- a/games/oot2d-2014/maps/houses.json +++ b/games/oot2d-2014/maps/houses.json @@ -2,6 +2,7 @@ "id": "houses", "name": "Houses", "version": "1.0.0", + "backgroundColor": "#382828", "tileWidth": 8, "tileHeight": 8, "columns": 124, @@ -15798,5 +15799,8 @@ "source": "Maps/Houses.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 30, + "atlasY": 555 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/kakariko.json b/games/oot2d-2014/maps/kakariko.json index d6411d10..91b1a65c 100644 --- a/games/oot2d-2014/maps/kakariko.json +++ b/games/oot2d-2014/maps/kakariko.json @@ -2,6 +2,7 @@ "id": "kakariko", "name": "Kakariko", "version": "1.0.0", + "backgroundColor": "#489848", "tileWidth": 8, "tileHeight": 8, "columns": 166, diff --git a/games/oot2d-2014/maps/kokiri-forest.json b/games/oot2d-2014/maps/kokiri-forest.json index b292ec64..26f38eac 100644 --- a/games/oot2d-2014/maps/kokiri-forest.json +++ b/games/oot2d-2014/maps/kokiri-forest.json @@ -2,6 +2,7 @@ "id": "kokiri-forest", "name": "Kokiri Forest", "version": "1.0.0", + "backgroundColor": "#768D2D", "tileWidth": 8, "tileHeight": 8, "columns": 218, @@ -127110,5 +127111,8 @@ "source": "Maps/Kokiri_Forest.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1827, + "atlasY": 715 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/lost-woods.json b/games/oot2d-2014/maps/lost-woods.json index 6b5f2c6c..ab957934 100644 --- a/games/oot2d-2014/maps/lost-woods.json +++ b/games/oot2d-2014/maps/lost-woods.json @@ -2,6 +2,7 @@ "id": "lost-woods", "name": "Lost Woods", "version": "1.0.0", + "backgroundColor": "#768D2D", "tileWidth": 8, "tileHeight": 8, "columns": 296, @@ -155760,5 +155761,8 @@ "source": "Maps/Lost_Woods.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1195, + "atlasY": 683 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/market-central.json b/games/oot2d-2014/maps/market-central.json index a3932b1d..5b0621f8 100644 --- a/games/oot2d-2014/maps/market-central.json +++ b/games/oot2d-2014/maps/market-central.json @@ -2,6 +2,7 @@ "id": "market-central", "name": "Market Central", "version": "1.0.0", + "backgroundColor": "#489848", "tileWidth": 8, "tileHeight": 8, "columns": 102, @@ -46800,5 +46801,8 @@ "source": "Maps/Market_Central.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 46, + "atlasY": 1101 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/market-enter.json b/games/oot2d-2014/maps/market-enter.json index e1d2a318..40ef62ed 100644 --- a/games/oot2d-2014/maps/market-enter.json +++ b/games/oot2d-2014/maps/market-enter.json @@ -2,6 +2,7 @@ "id": "market-enter", "name": "Market Enter", "version": "1.0.0", + "backgroundColor": "#489848", "tileWidth": 8, "tileHeight": 8, "columns": 32, @@ -4878,5 +4879,8 @@ "source": "Maps/Market_Enter.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1888, + "atlasY": 175 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/pots.json b/games/oot2d-2014/maps/pots.json index ed3420ae..a97c4f21 100644 --- a/games/oot2d-2014/maps/pots.json +++ b/games/oot2d-2014/maps/pots.json @@ -2,6 +2,7 @@ "id": "pots", "name": "Pots", "version": "1.0.0", + "backgroundColor": "#2A2E28", "tileWidth": 8, "tileHeight": 8, "columns": 24, @@ -4506,5 +4507,8 @@ "source": "Maps/Pots.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 1624, + "atlasY": 127 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/ranch.json b/games/oot2d-2014/maps/ranch.json index 4f368b70..7de4865b 100644 --- a/games/oot2d-2014/maps/ranch.json +++ b/games/oot2d-2014/maps/ranch.json @@ -2,6 +2,7 @@ "id": "ranch", "name": "Ranch", "version": "1.0.0", + "backgroundColor": "#4A9649", "tileWidth": 8, "tileHeight": 8, "columns": 114, @@ -55104,5 +55105,8 @@ "source": "Maps/Ranch.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 109, + "atlasY": 1339 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/shooting-gallery.json b/games/oot2d-2014/maps/shooting-gallery.json index 22177327..b299691c 100644 --- a/games/oot2d-2014/maps/shooting-gallery.json +++ b/games/oot2d-2014/maps/shooting-gallery.json @@ -2,6 +2,7 @@ "id": "shooting-gallery", "name": "Shooting Gallery", "version": "1.0.0", + "backgroundColor": "#303030", "tileWidth": 8, "tileHeight": 8, "columns": 24, @@ -4386,5 +4387,8 @@ "source": "Maps/Shooting_Gallery.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 507, + "atlasY": 1486 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/smb.json b/games/oot2d-2014/maps/smb.json index 5e30fcaf..2eaecc90 100644 --- a/games/oot2d-2014/maps/smb.json +++ b/games/oot2d-2014/maps/smb.json @@ -2,6 +2,7 @@ "id": "smb", "name": "SMB", "version": "1.0.0", + "backgroundColor": "#5C94FC", "tileWidth": 8, "tileHeight": 8, "columns": 448, @@ -19067,5 +19068,8 @@ "source": "Maps/SMB.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 554, + "atlasY": 1296 + } } \ No newline at end of file diff --git a/games/oot2d-2014/maps/windmill.json b/games/oot2d-2014/maps/windmill.json index 4774c8a3..fe87d4b7 100644 --- a/games/oot2d-2014/maps/windmill.json +++ b/games/oot2d-2014/maps/windmill.json @@ -2,6 +2,7 @@ "id": "windmill", "name": "Windmill", "version": "1.0.0", + "backgroundColor": "#606060", "tileWidth": 8, "tileHeight": 8, "columns": 32, @@ -4794,5 +4795,8 @@ "source": "Maps/Windmill.zmap from 'The Legend of Zelda: Ocarina of Time 2D' v0.10.2 by CheerfulSage & GodsTurf (oot-2d.com, 2014) \u2014 archive the-legend-of-zelda-ocarina-of-time-2d-0-10-2-en-win.zip in the PixelRPG/oot-2d repo; converted by tools/export-pixelrpg-maps.py (zmap parser: fluxcompile/oot2d-map)" }, "objectPlacements": [], - "editorData": {} + "editorData": { + "atlasX": 81, + "atlasY": 1647 + } } \ No newline at end of file diff --git a/packages/engine/src/engine.ts b/packages/engine/src/engine.ts index 9d84db84..67a53130 100644 --- a/packages/engine/src/engine.ts +++ b/packages/engine/src/engine.ts @@ -296,6 +296,16 @@ export class Engine { newMapScene.camera.zoom = carryZoom } + // Map-declared room colour → GL clear colour. A colour *actor* + // would go through Excalibur's `Rectangle` Raster (2D-canvas + // rasterise), which the GJS canvas path doesn't survive — the + // clear colour is pure GL and also matches the original-game + // semantic (fill the screen, tiles on top). Reset to transparent + // for maps without one so the editor backdrop shows through. + this.excalibur.backgroundColor = mapResource.mapData.backgroundColor + ? Color.fromHex(mapResource.mapData.backgroundColor) + : Color.Transparent + // Re-entry: drop the stale scene instance so addScene doesn't // collide and the room rebuilds fresh from data. if (this.excalibur.scenes[mapId]) this.excalibur.removeScene(mapId) diff --git a/packages/engine/src/types/data/MapData.ts b/packages/engine/src/types/data/MapData.ts index 08b7be6d..b861f2ce 100644 --- a/packages/engine/src/types/data/MapData.ts +++ b/packages/engine/src/types/data/MapData.ts @@ -58,6 +58,15 @@ export interface MapData { */ version: string + /** + * Optional solid fill colour (`#rrggbb`) painted across the map + * bounds *below* every tile layer. Sparse maps (ported games often + * store only detail tiles over a flat room colour) rely on this to + * not render as void — the engine adds a background rect and the + * editor's previews fill with it. Absent = transparent. + */ + backgroundColor?: string + /** * Array of layers that make up the map * Each layer's tiles will be converted to Excalibur Tiles diff --git a/packages/gjs/src/widgets/editor/map-preview.ts b/packages/gjs/src/widgets/editor/map-preview.ts index 19f26b47..d57829d1 100644 --- a/packages/gjs/src/widgets/editor/map-preview.ts +++ b/packages/gjs/src/widgets/editor/map-preview.ts @@ -49,6 +49,8 @@ export class MapPreview extends Gtk.Widget { private _mapWidth = 0 private _mapHeight = 0 private _accentColor: Gdk.RGBA + /** Map-declared fill behind the tiles (`MapData.backgroundColor`). */ + private _mapBackground: Gdk.RGBA | null = null private _loaded = false // The composited tiles, rasterised to ONE texture so repaints (atlas // pan, card hover) are O(1) instead of re-rendering N clipped tile nodes @@ -141,6 +143,12 @@ export class MapPreview extends Gtk.Widget { this._mapWidth = mapData.columns * mapData.tileWidth this._mapHeight = mapData.rows * mapData.tileHeight + this._mapBackground = null + if (mapData.backgroundColor) { + const rgba = new Gdk.RGBA() + if (rgba.parse(mapData.backgroundColor)) this._mapBackground = rgba + } + const ops: DrawOp[] = [] for (const layer of mapData.layers ?? []) { if (!layer.visible || !layer.sprites) continue @@ -175,7 +183,7 @@ export class MapPreview extends Gtk.Widget { background.init(0, 0, width, height) snapshot.append_color(this._accentColor, background) - if (!this._loaded || !this._ops.length || !this._mapWidth || !this._mapHeight) return + if (!this._loaded || !this._mapWidth || !this._mapHeight) return const scale = Math.min(width / this._mapWidth, height / this._mapHeight) const dw = this._mapWidth * scale @@ -183,6 +191,11 @@ export class MapPreview extends Gtk.Widget { const dest = new Graphene.Rect() dest.init((width - dw) / 2, (height - dh) / 2, dw, dh) + // The map's own room colour sits below the tiles (the bake also + // includes it; this covers the pre-bake paint + ops-free maps). + if (this._mapBackground && !this._baked) snapshot.append_color(this._mapBackground, dest) + if (!this._ops.length && !this._baked) return + // Fast path: paint the pre-rasterised composite (one texture). if (this._baked) { snapshot.append_scaled_texture(this._baked, Gsk.ScalingFilter.NEAREST, dest) @@ -221,11 +234,12 @@ export class MapPreview extends Gtk.Widget { // thumbnail texture (it's only ever shown card-sized). const bakeScale = Math.min(1, BAKE_MAX_EDGE / Math.max(this._mapWidth, this._mapHeight)) const sub = Gtk.Snapshot.new() + const region = new Graphene.Rect() + region.init(0, 0, this._mapWidth * bakeScale, this._mapHeight * bakeScale) + if (this._mapBackground) sub.append_color(this._mapBackground, region) for (const op of this._ops) this._paintTile(sub, op, bakeScale) const node = sub.to_node() if (!node) return null - const region = new Graphene.Rect() - region.init(0, 0, this._mapWidth * bakeScale, this._mapHeight * bakeScale) try { return renderer.render_texture(node, region) } catch (error) {