tuxpaint-pencil-sharpener/magic/src/swirls.c
Bill Kendrick b9e5d59dad New magic tools: Circles, and Rays
Convert the image into circular or ray brushstrokes.

Inspired by "Night Sky Scene [Pen Parallax]" Scratch Project
by -HexaScape- <https://scratch.mit.edu/users/-HexaScape->

Sound effects based on Richard Wagner's "Ride of the Valkyries"
licensed as Creative Commons Attribution 3.0 Unported (CC BY 3.0)
by https://pmmusic.pro/; conducted by Philip Milman.

h/t my son William for pointing out the Scratch project
2023-01-28 23:12:28 -08:00

350 lines
10 KiB
C

/* swirls.c
Transforms parts of the image into brush strokes that
swirl around where you click.
Inspired by "Night Sky Scene [Pen Parallax]" Scratch Project
by -HexaScape- <https://scratch.mit.edu/users/-HexaScape->
Last updated: January 28, 2023
*/
#include <stdio.h>
#include <string.h>
#include <libintl.h>
#include <math.h>
#include "tp_magic_api.h"
#include "SDL_image.h"
#include "SDL_mixer.h"
enum {
SWIRL_TOOL_CIRCLES,
SWIRL_TOOL_RAYS,
NUM_SWIRL_TOOLS
};
char * swirl_names[NUM_SWIRL_TOOLS] = {
gettext_noop("Circles"),
gettext_noop("Rays")
};
char * swirl_descriptions[NUM_SWIRL_TOOLS][2] = {
{
gettext_noop("Click to turn your entire picture into circular brushstrokes."),
gettext_noop("Click and drag to transform parts of your picture to circular brushstrokes.")
},
{
gettext_noop("Click to turn your entire picture into brushstroke rays."),
gettext_noop("Click and drag to transform parts of your picture to brushstroke rays.")
}
};
char * swirl_icon_filenames[NUM_SWIRL_TOOLS] = {
"swirls_circles.png",
"swirls_rays.png"
};
char * swirl_sfx_filenames[NUM_SWIRL_TOOLS] = {
"swirls_circles.ogg",
"swirls_rays.ogg"
};
#define SWIRLS_NUM_STROKES_PER_DRAG_LINE 5
#define SWIRLS_DRAG_LINE_STROKE_RADIUS 64
#define SWIRLS_STROKE_LENGTH 10
Mix_Chunk *snd_effects[NUM_SWIRL_TOOLS];
SDL_Surface * swirls_snapshot = NULL;
int swirls_start_x, swirls_start_y;
Uint32 swirl_stroke_color;
Uint32 swirls_api_version(void);
int swirls_init(magic_api * api);
int swirls_get_tool_count(magic_api * api);
SDL_Surface *swirls_get_icon(magic_api * api, int which);
char *swirls_get_name(magic_api * api, int which);
int swirls_get_group(magic_api * api, int which);
char *swirls_get_description(magic_api * api, int which, int mode);
int swirls_requires_colors(magic_api * api, int which);
int swirls_modes(magic_api * api, int which);
void swirls_shutdown(magic_api * api);
void swirls_click(magic_api * api, int which, int mode,
SDL_Surface * canvas, SDL_Surface * snapshot, int x,
int y, SDL_Rect * update_rect);
void swirls_set_color(magic_api * api, int which, SDL_Surface * canvas,
SDL_Surface * last, Uint8 r, Uint8 g, Uint8 b, SDL_Rect * update_rect);
void swirls_drag(magic_api * api, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int ox, int oy, int x, int y,
SDL_Rect * update_rect);
void swirls_line_callback_drag(void *ptr, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int x, int y);
void swirls_draw_stroke(magic_api * api, int which, SDL_Surface * canvas, int x, int y);
void swirls_line_callback_draw_stroke(void *ptr, int which ATTRIBUTE_UNUSED,
SDL_Surface * canvas,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x, int y);
void swirls_release(magic_api * api, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int x, int y,
SDL_Rect * update_rect);
void swirls_switchin(magic_api * api, int which, int mode,
SDL_Surface * canvas);
void swirls_switchout(magic_api * api, int which, int mode,
SDL_Surface * canvas);
double get_angle(int x, int y, int target_x, int target_y);
Uint32 swirls_api_version(void)
{
return (TP_MAGIC_API_VERSION);
}
int swirls_init(magic_api * api)
{
int i;
char fname[1024];
for (i = 0; i < NUM_SWIRL_TOOLS; i++) {
snprintf(fname, sizeof(fname), "%ssounds/magic/%s",
api->data_directory, swirl_sfx_filenames[i]);
snd_effects[i] = Mix_LoadWAV(fname);
}
return (1);
}
int swirls_get_tool_count(magic_api * api ATTRIBUTE_UNUSED)
{
return (NUM_SWIRL_TOOLS);
}
SDL_Surface *swirls_get_icon(magic_api * api, int which)
{
char fname[1024];
snprintf(fname, sizeof(fname), "%simages/magic/%s",
api->data_directory, swirl_icon_filenames[which]);
return (IMG_Load(fname));
}
char *swirls_get_name(magic_api * api ATTRIBUTE_UNUSED,
int which)
{
return strdup(gettext(swirl_names[which]));
}
int swirls_get_group(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED)
{
return MAGIC_TYPE_DISTORTS;
}
char *swirls_get_description(magic_api * api ATTRIBUTE_UNUSED,
int which, int mode)
{
return strdup(gettext(swirl_descriptions[which][mode]));
}
int swirls_requires_colors(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED)
{
return 0;
}
int swirls_modes(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED)
{
return MODE_PAINT | MODE_FULLSCREEN;
}
void swirls_shutdown(magic_api * api ATTRIBUTE_UNUSED)
{
int i;
for (i = 0; i < NUM_SWIRL_TOOLS; i++) {
if (snd_effects[i] != NULL)
Mix_FreeChunk(snd_effects[i]);
}
}
void
swirls_click(magic_api * api, int which, int mode,
SDL_Surface * canvas, SDL_Surface * snapshot, int x, int y,
SDL_Rect * update_rect)
{
if (snd_effects[which] != NULL)
api->stopsound();
swirls_start_x = x;
swirls_start_y = y;
if (mode == MODE_PAINT) {
swirls_drag(api, which, canvas, snapshot, x, y, x, y, update_rect);
} else {
if (snd_effects[which] != NULL) {
api->playsound(snd_effects[which], (x * 255) / canvas->w, 255);
}
for (x = 0; x < canvas->w; x++) {
for (y = 0; y < canvas->h; y++) {
if (rand() % 100 == 0) {
swirls_draw_stroke(api, which, canvas, x, y);
}
}
}
update_rect->x = 0;
update_rect->y = 0;
update_rect->w = canvas->w;
update_rect->h = canvas->h;
}
}
void
swirls_drag(magic_api * api ATTRIBUTE_UNUSED, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int ox, int oy, int x, int y, SDL_Rect * update_rect)
{
api->line((void *) api, which, canvas, snapshot, ox, oy, x, y, 1 /* FIXME: Consider fewer iterations? */,
swirls_line_callback_drag);
/* FIXME: Would be good to only update the area around the line (ox,oy)->(x,y) (+/- the maxium radius of the effect) */
update_rect->x = 0;
update_rect->y = 0;
update_rect->w = canvas->w;
update_rect->h = canvas->h;
}
void swirls_release(magic_api * api, int which,
SDL_Surface * canvas ATTRIBUTE_UNUSED,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x ATTRIBUTE_UNUSED, int y ATTRIBUTE_UNUSED,
SDL_Rect * update_rect ATTRIBUTE_UNUSED) {
if (snd_effects[which] != NULL)
api->stopsound();
}
void swirls_set_color(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED,
SDL_Surface * last ATTRIBUTE_UNUSED,
Uint8 r ATTRIBUTE_UNUSED, Uint8 g ATTRIBUTE_UNUSED, Uint8 b ATTRIBUTE_UNUSED,
SDL_Rect * update_rect ATTRIBUTE_UNUSED)
{
}
void swirls_line_callback_drag(void *ptr, int which,
SDL_Surface * canvas,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x, int y)
{
int i, ang_deg, radius, nx, ny;
double ang_rad;
magic_api *api = (magic_api *) ptr;
if (snd_effects[which] != NULL)
api->playsound(snd_effects[which], (x * 255) / canvas->w, 255);
for (i = 0; i < SWIRLS_NUM_STROKES_PER_DRAG_LINE; i++) {
ang_deg = (rand() % 360);
ang_rad = (ang_deg * M_PI) / 180.0;
radius = (rand() % (SWIRLS_DRAG_LINE_STROKE_RADIUS * 2)) - SWIRLS_DRAG_LINE_STROKE_RADIUS;
nx = x + (int) (cos(ang_rad) * radius);
ny = y + (int) (sin(ang_rad) * radius);
swirls_draw_stroke(api, which, canvas, nx, ny);
}
}
void swirls_draw_stroke(magic_api * api, int which, SDL_Surface * canvas, int x, int y) {
int x1, y1, x2, y2;
double a;
Uint8 r, g, b;
float h, s, v;
a = get_angle(x, y, swirls_start_x, swirls_start_y);
if (which == SWIRL_TOOL_CIRCLES) {
a = a + (M_PI / 2.0);
}
x1 = x - cos(a) * SWIRLS_STROKE_LENGTH;
y1 = y - sin(a) * SWIRLS_STROKE_LENGTH;
x2 = x + cos(a) * SWIRLS_STROKE_LENGTH;
y2 = y + sin(a) * SWIRLS_STROKE_LENGTH;
swirl_stroke_color = api->getpixel(swirls_snapshot, x, y);
SDL_GetRGB(swirl_stroke_color, canvas->format, &r, &g, &b);
api->rgbtohsv(r, g, b, &h, &s, &v);
h = h + (((rand() % 7) - 3) / 10.0);
if (s > 0.00) {
s = s + (((rand() % 3) - 1) / 10.0);
}
v = v + (((rand() % 3) - 1) / 10.0);
if (h < 0.0) {
h = h - 360.0;
} else if (h >= 360.0) {
h = h - 360.0;
}
if (s < 0.0) {
s = 0.0;
} else if (s > 1.0) {
s = 1.0;
}
if (v < 0.0) {
v = 0.0;
} else if (v > 1.0) {
v = 1.0;
}
api->hsvtorgb(h, s, v, &r, &g, &b);
swirl_stroke_color = SDL_MapRGB(canvas->format, r, g, b);
api->line((void *) api, 0 /* N/A */, canvas, NULL /* N/A */,
x1, y1, x2, y2, 1, swirls_line_callback_draw_stroke);
}
void swirls_line_callback_draw_stroke(void *ptr, int which ATTRIBUTE_UNUSED,
SDL_Surface * canvas,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x, int y) {
magic_api *api = (magic_api *) ptr;
int xx, yy;
for (yy = -1; yy <= 1; yy++) {
for (xx = -1; xx <= 1; xx++) {
api->putpixel(canvas, x + xx, y + yy, swirl_stroke_color);
}
}
}
void swirls_switchin(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas)
{
if (swirls_snapshot == NULL)
swirls_snapshot = SDL_CreateRGBSurface(SDL_SWSURFACE, canvas->w, canvas->h,
canvas->format->BitsPerPixel, canvas->format->Rmask,
canvas->format->Gmask, canvas->format->Bmask,
canvas->format->Amask);
if (swirls_snapshot != NULL)
SDL_BlitSurface(canvas, NULL, swirls_snapshot, NULL);
}
void swirls_switchout(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED,
int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED)
{
}
double get_angle(int x, int y, int target_x, int target_y) {
return atan2((double) (y - target_y), (double) (x - target_x));
}