瀏覽代碼

Use premultiplied alpha for vertex colors

Introduce a new type for colors with premultiplied alpha.
Michael Ragazzon 2 年之前
父節點
當前提交
c802aeb21b
共有 64 個文件被更改,包括 258 次插入233 次删除
  1. 7 14
      Backends/RmlUi_Renderer_GL3.cpp
  2. 29 1
      Include/RmlUi/Core/Colour.h
  3. 24 22
      Include/RmlUi/Core/Colour.inl
  4. 2 2
      Include/RmlUi/Core/DecorationTypes.h
  5. 1 1
      Include/RmlUi/Core/ElementText.h
  6. 6 2
      Include/RmlUi/Core/FontEffect.h
  7. 2 2
      Include/RmlUi/Core/FontEngineInterface.h
  8. 7 7
      Include/RmlUi/Core/GeometryUtilities.h
  9. 4 3
      Include/RmlUi/Core/Math.h
  10. 3 2
      Include/RmlUi/Core/Types.h
  11. 2 2
      Include/RmlUi/Core/Vertex.h
  12. 1 1
      Samples/basic/bitmapfont/src/FontEngineBitmap.cpp
  13. 1 1
      Samples/basic/bitmapfont/src/FontEngineBitmap.h
  14. 1 1
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp
  15. 2 2
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h
  16. 1 1
      Samples/invaders/src/DecoratorDefender.cpp
  17. 1 1
      Samples/invaders/src/DecoratorStarfield.cpp
  18. 1 1
      Samples/invaders/src/Defender.cpp
  19. 2 2
      Samples/invaders/src/Invader.cpp
  20. 1 1
      Samples/invaders/src/Shield.cpp
  21. 1 1
      Samples/invaders/src/Sprite.cpp
  22. 2 2
      Samples/invaders/src/Sprite.h
  23. 1 1
      Samples/luainvaders/src/DecoratorDefender.cpp
  24. 1 1
      Samples/luainvaders/src/DecoratorStarfield.cpp
  25. 1 1
      Samples/luainvaders/src/Defender.cpp
  26. 2 2
      Samples/luainvaders/src/Invader.cpp
  27. 1 1
      Samples/luainvaders/src/Shield.cpp
  28. 1 1
      Samples/luainvaders/src/Sprite.cpp
  29. 2 2
      Samples/luainvaders/src/Sprite.h
  30. 1 3
      Source/Core/ConvolutionFilter.cpp
  31. 6 9
      Source/Core/DecoratorGradient.cpp
  32. 1 4
      Source/Core/DecoratorNinePatch.cpp
  33. 1 1
      Source/Core/DecoratorShader.cpp
  34. 1 5
      Source/Core/DecoratorTiled.cpp
  35. 21 22
      Source/Core/ElementBackgroundBorder.cpp
  36. 1 2
      Source/Core/ElementText.cpp
  37. 1 5
      Source/Core/Elements/ElementImage.cpp
  38. 2 7
      Source/Core/Elements/ElementProgress.cpp
  39. 9 7
      Source/Core/Elements/WidgetTextInput.cpp
  40. 1 1
      Source/Core/Elements/WidgetTextInput.h
  41. 13 0
      Source/Core/FontEffect.cpp
  42. 2 0
      Source/Core/FontEffectBlur.cpp
  43. 2 0
      Source/Core/FontEffectGlow.cpp
  44. 2 0
      Source/Core/FontEffectOutline.cpp
  45. 1 1
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp
  46. 1 1
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h
  47. 6 11
      Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp
  48. 2 2
      Source/Core/FontEngineDefault/FontFaceHandleDefault.h
  49. 2 2
      Source/Core/FontEngineDefault/FontFaceLayer.cpp
  50. 3 3
      Source/Core/FontEngineDefault/FontFaceLayer.h
  51. 1 1
      Source/Core/FontEngineInterface.cpp
  52. 14 12
      Source/Core/GeometryBackgroundBorder.cpp
  53. 12 10
      Source/Core/GeometryBackgroundBorder.h
  54. 5 3
      Source/Core/GeometryBoxShadow.cpp
  55. 9 8
      Source/Core/GeometryUtilities.cpp
  56. 2 2
      Source/Core/Math.cpp
  57. 1 1
      Source/Core/PropertyParserBoxShadow.cpp
  58. 1 1
      Source/Core/PropertyParserColorStopList.cpp
  59. 7 5
      Source/Debugger/Geometry.cpp
  60. 1 4
      Source/Lottie/ElementLottie.cpp
  61. 1 4
      Source/SVG/ElementSVG.cpp
  62. 4 4
      Tests/Source/UnitTests/Math.cpp
  63. 10 10
      Tests/Source/UnitTests/Properties.cpp
  64. 1 1
      Tests/Source/VisualTests/CaptureScreen.cpp

+ 7 - 14
Backends/RmlUi_Renderer_GL3.cpp

@@ -82,11 +82,6 @@ void main() {
 	fragTexCoord = inTexCoord0;
 	fragColor = inColor0;
 
-#if 1 // TODO: Make all vertex colors already premultiplied, and remove this step.
-	// Pre-multiply vertex colors with their alpha.
-	fragColor.rgb = fragColor.rgb * fragColor.a;
-#endif
-
 	vec2 translatedPos = inPosition + _translate;
 	vec4 outPos = _transform * vec4(translatedPos, 0.0, 1.0);
 
@@ -1311,13 +1306,11 @@ void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vecto
 	RenderGeometry(vertices, 4, indices, 6, RenderInterface_GL3::TexturePostprocess, {});
 }
 
-static Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0)
+static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0)
 {
 	Rml::Colourf result;
-	result.alpha = (1.f / 255.f) * float(c0.alpha);
-	result.red = (1.f / 255.f) * float(c0.red) * result.alpha;
-	result.green = (1.f / 255.f) * float(c0.green) * result.alpha;
-	result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha;
+	for (int i = 0; i < 4; i++)
+		result[i] = (1.f / 255.f) * float(c0[i]);
 	return result;
 }
 
@@ -1489,7 +1482,7 @@ struct CompiledFilter {
 
 	// Drop shadow
 	Rml::Vector2f offset;
-	Rml::Colourb color;
+	Rml::ColourbPremultiplied color;
 
 	// ColorMatrix
 	Rml::Matrix4f color_matrix;
@@ -1513,7 +1506,7 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String&
 	{
 		filter.type = FilterType::DropShadow;
 		filter.sigma = Rml::Get(parameters, "sigma", 0.f);
-		filter.color = Rml::Get(parameters, "color", Rml::Colourb());
+		filter.color = Rml::Get(parameters, "color", Rml::Colourb()).ToPremultiplied();
 		filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f));
 	}
 	else if (name == "brightness")
@@ -1642,7 +1635,7 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String&
 			const Rml::ColorStop& stop = color_stop_list[i];
 			RMLUI_ASSERT(stop.position.unit == Rml::Unit::NUMBER);
 			shader.stop_positions[i] = stop.position.number;
-			shader.stop_colors[i] = ToPremultipliedAlpha(stop.color);
+			shader.stop_colors[i] = ConvertToColorf(stop.color);
 		}
 	};
 
