class new in Git master
DistanceFieldGLCreate a signed distance field using OpenGL.
Converts a high-resolution black and white image (such as vector artwork or font glyphs) to a low-resolution grayscale image with each pixel being a signed distance to the nearest edge in the original image. Such a distance field image then occupies much less memory as the spatial resolution is converted to pixel values amd can be scaled without it being jaggy at small sizes or blurry when large. It also makes it possible to implement outlining, glow or drop shadow essentially for free.


You can use the magnum-distancefieldconverter utility to perform distance field conversion on a command line. Distance field textures can be rendered with Shaders::
Algorithm based on: Chris Green - Improved Alpha-Tested Magnification for Vector Textures and Special Effects, SIGGRAPH 2007, http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_
Example usage
The following snippet uploads an image to a GL::12
pixels and spanning the whole output image area:
ImageView2D image = …; GL::Texture2D input; input .setMinificationFilter(GL::SamplerFilter::Nearest) .setMagnificationFilter(GL::SamplerFilter::Nearest) .setStorage(1, GL::textureFormat(image.format()), image.size()) .setSubImage(0, {}, image); GL::Texture2D output; output.setStorage(1, GL::TextureFormat::R8, image.size()/4); TextureTools::DistanceFieldGL distanceField{12}; distanceField(input, output, {{}, image.size()/4});
In the output (an example of which is shown above on the right, scaled up to match the original), value of 1.0
(when normalized from the actual pixel format, so 255
for the GL::0.0
means that the pixel was originally black and nearest white pixel is further away than the radius. Edges are thus at values around 0.5
.
The resulting texture is meant to be used with bilinear filtering, i.e. with GL::smoothstep()
function can be used as shown in the following snippet, with a step around 0.5
and smoothness
being a configurable factor controlling edge smoothness.
float factor = smoothstep(0.5 - smoothness, 0.5 + smoothness, texture(distanceFieldTexture, coordinates).r);
The Shaders::
Parameter tuning
Quality of the generated distance field is affected by two variables — the ratio between input and output size, and the radius. A bigger size ratio will result in bigger memory savings but at the cost of losing finer detail, so the choice depends mainly on the content that's actually being processed. The image shown above could get away with being reduced down even eight or sixteen times without noticeable quality loss, on the other hand vector art consisting of fine lines or for example CJK glyphs might likely have artifacts already with the ratio of 4
used above.
The radius should be at least as large as the size ratio in order to contribute to at least one pixel on every side of an edge in the output, otherwise the resulting rendering will be extremely blocky. After that, its value is dictated mainly by the desired use of the output — if you need to draw the output with larger antialiasing smoothness, big outlines or shadows, the radius needs to get bigger. With the size ratio of 4
and radius of 12
used above, the output allows for smoothness, outline or other effect ±3 pixels around the edge.
Finally, with very large radii you may run into quantization issues with 8-bit texture formats, causing again blocky artifacts. A solution is then to use GL::
Effect of input parameters on final rendered image
In order to ensure consistent look when rendering regardless of the parameters picked for distance field conversion, the rendering has to take the input size and radius into account. Assuming image.size()
is size of the input image and renderedSize
is pixel size at which the distance field image is drawn on the screen, the ratio
calculated below is then distance that corresponds to one pixel on the screen. Note that the ratio at which the distance field output is sized down has no effect here, and thus it can be chosen dynamically to achieve desired quality / memory use tradeoff.
Vector2 renderedSize = …; Float ratio = renderedSize.x()/(image.size().x()*distanceField.radius());
For a concrete example, if the input was {256, 256}
, it's now rendered at a size of {128, 128}
and it was converted with a radius of 12
, the ratio
will be 1.0f/6
. I.e., if you set the shader smoothness
to 1.0f/6
, the edge smoothness radius will be exactly one pixel.
Incremental distance field calculation
Besides converting whole texture at once, it's possible to process just a part. This is mainly useful with use cases like dynamically populated texture atlases, where it'd be wasteful to repeatedly process already filled parts. The output area to process is specified with the third argument to operator()() (which was above set to the whole output texture size). The input texture is still taken as a whole, i.e. it's assumed that it contains exactly the data meant to be processed and placed into the output area. Additionally, to avoid needless OpenGL state changes, it's recommended to supply a GL::
/* Construct and set up just once */ TextureTools::DistanceFieldGL distanceField{…}; GL::Framebuffer outputFramebuffer{{{}, image.size()/4}}; outputFramebuffer.attachTexture(GL::Framebuffer::ColorAttachment{0}, output, 0); /* Call the distance field processing each time the input texture is updated */ Range2Di updatedRange = …; distanceField(input, output, updatedRange);
Constructors, destructors, conversion operators
- DistanceFieldGL(UnsignedInt radius) explicit
- Constructor.
- DistanceFieldGL(NoCreateT) explicit noexcept new in Git master
- Construct without creating the internal OpenGL state.
- DistanceFieldGL(const DistanceFieldGL&) deleted
- Copying is not allowed.
- DistanceFieldGL(DistanceFieldGL&&) noexcept new in Git master
- Move constructor.
Public functions
- auto operator=(const DistanceFieldGL&) -> DistanceFieldGL& deleted
- Copying is not allowed.
- auto operator=(DistanceFieldGL&&) -> DistanceFieldGL& noexcept
- Move constructor.
- auto radius() const -> UnsignedInt
- Distance field calculation radius.
-
void operator()(GL::
Texture2D& input, GL:: Framebuffer& output, const Range2Di& rectangle, const Vector2i& imageSize = {}) new in Git master - Calculate distance field to a framebuffer.
-
void operator()(GL::
Texture2D& input, GL:: Texture2D& output, const Range2Di& rectangle, const Vector2i& imageSize = {}) - Calculate distance field to a texture.
-
void operator()(GL::
Texture2D& input, GL:: Texture2DArray& output, Int layer, const Range2Di& rectangle, const Vector2i& imageSize = {}) new in Git master - Calculate distance field to a texture array layer.
Function documentation
Magnum:: TextureTools:: DistanceFieldGL:: DistanceFieldGL(UnsignedInt radius) explicit
Constructor.
Parameters | |
---|---|
radius | Distance field calculation radius |
Prepares the shader and other internal state for given radius
.
Magnum:: TextureTools:: DistanceFieldGL:: DistanceFieldGL(NoCreateT) explicit noexcept new in Git master
Construct without creating the internal OpenGL state.
The constructed instance is equivalent to moved-from state, i.e. no APIs can be safely called on the object. Useful in cases where you will overwrite the instance later anyway. Move another object over it to make it useful.
This function can be safely used for constructing (and later destructing) objects even without any OpenGL context being active. However note that this is a low-level and a potentially dangerous API, see the documentation of NoCreate for alternatives.
Magnum:: TextureTools:: DistanceFieldGL:: DistanceFieldGL(DistanceFieldGL&&) noexcept new in Git master
Move constructor.
Performs a destructive move, i.e. the original object isn't usable afterwards anymore.
void Magnum:: TextureTools:: DistanceFieldGL:: operator()(GL:: Texture2D& input,
GL:: Framebuffer& output,
const Range2Di& rectangle,
const Vector2i& imageSize = {}) new in Git master
Calculate distance field to a framebuffer.
Parameters | |
---|---|
input | Input texture |
output | Output framebuffer |
rectangle | Rectangle in the output where to render |
imageSize | Input texture size. Mandatory on OpenGL ES and WebGL, on desktop GL if left at default the size is internally queried using GL:: |
The output
texture is expected to have a framebuffer-drawable GL::
Additionally, the ratio of the input
size (or imageSize
on OpenGL ES) and rectangle
size is expected to be a multiple of 2, as that's what the generator shader relies on for correct pixel addressing.
void Magnum:: TextureTools:: DistanceFieldGL:: operator()(GL:: Texture2D& input,
GL:: Texture2D& output,
const Range2Di& rectangle,
const Vector2i& imageSize = {})
Calculate distance field to a texture.
Parameters | |
---|---|
input | Input texture |
output | Output texture |
rectangle | Rectangle in the output where to render |
imageSize | Input texture size. Mandatory on OpenGL ES and WebGL, on desktop GL if left at default the size is internally queried using GL:: |
Convenience variant of operator()(GL::output
attached and destroys it again after the operation.
void Magnum:: TextureTools:: DistanceFieldGL:: operator()(GL:: Texture2D& input,
GL:: Texture2DArray& output,
Int layer,
const Range2Di& rectangle,
const Vector2i& imageSize = {}) new in Git master
Calculate distance field to a texture array layer.
Parameters | |
---|---|
input | Input texture |
output | Output texture |
layer | Layer in the output where to render |
rectangle | Rectangle in the output where to render |
imageSize | Input texture size. Mandatory on OpenGL ES and WebGL, on desktop GL if left at default the size is internally queried using GL:: |
Convenience variant of operator()(GL::output
layer
attached and destroys it again after the operation.