Skip to content

Instantly share code, notes, and snippets.

@H2CO3
Created April 6, 2015 11:05
Show Gist options
  • Save H2CO3/b2284aa2f03c8ec9f4ad to your computer and use it in GitHub Desktop.
Save H2CO3/b2284aa2f03c8ec9f4ad to your computer and use it in GitHub Desktop.
Adding gradient drawing primitives to SDL2_gfx
Index: SDL2_gfxPrimitives.c
===================================================================
--- SDL2_gfxPrimitives.c (revision 29)
+++ SDL2_gfxPrimitives.c (working copy)
@@ -3788,3 +3788,288 @@
/* Draw polygon */
return filledPolygonRGBA(renderer, px, py, 4, r, g, b, a);
}
+
+/////// Gradient Drawing //////
+
+#define RMASK 0xff000000
+#define GMASK 0x00ff0000
+#define BMASK 0x0000ff00
+#define AMASK 0x000000ff
+
+#define RGBA32(rv, gv, bv, av) \
+ (((Uint32)(rv) << 24) | ((Uint32)(gv) << 16) | ((Uint32)(bv) << 8) | ((Uint32)(av) << 0))
+
+static SDL_Color interpolate_color(
+ const SDL_ColorStop color_stops[],
+ unsigned n,
+ double p
+)
+{
+ // find which two color stop points 'p' is in between,
+ // i. e. for which 'idx' holds 'p <= stop_points[idx]'
+ unsigned i;
+ for (i = 0; i < n; i++) {
+ if (p <= color_stops[i].progress) {
+ break;
+ }
+ }
+
+ // first, check degenerate cases
+ if (i == 0) {
+ // degenerate case: first color-stop point is
+ // already >= p (probably p is 0 or negative)
+ return color_stops[0].color;
+ } else if (i == n) {
+ // degenerate case: p > maximal color-stop point
+ // (there's no higher color stop point to interpolate
+ // towards, p is probably 1 or more)
+ return color_stops[n - 1].color;
+ }
+
+ // non-degenerate case: p falls somewhere between
+ // two distinct neighboring color-stop points.
+ // Interpolate between these points to find the
+ // appropriate linear combination of colors.
+ double p0 = color_stops[i - 1].progress;
+ double p1 = color_stops[i].progress;
+ double q = (p - p0) / (p1 - p0);
+
+ // return (1 - q) * it[-1].color + q * it[0].color;
+ // https://www.youtube.com/watch?v=LKnqECcg6Gw
+ SDL_Color c0 = color_stops[i - 1].color;
+ SDL_Color c1 = color_stops[i].color;
+ Uint8 r = sqrt(c0.r * c0.r * (1 - q) + c1.r * c1.r * q);
+ Uint8 g = sqrt(c0.g * c0.g * (1 - q) + c1.g * c1.g * q);
+ Uint8 b = sqrt(c0.b * c0.b * (1 - q) + c1.b * c1.b * q);
+ Uint8 a = sqrt(c0.a * c0.a * (1 - q) + c1.a * c1.a * q);
+ return (SDL_Color){ r, g, b, a };
+}
+
+static SDL_Texture *renderPixelBuffer(
+ SDL_Renderer *renderer,
+ Uint32 *buf, // must be non-const, blame SDL_CreateRGBSurfaceFrom
+ int width,
+ int height
+)
+{
+ SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(
+ buf,
+ width,
+ height,
+ 32,
+ width * sizeof buf[0],
+ RMASK,
+ GMASK,
+ BMASK,
+ AMASK
+ );
+
+ SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ return texture;
+}
+
+static int compare_colorstops(const void *p1, const void *p2)
+{
+ const SDL_ColorStop *s1 = p1, *s2 = p2;
+ return (s1->progress > s2->progress) - (s1->progress < s2->progress);
+}
+
+static int intmin(int x, int y)
+{
+ return x < y ? x : y;
+}
+
+
+SDL_Texture *linearGradient(
+ SDL_Renderer *renderer,
+ int w,
+ int h,
+ int vx,
+ int vy,
+ SDL_ColorStop color_stops[],
+ unsigned n
+)
+{
+ if (renderer == NULL) {
+ return NULL;
+ }
+
+ // negative width/height make no sense
+ if (w < 0 || h < 0) {
+ return NULL;
+ }
+
+ // direction vector cannot be null vector
+ if (vx == 0 && vy == 0) {
+ return NULL;
+ }
+
+ // at least 2 color stop points (start, end) are required
+ if (n < 2) {
+ return NULL;
+ }
+
+ // sort color stops with respect to progress
+ qsort(color_stops, n, sizeof color_stops[0], compare_colorstops);
+
+ // Prepare pixel buffer
+ Uint32 *buf = malloc(w * h * sizeof buf[0]);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ Uint32 *bufp = buf;
+
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ // Project point to the 'main' color line crossing the origin.
+ // On this line, the progress 'p' is proportional
+ // to the distance from the origin (1 = the lenght of the diagonal).
+ // 'isochromatic' lines are perpendicular to this line: m_perp = -1 / m
+ int dx, dy;
+ if (vy == 0) {
+ dx = 0;
+ dy = -y;
+ } else if (vx == 0) {
+ // +/- infty
+ dx = -x;
+ dy = 0;
+ } else {
+ double m = (double)vy / vx;
+ dx = (y - m * x) / (m + 1 / m);
+ dy = -1 / m * dx;
+ }
+
+ int xp = x + dx;
+ int yp = y + dy;
+
+ // compute color stop parameter
+ double p;
+ if (abs(vy * dx) < abs(dy * vx)) {
+ p = fabs((double)xp / w);
+ } else {
+ p = fabs((double)yp / h);
+ }
+
+ // interpolate between neighboring color stops
+ SDL_Color c = interpolate_color(color_stops, n, p);
+ *bufp++ = RGBA32(c.r, c.g, c.b, c.a);
+ }
+ }
+
+ // render prepared pixel array
+ SDL_Texture *texture = renderPixelBuffer(
+ renderer,
+ buf,
+ w,
+ h
+ );
+
+ // clean up
+ free(buf);
+
+ return texture;
+}
+
+static SDL_Texture *ellipsoidalGradient(
+ SDL_Renderer *renderer,
+ int rx,
+ int ry,
+ SDL_ColorStop color_stops[],
+ unsigned n,
+ int isRadial
+)
+{
+ if (renderer == NULL) {
+ return NULL;
+ }
+
+ // negative radii make no sense
+ if (rx < 0 || ry < 0) {
+ return NULL;
+ }
+
+ // at least two color stop points must be specified (begin, end)
+ if (n < 2) {
+ return NULL;
+ }
+
+ // sort color stops with respect to progress
+ qsort(color_stops, n, sizeof color_stops[0], compare_colorstops);
+
+ // prepare buffer with transparent pixels
+ Uint32 *buf = calloc(2 * rx * 2 * ry, sizeof buf[0]);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ for (int y = -ry; y < +ry; y++) {
+ for (int x = -rx; x < +rx; x++) {
+ // check if point is within ellipse
+ // r = normalized "radius"
+ double r = sqrt((double)x * x / (rx * rx) + (double)y * y / (ry * ry));
+ if (r > 1) {
+ continue;
+ }
+
+ // for a radial gradient, the color-stop progress is the normalized radius.
+ // for a conical one, it is the normalized (divided-by-two-pi) direction angle.
+ const double pi = 4 * atan(1), tau = 2 * pi;
+ double p = isRadial ? r : (atan2(y, x) + pi) / tau;
+
+ // interpolate between colors
+ SDL_Color c = interpolate_color(color_stops, n, p);
+ buf[2 * rx * (ry + y) + rx + x] = RGBA32(c.r, c.g, c.b, c.a);
+ }
+ }
+
+ // blit pixles at once
+ SDL_Texture *texture = renderPixelBuffer(
+ renderer,
+ buf,
+ 2 * rx,
+ 2 * ry
+ );
+
+ // free pixel buffer
+ free(buf);
+
+ return texture;
+}
+
+SDL_Texture *radialGradient(
+ SDL_Renderer *renderer,
+ int rx,
+ int ry,
+ SDL_ColorStop color_stops[],
+ unsigned n
+)
+{
+ return ellipsoidalGradient(
+ renderer,
+ rx,
+ ry,
+ color_stops,
+ n,
+ 1
+ );
+}
+
+SDL_Texture *conicalGradient(
+ SDL_Renderer *renderer,
+ int rx,
+ int ry,
+ SDL_ColorStop color_stops[],
+ unsigned n
+)
+{
+ return ellipsoidalGradient(
+ renderer,
+ rx,
+ ry,
+ color_stops,
+ n,
+ 0
+ );
+}
Index: SDL2_gfxPrimitives.h
===================================================================
--- SDL2_gfxPrimitives.h (revision 29)
+++ SDL2_gfxPrimitives.h (working copy)
@@ -233,6 +233,39 @@
SDL2_GFXPRIMITIVES_SCOPE int stringColor(SDL_Renderer * renderer, Sint16 x, Sint16 y, const char *s, Uint32 color);
SDL2_GFXPRIMITIVES_SCOPE int stringRGBA(SDL_Renderer * renderer, Sint16 x, Sint16 y, const char *s, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
+ /* Gradients */
+
+ typedef struct SDL_ColorStop {
+ SDL_Color color;
+ double progress;
+ } SDL_ColorStop;
+
+ SDL2_GFXPRIMITIVES_SCOPE SDL_Texture *linearGradient(
+ SDL_Renderer *renderer,
+ int w,
+ int h,
+ int vx,
+ int vy,
+ SDL_ColorStop color_stops[],
+ unsigned n
+ );
+
+ SDL2_GFXPRIMITIVES_SCOPE SDL_Texture *radialGradient(
+ SDL_Renderer *renderer,
+ int rx,
+ int ry,
+ SDL_ColorStop color_stops[],
+ unsigned n
+ );
+
+ SDL2_GFXPRIMITIVES_SCOPE SDL_Texture *conicalGradient(
+ SDL_Renderer *renderer,
+ int rx,
+ int ry,
+ SDL_ColorStop color_stops[],
+ unsigned n
+ );
+
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment