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

Fixes incorrect color brightness check #9677

Merged
merged 5 commits into from
Nov 27, 2023

Conversation

EvilDragonfiend
Copy link
Member

@EvilDragonfiend EvilDragonfiend commented Aug 14, 2023

About The Pull Request

Fixes incorrect color brightness check

color_hex2num() proc is incredibly bad because the mix of 3 colors does not mean anything. Hue? Saturation? Brightness? All wrong.
This PR fixes where this incorrect color calculation is used at by adding new procs get_color_brightness_from_hex() and get_color_saturation_from_hex()

image

  • Reference: color calculation
    V: Brightness
    S: Saturation
    H: Hue (not really needed)

Why It's Good For The Game

Correct color calculation

Testing Photographs and Procedure

image

red jumpsuit

image

red SM

image

colourful lights

Changelog

🆑
code: better color calculation code
fix: single pure colors (red, green, blue) will not be considered to be too dark.
/:cl:

@itsmeow
Copy link
Member

itsmeow commented Aug 14, 2023

We already have a proc that does this, is_color_dark. We should either replace that or adapt it for this use

Copy link
Member

@PowerfulBacon PowerfulBacon left a comment

Choose a reason for hiding this comment

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

Use or replace is_color_dark

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 1, 2023

I tested some, I don't like how rgb2num(color, COLORSPACE_HSL)[3] calculates brightness level. I failed to find brightness consistency from is_color_dark() proc.
It evaluates color-value more dark when it has color, This means it allows more dark color to mono(from black-white) while doesn't allow high saturation(from black-colored)

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 1, 2023

This is my code to test color values

#define hex2num(X) text2num(X, 16)


/// returns HSV brightness 0 to 100 by color hex
/proc/get_color_brightness_from_hex(A)
    if(!A || length(A) != length_char(A))
        return 0
    var/R = hex2num(copytext(A, 2, 4))
    var/G = hex2num(copytext(A, 4, 6))
    var/B = hex2num(copytext(A, 6, 8))
    return round(max(R, G, B)/2.55, 1)

/// returns HSV brightness 0 to 100 by color hex
/proc/get_color_saturation_from_hex(A)
    if(!A || length(A) != length_char(A))
        return 0
    var/R = hex2num(copytext(A, 2, 4))
    var/G = hex2num(copytext(A, 4, 6))
    var/B = hex2num(copytext(A, 6, 8))
    var/brightness = max(R, G, B)
    if(brightness == 0)
        return 0

    return round((brightness - min(R, G, B))/brightness*100, 1)

/proc/is_color_dark(color)
    var/hsl = rgb2num(color, COLORSPACE_HSL)
    return hsl[3]

/proc/main()
    var/col = "#550000"
    world.log << col
    world.log << "satur: [get_color_saturation_from_hex(col)]"
    world.log << "bright: [get_color_brightness_from_hex(col)]"
    world.log << "isdark: [is_color_dark(col)]"
    world.log << "---------------------------"
    col = "#330000"
    world.log << col
    world.log << "satur: [get_color_saturation_from_hex(col)]"
    world.log << "bright: [get_color_brightness_from_hex(col)]"
    world.log << "isdark: [is_color_dark(col)]"
    world.log << "---------------------------"
    col = "#333333"
    world.log << col
    world.log << "satur: [get_color_saturation_from_hex(col)]"
    world.log << "bright: [get_color_brightness_from_hex(col)]"
    world.log << "isdark: [is_color_dark(col)]"
    world.log << "---------------------------"
    col = "#552a2a"
    world.log << col
    world.log << "satur: [get_color_saturation_from_hex(col)]"
    world.log << "bright: [get_color_brightness_from_hex(col)]"
    world.log << "isdark: [is_color_dark(col)]"
    col = "#ff0000"
    world.log << col
    world.log << "satur: [get_color_saturation_from_hex(col)]"
    world.log << "bright: [get_color_brightness_from_hex(col)]"
    world.log << "isdark: [is_color_dark(col)]"
    world.log << "---------------------------"

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 1, 2023

image

this is the difference (alike)
I prefer saturation is not being checked from is_dark() for brightness.

@PowerfulBacon
Copy link
Member

All dark colour checking should use a single and consistent proc

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 2, 2023

I will do as this way

#define BRIGHTNESS_BYOND_COLOR "brightness_byond_color"
#define BRIGHTNESS_HSV_PURE "brightness_hsv_pure"
/proc/is_color_dark(color_code, threshold = 25, method=BRIGHTNESS_BYOND_COLOR)
    switch(method)
        if(BRIGHTNESS_BYOND_COLOR)
            var/hsl = rgb2num(color, COLORSPACE_HSL)
            return hsl[3] < threshold
        if(BRIGHTNESS_HSV_PURE)
            var/brightness
            ..// does some calculation here
            return brightness < threshold

I still don't like how byond calculates brightness at its internal proc.

@PowerfulBacon
Copy link
Member

If the old method is flawed, why even have it. Just have a single and consistent method for checking if the colour is dark. Any uses of the old function are wrong if it doesn't work

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 3, 2023

If the old method is flawed, why even have it. Just have a single and consistent method for checking if the colour is dark. Any uses of the old function are wrong if it doesn't work

image