@@ -1806,7 +1799,7 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand
 			UseProgram(ProgramId::DropShadow);
 			glDisable(GL_BLEND);
 
-			Rml::Colourf color = ToPremultipliedAlpha(filter.color);
+			Rml::Colourf color = ConvertToColorf(filter.color);
 			glUniform4fv(GetUniformLocation(UniformId::Color), 1, &color[0]);
 
 			const Gfx::FramebufferData& primary = render_layers.GetPostprocessPrimary();

+ 29 - 1
Include/RmlUi/Core/Colour.h

@@ -33,13 +33,15 @@
 
 namespace Rml {
 
+using byte = unsigned char;
+
 /**
     Templated class for a four-component RGBA colour.
 
     @author Peter Curry
  */
 
-template <typename ColourType, int AlphaDefault>
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
 class Colour {
 public:
 	/// Initialising constructor.
@@ -99,6 +101,32 @@ public:
 	/// @return A constant pointer to the first value.
 	inline operator ColourType*() { return &red; }
 
+	// Convert color to premultiplied alpha.
+	template <typename IsPremultiplied = std::integral_constant<bool, PremultipliedAlpha>,
+		typename = typename std::enable_if_t<!IsPremultiplied::value && std::is_same<ColourType, byte>::value>>
+	inline Colour<ColourType, AlphaDefault, true> ToPremultiplied() const
+	{
+		return Colour<ColourType, AlphaDefault, true>{
+			ColourType((red * alpha) / 255),
+			ColourType((green * alpha) / 255),
+			ColourType((blue * alpha) / 255),
+			alpha,
+		};
+	}
+	// Convert color to premultiplied alpha, after multiplying alpha by opacity.
+	template <typename IsPremultiplied = std::integral_constant<bool, PremultipliedAlpha>,
+		typename = typename std::enable_if_t<!IsPremultiplied::value && std::is_same<ColourType, byte>::value>>
+	inline Colour<ColourType, AlphaDefault, true> ToPremultiplied(float opacity) const
+	{
+		const float new_alpha = alpha * opacity;
+		return Colour<ColourType, AlphaDefault, true>{
+			ColourType(red * (new_alpha / 255.f)),
+			ColourType(green * (new_alpha / 255.f)),
+			ColourType(blue * (new_alpha / 255.f)),
+			ColourType(new_alpha),
+		};
+	}
+
 	ColourType red, green, blue, alpha;
 
 #if defined(RMLUI_COLOUR_USER_EXTRA)

+ 24 - 22
Include/RmlUi/Core/Colour.inl

@@ -28,41 +28,43 @@
 
 namespace Rml {
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault>::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha>::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha)
 {}
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault>::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) :
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha>::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) :
 	red(red), green(green), blue(blue), alpha(alpha)
 {}
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator+(const Colour<ColourType, AlphaDefault> rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator+(
+	const Colour<ColourType, AlphaDefault, PremultipliedAlpha> rhs) const
 {
-	return Colour<ColourType, AlphaDefault>(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha);
+	return Colour<ColourType, AlphaDefault, PremultipliedAlpha>(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha);
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator-(const Colour<ColourType, AlphaDefault> rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator-(
+	const Colour<ColourType, AlphaDefault, PremultipliedAlpha> rhs) const
 {
-	return Colour<ColourType, AlphaDefault>(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha);
+	return Colour<ColourType, AlphaDefault, PremultipliedAlpha>(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha);
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator*(float rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator*(float rhs) const
 {
 	return Colour((ColourType)(red * rhs), (ColourType)(green * rhs), (ColourType)(blue * rhs), (ColourType)(alpha * rhs));
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator/(float rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator/(float rhs) const
 {
 	return Colour((ColourType)(red / rhs), (ColourType)(green / rhs), (ColourType)(blue / rhs), (ColourType)(alpha / rhs));
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator+=(const Colour rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator+=(const Colour rhs)
 {
 	red += rhs.red;
 	green += rhs.green;
@@ -70,8 +72,8 @@ void Colour<ColourType, AlphaDefault>::operator+=(const Colour rhs)
 	alpha += rhs.alpha;
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator-=(const Colour rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator-=(const Colour rhs)
 {
 	red -= rhs.red;
 	green -= rhs.green;
@@ -79,8 +81,8 @@ void Colour<ColourType, AlphaDefault>::operator-=(const Colour rhs)
 	alpha -= rhs.alpha;
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator*=(float rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator*=(float rhs)
 {
 	red = (ColourType)(red * rhs);
 	green = (ColourType)(green * rhs);
@@ -88,8 +90,8 @@ void Colour<ColourType, AlphaDefault>::operator*=(float rhs)
 	alpha = (ColourType)(alpha * rhs);
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator/=(float rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator/=(float rhs)
 {
 	*this *= (1.0f / rhs);
 }

+ 2 - 2
Include/RmlUi/Core/DecorationTypes.h

@@ -35,7 +35,7 @@
 namespace Rml {
 
 struct ColorStop {
-	Colourb color;
+	ColourbPremultiplied color;
 	NumericValue position;
 };
 inline bool operator==(const ColorStop& a, const ColorStop& b)
@@ -48,7 +48,7 @@ inline bool operator!=(const ColorStop& a, const ColorStop& b)
 }
 
 struct BoxShadow {
-	Colourb color;
+	ColourbPremultiplied color;
 	NumericValue offset_x, offset_y;
 	NumericValue blur_radius;
 	NumericValue spread_distance;

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

@@ -114,7 +114,7 @@ private:
 	// The decoration geometry we've generated for this string.
 	UniquePtr<Geometry> decoration;
 
-	Colourb colour;
+	ColourbPremultiplied colour;
 	float opacity;
 
 	int font_handle_version;

+ 6 - 2
Include/RmlUi/Core/FontEffect.h

@@ -59,8 +59,8 @@ public:
 	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 the glyph shares its
-	/// texture with other glyphs.
+	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, destination texture, RGBA-ordered with pre-multiplied alpha. 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.
@@ -79,6 +79,10 @@ public:
 	size_t GetFingerprint() const;
 	void SetFingerprint(size_t fingerprint);
 
+protected:
+	// Helper function to copy the alpha value to the colour channels for each pixel, assuming RGBA-ordered bytes, resulting in a grayscale texture.
+	static void FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride);
+
 private:
 	Layer layer;
 

+ 2 - 2
Include/RmlUi/Core/FontEngineInterface.h

@@ -101,13 +101,13 @@ public:
 	/// @param[in] font_effects_handle The handle to the prepared font effects for which the geometry should be generated.
 	/// @param[in] string The string to render.
 	/// @param[in] position The position of the baseline of the first character to render.
-	/// @param[in] colour The colour to render the text. Colour alpha is premultiplied with opacity.
+	/// @param[in] colour The colour to render the text.
 	/// @param[in] opacity The opacity of the text, should be applied to font effects.
 	/// @param[in] letter_spacing The letter spacing size in pixels.
 	/// @param[out] geometry An array of geometries to generate the geometry into.
 	/// @return The width, in pixels, of the string geometry.
 	virtual int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position,
-		const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry);
+		ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry);
 
 	/// Called by RmlUi to determine if the text geometry is required to be re-generated. Whenever the returned version
 	/// is changed, all geometry belonging to the given face handle will be re-generated.

+ 7 - 7
Include/RmlUi/Core/GeometryUtilities.h

@@ -54,7 +54,7 @@ public:
 	/// @param[in] dimensions The dimensions of the quad to generate.
 	/// @param[in] colour The colour to be assigned to each of the quad's vertices.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset = 0);
+	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, int index_offset = 0);
 	/// Generates a quad from a position, size, colour and texture coordinates.
 	/// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into.
 	/// @param[out] indices An array of at least six indices that the generated index data will be written into.
@@ -64,15 +64,15 @@ public:
 	/// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad.
 	/// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord,
-		Vector2f bottom_right_texcoord, int index_offset = 0);
+	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour,
+		Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset = 0);
 
 	/// Generates the geometry required to render a line.
 	/// @param[out] geometry The geometry to append the newly created geometry into.
 	/// @param[in] position The top-left position the line.
 	/// @param[in] position The size of the line.
 	/// @param[in] color The color to draw the line in.
-	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color);
+	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color);
 
 	/// Generates the geometry for an element's background and border, with support for the border-radius property.
 	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
@@ -82,8 +82,8 @@ public:
 	/// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background.
 	/// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order.
 	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
-	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour,
-		const Colourb border_colours[4]);
+	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius,
+		ColourbPremultiplied background_colour, const ColourbPremultiplied border_colours[4]);
 
 	/// Generates the background geometry for an element's area, with support for border-radius.
 	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
@@ -93,7 +93,7 @@ public:
 	/// @param[in] colour The colour applied to the background.
 	/// @param[in] area Either the border, padding or content area to be filled.
 	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
-	static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb colour,
+	static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied colour,
 		BoxArea area = BoxArea::Padding);
 
 private:

+ 4 - 3
Include/RmlUi/Core/Math.h

@@ -35,9 +35,10 @@
 namespace Rml {
 
 using byte = unsigned char;
-template <typename ColourType, int AlphaDefault>
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
 class Colour;
-using Colourb = Colour<byte, 255>;
+using Colourb = Colour<byte, 255, false>;
+using ColourbPremultiplied = Colour<byte, 255, true>;
 template <typename Type>
 class Vector2;
 using Vector2f = Vector2<float>;
@@ -92,7 +93,7 @@ namespace Math {
 	RMLUICORE_API Vector2i Clamp<Vector2i>(Vector2i value, Vector2i min, Vector2i max);
 
 	/// Color interpolation.
-	RMLUICORE_API Colourb RoundedLerp(float t, Colourb c0, Colourb c1);
+	RMLUICORE_API ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied c0, ColourbPremultiplied c1);
 
 	/// Evaluates if a number is, or close to, zero.
 	/// @param[in] value The number to compare to zero.

+ 3 - 2
Include/RmlUi/Core/Types.h

@@ -59,8 +59,9 @@ namespace Rml {
 
 // Color and linear algebra
 enum class ColorFormat { RGBA8, A8 };
-using Colourf = Colour<float, 1>;
-using Colourb = Colour<byte, 255>;
+using Colourf = Colour<float, 1, false>;
+using Colourb = Colour<byte, 255, false>;
+using ColourbPremultiplied = Colour<byte, 255, true>;
 using Vector2i = Vector2<int>;
 using Vector2f = Vector2<float>;
 using Vector3i = Vector3<int>;

+ 2 - 2
Include/RmlUi/Core/Vertex.h

@@ -43,8 +43,8 @@ namespace Rml {
 struct RMLUICORE_API Vertex {
 	/// Two-dimensional position of the vertex (usually in pixels).
 	Vector2f position;
-	/// RGBA-ordered 8-bit / channel colour.
-	Colourb colour;
+	/// RGBA-ordered 8-bit/channel colour with premultiplied alpha.
+	ColourbPremultiplied colour;
 	/// Texture coordinate for any associated texture.
 	Vector2f tex_coord;
 };

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineBitmap.cpp

@@ -155,7 +155,7 @@ int FontFaceBitmap::GetStringWidth(const String& string, Character previous_char
 	return width;
 }
 
-int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, const Colourb& colour, GeometryList& geometry_list)
+int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, ColourbPremultiplied colour, GeometryList& geometry_list)
 {
 	int width = 0;
 

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineBitmap.h

@@ -65,7 +65,7 @@ public:
 	int GetStringWidth(const String& string, Character prior_character);
 
 	// Generate the string geometry, returning its width.
-	int GenerateString(const String& string, const Vector2f& position, const Colourb& colour, GeometryList& geometry);
+	int GenerateString(const String& string, const Vector2f& position, ColourbPremultiplied colour, GeometryList& geometry);
 
 	const FontMetrics& GetMetrics() const { return metrics; }
 

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp

@@ -81,7 +81,7 @@ int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, const Strin
 }
 
 int FontEngineInterfaceBitmap::GenerateString(FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, const String& string,
-	const Vector2f& position, const Colourb& colour, float /*opacity*/, float /*letter_spacing*/, GeometryList& geometry)
+	const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/, float /*letter_spacing*/, GeometryList& geometry)
 {
 	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
 	return handle_bitmap->GenerateString(string, position, colour, geometry);

+ 2 - 2
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h

@@ -38,7 +38,7 @@ using Rml::FontFaceHandle;
 
 using Rml::byte;
 using Rml::Character;
-using Rml::Colourb;
+using Rml::ColourbPremultiplied;
 using Rml::String;
 using Rml::Texture;
 using Rml::Vector2f;
@@ -77,7 +77,7 @@ public:
 
 	/// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text.
 	int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position,
-		const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry) override;
+		ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) override;
 
 	/// Called by RmlUi to determine if the text geometry is required to be re-generated.eometry.
 	int GetVersion(FontFaceHandle handle) override;

+ 1 - 1
Samples/invaders/src/DecoratorDefender.cpp

@@ -65,7 +65,7 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH
 	if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface())
 	{
 		Rml::TextureHandle texture = GetTexture(image_index)->GetHandle();
-		Rml::Colourb color = element->GetProperty<Rml::Colourb>("color");
+		Rml::ColourbPremultiplied color = element->GetProperty<Rml::Colourb>("color").ToPremultiplied();
 
 		Rml::Vertex vertices[4];
 		int indices[6];

+ 1 - 1
Samples/invaders/src/DecoratorStarfield.cpp

@@ -117,7 +117,7 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData
 
 	for (size_t i = 0; i < star_field->star_layers.size(); i++)
 	{
-		Rml::Colourb color = star_field->star_layers[i].colour;
+		Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied();
 
 		for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++)
 		{

+ 1 - 1
Samples/invaders/src/Defender.cpp

@@ -95,7 +95,7 @@ void Defender::Update(double t)
 
 void Defender::Render(float dp_ratio, Rml::TextureHandle texture)
 {
-	Rml::Colourb color = GameDetails::GetDefenderColour();
+	Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied();
 
 	// Render our sprite if rendering is enabled
 	if (render)

+ 2 - 2
Samples/invaders/src/Invader.cpp

@@ -173,10 +173,10 @@ void Invader::UpdateAnimation()
 
 void Invader::Render(float dp_ratio, Rml::TextureHandle texture)
 {
-	Rml::Colourb color(255);
+	Rml::ColourbPremultiplied color(255);
 
 	if (type == MOTHERSHIP)
-		color = MOTHERSHIP_COLOUR;
+		color = MOTHERSHIP_COLOUR.ToPremultiplied();
 
 	int sprite_index = GetSpriteIndex();
 	int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2);

+ 1 - 1
Samples/invaders/src/Shield.cpp

@@ -123,7 +123,7 @@ void Shield::Render(float dp_ratio)
 		const Rml::Vector2f scaled_position = (dp_ratio * position).Round();
 		const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio);
 
-		Rml::Colourb color = GameDetails::GetDefenderColour();
+		Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied();
 		ColoredPointList points;
 		points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS);
 

+ 1 - 1
Samples/invaders/src/Sprite.cpp

@@ -38,7 +38,7 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te
 
 Sprite::~Sprite() {}
 
-void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture)
+void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture)
 {
 	Rml::RenderInterface* render_interface = Rml::GetRenderInterface();
 	if (!render_interface)

+ 2 - 2
Samples/invaders/src/Sprite.h

@@ -40,7 +40,7 @@ public:
 	Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord);
 	~Sprite();
 
-	void Render(Rml::Vector2f position, float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture);
+	void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture);
 
 	Rml::Vector2f dimensions;
 	Rml::Vector2f top_left_texcoord;
@@ -48,7 +48,7 @@ public:
 };
 
 struct ColoredPoint {
-	Rml::Colourb color;
+	Rml::ColourbPremultiplied color;
 	Rml::Vector2f position;
 };
 using ColoredPointList = Rml::Vector<ColoredPoint>;

