WIP "Bloom" magic effect. Mend crash in "perspective.c"

This commit is contained in:
Bill Kendrick 2023-02-06 02:22:21 -08:00
parent 65da4f4df4
commit 948158c1fd
4 changed files with 412 additions and 13 deletions

View file

@ -7,7 +7,7 @@ Various contributors (see below, and AUTHORS.txt)
https://tuxpaint.org/ https://tuxpaint.org/
2022.January.30 (0.9.29) 2023.February.6 (0.9.29)
* Improvements to "Stamp" tool: * Improvements to "Stamp" tool:
----------------------------- -----------------------------
* Stamps may now be rotated. * Stamps may now be rotated.
@ -32,8 +32,7 @@ https://tuxpaint.org/
(Inspired by "Night Sky Scene [Pen Parallax]" Scratch Project (Inspired by "Night Sky Scene [Pen Parallax]" Scratch Project
by -HexaScape- <https://scratch.mit.edu/users/-HexaScape->) by -HexaScape- <https://scratch.mit.edu/users/-HexaScape->)
[WIP] - Fur sound effect needed [WIP] - Fur icon needed
[WIP] - Fur sound icon needed
("Circles" and "Rays" sound effects based on Richard Wagner's ("Circles" and "Rays" sound effects based on Richard Wagner's
"Ride of the Valkyries", recording licensed as Creative Commons "Ride of the Valkyries", recording licensed as Creative Commons
@ -43,6 +42,12 @@ https://tuxpaint.org/
("Fur" sound effected licensed as Creative Commons 0 (CC0 1.0) ("Fur" sound effected licensed as Creative Commons 0 (CC0 1.0)
by https://freesound.org/people/Kawgrim/) by https://freesound.org/people/Kawgrim/)
* "Bloom" - Apply a glowing light bloom effect to the image.
Bill Kendrick <bill@newbreedsoftware.com>
[WIP] - Bloom icon needed
[WIP] - Bloom sound effect needed
* [WIP] "Rivulet"; apply rivulets of water to the canvas * [WIP] "Rivulet"; apply rivulets of water to the canvas
- needs better icon - needs better icon
- needs sound effect - needs sound effect
@ -167,6 +172,10 @@ https://tuxpaint.org/
(h/t Miyagi Andel for reporting & Shin-ichi TOYAMA for pinpointing (h/t Miyagi Andel for reporting & Shin-ichi TOYAMA for pinpointing
the issue) the issue)
* Mend crash bug in "perspective.c" Magic tools.
(Perspective, Panels, Timezoom, Zoom, Rush)
Bill Kendrick <bill@newbreedsoftware.com>
* Disallow using Ctrl-Z shortcut while drawing or rotating shapes * Disallow using Ctrl-Z shortcut while drawing or rotating shapes
or shapes, to avoid unexpected results. or shapes, to avoid unexpected results.
Closes https://sourceforge.net/p/tuxpaint/bugs/239/ Closes https://sourceforge.net/p/tuxpaint/bugs/239/

BIN
magic/icons/bloom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

367
magic/src/bloom.c Normal file
View file

@ -0,0 +1,367 @@
/* bloom.c
Applies a "bloom" effect to the image.
(https://en.wikipedia.org/wiki/Bloom_(shader_effect))
Last updated: February 6, 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"
/* Radius of the painting tool */
#define BLOOM_PAINT_RADIUS 24
/* Overall weight to apply the sampled pixels */
#define BLOOM_WEIGHT_CONST 0.025
/* Length of spike shape */
#define BLOOM_SPIKE_LENGTH 5
/* From https://www.shadertoy.com/view/lsXGWn */
//float sample_weights[9] = {
// 0.05, 0.09, 0.12, 0.15, 0.16, 0.15, 0.12, 0.09, 0.05
//};
/* Take N digits consecutive digits (1-n), calculate SIN() of
* (PI/(N+1))*n, take the SQRT(), and scale by a constant
* so they all SUM() to approx. 1.0000 */
#define NUM_SAMPLE_WEIGHTS 13
float sample_weights[NUM_SAMPLE_WEIGHTS] = {
0.0449, 0.0627, 0.0752, 0.0842, 0.0904, 0.0940, 0.0952, 0.0940, 0.0904, 0.0842, 0.0752, 0.0627, 0.0449
};
Mix_Chunk * snd_effects = NULL;
Uint8 * bloom_mask = NULL;
int bloom_scale;
Uint32 bloom_api_version(void);
int bloom_init(magic_api * api);
int bloom_get_tool_count(magic_api * api);
SDL_Surface *bloom_get_icon(magic_api * api, int which);
char *bloom_get_name(magic_api * api, int which);
int bloom_get_group(magic_api * api, int which);
char *bloom_get_description(magic_api * api, int which, int mode);
int bloom_requires_colors(magic_api * api, int which);
int bloom_modes(magic_api * api, int which);
void bloom_shutdown(magic_api * api);
void bloom_click(magic_api * api, int which, int mode,
SDL_Surface * canvas, SDL_Surface * snapshot, int x,
int y, SDL_Rect * update_rect);
void bloom_set_color(magic_api * api, int which, SDL_Surface * canvas,
SDL_Surface * last, Uint8 r, Uint8 g, Uint8 b, SDL_Rect * update_rect);
void bloom_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 bloom_line_callback_drag(void *ptr, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int x, int y);
void bloom_release(magic_api * api, int which, SDL_Surface * canvas,
SDL_Surface * snapshot, int x, int y,
SDL_Rect * update_rect);
void bloom_switchin(magic_api * api, int which, int mode,
SDL_Surface * canvas);
void bloom_switchout(magic_api * api, int which, int mode,
SDL_Surface * canvas);
float luminance(float r, float g, float b);
float change_luminance(float c_in, float l_in, float l_out);
Uint32 bloom_api_version(void)
{
return (TP_MAGIC_API_VERSION);
}
int bloom_init(magic_api * api)
{
char fname[1024];
snprintf(fname, sizeof(fname), "%ssounds/magic/bloom.ogg",
api->data_directory);
snd_effects = NULL; /* FIXME Mix_LoadWAV(fname); */
bloom_scale = sqrt(2 * (BLOOM_PAINT_RADIUS * BLOOM_PAINT_RADIUS));
return (1);
}
int bloom_get_tool_count(magic_api * api ATTRIBUTE_UNUSED)
{
return (1);
}
SDL_Surface *bloom_get_icon(magic_api * api, int which ATTRIBUTE_UNUSED)
{
char fname[1024];
snprintf(fname, sizeof(fname), "%simages/magic/bloom.png",
api->data_directory);
return (IMG_Load(fname));
}
char *bloom_get_name(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED)
{
return strdup(gettext("Bloom"));
}
int bloom_get_group(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED ATTRIBUTE_UNUSED)
{
return MAGIC_TYPE_COLOR_FILTERS;
}
char *bloom_get_description(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED)
{
return strdup(gettext("Bloom!")); /* FIXME */
}
int bloom_requires_colors(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED)
{
return 0; /* TODO: Maybe some day? */
}
int bloom_modes(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED ATTRIBUTE_UNUSED)
{
return (MODE_PAINT | MODE_FULLSCREEN);
}
void bloom_shutdown(magic_api * api ATTRIBUTE_UNUSED)
{
if (snd_effects != NULL) {
Mix_FreeChunk(snd_effects);
snd_effects = NULL;
}
if (bloom_mask != NULL) {
free(bloom_mask);
bloom_mask = NULL;
}
}
void
bloom_click(magic_api * api, int which, int mode,
SDL_Surface * canvas, SDL_Surface * snapshot, int x, int y,
SDL_Rect * update_rect)
{
if (bloom_mask == NULL)
return;
if (snd_effects != NULL)
api->stopsound();
if (mode == MODE_PAINT) {
memset(bloom_mask, 0, (canvas->w * canvas->h));
bloom_drag(api, which, canvas, snapshot, x, y, x, y, update_rect);
} else {
if (snd_effects != NULL) {
api->playsound(snd_effects, (x * 255) / canvas->w, 255);
}
memset(bloom_mask, 128, (canvas->w * canvas->h));
bloom_release(api, which, canvas, snapshot, x, y,
update_rect);
update_rect->x = 0;
update_rect->y = 0;
update_rect->w = canvas->w;
update_rect->h = canvas->h;
}
}
void
bloom_drag(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, SDL_Surface * canvas,
SDL_Surface * snapshot, int ox, int oy, int x, int y, SDL_Rect * update_rect)
{
if (bloom_mask == NULL)
return;
api->line((void *) api, which, canvas, snapshot, ox, oy, x, y, 1 /* FIXME: Consider fewer iterations? */,
bloom_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 bloom_release(magic_api * api, int which ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x ATTRIBUTE_UNUSED, int y ATTRIBUTE_UNUSED,
SDL_Rect * update_rect ATTRIBUTE_UNUSED) {
int sample, offset, offset_flip, xx, yy;
Uint8 r, g, b;
float rf, gf, bf, mask_weight, lum;
float sums[3];
Uint32 color;
if (bloom_mask == NULL)
return;
if (snd_effects != NULL)
api->stopsound();
SDL_BlitSurface(snapshot, NULL, canvas, NULL);
for (y = 0; y < canvas->h; y++) {
for (x = 0; x < canvas->w; x++) {
if (bloom_mask[y * canvas->w + x] > 0) {
sums[0] = 0.0;
sums[1] = 0.0;
sums[2] = 0.0;
/* (Pull from snapshot) */
for (sample = 0; sample < NUM_SAMPLE_WEIGHTS; sample++) {
/* Horizontal samples */
color = api->getpixel(snapshot, x - ((NUM_SAMPLE_WEIGHTS - 1) / 2) + sample, y);
SDL_GetRGB(color, snapshot->format, &r, &g, &b);
sums[0] += r * sample_weights[sample];
sums[1] += g * sample_weights[sample];
sums[2] += b * sample_weights[sample];
/* Vertical samples */
color = api->getpixel(snapshot, x, y - ((NUM_SAMPLE_WEIGHTS - 1) / 2) + sample);
SDL_GetRGB(color, snapshot->format, &r, &g, &b);
sums[0] += r * sample_weights[sample];
sums[1] += g * sample_weights[sample];
sums[2] += b * sample_weights[sample];
}
/* (Blend an "X" shape, additively, onto target canvas) */
for (offset = -BLOOM_SPIKE_LENGTH; offset <= BLOOM_SPIKE_LENGTH; offset++) {
for (offset_flip = -1; offset <= 1; offset += 2) {
xx = x + offset;
yy = y + (offset * offset_flip);
if (xx >= 0 && xx < canvas->w && yy >= 0 && yy < canvas->h) {
color = api->getpixel(snapshot, xx, yy);
SDL_GetRGB(color, snapshot->format, &r, &g, &b);
mask_weight = (float) (bloom_mask[(yy) * canvas->w + xx] / 255.0);
mask_weight *= BLOOM_WEIGHT_CONST;
mask_weight *= ((BLOOM_SPIKE_LENGTH + 1) - (abs(offset)) / BLOOM_SPIKE_LENGTH);
rf = (((float) r) + (sums[0] * mask_weight)) / 255.0;
gf = (((float) g) + (sums[1] * mask_weight)) / 255.0;
bf = (((float) b) + (sums[2] * mask_weight)) / 255.0;
/* Reinhard Tonemap (Luminence) */
lum = luminance(rf, gf, bf);
if (lum > 0.0) {
float numerator = lum * (1.0f + lum);
float l_new = numerator / (1.0f + lum);
rf = change_luminance(rf, lum, l_new);
gf = change_luminance(gf, lum, l_new);
bf = change_luminance(bf, lum, l_new);
}
/* Clamp */
if (rf > 1.0)
rf = 1.0;
if (gf > 1.0)
gf = 1.0;
if (bf > 1.0)
bf = 1.0;
rf *= 255.0;
gf *= 255.0;
bf *= 255.0;
api->putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, (Uint8) rf, (Uint8) gf, (Uint8) bf));
}
}
}
}
}
}
update_rect->x = 0;
update_rect->y = 0;
update_rect->w = canvas->w;
update_rect->h = canvas->h;
}
void bloom_set_color(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED 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)
{
/* TODO: Maybe some day? */
}
void bloom_line_callback_drag(void *ptr, int which ATTRIBUTE_UNUSED,
SDL_Surface * canvas,
SDL_Surface * snapshot ATTRIBUTE_UNUSED,
int x, int y)
{
int xrad, yrad, xx, yy, chg, n;
magic_api *api = (magic_api *) ptr;
if (snd_effects != NULL)
api->playsound(snd_effects, (x * 255) / canvas->w, 255);
for (yrad = -BLOOM_PAINT_RADIUS; yrad < BLOOM_PAINT_RADIUS; yrad++) {
yy = y + yrad;
if (yy >= 0 && yy < canvas->h) {
for (xrad = -BLOOM_PAINT_RADIUS; xrad < BLOOM_PAINT_RADIUS; xrad++) {
xx = x + xrad;
if (xx >= 0 && xx < canvas->w) {
if (api->in_circle(xrad, yrad, BLOOM_PAINT_RADIUS)) {
/* Add to the bloom mask */
n = (int) bloom_mask[yy * canvas->w + xx];
chg = sqrt(bloom_scale - sqrt((xrad * xrad) + (yrad * yrad)));
n += chg;
if (n > 255) {
n = 255;
}
bloom_mask[yy * canvas->w + xx] = (Uint8) n;
/* Draw on the canvas temporarily */
api->putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, n, n, n));
}
}
}
}
}
}
void bloom_switchin(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED)
{
if (bloom_mask == NULL)
bloom_mask = (Uint8 *) malloc(sizeof(Uint8) * canvas-> w * canvas->h);
}
void bloom_switchout(magic_api * api ATTRIBUTE_UNUSED,
int which ATTRIBUTE_UNUSED ATTRIBUTE_UNUSED,
int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED)
{
}
float luminance(float r, float g, float b) {
return (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
}
float change_luminance(float c_in, float l_in, float l_out) {
return c_in * (l_out / l_in);
}

View file

@ -32,7 +32,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt) (See COPYING.txt)
Last updated: January 25, 2023 Last updated: February 6, 2023
*/ */
#include <stdio.h> #include <stdio.h>
@ -146,7 +146,7 @@ enum
/* A copy of canvas at switchin, will be used to draw from it as snapshot changes at each click */ /* A copy of canvas at switchin, will be used to draw from it as snapshot changes at each click */
static SDL_Surface *canvas_back; static SDL_Surface *canvas_back = NULL;
static Mix_Chunk *perspective_snd_effect[perspective_NUM_TOOLS + 1]; static Mix_Chunk *perspective_snd_effect[perspective_NUM_TOOLS + 1];
@ -252,6 +252,9 @@ void perspective_drag(magic_api * api, int which, SDL_Surface * canvas,
int oy ATTRIBUTE_UNUSED, int x, int y, int oy ATTRIBUTE_UNUSED, int x, int y,
SDL_Rect * update_rect) SDL_Rect * update_rect)
{ {
if (canvas_back == NULL)
return;
latest_x = x; latest_x = x;
latest_y = y; latest_y = y;
@ -479,6 +482,9 @@ void perspective_release(magic_api * api, int which,
SDL_Surface * canvas, SDL_Surface * last, int x, SDL_Surface * canvas, SDL_Surface * last, int x,
int y, SDL_Rect * update_rect) int y, SDL_Rect * update_rect)
{ {
if (canvas_back == NULL)
return;
if (which == TOOL_PANELS) if (which == TOOL_PANELS)
return; return;
@ -662,6 +668,10 @@ void perspective_release(magic_api * api, int which,
canvas->format->Gmask, canvas->format->Gmask,
canvas->format->Bmask, 0); canvas->format->Bmask, 0);
printf("SDL_BlitSurface(canvas_back (%d), update_rect (%d), aux_surf (%d), NULL\n",
canvas_back, update_rect, aux_surf);
fflush(stdout);
SDL_BlitSurface(canvas_back, update_rect, aux_surf, NULL); SDL_BlitSurface(canvas_back, update_rect, aux_surf, NULL);
scaled_surf = api->scale(aux_surf, canvas->w, canvas->h, 0); scaled_surf = api->scale(aux_surf, canvas->w, canvas->h, 0);
SDL_BlitSurface(scaled_surf, NULL, canvas, NULL); SDL_BlitSurface(scaled_surf, NULL, canvas, NULL);
@ -687,6 +697,9 @@ void perspective_preview(magic_api * api, int which,
int ox_distance, oy_distance; int ox_distance, oy_distance;
int center_ofset_x, center_ofset_y; int center_ofset_x, center_ofset_y;
if (canvas_back == NULL)
return;
update_rect->x = update_rect->y = 0; update_rect->x = update_rect->y = 0;
update_rect->w = canvas->w; update_rect->w = canvas->w;
@ -826,13 +839,20 @@ void perspective_switchin(magic_api * api ATTRIBUTE_UNUSED,
amask = amask =
~(canvas->format->Rmask | canvas->format->Gmask | canvas->format->Bmask); ~(canvas->format->Rmask | canvas->format->Gmask | canvas->format->Bmask);
canvas_back = SDL_CreateRGBSurface(SDL_SWSURFACE, if (canvas_back == NULL) {
canvas->w, canvas_back = SDL_CreateRGBSurface(SDL_SWSURFACE,
canvas->h, canvas->w,
canvas->format->BitsPerPixel, canvas->h,
canvas->format->Rmask, canvas->format->BitsPerPixel,
canvas->format->Gmask, canvas->format->Rmask,
canvas->format->Bmask, amask); canvas->format->Gmask,
canvas->format->Bmask, amask);
}
if (canvas_back == NULL) {
fprintf(stderr, "perspective cannot create background canvas!\n");
return;
}
SDL_BlitSurface(canvas, NULL, canvas_back, NULL); SDL_BlitSurface(canvas, NULL, canvas_back, NULL);
} }
@ -842,7 +862,10 @@ void perspective_switchout(magic_api * api ATTRIBUTE_UNUSED,
int mode ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED) SDL_Surface * canvas ATTRIBUTE_UNUSED)
{ {
SDL_FreeSurface(canvas_back); if (canvas_back != NULL) {
SDL_FreeSurface(canvas_back);
canvas_back = NULL;
}
} }
int perspective_modes(magic_api * api ATTRIBUTE_UNUSED, int which) int perspective_modes(magic_api * api ATTRIBUTE_UNUSED, int which)