Skip to content

Commit b1a97ed

Browse files
committed
TextureTools: initial rude implementation of distance field computation.
Not yet ported anywhere else than GL >= 3.3 with some GL 4.3 features. Still, this closes #3.
1 parent 7ae405b commit b1a97ed

File tree

5 files changed

+233
-1
lines changed

5 files changed

+233
-1
lines changed

Diff for: src/TextureTools/CMakeLists.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
corrade_add_resource(MagnumTextureTools_RCS MagnumTextureTools
2+
DistanceFieldShader.vert DistanceFieldShader.frag
3+
../Shaders/compatibility.glsl ALIAS compatibility.glsl)
4+
15
set(MagnumTextureTools_SRCS
2-
Atlas.cpp)
6+
Atlas.cpp
7+
DistanceField.cpp
8+
${MagnumTextureTools_RCS})
39

410
set(MagnumTextureTools_HEADERS
511
Atlas.h
12+
DistanceField.h
613

714
magnumTextureToolsVisibility.h)
815

Diff for: src/TextureTools/DistanceField.cpp

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
Copyright © 2010, 2011, 2012 Vladimír Vondruš <[email protected]>
3+
4+
This file is part of Magnum.
5+
6+
Magnum is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Lesser General Public License version 3
8+
only, as published by the Free Software Foundation.
9+
10+
Magnum is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License version 3 for more details.
14+
*/
15+
16+
#include "TextureTools/DistanceField.h"
17+
18+
#include <Utility/Resource.h>
19+
#include "Math/Geometry/Rectangle.h"
20+
#include "AbstractShaderProgram.h"
21+
#include "Extensions.h"
22+
#include "Framebuffer.h"
23+
#include "Mesh.h"
24+
#include "Shader.h"
25+
#include "Texture.h"
26+
27+
namespace Magnum { namespace TextureTools {
28+
29+
namespace {
30+
31+
class DistanceFieldShader: public AbstractShaderProgram {
32+
public:
33+
enum: Int {
34+
TextureLayer = 8
35+
};
36+
37+
explicit DistanceFieldShader();
38+
39+
inline DistanceFieldShader* setRadius(Int radius) {
40+
setUniform(radiusUniform, radius);
41+
return this;
42+
}
43+
44+
inline DistanceFieldShader* setScaling(Vector2 scaling) {
45+
setUniform(scalingUniform, scaling);
46+
return this;
47+
}
48+
49+
private:
50+
static const Int radiusUniform = 0,
51+
scalingUniform = 1;
52+
};
53+
54+
DistanceFieldShader::DistanceFieldShader() {
55+
MAGNUM_ASSERT_VERSION_SUPPORTED(Version::GL330);
56+
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_attrib_location);
57+
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_uniform_location);
58+
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::shading_language_420pack);
59+
60+
/** @todo compatibility! */
61+
62+
Corrade::Utility::Resource rs("MagnumTextureTools");
63+
attachShader(Shader::fromData(Version::GL330, Shader::Type::Vertex, rs.get("DistanceFieldShader.vert")));
64+
65+
Shader fragmentShader(Version::GL330, Shader::Type::Fragment);
66+
fragmentShader.addSource(rs.get("compatibility.glsl"));
67+
fragmentShader.addSource(rs.get("DistanceFieldShader.frag"));
68+
attachShader(fragmentShader);
69+
70+
link();
71+
}
72+
73+
}
74+
75+
void distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius) {
76+
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::framebuffer_object);
77+
78+
/** @todo Disable depth test and then enable it back (if was previously) */
79+
80+
Framebuffer framebuffer(rectangle);
81+
framebuffer.attachTexture2D(Framebuffer::ColorAttachment(0), output, 0);
82+
framebuffer.bind(Framebuffer::Target::Draw);
83+
84+
DistanceFieldShader shader;
85+
shader.setRadius(radius)
86+
->setScaling(Vector2(input->imageSize(0))/rectangle.size())
87+
->use();
88+
89+
input->bind(DistanceFieldShader::TextureLayer);
90+
91+
Mesh mesh;
92+
mesh.setPrimitive(Mesh::Primitive::Triangles)
93+
->setVertexCount(3)
94+
->draw();
95+
}
96+
97+
}}

