Author Topic: Problem with HSL to RGB function  (Read 3362 times)

0 Members and 1 Guest are viewing this topic.

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Problem with HSL to RGB function
« on: February 12, 2020, 05:50:12 pm »
I've plugged the HSL to RGB colorspace conversion formula into a function, and tested it by drawing a strip from hue 0 upwards, staying at full saturation and half lightness.  Instead of a smooth rainbow, I got six blocks of color, so it seems there's rounding going on somewhere and I can't find it.

Code: QB64: [Select]
  1. screen _newimage(640, 480, 32)
  2.  
  3. for hue = 0 to 239
  4.    line(hue, 0)-step(0, 100), hsl&(hue, 240, 120)
  5. next hue
  6.  
  7. function hsl&(h, s, l)
  8.  
  9. v = 240
  10. l = l / v
  11. c = (1 - abs((2 * l) - 1)) * (s / v)
  12. x = c * (1 - abs(((h / (v / 6)) mod 2) - 1))
  13. m = l - (c / 2)
  14.  
  15. r = x: g = x: b = x
  16. v1 = v / 6
  17. if h  <     v1 or  h => 5 * v1 then r = c
  18. if h =>     v1 and h  < 3 * v1 then g = c
  19. if h => 3 * v1 and h  < 5 * v1 then b = c
  20. if h  < 2 * v1                 then b = 0
  21. if h => 2 * v1 and h  < 4 * v1 then r = 0
  22. if h => 4 * v1                 then g = 0
  23.  
  24. hsl& = _rgb((r + m) * 255, (g + m) * 255, (b + m) * 255)
  25.  
  26.  

I'm using HSL as values from 0 to 240, like mspaint's color picker does, but this is arbitrary and could easily be changed to work on some other scale by changing the value of v.

Here's my source for the formula:

https://www.rapidtables.com/convert/color/hsl-to-rgb.html

FellippeHeitor

  • Guest
Re: Problem with HSL to RGB function
« Reply #1 on: February 12, 2020, 07:09:27 pm »
This one adapted from http://stackoverflow.com/questions/4106363/converting-rgb-to-hsb-colors also takes an alpha parameter (values range from 0 to 360 - for hue - and from 0 to 255 for saturation, brightness and alpha):

Code: QB64: [Select]
  1. FUNCTION hsb~& (__H AS _FLOAT, __S AS _FLOAT, __B AS _FLOAT, A AS _FLOAT)
  2.     DIM H AS _FLOAT, S AS _FLOAT, B AS _FLOAT
  3.  
  4.     H = __H
  5.     S = map(__S, 0, 255, 0, 1)
  6.     B = map(__B, 0, 255, 0, 1)
  7.  
  8.     IF S = 0 THEN
  9.         hsb~& = _RGBA32(B * 255, B * 255, B * 255, A)
  10.         EXIT FUNCTION
  11.     END IF
  12.  
  13.     DIM fmx AS _FLOAT, fmn AS _FLOAT
  14.     DIM fmd AS _FLOAT, iSextant AS INTEGER
  15.     DIM imx AS INTEGER, imd AS INTEGER, imn AS INTEGER
  16.  
  17.     IF B > .5 THEN
  18.         fmx = B - (B * S) + S
  19.         fmn = B + (B * S) - S
  20.     ELSE
  21.         fmx = B + (B * S)
  22.         fmn = B - (B * S)
  23.     END IF
  24.  
  25.     iSextant = INT(H / 60)
  26.  
  27.     IF H >= 300 THEN
  28.         H = H - 360
  29.     END IF
  30.  
  31.     H = H / 60
  32.     H = H - (2 * INT(((iSextant + 1) MOD 6) / 2))
  33.  
  34.     IF iSextant MOD 2 = 0 THEN
  35.         fmd = (H * (fmx - fmn)) + fmn
  36.     ELSE
  37.         fmd = fmn - (H * (fmx - fmn))
  38.     END IF
  39.  
  40.     imx = _ROUND(fmx * 255)
  41.     imd = _ROUND(fmd * 255)
  42.     imn = _ROUND(fmn * 255)
  43.  
  44.     SELECT CASE INT(iSextant)
  45.         CASE 1
  46.             hsb~& = _RGBA32(imd, imx, imn, A)
  47.         CASE 2
  48.             hsb~& = _RGBA32(imn, imx, imd, A)
  49.         CASE 3
  50.             hsb~& = _RGBA32(imn, imd, imx, A)
  51.         CASE 4
  52.             hsb~& = _RGBA32(imd, imn, imx, A)
  53.         CASE 5
  54.             hsb~& = _RGBA32(imx, imn, imd, A)
  55.         CASE ELSE
  56.             hsb~& = _RGBA32(imx, imd, imn, A)
  57.     END SELECT
  58.  
  59.  
  60. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  61.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!

  [ You are not allowed to view this attachment ]  
