diff --git a/docs/AUTHORS.txt b/docs/AUTHORS.txt index a94b3f59f..0b26ac7a4 100644 --- a/docs/AUTHORS.txt +++ b/docs/AUTHORS.txt @@ -6,7 +6,7 @@ Copyright (c) 2002-2024 Various contributors (see below, and CHANGES.txt) https://tuxpaint.org/ -June 17, 2002 - September 24, 2024 +June 17, 2002 - September 26, 2024 * Design and Coding: @@ -197,6 +197,22 @@ June 17, 2002 - September 24, 2024 Creative Commons 0 by lorefold + "ASCII Typewriter" & "ASCII Computer" magic tools + by Bill Kendrick + + "ASCII Computer" font: IBM CGA Adapter + taken from "Typography in 16-bits: System fonts" by Damien Guard + + + "ASCII Typewriter" font based on: "Patrician" + + Creative Commons CC0 1.0 Universal + by Richard Polt (based on a 1959 Royal FP typewriter) + And using a subset of characters taken from + "Character representation of grey scale images" + + by Paul Bourke + Bloom magic tool by Bill Kendrick diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index 26caecac9..07f342948 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -6,7 +6,7 @@ Copyright (c) 2002-2024 Various contributors (see below, and AUTHORS.txt) https://tuxpaint.org/ -2024.September.25 (0.9.34) +2024.September.26 (0.9.34) * New Magic Tools: ---------------- * "Comic Dots", draws repeating dots (using a multiply blend) @@ -31,6 +31,25 @@ https://tuxpaint.org/ + Closes https://sourceforge.net/p/tuxpaint/feature-requests/260/ + * WIP "ASCII Typewriter" & "ASCII Computer", turn your drawing into + ASCII art. + + TODO Sound effects + + TODO Icons + + TODO Documentation + + Code by Bill Kendrick + + Computer font: IBM CGA Adapter + taken from "Typography in 16-bits: System fonts" + + by Damien Guard + + Typewriter font based on: "Patrician" + + Creative Commons CC0 1.0 Universal + by Richard Polt (based on a 1959 Royal FP typewriter) + And using a subset of characters taken from + "Character representation of grey scale images" + + by Paul Bourke + * Magic Tool Improvements: ------------------------ * Sound pause/resume functions added to API diff --git a/magic/icons/ascii-computer.png b/magic/icons/ascii-computer.png index e9474a009..49c2a63d6 100644 Binary files a/magic/icons/ascii-computer.png and b/magic/icons/ascii-computer.png differ diff --git a/magic/icons/ascii-typewriter.png b/magic/icons/ascii-typewriter.png index ec142bc07..e416408fa 100644 Binary files a/magic/icons/ascii-typewriter.png and b/magic/icons/ascii-typewriter.png differ diff --git a/magic/src/ascii.c b/magic/src/ascii.c index 4cc0caa5b..993d2b16c 100644 --- a/magic/src/ascii.c +++ b/magic/src/ascii.c @@ -22,9 +22,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA (See COPYING.txt) - Last updated: September 25, 2024 + Last updated: September 26, 2024 */ +//#define DEBUG + +#if defined(DEBUG) +#define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +#define DEBUG_PRINTF(...) +#endif + #include #include #include "tp_magic_api.h" @@ -38,8 +46,8 @@ enum { }; char * ascii_tool_names[NUM_TOOLS] = { - "Typewriter", - "Computer", + gettext_noop("Typewriter"), + gettext_noop("Computer"), }; char * ascii_tool_filenames[NUM_TOOLS] = { @@ -91,6 +99,8 @@ Uint8 ascii_default_size(magic_api * api, int which, int mode); void ascii_set_size(magic_api * api, int which, int mode, SDL_Surface * canvas, SDL_Surface * last, Uint8 size, SDL_Rect * update_rect); void do_ascii_effect(void *ptr, int which, SDL_Surface * canvas, SDL_Surface * last, int x, int y); +int get_best_char(int which, int brightness); +int get_bright(magic_api * api, int r, int g, int b); Uint32 ascii_api_version(void) @@ -101,9 +111,11 @@ Uint32 ascii_api_version(void) int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 complexity_level ATTRIBUTE_UNUSED) { char fname[1024]; - int i, j, x, y, xx, w, num_chars, all_clear, area, bright; + int i, j, x, y, xx, w, num_chars, all_clear, area, bright, clear_brightness; + int min_bright, max_bright; Uint32 clear_pixel, pixel; Uint8 r, g, b; + Uint8 clear_r, clear_g, clear_b; for (i = 0; i < NUM_TOOLS; i++) { @@ -129,12 +141,14 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 } clear_pixel = api->getpixel(ascii_bitmap[i], 0, 0); -// printf("%s; clear pixel %d\n", fname, clear_pixel); + SDL_GetRGB(clear_pixel, ascii_bitmap[i]->format, &clear_r, &clear_g, &clear_b); + DEBUG_PRINTF("%s; clear pixel %d (%d,%d,%d)\n", fname, clear_pixel, clear_r, clear_g, clear_b); + clear_brightness = (clear_r + clear_g + clear_b) / 3; + num_chars = 0; for (x = 0; x < ascii_bitmap[i]->w; x++) { - ascii_char_x[i][num_chars] = x; - + /* Skip whitespace between characters */ do { all_clear = 1; @@ -148,6 +162,9 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 } while (all_clear && x < ascii_bitmap[i]->w); + ascii_char_x[i][num_chars] = x; + + /* Capture the extent of the character */ all_clear = 0; for (xx = x; xx < ascii_bitmap[i]->w && !all_clear; xx++) { @@ -164,26 +181,26 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 /* Magenta counts as a connecting pixel, but we * want it to appear as the clear color */ api->putpixel(ascii_bitmap[i], xx, y, clear_pixel); -// printf("x"); + DEBUG_PRINTF("x"); } else { -// printf("#"); + DEBUG_PRINTF("#"); } } else { -// printf("-"); + DEBUG_PRINTF("-"); } } -// printf("\n"); + DEBUG_PRINTF("\n"); } - x = xx; + x = xx - 1; num_chars++; -// printf(".......................................\n"); + DEBUG_PRINTF(".......................................\n"); } ascii_num_chars[i] = num_chars; - printf("%s has %d characters\n", fname, num_chars); + DEBUG_PRINTF("%s has %d characters\n", fname, num_chars); /* Determine the max. width of any character */ ascii_char_x[i][num_chars] = x; @@ -191,7 +208,7 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 for (j = 0; j < num_chars; j++) { w = ascii_char_x[i][j + 1] - ascii_char_x[i][j]; -// printf("%d->%d = %d\n", j, j + 1, w); + DEBUG_PRINTF("%d->%d = %d\n", j, j + 1, w); if (w > ascii_char_maxwidth[i]) { ascii_char_maxwidth[i] = w; @@ -201,7 +218,7 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 /* Calculate the intensity of each character */ area = ascii_char_maxwidth[i] * ascii_bitmap[i]->h; -// printf("%s max char width is %d -- * %d = area %d\n", fname, ascii_char_maxwidth[i], ascii_bitmap[i]->h, area); + DEBUG_PRINTF("%s max char width is %d -- * %d = area %d\n", fname, ascii_char_maxwidth[i], ascii_bitmap[i]->h, area); for (j = 0; j < num_chars; j++) { @@ -213,14 +230,40 @@ int ascii_init(magic_api * api, Uint8 disabled_features ATTRIBUTE_UNUSED, Uint8 pixel = api->getpixel(ascii_bitmap[i], x, y); SDL_GetRGB(pixel, ascii_bitmap[i]->format, &r, &g, &b); -// printf("%3d ", (r + g + b) / 3); - bright += ((r + g + b) / 3); + DEBUG_PRINTF("%3d (%3d) ", (r + g + b) / 3, get_bright(api, r, g, b)); + bright += get_bright(api, r, g, b); } -// printf("\n"); + DEBUG_PRINTF("\n"); } -// printf("char %d brightness = %d\n", j, bright / area); + DEBUG_PRINTF("char %3d brightness = %3d before padding -- ", j, bright / area); + w = ascii_char_maxwidth[i] - (ascii_char_x[i][j + 1] - ascii_char_x[i][j]) - 2; /* don't let padding affect _too_ much */ + if (w >= 1) + bright += (clear_brightness * ascii_bitmap[i]->h * w); + DEBUG_PRINTF("%3d after padding %d width\n", bright / area, w); ascii_char_brightness[i][j] = bright / area; } + + /* Stretch the brightnesses, so we cover more of 0->255 */ + min_bright = 255; + max_bright = 0; + for (j = 0; j < num_chars; j++) + { + if (ascii_char_brightness[i][j] > max_bright) + max_bright = ascii_char_brightness[i][j]; + if (ascii_char_brightness[i][j] < max_bright) + min_bright = ascii_char_brightness[i][j]; + } + DEBUG_PRINTF("brightnesses between %d and %d\n", min_bright, max_bright); + + /* https://rosettacode.org/wiki/Map_range#C */ +#define map_range(a1,a2,b1,b2,s) (b1 + (s-a1)*(b2-b1)/(a2-a1)) + + for (j = 0; j < num_chars; j++) + { + DEBUG_PRINTF("mapping %3d -> ", ascii_char_brightness[i][j]); + ascii_char_brightness[i][j] = map_range(min_bright, max_bright, 0, 255, ascii_char_brightness[i][j]); + DEBUG_PRINTF("%3d\n", ascii_char_brightness[i][j]); + } } return (1); @@ -246,7 +289,7 @@ char *ascii_get_name(magic_api * api ATTRIBUTE_UNUSED, int which) { char tmp[1024]; - snprintf(tmp, sizeof(tmp), gettext("ASCII %s"), ascii_tool_names[which]); + snprintf(tmp, sizeof(tmp), gettext("ASCII %s"), gettext(ascii_tool_names[which])); return(strdup(tmp)); } @@ -373,8 +416,10 @@ void ascii_set_size(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED void do_ascii_effect(void *ptr, int which, SDL_Surface * canvas, SDL_Surface * last, int x, int y) { magic_api *api = (magic_api *) ptr; - int w, h, n; + int w, h, n, xx, yy, brightness; + Uint8 r, g, b; Uint32 clear_pixel; + Uint8 clear_brightness; SDL_Rect src, dest; w = ascii_char_maxwidth[which]; @@ -386,6 +431,8 @@ void do_ascii_effect(void *ptr, int which, SDL_Surface * canvas, SDL_Surface * l if (!api->touched(x, y)) { clear_pixel = api->getpixel(ascii_bitmap[which], 0, 0); + SDL_GetRGB(clear_pixel, ascii_bitmap[which]->format, &r, &g, &b); + clear_brightness = ((r + g + b) / 3.0); dest.x = x; dest.y = y; @@ -394,15 +441,79 @@ void do_ascii_effect(void *ptr, int which, SDL_Surface * canvas, SDL_Surface * l SDL_FillRect(canvas, &dest, clear_pixel); - n = rand() % ascii_num_chars[which]; - src.x = ascii_char_x[which][n]; - src.y = 0; - src.w = ascii_char_x[which][n + 1] - ascii_char_x[which][n]; - src.h = h; + brightness = 0; + for (yy = y; yy < y + h; yy++) + { + for (xx = x; xx < x + w; xx++) + { + SDL_GetRGB(api->getpixel(last, xx, yy), last->format, &r, &g, &b); + brightness += get_bright(api, r, g, b); + } + } + brightness = brightness / (w * h); - dest.x = x + (w - src.w) / 2; - dest.y = y; + /* FIXME: Increase contrast */ - SDL_BlitSurface(ascii_bitmap[which], &src, canvas, &dest); + if (brightness != clear_brightness) + { + n = get_best_char(which, brightness); + src.x = ascii_char_x[which][n]; + src.y = 0; + src.w = ascii_char_x[which][n + 1] - ascii_char_x[which][n]; + src.h = h; + + dest.x = x + (w - src.w) / 2; + dest.y = y; + + SDL_BlitSurface(ascii_bitmap[which], &src, canvas, &dest); + } } } + +int get_best_char(int which, int brightness) +{ + int i, diff, best_idx, best_diff; + + best_idx = -1; + best_diff = 255; + for (i = 0; i < ascii_num_chars[which]; i++) + { + diff = abs(ascii_char_brightness[which][i] - brightness); + + if (diff == best_diff) + { + if (rand() % 10 <= 3) + best_idx = 1; + } + else if (diff < best_diff) + { + best_diff = diff; + best_idx = i; + } + } + + if (best_idx == -1) + { + /* Shouldn't happen, but just in case */ + best_idx = rand() % ascii_num_chars[which]; + printf("!?\n"); + } + + DEBUG_PRINTF("best for brightness %d is %d (brightness %d)\n", + brightness, best_idx, ascii_char_brightness[which][best_idx]); + + return best_idx; +} + +int get_bright(magic_api * api, int r, int g, int b) +{ + float fr, fg, fb, y; + + fr = api->sRGB_to_linear(r); + fg = api->sRGB_to_linear(g); + fb = api->sRGB_to_linear(b); + + y = (0.2126 * fr) + (0.7152 * fg) + (0.0722 * fb); + + return (int) (y * 255); +} diff --git a/src/org.tuxpaint.Tuxpaint.appdata.xml.in b/src/org.tuxpaint.Tuxpaint.appdata.xml.in index 7d3695ce8..a925cb983 100644 --- a/src/org.tuxpaint.Tuxpaint.appdata.xml.in +++ b/src/org.tuxpaint.Tuxpaint.appdata.xml.in @@ -47,10 +47,10 @@ - + New Fill mode: "Eraser" flood fill. - New Magic tools: "Comic dots", "Rotate", and various "Fractals". + New Magic tools: "Comic dots", "Rotate", "ASCII Computer", "ASCII Typewriter", and various "Fractals". New brush: Fluff (gradient). diff --git a/src/tuxpaint.c b/src/tuxpaint.c index 37ceb5d3a..fb20d72b5 100644 --- a/src/tuxpaint.c +++ b/src/tuxpaint.c @@ -11918,7 +11918,7 @@ static SDL_Surface *thumbnail2(SDL_Surface * src, int max_x, int max_y, int keep SDL_GetRGBA(getpixel(src, src_x, src_y), src->format, &r, &g, &b, &a); #ifdef GAMMA_CORRECTED_THUMBNAILS - /* per: http://www.4p8.com/eric.brasseur/gamma.html */ + /* per: http://www.ericbrasseur.org/gamma.html?i=1 */ tr = tr + sRGB_to_linear_table[r]; tg = tg + sRGB_to_linear_table[g];
New Fill mode: "Eraser" flood fill.
New Magic tools: "Comic dots", "Rotate", and various "Fractals".
New Magic tools: "Comic dots", "Rotate", "ASCII Computer", "ASCII Typewriter", and various "Fractals".
New brush: Fluff (gradient).