EncDotNet.S100.Renderers.Skia
EncDotNet.S100.Renderers.Skia rasterises S-100 vector scenes and coverage
grids to SkiaSharp bitmaps, with no map
control or UI framework. Reference it when you render S-100 data to images
without the EncDotNet.S100 facade: in a tile server, a batch image job, or your
own compositor. It draws the vector scene IR from
EncDotNet.S100.Rendering.Scene,
which it brings in as a dependency.
For the end-to-end path, from display list to PNG, see Render images and map tiles.
Install
dotnet add package EncDotNet.S100.Renderers.Skia
If you publish for linux-arm64, also follow
Linux arm64 native dependency.
Render a display list to PNG
HeadlessVectorRenderer.Render lowers a display list to a VectorScene,
fits the viewport to the scene's extent and rasterises it:
using EncDotNet.S100.Pipelines;
using EncDotNet.S100.Pipelines.Vector;
using EncDotNet.S100.Renderers.Skia.Scene;
using SkiaSharp;
static void RenderToPng(
IReadOnlyList<DrawingInstruction> instructions,
IFeatureGeometryProvider geometryProvider,
ColorPalette palette,
Func<string, string?>? symbolProvider,
Func<string, LineStyle?>? lineStyleProvider,
string outputPath)
{
using SKBitmap bitmap = HeadlessVectorRenderer.Render(
instructions,
geometryProvider,
palette,
symbolProvider,
lineStyleProvider,
symbolScale: 1.0,
textScale: 1.0,
widthPixels: 1024,
heightPixels: 1024,
background: RgbaColor.Transparent);
using var image = SKImage.FromBitmap(bitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
File.WriteAllBytes(outputPath, data.ToArray());
}
Optional parameters add an area-fill provider for pattern fills, hide
instruction categories, draw the offline basemap underneath (BasemapKind), or
render an explicit Viewport instead of fitting one. With an explicit viewport,
S-100 Part 9 scale-visibility culling applies.
Main entry points
Vector scenes
These types are in the EncDotNet.S100.Renderers.Skia.Scene namespace.
HeadlessVectorRenderer: renders a display list in one call, as above.BuildSceneandTryGetWorldBoundsexpose the lowering and bounds steps so a compositor can lower a dataset and fit it into a shared viewport.TryGetSeamAwareWorldBoundsframes datasets that cross the ±180° antimeridian on their true extent.SkiaDisplayListRenderer: draws aVectorScenefor a givenViewport. It implementsIVectorSceneRenderer<SKCanvas>.Renderreturns a new bitmap;RenderOntodraws onto a canvas you own.- Set
HonorScaleVisibilitytofalsewhen the viewport is fitted to the data rather than chosen by the user. A fitted scale isn't the dataset's compilation scale and would hide scale-limited detail. - Set
EnableSeamWraptofalsewhen you draw geometry that's already continuous across the antimeridian into a small viewport, such as a map tile. Otherwise, off-tile vertices of large polygons wrap to the other side of the world. - The
RenderOntooverload that takesOverlayDrawOptionsdraws a label overlay: it can suppress text, keep labels upright under a rotated viewport, and filter which point and text ops it draws.
- Set
LabelDeclutterer: given aVectorScene, returns the text ops to hide so labels don't overlap symbols or higher-priority labels (S-100 Part 9). Point symbols reserve their screen space first, then labels are placed in priority order. The result is deterministic.OverlayDrawOptions: the options for an overlayRenderOntopass: cull bounds, suppressed text, text rotation and screen centre, and point and text draw filters.
Coverage grids
SkiaCoverageRenderer: anICoverageRenderer<SKBitmap>that maps coverage grid cells (S-102, S-104, S-111) to pixel colours.SkiaCoverageArrowRenderer: draws oriented coverage symbols, such as S-111 current arrows.CoverageHeadlessRenderer: rasterises aStyledCoverageLayer.Renderfits the viewport to the grid;DrawOntodraws the grid and arrows into a shared viewport so they line up with vector layers in a composite.
Composite images
CompositeLayer: one layer of a composite, drawn against a sharedViewporton a transparent background. UseVectorCompositeLayerfor aVectorSceneandCoverageCompositeLayerfor a coverage layer. AVectorCompositeLayercan carryFinerCoverages, the data coverages of finer chart cells that hide it where they overlap and draw, and it draws only the part of its scene near the viewport, so drawing one tile of a large composite doesn't walk every op.HeadlessCompositeRenderer: clears the background once, then draws an ordered list ofCompositeLayers against a shared viewport. It doesn't decide the order or which layers to suppress. The S-98 interoperability engine (HeadlessCompositorinEncDotNet.S100.Datasets.Pipelines) makes that decision; this renderer draws the result.
Basemap and symbols
NaturalEarthBasemap: the bundled, offline Natural Earth 1:10m land layer (public domain). The data is already projected to EPSG:3857, cut into tiles, and stored at several levels of detail.SelectLevelpicks the coarsest level that stays within a pixel at a given resolution,GetLandPolygonsreturns that level's land in a rectangle, andGetLandScenereturns the land in aViewportasAreaPaintOps filled withLandFill(RGB 238, 232, 220). The headless renderers,HeadlessCompositorand SoundCharts all draw land from this one asset.SkiaSvgRasterizer: rasterises SVG portrayal symbols into tiled pattern bitmaps.
Linux arm64 native dependency
When you publish a linux-arm64 executable that uses this renderer, reference
the self-contained SkiaSharp native library in your application project:
<!-- In your app's .csproj -->
<ItemGroup>
<PackageReference Include="SkiaSharp.NativeAssets.Linux" ExcludeAssets="all" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
</ItemGroup>
The arm64 libSkiaSharp.so in SkiaSharp.NativeAssets.Linux declares
undefined uuid_* and FT_Get_BDF_Property symbols. Once fontconfig and
freetype load on an ordinary arm64 host, any render crashes with
undefined symbol: …. The NoDependencies build is self-contained and renders
on both x64 and arm64. The executable chooses native assets for its runtime, so
this library can't make the swap for you. See
issue #23.
Without fontconfig, the host may have no usable system font. The package
embeds an Open Sans face (Apache-2.0) and uses it for labels in that case; see
Embedded render fonts.
Stability and versioning
The supported surface of this package is the headless rendering entry points:
SkiaDisplayListRenderer (including its RenderOnto overloads),
HeadlessVectorRenderer, CoverageHeadlessRenderer,
HeadlessCompositeRenderer, the CompositeLayer family
(VectorCompositeLayer, CoverageCompositeLayer), OverlayDrawOptions,
LabelDeclutterer, SkiaCoverageRenderer, SkiaCoverageArrowRenderer and
SkiaSvgRasterizer. Internal and undocumented types, such as colour and font
helpers and diagnostics, can change at any time.
All EncDotNet.S100.* packages share one version, taken from the release git
tag. Versioning follows Semantic Versioning: from 1.0.0
on, a breaking change to the supported surface comes only in a major version.
Below 1.0.0, a minor version can include breaking changes, and the release
notes list them.
How SkiaDisplayListRenderer draws a scene
This section is for contributors and for hosts that redraw a scene every frame, as the Mapsui tiled renderer does for its symbol overlay.
- Lowering.
VectorSceneBuilder(inEncDotNet.S100.Rendering.Scene) applies S-100 Part 9 draw order; colour, symbol and line-style resolution; millimetre-to-pixel conversion (1 px = 0.32 mm); text-anchor selection; and the latitude/longitude-to-EPSG:3857 half of the projection. This renderer applies the other half, EPSG:3857 to screen pixels, from theViewport. - Pattern fills. When a pattern resolver is supplied, as both the headless
path and the Mapsui tiled renderer do, pattern fills are lowered into the
scene as
PatternAreaPaintOps.PatternPriorityClipperclips them so a lower-priority pattern doesn't show through a higher-priority pattern or an opaque solid fill. - Antimeridian. For a fitted viewport,
SeamAwareBoundsAccumulatorframes datasets that cross ±180° on their true extent, andWorldToScreenwraps ops into the shifted window at draw time. Turn this off withEnableSeamWraporWorldToScreen.Create(viewport, allowSeamWrap). - Per-frame cost. Parsed symbol pictures are cached for the process, keyed
by the resolved SVG. Point and text ops whose anchor falls outside the
viewport plus
PointCullMarginPxare skipped before any other work. Line ops whose padded bounding box misses the cull rectangle are skipped before the native draw. Text drawing reusesSKFontandSKPaintobjects for the length of a render; line drawing reuses oneSKPath, oneSKPaint, a point buffer and a dash-effect cache. - Rotated viewports.
RenderOntotakes an optional cull rectangle so a caller that rotates the canvas can widen it to the rotated viewport's bounding box. - Missing glyphs.
DrawTextfalls back per run of text: characters the primary typeface lacks are drawn with a face fromSKFontManager.MatchCharacterinstead of.notdefboxes.