Browse Source

Merge branch 'blur'

Michael Ragazzon 6 years ago
parent
commit
0b53977b06

+ 6 - 4
CMake/FileList.cmake

@@ -32,10 +32,10 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/EventIterators.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventSpecification.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutlineInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadowInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.h
@@ -43,6 +43,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Memory.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h
     ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.h
@@ -231,11 +232,11 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutlineInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadowInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEngineInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
@@ -247,6 +248,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp

+ 29 - 20
Include/RmlUi/Core/ConvolutionFilter.h

@@ -32,6 +32,21 @@
 namespace Rml {
 namespace Core {
 
+enum class FilterOperation {
+	// The result is the sum of all the filtered pixels.
+	Sum,
+	// The result is the largest value of all filtered pixels.
+	Dilation,
+	// The result is the smallest value of all the filtered pixels.
+	Erosion
+};
+
+enum class ColorFormat {
+	RGBA8,
+	A8
+};
+
+
 /**
 	A programmable convolution filter, designed to aid in the generation of texture data by custom
 	FontEffect types.
@@ -39,31 +54,24 @@ namespace Core {
 	@author Peter Curry
  */
 
-class ConvolutionFilter
+class RMLUICORE_API ConvolutionFilter
 {
 public:
-	enum FilterOperation
-	{
-		// The result is the median value of all the filtered pixels.
-		MEDIAN,
-		// The result is the smallest value of all filtered pixels.
-		DILATION,
-		// The result is the largest value of all the filtered pixels.
-		EROSION
-	};
-
 	ConvolutionFilter();
 	~ConvolutionFilter();
 
+	/// Initialises a square kernel filter with the given radius.
+	bool Initialise(int kernel_radius, FilterOperation operation);
+
 	/// Initialises the filter. A filter must be initialised and populated with values before use.
-	/// @param[in] kernel_size The size of the filter's kernel each side of the origin. So, for example, a filter initialised with a size of 1 will store 9 values.
+	/// @param[in] kernel_radii The size of the filter's kernel on each side of the origin along both axes. So, for example, a filter initialised with radii (1,1) will store 9 values.
 	/// @param[in] operation The operation the filter conducts to determine the result.
-	bool Initialise(int kernel_size, FilterOperation operation = MEDIAN);
+	bool Initialise(Vector2i kernel_radii, FilterOperation operation);
 
 	/// Returns a reference to one of the rows of the filter kernel.
-	/// @param[in] index The index of the desired row.
-	/// @return The row of kernel values.
-	float* operator[](int index);
+	/// @param[in] kernel_y_index The index of the desired row.
+	/// @return Pointer to the first value in the kernel row.
+	float* operator[](int kernel_y_index);
 
 	/// Runs the convolution filter. The filter will operate on each pixel in the destination
 	/// surface, setting its opacity to the result the filter on the source opacity values. The
@@ -71,16 +79,17 @@ public:
 	/// @param[in] destination The RGBA-encoded destination buffer.
 	/// @param[in] destination_dimensions The size of the destination region (in pixels).
 	/// @param[in] destination_stride The stride (in bytes) of the destination region.
+	/// @param[in] destination_color_format Determines the representation of the bytes in the destination texture, only the alpha channel will be written to.
 	/// @param[in] source The opacity information for the source buffer.
 	/// @param[in] source_dimensions The size of the source region (in pixels). The stride is assumed to be equivalent to the horizontal width.
 	/// @param[in] source_offset The offset of the source region from the destination region. This is usually the same as the kernel size.
-	void Run(byte* destination, const Vector2i& destination_dimensions, int destination_stride, const byte* source, const Vector2i& source_dimensions, const Vector2i& source_offset) const;
+	void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, ColorFormat destination_color_format, const byte* source, Vector2i source_dimensions, Vector2i source_offset) const;
 
 private:
-	int kernel_size;
-	float* kernel;
+	Vector2i kernel_size;
+	UniquePtr<float[]> kernel;
 
-	FilterOperation operation;
+	FilterOperation operation = FilterOperation::Sum;
 };
 
 }

+ 5 - 8
Include/RmlUi/Core/FontEffect.h

