Browse Source

Use premultiplied alpha for textures submitted through the render interface

Michael Ragazzon 2 years ago
parent
commit
7ae3bb3a29

+ 11 - 2
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -68,8 +68,8 @@ public:
 		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		const size_t i = source.rfind('.');
-		Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
+		const size_t i_ext = source.rfind('.');
+		Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
 		SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
 
@@ -95,6 +95,15 @@ public:
 				surface = new_surface;
 			}
 
+			// RmlUi assumes premultiplied alpha, convert the color values accordingly.
+			byte* pixels = static_cast<byte*>(surface->pixels);
+			for (int i = 0; i < surface->w * surface->h * 4; i += 4)
+			{
+				const byte alpha = pixels[i + 3];
+				for (int j = 0; j < 3; ++j)
+					pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
+			}
+
 			success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions);
 			SDL_FreeSurface(surface);
 		}

+ 12 - 34
Backends/RmlUi_Renderer_GL3.cpp

@@ -58,8 +58,6 @@
 // Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied.
 static constexpr int NUM_MSAA_SAMPLES = 2;
 
-#define RMLUI_PREMULTIPLIED_ALPHA 1
-
 #define MAX_NUM_STOPS 16
 #define BLUR_SIZE 7
 #define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2)
@@ -67,9 +65,7 @@ static constexpr int NUM_MSAA_SAMPLES = 2;
 #define RMLUI_STRINGIFY_IMPL(x) #x
 #define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x)
 
-#define RMLUI_SHADER_HEADER     \
-	RMLUI_SHADER_HEADER_VERSION \
-	"#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n"
+#define RMLUI_SHADER_HEADER RMLUI_SHADER_HEADER_VERSION "#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n"
 
 static const char* shader_vert_main = RMLUI_SHADER_HEADER R"(
 uniform vec2 _translate;
@@ -86,7 +82,7 @@ void main() {
 	fragTexCoord = inTexCoord0;
 	fragColor = inColor0;
 
-#if RMLUI_PREMULTIPLIED_ALPHA
+#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
@@ -864,13 +860,10 @@ void RenderInterface_GL3::BeginFrame()
 	glDisable(GL_SCISSOR_TEST);
 	glDisable(GL_CULL_FACE);
 
+	// Set blending function for premultiplied alpha.
 	glEnable(GL_BLEND);
 	glBlendEquation(GL_FUNC_ADD);
-#if RMLUI_PREMULTIPLIED_ALPHA
 	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-#else
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-#endif
 
 #ifndef RMLUI_PLATFORM_EMSCRIPTEN
 	// We do blending in nonlinear sRGB space because that is the common practice and gives results that we are used to.
@@ -910,7 +903,7 @@ void RenderInterface_GL3::EndFrame()
 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 
 	// Assuming we have an opaque background, we can just write to it with the premultiplied alpha blend mode and we'll get the correct result.
-	// Instead, if we had a transparent destination that didn't use pre-multiplied alpha, we would need to perform a manual un-premultiplication step.
+	// Instead, if we had a transparent destination that didn't use premultiplied alpha, we would need to perform a manual un-premultiplication step.
 	glActiveTexture(GL_TEXTURE0);
 	Gfx::BindTexture(fb_postprocess);
 	UseProgram(ProgramId::Passthrough);
@@ -1245,11 +1238,16 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
 		for (long x = 0; x < header.width; x++)
 		{
-			image_dest[write_index] = image_src[read_index + 2];
-			image_dest[write_index + 1] = image_src[read_index + 1];
+			image_dest[write_index] = image_src[read_index + 1];
+			image_dest[write_index + 1] = image_src[read_index + 2];
 			image_dest[write_index + 2] = image_src[read_index];
 			if (color_mode == 4)
-				image_dest[write_index + 3] = image_src[read_index + 3];
+			{
+				const byte alpha = image_src[read_index + 3];
+				for (size_t j = 0; j < 3; j++)
+					image_dest[write_index + j] = byte((int(image_dest[write_index + j]) * int(alpha)) / 255);
+				image_dest[write_index + 3] = alpha;
+			}
 			else
 				image_dest[write_index + 3] = 255;
 
@@ -1279,26 +1277,6 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co
 		return false;
 	}
 
-#if RMLUI_PREMULTIPLIED_ALPHA
-	using Rml::byte;
-	Rml::UniquePtr<byte[]> source_premultiplied;
-	if (source)
-	{
-		const size_t num_bytes = source_dimensions.x * source_dimensions.y * 4;
-		source_premultiplied = Rml::UniquePtr<byte[]>(new byte[num_bytes]);
-
-		for (size_t i = 0; i < num_bytes; i += 4)
-		{
-			const byte alpha = source[i + 3];
-			for (size_t j = 0; j < 3; j++)
-				source_premultiplied[i + j] = byte((int(source[i + j]) * int(alpha)) / 255);
-			source_premultiplied[i + 3] = alpha;
-		}
-
-		source = source_premultiplied.get();
-	}
-#endif
-
 	glBindTexture(GL_TEXTURE_2D, texture_id);
 
 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);

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