« Last Edit: February 13, 2020, 09:02:47 am by FellippeHeitor »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Problem with HSL to RGB function
« Reply #2 on: February 13, 2020, 02:27:27 am »
In alternative you may try my converthelper.bm library, it's in the IMG-Support folder of my QB64 Library collection which you can download from the Bonus Stuff Section here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

This library contains raw converting routines between RGB/HSB and vise versa, but also some convenience FUNCTIONs, which are in symmetry with the respective RGB colorspace functions provided by QB64 itself:

RGB32 <--> HSB32
RGBA32 <--> HSBA32

RED32 <--> HUE32
GREEN32 <--> SAT32
BLUE32 <--> BRI32

Documentation and examples are also provided in the archive.

EDIT:
Make sure to move the extracted QB64Library folder with its entire contents into your QB64 folder (where qb64.exe is in). You find an overview of all libraries in the QB64Library-Info.html file.
« Last Edit: February 13, 2020, 07:47:42 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Re: Problem with HSL to RGB function
« Reply #3 on: February 14, 2020, 11:50:45 pm »
Those are cool and helpful and all, and thanks... but if you don't mind, it's clear I made a mistake somewhere in here, and if possible I'd like to figure out what it is.  I'll take another comb through it when I get a chance, and try to compare to your function when I can.  Speaking of which, why's it bleeding past red back into yellow and then red and then yellow again?

FellippeHeitor

  • Guest
Re: Problem with HSL to RGB function
« Reply #4 on: February 14, 2020, 11:55:35 pm »
I took the screenshot before I fixed the code to post. It was still taking 255 for hue and I did the loop up to 360. The version of the code I posted will produce a slightly different output.

Also I do apologize: it is a bad habit of many of us to try to shove our own code when someone asks for help. Please don't interpret it as arrogance. In this case specifically it is my inability to debug your code, especially since I didn't translate mine from a math formula, like you did, but merely from another programming language, which was much easier than the task you set out to do.

I hope it is at least helpful as a comparison source.
« Last Edit: February 15, 2020, 12:00:24 am by FellippeHeitor »

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Re: Problem with HSL to RGB function
« Reply #5 on: February 17, 2020, 01:58:31 pm »
I found part of the problem, but there's still some math to fix.  I was assuming the mod operator would return a floating point value, and QB64's mod operator specifically only returns integers.  So I just coded my own function, and now I'm getting gradual changes, but still not quite correct.

