You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

343 lines
16 KiB

#ifndef Magnum_TextureTools_DistanceFieldGL_h
#define Magnum_TextureTools_DistanceFieldGL_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024, 2025
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Class @ref Magnum::TextureTools::DistanceFieldGL
* @m_since_latest
*/
#include "Magnum/configure.h"
#ifdef MAGNUM_TARGET_GL
#include <Corrade/Containers/Pointer.h>
#include "Magnum/Magnum.h"
#include "Magnum/GL/GL.h"
#include "Magnum/TextureTools/visibility.h"
namespace Magnum { namespace TextureTools {
/**
@brief Create a signed distance field using OpenGL
@m_since_latest
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.
@m_class{m-row}
@parblock
@m_div{m-col-m-6 m-nopadt}
@image html distancefield-src.png width=256px
@m_enddiv
@m_div{m-col-m-6 m-nopadt}
@image html distancefield-dst.png width=256px
@m_enddiv
@endparblock
You can use the @ref magnum-distancefieldconverter "magnum-distancefieldconverter"
utility to perform distance field conversion on a command line. Distance field
textures can be rendered with @ref Shaders::DistanceFieldVectorGL, this
functionality is also used to implement @ref Text::DistanceFieldGlyphCacheGL /
@ref Text::DistanceFieldGlyphCacheArrayGL for text rendering, which is then
exposed in the @ref magnum-fontconverter "magnum-fontconverter" utility.
Algorithm based on: *Chris Green - Improved Alpha-Tested Magnification for
Vector Textures and Special Effects, SIGGRAPH 2007,
http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf*
@note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features
for more information.
@section TextureTools-DistanceFieldGL-usage Example usage
The following snippet uploads an image to a @ref GL::Texture2D, creates a
second smaller @ref GL::Texture2D for the output and then performs the distance
field conversion with a radius of @cpp 12 @ce pixels and spanning the whole
output image area:
@snippet TextureTools-gl.cpp DistanceFieldGL
In the output (an example of which is shown above on the right, scaled up to
match the original), value of @cpp 1.0 @ce (when normalized from the actual
pixel format, so @cpp 255 @ce for the @ref GL::TextureFormat::R8 used above)
means that the pixel was originally colored white and nearest black pixel is
further away than the specified radius. Correspondingly, value of @cpp 0.0 @ce
means that the pixel was originally black and nearest white pixel is further
away than the radius. Edges are thus at values around @cpp 0.5 @ce.
The resulting texture is meant to be used with bilinear filtering, i.e. with
@ref GL::SamplerFilter::Linear. To get the original image back, the GLSL
@glsl smoothstep() @ce function can be used as shown in the following snippet,
with a step around @cpp 0.5 @ce and @cpp smoothness @ce being a configurable
factor controlling edge smoothness.
@code{.glsl}
float factor = smoothstep(0.5 - smoothness,
0.5 + smoothness,
texture(distanceFieldTexture, coordinates).r);
@endcode
The @ref Shaders::DistanceFieldVectorGL implements also outlining, edge dilate,
erode, and other effects with the distance field input.
@section TextureTools-DistanceFieldGL-parameters 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 @cpp 4 @ce 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 @cpp 4 @ce and radius of @cpp 12 @ce 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
@ref GL::TextureFormat::R16 instead. Using an even larger format probably won't
improve the result any further, and since the distance is normalized to a
@f$ [0, 1] @f$ range, a floating-point format such as
@ref GL::TextureFormat::R16F would also not, but rather resulting in worse
precision than the 16-bit normalized integer format.
@subsection TextureTools-DistanceFieldGL-parameters-rendering 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 @cpp image.size() @ce 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.
@snippet TextureTools-gl.cpp DistanceFieldGL-parameters-rendering
For a concrete example, if the input was @cpp {256, 256} @ce, it's now rendered
at a size of @cpp {128, 128} @ce and it was converted with a radius of
@cpp 12 @ce, the `ratio` will be @cpp 1.0f/6 @ce. I.e., if you set the shader
`smoothness` to @cpp 1.0f/6 @ce, the edge smoothness radius will be exactly one
pixel.
@section TextureTools-DistanceFieldGL-incremental 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
@ref 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 @ref GL::Framebuffer with the output texture attached so the
implementation doesn't need to create a temporary one each time:
@snippet TextureTools-gl.cpp DistanceFieldGL-incremental
*/
class MAGNUM_TEXTURETOOLS_EXPORT DistanceFieldGL {
public:
/**
* @brief Constructor
* @param radius Distance field calculation radius
*
* Prepares the shader and other internal state for given @p radius.
*/
explicit DistanceFieldGL(UnsignedInt radius);
/**
* @brief Construct without creating the internal OpenGL state
* @m_since_latest
*
* 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 @ref NoCreate for alternatives.
*/
explicit DistanceFieldGL(NoCreateT) noexcept;
/** @brief Copying is not allowed */
DistanceFieldGL(const DistanceFieldGL&) = delete;
/**
* @brief Move constructor
* @m_since_latest
*
* Performs a destructive move, i.e. the original object isn't usable
* afterwards anymore.
*/
DistanceFieldGL(DistanceFieldGL&&) noexcept;
~DistanceFieldGL();
/** @brief Copying is not allowed */
DistanceFieldGL& operator=(const DistanceFieldGL&) = delete;
/** @brief Move constructor */
DistanceFieldGL& operator=(DistanceFieldGL&&) noexcept;
/** @brief Distance field calculation radius */
UnsignedInt radius() const;
/**
* @brief Calculate distance field to a framebuffer
* @param input Input texture
* @param output Output framebuffer
* @param rectangle Rectangle in the output where to render
* @param imageSize Input texture size. Mandatory on OpenGL ES and
* WebGL, on desktop GL if left at default the size is internally
* queried using @ref GL::Texture2D::imageSize() instead.
* @m_since_latest
*
* The @p output texture is expected to have a framebuffer-drawable
* @ref GL::TextureFormat. On desktop OpenGL and
* OpenGL ES 3.0 it's common to render to @ref GL::TextureFormat::R8.
* On OpenGL ES 2.0 you can use @ref GL::TextureFormat::Red if
* @gl_extension{EXT,texture_rg} is available; if not, the smallest yet
* still quite inefficient supported format is in most cases
* @ref GL::TextureFormat::RGB. The @ref GL::TextureFormat::Luminance
* format usually isn't renderable.
*
* Additionally, the ratio of the @p input size (or @p imageSize on
* OpenGL ES) and @p rectangle size is expected to be a multiple of 2,
* as that's what the generator shader relies on for correct pixel
* addressing.
*
* @attention For consistent results on implementations that don't
* support @glsl texelFetch() @ce and have rely on regular texture
* filtering it's recommended to use @ref GL::SamplerFilter::Nearest
* on the @p input.
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
void operator()(GL::Texture2D& input, GL::Framebuffer& output, const Range2Di& rectangle, const Vector2i& imageSize
#ifndef MAGNUM_TARGET_GLES
= {}
#endif
);
#else
/* To avoid having to include Vector2 */
void operator()(GL::Texture2D& input, GL::Framebuffer& output, const Range2Di& rectangle, const Vector2i& imageSize);
#ifndef MAGNUM_TARGET_GLES
void operator()(GL::Texture2D& input, GL::Framebuffer& output, const Range2Di& rectangle);
#endif
#endif
/**
* @brief Calculate distance field to a texture
* @param input Input texture
* @param output Output texture
* @param rectangle Rectangle in the output where to render
* @param imageSize Input texture size. Mandatory on OpenGL ES and
* WebGL, on desktop GL if left at default the size is internally
* queried using @ref GL::Texture2D::imageSize() instead.
*
* Convenience variant of @ref operator()(GL::Texture2D&, GL::Framebuffer&, const Range2Di&, const Vector2i&)
* that creates a temporary framebuffer with @p output attached and
* destroys it again after the operation.
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
void operator()(GL::Texture2D& input, GL::Texture2D& output, const Range2Di& rectangle, const Vector2i& imageSize
#ifndef MAGNUM_TARGET_GLES
= {}
#endif
);
#else
/* To avoid having to include Vector2 */
void operator()(GL::Texture2D& input, GL::Texture2D& output, const Range2Di& rectangle, const Vector2i& imageSize);
#ifndef MAGNUM_TARGET_GLES
void operator()(GL::Texture2D& input, GL::Texture2D& output, const Range2Di& rectangle);
#endif
#endif
#ifndef MAGNUM_TARGET_GLES2
/**
* @brief Calculate distance field to a texture array layer
* @param input Input texture
* @param output Output texture
* @param layer Layer in the output where to render
* @param rectangle Rectangle in the output where to render
* @param imageSize Input texture size. Mandatory on OpenGL ES and
* WebGL, on desktop GL if left at default the size is internally
* queried using @ref GL::Texture2D::imageSize() instead.
* @m_since_latest
*
* Convenience variant of @ref operator()(GL::Texture2D&, GL::Framebuffer&, const Range2Di&, const Vector2i&)
* that creates a temporary framebuffer with @p output @p layer
* attached and destroys it again after the operation.
* @requires_gl30 Extension @gl_extension{EXT,texture_array}
* @requires_gles30 Texture arrays are not available in OpenGL ES 2.0.
* @requires_webgl20 Texture arrays are not available in WebGL 1.0.
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle, const Vector2i& imageSize
#ifndef MAGNUM_TARGET_GLES
= {}
#endif
);
#else
/* To avoid having to include Vector2 */
void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle, const Vector2i& imageSize);
#ifndef MAGNUM_TARGET_GLES
void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle);
#endif
#endif
#endif
private:
struct State;
Containers::Pointer<State> _state;
};
}}
#else
#error this header is available only in the OpenGL build
#endif
#endif