Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add outline rendering to TextLayer #5483

Merged
merged 2 commits into from
Feb 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions docs/api-reference/layers/text-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,11 @@ Advance options for fine tuning the appearance and performance of the generated
Options:

* `fontSize` (Number): Font size in pixels. Default is `64`. This option is only applied for generating `fontAtlas`, it does not impact the size of displayed text labels. Larger `fontSize` will give you a sharper look when rendering text labels with very large font sizes. But larger `fontSize` requires more time and space to generate the `fontAtlas`.
* `buffer` (Number): Whitespace buffer around each side of the character. Default is `2`. In general, bigger `fontSize` requires bigger `buffer`. Increase `buffer` will add more space between each character when layout `characterSet` in `fontAtlas`. This option could be tuned to provide sufficient space for drawing each character and avoiding overlapping of neighboring characters. But the cost of bigger `buffer` is more time and space to generate `fontAtlas`.
* `buffer` (Number): Whitespace buffer around each side of the character. Default is `4`. In general, bigger `fontSize` requires bigger `buffer`. Increase `buffer` will add more space between each character when layout `characterSet` in `fontAtlas`. This option could be tuned to provide sufficient space for drawing each character and avoiding overlapping of neighboring characters.
* `sdf` (Boolean): Flag to enable / disable `sdf`. Default is `false`. [`sdf` (Signed Distance Fields)](http://cs.brown.edu/people/pfelzens/papers/dt-final.pdf) will provide a sharper look when rendering with very large or small font sizes. `TextLayer` integrates with [`TinySDF`](https://github.com/mapbox/tiny-sdf) which implements the `sdf` algorithm.
* `radius` (Number): How many pixels around the glyph shape to use for encoding distance. Default is `3`. Bigger radius can have more halo effect.
* `cutoff` (Number): How much of the radius (relative) is used for the inside part the glyph. Default is `0.25`. Bigger `cutoff` makes character thinner. Smaller `cutoff` makes character look thicker.

`radius` and `cutoff` will be applied only when `sdf` enabled.
* `radius` (Number): How many pixels around the glyph shape to use for encoding distance. Default is `12`. Bigger radius yields higher quality outcome. Only applies when `sdf: true`.
* `cutoff` (Number): How much of the radius (relative) is used for the inside part the glyph. Default is `0.25`. Bigger `cutoff` makes character thinner. Smaller `cutoff` makes character look thicker. Only applies when `sdf: true`.
* `smoothing` (Number): How much smoothing to apply to the text edges. Default `0.1`. Only applies when `sdf: true`.

##### `wordBreak` (String, optional)

Expand All @@ -166,6 +165,19 @@ Available options are `break-all` and `break-word`. A valid `maxWidth` has to be

`maxWidth` is used together with `break-word` for wrapping text. The value of `maxWidth` specifies the width limit to break the text into multiple lines.

##### `outlineWidth` (Number, optional)

* Default: `0`

Width of outline around the text, relative to the font size.

##### `outlineColor` (Array, optional)

* Default: `[0, 0, 0, 255]`

Color of outline around the text, in `[r, g, b, [a]]`. Each channel is a number between 0-255 and `a` is 255 if not supplied.


### Data Accessors

##### `getText` ([Function](/docs/developer-guide/using-layers.md#accessors), optional)
Expand Down
1 change: 1 addition & 0 deletions docs/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Upgrading from deck.gl v8.4 to v8.5

- `TextLayer`'s `backgroundColor` prop is deprecated. Use `background: true` and `getBackgroundColor` instead.
- `TextLayer`'s default `fontSettings` have changed. When using `sdf`, the default `buffer` is now `4` and the default `radius` is now `12`.

## Upgrading from deck.gl v8.3 to v8.4

Expand Down
6 changes: 3 additions & 3 deletions modules/layers/src/text-layer/font-atlas-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
export const DEFAULT_FONT_FAMILY = 'Monaco, monospace';
export const DEFAULT_FONT_WEIGHT = 'normal';
export const DEFAULT_FONT_SIZE = 64;
export const DEFAULT_BUFFER = 2;
export const DEFAULT_BUFFER = 4;
export const DEFAULT_CUTOFF = 0.25;
export const DEFAULT_RADIUS = 3;
export const DEFAULT_RADIUS = 12;

const MAX_CANVAS_WIDTH = 1024;

Expand Down Expand Up @@ -209,7 +209,7 @@ export default class FontAtlasManager {

for (const char of characterSet) {
populateAlphaChannel(tinySDF.draw(char), imageData);
ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y - buffer);
ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y + buffer);
}
} else {
for (const char of characterSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,45 @@ precision highp float;

uniform float opacity;
uniform sampler2D iconsTexture;
uniform float buffer;
uniform float gamma;
uniform bool sdf;
uniform float alphaCutoff;
uniform float buffer;
uniform float outlineBuffer;
uniform vec4 outlineColor;

varying vec4 vColor;
varying vec2 vTextureCoords;
varying float vGamma;
varying vec2 uv;

void main(void) {
geometry.uv = uv;

if (!picking_uActive) {
float alpha = texture2D(iconsTexture, vTextureCoords).a;
vec4 color = vColor;

// if enable sdf (signed distance fields)
if (sdf) {
alpha = smoothstep(buffer - vGamma, buffer + vGamma, alpha);
float distance = alpha;
alpha = smoothstep(buffer - gamma, buffer + gamma, distance);

if (outlineBuffer > 0.0) {
float inFill = alpha;
float inBorder = smoothstep(outlineBuffer - gamma, outlineBuffer + gamma, distance);
color = mix(outlineColor, vColor, inFill);
alpha = inBorder;
}
}

// Take the global opacity and the alpha from vColor into account for the alpha component
float a = alpha * vColor.a;
// Take the global opacity and the alpha from color into account for the alpha component
float a = alpha * color.a;

if (a < alphaCutoff) {
// We are now in the background, let's decide what to draw
discard;
}

gl_FragColor = vec4(vColor.rgb, a * opacity);
gl_FragColor = vec4(color.rgb, a * opacity);
}

DECKGL_FILTER_COLOR(gl_FragColor, geometry);
Expand Down
47 changes: 30 additions & 17 deletions modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,26 @@
// THE SOFTWARE.

import GL from '@luma.gl/constants';
import {log} from '@deck.gl/core';
import IconLayer from '../../icon-layer/icon-layer';

import fs from './multi-icon-layer-fragment.glsl';

// TODO expose as layer properties
const DEFAULT_GAMMA = 0.2;
const DEFAULT_BUFFER = 192.0 / 256;
const EMPTY_ARRAY = [];

const defaultProps = {
getIconOffsets: {type: 'accessor', value: x => x.offsets},
alphaCutoff: 0.001
alphaCutoff: 0.001,
smoothing: 0.1,
outlineWidth: 0,
outlineColor: {type: 'color', value: [0, 0, 0, 255]}
};

export default class MultiIconLayer extends IconLayer {
getShaders() {
return Object.assign({}, super.getShaders(), {
inject: {
'vs:#decl': `
uniform float gamma;
varying float vGamma;
`,
'vs:#main-end': `
vGamma = gamma / (sizeScale * iconSize.y);
`
},
fs
});
return {...super.getShaders(), fs};
}

initializeState() {
Expand All @@ -66,16 +58,37 @@ export default class MultiIconLayer extends IconLayer {
});
}

updateState(params) {
super.updateState(params);
const {props, oldProps} = params;
let {outlineColor} = props;

if (outlineColor !== oldProps.outlineColor) {
outlineColor = outlineColor.map(x => x / 255);
outlineColor[3] = Number.isFinite(outlineColor[3]) ? outlineColor[3] : 1;

this.setState({
outlineColor
});
}
if (!props.sdf && props.outlineWidth) {
log.warn(`${this.id}: fontSettings.sdf is required to render outline`)();
}
}

draw({uniforms}) {
const {sdf} = this.props;
const {sdf, smoothing, outlineWidth} = this.props;
const {outlineColor} = this.state;

super.draw({
uniforms: Object.assign({}, uniforms, {
// Refer the following doc about gamma and buffer
// https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817
buffer: DEFAULT_BUFFER,
gamma: DEFAULT_GAMMA,
sdf: Boolean(sdf)
outlineBuffer: outlineWidth ? Math.max(smoothing, DEFAULT_BUFFER * (1 - outlineWidth)) : -1,
gamma: smoothing,
sdf: Boolean(sdf),
outlineColor
})
});
}
Expand Down
12 changes: 11 additions & 1 deletion modules/layers/src/text-layer/text-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const DEFAULT_FONT_SETTINGS = {
buffer: DEFAULT_BUFFER,
sdf: false,
radius: DEFAULT_RADIUS,
cutoff: DEFAULT_CUTOFF
cutoff: DEFAULT_CUTOFF,
smoothing: 0.1
};

const TEXT_ANCHOR = {
Expand Down Expand Up @@ -76,6 +77,8 @@ const defaultProps = {
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: DEFAULT_FONT_WEIGHT,
lineHeight: DEFAULT_LINE_HEIGHT,
outlineWidth: {type: 'number', value: 0, min: 0},
outlineColor: {type: 'color', value: DEFAULT_COLOR},
fontSettings: {},

// auto wrapping options
Expand Down Expand Up @@ -296,6 +299,8 @@ export default class TextLayer extends CompositeLayer {
background,
billboard,
fontSettings,
outlineWidth,
outlineColor,
sizeScale,
sizeUnits,
sizeMinPixels,
Expand Down Expand Up @@ -369,6 +374,11 @@ export default class TextLayer extends CompositeLayer {
new CharactersLayerClass(
{
sdf: fontSettings.sdf,
smoothing: Number.isFinite(fontSettings.smoothing)
Pessimistress marked this conversation as resolved.
Show resolved Hide resolved
? fontSettings.smoothing
: DEFAULT_FONT_SETTINGS.smoothing,
outlineWidth,
outlineColor,
iconAtlas: texture,
iconMapping: mapping,

Expand Down