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/
2022.January.30 (0.9.29)
2023.February.6 (0.9.29)
* Improvements to "Stamp" tool:
-----------------------------
* Stamps may now be rotated.
@ -32,8 +32,7 @@ https://tuxpaint.org/
(Inspired by "Night Sky Scene [Pen Parallax]" Scratch Project
by -HexaScape- <https://scratch.mit.edu/users/-HexaScape->)
[WIP] - Fur sound effect needed
[WIP] - Fur sound icon needed
[WIP] - Fur icon needed
("Circles" and "Rays" sound effects based on Richard Wagner's
"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)
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
- needs better icon
- needs sound effect
@ -167,6 +172,10 @@ https://tuxpaint.org/
(h/t Miyagi Andel for reporting & Shin-ichi TOYAMA for pinpointing
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
or shapes, to avoid unexpected results.
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
(See COPYING.txt)
Last updated: January 25, 2023
Last updated: February 6, 2023
*/
#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 */
static SDL_Surface *canvas_back;
static SDL_Surface *canvas_back = NULL;
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,
SDL_Rect * update_rect)
{
if (canvas_back == NULL)
return;
latest_x = x;
latest_y = y;
@ -479,6 +482,9 @@ void perspective_release(magic_api * api, int which,
SDL_Surface * canvas, SDL_Surface * last, int x,
int y, SDL_Rect * update_rect)
{
if (canvas_back == NULL)
return;
if (which == TOOL_PANELS)
return;
@ -662,6 +668,10 @@ void perspective_release(magic_api * api, int which,
canvas->format->Gmask,
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);
scaled_surf = api->scale(aux_surf, canvas->w, canvas->h, 0);
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 center_ofset_x, center_ofset_y;
if (canvas_back == NULL)
return;
update_rect->x = update_rect->y = 0;
update_rect->w = canvas->w;
@ -826,6 +839,7 @@ void perspective_switchin(magic_api * api ATTRIBUTE_UNUSED,
amask =
~(canvas->format->Rmask | canvas->format->Gmask | canvas->format->Bmask);
if (canvas_back == NULL) {
canvas_back = SDL_CreateRGBSurface(SDL_SWSURFACE,
canvas->w,
canvas->h,
@ -833,6 +847,12 @@ void perspective_switchin(magic_api * api ATTRIBUTE_UNUSED,
canvas->format->Rmask,
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);
}
@ -842,7 +862,10 @@ void perspective_switchout(magic_api * api ATTRIBUTE_UNUSED,
int mode ATTRIBUTE_UNUSED,
SDL_Surface * canvas ATTRIBUTE_UNUSED)
{
if (canvas_back != NULL) {
SDL_FreeSurface(canvas_back);
canvas_back = NULL;
}
}
int perspective_modes(magic_api * api ATTRIBUTE_UNUSED, int which)