colour icon indicating copy to clipboard operation
colour copied to clipboard

color gradient from one color to black give strange result.

Open LBdN opened this issue 8 years ago • 5 comments

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 ?

LBdN avatar Apr 28 '17 18:04 LBdN

Can you check what hue have your start and end color ? Can you reproduce it in python interactive shell and copy paste here for reproduction ?

vaab avatar Apr 28 '17 23:04 vaab

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>]

LBdN avatar May 02 '17 07:05 LBdN

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.

vaab avatar May 03 '17 02:05 vaab

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

four43 avatar Aug 30 '18 22:08 four43

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

LBdN avatar Feb 26 '21 18:02 LBdN