750 lines
22 KiB
C
750 lines
22 KiB
C
/*
|
|
ascii.c
|
|
|
|
Converts the image to ASCII art.
|
|
|
|
Tux Paint - A simple drawing program for children.
|
|
|
|
Copyright (c) 2024 by Bill Kendrick
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
(See COPYING.txt)
|
|
|
|
Last updated: September 27, 2024
|
|
*/
|
|
|
|
//#define DEBUG
|
|
|
|
#if defined(DEBUG)
|
|
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define DEBUG_PRINTF(...)
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "tp_magic_api.h"
|
|
#include "SDL_image.h"
|
|
#include "SDL_mixer.h"
|
|
|
|
enum {
|
|
TOOL_TYPEWRITER,
|
|
TOOL_COMPUTER,
|
|
NUM_TOOLS
|
|
};
|
|
|
|
#define TOOL_COMPUTER_COLOR NUM_TOOLS
|
|
|
|
char * ascii_tool_names[NUM_TOOLS + 1] = {
|
|
gettext_noop("Typewriter"),
|
|
gettext_noop("Computer"),
|
|
gettext_noop("Color Computer"), // special version of "computer"
|
|
};
|
|
|
|
char * ascii_tool_filenames[NUM_TOOLS + 1] = {
|
|
"typewriter",
|
|
"computer",
|
|
"color_computer",
|
|
};
|
|
|
|
static Mix_Chunk *ascii_snd[NUM_TOOLS];
|
|
|
|
/* For each variation, we'll have a bitmap with an arbitrary number
|
|
* of potentially-proportionally-spaced characters (which we'll treat
|
|
* as fixed-width), so we need to keep track of each character's X
|
|
* poistion, how many characters there are, and the maximum width.
|
|
*/
|
|
#define MAX_CHARS 256
|
|
SDL_Surface * ascii_bitmap[NUM_TOOLS];
|
|
int ascii_char_x[NUM_TOOLS][MAX_CHARS];
|
|
int ascii_num_chars[NUM_TOOLS];
|
|
int ascii_char_maxwidth[NUM_TOOLS];
|
|
int ascii_char_brightness[NUM_TOOLS][MAX_CHARS];
|
|
SDL_Surface * ascii_snapshot = NULL;
|
|
int ascii_size;
|
|
Uint8 ascii_r, ascii_g, ascii_b;
|
|
Uint8 ascii_clear_r[NUM_TOOLS], ascii_clear_g[NUM_TOOLS], ascii_clear_b[NUM_TOOLS];
|
|
|
|
/* Based on CGA color palette
|
|
<https://en.wikipedia.org/wiki/Color_Graphics_Adapter#Color_palette> */
|
|
const Uint8 ascii_computer_colors[16][3] = {
|
|
{ 0x00, 0x00, 0x00 }, // Black
|
|
{ 0x55, 0x55, 0x55 }, // Dark gray
|
|
{ 0xAA, 0xAA, 0xAA }, // Light gray
|
|
{ 0xFF, 0xFF, 0xFF }, // White
|
|
{ 0x00, 0x00, 0xAA }, // Blue
|
|
{ 0x55, 0x55, 0xFF }, // Light blue
|
|
{ 0x00, 0xAA, 0x00 }, // Green
|
|
{ 0x55, 0xFF, 0x55 }, // Light green
|
|
{ 0x00, 0xAA, 0xAA }, // Cyan
|
|
{ 0x55, 0xFF, 0xFF }, // Light cyan
|
|
{ 0xAA, 0x00, 0x00 }, // Red
|
|
{ 0xFF, 0x55, 0x55 }, // Light red
|
|
{ 0xAA, 0x00, 0xAA }, // Magenta
|
|
{ 0xFF, 0x55, 0xFF }, // Light magenta
|
|
{ 0xAA, 0x55, 0x00 }, // Brown
|
|
{ 0xFF, 0xFF, 0x55 }, // Yellow
|
|
};
|
|
|
|
Uint32 ascii_api_version(void);
|
|
int ascii_init(magic_api * api, Uint8 disabled_features, Uint8 complexity_level);
|
|
int ascii_get_tool_count(magic_api * api);
|
|
SDL_Surface *ascii_get_icon(magic_api * api, int which);
|
|
char *ascii_get_name(magic_api * api, int which);
|
|
int ascii_get_group(magic_api * api, int which);
|
|
int ascii_get_order(int which);
|
|
char *ascii_get_description(magic_api * api, int which, int mode);
|
|
|
|
void ascii_drag(magic_api * api, int which, SDL_Surface * canvas,
|
|
SDL_Surface * last, int ox, int oy, int x, int y, SDL_Rect * update_rect);
|
|
|
|
void ascii_click(magic_api * api, int which, int mode,
|
|
SDL_Surface * canvas, SDL_Surface * last, int x, int y, SDL_Rect * update_rect);
|
|
|
|
void ascii_release(magic_api * api, int which,
|
|
SDL_Surface * canvas, SDL_Surface * last, int x, int y, SDL_Rect * update_rect);
|
|
|
|
void ascii_shutdown(magic_api * api);
|
|
void ascii_set_color(magic_api * api, int which, SDL_Surface * canvas,
|
|
SDL_Surface * last, Uint8 r, Uint8 g, Uint8 b, SDL_Rect * update_rect);
|
|
int ascii_requires_colors(magic_api * api, int which);
|
|
void ascii_switchin(magic_api * api, int which, int mode, SDL_Surface * canvas);
|
|
void ascii_switchout(magic_api * api, int which, int mode, SDL_Surface * canvas);
|
|
int ascii_modes(magic_api * api, int which);
|
|
Uint8 ascii_accepted_sizes(magic_api * api, int which, int mode);
|
|
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)
|
|
{
|
|
return (TP_MAGIC_API_VERSION);
|
|
}
|
|
|
|
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, 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++)
|
|
{
|
|
ascii_snd[i] = NULL;
|
|
ascii_bitmap[i] = NULL;
|
|
}
|
|
|
|
/* (N.B. Computer & Color Computer share sound & bitmap) */
|
|
for (i = 0; i < NUM_TOOLS; i++)
|
|
{
|
|
/* Load our sound */
|
|
snprintf(fname, sizeof(fname), "%ssounds/magic/ascii-%s.ogg", api->data_directory, ascii_tool_filenames[i]);
|
|
ascii_snd[i] = Mix_LoadWAV(fname);
|
|
|
|
/* Load and process our bitmap "font" */
|
|
snprintf(fname, sizeof(fname), "%simages/magic/ascii-%s.png", api->data_directory, ascii_tool_filenames[i]);
|
|
ascii_bitmap[i] = IMG_Load(fname);
|
|
|
|
if (ascii_bitmap[i] == NULL)
|
|
{
|
|
fprintf(stderr, "Cannot load %s\n", fname);
|
|
return(0);
|
|
}
|
|
|
|
clear_pixel = api->getpixel(ascii_bitmap[i], 0, 0);
|
|
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;
|
|
ascii_clear_r[i] = clear_r;
|
|
ascii_clear_g[i] = clear_g;
|
|
ascii_clear_b[i] = clear_b;
|
|
|
|
num_chars = 0;
|
|
for (x = 0; x < ascii_bitmap[i]->w; x++)
|
|
{
|
|
/* Skip whitespace between characters */
|
|
do
|
|
{
|
|
all_clear = 1;
|
|
for (y = 0; y < ascii_bitmap[i]->h /* && all_clear */; y++) {
|
|
pixel = api->getpixel(ascii_bitmap[i], x, y);
|
|
if (pixel != clear_pixel)
|
|
all_clear = 0;
|
|
}
|
|
if (all_clear)
|
|
x++;
|
|
}
|
|
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++)
|
|
{
|
|
all_clear = 1;
|
|
for (y = 0; y < ascii_bitmap[i]->h /* && all_clear */; y++)
|
|
{
|
|
pixel = api->getpixel(ascii_bitmap[i], xx, y);
|
|
if (pixel != clear_pixel)
|
|
{
|
|
all_clear = 0;
|
|
SDL_GetRGB(pixel, ascii_bitmap[i]->format, &r, &g, &b);
|
|
if (r == 255 && g == 0 && b == 255)
|
|
{
|
|
/* 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);
|
|
DEBUG_PRINTF("x");
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINTF("#");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINTF("-");
|
|
}
|
|
}
|
|
DEBUG_PRINTF("\n");
|
|
}
|
|
x = xx - 1;
|
|
num_chars++;
|
|
DEBUG_PRINTF(".......................................\n");
|
|
}
|
|
ascii_num_chars[i] = 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;
|
|
ascii_char_maxwidth[i] = 0;
|
|
for (j = 0; j < num_chars; j++)
|
|
{
|
|
w = ascii_char_x[i][j + 1] - ascii_char_x[i][j];
|
|
DEBUG_PRINTF("%d->%d = %d\n", j, j + 1, w);
|
|
if (w > ascii_char_maxwidth[i])
|
|
{
|
|
ascii_char_maxwidth[i] = w;
|
|
}
|
|
}
|
|
|
|
/* Calculate the intensity of each character */
|
|
area = ascii_char_maxwidth[i] * ascii_bitmap[i]->h;
|
|
|
|
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++)
|
|
{
|
|
bright = 0;
|
|
for (y = 0; y < ascii_bitmap[i]->h; y++)
|
|
{
|
|
for (x = ascii_char_x[i][j]; x < ascii_char_x[i][j + 1]; x++)
|
|
{
|
|
pixel = api->getpixel(ascii_bitmap[i], x, y);
|
|
SDL_GetRGB(pixel, ascii_bitmap[i]->format, &r, &g, &b);
|
|
|
|
DEBUG_PRINTF("%3d (%3d) ", (r + g + b) / 3, get_bright(api, r, g, b));
|
|
bright += get_bright(api, r, g, b);
|
|
}
|
|
DEBUG_PRINTF("\n");
|
|
}
|
|
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);
|
|
}
|
|
|
|
int ascii_get_tool_count(magic_api * api ATTRIBUTE_UNUSED)
|
|
{
|
|
return (NUM_TOOLS + 1);
|
|
}
|
|
|
|
SDL_Surface *ascii_get_icon(magic_api * api, int which)
|
|
{
|
|
char fname[1024];
|
|
|
|
snprintf(fname, sizeof(fname), "%simages/magic/ascii_%s.png", api->data_directory, ascii_tool_filenames[which]);
|
|
|
|
return (IMG_Load(fname));
|
|
}
|
|
|
|
char *ascii_get_name(magic_api * api ATTRIBUTE_UNUSED, int which)
|
|
{
|
|
char tmp[1024];
|
|
|
|
snprintf(tmp, sizeof(tmp), gettext("ASCII %s"), gettext(ascii_tool_names[which]));
|
|
return(strdup(tmp));
|
|
}
|
|
|
|
int ascii_get_group(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED)
|
|
{
|
|
return MAGIC_TYPE_DISTORTS;
|
|
}
|
|
|
|
int ascii_get_order(int which)
|
|
{
|
|
return 2050 + which;
|
|
}
|
|
|
|
char *ascii_get_description(magic_api * api ATTRIBUTE_UNUSED, int which, int mode)
|
|
{
|
|
char tmp[1024];
|
|
|
|
if (mode == MODE_PAINT)
|
|
snprintf(tmp, sizeof(tmp), gettext("Click and drag to transform parts of your drawing to ASCII art (%s style)."), ascii_tool_names[which]);
|
|
else
|
|
snprintf(tmp, sizeof(tmp), gettext("Click to transform your entire drawing to ASCII art (%s style)."), ascii_tool_names[which]);
|
|
|
|
return(strdup(tmp));
|
|
}
|
|
|
|
void ascii_drag(magic_api * api, int which, SDL_Surface * canvas,
|
|
SDL_Surface * last, int ox, int oy, int x, int y, SDL_Rect * update_rect)
|
|
{
|
|
api->line((void *)api, which, canvas, last, ox, oy, x, y, 1, do_ascii_effect);
|
|
|
|
/* FIXME */
|
|
update_rect->x = 0;
|
|
update_rect->y = 0;
|
|
update_rect->w = canvas->w;
|
|
update_rect->h = canvas->h;
|
|
|
|
if (which == TOOL_COMPUTER_COLOR)
|
|
which = TOOL_COMPUTER;
|
|
|
|
api->playsound(ascii_snd[which], (x * 255) / canvas->w, 255);
|
|
}
|
|
|
|
void ascii_click(magic_api * api, int which, int mode,
|
|
SDL_Surface * canvas, SDL_Surface * last, int x, int y, SDL_Rect * update_rect)
|
|
{
|
|
if (mode == MODE_PAINT)
|
|
ascii_drag(api, which, canvas, last, x, y, x, y, update_rect);
|
|
else
|
|
{
|
|
int xx, yy;
|
|
|
|
if (which == TOOL_COMPUTER_COLOR)
|
|
api->playsound(ascii_snd[TOOL_COMPUTER], (x * 255) / canvas->w, 255);
|
|
else
|
|
api->playsound(ascii_snd[which], (x * 255) / canvas->w, 255);
|
|
|
|
for (yy = 0; yy < canvas->h; yy++)
|
|
{
|
|
for (xx = 0; xx < canvas->w; xx++)
|
|
{
|
|
do_ascii_effect(api, which, canvas, last, xx, yy);
|
|
}
|
|
if (yy % 10 == 0)
|
|
api->update_progress_bar();
|
|
}
|
|
|
|
update_rect->x = 0;
|
|
update_rect->y = 0;
|
|
update_rect->w = canvas->w;
|
|
update_rect->h = canvas->h;
|
|
}
|
|
}
|
|
|
|
void ascii_release(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED,
|
|
SDL_Surface * canvas ATTRIBUTE_UNUSED,
|
|
SDL_Surface * last ATTRIBUTE_UNUSED, int x ATTRIBUTE_UNUSED,
|
|
int y ATTRIBUTE_UNUSED, SDL_Rect * update_rect ATTRIBUTE_UNUSED)
|
|
{
|
|
api->stopsound();
|
|
}
|
|
|
|
void ascii_shutdown(magic_api * api ATTRIBUTE_UNUSED)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_TOOLS; i++)
|
|
{
|
|
if (ascii_snd[i] != NULL)
|
|
Mix_FreeChunk(ascii_snd[i]);
|
|
if (ascii_bitmap[i] != NULL)
|
|
SDL_FreeSurface(ascii_bitmap[i]);
|
|
}
|
|
|
|
if (ascii_snapshot != NULL)
|
|
{
|
|
SDL_FreeSurface(ascii_snapshot);
|
|
ascii_snapshot = NULL;
|
|
}
|
|
}
|
|
|
|
void ascii_set_color(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, SDL_Surface * canvas ATTRIBUTE_UNUSED,
|
|
SDL_Surface * last ATTRIBUTE_UNUSED, Uint8 r, Uint8 g, Uint8 b, SDL_Rect * update_rect ATTRIBUTE_UNUSED)
|
|
{
|
|
/* If the bitmap's "clear" color, choose the opposite! */
|
|
if (abs(r - ascii_clear_r[which]) < 8 &&
|
|
abs(g - ascii_clear_g[which]) < 8 &&
|
|
abs(b - ascii_clear_b[which]) < 8)
|
|
{
|
|
r = 255 - r;
|
|
g = 255 - g;
|
|
b = 255 - b;
|
|
}
|
|
|
|
ascii_r = r;
|
|
ascii_g = g;
|
|
ascii_b = b;
|
|
}
|
|
|
|
int ascii_requires_colors(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED)
|
|
{
|
|
if (which == TOOL_TYPEWRITER || which == TOOL_COMPUTER)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ascii_switchin(magic_api * api ATTRIBUTE_UNUSED,
|
|
int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, SDL_Surface * canvas ATTRIBUTE_UNUSED)
|
|
{
|
|
if (ascii_snapshot == NULL)
|
|
ascii_snapshot = SDL_CreateRGBSurface(SDL_SWSURFACE, canvas->w, canvas->h,
|
|
canvas->format->BitsPerPixel, canvas->format->Rmask,
|
|
canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
|
|
|
|
if (ascii_snapshot != NULL)
|
|
{
|
|
/* FIXME: When switching from PAINT to FULLSCREEN mode,
|
|
* we switch out & back in, which means we take a fresh
|
|
* snapshot even though we didn't leave the overall tool,
|
|
* which is less than ideal. -bjk 2024.09.27 */
|
|
SDL_BlitSurface(canvas, NULL, ascii_snapshot, NULL);
|
|
}
|
|
}
|
|
|
|
void ascii_switchout(magic_api * api ATTRIBUTE_UNUSED,
|
|
int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, SDL_Surface * canvas ATTRIBUTE_UNUSED)
|
|
{
|
|
}
|
|
|
|
int ascii_modes(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED)
|
|
{
|
|
return (MODE_PAINT | MODE_FULLSCREEN);
|
|
}
|
|
|
|
|
|
Uint8 ascii_accepted_sizes(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED)
|
|
{
|
|
if (mode == MODE_PAINT)
|
|
return 6;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
Uint8 ascii_default_size(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode)
|
|
{
|
|
if (mode == MODE_PAINT)
|
|
return 3;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void ascii_set_size(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED,
|
|
SDL_Surface * canvas ATTRIBUTE_UNUSED, SDL_Surface * last ATTRIBUTE_UNUSED,
|
|
Uint8 size, SDL_Rect * update_rect ATTRIBUTE_UNUSED)
|
|
{
|
|
ascii_size = size;
|
|
}
|
|
|
|
void do_ascii_effect(void *ptr, int which, SDL_Surface * canvas, SDL_Surface * last ATTRIBUTE_UNUSED, int x, int y)
|
|
{
|
|
magic_api *api = (magic_api *) ptr;
|
|
int w, h, n, sx, sy, xx, yy, ww, brightness, rr, gg, bb;
|
|
Uint8 r, g, b;
|
|
Uint32 clear_pixel;
|
|
Uint8 clear_brightness;
|
|
SDL_Rect src, dest;
|
|
int computer_color = 0;
|
|
|
|
if (which == TOOL_COMPUTER_COLOR)
|
|
{
|
|
which = TOOL_COMPUTER;
|
|
computer_color = 1;
|
|
}
|
|
|
|
w = ascii_char_maxwidth[which];
|
|
h = ascii_bitmap[which]->h;
|
|
|
|
x = (x / w) * w;
|
|
y = (y / h) * h;
|
|
|
|
for (sy = y - (h * (ascii_size - 1)); sy <= y + (h * (ascii_size - 1)); sy += h)
|
|
{
|
|
for (sx = x - (w * (ascii_size - 1)); sx <= x + (w * (ascii_size - 1)); sx += w)
|
|
{
|
|
if (!api->touched(sx, sy))
|
|
{
|
|
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);
|
|
|
|
brightness = 0;
|
|
rr = 0;
|
|
gg = 0;
|
|
bb = 0;
|
|
for (yy = sy; yy < sy + h; yy++)
|
|
{
|
|
for (xx = sx; xx < sx + w; xx++)
|
|
{
|
|
SDL_GetRGB(api->getpixel(ascii_snapshot, xx, yy), ascii_snapshot->format, &r, &g, &b);
|
|
brightness += get_bright(api, r, g, b);
|
|
|
|
if (computer_color)
|
|
{
|
|
rr += r;
|
|
gg += g;
|
|
bb += b;
|
|
}
|
|
}
|
|
}
|
|
brightness = brightness / (w * h);
|
|
|
|
|
|
/* Background (and also, all we draw, if "Space") */
|
|
dest.x = sx;
|
|
dest.y = sy;
|
|
dest.w = w;
|
|
dest.h = h;
|
|
|
|
SDL_FillRect(canvas, &dest, clear_pixel);
|
|
|
|
|
|
if (brightness != clear_brightness)
|
|
{
|
|
/* A visible character */
|
|
|
|
if (computer_color)
|
|
{
|
|
int i, best;
|
|
|
|
/* Find the best color, based on the avg. of the
|
|
pixels we're replacing */
|
|
rr /= (w * h);
|
|
gg /= (w * h);
|
|
bb /= (w * h);
|
|
|
|
DEBUG_PRINTF("avg is %02x%02x%02x; ", rr, gg, bb);
|
|
|
|
/* Map each RGB component to _plausible_ values (0x00, 0x55, 0xAA, 0xFF) */
|
|
if (rr < 0x40)
|
|
rr = 0x00;
|
|
else if (rr <= 0x80)
|
|
rr = 0x55;
|
|
else if (rr <= 0xC0)
|
|
rr = 0xAA;
|
|
else
|
|
rr = 0xFF;
|
|
|
|
if (gg < 0x40)
|
|
gg = 0x00;
|
|
else if (gg <= 0x80)
|
|
gg = 0x55;
|
|
else if (gg <= 0xC0)
|
|
gg = 0xAA;
|
|
else
|
|
gg = 0xFF;
|
|
|
|
|
|
if (bb < 0x40)
|
|
bb = 0x00;
|
|
else if (bb <= 0x80)
|
|
bb = 0x55;
|
|
else if (bb <= 0xC0)
|
|
bb = 0xAA;
|
|
else
|
|
bb = 0xFF;
|
|
|
|
best = -1;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if (rr == ascii_computer_colors[i][0] &&
|
|
gg == ascii_computer_colors[i][1] &&
|
|
bb == ascii_computer_colors[i][2])
|
|
{
|
|
/* Exact match */
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
if (best == -1)
|
|
{
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if ((rr == ascii_computer_colors[i][0] &&
|
|
gg == ascii_computer_colors[i][1] &&
|
|
abs(bb - ascii_computer_colors[i][2]) <= 0x55) ||
|
|
(gg == ascii_computer_colors[i][1] &&
|
|
bb == ascii_computer_colors[i][2] &&
|
|
abs(rr - ascii_computer_colors[i][0]) <= 0x55) ||
|
|
(bb == ascii_computer_colors[i][2] &&
|
|
rr == ascii_computer_colors[i][0] &&
|
|
abs(gg - ascii_computer_colors[i][1]) <= 0x55))
|
|
{
|
|
/* Very close match */
|
|
best = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best == -1)
|
|
{
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if ((rr == ascii_computer_colors[i][0] &&
|
|
abs(gg - ascii_computer_colors[i][1]) <= 0x55 &&
|
|
abs(bb - ascii_computer_colors[i][2]) <= 0x55) ||
|
|
(gg == ascii_computer_colors[i][1] &&
|
|
abs(bb - ascii_computer_colors[i][2]) <= 0x55 &&
|
|
abs(rr - ascii_computer_colors[i][0]) <= 0x55) ||
|
|
(bb == ascii_computer_colors[i][2] &&
|
|
abs(rr - ascii_computer_colors[i][0]) <= 0x55 &&
|
|
abs(gg - ascii_computer_colors[i][1]) <= 0x55))
|
|
{
|
|
/* Pretty close match */
|
|
best = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_PRINTF("best for %02x%02x%02x = %d: ", rr, gg, bb, best);
|
|
if (best == -1)
|
|
best = 0; // oops!
|
|
rr = ascii_computer_colors[best][0];
|
|
gg = ascii_computer_colors[best][1];
|
|
bb = ascii_computer_colors[best][2];
|
|
DEBUG_PRINTF("%02x%02x%02x\n", rr, gg, bb);
|
|
}
|
|
else
|
|
{
|
|
/* Use the user-chosen color */
|
|
rr = ascii_r;
|
|
gg = ascii_g;
|
|
bb = ascii_b;
|
|
}
|
|
|
|
|
|
/* Blit the glyph */
|
|
n = get_best_char(which, brightness);
|
|
|
|
ww = ascii_char_x[which][n + 1] - ascii_char_x[which][n];
|
|
|
|
dest.x = sx + (w - ww) / 2;
|
|
dest.y = sy;
|
|
dest.w = ww;
|
|
dest.h = h;
|
|
SDL_FillRect(canvas, &dest, SDL_MapRGB(canvas->format, rr, gg, bb));
|
|
|
|
src.x = ascii_char_x[which][n];
|
|
src.y = 0;
|
|
src.w = ww;
|
|
src.h = h;
|
|
|
|
dest.x = sx + (w - src.w) / 2;
|
|
dest.y = sy;
|
|
|
|
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);
|
|
}
|