diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index 1e3220157..6ea3b6fb1 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -16,6 +16,15 @@ $Id$ style effects!) (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 --------------------- * Ensured Tux Paint's built-in help ("tuxpaint --help"), diff --git a/magic/src/halftone.c b/magic/src/halftone.c index bdb27cbd0..baaf55a6c 100644 --- a/magic/src/halftone.c +++ b/magic/src/halftone.c @@ -1,6 +1,6 @@ /* halftone.c - Last modified: 2021.02.20 + Last modified: 2021.09.04 */ @@ -16,6 +16,12 @@ #include "SDL_image.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 { 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]); 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; } - update_rect->x = ox - 16; - update_rect->y = oy - 16; - update_rect->w = (x + 16) - update_rect->x; - update_rect->h = (y + 16) - update_rect->h; + ox = (ox / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2); + oy = (oy / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2); + x = (x / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2); + 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 255); // distance @@ -203,17 +206,17 @@ enum }; Uint8 chan_colors[NUM_CHANS][3] = { - {0, 255, 255}, - {255, 0, 255}, - {255, 255, 0}, - {0, 0, 0} + {0, 255, 255}, /* Cyan */ + {255, 0, 255}, /* Magenta */ + {255, 255, 0}, /* Yellow */ + {0, 0, 0} /* Black */ }; int chan_angles[NUM_CHANS] = { - 100, - 15, - 0, - 45 + 75, /* Cyan */ + 15, /* Magenta */ + 90, /* Yellow */ + 45 /* Black */ }; 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, SDL_Surface * canvas, SDL_Surface * snapshot ATTRIBUTE_UNUSED, int x, int y) { Uint8 r, g, b, or, og, ob; Uint32 total_r, total_g, total_b; + int px_cnt; Uint32 pixel; - int xx, yy, xxx, yyy, channel, ox, oy, sqx, sqy; + int xxx, yyy, channel, ox, oy, sqx, sqy; SDL_Rect dest; magic_api *api = (magic_api *) ptr; float cmyk[4]; + /* Start the pixel with white */ pixel = SDL_MapRGB(square->format, 255, 255, 255); SDL_FillRect(square, NULL, pixel); - /* Lock to grid, centered around mouse */ - x = ((x / 8) - 1) * 8; - y = ((y / 8) - 1) * 8; + /* Lock to a grid, centered around mouse */ + x = (x / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2); + y = (y / GRID_SIZE) * GRID_SIZE + (GRID_SIZE / 2); if (api->touched(x, y)) { 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 */ - total_r = total_g = total_b = 0; - for (xxx = 0; xxx < 4; xxx++) + SDL_GetRGB(api->getpixel(canvas_backup, x + xxx, y + yyy), canvas_backup->format, &r, &g, &b); + total_r += r; + 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, - &b); - total_r += r; - total_g += g; - total_b += b; - } - } - total_r /= 16; - total_g /= 16; - total_b /= 16; + /* Use the pure C, Y, M, or K color to paint with */ + r = chan_colors[channel][0]; + g = chan_colors[channel][1]; + b = chan_colors[channel][2]; - /* Convert to CMYK values */ - 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++) - { - 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); - } - } + /* Additively blend with whatever we have in the + 'square' buffer (which starts as white) + (since the target is RGB, we use `min()`) */ + SDL_GetRGB(api->getpixel(square, sqx, sqy), square->format, &or, &og, &ob); + 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); } } } } - dest.x = x; - dest.y = y; + /* Copy the results to the canvas */ + 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); } @@ -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) { if (canvas_backup == NULL) - canvas_backup = - 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); + { + canvas_backup = + 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) - square = - SDL_CreateRGBSurface(SDL_ANYFORMAT, 16, 16, canvas->format->BitsPerPixel, canvas->format->Rmask, - canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask); - - /* FIXME: What to do if they come back NULL!? :( */ + { + square = + SDL_CreateRGBSurface(SDL_ANYFORMAT, GRID_SIZE, GRID_SIZE, canvas->format->BitsPerPixel, canvas->format->Rmask, + canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask); + } SDL_BlitSurface(canvas, NULL, canvas_backup, NULL); }