it's what I showed to you. The perspective to judge if the color is dark or not is different. The byond way works but it's NOT how I don't expect it's working as because saturation interferes brightness in its calculation.
"is_dark()" proc can be interpreted as that it allows more dark color when it has no saturation and I exactly do NOT like that part.

@itsmeow
Copy link
Member

itsmeow commented Sep 3, 2023

In BYOND color uses matrix multiplication. This effectively means the more saturated something is the darker it is tinted. This is exactly as expected since most color application uses matrix multiply. This also depends on the base color. A gray vs white

@EvilDragonfiend
Copy link
Member Author

In BYOND color uses matrix multiplication. This effectively means the more saturated something is the darker it is tinted. This is exactly as expected since most color application uses matrix multiply. This also depends on the base color. A gray vs white

Yes, I know. but that's the exact part I don't like.

@itsmeow
Copy link
Member

itsmeow commented Sep 3, 2023

Yea but we need this. For color picking

@EvilDragonfiend
Copy link
Member Author

EvilDragonfiend commented Sep 3, 2023

Yea but we need this. For color picking

That doesn't make sense in color picking even because
let's say the threshold is "25". You can simply understand the threshold is 25 when it's mono-color. but once you get saturation, threshold for brightness becomes 33 or 43 somewhere and you need to check every saturation how brightness allowance is changed.

image

The top image is how it works. the left side is simple to understand, but by saturation change, 'is_dark()` requires you to use higher brightness value in the color picker.

@PowerfulBacon
Copy link
Member

is_color_dark should return true if the colour looks dark and false if the colour doesn't look dark. The method it uses doesn't matter but I still don't see why it can't be consistent. The use case shouldn't affect the method for getting the percieved darkness of the colour.

@itsmeow
Copy link
Member

itsmeow commented Sep 6, 2023

The old method is slightly confusing but accurate to the game as more saturated colors ARE darker in some cases.

var/R = hex2num(copytext(A, 2, 4))
var/G = hex2num(copytext(A, 4, 6))
var/B = hex2num(copytext(A, 6, 8))
return round(max(R, G, B)/2.55, 1)
Copy link
Member

Choose a reason for hiding this comment

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

don't simplify the constant expression, it's confusing. do / 255 / 100

Copy link
Member

Choose a reason for hiding this comment

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

/ 255 / 100 is a lot more confusing imo as it is ambiguous if this means (x / 255) / 100 or x / (255 / 100) which could either be x/25500 or x/2.55. In the expression requested, this would be equivalent to x/25500 and not x/2.55.

Copy link
Member Author

@EvilDragonfiend EvilDragonfiend Sep 21, 2023

Choose a reason for hiding this comment

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

It should be x / (255 / 100) or (x / 255) * 100, but I am not a fan of neither. I will put a comment instead.

@EvilDragonfiend
Copy link
Member Author

updated. Now the proc has a method parameter.

return get_color_brightness_from_hex(hex_string, method) < threshold

/// returns HSV brightness 0 to 100 by color hex
/proc/get_color_brightness_from_hex(hex_string, method=BRIGHTNESS_BYOND_COLOR)
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need different methods? I don't care if you change how existing things work since if they are broken and you are making new procs to fix it then just change it. Switching different methods is the same as making new functions. I don't see why there has to be at all a difference in how we are checking these.

Copy link
Member Author

@EvilDragonfiend EvilDragonfiend Nov 2, 2023

Choose a reason for hiding this comment

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

There are 2 color check system in the code currently.

  • what Kat implemented. (AKA "kat color check")
  • weird color checking in the spray code (AKA "bad color check")

What I was requested to do was to change "Bad color check" to "Kat color check" but it's what I don't agree with. Kat color check isn't wrong and it's working as intended, but It's not still what I think it's correct to replace "Bad color check", and I've said the reasons above.
The only way to fix it here I believe is to calculate it differently rather than "Kat color check", and it's "Dragon color check" which doesn't count saturation to brightness.
That's why I am putting two methods for checking colors.

Copy link
Member

@PowerfulBacon PowerfulBacon Nov 20, 2023

Choose a reason for hiding this comment

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

There should be 2 different procs then, is_color_dark and is_color_unsaturated. Is colour dark should only check if a colour visually looks dark, which is colours that have a low bright ness and low saturation.

I dislike procs that have 2 different behaviours for the same thing with an enum passed in as a parameter to determine how the proc should function. They should just be different procs if they are going to behave differently.

Copy link
Member Author

@EvilDragonfiend EvilDragonfiend Nov 27, 2023

Choose a reason for hiding this comment

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

Both of those should be used to check if a color is dark because both needs to check brightness, but byond one needs brightness + saturation while mine don't use saturation - only brightness.
your suggestion is_color_unsaturated() is not correct because this exactly means it's used for the old is_color_dark() as it uses saturation, but mine don't use it.

I made changes here to make two procs: is_color_dark_with_saturation() and is_color_dark_without_saturation().
The old is_dark() is now is_color_dark_with_saturation(), and my proc is is_color_dark_without_saturation()

@itsmeow itsmeow added this pull request to the merge queue Nov 27, 2023
Merged via the queue into BeeStation:master with commit b320f97 Nov 27, 2023
8 checks passed
@EvilDragonfiend EvilDragonfiend deleted the colorfix branch November 27, 2023 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Not sorted
Development

Successfully merging this pull request may close these issues.

3 participants