ThinkGeo.com    |     Documentation    |     Premium Support

Feature request: label opacity independent of Layer.Opacity (classic and GPU)

When a FeatureLayer is see-through ( Opacity below 1), its labels become see-through too, in both renderers. Users often set a layer to 50–70 % so the base map shows through, but they still want readable labels.

Classic renderer: Layer.Opacity blends the whole rendered tile, labels included.

GPU (ThinkGeo.Gpu 15.0.0-beta154): FeatureLayerTranslator multiplies the text and icon opacity by FeatureLayer.Opacity . Shapes are fine — fills, lines and circles keep their own opacity and the group is blended at the layer opacity (the beta148 fix). Only symbols are multiplied, and there is no way to change it afterwards: SymbolStyleLayer is read-only, and MapStyle has SetHeatmapPaint but nothing for symbols. Making the text colour more opaque does not help; it is already at 100 %.

Request: a FeatureLayer.LabelOpacity property (or an option “labels ignore layer opacity”), honoured by the classic renderer and by the GPU translator. Labels would then keep their own opacity no matter how see-through the layer is.

Small example

var layer = new InMemoryFeatureLayer();
layer.InternalFeatures.Add(new Feature(0, 0, "p", new Dictionary<string, string> { ["name"] = "Label" }));
layer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = new PointStyle(PointSymbolType.Circle, 8, GeoBrushes.Blue);
layer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = new TextStyle("name", new GeoFont("Arial", 12), GeoBrushes.Black);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
layer.Opacity = 0.25;

var style = new MapStyle().AddFeatureLayer(layer);
// The SymbolStyleLayer resolves a paint with Opacity 0.25. Expected: 1.0, the brush is opaque.
// The classic renderer draws the label at 25 % as well.

Our workarounds today (we would rather not depend on them):

  • Classic: a custom ZoomLevel in CustomZoomLevels that draws the shapes offscreen at the layer opacity and the labels directly. We need this hook because ShapeFileFeatureLayer.DrawCore does not raise DrawingFeatures and ZoomLevelSet.GetZoomLevelForDrawing is not virtual.
  • GPU: we translate each layer twice on the same FeatureSourceVectorTileSource — shapes at the layer opacity, text styles at opacity 1 — and add both under the same source id.

Versions: ThinkGeo.Core / ThinkGeo.UI.Wpf / ThinkGeo.Gpu 15.0.0-beta154, .NET 10, WPF.

Alternative it would also be okay to have a flag to disable that labels share opacity with the layer.

Hi Julian,

Both renderers can already do this:

GPU. There is a slot for exactly this, and a sample in the HowDoI: Styling -> Insert a Layer Under the Labels.


It inserts a translucent wash above the roads and below the labels, so everything under it is muted and the labels stay crisp:

await style.OpenAsync();   // the slot is found by walking the loaded layers
style.InsertStyleLayersAt(StyleLayerSlot.UnderLabels, washSource, "washsrc",
    index => StyleLayer.CreateFill("wash", index, "washsrc", "world",
        paint: (_, _) => new FillPaint(GeoColors.White, 0.6f, null, false, 0f, 0f)));

washSource is a FeatureSourceVectorTileSource holding one world-covering polygon. Leave FeatureLayer.Opacity at 1 and control the effect with the wash’s own opacity. An overlay cannot do this because an overlay is always above everything, labels included.

Classic. Add the same data as two layers and give each its own opacity:

shapes.LabelDisplayMode = LabelDisplayMode.ShapesOnly; 
shapes.Opacity = 0.5;
labels.LabelDisplayMode = LabelDisplayMode.LabelsOnly;
labels.Opacity = 1.0;

If neither covers your case — say, you need the shapes themselves translucent against each other rather than a wash over them — tell us what it looks like.

Thanks,
Ben

1 Like

Thanks Ben! You said “Add the same data as two layers and give each its own opacity” - wouldnt that also hurt performance? (One more layer to render?)

Hi Julian,

Yes, it does — it draws the same layer twice.

There is a more efficient way: apply the opacity to each style instead of to the layer.

// not layer.Opacity = 0.5
areaStyle = AreaStyle.CreateSimpleAreaStyle(GeoColor.FromArgb(128, GeoColors.Blue), ...);
lineStyle = LineStyle.CreateSimpleLineStyle(GeoColor.FromArgb(128, GeoColors.Black), 2, false);
textStyle = TextStyle.CreateSimpleTextStyle("name", ...);   // left opaque

That draws everything once, but the cost is the one you already found on the GPU side in 12446: each shape is now blended on its own, so where two of them cover the same pixel the color compounds — two 50% fills read as 75%, an outline over its own fill darkens the edge. That is exactly why we moved the GPU to an offscreen composite rather than leaving the opacity per style layer.

So: if the features in that layer do not overlap — parcels, a coverage grid — the style colours give you the effect for less than you pay now. If they do overlap, or the layer has an outline over its fill, take the two layers (and about 20% slower than what you are doing now); nothing cheaper reproduces a single faded picture.

Thanks,
Ben

1 Like