Skip to content

Commit 30f9d8f

Browse files
committed
FEAT: new natives rgb-to-hsv and hsv-to-rgb
These natives were in Rebol2 and so why not to have them in Rebol3 again. Could be enhanced in future for batch conversion and or more precise version not depending on tuple value for HSV. Implements wish: metaeducation/rebol-issues#2342
1 parent 5eb7fb2 commit 30f9d8f

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

src/core/n-image.c

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/***********************************************************************
2+
**
3+
** REBOL [R3] Language Interpreter and Run-time Environment
4+
**
5+
** Copyright 2012 REBOL Technologies
6+
** Copyright 2012-2019 Rebol Open Source Contributors
7+
** REBOL is a trademark of REBOL Technologies
8+
**
9+
** Licensed under the Apache License, Version 2.0 (the "License");
10+
** you may not use this file except in compliance with the License.
11+
** You may obtain a copy of the License at
12+
**
13+
** http://www.apache.org/licenses/LICENSE-2.0
14+
**
15+
** Unless required by applicable law or agreed to in writing, software
16+
** distributed under the License is distributed on an "AS IS" BASIS,
17+
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
** See the License for the specific language governing permissions and
19+
** limitations under the License.
20+
**
21+
************************************************************************
22+
**
23+
** Module: n-image.c
24+
** Summary: image related native functions
25+
** Section: natives
26+
** Author: Oldes
27+
** Notes:
28+
**
29+
***********************************************************************/
30+
31+
#include "sys-core.h"
32+
33+
typedef struct REBCLR {
34+
union {
35+
unsigned char r;
36+
unsigned char h;
37+
};
38+
union {
39+
unsigned char g;
40+
unsigned char s;
41+
};
42+
union {
43+
unsigned char b;
44+
unsigned char v;
45+
};
46+
unsigned char a;
47+
} REBCLR;
48+
49+
// Conversion using raw tuple bytes is (a little bit) faster but less precise
50+
// using decimals in conversions is producing results closer to Rebol2
51+
#define HSV_CONVERSION_USING_DECIMAL
52+
// Currently HSW is using tuple datatype, which is limited to range 0-255
53+
// That is also adding some precision lost for round-trip conversions.
54+
55+
// Code based on: https://stackoverflow.com/q/3018313/494472
56+
57+
/***********************************************************************
58+
**
59+
*/ REBNATIVE(hsv_to_rgb)
60+
/*
61+
// hsv-to-rgb: native [
62+
// "Converts HSV (hue, saturation, value) to RGB"
63+
// hsv [tuple!]
64+
// ]
65+
***********************************************************************/
66+
{
67+
REBCLR *val = (REBCLR*)VAL_TUPLE(D_ARG(1));
68+
if (val->s == 0) {
69+
// achromatic (grey)
70+
val->r = val->v;
71+
val->g = val->v;
72+
val->b = val->v;
73+
return R_ARG1;
74+
}
75+
#ifdef HSV_CONVERSION_USING_DECIMAL
76+
77+
REBDEC h = (float)val->h;
78+
REBDEC s = (float)val->s / 255.0;
79+
REBDEC v = (float)val->v / 255.0;
80+
81+
int i;
82+
REBDEC f, p, q, t;
83+
84+
h /= 42.5; // sector 0 to 5; we are using 255 instead of 360 because the value is in tuple's byte ! (255 / 6)
85+
i = floor(h);
86+
f = h - i; // factorial part of h
87+
p = v * (1 - s);
88+
q = v * (1 - s * f);
89+
t = v * (1 - s * (1 - f));
90+
switch (i) {
91+
case 0: val->r = (REBYTE)(255.0 * v); val->g = (REBYTE)(255.0 * t); val->b = (REBYTE)(255.0 * p); break;
92+
case 1: val->r = (REBYTE)(255.0 * q); val->g = (REBYTE)(255.0 * v); val->b = (REBYTE)(255.0 * p); break;
93+
case 2: val->r = (REBYTE)(255.0 * p); val->g = (REBYTE)(255.0 * v); val->b = (REBYTE)(255.0 * t); break;
94+
case 3: val->r = (REBYTE)(255.0 * p); val->g = (REBYTE)(255.0 * q); val->b = (REBYTE)(255.0 * v); break;
95+
case 4: val->r = (REBYTE)(255.0 * t); val->g = (REBYTE)(255.0 * p); val->b = (REBYTE)(255.0 * v); break;
96+
default: val->r = (REBYTE)(255.0 * v); val->g = (REBYTE)(255.0 * p); val->b = (REBYTE)(255.0 * q); break; // case 5
97+
}
98+
#else
99+
REBYTE region, remainder, p, q, t;
100+
101+
region = val->h / 43; // six regions: 255 / 6
102+
remainder = (val->h - (region * 43)) * 6;
103+
104+
p = (val->v * (255 - val->s)) >> 8;
105+
q = (val->v * (255 - ((val->s * remainder) >> 8))) >> 8;
106+
t = (val->v * (255 - ((val->s * (255 - remainder)) >> 8))) >> 8;
107+
108+
switch (region) {
109+
case 0: val->r = val->v; val->g = t; val->b = p; break;
110+
case 1: val->r = q; val->g = val->v; val->b = p; break;
111+
case 2: val->r = p; val->g = val->v; val->b = t; break;
112+
case 3: val->r = p; val->g = q; val->b = val->v; break;
113+
case 4: val->r = t; val->g = p; val->b = val->v; break;
114+
default: // case 5
115+
val->r = val->v; val->g = p; val->b = q;
116+
break;
117+
}
118+
#endif
119+
return R_ARG1;
120+
}
121+
122+
/***********************************************************************
123+
**
124+
*/ REBNATIVE(rgb_to_hsv)
125+
/*
126+
// rgb-to-hsv: native [
127+
// "Converts RGB value to HSV (hue, saturation, value)"
128+
// rgb [tuple!]
129+
// ]
130+
***********************************************************************/
131+
{
132+
REBCLR *val = (REBCLR*)VAL_TUPLE(D_ARG(1));
133+
REBYTE rgbMin, rgbMax, r, g, b;
134+
135+
r = val->r;
136+
g = val->g;
137+
b = val->b;
138+
139+
rgbMax = r > g ? (r > b ? r : b) : (g > b ? g : b);
140+
rgbMin = r < g ? (r < b ? r : b) : (g < b ? g : b);
141+
142+
val->v = rgbMax;
143+
if (val->v == 0 || rgbMax == rgbMin) {
144+
val->h = val->s = 0;
145+
return R_ARG1;
146+
}
147+
148+
#ifdef HSV_CONVERSION_USING_DECIMAL
149+
REBDEC delta = rgbMax - rgbMin;
150+
151+
val->s = (REBYTE)(255.0 * delta / rgbMax);
152+
if (rgbMax == r)
153+
val->h = (REBYTE)(42.5 * (g - b) / delta);
154+
else if (rgbMax == g)
155+
val->h = (REBYTE)(85.0 + 42.5 * (b - r) / delta);
156+
else
157+
val->h = (REBYTE)(170.0 + 42.5 * (r - g) / delta);
158+
159+
#else
160+
REBINT delta = rgbMax - rgbMin;
161+
val->s = (REBYTE)(255 * delta / rgbMax);
162+
163+
if (rgbMax == r)
164+
val->h = 0 + 43 * (g - b) / delta;
165+
else if (rgbMax == g)
166+
val->h = 85 + 43 * (b - r) / delta;
167+
else
168+
val->h = 171 + 43 * (r - g) / delta;
169+
#endif
170+
171+
return R_ARG1;
172+
}