+ 1 - 1
Samples/luainvaders/src/DecoratorDefender.cpp

@@ -65,7 +65,7 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH
 	if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface())
 	{
 		Rml::TextureHandle texture = GetTexture(image_index)->GetHandle();
-		Rml::Colourb color = element->GetProperty<Rml::Colourb>("color");
+		Rml::ColourbPremultiplied color = element->GetProperty<Rml::Colourb>("color").ToPremultiplied();
 
 		Rml::Vertex vertices[4];
 		int indices[6];

+ 1 - 1
Samples/luainvaders/src/DecoratorStarfield.cpp

@@ -117,7 +117,7 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData
 
 	for (size_t i = 0; i < star_field->star_layers.size(); i++)
 	{
-		Rml::Colourb color = star_field->star_layers[i].colour;
+		Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied();
 
 		for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++)
 		{

+ 1 - 1
Samples/luainvaders/src/Defender.cpp

@@ -95,7 +95,7 @@ void Defender::Update(double t)
 
 void Defender::Render(float dp_ratio, Rml::TextureHandle texture)
 {
-	Rml::Colourb color = GameDetails::GetDefenderColour();
+	Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied();
 
 	// Render our sprite if rendering is enabled
 	if (render)

+ 2 - 2
Samples/luainvaders/src/Invader.cpp

@@ -173,10 +173,10 @@ void Invader::UpdateAnimation()
 
 void Invader::Render(float dp_ratio, Rml::TextureHandle texture)
 {
-	Rml::Colourb color(255);
+	Rml::ColourbPremultiplied color(255);
 
 	if (type == MOTHERSHIP)
-		color = MOTHERSHIP_COLOUR;
+		color = MOTHERSHIP_COLOUR.ToPremultiplied();
 
 	int sprite_index = GetSpriteIndex();
 	int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2);

+ 1 - 1
Samples/luainvaders/src/Shield.cpp

@@ -123,7 +123,7 @@ void Shield::Render(float dp_ratio)
 		const Rml::Vector2f scaled_position = (dp_ratio * position).Round();
 		const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio);
 
-		Rml::Colourb color = GameDetails::GetDefenderColour();
+		Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied();
 		ColoredPointList points;
 		points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS);
 

+ 1 - 1
Samples/luainvaders/src/Sprite.cpp

@@ -38,7 +38,7 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te
 
 Sprite::~Sprite() {}
 
-void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture)
+void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture)
 {
 	Rml::RenderInterface* render_interface = Rml::GetRenderInterface();
 	if (!render_interface)

+ 2 - 2
Samples/luainvaders/src/Sprite.h

@@ -40,7 +40,7 @@ public:
 	Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord);
 	~Sprite();
 