Code: QB64: [Select]
  1. screen _newimage(640, 480, 32)
  2.  
  3. for hue = 0 to 240
  4.    for lum = 0 to 240
  5.       pset(hue, 240 - lum), hsl&(hue, 240, lum)
  6.    next lum
  7. next hue
  8.  
  9.  
  10. function hsl&(h1, s, l1)
  11.  
  12. v = 240
  13. h = h1 / (v / 6)
  14. l = l1 / v
  15. c = (1 - abs((2 * l) - 1)) * (s / v)
  16. x = c * (1 - abs(mod_dec(h, 2) - 1))
  17. m = l - (c / 2)
  18.  
  19. c = (c + m) * 255: x = (x + m) * 255: z = m * 255
  20.    case 0
  21.       hsl& = _rgb(c, x, z)
  22.    case 1
  23.       hsl& = _rgb(x, c, z)
  24.    case 2
  25.       hsl& = _rgb(z, c, x)
  26.    case 3
  27.       hsl& = _rgb(z, x, c)
  28.    case 4
  29.       hsl& = _rgb(x, z, c)
  30.    case else
  31.       hsl& = _rgb(c, z, x)
  32.  
  33.  
  34.  
  35. function mod_dec(n, d)
  36. mod_dec = (n / abs(d)) - int(n / abs(d))
  37.  

This is producing gradient bands separated by a sharp edge, because the x value is rising from 0 to 0.5 in the red-to-yellow part, then continuing from 0.5 to 1 in the yellow-to-green part.  The secondary color component is rising from 0 to 1 across twice the range it should be, and starting abruptly again at 0 instead of traveling gradually back down.  So I know what needs to happen from here.  Thankfully, saturation and luminance are both behaving correctly.

However, I've been picking apart the math for an hour and am getting a headache, and my formula matches both the page I linked and the wikipedia article on conversion, so I'm taking a break for now.  If anyone wants to check this out and see if they can correct the math before me, have at it!
« Last Edit: February 17, 2020, 01:59:54 pm by johannhowitzer »

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Re: Problem with HSL to RGB function
« Reply #6 on: February 21, 2020, 01:47:50 pm »
Found the error.  It wasn't in the HSL function itself - I'd just made a simple mistake in coding my own mod function.  My function mod_dec() in the above post compares the division result to the division result rounded, in order to get the remainder - however, after this, I neglected to multiply this remainder by the divisor.  Example: in the case of 8 mod 3, the result should be 2, but as I was taking the difference of (8 / 3) and int(8 / 3), I would first get a result of 2/3.  So here's the corrected function:

Code: QB64: [Select]
  1. FUNCTION mod_dec(n, d)
  2. mod_dec = ((n / d) - INT(n / d)) * d

The HSL to RGB function now works correctly.  My first mistake was assuming QB64's mod operator preserved decimals, and my second was in coding my own mod function, I forgot to multiply by the divisor at the end.  Always check your functions.  :)

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Problem with HSL to RGB function
« Reply #7 on: February 21, 2020, 02:14:07 pm »
Hi johannhowitzer,

That's pretty nice. I fooled around with rainbow range and could get either to Black or to White but not both.


Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Re: Problem with HSL to RGB function
« Reply #8 on: February 21, 2020, 08:56:13 pm »
I'm not sure I understand what you mean.  What variables did you change in the input?

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Problem with HSL to RGB function
« Reply #9 on: February 21, 2020, 09:22:50 pm »
Just trying to get a color picker with ranges top dark, bottom light. Forget about hsl standing for anything except h for hue:

Code: QB64: [Select]
  1. _TITLE "Rainbow dark to light" 'b+ 2020-02-16
  2. CONST xmax = 800, ymax = 600
  3. SCREEN _NEWIMAGE(xmax, ymax, 32)
  4. _SCREENMOVE 300, 40
  5. FOR hue = 0 TO 360
  6.     FOR gray = 0 TO 1 STEP .01
  7.         PSET (hue, gray * 100), hsl2rgb~&(hue, gray, gray)
  8.     NEXT
  9.  
  10. FUNCTION hsl2rgb~& (h, w, c) ' h = hue from 0 o 360, w = white from 0 (black) to 1 (white)
  11.     ' h range 0 to 360
  12.     y = w * 360
  13.     d = c * 280
  14.     fraction = h / 360
  15.     radians = fraction * 2 * _PI + _PI(1 / 6)
  16.     r = SIN(radians) * y + d
  17.     g = SIN(radians - 2 / 3 * _PI) * y + d
  18.     b = SIN(radians + 2 / 3 * _PI) * y + d
  19.     hsl2rgb~& = _RGB32(r, g, b)
  20.  
  21.  

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
Re: Problem with HSL to RGB function
« Reply #10 on: February 21, 2020, 09:46:57 pm »
OK, couple things...

