Author Topic: formula to develop a gradient of a color from Black to max light with RGB system  (Read 3298 times)

0 Members and 1 Guest are viewing this topic.

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Yes I like this effect and the use of _RGBA for black visibility...

I question:
there is a formula to develop a gradient of a color from Black to max light with RGB system?
I try to be clear... if I use this
Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(500, 500, 32)
  2. FOR a = 1 TO 255 STEP 1
  3.     LINE (1 + a, 1)-(2 + a, 300), _RGB(0, 0, a), BF
  4.  
you get a gradient of blue, you can easily do the same with green and with red. But for the other color there is a formula?
Programming isn't difficult, only it's  consuming time and coffee

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Hi. Yes, sure. Take the starting color, for example RGB32 (120, 70, 60) and suppose the end of the color effect should be RGB32 (255, 0, 55). Then determine the number of steps that will lead to the target color, say 50 steps. Then you just compare the input and output values.
So, the first start color of RED is 120. The end is 255. That is a difference of 135, the number of steps to go to 50, so you add 135/50 in one step.
The second GREEN color is 70, the end is 0. The start is less than the target, so you take a -70/50 in one step.
And the third color BLUE turn the same thing. The start is 60, end 55. You add -5/50 in one step.
« Last Edit: April 18, 2019, 03:58:09 pm by Petr »

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Thanks Petr
I'll study and use your info!
Programming isn't difficult, only it's  consuming time and coffee

FellippeHeitor

  • Guest
lerpColor() takes two colors and returns an intermediate value between the two ranging from 0 to 1.

This example shows from black to a shade of orange. I've also used map() to translate screen coordinates into the 0-1 range.

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(500, 500, 32)
  2.  
  3.  
  4. color1 = _RGB32(0) 'black
  5. color2 = _RGB32(200, 116, 67) 'orange
  6.  
  7. FOR i = 0 TO _WIDTH
  8.     LINE (i, 0)-(i, _HEIGHT), lerpColor(color1, color2, map(i, 0, _WIDTH, 0, 1))
  9.  
  10.  
  11. FUNCTION lerpColor~& (c1 AS _UNSIGNED LONG, c2 AS _UNSIGNED LONG, __v!)
  12.     DIM v!
  13.     v! = constrain(__v!, 0, 1)
  14.  
  15.     DIM r1 AS SINGLE, g1 AS SINGLE, b1 AS SINGLE
  16.     DIM r2 AS SINGLE, g2 AS SINGLE, b2 AS SINGLE
  17.     DIM rstep AS SINGLE, gstep AS SINGLE, bstep AS SINGLE
  18.  
  19.     r1 = _RED32(c1)
  20.     g1 = _GREEN32(c1)
  21.     b1 = _BLUE32(c1)
  22.  
  23.     r2 = _RED32(c2)
  24.     g2 = _GREEN32(c2)
  25.     b2 = _BLUE32(c2)
  26.  
  27.     rstep = map(v!, 0, 1, r1, r2)
  28.     gstep = map(v!, 0, 1, g1, g2)
  29.     bstep = map(v!, 0, 1, b1, b2)
  30.  
  31.     lerpColor~& = _RGB32(rstep, gstep, bstep)
  32.  
  33. FUNCTION min! (a!, b!)
  34.     IF a! < b! THEN min! = a! ELSE min! = b!
  35.  
  36. FUNCTION max! (a!, b!)
  37.     IF a! > b! THEN max! = a! ELSE max! = b!
  38.  
  39. FUNCTION constrain! (n!, low!, high!)
  40.     constrain! = max(min(n!, high!), low!)
  41.  
  42. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  43.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  44.  

