Color3 values should support math operations

Well, let’s think of it this way. Mathematical operations on colors in gamma space are incorrect. If I take white (255,255,255) and divide it by 2, I get (127.5,127.5,127.5), right? You might expect this to be half as bright. It’s not, though.

Monitors show you color in gamma space because weird reasons I won’t go into. When you do math with colors in shaders, you must convert to linear space first or everything you do is wrong. Normals won’t look right, lighting and shading will be too dark or too bright. Fortunately, conversion is simple… but keeping track of it isn’t. This issue is compounded by the fact that most image formats are in gamma space, and you need to convert back to gamma space when rendering.

Sure, it might be simple enough to make the distinction in your API. Maybe LinearColor3, for example, with conversion functions in between. But it’s a mindset change; 0-255 makes no sense, you really want 0.0-1.0. Linear colors don’t look like you expect them too, either, and different color gamuts make the problem more complicated.

I’m not really the best at explaining this, so TL;DR: linear color space is good for math, gamma color space is not.

2 Likes

Is this referring to the thread this one was linked from? I ask that because Color3 values are already in terms of 0-1 right now.

1 Like

Not sure. I was just mentioning that 0-255 is really pretty meaningless. You shouldn’t be choosing colors that way anyway. I would use a Color3Value myself; the property editor in Studio will take RRGGBB, #RRGGBB, or RRR, GGG, BBB.

1 Like

I replied to you in the other thread here regarding 0-255 since it’s more related to that thread than this one.

Uhhh I think the major point here was that in a semantic sense, mathematical operations in linear space don’t make sense on colors (i.e. halving each channel does not mean you get the color at half the brightness, speaking in gamma space), so the discussion fits here better. The difference between 0-1 and 0-255 is just notation.

(Sure are lots of color threads today)

As a Roblox developer, it is currently inefficient and ugly to make simple number operations across the red, green, and blue segments of Color3.

Currently, if I want to darken a color, i’d have to do something like this:

  • Color3.new(color.r * 0.8, color.g * 0.8, color.b * 0.8)

When instead I could do:

  • color * 0.8

This gets more annoying when using more complicated number offsets, creating something like this:

  • Color3.new(color.r * 0.8 + 0.1, color.g * 0.8 + 0.1, color.b * 0.8 + 0.1)

When it could be this instead:

  • color * 0.8 + 0.1

The numbers would automatically clamp down if higher than 1 or up if lower than 0.

This suggestion isn’t completely necessary, however it would be a great quality of life improvement. I’m interested in how other developers feel about this, so i’m putting a poll below. Thanks!

How do you feel about this suggestion?

  • Love it
  • Like it
  • Indifferent
  • Don’t like it
  • Hate it

0 voters

9 Likes

This is still an issue.

I can’t multiply a Color3 by a number, I have to write a wrapper to do it manually. This seems like something that should be implemented and would benefit both new & experienced devs.

My use case is similar to a tween, I was trying to take a weighted point between two Color3 values.

5 Likes

As a Roblox developer, I want to perform math directly on Color3 values, because it is more convenient than converting between data types.

If Roblox is able to address this issue, it would improve my development experience because there would be no more need to convert Color3s to Vector3s to do simple math on Color3 values.

Color3s and Vector3s are extremely similar but Color3s lack so many of the features that Vector3s have.

I propose that Vector3 math is applied to Color3s values. Additionally, or in place the above, I propose the following methods be added:
Color3 Color3.fromVector3(Vector3 color)
Vector3 Color3:ToVector3()

These methods simply treat r as x, g as y, and b as z.

Examples:

local color = Color3.fromVector3(Vector3.new(255, 127, 0)/255) -- Orange

color = color + Color3.new(0, -0.5, 1) -- Magenta

-- Possibly?
color = color - Vector3.new(0, -0.5, 1) -- Back to orange?

color = color:ToVector3() -- 1, 0.5, 0

This form of Vector3 math/conversion could be used to visualize values and easily convert between color and position. It also allows Roblox to partially merge the functionality of Vector3s and Color3s which can reduce the amount of work needed to be done when updating Color3s.

This form of math is also much easier to type and to read.

-- Before
local colorAddVector = Color3.new(color.r+vector.x, color.g+vector.y, color.b+vector.z)
-- After
local colorAddVector = color+vector
-- Before
local color = Color3.new(0.65, 0.45, 0.55)
color = Color3.new(color.r/2, color.g/2, color.b/2) -- Half as bright
-- After
local color = Color3.new(0.65, 0.45, 0.55)
color = color/2 -- Half as bright
10 Likes

It’s still a pain trying to change Color values. I would like to see support for math operations on Color3s.

There’s a legitimate request here for sure about better methods to work with colors, but this is an A/B problem. Arithmetic operations are certainly not the best solution to providing effective color handling.

