deck.gl icon indicating copy to clipboard operation
deck.gl copied to clipboard

[Feat] Improving the getColor method in IconLayer

Open mo3g666 opened this issue 6 months ago • 2 comments

Target Use Case

Current getColor implementation with mask:true has critical limitations:

  1. Only works with binary masks - completely replaces all non-transparent pixels with solid color
  2. Destroys image details - cannot preserve:
    • Multi-tone icons (e.g. logos with shadows)
    • Built-in color schemes
  3. Performance overhead - requires generating separate atlases for each color variation

Proposal

Introduce alternative color blending via tint mode when mask:false:

new IconLayer({
  getColor: d => [r, g, b, a], // Applies as tint color
  iconMapping: {
    customIcon: {
      mask: false, // Enables tint blending
      // Original colors are preserved but blended with tint
    }
  }
});

tint Mode vs Current mask:true

Feature mask:true (Current) Proposed tint Mode
Detail Preservation ❌ Flat single-color ✅ Original colors + tint blend
Dynamic Updates ❌ Requires atlas rebuild ✅ Instant color change
Performance ⚠️ High memory usage ✅ Low overhead
Supported Icons Only monochrome masks Any PNG/SVG (multi-color, gradients)

mo3g666 avatar May 14 '25 13:05 mo3g666

Thanks for writing up this proposal. This sounds similar to how getColor works in ScenegraphLayer, where [255, 255, 255, 255] preserves the original colors and any other value is blended via multiply.

mask: false behavior is already defined and we wouldn't want to introduce a breaking change for this. From the docs:

If mask = false, only the alpha component will be used to control the opacity of the icon.

mask (boolean, optional): whether icon is treated as a transparency mask. If true, user defined color is applied. If false, pixel color from the image is applied. User still can specify the opacity through getColor. Default: false

So, I don't think mask is the appropriate config for this. Instead I'd suggest adding a new tint: boolean property to the iconMapping. It could be false by default. If true, it blends getColor with the icon's texture color.

Here's the relevant shader.

Backwards compatibility should be preserved, so if mask: true then I think the new tint setting should be ignored. Unless, there's a proposal for how these should interact with each other.

Wdyt @Pessimistress @felixpalmer @ibgreen ?

@mo3g666 would you be able to create a PR for this that includes a icon render test?

chrisgervang avatar Jun 03 '25 21:06 chrisgervang

This is similar to the autoHighlight issue I raised in: https://github.com/visgl/deck.gl/issues/9291, at the end of which my recommendation is to allow passing in the standard blending factors (similar to how we do in Layer.parameters: https://github.com/visgl/deck.gl/blob/master/modules/mapbox/src/deck-utils.ts#L127-L130)

So the resulting API would be:

new IconLayer({
  getColor: d => [r, g, b, a], // Applies as tint color
  iconMapping: {
    customIcon: {
      mask: true | false, // Remains as before
      blendColorSrcFactor: 'src-alpha',
      blendColorDstFactor: 'one-minus-src-alpha',
      blendAlphaSrcFactor: 'one',
      blendAlphaDstFactor: 'one-minus-src-alpha',
      ...
    }
  }
});

The advantage is that we are using an existing set of defined constants and giving more flexibility for the user. The mask: "tint" is in effect a shorthand for one specific combination of the above factors. While it may be more verbose, in most cases it will not be necessary to specify all 4 factors

felixpalmer avatar Jun 04 '25 12:06 felixpalmer