-	void Render(Rml::Vector2f position, float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture);
+	void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture);
 
 	Rml::Vector2f dimensions;
 	Rml::Vector2f top_left_texcoord;
@@ -48,7 +48,7 @@ public:
 };
 
 struct ColoredPoint {
-	Rml::Colourb color;
+	Rml::ColourbPremultiplied color;
 	Rml::Vector2f position;
 };
 using ColoredPointList = Rml::Vector<ColoredPoint>;

+ 1 - 3
Source/Core/ConvolutionFilter.cpp

@@ -109,11 +109,9 @@ void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimens
 
 			opacity = Math::Min(255.f, opacity);
 
-			const int destination_index = x * destination_bytes_per_pixel + destination_alpha_offset;
+			const int destination_index = y * destination_stride + x * destination_bytes_per_pixel + destination_alpha_offset;
 			destination[destination_index] = byte(opacity);
 		}
-
-		destination += destination_stride;
 	}
 }
 

+ 6 - 9
Source/Core/DecoratorGradient.cpp

@@ -195,13 +195,10 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem
 	const ComputedValues& computed = element->GetComputedValues();
 	const float opacity = computed.opacity();
 
-	GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb(), paint_area);
+	GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), ColourbPremultiplied(), paint_area);
 
-	// Apply opacity
-	Colourb colour_start = start;
-	colour_start.alpha = (byte)(opacity * (float)colour_start.alpha);
-	Colourb colour_stop = stop;
-	colour_stop.alpha = (byte)(opacity * (float)colour_stop.alpha);
+	ColourbPremultiplied colour_start = start.ToPremultiplied(opacity);
+	ColourbPremultiplied colour_stop = stop.ToPremultiplied(opacity);
 
 	const Vector2f offset = box.GetPosition(paint_area);
 	const Vector2f size = box.GetSize(paint_area);
@@ -324,7 +321,7 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const byte alpha = byte(computed.opacity() * 255.f);
-	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), paint_area);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area);
 
 	const Vector2f render_offset = box.GetPosition(paint_area);
 	for (Vertex& vertex : geometry.GetVertices())
@@ -492,7 +489,7 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const byte alpha = byte(computed.opacity() * 255.f);
-	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area);
 
 	const Vector2f render_offset = box.GetPosition(box_area);
 	for (Vertex& vertex : geometry.GetVertices())
@@ -688,7 +685,7 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const byte alpha = byte(computed.opacity() * 255.f);
-	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area);
 
 	const Vector2f render_offset = box.GetPosition(box_area);
 	for (Vertex& vertex : geometry.GetVertices())

+ 1 - 4
Source/Core/DecoratorNinePatch.cpp

@@ -67,10 +67,7 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo
 	const Vector2f surface_offset = element->GetBox().GetPosition(paint_area);
 	const Vector2f surface_dimensions = element->GetBox().GetSize(paint_area).Round();
 
