wayfire icon indicating copy to clipboard operation
wayfire copied to clipboard

Inconsistent colors between textures from cairo surfaces and from OpenGL::render_rectangle()

Open AhoyISki opened this issue 3 years ago • 1 comments

Describe the bug I'm making a more advanced plugin for decorations in wayfire. One of the features of this plugin is the ability to have rounded corners with outlines. These rounded corners are made using cairo, while the rest of the border uses the OpenGL::render_rectangle() function. I have noticed that, when a color is transparent, said color doesn't match between these two textures.

To Reproduce Steps to reproduce the behavior:

  1. Create a cairo surface with a transparent color and render it with OpenGL::render_texture()..
  2. Render a rectangle using OpenGL::render_rectangle() with the same color.
  3. Compare the 2 colors.

Expected behavior The two colors will be the same, since they have the same rgba values.

Screenshots or stacktrace Here is what it looks like when the background isn't transparent: image

And when I set the transparency values to 0.9: image

In the pictures, the first color value is the one from the kitty terminal that I'm using. It also differs, but I think that warrants its own issue. The second color is the border color, rendered with OpenGL::render_rectangle(), and the third color is the color form the cairo surface, rendered with OpenGL::render_texture.

All colors are working on a completely white background, so that is not the cause of the difference. I know that the difference isn't even noticeable at all, but it is still non standard behavior.

The whole plugin (still in early alpha!) can be found here, but here are the relevant bits of code:

Function that generates the corner surface:

cairo_surface_t *decoration_theme_t::form_corner(bool active) const {
	float outline_radius = corner_radius - (float)outline_size / 2;

        const auto format = CAIRO_FORMAT_ARGB32;
        cairo_surface_t *surface = cairo_image_surface_create(format, corner_radius, corner_radius);
        auto cr = cairo_create(surface);

        float t = 0.9; // <----------------------------------------------- Transparency

        wf::color_t back = { 0.114, 0.122, 0.129, t }; // <--------------- Background

        /* Clearance */
        cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
        cairo_set_source_rgba(cr, 0, 0, 0, 0);
        cairo_rectangle(cr, 0, 0, corner_radius, corner_radius);
        cairo_fill(cr);

        cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
        /* Border */
	wf::color_t color = active ? active_border : inactive_border;
        cairo_set_source_rgba(cr, back.r, back.g, back.b, back.a);
        cairo_arc(cr, 0, 0, corner_radius, 0, M_PI / 2);
        cairo_line_to(cr, 0, 0);
        cairo_fill(cr);

        /* Outline */
        color = active ? active_outline : inactive_outline;
        cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
        cairo_set_line_width(cr, outline_size);
        cairo_arc(cr, 0, 0, outline_radius, 0, M_PI / 2);
        cairo_stroke(cr);
        cairo_destroy(cr);

        return surface;
}

Function that uploads said surface to an OpenGL texture:

void update_corners(
	wf::firedecor::decoration_theme_t::edge_colors_t colors, int corner_radius) {
	if ((current_corner_radius != corner_radius) ||
            !(current_edge.active_border == colors.active_border) ||
            !(current_edge.active_border == colors.inactive_border) ||
	    !(current_edge.active_outline == colors.active_outline) ||
	    !(current_edge.inactive_outline == colors.inactive_outline)) {
		auto surface = theme.form_corner(ACTIVE);
		cairo_surface_upload_to_texture(surface, corners.active);
		surface = theme.form_corner(INACTIVE);
		cairo_surface_upload_to_texture(surface, corners.inactive);
		current_edge.active_border = colors.active_border;
		current_edge.inactive_border = colors.inactive_border;
		current_edge.active_outline = colors.active_outline;
		current_edge.inactive_outline = colors.inactive_outline;
		current_corner_radius = corner_radius;
	}
};

Function that renders the background. Notice here that I used a trick that I saw here #1258, before doing so, the colors were much more discernible.

