From 683a9fa7184327c6180ddc64213c4b2f2c72ad3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 16 Apr 2025 15:01:03 +0200 Subject: [PATCH] TextureTools: rewrite docs for DistanceFieldGL from scratch. I fixed some minor English crimes in the method docs and then realized this is all just bad and the utter uselessness and lack of information led to way too many confused questions over the years. So let's do it properly, finally, ugh. --- doc/distancefield-dst.png | Bin 0 -> 2797 bytes doc/distancefield-src.png | Bin 0 -> 4882 bytes doc/generated/README.md | 14 ++ doc/snippets/CMakeLists.txt | 3 +- doc/snippets/TextureTools-gl.cpp | 92 +++++++++++ src/Magnum/TextureTools/DistanceFieldGL.h | 187 ++++++++++++++++------ 6 files changed, 249 insertions(+), 47 deletions(-) create mode 100644 doc/distancefield-dst.png create mode 100644 doc/distancefield-src.png create mode 100644 doc/snippets/TextureTools-gl.cpp diff --git a/doc/distancefield-dst.png b/doc/distancefield-dst.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9cb30b400ed423105cc1fa4985f8d2a974c25f GIT binary patch literal 2797 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 literal 0 HcmV?d00001 diff --git a/doc/distancefield-src.png b/doc/distancefield-src.png new file mode 100644 index 0000000000000000000000000000000000000000..a471edfda8f9386f6c087eeab1246a93805ecdf7 GIT binary patch literal 4882 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 literal 0 HcmV?d00001 diff --git a/doc/generated/README.md b/doc/generated/README.md index f888b593b..86dc3f45d 100644 --- a/doc/generated/README.md +++ b/doc/generated/README.md @@ -41,3 +41,17 @@ output is put into `doc/` directory. The executable requires two textures: Apply `pngcrush` to the result for smaller file sizes: for f in $(ls shaders-*.png); do pngcrush -ow $f; done + +### Distance field vector images + +Similarly as above, just with slightly different parameters. The +`distancefield-src.png` is generated as full-page PNG output at 192 DPI +(512x512) from `vector.svg`, the `distancefield-dst.png` is then converted with + +```bash +magnum-distancefieldconverter distancefield-src.png distancefield-dst.png --output-size "128 128" --radius 24 +``` + +This is chosen so it corresponds to the `TextureTools::DistanceFieldGL` doc +snippet where it takes a 256x256 image and converts it to 64x64 with a radius +12, but is scaled twice to look better on HiDPI displays. diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index b6cdd2daa..da43e5349 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -94,7 +94,8 @@ if(MAGNUM_WITH_GL) GL.cpp MeshTools-gl.cpp Shaders-gl.cpp - Text-gl.cpp) + Text-gl.cpp + TextureTools-gl.cpp) target_link_libraries(snippets-GL PRIVATE MagnumGL) if(CORRADE_TESTSUITE_TEST_TARGET) add_dependencies(${CORRADE_TESTSUITE_TEST_TARGET} snippets-GL) diff --git a/doc/snippets/TextureTools-gl.cpp b/doc/snippets/TextureTools-gl.cpp new file mode 100644 index 000000000..ed3a5aea4 --- /dev/null +++ b/doc/snippets/TextureTools-gl.cpp @@ -0,0 +1,92 @@ +/* + 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š + + 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. +*/ + +#include "Magnum/ImageView.h" +#include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Texture.h" +#include "Magnum/GL/TextureFormat.h" +#include "Magnum/Math/Range.h" +#include "Magnum/TextureTools/DistanceFieldGL.h" + +#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ + +using namespace Magnum; + +/* Make sure the name doesn't conflict with any other snippets to avoid linker + warnings, unlike with `int main()` there now has to be a declaration to + avoid -Wmisssing-prototypes */ +void mainTextureToolsGL(); +void mainTextureToolsGL() { +/* On ES it requires one extra parameter with input image size */ +#ifndef MAGNUM_TARGET_GLES +{ +/* [DistanceFieldGL] */ +ImageView2D image = DOXYGEN_ELLIPSIS(ImageView2D{PixelFormat{}, {}, nullptr}); + +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}); +/* [DistanceFieldGL] */ +} +#endif + +{ +ImageView2D image{PixelFormat{}, {}, nullptr}; +TextureTools::DistanceFieldGL distanceField{0}; +/* [DistanceFieldGL-parameters-rendering] */ +Vector2 renderedSize = DOXYGEN_ELLIPSIS({}); +Float ratio = renderedSize.x()/(image.size().x()*distanceField.radius()); +/* [DistanceFieldGL-parameters-rendering] */ +static_cast(ratio); +} + +/* On ES it requires one extra parameter with input image size */ +#ifndef MAGNUM_TARGET_GLES +{ +ImageView2D image{PixelFormat{}, {}, nullptr}; +GL::Texture2D input, output; +/* [DistanceFieldGL-incremental] */ +/* Construct and set up just once */ +TextureTools::DistanceFieldGL distanceField{DOXYGEN_ELLIPSIS(0)}; +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 = DOXYGEN_ELLIPSIS({}); +distanceField(input, output, updatedRange); +/* [DistanceFieldGL-incremental] */ +} +#endif +} diff --git a/src/Magnum/TextureTools/DistanceFieldGL.h b/src/Magnum/TextureTools/DistanceFieldGL.h index 443c97576..ece86ac1d 100644 --- a/src/Magnum/TextureTools/DistanceFieldGL.h +++ b/src/Magnum/TextureTools/DistanceFieldGL.h @@ -28,6 +28,7 @@ /** @file * @brief Class @ref Magnum::TextureTools::DistanceFieldGL + * @m_since_latest */ #include "Magnum/configure.h" @@ -43,51 +44,144 @@ namespace Magnum { namespace TextureTools { /** @brief Create a signed distance field using OpenGL +@m_since_latest -Converts a binary black/white image (stored in the red channel of @p input) to -a signed distance field (stored in the red channel of @p output @p rectangle). -The purpose of this function is to convert a high-resolution binary image (such -as vector artwork or font glyphs) to a low-resolution grayscale image. The -image will then occupy much less memory and can be scaled without aliasing -issues. Additionally it provides foundation for features like outlining, glow -or drop shadow essentially for free. +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 also use the @ref magnum-distancefieldconverter "magnum-distancefieldconverter" -utility to do distance field conversion on command-line. This functionality is -also used inside the @ref magnum-fontconverter "magnum-fontconverter" utility. +@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 +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-algorithm The algorithm +@section TextureTools-DistanceFieldGL-usage Example usage -For each pixel inside the @p output sub-rectangle the algorithm looks at -corresponding pixel in the input and tries to find nearest pixel of opposite -color in an area defined @p radius. Signed distance between the points is then -saved as value of given pixel in @p output. Value of 1.0 means that the pixel -was originally colored white and nearest black pixel is farther than @p radius, -value of 0.0 means that the pixel was originally black and nearest white pixel -is farther than @p radius. Values around 0.5 are around edges. +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: -The resulting texture can be used with bilinear filtering. It can be converted -back to binary form in shader using e.g. GLSL @glsl smoothstep() @ce function -with step around 0.5 to create antialiased edges. Or you can exploit the -distance field features to create many other effects. See also -@ref Shaders::DistanceFieldVectorGL. +@snippet TextureTools-gl.cpp DistanceFieldGL -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* +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 -@attention This is a GPU-only implementation, so it expects an active GL - context. +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 Max lookup radius in the input texture + * @param radius Distance field calculation radius * * Prepares the shader and other internal state for given @p radius. */ @@ -129,25 +223,25 @@ class MAGNUM_TEXTURETOOLS_EXPORT DistanceFieldGL { /** @brief Move constructor */ DistanceFieldGL& operator=(DistanceFieldGL&&) noexcept; - /** @brief Max lookup radius */ + /** @brief Distance field calculation radius */ UnsignedInt radius() const; /** - * @brief Calculate the distance field to a framebuffer - * @param input Input texture - * @param output Output framebuffer - * @param rectangle Rectangle in output texture where to render - * @param imageSize Input texture size. Needed only for OpenGL ES, - * on desktop GL the information is gathered automatically using - * @ref GL::Texture2D::imageSize(). + * @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. Needed only for OpenGL ES, + * on desktop GL the size is queried automatically using + * @ref GL::Texture2D::imageSize() and this parameter is ignored. * @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 but - * still inefficient supported format is in most cases + * @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. * @@ -176,16 +270,17 @@ class MAGNUM_TEXTURETOOLS_EXPORT DistanceFieldGL { #endif /** - * @brief Calculate the distance field to a texture - * @param input Input texture - * @param output Output texture - * @param rectangle Rectangle in output texture where to render - * @param imageSize Input texture size. Needed only for OpenGL ES, + * @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. Needed only for OpenGL ES, * on desktop GL the information is gathered automatically using * @ref GL::Texture2D::imageSize(). * - * Creates a framebuffer with @p output attached and calls - * @ref operator()(GL::Texture2D&, GL::Framebuffer&, const Range2Di&, const Vector2i&). + * 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