Same example but now going from a shade of orange to a shade of blue:
Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(500, 500, 32)
  2.  
  3.  
  4. color1 = _RGB32(200, 116, 67) 'orange
  5. color2 = _RGB32(22, 122, 211) 'blue
  6.  
  7. FOR i = 0 TO _WIDTH
  8.     LINE (i, 0)-(i, _HEIGHT), lerpColor(color1, color2, map(i, 0, _WIDTH, 0, 1))
  9.  
  10.  
  11. FUNCTION lerpColor~& (c1 AS _UNSIGNED LONG, c2 AS _UNSIGNED LONG, __v!)
  12.     DIM v!
  13.     v! = constrain(__v!, 0, 1)
  14.  
  15.     DIM r1 AS SINGLE, g1 AS SINGLE, b1 AS SINGLE
  16.     DIM r2 AS SINGLE, g2 AS SINGLE, b2 AS SINGLE
  17.     DIM rstep AS SINGLE, gstep AS SINGLE, bstep AS SINGLE
  18.  
  19.     r1 = _RED32(c1)
  20.     g1 = _GREEN32(c1)
  21.     b1 = _BLUE32(c1)
  22.  
  23.     r2 = _RED32(c2)
  24.     g2 = _GREEN32(c2)
  25.     b2 = _BLUE32(c2)
  26.  
  27.     rstep = map(v!, 0, 1, r1, r2)
  28.     gstep = map(v!, 0, 1, g1, g2)
  29.     bstep = map(v!, 0, 1, b1, b2)
  30.  
  31.     lerpColor~& = _RGB32(rstep, gstep, bstep)
  32.  
  33. FUNCTION min! (a!, b!)
  34.     IF a! < b! THEN min! = a! ELSE min! = b!
  35.  
  36. FUNCTION max! (a!, b!)
  37.     IF a! > b! THEN max! = a! ELSE max! = b!
  38.  
  39. FUNCTION constrain! (n!, low!, high!)
  40.     constrain! = max(min(n!, high!), low!)
  41.  
  42. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  43.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  44.  
« Last Edit: April 18, 2019, 04:50:08 pm by FellippeHeitor »

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3.  
  4. FOR show = 0 TO 3
  5.     EFF _RGB32(RND * 255, RND * 255, RND * 255), _RGB32(RND * 255, RND * 255, RND * 255), 1024, show
  6.     SLEEP 2
  7.     CLS
  8.  
  9.  
  10.  
  11. SUB EFF (color1 AS _UNSIGNED LONG, color2 AS _UNSIGNED LONG, steps AS LONG, efect AS _BYTE)
  12.  
  13.     R1 = _RED32(color1)
  14.     G1 = _GREEN32(color1)
  15.     B1 = _BLUE32(color1)
  16.     A1 = _ALPHA32(color1)
  17.  
  18.     R2 = _RED32(color2)
  19.     G2 = _GREEN32(color2)
  20.     B2 = _BLUE32(color2)
  21.     A2 = _ALPHA32(color2)
  22.     IF R1 < R2 THEN
  23.         sR = R1
  24.         DiffR = (R2 - R1) / steps
  25.     ELSE
  26.         sR = R2
  27.         DiffR = (R1 - R2) / steps
  28.     END IF
  29.     IF G1 < G2 THEN
  30.         sG = G1
  31.         DiffG = (G2 - G1) / steps
  32.     ELSE
  33.         DiffG = (G1 - G2) / steps
  34.         sG = G2
  35.     END IF
  36.     IF B1 < B2 THEN
  37.         sB = B1
  38.         DiffB = (B2 - B1) / steps
  39.     ELSE
  40.         DiffB = (B1 - B2) / steps
  41.         sB = B2
  42.     END IF
  43.     IF A1 < A2 THEN
  44.         sA = A1
  45.         DiffA = (A2 - A1) / steps
  46.     ELSE
  47.         DiffA = (A1 - A2) / steps
  48.         sA = A2
  49.     END IF
  50.     SELECT CASE efect
  51.         CASE 0, 3 'LINE b
  52.             startX = _WIDTH / 2
  53.             startY = _HEIGHT / 2
  54.         CASE 1, 2
  55.             startX = 1
  56.             startY = 1
  57.     END SELECT
  58.  
  59.     FOR D = 1 TO steps
  60.         newcolor~& = _RGBA32((sR + (DiffR * D)), (sG + (DiffG * D)), (sB + (DiffB * D)), (sA + (DiffA * D)))
  61.         SELECT CASE efect
  62.             CASE 0 'LINE B
  63.                 LINE (startX - D, startY - D)-(startX + D, startY + D), newcolor~&, B
  64.  
  65.             CASE 1
  66.                 LINE (startX, startY + D)-(steps, startY + D), newcolor~&
  67.  
  68.             CASE 2
  69.                 LINE (startX + D, startY)-(startX + D, steps), newcolor~&
  70.  
  71.             CASE 3
  72.                 FOR E = 0 TO _PI(2) STEP .001
  73.                     PSET (startX + SIN(E) * D, startY + COS(E) * D), newcolor~&
  74.                 NEXT E
  75.         END SELECT
  76.     NEXT D
  77.  

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
I use my own midInk, input 2 RGB values and the fraction from 0 to 1 you want the blend of the first color towards the 2nd.
Code: QB64: [Select]
  1. _TITLE "midInk test" 'B+ 2019-04-18
  2.  
  3. CONST xmax = 800
  4. CONST ymax = 600
  5. SCREEN _NEWIMAGE(xmax, ymax, 32)
  6.  
  7. horizon% = rand(100, ymax - 100)
  8. FOR i = 0 TO horizon%
  9.     midInk 0, 0, 68, 40, 200, 255, i / horizon%
  10.     LINE (0, i)-(xmax, i)
  11. FOR i = horizon% TO ymax
  12.     midInk 120, 200, 100, 0, 48, 0, (i - horizon%) / (ymax - horizon%)
  13.     LINE (0, i)-(xmax, i)
  14.  
  15. 'from black to white just does greys
  16. FOR i = 0 TO xmax
  17.     midInk 0, 0, 0, 255, 255, 255, i / xmax
  18.     LINE (i, 0)-(i, ymax), , BF
  19.  
  20. FUNCTION rand% (lo%, hi%)
  21.     rand% = INT(RND * (hi% - lo% + 1)) + lo%
  22.  
  23. SUB midInk (r1%, g1%, b1%, r2%, g2%, b2%, fr##)
  24.     COLOR _RGB32(r1% + (r2% - r1%) * fr##, g1% + (g2% - g1%) * fr##, b1% + (b2% - b1%) * fr##)
  25.  
  26.  

EDIT: want _RGB32() not _RGB()
« Last Edit: April 18, 2019, 06:51:18 pm by bplus »

FellippeHeitor

  • Guest
Very concise, bplus! I remember having seen it on previous projects you've shared. Thanks for sharing it again!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Thanks Fellippe, I think it should be _RGB32 for color, it's been awhile since I've looked at the code.

Offline Raven_Singularity

  • Forum Regular
  • Posts: 158
    • View Profile
I just wanted to add a technical note, which is beyond what you were asking, but still interesting.

Original CRT TVs had extremely fine-grained brightness available.  The intensity of photons shot out determine the pixel brightness.  There was no particular upper bound for brightness, it was an analog signal.

Once computer CRTs came out, they eventually adopted RGB gammut with 256 shades of red, green, blue to mix together.  CRT TVs had many thousands of steps of brightness, while CRT monitors only had 256.  The goal was making computer monitors less blurry and more precise.  But the severe limitation of only 256 brightness steps from thousands meant if you based it on a linear scale, 256 equal sized steps, the result is almost no dark shades, and too many bright shades.  To deal with this perceptual brightness issue, the RGB gammut uses more values in the darker ranges (humans can see small shifts in dark colours easily) and less in the bright end.

The problem with a non-linear RGB is if you draw from 0 to 255 brightness, one pixel row at a time, you end up with a gradient that doesn't evenly transition from dark to medium to light.  One hack to get around this is "gamma 2.2" adjustment, which almost matches the real brightness scale.  In this way, a gradient would have less of the dark colours, and more mids and bright tones across the gradient.

Most graphics tools, including Photoshop, fail to handle linear brightness correctly.  Most apps produce poor quality gradients.  Also, when you scale an image down the app combines pixel colour brightnesses together to make the smaller image, but because RGB isn't linear the shrunk image becomes darker than it should actually be.

I'm not offering code to produce a linear gradient on-screen in QB64 -- that's beyond my experience level -- but I can tell you that the gradients being described here do not match an actual linear brightness scale.  There's also other issues with RGB, such as green appearing brighter to humans than red, I think by around ~12%.

I can't wait until all computer monitors and video cards support 10bit or higher linear RGB colourspace (instead of 8bit non-linear).  10bit offers 1024 linear brightness steps per RGB channel, instead of 256 non-linear steps.  The problem with 8bit RGB is it became so prevelant on PCs that nearly every app out there is using it currently, and it's a lot of work converting over all these apps to a new colourspace that operates quite differently.

With gradients in 8bit RGB, the best gradients you can produce use a linear colourspace that then downsample back to 8bit, matching the closest colour brightness value, and making full use of dithering to help perceptually fill in the gaps in-between the RGB brightness steps.  Dithering also removes the visible banding you get with non-linear RGB gradients, which is by far the ugliest aspect of 8bit RGB gradients (especially visible with radial gradients, terrible!).

As an example of an app that correctly handles non-linear RGB colourspace:

ImageWorsener

I always use this app for scaling down large images, it produces much higher quality results than standard image editing apps (Gimp, Photoshop, QB64, etc.).  It doesn't darken the shrunk images, properly handles translucency (opacity in RGBA is 8bits as well, but it is a linear 8bits), and maintains much more of the visible hard lines and details when shrinking.

Probably too much information, but it's fascinated me for the last decade or so!

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Maybe it might be interested, for you, TempodiBasic. It's a simple creation of a color gradient (this is just the beginning of a program to do aged photos by going to yellow at the edges). First I make a mask (edge), then I take it, calculate the transition and throw with the alpha transition to the original photo. It's a simple way.

Use own photo.

Code: QB64: [Select]
  1.  
  2.  
  3. img = _LOADIMAGE("velikonoce.jpg", 32) ' <- insert your photo please
  4.  
  5. W = _WIDTH(img)
  6. H = _HEIGHT(img)
  7. M = _NEWIMAGE(W, H, 32) 'mask
  8.  
  9. X1 = 1 + RND * 240 'left border - line
  10. X2 = 1 + RND * 190
  11.  
  12. LINE (X1, 0)-(X2, H) 'left border
  13. CIRCLE (-50, H / 2), W 'right border
  14.  
  15. DIM V2(H) AS INTEGER
  16.  
  17. FOR search = 1 TO H 'left border
  18.     x = 0
  19.     DO UNTIL POINT(x, search)
  20.         x = x + 1
  21.     LOOP
  22.     V(search) = x
  23.  
  24.  
  25. FOR search = 1 TO H 'right border
  26.     x = W - 1
  27.     DO UNTIL POINT(x, search)
  28.         x = x - 1
  29.     LOOP
  30.     V2(search) = x
  31.  
  32.  
  33. SCREEN M 'now is used as output screen
  34. PRINT "Program use this mask. Press key..."
  35. _PUTIMAGE , img, M
  36.  
  37. FOR y = 1 TO H
  38.     d = 255 / V(y)
  39.     FOR x = 1 TO W
  40.         alpha = 255 - d * x
  41.         PSET (x, y), _RGBA32(255, 255, 0, alpha)
  42. NEXT x, y
  43.  
  44. FOR y = 1 TO H
  45.     d = 255 / (W - V2(y))
  46.     FOR x = W TO 1 STEP -1
  47.         alpha = 255 - d * (W - x)
  48.         PSET (x, y), _RGBA32(255, 255, 0, alpha)
  49. NEXT x, y
  50.  
« Last Edit: April 24, 2019, 02:47:13 pm by Petr »

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
@Petr
fine and interesting this last your post, also if it is not the reason for my question, these are more informations, your previouse response and your code as so also code of Fellippe and code of Bplus have already given to me the answer.
Programming isn't difficult, only it's  consuming time and coffee