Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alpha blending to Travertino #3140

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

proneon267
Copy link
Contributor

Related PR: #2484
This PR adds alpha blending operation capabilities to Travertino. The following APIs are added to Travertino:

  • Color.blend_over()
  • straight_alpha_blending_over()
  • reverse_straight_alpha_blending_over()

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

@proneon267
Copy link
Contributor Author

The MapView testbed failure on windows is unrelated.

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of minor notes inline, but otherwise I think looks good.

Procedurally, we should wait until #3129 lands, so we can ensure that there's no coverage drop as a result of this change. I think we're good, but merging after #3129 will make certain of that.

self.g,
self.b,
1, # alpha will be 1 for a rgb color.
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Shouldn't it be inheriting the definition from the parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is inheriting it, so I have removed it.

def blend_over(self, back_color: rgba) -> rgba:
"""Returns an alpha blended color, by performing the "over" straight alpha
blending operation, compositing the front color over the back color."""
return straight_alpha_blending_over(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a matter of API consistency, I'm not sure about having a straight_alpha_blending_over primitive, with a blend_over member method.

You've made a clear (and well documented) distinction between straight and pre-multiplied blending; is there any practical reason why we would ever implement a premultiplied_alpha_blending_over()? Or, if we did need it, use a boolean/enum argument (i.e., alpha_blend_over(..., straight=True) or alpha_blend_over(... , method=STRAIGHT))?

I'm not saying we need to implement the other blending method - more highlighting the opportunity for a naming cleanup right now that doesn't preclude adding the alternative later if it were required.

So - why not use alpha_blend_over() and alpha_unblend_over()?

Copy link
Contributor Author

@proneon267 proneon267 Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since, CSS's rgba() only does straight alpha blending, so I doubt we are going to be needing premultiplied alpha blending in the near future. Therefore, I have changed up the names to alpha_blend_over() and alpha_unblend_over().

Should I convert the alpha_blend_over() and alpha_unblend_over() functions, into methods of the Color class? or keep them both in the form of functions, as well as methods of Color class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense for Color to have 2 methods (blend_over() and unblend_over()); I don't think there's a need for standalone versions, or for alpha to be in the name.

The one detail that you need to watch for: the instance, or the background being blended with, could be RGB, RGBA, HSL, or HSLA - so:

  1. the type declaration needs to refer to Color, not rgba;
  2. the implementation needs to do the rgba conversion on the value provided, and
  3. the tests should include validation of every combination of rgb/hsl etc as base and background color.

@proneon267
Copy link
Contributor Author

The MapView testbed failure on windows is unrelated.

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Headed in the right direction, but some more modifications needed.

):
try:
if {type(actual), type(expected)} == {rgba}:
assert abs(actual.rgba.r - expected.rgba.r) < tolerance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what pytest.approx() is for. It also works on tuples, not just single values.

Comment on lines +51 to +54
:param round_to_nearest_int: Should the rgb values of the blended color be
rounded to the nearest int? If the blended color will be later deblended
to get the original front color, then keeping the decimal precision will
give a more accurate value of the original front color.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be indented after the first line.

Comment on lines +49 to +50
:param front_color: The foreground color in the form of :any:`rgba()`.
:param back_color: The background color in the form of :any:`rgba()`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to provide type details here (and the type detail is wrong anyway... and still includes the front color as an argument)

else:
final_blended_color = blended_color
# Return in the same color format as the current color class.
return getattr(final_blended_color, current_color_class_type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice thought, but I'm not sure it works in the general case. rgb(...).blend_over(hsla(...)) will drop all the alpha components from the resulting color. If you're blending colors, I don't know that the preservation of type is important; I'd be fine with this always returning rgba as long as it can accept any input types.

front_color_alpha,
)
# Return in the same color format as the current color class.
return getattr(front_color, type(self).__name__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with blend_over - this is a nice thought, but I don't think it works in the general case.

return rgb(self.r, self.g, self.b)

@property
def hsla(self) -> hsla:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice utility, but it's only tested implicitly. Type conversions like this should have explicit tests.

return hsla(
min(360, max(0, round(hue, 4))),
# Considering the CSS specs, specify that the saturation and lightness can
# be a percentage, so rounding to 4 decimal places would preserve precision.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow the logic here. How does rounding to 4dp preserve precision? Isn't it losing precision by definition?

# The derived front color from the blended color, will be equal to the
# original front color, within the given tolerance range.
assert_equal_color(
calculated_front_color, front_color, alpha_unblending_operation=True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a matter of style - long parameter names aren't always a virtue. unblend=True would mean this doesn't need to be split over 3 lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants