mirror of https://github.com/mosra/magnum.git
Browse Source
Not yet ported anywhere else than GL >= 3.3 with some GL 4.3 features. Still, this closes #3.pull/278/head
5 changed files with 233 additions and 1 deletions
@ -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(); |
||||
} |
||||
|
||||
}} |
||||
@ -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 |
||||
@ -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; |
||||
} |
||||
Loading…
Reference in new issue