Image resources
The shared Map facade provides addImage, updateImage, removeImage, getImage, hasImage and listImages. Queries, updates and removals include loaded style sprites and runtime images. Scripts import map from sqsmap; the independent browser SDK uses MapView. Await queries, and confirm chained writes with await map.sqs.flush() or await view.flush() in the SDK. Applied command confirmation does not imply network readiness.
// @gis-api 0.5-session
// @permissions ["project.read","map.write"]
import {map} from "sqsmap";
map.addImage("marker", {width:1,height:1,data:new Uint8Array([0,0,255,255])}, {pixelRatio:1,sdf:false});
await map.sqs.flush();
const image = await map.getImage("marker");
image.data.data.set([255,0,0,255]);
map.updateImage("marker", image.data);
await map.sqs.flush();
export default {ids:await map.listImages(),info:await map.sqs.getImageInfo("marker")};
Input is straight-alpha RGBA8; data accepts Uint8Array, Uint8ClampedArray or an ordinary byte array. Writes capture bytes at submission; later input edits do not change the map. getImage returns {data:{width,height,data:Uint8Array},pixelRatio,sdf}, or undefined for a missing ID. Returned pixels are caller-owned copies that may be edited and passed to updateImage; metadata objects are immutable. Updates require matching dimensions and retain pixelRatio/sdf. The script extension map.sqs.getImageInfo(id) (SDK: view.getImageInfo) reports source, imageInstanceId, exact string revision and memory metadata. Remove/re-add creates a new identity; historical copies remain valid.
Limits are128 user registrations/overrides,1..512 pixels per edge,1MiB raw pixels per image and16MiB per map. The combined atlas also has a2048-pixel edge limit. pixelRatio is0.125..16; sdf is boolean, defaulting to1/false. Larger existing sprite images can be read; updates still follow the raw image budget. Image write JSON allows8MiB, with16MiB for all queued arguments; ordinary calls retain64KiB. VM memory and final-result budgets still apply: avoid exporting full large pixel arrays as execution summaries.
Style sprite updates preserve identity and advance revision; same-size overrides may reuse the original atlas slot. Removal creates a same-generation tombstone, so a late sprite response cannot revive that ID. New style generations/clear release old resources; failed style replacement restores them. Duplicate IDs, invalid dimensions/bytes, unknown arguments, atlas and event capacity failures report explicit errors and preserve prior pixels/atlas.
Retained scripts subscribe with map.on("sqs.imageschange", listener) to successful explicit image writes. Historical payload fields are method/id/imageCount/runtimeByteLength. Unsubscribe, script stop and map destruction release listeners. The independent SDK exposes view.onImagesChange. This event is not network progress or style readiness; the complete example initializes from actual basemapStatus and view notifications, and presents public attribution queries/events. Applications own layer and control UI.
Native ABI1 adds capability SQSM_MAP_IMAGES=1048576 and sqsm_map_image_json. The strict request is {method,args} with8MiB request/32MiB output budgets; missing getImage is null on the wire and becomes undefined in JS. Queries preserve command watermarks; writes use common confirmation/events. Output is independently owned, zeroed on failure, readable after map destruction and released with sqsm_map_bytes_free. Use matching SDK/declarations and capabilities rather than treating missing old-library methods as successful.
Identical cross-host script / Complete HTML. The copyable code below includes operation buttons, querying, initialization, events, attribution and destruction cleanup. Serve the HTML over HTTP(S). It uses bundled synthetic offline vector tiles without external map keys; deploy the referenced development SDK and fixtures together.
loadImage, stretchable images, dynamic callbacks and complete font fallback remain in subsequent work; unsupported options report CAPABILITY_UNAVAILABLE. Methods and semantics are shared where hosts are connected. Mobile application scripts, Windows/macOS, hardware and formal release retain separate acceptance. SqsMap has an independently implemented engine and follows MapLibre public Map method names and argument semantics.
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SqsMap image resources</title>
<style>body{margin:0;font:16px system-ui;background:#f5f7fa;color:#182330}main{display:grid;grid-template-columns:1fr 280px;min-height:100vh}#map{min-height:70vh}aside{padding:20px}button{padding:8px;margin:4px}pre{white-space:pre-wrap;overflow-wrap:anywhere}@media(max-width:650px){main{grid-template-columns:1fr}}</style>
<main><div id="map"></div><aside><h1>Editable map image</h1><button id="add" disabled>Add blue image</button><button id="update" disabled>Update to red</button><button id="remove" disabled>Remove image</button><button id="query" disabled>Read image metadata</button><button id="destroy">Destroy map</button><p id="attribution"></p><pre id="status">Starting</pre></aside></main>
<script type="module">
import {MapView} from 'https://www.sqsmap.com/sdk/sqsmap-core/dist/index.js';
const status=document.querySelector('#status'),buttons=['add','update','remove','query'].map(id=>document.getElementById(id));let view,closed=false,busy=false,initialized=false,offImages,offView,offAttributions,offError;
function image(color){const data=new Uint8Array(16*16*4);for(let i=0;i<data.length;i+=4)data.set(color,i);return {width:16,height:16,data};}
async function refresh(){if(!view||closed)return;const exists=await view.hasImage('marker');if(closed)return;buttons[0].disabled=busy||exists||!initialized;buttons[1].disabled=buttons[2].disabled=busy||!exists;buttons[3].disabled=busy;}
async function perform(fn){if(closed||busy)return;busy=true;try{await refresh();await fn();await view.flush({render:true});await refresh();}catch(error){if(!closed)status.textContent=String(error);}finally{busy=false;if(!closed)await refresh();}}
buttons[0].addEventListener('click',()=>perform(()=>view.addImage('marker',image([0,0,255,255]))));
buttons[1].addEventListener('click',()=>perform(()=>view.updateImage('marker',image([255,0,0,255]))));
buttons[2].addEventListener('click',()=>perform(()=>view.removeImage('marker')));
buttons[3].addEventListener('click',()=>perform(async()=>{const snapshot=await view.getImage('marker');status.textContent=JSON.stringify({ids:await view.listImages(),info:await view.getImageInfo('marker'),firstPixel:snapshot?Array.from(snapshot.data.data.slice(0,4)):null},null,2);}));
function destroy(){if(closed)return;closed=true;offImages?.();offView?.();offAttributions?.();offError?.();view?.destroy();for(const button of buttons)button.disabled=true;status.textContent='Map destroyed; observers removed.';}
document.querySelector('#destroy').addEventListener('click',destroy);window.addEventListener('pagehide',destroy,{once:true});
try{
view=await MapView.create(document.querySelector('#map'),{worker:false});if(closed){view.destroy();throw Error('Initialization cancelled.');}
if(view.capabilities.imageRegistry!==true)throw Error('A matching image-capable SDK is required.');
offImages=view.onImagesChange(event=>{if(!closed){status.textContent=JSON.stringify(event,null,2);refresh().catch(error=>{if(!closed)status.textContent=String(error);});}});
offError=view.on('error',event=>{if(!closed)status.textContent=event.message;});
async function attribution(){try{const value=await view.getAttributions();if(!closed)document.querySelector('#attribution').textContent=value.text;}catch(error){if(!closed)status.textContent=String(error);}}
offAttributions=view.onAttributionsChange(attribution);await attribution();
// Read actual style progress, then initialize the image once. No fixed loading delay.
offView=view.on('view',()=>{if(initialized||closed||!['ready','degraded'].includes(view.basemapStatus.state)||view.basemapStatus.readyTiles===0)return;initialized=true;perform(()=>view.addImage('marker',image([0,0,255,255])));});
view.jumpTo({center:[0,0],zoom:0});view.setStyle('https://www.sqsmap.com/sdk/sqsmap-core/image-fixtures/style.json');await view.flush();await refresh();
}catch(error){if(!closed)status.textContent=String(error);}
</script></html>
For external image loading, see Load external images.
Common point image symbols also support demand completion and SDF recoloring. See Symbol images and demand events. General text/collision/3D symbols and the mobile host remain pending.