Created
April 6, 2015 11:05
-
-
Save H2CO3/b2284aa2f03c8ec9f4ad to your computer and use it in GitHub Desktop.
Adding gradient drawing primitives to SDL2_gfx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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