-	const float opacity = computed.opacity();
-	Colourb quad_colour = computed.image_color();
-
-	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
+	const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 
 	/* In the following, we operate on the four diagonal vertices in the grid, as they define the whole grid. */
 

+ 1 - 1
Source/Core/DecoratorShader.cpp

@@ -67,7 +67,7 @@ DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxAr
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const byte alpha = byte(computed.opacity() * 255.f);
-	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), render_area);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area);
 
 	const Vector2f offset = box.GetPosition(render_area);
 	for (Vertex& vertex : geometry.GetVertices())

+ 1 - 5
Source/Core/DecoratorTiled.cpp

@@ -104,11 +104,7 @@ void DecoratorTiled::Tile::GenerateGeometry(Vector<Vertex>& vertices, Vector<int
 	if (surface_dimensions.x <= 0 || surface_dimensions.y <= 0)
 		return;
 
-	float opacity = computed.opacity();
-	Colourb quad_colour = computed.image_color();
-
-	// Apply opacity
-	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
+	const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 
 	if (!tile_data_calculated)
 		return;

+ 21 - 22
Source/Core/ElementBackgroundBorder.cpp

@@ -85,7 +85,7 @@ Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea cli
 	{
 		const Box& box = element->GetBox();
 		const Vector4f border_radius = element->GetComputedValues().border_radius();
-		GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, Colourb(255), clip_area);
+		GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, ColourbPremultiplied(255), clip_area);
 	}
 
 	return &geometry;
@@ -112,29 +112,28 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element)
 {
 	const ComputedValues& computed = element->GetComputedValues();
 
-	Colourb background_color = computed.background_color();
-	Colourb border_colors[4] = {
-		computed.border_top_color(),
-		computed.border_right_color(),
-		computed.border_bottom_color(),
-		computed.border_left_color(),
-	};
-	const Vector4f border_radius = computed.border_radius();
 	const bool has_box_shadow = computed.has_box_shadow();
+	const float opacity = computed.opacity();
 
-	if (!has_box_shadow)
-	{
-		// Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while
-		// opacity is applied to the entire box-shadow texture when that is rendered.
-		const float opacity = computed.opacity();
-		if (opacity < 1.f)
-		{
-			background_color.alpha = (byte)(opacity * (float)background_color.alpha);
-
-			for (int i = 0; i < 4; ++i)
-				border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha);
-		}
-	}
+	// Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while
+	// opacity is applied to the entire box-shadow texture when that is rendered.
+	bool apply_opacity = (!has_box_shadow && opacity < 1.f);
+
+	auto ConvertColor = [=](Colourb color) {
+		if (apply_opacity)
+			return color.ToPremultiplied(opacity);
+		else
+			return color.ToPremultiplied();
+	};
+
+	ColourbPremultiplied background_color = ConvertColor(computed.background_color());
+	ColourbPremultiplied border_colors[4] = {
+		ConvertColor(computed.border_top_color()),
+		ConvertColor(computed.border_right_color()),
+		ConvertColor(computed.border_bottom_color()),
+		ConvertColor(computed.border_left_color()),
+	};
+	const Vector4f border_radius = computed.border_radius();
 
 	Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry;
 	RMLUI_ASSERT(!geometry);

+ 1 - 2
Source/Core/ElementText.cpp

@@ -340,8 +340,7 @@ void ElementText::OnPropertyChange(const PropertyIdSet& changed_properties)
 		const float new_opacity = computed.opacity();
 		const bool opacity_changed = opacity != new_opacity;
 
-		Colourb new_colour = computed.color();
-		new_colour.alpha = byte(new_opacity * float(new_colour.alpha));
+		ColourbPremultiplied new_colour = computed.color().ToPremultiplied(new_opacity);
 		colour_changed = colour != new_colour;
 
 		if (colour_changed)

+ 1 - 5
Source/Core/Elements/ElementImage.cpp

@@ -192,11 +192,7 @@ void ElementImage::GenerateGeometry()
 	}
 
 	const ComputedValues& computed = GetComputedValues();
-
-	float opacity = computed.opacity();
-	Colourb quad_colour = computed.image_color();
-	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
-
+	ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 	Vector2f quad_size = GetBox().GetSize(BoxArea::Content).Round();
 
 	GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]);

+ 2 - 7
Source/Core/Elements/ElementProgress.cpp

@@ -243,13 +243,8 @@ void ElementProgress::GenerateGeometry()
 		texcoords[1] = Vector2f(1, 1);
 	}
 
-	Colourb quad_colour;
-	{
-		const ComputedValues& computed = GetComputedValues();
-		const float opacity = computed.opacity();
-		quad_colour = computed.image_color();
-		quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
-	}
+	const ComputedValues& computed = GetComputedValues();
+	const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 
 	switch (direction)
 	{

+ 9 - 7
Source/Core/Elements/WidgetTextInput.cpp

@@ -287,8 +287,8 @@ void WidgetTextInput::UpdateSelectionColours()
 	// Determine what the colour of the selected text is. If our 'selection' element has the 'color'
 	// attribute set, then use that. Otherwise, use the inverse of our own text colour.
 	Colourb colour;
-	const Property* colour_property = selection_element->GetLocalProperty("color");
-	if (colour_property != nullptr)
+	const Property* colour_property = selection_element->GetLocalProperty(PropertyId::Color);
+	if (colour_property)
 		colour = colour_property->Get<Colourb>();
 	else
 	{
@@ -304,11 +304,13 @@ void WidgetTextInput::UpdateSelectionColours()
 	// If the 'background-color' property has been set on the 'selection' element, use that as the
 	// background colour for the selected text. Otherwise, use the inverse of the selected text
 	// colour.
-	colour_property = selection_element->GetLocalProperty("background-color");
-	if (colour_property != nullptr)
-		selection_colour = colour_property->Get<Colourb>();
+	colour_property = selection_element->GetLocalProperty(PropertyId::BackgroundColor);
+	if (colour_property)
+		colour = colour_property->Get<Colourb>();
 	else
-		selection_colour = Colourb(255 - colour.red, 255 - colour.green, 255 - colour.blue, colour.alpha);
+		colour = Colourb(255 - colour.red, 255 - colour.green, 255 - colour.blue, colour.alpha);
+
+	selection_colour = colour.ToPremultiplied();
 
 	// Color may have changed, so we update the cursor geometry.
 	GenerateCursor();
@@ -1268,7 +1270,7 @@ void WidgetTextInput::GenerateCursor()
 			color = property->Get<Colourb>();
 	}
 
-	GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color);
+	GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color.ToPremultiplied());
 }
 
 void WidgetTextInput::ForceFormattingOnNextLayout()

+ 1 - 1
Source/Core/Elements/WidgetTextInput.h

@@ -242,7 +242,7 @@ private:
 	int selection_length;
 
 	// The colour of the background of selected text.
-	Colourb selection_colour;
+	ColourbPremultiplied selection_colour;
 	// The selection background.
 	Geometry selection_geometry;
 

+ 13 - 0
Source/Core/FontEffect.cpp

@@ -79,4 +79,17 @@ void FontEffect::SetFingerprint(size_t _fingerprint)
 	fingerprint = _fingerprint;
 }
 