void render_background(const wf::framebuffer_t& fb, 
	wf::geometry_t rect, const wf::geometry_t& scissor, bool active, wf::point_t origin) {
	auto colors = theme.get_edge_colors();

	int r = theme.get_corner_radius();
	update_corners(colors, r);
	r = current_corner_radius;
	auto& corner = active ? corners.active : corners.inactive;

	wf::geometry_t top_left = { rect.x, rect.y, r, r };
	wf::geometry_t top_right = { rect.width - r + origin.x, rect.y, r, r };
	wf::geometry_t bottom_left = { rect.x, rect.height - r + origin.y, r, r };
	wf::geometry_t bottom_right = { rect.width - r, rect.height - r, r, r };
	int outline_size = theme.get_outline_size();

	bottom_right = bottom_right + origin;

	float t = 0.9; // <----------------------------------------------- Transparency

 	wf::color_t back = { 0.114 * t, 0.122 * t, 0.129 * t, t }; // <--- Background

	/** Non corner background */
	OpenGL::render_begin(fb);
	fb.logic_scissor(scissor);

	/** Middle rectangle and central outlines */
	OpenGL::render_rectangle(
		{ rect.x + r, rect.y, rect.width - 2 * r, rect.height },
		back,
		fb.get_orthographic_projection());
	OpenGL::render_rectangle(
		{ rect.x + r, rect.y, rect.width - 2 * r, outline_size},
		  active ? colors.active_outline : colors.inactive_outline,
		fb.get_orthographic_projection());
	OpenGL::render_rectangle(
		{ rect.x + r, rect.height - outline_size + origin.y,
		  rect.width - 2 * r, outline_size },
		active ? colors.active_outline : colors.inactive_outline,
		fb.get_orthographic_projection());

	/** Left border and outline */
	OpenGL::render_rectangle(
		{ rect.x, rect.y + r, r, rect.height - 2 * r },
		back,
		fb.get_orthographic_projection());
	OpenGL::render_rectangle(
		{ rect.x, rect.y + r, outline_size, rect.height - 2 * r },
		  active ? colors.active_outline : colors.inactive_outline,
		fb.get_orthographic_projection());

	/** Right border and outline */
	OpenGL::render_rectangle(
		{ rect.width - r + origin.x, rect.y + r, r, rect.height - 2 * r },
		back,
		fb.get_orthographic_projection());
	OpenGL::render_rectangle(
		{ rect.width - outline_size + origin.x, 
		  rect.y + r, outline_size, rect.height - 2 * r },
		active ? colors.active_outline : colors.inactive_outline,
		fb.get_orthographic_projection());
	/** Corner background */

	/* Top left corner */
	OpenGL::render_texture(corner.tex, fb, top_left, glm::vec4(1.0f),
		OpenGL::TEXTURE_TRANSFORM_INVERT_X);

	/* Top right corner */
	OpenGL::render_texture(corner.tex, fb, top_right, glm::vec4(1.0f));

	/* Bottom left corner */
	OpenGL::render_texture(corner.tex, fb, bottom_left, glm::vec4(1.0f),
		OpenGL::TEXTURE_TRANSFORM_INVERT_X | OpenGL::TEXTURE_TRANSFORM_INVERT_Y);

	/* Bottom right corner */
	OpenGL::render_texture(corner.tex, fb, bottom_right, glm::vec4(1.0f),
		OpenGL::TEXTURE_TRANSFORM_INVERT_Y);
	OpenGL::render_end();
}

Wayfire version 0.7.2-1, git

AhoyISki avatar Feb 28 '22 04:02 AhoyISki

It looks like the difference is only 1 in all channels. Maybe cairo does the color*alpha premultiplication differently which could lead to some rounding differences. In the window shadows plugin I did the premultiplication similar to how you did it. However, I don't think it is a wayfire bug, just a difference in implementation between cairo and your code, given how small the difference is.

timgott avatar Mar 08 '22 13:03 timgott

As already mentioned, I don't see evidence of a bug on Wayfire's side, you probably need to handle the rounding differently.

ammen99 avatar Mar 08 '24 12:03 ammen99