### Introduce the problem
I have been messing about with doing some testing to …render radial gradients and I believe I may have come up with some code to handle this that isn't all too complicated.
The code written below is in Python. I ported the LVGL code to Python for faster testing purposes and it would be easy to port over to C.
It needs some work but the basic idea is there. This writes the data to a bmp file for viewing.
### Proposal
Here is the code.
```python
import math
import struct
# typedef struct {
# uint8_t blue;
# uint8_t green;
# uint8_t red;
# } lv_color_t;
class lv_color_t:
def __init__(self, red: int, green: int, blue: int):
self.red = red
self.green = green
self.blue = blue
def __str__(self):
return f'lv_color_t(red={self.red}, green={self.green}, blue={self.blue})'
class lv_grad_color_t(lv_color_t):
pass
# typedef struct {
# lv_color_t color; /**< The stop color */
# lv_opa_t opa; /**< The opacity of the color*/
# uint8_t frac; /**< The stop position in 1/255 unit */
# } lv_gradient_stop_t;
class lv_gradient_stop_t:
def __init__(self, color: lv_color_t, opa: int, frac: int):
self.color = color
self.opa = opa
self.frac = frac
# typedef struct _lv_gradient_cache_t {
# lv_color_t * color_map;
# lv_opa_t * opa_map;
# uint32_t size;
# } lv_grad_t;
class lv_grad_t:
def __init__(self):
self.color_map: list[lv_color_t] = []
self.opa_map: list[int] = []
self.size: int = 0
# typedef struct {
# lv_gradient_stop_t stops[LV_GRADIENT_MAX_STOPS];
# uint8_t stops_count;
# lv_grad_dir_t dir : 3;
# } lv_grad_dsc_t;
class lv_grad_dsc_t:
def __init__(self):
self.stops: list[lv_gradient_stop_t] = []
self.stops_count: int = 0
self.dir: int = 0
def GRAD_CONV(t, x):
t.red = x.red
t.green = x.green
t.blue = x.blue
def GRAD_CM(r, g, b):
return lv_color_t(r, g, b)
def LV_UDIV255(x):
return (x * 0x8081) >> 0x17
def lv_gradient_color_calculate(dsc: lv_grad_dsc_t, rnge: int, frac: int, color_out: lv_grad_color_t, opa_out: list[int]):
# Clip out-of-bounds first
mn = (dsc.stops[0].frac * rnge) >> 8
if frac <= mn:
GRAD_CONV(color_out, dsc.stops[0].color)
opa_out.append(dsc.stops[0].opa)
return
mx = dsc.stops[dsc.stops_count - 1].frac / 255.0 * rnge
print(mx, frac, rnge)
if frac >= mx:
GRAD_CONV(color_out, dsc.stops[dsc.stops_count - 1].color)
opa_out.append(dsc.stops[dsc.stops_count - 1].opa)
return
# Find the 2 closest stop now
found_i = 0
for i in range(1, dsc.stops_count, 1):
cur = (dsc.stops[i].frac * rnge) >> 8
if frac <= cur:
found_i = i
break
one = dsc.stops[found_i - 1].color
two = dsc.stops[found_i].color
mn = (dsc.stops[found_i - 1].frac * rnge) >> 8
mx = (dsc.stops[found_i].frac * rnge) >> 8
d = mx - mn
# Then interpolate
frac -= mn
mix = int((frac * 255) / d)
imix = 255 - mix
GRAD_CONV(color_out, GRAD_CM(
LV_UDIV255(two.red * mix + one.red * imix),
LV_UDIV255(two.green * mix + one.green * imix),
LV_UDIV255(two.blue * mix + one.blue * imix)
))
opa_out.append(LV_UDIV255(dsc.stops[found_i].opa * mix + dsc.stops[found_i - 1].opa * imix))
def allocate_item(g: lv_grad_dsc_t, w: int, h: int) -> lv_grad_t:
if g.dir == LV_GRAD_DIR_HOR:
size = w
else:
size = h
item = lv_grad_t()
for _ in range(size):
item.color_map.append(lv_color_t(0, 0, 0))
item.opa_map.append(0)
item.size = size
return item
def lv_gradient_get(g: lv_grad_dsc_t, w: int, h: int) -> lv_grad_t:
# No gradient, no cache
if g.dir == LV_GRAD_DIR_NONE:
return None
# Step 1: Search cache for the given key
item = allocate_item(g, w, h)
# Step 3: Fill it with the gradient, as expected
for i in range(item.size):
opa = []
lv_gradient_color_calculate(g, item.size, i, item.color_map[i], opa)
item.opa_map[i] = opa[0]
return item
LV_GRAD_DIR_NONE = 0
LV_GRAD_DIR_VER = 1
LV_GRAD_DIR_HOR = 2
LV_GRAD_DIR_RADIAL = 3
LV_GRAD_DIR_CONICAL = 4
def bmp_header(o_file, fsize):
o_file.write(bytes("BM", "ascii"))
o_file.write(struct.pack("<I", fsize))
o_file.write(b"\00\x00")
o_file.write(b"\00\x00")
o_file.write(struct.pack("<I", 54))
def bytes_per_row_with_pad(source_width):
pixel_bytes = 3 * source_width
padding_bytes = (4 - (pixel_bytes % 4)) % 4
return pixel_bytes + padding_bytes
def dib_header(o_file, w, h):
o_file.write(struct.pack("<I", 40))
o_file.write(struct.pack("<I", w))
o_file.write(struct.pack("<I", h))
o_file.write(struct.pack("<H", 1))
o_file.write(struct.pack("<H", 24))
for _ in range(24):
o_file.write(b"\x00")
def lv_radial_gradient(radius, dsc: lv_grad_dsc_t, o_file, background_color: lv_color_t = lv_color_t(0, 0, 0)):
img_size_width = radius * 2
img_size_height = radius * 2
dsc.dir = LV_GRAD_DIR_HOR
grad = lv_gradient_get(dsc, radius, 1)
for c in grad.color_map:
print(c)
res = bytearray(img_size_height * bytes_per_row_with_pad(img_size_width))
row_buffer = bytearray(bytes_per_row_with_pad(img_size_width))
print(grad.size)
for y in range(img_size_height, 0, -1):
buffer_index = 0
for x in range(img_size_width):
dist = int(abs(math.sqrt((radius - x) ** 2 + (radius - y) ** 2)))
if dist >= grad.size:
color_rgb = background_color
else:
color_rgb = grad.color_map[~dist]
row_buffer[buffer_index] = color_rgb.blue & 0xFF
buffer_index += 1
row_buffer[buffer_index] = color_rgb.green & 0xFF
buffer_index += 1
row_buffer[buffer_index] = color_rgb.red & 0xFF
buffer_index += 1
o_file.write(row_buffer)
res.extend(row_buffer)
return res
grad_dsc = lv_grad_dsc_t()
grad_dsc.stops.append(lv_gradient_stop_t(lv_color_t(255, 0, 0), 255, 10))
grad_dsc.stops.append(lv_gradient_stop_t(lv_color_t(0, 0, 0), 255, 100))
grad_dsc.stops_count = 2
print("saving starts")
output_file = open(r'radial.bmp', "wb")
filesize = 54 + 400 * bytes_per_row_with_pad(400)
bmp_header(output_file, filesize)
dib_header(output_file, 400, 400)
lv_radial_gradient(200, grad_dsc, output_file)
output_file.close()
print("saving done")
```
and the output looks like this.
![image](https://github.com/lvgl/lvgl/assets/10932297/d837eb3f-efb3-4e76-a9f1-68d2a975a3d0)