+void FontEffect::FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride)
+{
+	for (int y = 0; y < dimensions.y; ++y)
+	{
+		for (int x = 0; x < dimensions.x; ++x)
+		{
+			const int i = y * stride + x * 4;
+			const byte alpha = destination[i + 3];
+			destination[i + 0] = destination[i + 1] = destination[i + 2] = alpha;
+		}
+	}
+}
+
 } // namespace Rml

+ 2 - 0
Source/Core/FontEffectBlur.cpp

@@ -110,6 +110,8 @@ void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i
 
 	filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0),
 		ColorFormat::A8);
+
+	FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride);
 }
 
 FontEffectBlurInstancer::FontEffectBlurInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid)

+ 2 - 0
Source/Core/FontEffectGlow.cpp

@@ -140,6 +140,8 @@ void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i
 
 	filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions,
 		Vector2i(0), ColorFormat::A8);
+
+	FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride);
 }
 
 FontEffectGlowInstancer::FontEffectGlowInstancer() :

+ 2 - 0
Source/Core/FontEffectOutline.cpp

@@ -93,6 +93,8 @@ void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vecto
 {
 	filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions,
 		Vector2i(width), glyph.color_format);
+
+	FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride);
 }
 
 FontEffectOutlineInstancer::FontEffectOutlineInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid)

+ 1 - 1
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp

@@ -78,7 +78,7 @@ int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, const Stri
 }
 
 int FontEngineInterfaceDefault::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string,
-	const Vector2f& position, const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry)
+	const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry)
 {
 	auto handle_default = reinterpret_cast<FontFaceHandleDefault*>(handle);
 	return handle_default->GenerateString(geometry, string, position, colour, opacity, letter_spacing, (int)font_effects_handle);

+ 1 - 1
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h

@@ -59,7 +59,7 @@ public:
 	int GetStringWidth(FontFaceHandle, const String& string, float letter_spacing, Character prior_character) override;
 
 	/// Generates the geometry required to render a single line of text.
-	int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, const Colourb& colour, float opacity,
+	int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity,
 		float letter_spacing, GeometryList& geometry) override;
 
 	/// Returns the current version of the font face.

+ 6 - 11
Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp

@@ -188,7 +188,7 @@ bool FontFaceHandleDefault::GenerateLayerTexture(UniquePtr<const byte[]>& textur
 	return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs);
 }
 
-int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const Colourb colour,
+int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const ColourbPremultiplied colour,
 	const float opacity, const float letter_spacing, const int layer_configuration_index)
 {
 	int geometry_index = 0;
@@ -209,17 +209,11 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 	{
 		FontFaceLayer* layer = layer_configuration[i];
 
-		Colourb layer_colour;
+		ColourbPremultiplied layer_colour;
 		if (layer == base_layer)
-		{
 			layer_colour = colour;
-		}
 		else
-		{
-			layer_colour = layer->GetColour();
-			if (opacity < 1.f)
-				layer_colour.alpha = byte(opacity * float(layer_colour.alpha));
-		}
+			layer_colour = layer->GetColour(opacity);
 
 		const int num_textures = layer->GetNumTextures();
 
@@ -253,9 +247,10 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 			// Adjust the cursor for the kerning between this character and the previous one.
 			line_width += GetKerning(prior_character, character);
 
+			ColourbPremultiplied glyph_color = layer_colour;
 			// Use white vertex colors on RGB glyphs.
-			const Colourb glyph_color =
-				(layer == base_layer && glyph->color_format == ColorFormat::RGBA8 ? Colourb(255, layer_colour.alpha) : layer_colour);
+			if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8)
+				glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha);
 
 			layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color);
 

+ 2 - 2
Source/Core/FontEngineDefault/FontFaceHandleDefault.h

@@ -84,8 +84,8 @@ public:
 	/// @param[in] opacity The opacity of the text, should be applied to font effects.
 	/// @param[in] layer_configuration Face configuration index to use for generating string.
 	/// @return The width, in pixels, of the string geometry.
-	int GenerateString(GeometryList& geometry, const String& string, Vector2f position, Colourb colour, float opacity, float letter_spacing,
-		int layer_configuration = 0);
+	int GenerateString(GeometryList& geometry, const String& string, Vector2f position, ColourbPremultiplied colour, float opacity,
+		float letter_spacing, int layer_configuration = 0);
 
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
 	int GetVersion() const;

+ 2 - 2
Source/Core/FontEngineDefault/FontFaceLayer.cpp

@@ -265,9 +265,9 @@ int FontFaceLayer::GetNumTextures() const
 	return (int)textures.size();
 }
 
-Colourb FontFaceLayer::GetColour() const
+ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const
 {
-	return colour;
+	return colour.ToPremultiplied(opacity);
 }
 
 } // namespace Rml

+ 3 - 3
Source/Core/FontEngineDefault/FontFaceLayer.h

@@ -71,7 +71,7 @@ public:
 	/// @param[in] character_code The character to generate geometry for.
 	/// @param[in] position The position of the baseline.
 	/// @param[in] colour The colour of the string.
-	inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const Colourb colour) const
+	inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const ColourbPremultiplied colour) const
 	{
 		auto it = character_boxes.find(character_code);
 		if (it == character_boxes.end())
@@ -101,8 +101,8 @@ public:
 	/// Returns the number of textures employed by this layer.
 	int GetNumTextures() const;
 
-	/// Returns the layer's colour.
-	Colourb GetColour() const;
+	/// Returns the layer's colour after applying the given opacity.
+	ColourbPremultiplied GetColour(float opacity) const;
 
 private:
 	struct TextureBox {

+ 1 - 1
Source/Core/FontEngineInterface.cpp

@@ -68,7 +68,7 @@ int FontEngineInterface::GetStringWidth(FontFaceHandle /*handle*/, const String&
 }
 
 int FontEngineInterface::GenerateString(FontFaceHandle /*face_handle*/, FontEffectsHandle /*font_effects_handle*/, const String& /*string*/,
-	const Vector2f& /*position*/, const Colourb& /*colour*/, float /*opacity*/, float /*letter_spacing*/, GeometryList& /*geometry*/)
+	const Vector2f& /*position*/, ColourbPremultiplied /*colour*/, float /*opacity*/, float /*letter_spacing*/, GeometryList& /*geometry*/)
 {
 	return 0;
 }

+ 14 - 12
Source/Core/GeometryBackgroundBorder.cpp

@@ -100,7 +100,7 @@ BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_posi
 	return metrics;
 }
 
-void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colourb color)
+void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color)
 {
 	const int offset_vertices = (int)vertices.size();
 
@@ -111,7 +111,7 @@ void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colo
 	FillBackground(offset_vertices);
 }
 
-void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4])
+void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4])
 {
 	RMLUI_ASSERT(border_colors);
 
@@ -153,7 +153,8 @@ void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSize
 	}
 }
 
-void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color)
+void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r,
+	ColourbPremultiplied color)
 {
 	if (R == 0 || r.x <= 0 || r.y <= 0)
 	{
@@ -168,7 +169,7 @@ void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_
 	}
 }
 