Diff for: src/TextureTools/DistanceField.h

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#ifndef Magnum_TextureTools_DistanceField_h
2+
#define Magnum_TextureTools_DistanceField_h
3+
/*
4+
Copyright © 2010, 2011, 2012 Vladimír Vondruš <[email protected]>
5+
6+
This file is part of Magnum.
7+
8+
Magnum is free software: you can redistribute it and/or modify
9+
it under the terms of the GNU Lesser General Public License version 3
10+
only, as published by the Free Software Foundation.
11+
12+
Magnum is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU Lesser General Public License version 3 for more details.
16+
*/
17+
18+
/** @file
19+
* @brief Function Magnum::TextureTools::distanceField()
20+
*/
21+
22+
#include "Magnum.h"
23+
24+
#include "TextureTools/magnumTextureToolsVisibility.h"
25+
26+
namespace Magnum { namespace TextureTools {
27+
28+
/**
29+
@brief Create signed distance field
30+
@param input Input texture
31+
@param output Output texture
32+
@param rectangle Rectangle in output texture where to render
33+
@param radius Max lookup radius in input texture
34+
35+
Converts binary image (stored in red channel of @p input) to signed distance
36+
field (stored in red channel in @p rectangle of @p output). The purpose of this
37+
function is to convert high-resolution binary image (such as vector artwork or
38+
font glyphs) to low-resolution grayscale image. The image will then occupy much
39+
less memory and can be scaled without aliasing issues. Additionally it provides
40+
foundation for features like outlining, glow or drop shadow essentialy for free.
41+
42+
For each pixel inside @p rectangle the algorithm looks at corresponding pixel in
43+
@p input and tries to find nearest pixel of opposite color in area given by
44+
@p radius. Signed distance between the points is then saved as value of given
45+
pixel in @p output. Value of `0` means that the pixel was originally colored
46+
white and nearest black pixel is farther than @p radius, value of `1` means that
47+
the pixel was originally black and nearest white pixel is farther than
48+
@p radius. Values around `0.5` are around edges.
49+
50+
The resulting texture can be used with bilinear filtering. It can be converted
51+
back to binary form in shader using e.g. GLSL `smoothstep()` function with step
52+
around `0.5` to create antialiased edges. Or you can exploit the distance field
53+
features to create many other effects.
54+
55+
Based on: *Chris Green - Improved Alpha-Tested Magnification for Vector Textures
56+
and Special Effects, SIGGRAPH 2007,
57+
http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf*
58+
59+
@attention This is GPU-only implementation, so it expects active context.
60+
*/
61+
void MAGNUM_TEXTURETOOLS_EXPORT distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius);
62+
63+
}}
64+
65+
#endif

Diff for: src/TextureTools/DistanceFieldShader.frag

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
layout(location = 0) uniform int radius;
2+
layout(location = 1) uniform vec2 scaling;
3+
layout(binding = 8) uniform sampler2D texture;
4+
5+
layout(pixel_center_integer) in vec4 gl_FragCoord;
6+
7+
out float value;
8+
9+
ivec2 rotate(const ivec2 vec) {
10+
return ivec2(-vec.y, vec.x);
11+
}
12+
13+
bool hasValue(const ivec2 position, const ivec2 offset) {
14+
return texelFetch(texture, position+offset, 0).r > 0.5;
15+
}
16+
17+
void main() {
18+
const ivec2 position = ivec2(gl_FragCoord.xy*scaling);
19+
20+
/* If pixel at the position is inside (1), we are looking for nearest pixel
21+
outside and the value will be positive (> 0.5). If it is outside (0), we
22+
are looking for nearest pixel inside and the value will be negative
23+
(< 0.5). */
24+
const bool isInside = hasValue(position, ivec2(0, 0));
25+
const float sign = isInside ? 1.0 : -1.0;
26+
27+
/* Minimal found distance is just out of the radius (i.e. infinity) */
28+
float minDistanceSquared = float((radius+1)*(radius+1));
29+
30+
/* Go in circles around the point and find nearest value */
31+
int radiusLimit = radius;
32+
for(int i = 1; i <= radiusLimit; ++i) {
33+
for(int j = 0, jmax = i*2; j != jmax; ++j) {
34+
const ivec2 offset = {-i+j, i};
35+
36+
/* If any of the four values is opposite of what is on the pixel,
37+
we found nearest value */
38+
if(hasValue(position, offset) == !isInside ||
39+
hasValue(position, rotate(offset)) == !isInside ||
40+
hasValue(position, rotate(rotate(offset))) == !isInside ||
41+
hasValue(position, rotate(rotate(rotate(offset)))) == !isInside) {
42+
const float distanceSquared = dot(vec2(offset), vec2(offset));
43+
44+
/* Set smaller distance, if found, or continue with lookup for
45+
smaller */
46+
if(minDistanceSquared < distanceSquared) continue;
47+
else minDistanceSquared = distanceSquared;
48+
49+
/* Set radius limit to max radius which can contain smaller
50+
value, e.g. for distance 3.5 we can find smaller value even
51+
in radius 3 */
52+
radiusLimit = min(radius, int(floor(length(vec2(offset)))));
53+
}
54+
}
55+
}
56+
57+
/* Final signed distance, normalized from [-radius-1, radius+1] to [0, 1] */
58+
value = sign*sqrt(minDistanceSquared)/float(radius*2+2)+0.5;
59+
}

Diff for: src/TextureTools/DistanceFieldShader.vert

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
void main() {
2+
gl_Position = vec4((gl_VertexID == 2) ? 3.0 : -1.0,
3+
(gl_VertexID == 1) ? -3.0 : 1.0, 0.0, 1.0);
4+
}

0 commit comments

Comments
 (0)