I'm trying to draw a Cairo rainbow gradient on a gtk drawing surface. To do this, the RGB color data will be stored in an array and from that array, the rainbow will be drawn. I tried some functions from this website but didn't get the expected result. The script and images are given below:
This is the script:
#include <cairo.h>
#include <gtk/gtk.h>
uint32_t rgb(double ratio)
{
int normalized = (int)(ratio * 256 * 6);
//find the region for this position
int region = normalized / 256;
//find the distance to the start of the closest region
int x = normalized % 256;
uint8_t r = 0, g = 0, b = 0;
switch (region)
{
case 0: r = 255; g = 0; b = 0; g += x; break;
case 1: r = 255; g = 255; b = 0; r -= x; break;
case 2: r = 0; g = 255; b = 0; b += x; break;
case 3: r = 0; g = 255; b = 255; g -= x; break;
case 4: r = 0; g = 0; b = 255; r += x; break;
case 5: r = 255; g = 0; b = 255; b -= x; break;
}
return (r << 16) | (g << 8) | b;
//return rgb value
}
//get red color
uint8_t getRed(uint32_t c) {
return c >> 16;
}
//get green color
uint8_t getGreen(uint32_t c) {
return c >> 8;
}
//get blue color
uint8_t getBlue(uint32_t c) {
return c;
}
//draw
void draw_gradient(cairo_t *);
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data)
{
draw_gradient(cr);
return FALSE;
}
void draw_gradient(cairo_t *cr)
{
int width = 256; //window width
int height = 50; //window height
cairo_pattern_t *gradient;
gradient =cairo_pattern_create_linear(0.0, 0.0, width, 0.0);
double range = width;
for (int i = 0; i < range; i++)
{
uint32_t color = rgb(i / range);//get rgb
printf("colors %d | %d %d %d\n", i, getRed(color), getGreen(color), getBlue(color));
cairo_pattern_add_color_stop_rgba (gradient, i, getRed(color), getGreen(color), getBlue(color), 1.0);
}
cairo_set_source (cr, gradient);
cairo_paint (cr);
cairo_pattern_destroy (gradient);
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER (window), darea);
g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL);
g_signal_connect(G_OBJECT(window), "destroy",G_CALLBACK(gtk_main_quit), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 400, 30);
gtk_window_set_title(GTK_WINDOW(window), "Linear gradients");
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
It only draws a 2 color linear gradient, check the image:
Cairo expects lots of things to be between 0.0 and 1.0, but you are passing in values between 0.0 and 256.0 (the colors) or 0 and width.
Here is a patch that fixes lots of things for me:
--- t.c.orig 2024-06-20 18:01:42.188560553 +0200
+++ t.c 2024-06-20 18:04:05.225964541 +0200
@@ -53,7 +53,7 @@ void draw_gradient(cairo_t *cr)
uint32_t color = rgb(i / range);//get rgb
printf("colors %d | %d %d %d\n", i, getRed(color), getGreen(color), getBlue(color));
- cairo_pattern_add_color_stop_rgba (gradient, i, getRed(color), getGreen(color), getBlue(color), 1.0);
+ cairo_pattern_add_color_stop_rgba (gradient, i / range, getRed(color) / 256.0, getGreen(color) / 256.0, getBlue(color) / 256.0, 1.0);
}
cairo_set_source (cr, gradient);
I do not have GTK headers installed and so I made a cairo-only version of your program.
#include <cairo.h>
#include <stdint.h>
#include <stdio.h>
uint32_t rgb(double ratio)
{
int normalized = (int)(ratio * 256 * 6);
//find the region for this position
int region = normalized / 256;
//find the distance to the start of the closest region
int x = normalized % 256;
uint8_t r = 0, g = 0, b = 0;
switch (region)
{
case 0: r = 255; g = 0; b = 0; g += x; break;
case 1: r = 255; g = 255; b = 0; r -= x; break;
case 2: r = 0; g = 255; b = 0; b += x; break;
case 3: r = 0; g = 255; b = 255; g -= x; break;
case 4: r = 0; g = 0; b = 255; r += x; break;
case 5: r = 255; g = 0; b = 255; b -= x; break;
}
return (r << 16) | (g << 8) | b;
//return rgb value
}
//get red color
uint8_t getRed(uint32_t c) {
return c >> 16;
}
//get green color
uint8_t getGreen(uint32_t c) {
return c >> 8;
}
//get blue color
uint8_t getBlue(uint32_t c) {
return c;
}
//draw
void draw_gradient(cairo_t *cr)
{
int width = 256; //window width
int height = 50; //window height
cairo_pattern_t *gradient;
gradient =cairo_pattern_create_linear(0.0, 0.0, width, 0.0);
double range = width;
for (int i = 0; i < range; i++)
{
uint32_t color = rgb(i / range);//get rgb
printf("colors %d | %d %d %d\n", i, getRed(color), getGreen(color), getBlue(color));
cairo_pattern_add_color_stop_rgba (gradient, i / range, getRed(color) / 256.0, getGreen(color) / 256.0, getBlue(color) / 256.0, 1.0);
}
cairo_set_source (cr, gradient);
cairo_paint (cr);
cairo_pattern_destroy (gradient);
}
int main(int argc, char *argv[])
{
cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 256, 50);
cairo_t *cr = cairo_create(s);
draw_gradient(cr);
cairo_surface_write_to_png(s, "gradient.png");
cairo_surface_destroy(s);
cairo_destroy(cr);
return 0;
}
This still does not look like what you expect it to look like, but that is a question on where you got the rgb()
function from.
Note that this can be greatly simplified. rgb()
looks like it has 6 points and just linearly interpolates between those points. You can just let cairo do that for you: Here is a version that produces the same output and does not use your rgb()
function.
--- t.c.own 2024-06-20 18:06:27.364129263 +0200
+++ t.c 2024-06-20 18:07:36.103342891 +0200
@@ -47,14 +47,12 @@ void draw_gradient(cairo_t *cr)
cairo_pattern_t *gradient;
gradient =cairo_pattern_create_linear(0.0, 0.0, width, 0.0);
- double range = width;
- for (int i = 0; i < range; i++)
-{
- uint32_t color = rgb(i / range);//get rgb
-
- printf("colors %d | %d %d %d\n", i, getRed(color), getGreen(color), getBlue(color));
- cairo_pattern_add_color_stop_rgba (gradient, i / range, getRed(color) / 256.0, getGreen(color) / 256.0, getBlue(color) / 256.0, 1.0);
- }
+ cairo_pattern_add_color_stop_rgb (gradient, 0.0 / 5.0, 1, 0, 0);
+ cairo_pattern_add_color_stop_rgb (gradient, 1.0 / 5.0, 1, 1, 0);
+ cairo_pattern_add_color_stop_rgb (gradient, 2.0 / 5.0, 0, 1, 0);
+ cairo_pattern_add_color_stop_rgb (gradient, 3.0 / 5.0, 0, 1, 1);
+ cairo_pattern_add_color_stop_rgb (gradient, 4.0 / 5.0, 0, 0, 1);
+ cairo_pattern_add_color_stop_rgb (gradient, 5.0 / 5.0, 1, 0, 1);
cairo_set_source (cr, gradient);
cairo_paint (cr);