@@ -38,10 +38,10 @@ namespace Core {
 	@author Peter Curry
  */
 
-class FontEffect
+class RMLUICORE_API FontEffect
 {
 public:
-	// Behind or in front of main text
+	// Behind or in front of the main text.
 	enum class Layer { Back, Front };
 
 	FontEffect();
@@ -59,19 +59,16 @@ public:
 	/// @return False if the effect is not providing support for the glyph, true otherwise.
 	virtual bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const;
 
-	/// Requests the effect to generate the texture data for a single glyph's bitmap. The default implementation does
-	/// nothing.
-	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that they glyph shares its texture with other glyphs.
+	/// Requests the effect to generate the texture data for a single glyph's bitmap. The default implementation does nothing.
+	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that the glyph shares its texture with other glyphs.
 	/// @param[in] destination_dimensions The dimensions of the glyph's area on its texture.
 	/// @param[in] destination_stride The stride of the glyph's texture.
 	/// @param[in] glyph The glyph the effect is being asked to generate an effect texture for.
-	virtual void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const;
+	virtual void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const;
 
 	/// Sets the colour of the effect's geometry.
-	/// @param[in] colour The effect's colour.
 	void SetColour(const Colourb& colour);
 	/// Returns the effect's colour.
-	/// @return The colour of the effect.
 	const Colourb& GetColour() const;
 
 	Layer GetLayer() const;

+ 1 - 1
Include/RmlUi/Core/FontEffectInstancer.h

@@ -42,7 +42,7 @@ class FontEffect;
 /**
 	A font effect instancer provides a method for allocating and deallocating font effects.
 
-	It is important at the same instancer that allocated a font effect releases it. This ensures there are no issues
+	It is important that the same instancer that allocated a font effect releases it. This ensures there are no issues
 	with memory from different DLLs getting mixed up.
 
 	@author Peter Curry

+ 1 - 1
Include/RmlUi/Core/FontGlyph.h

@@ -40,7 +40,7 @@ namespace Core {
 	@author Peter Curry
  */
 
-class FontGlyph
+class RMLUICORE_API FontGlyph
 {
 public:
 	FontGlyph() : dimensions(0,0), bearing(0,0), advance(0), bitmap_data(nullptr), bitmap_dimensions(0,0)

+ 1 - 1
Include/RmlUi/Core/Vector2.h

@@ -46,7 +46,7 @@ class Vector2
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector2(Type v = Type{ 0 });
+		explicit inline Vector2(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Include/RmlUi/Core/Vector3.h

@@ -46,7 +46,7 @@ class Vector3
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector3(Type v = Type{ 0 });
+		explicit inline Vector3(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Include/RmlUi/Core/Vector4.h

@@ -47,7 +47,7 @@ class Vector4
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector4(Type v = Type{ 0 });
+		explicit inline Vector4(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Samples/assets/invader.rcss

@@ -174,7 +174,7 @@ div#title_bar span
 	font-size: 22px;
 	font-weight: bold;
 	
-	font-effect: outline(1px black);
+	font-effect: glow(1px black);
 
 	decorator: tiled-horizontal( title-bar-l, title-bar-c, title-bar-r );
 }

+ 86 - 7
Samples/basic/demo/data/demo.rml

@@ -40,7 +40,7 @@ body.window
 {
 	width: 1300px;
 	height: 750px;
-	min-width: 940px;
+	min-width: 1090px;
 	min-height: 300px;
 	max-width: -1px;
 	max-height: -1px;
@@ -52,6 +52,7 @@ div#title_bar div#icon
 div#content
 { 
 	position: relative; 
+	/* background-color: #bbb; */
 }
 tabset
 {
@@ -61,21 +62,17 @@ tabs
 {
     display: block;
 	position: fixed;
-	width: 100%;
 	clip: none;
-	pointer-events: none;
 	text-align: right;
+	padding-left: 200px;
 	padding-right: 10px;
+	top: -47px;
 }
 tab
 {
-	position: relative;
     width: 100px;
-	pointer-events: auto;
 	padding: 0px 20px;
 	line-height: 40px;
-	top: -47px;
-	z-index: 2;
 	
 	font-size: 16px;
 	color: #ddd;
@@ -115,6 +112,7 @@ p.title
 {
 	font-size: 35px;
 	color: #b33;
+	font-effect: glow(2px #ed5);
 }
 .center {
 	text-align: center;
@@ -222,6 +220,68 @@ p.title
 .orientation-rotate     { decorator: image( icon-invader rotate-180      scale-none ); }
 
 
+/***  Font effects  ***/
+
+#font_effects div 
+{
+	display: inline-block;
+	width: 150px;
+	margin: 0px 30px 30px;
+	text-align: center;
+	font-size: 35px;
+	color: #b33;
+}
+#font_effects h1 
+{
+	margin: 15px 0 10px 0;
+}
+#font_effects .glow
+{
+	font-effect: glow(3px #ed5);
+}
+#font_effects .glow_sharper
+{
+	font-effect: glow(3px 1px #ed5);
+}
+#font_effects .glow_blurry
+{
+	font-effect: glow(2px 7px #ed5);
+}
+#font_effects .glow_shadow
+{
+	color: #ed5;
+	font-effect: glow(2px 4px 2px 3px #644);
+}
+#font_effects .outline_small
+{
+	font-effect: outline(2px #ed5);
+}
+#font_effects .outline_big
+{
+	font-effect: outline(4px #ed5);
+}
+#font_effects .blur_small
+{
+	color: transparent;
+	font-effect: blur(3px #ed5);
+}
+#font_effects .blur_big
+{
+	color: transparent;
+	font-effect: blur(10px #ed5);
+}
+#font_effects .shadow_up
+{
+	font-weight: bold;
+	font-effect: shadow(3px -3px #ed5);
+}
+#font_effects .shadow_down
+{
+	font-weight: bold;
+	font-effect: shadow(0px 2px #333);
+}
+
+
 /***  Animations  ***/
 
 #tweening_area
@@ -574,6 +634,25 @@ form h2
 	<img src="../../../assets/invader.tga" rect="0 0 500 300" style="margin-top: 10px; margin-bottom: 10px;"/>
 	<p>is used to render most sprites in this demo. Sprites can be used in decorators and image elements as if they were separate images.</p>
 </panel>
+<tab>Font effects</tab>
+<panel id="font_effects">
+	<h1>None</h1>
+	<div class="original">RmlUi 😍</div>
+	<h1>Glow</h1>
+	<div class="glow">RmlUi 😍</div>
+	<div class="glow_sharper">RmlUi 😍</div>
+	<div class="glow_blurry">RmlUi 😍</div>
+	<div class="glow_shadow">RmlUi 😍</div>
+	<h1>Outline</h1>
+	<div class="outline_small">RmlUi 😍</div>
+	<div class="outline_big">RmlUi 😍</div>
+	<h1>Shadow</h1>
+	<div class="shadow_up">RmlUi 😍</div>
+	<div class="shadow_down">RmlUi 😍</div>
+	<h1>Blur</h1>
+	<div class="blur_small">RmlUi 😍</div>
+	<div class="blur_big">RmlUi 😍</div>
+</panel>
 <tab>Animations</tab>
 <panel id="animations">
 	

+ 43 - 43
Source/Core/ConvolutionFilter.cpp

@@ -33,90 +33,90 @@ namespace Rml {
 namespace Core {
 
 ConvolutionFilter::ConvolutionFilter()
-{
-	kernel_size = 0;
-	kernel = nullptr;
-
-	operation = MEDIAN;
-}
+{}
 
 ConvolutionFilter::~ConvolutionFilter()
+{}
+
+bool ConvolutionFilter::Initialise(int _kernel_radius, FilterOperation _operation)
 {
-	delete[] kernel;
+	return Initialise(Vector2i(_kernel_radius), _operation);
 }
 
-// Initialises the filter. A filter must be initialised and populated with values before use.
-bool ConvolutionFilter::Initialise(int _kernel_size, FilterOperation _operation)
+bool ConvolutionFilter::Initialise(Vector2i _kernel_radii, FilterOperation _operation)
 {
-	if (_kernel_size <= 0)
+	if (_kernel_radii.x < 0 || _kernel_radii.y < 0)
+	{
+		RMLUI_ERRORMSG("Invalid input parameters to convolution filter.");
 		return false;
+	}
 
-	kernel_size = Math::Max(_kernel_size, 1);
-	kernel_size = kernel_size * 2 + 1;
+	kernel_size = _kernel_radii * 2 + Vector2i(1);
 
-	kernel = new float[kernel_size * kernel_size];
-	memset(kernel, 0, kernel_size * kernel_size * sizeof(float));
+	kernel = UniquePtr<float[]>(new float[kernel_size.x * kernel_size.y]);
+	memset(kernel.get(), 0, kernel_size.x * kernel_size.y * sizeof(float));
 
 	operation = _operation;
 	return true;
 }
 
-// Returns a reference to one of the rows of the filter kernel.
-float* ConvolutionFilter::operator[](int index)
+float* ConvolutionFilter::operator[](int kernel_y_index)
 {
-	RMLUI_ASSERT(kernel != nullptr);
+	RMLUI_ASSERT(kernel != nullptr && kernel_y_index >= 0 && kernel_y_index < kernel_size.y);
 
-	index = Math::Max(index, 0);
-	index = Math::Min(index, kernel_size - 1);
+	kernel_y_index = Math::Clamp(kernel_y_index, 0, kernel_size.y - 1);
 
-	return kernel + kernel_size * index;
+	return kernel.get() + kernel_size.x * kernel_y_index;
 }
 
-// Runs the convolution filter.
-void ConvolutionFilter::Run(byte* destination, const Vector2i& destination_dimensions, int destination_stride, const byte* source, const Vector2i& source_dimensions, const Vector2i& source_offset) const
+void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, const int destination_stride, const ColorFormat destination_color_format, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset) const
 {
+	const float initial_opacity = (operation == FilterOperation::Erosion ? FLT_MAX : 0.f);
+
+	const Vector2i kernel_radius = (kernel_size - Vector2i(1)) / 2;
+
 	for (int y = 0; y < destination_dimensions.y; ++y)
 	{
 		for (int x = 0; x < destination_dimensions.x; ++x)
 		{
-			int num_pixels = 0;
-			int opacity = 0;
+			float opacity = initial_opacity;
 
-			for (int kernel_y = 0; kernel_y < kernel_size; ++kernel_y)
+			for (int kernel_y = 0; kernel_y < kernel_size.y; ++kernel_y)
 			{
-				int source_y = y - source_offset.y - ((kernel_size - 1) / 2) + kernel_y;
+				int source_y = y - source_offset.y - kernel_radius.y + kernel_y;
 
-				for (int kernel_x = 0; kernel_x < kernel_size; ++kernel_x)
+				for (int kernel_x = 0; kernel_x < kernel_size.x; ++kernel_x)
 				{
-					int pixel_opacity;
+					float pixel_opacity;
 
-					int source_x = x - source_offset.x - ((kernel_size - 1) / 2) + kernel_x;
-					if (source_y >= 0 &&
-						source_y < source_dimensions.y &&
-						source_x >= 0 &&
-						source_x < source_dimensions.x)
+					int source_x = x - source_offset.x - kernel_radius.x + kernel_x;
+					if (source_y >= 0 && source_y < source_dimensions.y &&
+						source_x >= 0 && source_x < source_dimensions.x)
 					{
-						pixel_opacity = Math::RealToInteger(source[source_y * source_dimensions.x + source_x] * kernel[kernel_y * kernel_size + kernel_x]);
+						pixel_opacity = float(source[source_y * source_dimensions.x + source_x]) * kernel[kernel_y * kernel_size.x + kernel_x];
 					}
 					else
 						pixel_opacity = 0;
 
 					switch (operation)
 					{
-						case MEDIAN:	opacity += pixel_opacity; break;
-						case DILATION:	opacity = Math::Max(opacity, pixel_opacity); break;
-						case EROSION:	opacity = num_pixels == 0 ? pixel_opacity : Math::Min(opacity, pixel_opacity); break;
+					case FilterOperation::Sum:      opacity += pixel_opacity; break;
+					case FilterOperation::Dilation: opacity = Math::Max(opacity, pixel_opacity); break;
+					case FilterOperation::Erosion:  opacity = Math::Min(opacity, pixel_opacity); break;
 					}
-
-					++num_pixels;
 				}
 			}
 
-			if (operation == MEDIAN)
-				opacity /= num_pixels;
+			opacity = Math::Min(255.f, opacity);
+
+			int destination_index = 0;
+			switch (destination_color_format)
+			{
+			case ColorFormat::RGBA8: destination_index = x * 4 + 3; break;
+			case ColorFormat::A8:    destination_index = x; break;
+			}
 
-			opacity = Math::Min(255, opacity);
-			destination[x * 4 + 3] = (byte) opacity;
+			destination[destination_index] = byte(opacity);
 		}
 
 		destination += destination_stride;

+ 10 - 4
Source/Core/Factory.cpp

@@ -40,8 +40,10 @@
 #include "ElementImage.h"
 #include "ElementTextDefault.h"
 #include "EventInstancerDefault.h"
-#include "FontEffectOutlineInstancer.h"
-#include "FontEffectShadowInstancer.h"
+#include "FontEffectBlur.h"
+#include "FontEffectGlow.h"
+#include "FontEffectOutline.h"
+#include "FontEffectShadow.h"
 #include "PluginRegistry.h"
 #include "PropertyParserColour.h"
 #include "StreamFile.h"
@@ -98,8 +100,10 @@ struct DefaultInstancers {
 	Ptr<DecoratorInstancer> decorator_ninepatch = std::make_unique<DecoratorNinePatchInstancer>();
 	Ptr<DecoratorInstancer> decorator_gradient = std::make_unique<DecoratorGradientInstancer>();
 
-	Ptr<FontEffectInstancer> font_effect_shadow = std::make_unique<FontEffectShadowInstancer>();
+	Ptr<FontEffectInstancer> font_effect_blur = std::make_unique<FontEffectBlurInstancer>();
+	Ptr<FontEffectInstancer> font_effect_glow = std::make_unique<FontEffectGlowInstancer>();
 	Ptr<FontEffectInstancer> font_effect_outline = std::make_unique<FontEffectOutlineInstancer>();
+	Ptr<FontEffectInstancer> font_effect_shadow = std::make_unique<FontEffectShadowInstancer>();
 };
 
 static UniquePtr<DefaultInstancers> default_instancers;
@@ -151,8 +155,10 @@ bool Factory::Initialise()
 	RegisterDecoratorInstancer("ninepatch", default_instancers->decorator_ninepatch.get());
 	RegisterDecoratorInstancer("gradient", default_instancers->decorator_gradient.get());
 
-	RegisterFontEffectInstancer("shadow", default_instancers->font_effect_shadow.get());
+	RegisterFontEffectInstancer("blur", default_instancers->font_effect_blur.get());
+	RegisterFontEffectInstancer("glow", default_instancers->font_effect_glow.get());
 	RegisterFontEffectInstancer("outline", default_instancers->font_effect_outline.get());
+	RegisterFontEffectInstancer("shadow", default_instancers->font_effect_shadow.get());
 
 	// Register the core XML node handlers.
 	XMLParser::RegisterNodeHandler("", std::make_shared<XMLNodeHandlerDefault>());

+ 1 - 6
Source/Core/FontEffect.cpp

@@ -42,13 +42,11 @@ FontEffect::~FontEffect()
 {
 }
 
-// Asks the font effect if it requires, and will generate, its own unique texture.
 bool FontEffect::HasUniqueTexture() const
 {
 	return false;
 }
 
-// Gets the effect to resize and reposition a glyph's bitmap.
 bool FontEffect::GetGlyphMetrics(Vector2i& RMLUI_UNUSED_PARAMETER(origin), Vector2i& RMLUI_UNUSED_PARAMETER(dimensions), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
 {
 	RMLUI_UNUSED(origin);
@@ -58,8 +56,7 @@ bool FontEffect::GetGlyphMetrics(Vector2i& RMLUI_UNUSED_PARAMETER(origin), Vecto
 	return false;
 }
 
-// Requests the effect to generate the texture data for a single glyph's bitmap.
-void FontEffect::GenerateGlyphTexture(byte* RMLUI_UNUSED_PARAMETER(destination_data), const Vector2i& RMLUI_UNUSED_PARAMETER(destination_dimensions), int RMLUI_UNUSED_PARAMETER(destination_stride), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
+void FontEffect::GenerateGlyphTexture(byte* RMLUI_UNUSED_PARAMETER(destination_data), Vector2i RMLUI_UNUSED_PARAMETER(destination_dimensions), int RMLUI_UNUSED_PARAMETER(destination_stride), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
 {
 	RMLUI_UNUSED(destination_data);
 	RMLUI_UNUSED(destination_dimensions);
@@ -67,13 +64,11 @@ void FontEffect::GenerateGlyphTexture(byte* RMLUI_UNUSED_PARAMETER(destination_d
 	RMLUI_UNUSED(glyph);
 }
 
-// Sets the colour of the effect's geometry.
 void FontEffect::SetColour(const Colourb& _colour)
 {
 	colour = _colour;
 }
 
-// Returns the effect's colour.
 const Colourb& FontEffect::GetColour() const
 {
 	return colour;

+ 150 - 0
Source/Core/FontEffectBlur.cpp

@@ -0,0 +1,150 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * 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 "precompiled.h"
+#include "FontEffectBlur.h"
+#include "Memory.h"
+
+namespace Rml {
+namespace Core {
+
+FontEffectBlur::FontEffectBlur()
+{
+	width = 0;
+	SetLayer(Layer::Back);
+}
+
+FontEffectBlur::~FontEffectBlur()
+{
+}
+
+bool FontEffectBlur::HasUniqueTexture() const
+{
+	return true;
+}
+
+bool FontEffectBlur::Initialise(int _width)
+{
+	if (_width <= 0)
+		return false;
+
+	width = _width;
+
+	const float std_dev = .4f * float(width);
+	const float two_variance = 2.f * std_dev * std_dev;
+	const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance);
+
+	float sum_weight = 0.f;
+
+	// We separate the blur filter into two passes, horizontal and vertical, for performance reasons.
+	filter_x.Initialise(Vector2i(width, 0), FilterOperation::Sum);
+	filter_y.Initialise(Vector2i(0, width), FilterOperation::Sum);
+
+	for (int x = -width; x <= width; ++x)
+	{
+		float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance));
+
+		filter_x[0][x + width] = weight;
+		filter_y[x + width][0] = weight;
+		sum_weight += weight;
+	}
+
+	// Normalize the kernels
+	for (int x = -width; x <= width; ++x)
+	{
+		filter_x[0][x + width] /= sum_weight;
+		filter_y[x + width][0] /= sum_weight;
+	}
+
+	return true;
+}
+
+bool FontEffectBlur::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
+{
+	RMLUI_UNUSED(glyph);
+
+	if (dimensions.x * dimensions.y > 0)
+	{
+		origin.x -= width;
+		origin.y -= width;
+
+		dimensions.y += 2 * width;
+		dimensions.x += 2 * width;
+
+		return true;
+	}
+
+	return false;
+}
+
+void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+{
+	const Vector2i buf_dimensions = destination_dimensions;
+	const int buf_stride = buf_dimensions.x;
+	const int buf_size = buf_dimensions.x * buf_dimensions.y;
+	DynamicArray<byte, GlobalStackAllocator<byte>> x_output(buf_size);
+
+	filter_x.Run(x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
+
+	filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0));
+}
+
+
+
+
+
+FontEffectBlurInstancer::FontEffectBlurInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid)
+{
+	id_width = RegisterProperty("width", "1px", true).AddParser("length").GetId();
+	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
+	RegisterShorthand("font-effect", "width, color", ShorthandType::FallThrough);
+}
+
+FontEffectBlurInstancer::~FontEffectBlurInstancer()
+{
+}
+
+SharedPtr<FontEffect> FontEffectBlurInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
+{
+	RMLUI_UNUSED(name);
+
+	float width = properties.GetProperty(id_width)->Get< float >();
+	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
+
+	auto font_effect = std::make_shared<FontEffectBlur>();
+	if (font_effect->Initialise(Math::RealToInteger(width)))
+	{
+		font_effect->SetColour(color);
+		return font_effect;
+	}
+
+	return nullptr;
+}
+
+}
+}

+ 34 - 8
Source/Core/FontEffectShadowInstancer.h → Source/Core/FontEffectBlur.h

@@ -26,32 +26,58 @@
  *
  */
 
-#ifndef RMLUICOREFontEffectShadowInstancer_H
-#define RMLUICOREFontEffectShadowInstancer_H
+#ifndef RMLUICOREFONTEFFECTBLUR_H
+#define RMLUICOREFONTEFFECTBLUR_H
 
+#include "../../Include/RmlUi/Core/ConvolutionFilter.h"
+#include "../../Include/RmlUi/Core/FontEffect.h"
 #include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 
 namespace Rml {
 namespace Core {
 
 /**
-	A concrete font effect instancer for the shadow effect.
+	A concrete font effect for rendering Gaussian blurred text.
+ */
+
+class FontEffectBlur : public FontEffect
+{
+public:
+	FontEffectBlur();
+	virtual ~FontEffectBlur();
+
+	bool Initialise(int width);
+
+	bool HasUniqueTexture() const override;
+
+	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
+
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
-	@author Peter Curry
+private:
+	int width;
+	ConvolutionFilter filter_x, filter_y;
+};
+
+
+
+/**
+	A concrete font effect instancer for the blur effect.
  */
 
-class FontEffectShadowInstancer : public FontEffectInstancer
+class FontEffectBlurInstancer : public FontEffectInstancer
 {
 public:
-	FontEffectShadowInstancer();
-	virtual ~FontEffectShadowInstancer();
+	FontEffectBlurInstancer();
+	virtual ~FontEffectBlurInstancer();
 
 	SharedPtr<FontEffect> InstanceFontEffect(const String& name, const PropertyDictionary& properties) override;
 
 private:
-	PropertyId id_offset_x, id_offset_y, id_color;
+	PropertyId id_width, id_color;
 };
 
+
 }
 }
 

+ 188 - 0
Source/Core/FontEffectGlow.cpp

@@ -0,0 +1,188 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * 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 "precompiled.h"
+#include "FontEffectGlow.h"
+#include "Memory.h"
+
+namespace Rml {
+namespace Core {
+
+FontEffectGlow::FontEffectGlow()
+{
+	width_blur = 0;
+	width_outline = 0;
+	combined_width = 0;
+	SetLayer(Layer::Back);
+}
+
+FontEffectGlow::~FontEffectGlow()
+{
+}
+
+bool FontEffectGlow::HasUniqueTexture() const
+{
+	return true;
+}
+
+bool FontEffectGlow::Initialise(int _width_outline, int _width_blur, Vector2i _offset)
+{
+	if (_width_outline < 0 || _width_blur < 0)
+		return false;
+
+	width_outline = _width_outline;
+	width_blur = _width_blur;
+	combined_width = width_blur + width_outline;
+	offset = _offset;
+
+	// Outline filter.
+	filter_outline.Initialise(width_outline, FilterOperation::Dilation);
+	for (int x = -width_outline; x <= width_outline; ++x)
+	{
+		for (int y = -width_outline; y <= width_outline; ++y)
+		{
+			float weight = 1;
+
+			float distance = Math::SquareRoot(float(x * x + y * y));
+			if (distance > width_outline)
+			{
+				weight = (width_outline + 1) - distance;
+				weight = Math::Max(weight, 0.0f);
+			}
+
+			filter_outline[x + width_outline][y + width_outline] = weight;
+		}
+	}
+
+
+	// Gaussian blur filter
+	const float std_dev = (width_blur == 0 ? 1.f : .4f * float(width_blur));
+	const float two_variance = 2.f * std_dev * std_dev;
+	const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance);
+
+	float sum_weight = 0.f;
+
+	// We separate the blur filter into two passes, horizontal and vertical, for performance reasons.
+	filter_blur_x.Initialise(Vector2i(width_blur, 0), FilterOperation::Sum);
+	filter_blur_y.Initialise(Vector2i(0, width_blur), FilterOperation::Sum);
+
+	for (int x = -width_blur; x <= width_blur; ++x)
+	{
+		float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance));
+
+		filter_blur_x[0][x + width_blur] = weight;
+		filter_blur_y[x + width_blur][0] = weight;
+		sum_weight += weight;
+	}
+
+	// Normalize the kernels
+	for (int x = -width_blur; x <= width_blur; ++x)
+	{
+		filter_blur_x[0][x + width_blur] /= sum_weight;
+		filter_blur_y[x + width_blur][0] /= sum_weight;
+	}
+
+	return true;
+}
+
+bool FontEffectGlow::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
+{
+	RMLUI_UNUSED(glyph);
+
+	if (dimensions.x * dimensions.y > 0)
+	{
+		origin.x += offset.x - combined_width;
+		origin.y += offset.y - combined_width;
+
+		dimensions.x += 2 * combined_width;
+		dimensions.y += 2 * combined_width;
+
+		return true;
+	}
+
+	return false;
+}
+
+void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+{
+	const Vector2i buf_dimensions = destination_dimensions;
+	const int buf_stride = buf_dimensions.x;
+	const int buf_size = buf_dimensions.x * buf_dimensions.y;
+
+	DynamicArray<byte, GlobalStackAllocator<byte>> outline_output(buf_size);
+	DynamicArray<byte, GlobalStackAllocator<byte>> blur_x_output(buf_size);
+
+	filter_outline.Run(outline_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(combined_width));
+	
+	filter_blur_x.Run(blur_x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, outline_output.data(), buf_dimensions, Vector2i(0));
+
+	filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions, Vector2i(0));
+}
+
+
+
+FontEffectGlowInstancer::FontEffectGlowInstancer() : id_width_outline(PropertyId::Invalid), id_width_blur(PropertyId::Invalid),id_color(PropertyId::Invalid)
+{
+	id_width_outline = RegisterProperty("width-outline", "1px", true).AddParser("length").GetId();
+	id_width_blur = RegisterProperty("width-blur", "-1px", true).AddParser("length").GetId();
+	id_offset_x = RegisterProperty("offset-x", "0px", true).AddParser("length").GetId();
+	id_offset_y = RegisterProperty("offset-y", "0px", true).AddParser("length").GetId();
+	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
+	RegisterShorthand("font-effect", "width-outline, width-blur, offset-x, offset-y, color", ShorthandType::FallThrough);
+}
+
+FontEffectGlowInstancer::~FontEffectGlowInstancer()
+{
+}
+
+SharedPtr<FontEffect> FontEffectGlowInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
+{
+	RMLUI_UNUSED(name);
+
+	Vector2i offset;
+	int width_outline = properties.GetProperty(id_width_outline)->Get< int >();
+	int width_blur = properties.GetProperty(id_width_blur)->Get< int >();
+	offset.x = properties.GetProperty(id_offset_x)->Get< int >();
+	offset.y = properties.GetProperty(id_offset_y)->Get< int >();
+	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
+
+	if (width_blur < 0)
+		width_blur = width_outline;
+
+	auto font_effect = std::make_shared<FontEffectGlow>();
+	if (font_effect->Initialise(width_outline, width_blur, offset))
+	{
+		font_effect->SetColour(color);
+		return font_effect;
+	}
+
+	return nullptr;
+}
+
+}
+}

+ 48 - 29
Source/Core/FontEffectShadowInstancer.cpp → Source/Core/FontEffectGlow.h

@@ -26,44 +26,63 @@
  *
  */
 
-#include "precompiled.h"
-#include "FontEffectShadowInstancer.h"
-#include "FontEffectShadow.h"
+#ifndef RMLUICOREFONTEFFECTGLOW_H
+#define RMLUICOREFONTEFFECTGLOW_H
+
+#include "../../Include/RmlUi/Core/ConvolutionFilter.h"
+#include "../../Include/RmlUi/Core/FontEffect.h"
+#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 
 namespace Rml {
 namespace Core {
 
-FontEffectShadowInstancer::FontEffectShadowInstancer() : id_offset_x(PropertyId::Invalid), id_offset_y(PropertyId::Invalid), id_color(PropertyId::Invalid)
-{
-	id_offset_x = RegisterProperty("offset-x", "0px", true).AddParser("length").GetId();
-	id_offset_y = RegisterProperty("offset-y", "0px", true).AddParser("length").GetId();
-	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
-	RegisterShorthand("offset", "offset-x, offset-y", ShorthandType::FallThrough);
-	RegisterShorthand("font-effect", "offset-x, offset-y, color", ShorthandType::FallThrough);
-}
+/**
+	A font effect for rendering glow around text.
 
-FontEffectShadowInstancer::~FontEffectShadowInstancer()
-{
-}
+	Glow consists of an outline pass followed by a Gaussian blur pass.
 
-SharedPtr<FontEffect> FontEffectShadowInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
+ */
+
+class FontEffectGlow : public FontEffect
 {
-	RMLUI_UNUSED(name);
+public:
+	FontEffectGlow();
+	virtual ~FontEffectGlow();
+
+	bool Initialise(int width_outline, int width_blur, Vector2i offset);
+
+	bool HasUniqueTexture() const override;
+
+	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
+
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
+private:
+	int width_outline, width_blur, combined_width;
 	Vector2i offset;
-	offset.x = Math::RealToInteger(properties.GetProperty(id_offset_x)->Get< float >());
-	offset.y = Math::RealToInteger(properties.GetProperty(id_offset_y)->Get< float >());
-	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
-
-	auto font_effect = std::make_shared<FontEffectShadow>();
-	if (font_effect->Initialise(offset))
-	{
-		font_effect->SetColour(color);
-		return font_effect;
-	}
-
-	return nullptr;
-}
+	ConvolutionFilter filter_outline, filter_blur_x, filter_blur_y;
+};
+
+
+
+/**
+	A concrete font effect instancer for the glow effect.
+ */
+
+class FontEffectGlowInstancer : public FontEffectInstancer
+{
+public:
+	FontEffectGlowInstancer();
+	virtual ~FontEffectGlowInstancer();
+
+	SharedPtr<FontEffect> InstanceFontEffect(const String& name, const PropertyDictionary& properties) override;
+
+private:
+	PropertyId id_width_outline, id_width_blur, id_offset_x, id_offset_y, id_color;
+};
+
 
 }
 }
+
+#endif

+ 35 - 9
Source/Core/FontEffectOutline.cpp

@@ -42,13 +42,11 @@ FontEffectOutline::~FontEffectOutline()
 {
 }
 
-// Returns true.
 bool FontEffectOutline::HasUniqueTexture() const
 {
 	return true;
 }
 
-// Initialise the outline effect.
 bool FontEffectOutline::Initialise(int _width)
 {
 	if (_width <= 0)
@@ -56,7 +54,7 @@ bool FontEffectOutline::Initialise(int _width)
 
 	width = _width;
 
-	filter.Initialise(width, ConvolutionFilter::DILATION);
+	filter.Initialise(width, FilterOperation::Dilation);
 	for (int x = -width; x <= width; ++x)
 	{
 		for (int y = -width; y <= width; ++y)
@@ -77,7 +75,6 @@ bool FontEffectOutline::Initialise(int _width)
 	return true;
 }
 
-// Resizes and repositions the glyph to fit the outline.
 bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
 {
 	RMLUI_UNUSED(glyph);
@@ -87,8 +84,8 @@ bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions,
 		origin.x -= width;
 		origin.y -= width;
 
-		dimensions.x += width;
-		dimensions.y += width;
+		dimensions.x += 2 * width;
+		dimensions.y += 2 * width;
 
 		return true;
 	}
@@ -96,10 +93,39 @@ bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions,
 	return false;
 }
 
-// Expands the original glyph texture for the outline.
-void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	filter.Run(destination_data, destination_dimensions, destination_stride, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width, width));
+	filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
+}
+
+
+
+FontEffectOutlineInstancer::FontEffectOutlineInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid)
+{
+	id_width = RegisterProperty("width", "1px", true).AddParser("length").GetId();
+	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
+	RegisterShorthand("font-effect", "width, color", ShorthandType::FallThrough);
+}
+
+FontEffectOutlineInstancer::~FontEffectOutlineInstancer()
+{
+}
+
+SharedPtr<FontEffect> FontEffectOutlineInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
+{
+	RMLUI_UNUSED(name);
+
+	float width = properties.GetProperty(id_width)->Get< float >();
+	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
+
+	auto font_effect = std::make_shared<FontEffectOutline>();
+	if (font_effect->Initialise(Math::RealToInteger(width)))
+	{
+		font_effect->SetColour(color);
+		return font_effect;
+	}
+
+	return nullptr;
 }
 
 }

+ 23 - 18
Source/Core/FontEffectOutline.h

@@ -31,12 +31,11 @@
 
 #include "../../Include/RmlUi/Core/ConvolutionFilter.h"
 #include "../../Include/RmlUi/Core/FontEffect.h"
+#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 
 namespace Rml {
 namespace Core {
 
-class ConvolutionFilter;
-
 /**
 	A concrete font effect for rendering outlines around text.
 
@@ -49,34 +48,40 @@ public:
 	FontEffectOutline();
 	virtual ~FontEffectOutline();
 
-	/// Initialise the outline effect.
-	/// @param[in] width The width of the effect. This must be greater than zero.
-	/// @return True if the effect initialised successfully, false if not.
 	bool Initialise(int width);
 
-	/// Returns true.
-	/// @return True.
 	bool HasUniqueTexture() const override;
 
-	/// Resizes and repositions the glyph to fit the outline.
-	/// @param[out] origin The desired origin of the effect's glyph bitmap, as a pixel offset from its original origin. This defaults to (0, 0).
-	/// @param[out] dimensions The desired dimensions of the effect's glyph bitmap, in pixels. This defaults to the dimensions of the glyph's original bitmap.
-	/// @param[in] glyph The glyph the effect is being asked to size.
-	/// @return False if the effect is not providing support for the glyph, true otherwise.
 	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
 
-	/// Expands the original glyph texture for the outline.
-	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that they glyph shares its texture with other glyphs.
-	/// @param[in] destination_dimensions The dimensions of the glyph's area on its texture.
-	/// @param[in] destination_stride The stride of the glyph's texture.
-	/// @param[in] glyph The glyph the effect is being asked to generate an effect texture for.
-	void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
 private:
 	int width;
 	ConvolutionFilter filter;
 };
 
+
+
+/**
+	A concrete font effect instancer for the outline effect.
+
+	@author Peter Curry
+ */
+
+class FontEffectOutlineInstancer : public FontEffectInstancer
+{
+public:
+	FontEffectOutlineInstancer();
+	virtual ~FontEffectOutlineInstancer();
+
+	SharedPtr<FontEffect> InstanceFontEffect(const String& name, const PropertyDictionary& properties) override;
+
+private:
+	PropertyId id_width, id_color;
+};
+
+
 }
 }
 

+ 0 - 66
Source/Core/FontEffectOutlineInstancer.cpp

@@ -1,66 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * 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 "precompiled.h"
-#include "FontEffectOutlineInstancer.h"
-#include "FontEffectOutline.h"
-
-namespace Rml {
-namespace Core {
-
-FontEffectOutlineInstancer::FontEffectOutlineInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid)
-{
-	id_width = RegisterProperty("width", "1px", true).AddParser("length").GetId();
-	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
-	RegisterShorthand("font-effect", "width, color", ShorthandType::FallThrough);
-}
-
-FontEffectOutlineInstancer::~FontEffectOutlineInstancer()
-{
-}
-
-// Instances an outline font effect.
-SharedPtr<FontEffect> FontEffectOutlineInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
-{
-	RMLUI_UNUSED(name);
-
-	float width = properties.GetProperty(id_width)->Get< float >();
-	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
-
-	auto font_effect = std::make_shared<FontEffectOutline>();
-	if (font_effect->Initialise(Math::RealToInteger(width)))
-	{
-		font_effect->SetColour(color);
-		return font_effect;
-	}
-
-	return nullptr;
-}
-
-}
-}

+ 34 - 3
Source/Core/FontEffectShadow.cpp

@@ -41,20 +41,17 @@ FontEffectShadow::~FontEffectShadow()
 {
 }
 
-// Initialise the shadow effect.
 bool FontEffectShadow::Initialise(const Vector2i& _offset)
 {
 	offset = _offset;
 	return true;
 }
 
-// Returns true.
 bool FontEffectShadow::HasUniqueTexture() const
 {
 	return false;
 }
 
-// Resizes and repositions the glyph to fit the outline.
 bool FontEffectShadow::GetGlyphMetrics(Vector2i& origin, Vector2i& RMLUI_UNUSED_PARAMETER(dimensions), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
 {
 	RMLUI_UNUSED(dimensions);
@@ -64,5 +61,39 @@ bool FontEffectShadow::GetGlyphMetrics(Vector2i& origin, Vector2i& RMLUI_UNUSED_
 	return true;
 }
 
+
+
+FontEffectShadowInstancer::FontEffectShadowInstancer() : id_offset_x(PropertyId::Invalid), id_offset_y(PropertyId::Invalid), id_color(PropertyId::Invalid)
+{
+	id_offset_x = RegisterProperty("offset-x", "0px", true).AddParser("length").GetId();
+	id_offset_y = RegisterProperty("offset-y", "0px", true).AddParser("length").GetId();
+	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
+	RegisterShorthand("offset", "offset-x, offset-y", ShorthandType::FallThrough);
+	RegisterShorthand("font-effect", "offset-x, offset-y, color", ShorthandType::FallThrough);
+}
+
+FontEffectShadowInstancer::~FontEffectShadowInstancer()
+{
+}
+
+SharedPtr<FontEffect> FontEffectShadowInstancer::InstanceFontEffect(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties)
+{
+	RMLUI_UNUSED(name);
+
+	Vector2i offset;
+	offset.x = Math::RealToInteger(properties.GetProperty(id_offset_x)->Get< float >());
+	offset.y = Math::RealToInteger(properties.GetProperty(id_offset_y)->Get< float >());
+	Colourb color = properties.GetProperty(id_color)->Get< Colourb >();
+
+	auto font_effect = std::make_shared<FontEffectShadow>();
+	if (font_effect->Initialise(offset))
+	{
+		font_effect->SetColour(color);
+		return font_effect;
+	}
+
+	return nullptr;
+}
+
 }
 }

+ 22 - 10
Source/Core/FontEffectShadow.h

@@ -30,6 +30,7 @@
 #define RMLUICOREFONTEFFECTSHADOW_H
 
 #include "../../Include/RmlUi/Core/FontEffect.h"
+#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 
 namespace Rml {
 namespace Core {
@@ -46,26 +47,37 @@ public:
 	FontEffectShadow();
 	virtual ~FontEffectShadow();
 
-	/// Initialise the shadow effect.
-	/// @param[in] offset The offset, in pixels, of the shadow from the original text.
-	/// @return True if the effect initialised successfully, false if not.
 	bool Initialise(const Vector2i& offset);
 
-	/// Returns false.
-	/// @return False.
 	bool HasUniqueTexture() const override;
 
-	/// Repositions the glyph by the offset.
-	/// @param[out] origin The desired origin of the effect's glyph bitmap, as a pixel offset from its original origin. This defaults to (0, 0).
-	/// @param[out] dimensions The desired dimensions of the effect's glyph bitmap, in pixels. This defaults to the dimensions of the glyph's original bitmap.
-	/// @param[in] glyph The glyph the effect is being asked to size.
-	/// @return False if the effect is not providing support for the glyph, true otherwise.
 	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
 
 private:
 	Vector2i offset;
 };
 
+
+
+/**
+	A concrete font effect instancer for the shadow effect.
+
+	@author Peter Curry
+ */
+
+class FontEffectShadowInstancer : public FontEffectInstancer
+{
+public:
+	FontEffectShadowInstancer();
+	virtual ~FontEffectShadowInstancer();
+
+	SharedPtr<FontEffect> InstanceFontEffect(const String& name, const PropertyDictionary& properties) override;
+
+private:
+	PropertyId id_offset_x, id_offset_y, id_color;
+};
+
+
 }
 }
 

+ 14 - 5
Source/Core/FontEngineDefault/FontFaceLayer.cpp

@@ -77,9 +77,15 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace
 				Character character = pair.first;
 				const FontGlyph& glyph = pair.second;
 
-				RMLUI_ASSERT(character_boxes.find(character) != character_boxes.end());
+				auto it = character_boxes.find(character);
+				if (it == character_boxes.end())
+				{
+					// This can happen if the layers have been dirtied in FontHandleDefault. We will
+					// probably be regenerated soon, just skip the character for now.
+					continue;
+				}
 
-				TextureBox& box = character_boxes[character];
+				TextureBox& box = it->second;
 
 				Vector2i glyph_origin(Math::RealToInteger(box.origin.x), Math::RealToInteger(box.origin.y));
 				Vector2i glyph_dimensions(Math::RealToInteger(box.dimensions.x), Math::RealToInteger(box.dimensions.y));
@@ -114,12 +120,15 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace
 			}
 
 			TextureBox box;
-			box.origin = Vector2f((float)(glyph_origin.x + glyph.bearing.x), (float)(glyph_origin.y - glyph.bearing.y));
-			box.dimensions = Vector2f((float)glyph_dimensions.x - glyph_origin.x, (float)glyph_dimensions.y - glyph_origin.y);
+			box.origin = Vector2f(float(glyph_origin.x + glyph.bearing.x), float(glyph_origin.y - glyph.bearing.y));
+			box.dimensions = Vector2f(float(glyph_dimensions.x), float(glyph_dimensions.y));
+			
+			RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0);
+
 			character_boxes[character] = box;
 
 			// Add the character's dimensions into the texture layout engine.
-			texture_layout.AddRectangle((int)character, glyph_dimensions - glyph_origin);
+			texture_layout.AddRectangle((int)character, glyph_dimensions);
 		}
 
 		constexpr int max_texture_dimensions = 1024;

+ 8 - 20
Source/Core/FontEffectOutlineInstancer.h → Source/Core/Memory.cpp

@@ -26,33 +26,21 @@
  *
  */
 
-#ifndef RMLUICOREFONTEFFECTOUTLINEINSTANCER_H
-#define RMLUICOREFONTEFFECTOUTLINEINSTANCER_H
-
-#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
+#include "precompiled.h"
+#include "Memory.h"
 
 namespace Rml {
 namespace Core {
 
-/**
-	A concrete font effect instancer for the outline effect.
-
-	@author Peter Curry
- */
+namespace Detail {
 
-class FontEffectOutlineInstancer : public FontEffectInstancer
+BasicStackAllocator& GetGlobalBasicStackAllocator()
 {
-public:
-	FontEffectOutlineInstancer();
-	virtual ~FontEffectOutlineInstancer();
-
-	SharedPtr<FontEffect> InstanceFontEffect(const String& name, const PropertyDictionary& properties) override;
+	static BasicStackAllocator stack_allocator(10 * 1024);
+	return stack_allocator;
+}
 
-private:
-	PropertyId id_width, id_color;
-};
+}
 
 }
 }
-
-#endif

+ 169 - 0
Source/Core/Memory.h

@@ -0,0 +1,169 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * 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.
+ *
+ */
+
+#ifndef RMLUICOREMEMORY_H
+#define RMLUICOREMEMORY_H
+
+
+#include "../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+namespace Core {
+
+
+namespace Detail {
+
+	/**
+		Basic stack allocator.
+
+		A very cheap allocator which only moves a pointer up and down during allocation and deallocation, respectively.
+		The allocator is initialized with some fixed memory. If it runs out, it falls back to malloc.
+		
+		Warning: Using this is quite dangerous as deallocation must happen in exact reverse order of allocation.
+		
+		Do not use this class directly.
+	*/
+	class BasicStackAllocator
+	{
+	public:
+		BasicStackAllocator(size_t N) : N(N), data((byte*)malloc(N)), p(data)
+		{}
+
+		~BasicStackAllocator() noexcept {
+			RMLUI_ASSERT(p == data);
+			free(data);
+		}
+
+		void* allocate(size_t alignment, size_t byte_size)
+		{
+			size_t available_space = N - ((byte*)p - data);
+
+			if (std::align(alignment, byte_size, p, available_space))
+			{
+				void* result = p;
+				p = (byte*)p + byte_size;
+				return result;
+			}
+
+			// Fall back to malloc
+			return malloc(byte_size);
+		}
+
+		void deallocate(void* obj) noexcept
+		{
+			if (obj < data || obj >= data + N)
+			{
+				free(obj);
+				return;
+			}
+			p = obj;
+		}
+
+	private:
+		const size_t N;
+		byte* data;
+		void* p;
+	};
+
+
+	BasicStackAllocator& GetGlobalBasicStackAllocator();
+
+} /* namespace Detail */
+
+
+
+/**
+	Global stack allocator.
+
+	Can very cheaply allocate memory using the global stack allocator. Memory will be allocated from the
+	heap on the very first construction of a global stack allocator, and will persist and be re-used after.
+	Falls back to malloc if there is not enough space left.
+
+	Warning: Using this is quite dangerous as deallocation must happen in exact reverse order of allocation.
+	  Memory is shared between different global stack allocators. Should only be used for highly localized code,
+	  where memory is allocated and then quickly thrown away.
+*/
+
+template <typename T>
+class GlobalStackAllocator
+{
+public:
+	using value_type = T;
+
+	GlobalStackAllocator() = default;
+	template <class U>constexpr GlobalStackAllocator(const GlobalStackAllocator<U>&) noexcept {}
+
+	T* allocate(size_t num_objects) {
+		return reinterpret_cast<T*>(Detail::GetGlobalBasicStackAllocator().allocate(alignof(T), num_objects * sizeof(T)));
+	}
+
+	void deallocate(T* ptr, size_t) noexcept { 
+		Detail::GetGlobalBasicStackAllocator().deallocate(ptr);
+	}
+};
+
+template <class T, class U>
+bool operator==(const GlobalStackAllocator<T>&, const GlobalStackAllocator<U>&) { return true; }
+template <class T, class U>
+bool operator!=(const GlobalStackAllocator<T>&, const GlobalStackAllocator<U>&) { return false; }
+
+
+
+/**
+	A poor man's dynamic array.
+
+	Constructs N objects on initialization which are default initialized. Can not be resized.
+*/
+
+template <typename T, typename Alloc>
+class DynamicArray : Alloc, NonCopyMoveable {
+public:
+	DynamicArray(size_t N) : N(N) {
+		p = Alloc::allocate(N);
+		for (size_t i = 0; i < N; i++)
+			new (p + i) T;
+	}
+	~DynamicArray() noexcept {
+		for (size_t i = 0; i < N; i++)
+			p[i].~T();
+		Alloc::deallocate(p, N);
+	}
+
+	T* data() noexcept { return p; }
+
+	T& operator[](size_t i) noexcept { return p[i]; }
+
+private:
+	size_t N;
+	T* p;
+};
+
+}
+}
+
+#endif

+ 59 - 0
changelog.md

@@ -4,6 +4,59 @@
 
 ## RmlUi WIP
 
+
+### New font effects
+
+Two new font effects have been added.
+
+**Glow effect**
+
+Renders a blurred outline around the text. 
+
+The `glow` effect is declared as:
+```css
+font-effect: glow( <width-outline> <width-blur> <offset-x> <offset-y> <color> );
+```
+Both the outline pass and the subsequent blur pass can be controlled independently. Additionally, an offset can be applied which makes the effect suitable for generating drop shadows as well.
+
+**Blur effect**
+
+Renders a Gaussian blurred copy of the text.
+
+The `blur` effect is declared as:
+```css
+font-effect: blur( <width> <color> );
+```
+Note that, the blur effect will not replace the original text. To only show the blurred version of the text, set the `color` property of the original text to `transparent`.
+
+**Example usage**
+
+```css
+/* Declares a glow effect. */
+h1
+{
+	font-effect: glow( 3px #ee9 );
+}
+
+/* The glow effect can also create nice looking shadows. */
+p.glow_shadow
+{
+	color: #ed5;
+	font-effect: glow(2px 4px 2px 3px #644);
+}
+
+/* Renders a blurred version of the text, hides the original text. */
+h1
+{
+	color: transparent;
+	font-effect: blur(3px #ed5);
+}
+```
+
+See the `demo` sample for additional usage examples and results.
+
+
+
 ### CMake options
 
 New CMake option added.
@@ -12,6 +65,12 @@ New CMake option added.
 
 
 
+### Breaking changes
+
+- For users that implement custom font effects, there are some minor changes to the font effect interface and the convolution filter.
+
+
+
 ## RmlUi 3.0