diff --git a/src/TextureTools/CMakeLists.txt b/src/TextureTools/CMakeLists.txt index e7bbd4fa68..825658dadc 100644 --- a/src/TextureTools/CMakeLists.txt +++ b/src/TextureTools/CMakeLists.txt @@ -1,8 +1,15 @@ +corrade_add_resource(MagnumTextureTools_RCS MagnumTextureTools + DistanceFieldShader.vert DistanceFieldShader.frag + ../Shaders/compatibility.glsl ALIAS compatibility.glsl) + set(MagnumTextureTools_SRCS - Atlas.cpp) + Atlas.cpp + DistanceField.cpp + ${MagnumTextureTools_RCS}) set(MagnumTextureTools_HEADERS Atlas.h + DistanceField.h magnumTextureToolsVisibility.h) diff --git a/src/TextureTools/DistanceField.cpp b/src/TextureTools/DistanceField.cpp new file mode 100644 index 0000000000..e67146ef0f --- /dev/null +++ b/src/TextureTools/DistanceField.cpp @@ -0,0 +1,97 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "TextureTools/DistanceField.h" + +#include +#include "Math/Geometry/Rectangle.h" +#include "AbstractShaderProgram.h" +#include "Extensions.h" +#include "Framebuffer.h" +#include "Mesh.h" +#include "Shader.h" +#include "Texture.h" + +namespace Magnum { namespace TextureTools { + +namespace { + +class DistanceFieldShader: public AbstractShaderProgram { + public: + enum: Int { + TextureLayer = 8 + }; + + explicit DistanceFieldShader(); + + inline DistanceFieldShader* setRadius(Int radius) { + setUniform(radiusUniform, radius); + return this; + } + + inline DistanceFieldShader* setScaling(Vector2 scaling) { + setUniform(scalingUniform, scaling); + return this; + } + + private: + static const Int radiusUniform = 0, + scalingUniform = 1; +}; + +DistanceFieldShader::DistanceFieldShader() { + MAGNUM_ASSERT_VERSION_SUPPORTED(Version::GL330); + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_attrib_location); + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_uniform_location); + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::shading_language_420pack); + + /** @todo compatibility! */ + + Corrade::Utility::Resource rs("MagnumTextureTools"); + attachShader(Shader::fromData(Version::GL330, Shader::Type::Vertex, rs.get("DistanceFieldShader.vert"))); + + Shader fragmentShader(Version::GL330, Shader::Type::Fragment); + fragmentShader.addSource(rs.get("compatibility.glsl")); + fragmentShader.addSource(rs.get("DistanceFieldShader.frag")); + attachShader(fragmentShader); + + link(); +} + +} + +void distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius) { + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::framebuffer_object); + + /** @todo Disable depth test and then enable it back (if was previously) */ + + Framebuffer framebuffer(rectangle); + framebuffer.attachTexture2D(Framebuffer::ColorAttachment(0), output, 0); + framebuffer.bind(Framebuffer::Target::Draw); + + DistanceFieldShader shader; + shader.setRadius(radius) + ->setScaling(Vector2(input->imageSize(0))/rectangle.size()) + ->use(); + + input->bind(DistanceFieldShader::TextureLayer); + + Mesh mesh; + mesh.setPrimitive(Mesh::Primitive::Triangles) + ->setVertexCount(3) + ->draw(); +} + +}} diff --git a/src/TextureTools/DistanceField.h b/src/TextureTools/DistanceField.h new file mode 100644 index 0000000000..fe107020d0 --- /dev/null +++ b/src/TextureTools/DistanceField.h @@ -0,0 +1,65 @@ +#ifndef Magnum_TextureTools_DistanceField_h +#define Magnum_TextureTools_DistanceField_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Function Magnum::TextureTools::distanceField() + */ + +#include "Magnum.h" + +#include "TextureTools/magnumTextureToolsVisibility.h" + +namespace Magnum { namespace TextureTools { + +/** +@brief Create signed distance field +@param input Input texture +@param output Output texture +@param rectangle Rectangle in output texture where to render +@param radius Max lookup radius in input texture + +Converts binary image (stored in red channel of @p input) to signed distance +field (stored in red channel in @p rectangle of @p output). The purpose of this +function is to convert high-resolution binary image (such as vector artwork or +font glyphs) to 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 essentialy for free. + +For each pixel inside @p rectangle the algorithm looks at corresponding pixel in +@p input and tries to find nearest pixel of opposite color in area given by +@p radius. Signed distance between the points is then saved as value of given +pixel in @p output. Value of `0` means that the pixel was originally colored +white and nearest black pixel is farther than @p radius, value of `1` means that +the pixel was originally black and nearest white pixel is farther than +@p radius. Values around `0.5` are around edges. + +The resulting texture can be used with bilinear filtering. It can be converted +back to binary form in shader using e.g. GLSL `smoothstep()` function with step +around `0.5` to create antialiased edges. Or you can exploit the distance field +features to create many other effects. + +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* + +@attention This is GPU-only implementation, so it expects active context. +*/ +void MAGNUM_TEXTURETOOLS_EXPORT distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius); + +}} + +#endif diff --git a/src/TextureTools/DistanceFieldShader.frag b/src/TextureTools/DistanceFieldShader.frag new file mode 100644 index 0000000000..266e0c7101 --- /dev/null +++ b/src/TextureTools/DistanceFieldShader.frag @@ -0,0 +1,60 @@ +#line 2 +layout(location = 0) uniform int radius; +layout(location = 1) uniform vec2 scaling; +layout(binding = 8) uniform sampler2D texture; + +layout(pixel_center_integer) in vec4 gl_FragCoord; + +out float value; + +ivec2 rotate(const ivec2 vec) { + return ivec2(-vec.y, vec.x); +} + +bool hasValue(const ivec2 position, const ivec2 offset) { + return texelFetch(texture, position+offset, 0).r > 0.5; +} + +void main() { + const ivec2 position = ivec2(gl_FragCoord.xy*scaling); + + /* If pixel at the position is inside (1), we are looking for nearest pixel + outside and the value will be positive (> 0.5). If it is outside (0), we + are looking for nearest pixel inside and the value will be negative + (< 0.5). */ + const bool isInside = hasValue(position, ivec2(0, 0)); + const float sign = isInside ? 1.0 : -1.0; + + /* Minimal found distance is just out of the radius (i.e. infinity) */ + float minDistanceSquared = float((radius+1)*(radius+1)); + + /* Go in circles around the point and find nearest value */ + int radiusLimit = radius; + for(int i = 1; i <= radiusLimit; ++i) { + for(int j = 0, jmax = i*2; j != jmax; ++j) { + const ivec2 offset = {-i+j, i}; + + /* If any of the four values is opposite of what is on the pixel, + we found nearest value */ + if(hasValue(position, offset) == !isInside || + hasValue(position, rotate(offset)) == !isInside || + hasValue(position, rotate(rotate(offset))) == !isInside || + hasValue(position, rotate(rotate(rotate(offset)))) == !isInside) { + const float distanceSquared = dot(vec2(offset), vec2(offset)); + + /* Set smaller distance, if found, or continue with lookup for + smaller */ + if(minDistanceSquared < distanceSquared) continue; + else minDistanceSquared = distanceSquared; + + /* Set radius limit to max radius which can contain smaller + value, e.g. for distance 3.5 we can find smaller value even + in radius 3 */ + radiusLimit = min(radius, int(floor(length(vec2(offset))))); + } + } + } + + /* Final signed distance, normalized from [-radius-1, radius+1] to [0, 1] */ + value = sign*sqrt(minDistanceSquared)/float(radius*2+2)+0.5; +} diff --git a/src/TextureTools/DistanceFieldShader.vert b/src/TextureTools/DistanceFieldShader.vert new file mode 100644 index 0000000000..325b2e7330 --- /dev/null +++ b/src/TextureTools/DistanceFieldShader.vert @@ -0,0 +1,4 @@ +void main() { + gl_Position = vec4((gl_VertexID == 2) ? 3.0 : -1.0, + (gl_VertexID == 1) ? -3.0 : 1.0, 0.0, 1.0); +}