-void GeometryBackgroundBorder::DrawPoint(Vector2f pos, Colourb color)
+void GeometryBackgroundBorder::DrawPoint(Vector2f pos, ColourbPremultiplied color)
 {
 	const int offset_vertices = (int)vertices.size();
 
@@ -178,7 +179,8 @@ void GeometryBackgroundBorder::DrawPoint(Vector2f pos, Colourb color)
 	vertices[offset_vertices].colour = color;
 }
 
-void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points)
+void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1,
+	int num_points)
 {
 	RMLUI_ASSERT(num_points >= 2 && r.x > 0 && r.y > 0);
 
@@ -217,7 +219,7 @@ void GeometryBackgroundBorder::FillBackground(int index_start)
 }
 
 void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R,
-	Vector2f r, Colourb color0, Colourb color1)
+	Vector2f r, ColourbPremultiplied color0, ColourbPremultiplied color1)
 {
 	const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI;
 	const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI;
@@ -236,7 +238,7 @@ void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_oute
 	}
 }
 
-void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1)
+void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1)
 {
 	const bool different_color = (color0 != color1);
 
@@ -252,8 +254,8 @@ void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_i
 	}
 }
 
-void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1,
-	int num_points)
+void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0,
+	ColourbPremultiplied color1, int num_points)
 {
 	RMLUI_ASSERT(num_points >= 2 && R > 0 && r.x > 0 && r.y > 0);
 
@@ -270,7 +272,7 @@ void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f
 		const float t = float(i) / float(num_points - 1);
 
 		const float a = Math::Lerp(t, a0, a1);
-		const Colourb color = Math::RoundedLerp(t, color0, color1);
+		const ColourbPremultiplied color = Math::RoundedLerp(t, color0, color1);
 		const Vector2f unit_vector(Math::Cos(a), Math::Sin(a));
 
 		vertices[offset_vertices + 2 * i].position = unit_vector * r + pos_center;
@@ -291,8 +293,8 @@ void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f
 	}
 }
 
-void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1,
-	int num_points)
+void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0,
+	ColourbPremultiplied color1, int num_points)
 {
 	RMLUI_ASSERT(R > 0 && num_points >= 2);
 

+ 12 - 10
Source/Core/GeometryBackgroundBorder.h

@@ -75,10 +75,10 @@ public:
 	static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, Vector4f outer_radii);
 
 	// Generate geometry for the background, defined by the inner area of the border metrics.
-	void DrawBackground(const BorderMetrics& metrics, Colourb color);
+	void DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color);
 
 	/// Generate geometry for the border, defined by the intersection of the outer and inner areas of the border metrics.
-	void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]);
+	void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4]);
 
 private:
 	enum Edge { TOP, RIGHT, BOTTOM, LEFT };
@@ -88,14 +88,14 @@ private:
 	// All draw operations place vertices in clockwise order.
 
 	// Draw the corner, delegate to the specific corner shape drawing function.
-	void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color);
+	void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, ColourbPremultiplied color);
 
 	// Add a single point.
-	void DrawPoint(Vector2f pos, Colourb color);
+	void DrawPoint(Vector2f pos, ColourbPremultiplied color);
 
 	// Draw an arc by placing vertices along the ellipse formed by the two-axis radius r, spaced evenly between angles a0,a1 (inclusive). Colors are
 	// interpolated.
-	void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+	void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, int num_points);
 
 	// Generates triangles by connecting the added vertices.
 	void FillBackground(int index_start);
@@ -110,18 +110,20 @@ private:
 	// Where 'next' corner means along the clockwise direction. This way we can easily fill the triangles of the edges in FillEdge().
 
 	// Draw the corner, delegate to the specific corner shape drawing function.
-	void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color0,
-		Colourb color1);
+	void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r,
+		ColourbPremultiplied color0, ColourbPremultiplied color1);
 
 	// Draw a sharp border corner, ie. no border-radius. Does not produce any triangles.
-	void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1);
+	void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1);
 
 	// Draw an arc along the outer edge (radius R), and an arc along the inner edge (two-axis radius r),
 	// spaced evenly between angles a0,a1 (inclusive). Connect them by triangles. Colors are interpolated.
-	void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+	void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1,
+		int num_points);
 
 	// Draw an arc along the outer edge, and connect them by triangles to a point on the inner edge.
-	void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+	void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1,
+		int num_points);
 
 	// Add triangles between the previous corner to another one specified by the index (possibly yet-to-be-drawn).
 	void FillEdge(int index_next_corner);

+ 5 - 3
Source/Core/GeometryBoxShadow.cpp

@@ -120,11 +120,12 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha
 		{
 			Vector2f offset;
 			const Box& box = element->GetBox(i, offset);
+			ColourbPremultiplied white(255);
 
 			if (has_inner_shadow)
-				GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding);
+				GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, white, BoxArea::Padding);
 			if (has_outer_shadow)
-				GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border);
+				GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, white, BoxArea::Border);
 		}
 
 		RenderManager& render_manager = context->GetRenderManager();
@@ -232,7 +233,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha
 	vertices.resize(4);
 	indices.resize(6);
 	const byte alpha = byte(opacity * 255.f);
-	GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha));
+	GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions),
+		ColourbPremultiplied(alpha, alpha));
 
 	out_shadow_texture.Set("box-shadow", texture_callback);
 	out_shadow_geometry.SetTexture(&out_shadow_texture);

+ 9 - 8
Source/Core/GeometryUtilities.cpp

@@ -40,13 +40,14 @@ GeometryUtilities::GeometryUtilities() {}
 
 GeometryUtilities::~GeometryUtilities() {}
 
-void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset)
+void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour,
+	int index_offset)
 {
 	GenerateQuad(vertices, indices, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1), index_offset);
 }
 
-void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord,
-	Vector2f bottom_right_texcoord, int index_offset)
+void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour,
+	Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset)
 {
 	vertices[0].position = origin;
 	vertices[0].colour = colour;
@@ -73,7 +74,7 @@ void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f or
 	indices[5] = index_offset + 2;
 }
 
-void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color)
+void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color)
 {
 	Math::SnapToPixelGrid(position, size);
 
@@ -90,7 +91,7 @@ void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vect
 }
 
 void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius,
-	Colourb background_color, const Colourb border_colors[4])
+	ColourbPremultiplied background_color, const ColourbPremultiplied border_colors[4])
 {
 	RMLUI_ASSERT(border_colors);
 
@@ -148,7 +149,7 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B
 		for (int i = 0; i < num_vertices; i++)
 		{
 			GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position,
-				Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i);
+				Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i);
 		}
 	}
 #endif
@@ -162,8 +163,8 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B
 #endif
 }
 
-void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb color,
-	BoxArea fill_area)
+void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius,
+	ColourbPremultiplied color, BoxArea fill_area)
 {
 	RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content,
 		"Rectangle geometry only supports border, padding and content boxes.");

+ 2 - 2
Source/Core/Math.cpp

@@ -279,9 +279,9 @@ namespace Math {
 		return Vector2i(Clamp(value.x, min.x, max.x), Clamp(value.y, min.y, max.y));
 	}
 
