Skip to content

color gradient from one color to black give strange result. #32

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

Open
LBdN opened this issue Apr 28, 2017 · 5 comments
Open

color gradient from one color to black give strange result. #32

LBdN opened this issue Apr 28, 2017 · 5 comments

Comments

@LBdN
Copy link

LBdN commented Apr 28, 2017

When building a gradient from a color to black, the gradient go through many other hues, instead of going simply darker. It looks quite unnatural.
Wouldn't it be possible to treat white and black as special cases ?

@vaab
Copy link
Owner

vaab commented Apr 28, 2017 via email

@LBdN
Copy link
Author

LBdN commented May 2, 2017

ok, I dig a bit and the case is more rare than I thought.
It doesn't happen from a normal gradient.
It does happen when the end color of the gradient is a very dark version of the start color. I use the following code to transform one into the other :

def light_color(color, percentage, darken ):
    color = Color(color)
    if darken:
        new_lum = ( 1 - percentage/100.0) * color.luminance
    else:
        new_lum = ( 1 + percentage/100.0) * color.luminance
    color.luminance = max(min(new_lum,1), 0)
    return {"color" : [color]}

If the darkening percentage is very high, I get this kind of result :

selection_053

in the console, I have this result :

    Engine    inputs
    Engine      0: [<Color #df1c50>]
    Engine    results
    Engine      color:[<Color black>]

@vaab
Copy link
Owner

vaab commented May 3, 2017

May I share with you a few hints that might help us find the culprit...

  • in the current version of Color (in master branch, tag 0.1.4), the Color object will store the HSL value only and will do all the conversion for set/get any attributes. so:

    • .hsl or .hue, .saturation, .luminance are actually direct access to internal stored data.
    • the color scaling tools provided by colour (namely .range_to(..) method and color_scale(..) function) are using HSL format internally also.
  • In HSL, it is the .hue value that will give you the color of the object in the rainbow. Focusing on this attribute should show you a changing hue if you notice changing in color. Note that hue is a cyclic value: 0 is equal to 1.

    • so if you want to progessively go from a hue of 0 to a hue of 1 (which are equal and both red) using .range_to(..) or color_scale(..) means you'll ask to span hue across 0-1, which means you'll want to go through all colors.
    • and changing only .luminance should only make a fixed color brighter. At 0 it is all dark (it is black for all colors), and at 1 it is all light (white for all colors). This mean that you can have a black red and a black blue for instance that are both black (aka #000 in RGB). In that sense, HSL to RGB is not a bijection.

On your last message, it is not clear for me if the image you pasted is the result of using .range_to(..) (or color_scale(..) or if it is your code that was used to get this result. In the latter case, this is indeed quite a bit puzzling.

To go forward on this topic, I'll suggest you to:

  • print the .hue value in debugging logs
  • nail down a small full python interactive log showing me the problem: this will help you sort out this problem, and I'll be able to work on it on my side.

Many thanks for your interest in colour. If you have time also, you might want to check out this branch that is sketching the future of Colour : #31 ... I still need some reviews that it doesn't break anything.

@four43
Copy link

four43 commented Aug 30, 2018

I just came across this! Sorry if this is an older issue, but I have some code to reproduce:

from colour import Color

start = Color("#da0000")
print("start HSL: " + str(start.hsl))
end = Color("#7a0009")
print("end HSL: " + str(end.hsl))
for color in list(start.range_to(end, 10)):
    print(color.hsl)
start HSL: (0.0, 1.0, 0.42745098039215684)
end HSL: (0.9877049180327868, 1.0, 0.23921568627450981)
(0.0, 1.0, 0.42745098039215684)
(0.10974499089253187, 1.0, 0.4065359477124183)
(0.21948998178506374, 1.0, 0.3856209150326797)
(0.3292349726775956, 1.0, 0.36470588235294116)
(0.4389799635701275, 1.0, 0.3437908496732026)
(0.5487249544626593, 1.0, 0.32287581699346407)
(0.6584699453551912, 1.0, 0.3019607843137255)
(0.7682149362477231, 1.0, 0.28104575163398693)
(0.877959927140255, 1.0, 0.2601307189542484)
(0.9877049180327868, 1.0, 0.23921568627450984)

It's a red to a sort of dark red, but we go through the whole hue range before we end back up a little shifted from where we started. I think it's due to the wrapping at 0/360. Hue isn't linear, it's circle so the color_scale function might need to be more complex to handle that.

This rainbow type effect might be desired behavior for some, I would have liked to take the shortest path however. I could see this being implemented as a flag like force_shortest=True or something as to not break existing functionality but to allow crossing that hue border.
image

colour version 0.1.5

@LBdN
Copy link
Author

LBdN commented Feb 26, 2021

Hi @vaab, I solved the pb on my cython version, so you can try on your end.
Note the color instance returned by the delta method is the shortest distance in the color space between self and other.
It is not really a valid color, more a delta hence the name. But it serves it purpose correctly. :)

cpdef add(self, Color other):
      cdef float h = self.h + other.h
      if (h < <float>0): h += <float>1
      if (h > <float>1): h -= <float>1
      return Color(
         h = h,
         s = self.s + other.s,
         l = self.l + other.l,
         a = self.a + other.a,
         temp = True
         )
   cpdef delta(self, Color other):
      cdef float d1      = self.h - other.h    
      cdef float d2_sign = 1 if d1 < 0 else -1         
      cdef float d2 = <float>1.0 - abs(d1)
      cdef float d 
      if abs(d2) < abs(d1):
         d = d2_sign * d2
      else:
         d = d1
      return Color(
         h = d,
         s = self.s - other.s,
         l = self.l - other.l,
         a = self.a - other.a,
         temp = True
         )

image

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

No branches or pull requests

3 participants