Skip to content

Commit

Permalink
TextureTools: initial rude implementation of distance field computation.
Browse files Browse the repository at this point in the history
Not yet ported anywhere else than GL >= 3.3 with some GL 4.3 features.
Still, this closes #3.
  • Loading branch information
mosra committed Mar 3, 2013
1 parent 7ae405b commit b1a97ed
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 1 deletion.
9 changes: 8 additions & 1 deletion 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)

Expand Down
97 changes: 97 additions & 0 deletions src/TextureTools/DistanceField.cpp
@@ -0,0 +1,97 @@
/*
Copyright © 2010, 2011, 2012 Vladimír Vondruš <mosra@centrum.cz>
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 <Utility/Resource.h>
#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();
}

}}
65 changes: 65 additions & 0 deletions 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š <mosra@centrum.cz>
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
59 changes: 59 additions & 0 deletions src/TextureTools/DistanceFieldShader.frag
@@ -0,0 +1,59 @@
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;
}
4 changes: 4 additions & 0 deletions 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);
}

0 comments on commit b1a97ed

Please sign in to comment.