Even taking directly from the above post: color = color/2 -- Half as bright

Sounds great… except that it’s not true! The human eye does not handle color intensity linearly, so to have something appear “half as bright” you actually need to apply a gamma correction to the computations, not just a linear multiplier.

This feels like gatekeeping of a problem that doesn’t exist.

Everybody who has requested this has gone on to achieve what they needed, by writing a wrapper. The only difference is we all lost time writing the same code rather than it being built-in. If multiplying a color by 0.5 to get it “half as bright” was a use case & problem, people would still run into this with their wrappers.

Mathematical operators are technically incorrect on unit vectors too, but developers still survive because… people working with math typically understand some math!

6 Likes

IMO the built in solution should push you in the right direction though. Why not provide :Darker(alpha) / :Lighter(alpha) which are actually what people wanted in the first place and do the right thing rather than providing a math construct which makes you do more work for a worse result?

3 Likes

Going to put in my two cents, I’d be for a :Lighter and :Darker method. However, I think that if you were to implement Darker and Lighter methods, we would also need :Saturate and :Desaturate, and possibly :HueShift methods for consistency since (in my experience anyway), I’ve been in both situations (where I’ve needed to de/saturate, and where I need to darken/brighten colours).

To only have :Dark/Lighten and not desaturate/saturate would still be an inconvenience, and then you also run into the issue of needing a custom Saturate/Desaturate func but not a Dark/Lighten method which is just inconsistent, not implementing both would feel like a partial solution. I also think that implementing Dark/Lighten would give the impression that only modifying one channel feels like an arbitrary solution.

However that doesn’t take away from the fact that I do think that math operations should also be implemented, nearly every other data type that houses numbers in some form have it (Vector3, Vector2, Vector3Int16, Vector2Int16, UDim2, UDim, CFrame…, you literally work on the engine so you know this so I’m not sure why I’m listing). To not have Color3 math operations just feels like a feature that is so obvious and should be there for consistency.

To be frank I don’t really think most devs that want a darker colour really take into account the specifics, just would want the colour to be more or less saturated and they can change the number it’s multiplied by if need be. That’s just my opinion tho.

5 Likes

Sure. Fancy methods to darken and lighten a color with the correct gamma adjustment would be nice for simple use cases.

However, Roblox should not spend 4 years designing this magical API that satisfies everyone before doing the bare minimum for a simple datatype that should just be an extension of Vector3 behind the scenes anyway.

Currently, to write your own algorithms involving color transformations or moving between color spaces, you’re better off just using a Vector3 as an intermediary, or working with the RGB components as separate variables and duplicating math across all of them manually. Like a caveman.

If I recall, Vector3s have some optimization behind the scenes for mathematical operations. Where does that leave this classic gist standing in terms of performance? CIELUV Interpolator

8 Likes

I don’t see how it’d bloat API, wouldn’t shader language allow color multiplication and addition and things (tho its done via vec3, vec4, you get it). I have no idea why can’t the same be done to color. I had to multiply color few times and it’s VERY painstaking process, you’d have to extract colorvalues from one color and another color and reconstruct this Color3 value.

1 Like

You don’t see how adding functions for niche purposes bloats the API? :slightly_smiling_face:

Yeah, best not to comment on things we don’t know much about (e.g. how much effort for Roblox to support). I care more about the bloat argument.

Also it should be trivial to write helper functions for these things you need.

This is all I really wish were added. These would function using the normalized RGB values, of course.

Color3 + Color3 -- (R1 + R2, G1 + G2, B1 + B2)
Color3 - Color3 -- (R1 - R2, G1 - G2, B1 - B2)

Color3 * Color3 -- (R1 * R2, G1 * G2, B1 * B2)
Color3 * number -- (R1 * N, G2 * N, B3 * N)

Color3 / Color3 -- (R1 / R2, G1 / G2, B1 / B2)
Color3 / number -- (R1 / N, G1 / N, B1 / N)

Color3:ToRGB -- 0-255 Tuple
Color3:ToNormal -- 0-1 Tuple

All the other stuff about :Lighter/Darker and :HueShift would be cool also, but these above are my main pain points. I shouldn’t have to create a wrapper just to add two colors together.

6 Likes

Having math operations on Color3 would be extremely good and much faster than trying to treat Vector3 values as a Color value to do math on, and then convert said Vector3 value back into a Color3 value.

If Color3 could handle all math operations that Vector3 could do, it’d be perfect:

Color:Dot(Color)
Color:Cross(Color)
Color *= 0.5
etc
6 Likes

Bumping. Honestly surprised this isn’t a thing. You can add/subtract/divide/multiply Vector3s, both Vector3s and Color3s are compatible with TweenService, so it’s very odd that you cannot do math operations with Color3 values.

6 Likes

My full support

4 Likes