src/tests/run-tests.r3

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dt [ ;- delta time
2929
wrap load %units/series-test.r3
3030
wrap load %units/compress-test.r3
3131
wrap load %units/date-test.r3
32+
wrap load %units/image-test.r3
3233

3334
recycle/torture
3435
recycle

src/tests/units/image-test.r3

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Rebol [
2+
Title: "Rebol image test script"
3+
Author: "Oldes"
4+
File: %image-test.r3
5+
Tabs: 4
6+
Needs: [%../quick-test-module.r3]
7+
]
8+
9+
~~~start-file~~~ "Image"
10+
11+
===start-group=== "RGB - HSV conversions"
12+
;@@ https://github.com/rebol/rebol-issues/issues/2342
13+
14+
--test-- "RGB-TO-HSV"
15+
--assert 36.235.134 = rgb-to-hsv 134.116.10
16+
--assert 36.235.134.100 = rgb-to-hsv 134.116.10.100
17+
--assert 0.0.134 = rgb-to-hsv 134.134.134
18+
--assert 42.253.134 = rgb-to-hsv 134.134.1
19+
--test-- "HSV-TO-RGB"
20+
--assert 5.9.10 = hsv-to-rgb 134.116.10
21+
--assert 5.9.10.100 = hsv-to-rgb 134.116.10.100
22+
--assert 63.123.134 = hsv-to-rgb 134.134.134
23+
--assert 0.1.2 = hsv-to-rgb 134.134.2
24+
25+
===end-group===
26+
27+
28+
~~~end-file~~~

src/tools/file-base.r

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ core: [
5252
n-control.c
5353
n-crypt.c
5454
n-data.c
55+
n-image.c
5556
n-io.c
5657
n-loop.c
5758
n-math.c

0 commit comments

Comments
 (0)