- That's not the HSL function I wrote, if you're trying to get eyes on debugging your own function, it might be more helpful to start a new topic, and I'm not sure from looking at that function what it's supposed to do, so I might not be able to help you.

- I'm using QB64 1.0, I should probably update because I bet _PI is in a newer version, and my version doesn't like that it has two different argument setups.

- Seems weird that you're asking about light / dark when you're passing the "gray" value into two parameters of your function.  It doesn't look like you're doing anything at all with lightness here.  If you're trying to vertically invert everything visually, try subtracting the y coordinate of pset from the y coordinate you want as the bottom edge.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Problem with HSL to RGB function
« Reply #11 on: February 21, 2020, 11:58:25 pm »
This is what I was going for:
Code: QB64: [Select]
  1. _TITLE "Color picker" 'b+ 2020-02-21
  2. CONST xmax = 800, ymax = 600
  3. SCREEN _NEWIMAGE(xmax, ymax, 32)
  4. _SCREENMOVE 300, 40
  5. hueMult = 1 / xmax
  6. grayMult = 1 / ymax
  7. 'PRINT hueMult, grayMult
  8. WHILE _KEYDOWN(27) = 0
  9.     FOR gray = 0 TO ymax
  10.         FOR hue = 0 TO xmax
  11.             PSET (hue, gray), shade~&(hue * hueMult, gray * grayMult)
  12.         NEXT
  13.     NEXT
  14.     mx = _MOUSEX: my = _MOUSEY
  15.     IF mx <= xmax / 2 THEN px = mx + 20 ELSE px = mx - 100
  16.     IF my <= ymax / 2 THEN py = my + 36 ELSE py = my - 36
  17.     _PRINTSTRING (px, py), "RGB" + STR$(_RED32(POINT(mx, my))) + "," + STR$(_GREEN32(POINT(mx, my))) + "," + STR$(_BLUE32(POINT(mx, my)))
  18.     _DISPLAY
  19.     _LIMIT 60
  20.  
  21. FUNCTION shade~& (h, gray) ' hue 0 to 1, gray scale 0 = black to 1 = white eg .5 = middle gray
  22.     hue~& = rainbow~&(h)
  23.     r% = _RED32(hue~&): g% = _GREEN32(hue~&): b% = _BLUE32(hue~&)
  24.     IF gray < .5 THEN
  25.         shade~& = midInk~&(0, 0, 0, r%, g%, b%, 2 * gray)
  26.     ELSEIF gray > .5 THEN
  27.         shade~& = midInk~&(r%, g%, b%, 255, 255, 255, 2 * (gray - .5))
  28.     ELSEIF gray = .5 THEN
  29.         shade~& = hue~&
  30.     END IF
  31.  
  32. FUNCTION midInk~& (r1%, g1%, b1%, r2%, g2%, b2%, fr##)
  33.     midInk~& = _RGB32(r1% + (r2% - r1%) * fr##, g1% + (g2% - g1%) * fr##, b1% + (b2% - b1%) * fr##)
  34.  
  35. FUNCTION rainbow~& (fraction)
  36.     radians = fraction * 2 * _PI
  37.     r = SIN(radians) * 127 + 128
  38.     g = SIN(radians - 2 / 3 * _PI) * 127 + 128
  39.     b = SIN(radians + 2 / 3 * _PI) * 127 + 128
  40.     rainbow~& = _RGB32(r, g, b)
  41.  
  42.