@@ -125,7 +125,7 @@ public:
 	virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source);
 	/// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels.
 	/// @param[out] texture_handle The handle to write the texture handle for the generated texture to.
-	/// @param[in] source The raw 8-bit texture data. Each pixel is made up of four 8-bit values, indicating red, green, blue and alpha in that order.
+	/// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order.
 	/// @param[in] source_dimensions The dimensions, in pixels, of the source data.
 	/// @return True if the texture generation succeeded and the handle is valid, false if not.
 	virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions);

+ 3 - 1
Source/Core/FontEngineDefault/FontFaceLayer.cpp

@@ -220,8 +220,10 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 					{
 					case ColorFormat::A8:
 					{
+						// We use premultiplied alpha, so copy the alpha into all four channels.
 						for (int k = 0; k < num_bytes_per_line; ++k)
-							destination[k * 4 + 3] = source[k];
+							for (int c = 0; c < 4; ++c)
+								destination[k * 4 + c] = source[k];
 					}
 					break;
 					case ColorFormat::RGBA8:

+ 7 - 17
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -438,28 +438,18 @@ static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap&
 
 				if (glyph.color_format == ColorFormat::RGBA8)
 				{
-					// Swizzle channels (BGRA -> RGBA) and un-premultiply alpha.
+					// Swizzle channels (BGRA -> RGBA)
 					destination_bitmap = glyph.bitmap_owned_data.get();
 
 					for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4)
 					{
-						byte b = destination_bitmap[k];
-						byte g = destination_bitmap[k + 1];
-						byte r = destination_bitmap[k + 2];
+						std::swap(destination_bitmap[k], destination_bitmap[k + 2]);
+#ifdef RMLUI_DEBUG
 						const byte alpha = destination_bitmap[k + 3];
-						RMLUI_ASSERTMSG(b <= alpha && g <= alpha && r <= alpha, "Assumption of glyph data being premultiplied is broken.");
-
-						if (alpha > 0 && alpha < 255)
-						{
-							b = byte((b * 255) / alpha);
-							g = byte((g * 255) / alpha);
-							r = byte((r * 255) / alpha);
-						}
-
-						destination_bitmap[k] = r;
-						destination_bitmap[k + 1] = g;
-						destination_bitmap[k + 2] = b;
-						destination_bitmap[k + 3] = alpha;
+						for (int c = 0; c < 3; c++)
+							RMLUI_ASSERTMSG(destination_bitmap[k + c] <= alpha,
+								"Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case.");
+#endif
 					}
 				}
 			}

+ 2 - 5
Source/Core/TextureLayoutTexture.cpp

@@ -139,11 +139,8 @@ UniquePtr<byte[]> TextureLayoutTexture::AllocateTexture()
 
 	if (dimensions.x > 0 && dimensions.y > 0)
 	{
-		texture_data.reset(new byte[dimensions.x * dimensions.y * 4]);
-
-		// Set the texture to transparent white.
-		for (int i = 0; i < dimensions.x * dimensions.y; i++)
-			((unsigned int*)(texture_data.get()))[i] = 0x00ffffff;
+		// Set the texture to transparent black.
+		texture_data.reset(new byte[dimensions.x * dimensions.y * 4]());
 
 		for (size_t i = 0; i < rows.size(); ++i)
 			rows[i].Allocate(texture_data.get(), dimensions.x * 4);

+ 6 - 9
Source/Lottie/ElementLottie.cpp

@@ -236,20 +236,17 @@ void ElementLottie::UpdateTexture()
 		rlottie::Surface surface(reinterpret_cast<uint32_t*>(p_data), render_dimensions.x, render_dimensions.y, bytes_per_line);
 		animation->renderSync(next_frame, surface);
 
-		// Swizzle the channel order from rlottie's BGRA to RmlUi's RGBA, and change pre-multiplied to post-multiplied alpha.
+		// Swizzle the channel order from rlottie's BGRA to RmlUi's RGBA.
 		for (size_t i = 0; i < total_bytes; i += 4)
 		{
 			// Swap the RB order for correct color channels.
 			std::swap(p_data[i], p_data[i + 2]);
 
-			// The RmlUi samples shell uses post-multiplied alpha, while rlottie serves pre-multiplied alpha.
-			// Here, we un-premultiply the colors.
-			const byte a = p_data[i + 3];
-			if (a > 0 && a < 255)
-			{
-				for (size_t j = 0; j < 3; j++)
-					p_data[i + j] = (p_data[i + j] * 255) / a;
-			}
+#ifdef RMLUI_DEBUG
+			const byte alpha = p_data[i + 3];
+			for (int c = 0; c < 3; c++)
+				RMLUI_ASSERTMSG(p_data[i + c] <= alpha, "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case.");
+#endif
 		}
 
 		if (!render_interface->GenerateTexture(out_handle, p_data, render_dimensions))