-	Colourb RoundedLerp(float t, Colourb v0, Colourb v1)
+	ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied v0, ColourbPremultiplied v1)
 	{
-		return Colourb{
+		return ColourbPremultiplied{
 			static_cast<unsigned char>(RoundToInteger(Lerp(t, static_cast<float>(v0[0]), static_cast<float>(v1[0])))),
 			static_cast<unsigned char>(RoundToInteger(Lerp(t, static_cast<float>(v0[1]), static_cast<float>(v1[1])))),
 			static_cast<unsigned char>(RoundToInteger(Lerp(t, static_cast<float>(v0[2]), static_cast<float>(v1[2])))),

+ 1 - 1
Source/Core/PropertyParserBoxShadow.cpp

@@ -97,7 +97,7 @@ bool PropertyParserBoxShadow::ParseValue(Property& property, const String& value
 			}
 			else if (parser_color->ParseValue(prop, argument, empty_parameter_map))
 			{
-				shadow.color = prop.Get<Colourb>();
+				shadow.color = prop.Get<Colourb>().ToPremultiplied();
 			}
 			else
 			{

+ 1 - 1
Source/Core/PropertyParserColorStopList.cpp

@@ -72,7 +72,7 @@ bool PropertyParserColorStopList::ParseValue(Property& property, const String& v
 			return false;
 
 		ColorStop color_stop = {};
-		color_stop.color = p_color.Get<Colourb>();
+		color_stop.color = p_color.Get<Colourb>().ToPremultiplied();
 
 		if (values.size() <= 1)
 			color_stops.push_back(color_stop);

+ 7 - 5
Source/Debugger/Geometry.cpp

@@ -53,10 +53,12 @@ void Geometry::RenderOutline(const Vector2f origin, const Vector2f dimensions, c
 	Vertex vertices[4 * 4];
 	int indices[6 * 4];
 
-	GeometryUtilities::GenerateQuad(vertices + 0, indices + 0, Vector2f(0, 0), Vector2f(dimensions.x, width), colour, 0);
-	GeometryUtilities::GenerateQuad(vertices + 4, indices + 6, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour, 4);
-	GeometryUtilities::GenerateQuad(vertices + 8, indices + 12, Vector2f(0, 0), Vector2f(width, dimensions.y), colour, 8);
-	GeometryUtilities::GenerateQuad(vertices + 12, indices + 18, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour, 12);
+	ColourbPremultiplied colour_pre = colour.ToPremultiplied();
+
+	GeometryUtilities::GenerateQuad(vertices + 0, indices + 0, Vector2f(0, 0), Vector2f(dimensions.x, width), colour_pre, 0);
+	GeometryUtilities::GenerateQuad(vertices + 4, indices + 6, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour_pre, 4);
+	GeometryUtilities::GenerateQuad(vertices + 8, indices + 12, Vector2f(0, 0), Vector2f(width, dimensions.y), colour_pre, 8);
+	GeometryUtilities::GenerateQuad(vertices + 12, indices + 18, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour_pre, 12);
 
 	render_interface->RenderGeometry(vertices, 4 * 4, indices, 6 * 4, 0, origin);
 }
@@ -70,7 +72,7 @@ void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const
 	Vertex vertices[4];
 	int indices[6];
 
-	GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour, 0);
+	GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied(), 0);
 
 	render_interface->RenderGeometry(vertices, 4, indices, 6, 0, origin);
 }

+ 1 - 4
Source/Lottie/ElementLottie.cpp

@@ -135,10 +135,7 @@ void ElementLottie::GenerateGeometry()
 	};
 
 	const ComputedValues& computed = GetComputedValues();
-
-	const float opacity = computed.opacity();
-	Colourb quad_colour = computed.image_color();
-	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
+	ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 
 	const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round();
 	render_dimensions = Vector2i(render_dimensions_f);

+ 1 - 4
Source/SVG/ElementSVG.cpp

@@ -128,10 +128,7 @@ void ElementSVG::GenerateGeometry()
 	};
 
 	const ComputedValues& computed = GetComputedValues();
-
-	const float opacity = computed.opacity();
-	Colourb quad_colour = computed.image_color();
-	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
+	ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity());
 
 	const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round();
 	render_dimensions.x = int(render_dimensions_f.x);

+ 4 - 4
Tests/Source/UnitTests/Math.cpp

@@ -34,11 +34,11 @@ using namespace Rml;
 
 TEST_CASE("Math.RoundedLerp")
 {
-	const Colourb c0(0, 0, 0, 255);
-	const Colourb c1(255, 0, 0, 255);
-	const Colourb c2(127, 0, 0, 255);
+	const ColourbPremultiplied c0(0, 0, 0, 255);
+	const ColourbPremultiplied c1(255, 0, 0, 255);
+	const ColourbPremultiplied c2(127, 0, 0, 255);
 
-	Colourb c;
+	ColourbPremultiplied c;
 
 	c = Math::RoundedLerp(0.0f, c0, c1);
 	REQUIRE(c.red == c0.red);

+ 10 - 10
Tests/Source/UnitTests/Properties.cpp

@@ -117,31 +117,31 @@ TEST_CASE("Properties")
 			{
 				"red, blue",
 				{
-					ColorStop{Colourb(255, 0, 0), NumericValue{}},
-					ColorStop{Colourb(0, 0, 255), NumericValue{}},
+					ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}},
+					ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{}},
 				},
 			},
 			{
 				"red 5px, blue 50%",
 				{
-					ColorStop{Colourb(255, 0, 0), NumericValue{5.f, Unit::PX}},
-					ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}},
+					ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{5.f, Unit::PX}},
+					ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}},
 				},
 			},
 			{
 				"red, #00f 50%, rgba(0, 255,0, 150) 10dp",
 				{
-					ColorStop{Colourb(255, 0, 0), NumericValue{}},
-					ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}},
-					ColorStop{Colourb(0, 255, 0, 150), NumericValue{10.f, Unit::DP}},
+					ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}},
+					ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}},
+					ColorStop{ColourbPremultiplied(0, 150, 0, 150), NumericValue{10.f, Unit::DP}},
 				},
 			},
 			{
 				"red 50px 20%, blue 10in",
 				{
-					ColorStop{Colourb(255, 0, 0), NumericValue{50.f, Unit::PX}},
-					ColorStop{Colourb(255, 0, 0), NumericValue{20.f, Unit::PERCENT}},
-					ColorStop{Colourb(0, 0, 255), NumericValue{10.f, Unit::INCH}},
+					ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{50.f, Unit::PX}},
+					ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{20.f, Unit::PERCENT}},
+					ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{10.f, Unit::INCH}},
 				},
 			},
 		};

+ 1 - 1
Tests/Source/VisualTests/CaptureScreen.cpp

@@ -184,7 +184,7 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int
 	auto GenerateGeometry = [&](TextureGeometry& geometry, Rml::byte* data, Rml::Vector2i dimensions) -> bool {
 		if (!render_interface->GenerateTexture(geometry.texture_handle, data, dimensions))
 			return false;
-		const Rml::Colourb colour = {255, 255, 255, 255};
+		const Rml::ColourbPremultiplied colour = {255, 255, 255, 255};
 		const Rml::Vector2f uv_top_left = {0, 0};
 		const Rml::Vector2f uv_bottom_right = {1, 1};
 		Rml::GeometryUtilities::GenerateQuad(geometry.vertices, geometry.indices, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref),