Skip to content

Commit

Permalink
Merge pull request #606 from QB64-Phoenix-Edition/hsb-refactoring
Browse files Browse the repository at this point in the history
Refactor HSB color functions
  • Loading branch information
RhoSigma-QB64 authored Feb 2, 2025
2 parents 3965c5e + f06961e commit 094748c
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 145 deletions.
20 changes: 20 additions & 0 deletions internal/c/libqb/include/graphics.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ struct img_struct {
#define IMG_SCREEN 2 // img is linked to other screen pages
#define IMG_FREEMEM 4 // if set, it means memory must be freed

// used by HSB/RGB color conversion routines
struct hsb_color
{
double h; // [0,360] hue
double s; // [0,1] saturation
double b; // [0,1] brightness
};
struct rgb_color
{
double r; // [0,1] red
double g; // [0,1] green
double b; // [0,1] blue
};

/********** Render State **********/
/*
Apart from 'glTexParameter' based settings (with are texture specific)
Expand Down Expand Up @@ -176,6 +190,12 @@ struct hardware_graphics_command_struct {
#define HARDWARE_GRAPHICS_COMMAND__MAPTRIANGLE3D 5
#define HARDWARE_GRAPHICS_COMMAND__CLEAR_DEPTHBUFFER 6

uint32_t func__hsb32(double hue, double sat, double bri);
uint32_t func__hsba32(double hue, double sat, double bri, double alf);
double func__hue32(uint32_t argb);
double func__sat32(uint32_t argb);
double func__bri32(uint32_t argb);

void sub__depthbuffer(int32_t options, int32_t dst, int32_t passed);
void sub__maptriangle(int32_t cull_options, float sx1, float sy1, float sx2, float sy2, float sx3, float sy3, int32_t si, float dx1, float dy1, float dz1,
float dx2, float dy2, float dz2, float dx3, float dy3, float dz3, int32_t di, int32_t smooth_options, int32_t passed);
112 changes: 112 additions & 0 deletions internal/c/libqb/src/graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,118 @@ extern uint8_t *ablend128;
static int32_t depthbuffer_mode0 = DEPTHBUFFER_MODE__ON;
static int32_t depthbuffer_mode1 = DEPTHBUFFER_MODE__ON;

void hsb2rgb(hsb_color *hsb, rgb_color *rgb) {
double hu, hi, hf, pv, qv, tv;

if (hsb->s == 0.0) {
rgb->r = hsb->b; rgb->g = hsb->b; rgb->b = hsb->b; // no saturation = grayscale
} else {
hu = hsb->h / 60.0; // to sixtant [0,5]
if (hu >= 6.0) hu = hu - 6.0;
hf = modf(hu, &hi); // int/frac parts of hue
pv = hsb->b * (1.0 - hsb->s);
qv = hsb->b * (1.0 - (hsb->s * hf));
tv = hsb->b * (1.0 - (hsb->s * (1.0 - hf)));
switch (lround(hi)) {
case 0: {rgb->r = hsb->b; rgb->g = tv; rgb->b = pv; break;} // 0- 60 = Red->Yellow
case 1: {rgb->r = qv; rgb->g = hsb->b; rgb->b = pv; break;} // 60-120 = Yellow->Green
case 2: {rgb->r = pv; rgb->g = hsb->b; rgb->b = tv; break;} // 120-180 = Green->Cyan
case 3: {rgb->r = pv; rgb->g = qv; rgb->b = hsb->b; break;} // 180-240 = Cyan->Blue
case 4: {rgb->r = tv; rgb->g = pv; rgb->b = hsb->b; break;} // 240-300 = Blue->Magenta
case 5: {rgb->r = hsb->b; rgb->g = pv; rgb->b = qv; break;} // 300-360 = Magenta->Red
}
}
}

void rgb2hsb(rgb_color *rgb, hsb_color *hsb) {
double mini, maxi, diff, hu;
// --- find min/max and difference ---
mini = fmin(fmin(rgb->r, rgb->g), rgb->b);
maxi = fmax(fmax(rgb->r, rgb->g), rgb->b);
diff = maxi - mini;
// --- brightness ---
hsb->b = maxi;
// --- saturation (avoid division by zero) ---
maxi != 0.0 ? hsb->s = diff / maxi : hsb->s = 0.0;
// --- hue in degrees ---
if (hsb->s != 0.0) {
if (rgb->r == maxi) {
hu = ((rgb->g - rgb->b) / diff); // between Yellow & Magenta
if (hu < 0.0) hu = hu + 6.0;
} else if (rgb->g == maxi) {
hu = 2.0 + ((rgb->b - rgb->r) / diff); // between Cyan & Yellow
} else {
hu = 4.0 + ((rgb->r - rgb->g) / diff); // between Magenta & Cyan
}
hsb->h = hu * 60.0; // to degrees
} else {
hsb->h = 0.0; // technically there's no hue w/o saturation, commonly used is 0 (red)
}
}

uint32_t func__hsb32(double hue, double sat, double bri) {
hsb_color hsb; rgb_color rgb;
// --- prepare values for conversion ---
(hue < 0.0) ? hsb.h = 0.0 : ((hue > 360.0) ? hsb.h = 360.0 : hsb.h = hue);
(sat < 0.0) ? hsb.s = 0.0 : ((sat > 100.0) ? hsb.s = 100.0 : hsb.s = sat);
(bri < 0.0) ? hsb.b = 0.0 : ((bri > 100.0) ? hsb.b = 100.0 : hsb.b = bri);
hsb.s /= 100.0; hsb.b /= 100.0; // range [0,1]
// --- convert colorspace ---
hsb2rgb(&hsb, &rgb);
// --- build result ---
return ((lround(rgb.r * 255.0) << 16) + (lround(rgb.g * 255.0) << 8) + lround(rgb.b * 255.0)) | 0xFF000000;
}

uint32_t func__hsba32(double hue, double sat, double bri, double alf) {
hsb_color hsb; rgb_color rgb; double alpha;
// --- prepare values for conversion ---
(hue < 0.0) ? hsb.h = 0.0 : ((hue > 360.0) ? hsb.h = 360.0 : hsb.h = hue);
(sat < 0.0) ? hsb.s = 0.0 : ((sat > 100.0) ? hsb.s = 100.0 : hsb.s = sat);
(bri < 0.0) ? hsb.b = 0.0 : ((bri > 100.0) ? hsb.b = 100.0 : hsb.b = bri);
(alf < 0.0) ? alpha = 0.0 : ((alf > 100.0) ? alpha = 100.0 : alpha = alf);
hsb.s /= 100.0; hsb.b /= 100.0; alpha /= 100.0; // range [0,1]
// --- convert colorspace ---
hsb2rgb(&hsb, &rgb);
// --- build result ---
return (lround(alpha * 255.0) << 24) + (lround(rgb.r * 255.0) << 16) + (lround(rgb.g * 255.0) << 8) + lround(rgb.b * 255.0);
}

double func__hue32(uint32_t argb) {
rgb_color rgb; hsb_color hsb;
// --- prepare values for conversion ---
rgb.r = ((argb >> 16) & 0xFF) / 255.0;
rgb.g = ((argb >> 8) & 0xFF) / 255.0;
rgb.b = (argb & 0xFF) / 255.0;
// --- convert colorspace ---
rgb2hsb(&rgb, &hsb);
// --- build result ---
return hsb.h;
}

double func__sat32(uint32_t argb) {
rgb_color rgb; hsb_color hsb;
// --- prepare values for conversion ---
rgb.r = ((argb >> 16) & 0xFF) / 255.0;
rgb.g = ((argb >> 8) & 0xFF) / 255.0;
rgb.b = (argb & 0xFF) / 255.0;
// --- convert colorspace ---
rgb2hsb(&rgb, &hsb);
// --- build result ---
return hsb.s * 100.0;
}

double func__bri32(uint32_t argb) {
rgb_color rgb; hsb_color hsb;
// --- prepare values for conversion ---
rgb.r = ((argb >> 16) & 0xFF) / 255.0;
rgb.g = ((argb >> 8) & 0xFF) / 255.0;
rgb.b = (argb & 0xFF) / 255.0;
// --- convert colorspace ---
rgb2hsb(&rgb, &hsb);
// --- build result ---
return hsb.b * 100.0;
}

void sub__depthbuffer(int32_t options, int32_t dst, int32_t passed) {
// {ON|OFF|LOCK|_CLEAR}

Expand Down
145 changes: 0 additions & 145 deletions internal/support/include/afterlastline.bm
Original file line number Diff line number Diff line change
Expand Up @@ -75,84 +75,6 @@ FUNCTION _IKW_Clamp## (_value##, _minVal##, _maxVal##)
_CLAMP = _IIF(_value## < _realMin##, _realMin##, _IIF(_value## > _realMax##, _realMax##, _value##))
END FUNCTION

'_HSB32() returns the 32-bit ARGB color value build from the given HSB values,
' always with full alpha (opaque colors).
'---------- [0,360] [0,100] [0,100]
FUNCTION _IKW_HSB32~& (_hue#, _saturation#, _brightness#)
'--- option _explicit requirements ---
DIM _hu#, _sa#, _br#, _re#, _gr#, _bl#
'--- prepare values for conversion ---
_hu# = _CLAMP(_hue#, 0, 360)
_sa# = _CLAMP(_saturation#, 0, 100) / 100
_br# = _CLAMP(_brightness#, 0, 100) / 100
'--- convert colorspace ---
_HSBtoRGB _hu#, _sa#, _br#, _re#, _gr#, _bl#
'--- build result ---
_HSB32 = _RGB32(CINT(_re# * 255), CINT(_gr# * 255), CINT(_bl# * 255))
END FUNCTION

'_HSBA32() returns the 32-bit ARGB color value build from the given HSB values,
' using 0-100% alpha as given.
'---------- [0,360] [0,100] [0,100] [0,100]
FUNCTION _IKW_HSBA32~& (_hue#, _saturation#, _brightness#, _alfa#)
'--- option _explicit requirements ---
DIM _hu#, _sa#, _br#, _al#, _re#, _gr#, _bl#
'--- prepare values for conversion ---
_hu# = _CLAMP(_hue#, 0, 360)
_sa# = _CLAMP(_saturation#, 0, 100) / 100
_br# = _CLAMP(_brightness#, 0, 100) / 100
_al# = _CLAMP(_alfa#, 0, 100) / 100
'--- convert colorspace ---
_HSBtoRGB _hu#, _sa#, _br#, _re#, _gr#, _bl#
'--- build result ---
_HSBA32 = _RGBA32(CINT(_re# * 255), CINT(_gr# * 255), CINT(_bl# * 255), CINT(_al# * 255))
END FUNCTION

'_HUE32() returns the hue [0,360] of the given 32-bit ARGB color value.
'----------
FUNCTION _IKW_Hue32# (_argbColor~&)
'--- option _explicit requirements ---
DIM _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- prepare values for conversion ---
_re# = _RED32(_argbColor~&) / 255
_gr# = _GREEN32(_argbColor~&) / 255
_bl# = _BLUE32(_argbColor~&) / 255
'--- convert colorspace ---
_RGBtoHSB _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- build result ---
_HUE32 = _hu#
END FUNCTION

'_SATURATION32() returns the saturation [0,100] of the given 32-bit ARGB color value.
'----------
FUNCTION _IKW_Saturation32# (_argbColor~&)
'--- option _explicit requirements ---
DIM _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- prepare values for conversion ---
_re# = _RED32(_argbColor~&) / 255
_gr# = _GREEN32(_argbColor~&) / 255
_bl# = _BLUE32(_argbColor~&) / 255
'--- convert colorspace ---
_RGBtoHSB _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- build result ---
_SATURATION32 = _sa# * 100
END FUNCTION

'_BRIGHTNESS32() returns the brightness [0,100] of the given 32-bit ARGB color value.
'----------
FUNCTION _IKW_Brightness32# (_argbColor~&)
'--- option _explicit requirements ---
DIM _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- prepare values for conversion ---
_re# = _RED32(_argbColor~&) / 255
_gr# = _GREEN32(_argbColor~&) / 255
_bl# = _BLUE32(_argbColor~&) / 255
'--- convert colorspace ---
_RGBtoHSB _re#, _gr#, _bl#, _hu#, _sa#, _br#
'--- build result ---
_BRIGHTNESS32 = _br# * 100
END FUNCTION

'_EncodeURL$() returns the percent encoded version of the given plain text
' URL for use with the _OPENCLIENT("HTTP:url") syntax.
'----------
Expand Down Expand Up @@ -222,73 +144,6 @@ END FUNCTION
' rule #2 its names must begin with an underscore.
'==========================================================================

'_HSBtoRGB() converts colors from the HSB into the RGB colorspace. Note that
' HSB (B=Brightness) is also known as HSV (V=Value) and sometimes called
' HSI (I=Intensity), but that's just different names for the same thing.
' However, this SUB is not suitable for the HSL (L=Lightness) colorspace.
'----------
' IN: hu# = [0,360], sa# = [0,1], br# = [0,1]
'OUT: re#, gr#, bl# values in range [0,1] (through arguments)
'----------
SUB _HSBtoRGB (_hu#, _sa#, _br#, _re#, _gr#, _bl#)
'--- option _explicit requirements ---
DIM _hi%, _hf#, _pv#, _qv#, _tv#
'--- compute RGB ---
IF _sa# = 0 THEN
_re# = _br#: _gr# = _br#: _bl# = _br# 'no saturation = grayscale
ELSE
_hu# = _hu# / 60 ' 'to sixtant [0,5]
IF _hu# >= 6 THEN _hu# = _hu# - 6
_hi% = INT(_hu#): _hf# = _hu# - _hi% 'int/frac parts of hue
_pv# = _br# * (1 - _sa#)
_qv# = _br# * (1 - (_sa# * _hf#))
_tv# = _br# * (1 - (_sa# * (1 - _hf#)))
SELECT CASE _hi%
CASE 0: _re# = _br#: _gr# = _tv#: _bl# = _pv# ' 0- 60 = Red->Yellow
CASE 1: _re# = _qv#: _gr# = _br#: _bl# = _pv# ' 60-120 = Yellow->Green
CASE 2: _re# = _pv#: _gr# = _br#: _bl# = _tv# '120-180 = Green->Cyan
CASE 3: _re# = _pv#: _gr# = _qv#: _bl# = _br# '180-240 = Cyan->Blue
CASE 4: _re# = _tv#: _gr# = _pv#: _bl# = _br# '240-300 = Blue->Magenta
CASE 5: _re# = _br#: _gr# = _pv#: _bl# = _qv# '300-360 = Magenta->Red
END SELECT
END IF
END SUB

'_RGBtoHSB() converts colors from the RGB into the HSB colorspace. Note that
' HSB (B=Brightness) is also known as HSV (V=Value) and sometimes called
' HSI (I=Intensity), but that's just different names for the same thing.
' However, this SUB is not suitable for the HSL (L=Lightness) colorspace.
'----------
' IN: re#, gr#, bl# values in range [0,1]
'OUT: hu# = [0,360], sa# = [0,1], br# = [0,1] (through arguments)
'----------
SUB _RGBtoHSB (_re#, _gr#, _bl#, _hu#, _sa#, _br#)
'--- option _explicit requirements ---
DIM _mini#, _maxi#, _diff#
'--- find min/max and difference ---
_mini# = _MIN(_MIN(_re#, _gr#), _bl#)
_maxi# = _MAX(_MAX(_re#, _gr#), _bl#)
_diff# = _maxi# - _mini#
'--- brightness ---
_br# = _maxi#
'--- saturation (avoid division by zero) ---
IF _maxi# <> 0 THEN _sa# = _diff# / _maxi# ELSE _sa# = 0
'--- hue in degrees ---
IF _sa# <> 0 THEN
IF _re# = _maxi# THEN
_hu# = ((_gr# - _bl#) / _diff#) ' 'between Yellow & Magenta
IF _hu# < 0 THEN _hu# = _hu# + 6
ELSEIF _gr# = _maxi# THEN
_hu# = 2 + ((_bl# - _re#) / _diff#) 'between Cyan & Yellow
ELSE
_hu# = 4 + ((_re# - _gr#) / _diff#) 'between Magenta & Cyan
END IF
_hu# = _hu# * 60 'to degrees
ELSE
_hu# = 0 'technically there's no hue w/o saturation, commonly used is 0 (red)
END IF
END SUB

$IF _SOCKETS_ THEN
'_WhatIsMyIP$() is a helper function required by _CONNECTIONADDRESS$,
' but only required when network stuff is compiled in (_SOCKETS_).
Expand Down
50 changes: 50 additions & 0 deletions source/subs_functions/subs_functions.bas
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,56 @@ SUB reginternal
id.hr_syntax = "_ALPHA32(rgbaColor&)"
regid
clearid
id.n = "_HSB32"
id.subfunc = 1
id.callname = "func__hsb32"
id.args = 3
id.arg = MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER)
id.ret = ULONGTYPE - ISPOINTER
id.hr_syntax = "_HSB32(hue#, saturation#, brightness#)"
regid
clearid
id.n = "_HSBA32"
id.subfunc = 1
id.callname = "func__hsba32"
id.args = 4
id.arg = MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER)
id.ret = ULONGTYPE - ISPOINTER
id.hr_syntax = "_HSBA32(hue#, saturation#, brightness#, alpha#)"
regid
clearid
id.n = "_Hue32"
id.subfunc = 1
id.callname = "func__hue32"
id.args = 1
id.arg = MKL$(ULONGTYPE - ISPOINTER)
id.ret = DOUBLETYPE - ISPOINTER
id.hr_syntax = "_HUE32(rgbaColor&)"
regid
clearid
id.n = "_Saturation32"
id.subfunc = 1
id.callname = "func__sat32"
id.args = 1
id.arg = MKL$(ULONGTYPE - ISPOINTER)
id.ret = DOUBLETYPE - ISPOINTER
id.hr_syntax = "_SATURATION32(rgbaColor&)"
regid
clearid
id.n = "_Brightness32"
id.subfunc = 1
id.callname = "func__bri32"
id.args = 1
id.arg = MKL$(ULONGTYPE - ISPOINTER)
id.ret = DOUBLETYPE - ISPOINTER
id.hr_syntax = "_BRIGHTNESS32(rgbaColor&)"
regid
clearid
id.n = "Draw"
id.subfunc = 2
Expand Down

0 comments on commit 094748c

Please sign in to comment.