"Halftone" works much better
This commit is contained in:
parent
4b7715940d
commit
c5fd47b1af
2 changed files with 103 additions and 92 deletions
|
|
@ -16,6 +16,15 @@ $Id$
|
||||||
style effects!)
|
style effects!)
|
||||||
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/204/)
|
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/204/)
|
||||||
|
|
||||||
|
* Magic Tool Improvememnts:
|
||||||
|
-------------------------
|
||||||
|
* "Halftone" works much better, drawing large overlapping circles
|
||||||
|
of Cyan, Magenta, Yellow, and Black, based on the average
|
||||||
|
color of the area of the picture being replaced, to give a
|
||||||
|
"newsprint" effect.
|
||||||
|
* WIP - Need to make it run against entire image
|
||||||
|
* WIP - It seems to have the "Zoom" tool's icon!?!
|
||||||
|
|
||||||
* Documentation updates
|
* Documentation updates
|
||||||
---------------------
|
---------------------
|
||||||
* Ensured Tux Paint's built-in help ("tuxpaint --help"),
|
* Ensured Tux Paint's built-in help ("tuxpaint --help"),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* halftone.c
|
/* halftone.c
|
||||||
|
|
||||||
Last modified: 2021.02.20
|
Last modified: 2021.09.04
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,6 +16,12 @@
|
||||||
#include "SDL_image.h"
|
#include "SDL_image.h"
|
||||||
#include "SDL_mixer.h"
|
#include "SDL_mixer.h"
|
||||||
|
|
||||||
|
#define deg_cos(x) cos((x) * M_PI / 180.0)
|
||||||
|
#define deg_sin(x) sin((x) * M_PI / 180.0)
|
||||||
|
|
||||||
|
#define GRID_SIZE 16 /* Size of the grid, and hence max size of the circle (it may fill more, into a square shape) */
|
||||||
|
#define OFFSET_RADIUS 2 /* Radius for when offsetting C, M, Y, and K colors by their angles (see `chan_angles[]`) */
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
TOOL_HALFTONE,
|
TOOL_HALFTONE,
|
||||||
|
|
@ -84,14 +90,6 @@ int halftone_init(magic_api * api)
|
||||||
snprintf(fname, sizeof(fname), "%s/sounds/magic/%s", api->data_directory, snd_filenames[i]);
|
snprintf(fname, sizeof(fname), "%s/sounds/magic/%s", api->data_directory, snd_filenames[i]);
|
||||||
|
|
||||||
snd_effect[i] = Mix_LoadWAV(fname);
|
snd_effect[i] = Mix_LoadWAV(fname);
|
||||||
/*
|
|
||||||
if (snd_effect[i] == NULL)
|
|
||||||
{
|
|
||||||
SDL_FreeSurface(canvas_backup);
|
|
||||||
SDL_FreeSurface(square);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -184,10 +182,15 @@ void halftone_drag(magic_api * api, int which, SDL_Surface * canvas,
|
||||||
y = tmp;
|
y = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_rect->x = ox - 16;
|
ox = (ox / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
update_rect->y = oy - 16;
|
oy = (oy / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
update_rect->w = (x + 16) - update_rect->x;
|
x = (x / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
update_rect->h = (y + 16) - update_rect->h;
|
y = (y / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
|
|
||||||
|
update_rect->x = ox - GRID_SIZE / 2;
|
||||||
|
update_rect->y = oy - GRID_SIZE / 2;
|
||||||
|
update_rect->w = (x + GRID_SIZE / 2) - update_rect->x;
|
||||||
|
update_rect->h = (y + GRID_SIZE / 2) - update_rect->y;
|
||||||
|
|
||||||
api->playsound(snd_effect[which], (x * 255) / canvas->w, // pan
|
api->playsound(snd_effect[which], (x * 255) / canvas->w, // pan
|
||||||
255); // distance
|
255); // distance
|
||||||
|
|
@ -203,17 +206,17 @@ enum
|
||||||
};
|
};
|
||||||
|
|
||||||
Uint8 chan_colors[NUM_CHANS][3] = {
|
Uint8 chan_colors[NUM_CHANS][3] = {
|
||||||
{0, 255, 255},
|
{0, 255, 255}, /* Cyan */
|
||||||
{255, 0, 255},
|
{255, 0, 255}, /* Magenta */
|
||||||
{255, 255, 0},
|
{255, 255, 0}, /* Yellow */
|
||||||
{0, 0, 0}
|
{0, 0, 0} /* Black */
|
||||||
};
|
};
|
||||||
|
|
||||||
int chan_angles[NUM_CHANS] = {
|
int chan_angles[NUM_CHANS] = {
|
||||||
100,
|
75, /* Cyan */
|
||||||
15,
|
15, /* Magenta */
|
||||||
0,
|
90, /* Yellow */
|
||||||
45
|
45 /* Black */
|
||||||
};
|
};
|
||||||
|
|
||||||
void halftone_release(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED,
|
void halftone_release(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED,
|
||||||
|
|
@ -227,98 +230,95 @@ void halftone_set_color(magic_api * api ATTRIBUTE_UNUSED, Uint8 r ATTRIBUTE_UNUS
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void halftone_line_callback(void *ptr, int which ATTRIBUTE_UNUSED,
|
void halftone_line_callback(void *ptr, int which ATTRIBUTE_UNUSED,
|
||||||
SDL_Surface * canvas, SDL_Surface * snapshot ATTRIBUTE_UNUSED, int x, int y)
|
SDL_Surface * canvas, SDL_Surface * snapshot ATTRIBUTE_UNUSED, int x, int y)
|
||||||
{
|
{
|
||||||
Uint8 r, g, b, or, og, ob;
|
Uint8 r, g, b, or, og, ob;
|
||||||
Uint32 total_r, total_g, total_b;
|
Uint32 total_r, total_g, total_b;
|
||||||
|
int px_cnt;
|
||||||
Uint32 pixel;
|
Uint32 pixel;
|
||||||
int xx, yy, xxx, yyy, channel, ox, oy, sqx, sqy;
|
int xxx, yyy, channel, ox, oy, sqx, sqy;
|
||||||
SDL_Rect dest;
|
SDL_Rect dest;
|
||||||
magic_api *api = (magic_api *) ptr;
|
magic_api *api = (magic_api *) ptr;
|
||||||
float cmyk[4];
|
float cmyk[4];
|
||||||
|
|
||||||
|
/* Start the pixel with white */
|
||||||
pixel = SDL_MapRGB(square->format, 255, 255, 255);
|
pixel = SDL_MapRGB(square->format, 255, 255, 255);
|
||||||
SDL_FillRect(square, NULL, pixel);
|
SDL_FillRect(square, NULL, pixel);
|
||||||
|
|
||||||
/* Lock to grid, centered around mouse */
|
/* Lock to a grid, centered around mouse */
|
||||||
x = ((x / 8) - 1) * 8;
|
x = (x / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
y = ((y / 8) - 1) * 8;
|
y = (y / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2);
|
||||||
|
|
||||||
if (api->touched(x, y))
|
if (api->touched(x, y))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (xx = 0; xx < 16; xx = xx + 4)
|
/* Get the average color around the mouse */
|
||||||
|
total_r = total_g = total_b = 0;
|
||||||
|
px_cnt = 0;
|
||||||
|
for (xxx = -(GRID_SIZE / 2); xxx < (GRID_SIZE / 2); xxx++)
|
||||||
{
|
{
|
||||||
for (yy = 0; yy < 16; yy = yy + 4)
|
for (yyy = -(GRID_SIZE / 2); yyy < (GRID_SIZE / 2); yyy++)
|
||||||
{
|
{
|
||||||
/* Get avg color around the mouse */
|
SDL_GetRGB(api->getpixel(canvas_backup, x + xxx, y + yyy), canvas_backup->format, &r, &g, &b);
|
||||||
total_r = total_g = total_b = 0;
|
total_r += r;
|
||||||
for (xxx = 0; xxx < 4; xxx++)
|
total_g += g;
|
||||||
|
total_b += b;
|
||||||
|
px_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total_r /= (GRID_SIZE * GRID_SIZE);
|
||||||
|
total_g /= (GRID_SIZE * GRID_SIZE);
|
||||||
|
total_b /= (GRID_SIZE * GRID_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
/* Convert the average color from RGB to CMYK values, for 'painting' later */
|
||||||
|
halftone_rgb2cmyk(total_r, total_g, total_b, cmyk);
|
||||||
|
|
||||||
|
/* Draw C, M, Y and K blobs into our 'square' surface */
|
||||||
|
for (channel = 0; channel < NUM_CHANS; channel++)
|
||||||
|
{
|
||||||
|
for (xxx = -(GRID_SIZE / 2) - 1; xxx < (GRID_SIZE / 2) + 1; xxx++)
|
||||||
|
{
|
||||||
|
for (yyy = -(GRID_SIZE / 2) - 1; yyy < (GRID_SIZE / 2) + 1; yyy++)
|
||||||
{
|
{
|
||||||
for (yyy = 0; yyy < 4; yyy++)
|
/* A circle blob, radius based upon channel (C, M, Y or K) strength for this color */
|
||||||
|
|
||||||
|
ox = xxx + deg_cos(chan_angles[channel]) * OFFSET_RADIUS;
|
||||||
|
oy = yyy + deg_sin(chan_angles[channel]) * OFFSET_RADIUS;
|
||||||
|
|
||||||
|
sqx = (GRID_SIZE / 2) + ox;
|
||||||
|
sqy = (GRID_SIZE / 2) + oy;
|
||||||
|
|
||||||
|
/* Use intensity of the CMKY channel in question to decide
|
||||||
|
how big of a circle to paint */
|
||||||
|
if (api->in_circle(xxx, yyy, cmyk[channel] * GRID_SIZE))
|
||||||
{
|
{
|
||||||
SDL_GetRGB(api->getpixel(canvas_backup, x + xx + xxx, y + yy + yyy), canvas_backup->format, &r, &g,
|
/* Use the pure C, Y, M, or K color to paint with */
|
||||||
&b);
|
r = chan_colors[channel][0];
|
||||||
total_r += r;
|
g = chan_colors[channel][1];
|
||||||
total_g += g;
|
b = chan_colors[channel][2];
|
||||||
total_b += b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total_r /= 16;
|
|
||||||
total_g /= 16;
|
|
||||||
total_b /= 16;
|
|
||||||
|
|
||||||
/* Convert to CMYK values */
|
/* Additively blend with whatever we have in the
|
||||||
halftone_rgb2cmyk(total_r, total_g, total_b, cmyk);
|
'square' buffer (which starts as white)
|
||||||
|
(since the target is RGB, we use `min()`) */
|
||||||
/* Draw C, M, Y and K blobs into our 'square' surface */
|
SDL_GetRGB(api->getpixel(square, sqx, sqy), square->format, &or, &og, &ob);
|
||||||
for (channel = 0; channel < NUM_CHANS; channel++)
|
pixel = SDL_MapRGB(square->format, min(r * 1.2, or), min(g * 1.2, og), min(b * 1.2, ob));
|
||||||
{
|
api->putpixel(square, sqx, sqy, pixel);
|
||||||
r = chan_colors[channel][0];
|
|
||||||
g = chan_colors[channel][1];
|
|
||||||
b = chan_colors[channel][2];
|
|
||||||
|
|
||||||
for (xxx = 0; xxx < 8; xxx++)
|
|
||||||
{
|
|
||||||
for (yyy = 0; yyy < 8; yyy++)
|
|
||||||
{
|
|
||||||
/* A circle blob, radius based upon channel (C, M, Y or K) strength for this color */
|
|
||||||
|
|
||||||
/* FIXME: Base it upon this channel's angle! -bjk 2011.07.17 */
|
|
||||||
ox = xxx;
|
|
||||||
oy = yyy;
|
|
||||||
|
|
||||||
sqx = (xx + ox) % 16;
|
|
||||||
sqy = (yy + oy) % 16;
|
|
||||||
|
|
||||||
if (api->in_circle(xxx - 4, yyy - 4, cmyk[channel] * 6.0))
|
|
||||||
{
|
|
||||||
SDL_GetRGB(api->getpixel(square, sqx, sqy), square->format, &or, &og, &ob);
|
|
||||||
|
|
||||||
if (or == 255 && og == 255 && ob == 255)
|
|
||||||
{
|
|
||||||
/* If it's just white, put full color down */
|
|
||||||
pixel = SDL_MapRGB(square->format, r, g, b);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Otherwise, blend a little */
|
|
||||||
pixel = SDL_MapRGB(square->format, (r + or) / 2, (g + og) / 2, (b + ob) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
api->putpixel(square, sqx, sqy, pixel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dest.x = x;
|
/* Copy the results to the canvas */
|
||||||
dest.y = y;
|
dest.x = x - GRID_SIZE / 2;
|
||||||
|
dest.y = y - GRID_SIZE / 2;
|
||||||
|
dest.w = GRID_SIZE;
|
||||||
|
dest.h = GRID_SIZE;
|
||||||
|
|
||||||
SDL_BlitSurface(square, NULL, canvas, &dest);
|
SDL_BlitSurface(square, NULL, canvas, &dest);
|
||||||
}
|
}
|
||||||
|
|
@ -326,16 +326,18 @@ void halftone_line_callback(void *ptr, int which ATTRIBUTE_UNUSED,
|
||||||
void halftone_switchin(magic_api * api, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, SDL_Surface * canvas)
|
void halftone_switchin(magic_api * api, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, SDL_Surface * canvas)
|
||||||
{
|
{
|
||||||
if (canvas_backup == NULL)
|
if (canvas_backup == NULL)
|
||||||
canvas_backup =
|
{
|
||||||
SDL_CreateRGBSurface(SDL_ANYFORMAT, api->canvas_w, api->canvas_h, canvas->format->BitsPerPixel,
|
canvas_backup =
|
||||||
canvas->format->Rmask, canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
|
SDL_CreateRGBSurface(SDL_ANYFORMAT, api->canvas_w, api->canvas_h, canvas->format->BitsPerPixel,
|
||||||
|
canvas->format->Rmask, canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
|
||||||
|
}
|
||||||
|
|
||||||
if (square == NULL)
|
if (square == NULL)
|
||||||
square =
|
{
|
||||||
SDL_CreateRGBSurface(SDL_ANYFORMAT, 16, 16, canvas->format->BitsPerPixel, canvas->format->Rmask,
|
square =
|
||||||
canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
|
SDL_CreateRGBSurface(SDL_ANYFORMAT, GRID_SIZE, GRID_SIZE, canvas->format->BitsPerPixel, canvas->format->Rmask,
|
||||||
|
canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
|
||||||
/* FIXME: What to do if they come back NULL!? :( */
|
}
|
||||||
|
|
||||||
SDL_BlitSurface(canvas, NULL, canvas_backup, NULL);
|
SDL_BlitSurface(canvas, NULL, canvas_backup, NULL);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue