You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

486 lines
9.7 KiB

#include <SDL2/SDL.h>
#include <stdio.h>
#define HOR 32
#define VER 16
#define PAD 8
#define ZOOM 2
#define color1 0x000000
#define color2 0x72DEC2
#define color3 0xFFFFFF
#define color4 0x444444
#define color0 0x111111
#define SZ (HOR * VER * 16)
typedef struct Brush {
int x, y, px, py;
int mode, size, color;
int down, edit, erase;
} Brush;
char *modes[] = {
"line",
"tone",
"bold",
"full",
"hori",
"veri",
"exes",
"fixe"};
int colors[] = {color1, color2, color3, color4, color0};
int WIDTH = 8 * HOR + PAD * 2;
int HEIGHT = 8 * VER + PAD * 2;
int FPS = 30;
int GUIDES = 0;
SDL_Window *gWindow = NULL;
SDL_Renderer *gRenderer = NULL;
SDL_Texture *gTexture = NULL;
unsigned char chrbuf[SZ];
Uint32 *pixels;
/* helpers */
int
distance(int ax, int ay, int bx, int by)
{
return (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
}
int
spos(char *s, char *ss)
{
int a = 0, b = 0;
while(s[a] != '\0') {
if(s[a] == ss[b]) {
if(ss[b + 1] == '\0')
return a - b;
b++;
} else
b = 0;
a++;
}
return -1;
}
int
getclr(int r, int g, int b)
{
return r && g && b && r == g && g == b ? 1 : r > g && r > b ? 2 : g > r && g > b ? 3 : 0;
}
/* chr */
int
rowchr(int x, int y)
{
return (y % 8) + ((x / 8 + y / 8 * HOR) * 16);
}
int
getchr(int x, int y)
{
int ch1, ch2;
int r = rowchr(x, y);
int px = x % 8;
if(r < 0 || r > SZ - 8)
return 0;
ch1 = (chrbuf[r] >> (7 - px)) & 1;
ch2 = (chrbuf[r + 8] >> (7 - px)) & 1;
return ch1 && !ch2 ? 1 : !ch1 && ch2 ? 2 : ch1 && ch2 ? 3 : 0;
}
void
putchr(int x, int y, int color)
{
int r = rowchr(x, y);
int px = x % 8;
if(x < 0 || y < 0 || x > 8 * HOR || y > 8 * VER || r > SZ - 8)
return;
if(color == 0) {
chrbuf[r] &= ~(1UL << (7 - px));
chrbuf[r + 8] &= ~(1UL << (7 - px));
} else if(color == 2) {
chrbuf[r] |= 1UL << (7 - px);
chrbuf[r + 8] &= ~(1UL << (7 - px));
} else if(color == 1) {
chrbuf[r] &= ~(1UL << (7 - px));
chrbuf[r + 8] |= 1UL << (7 - px);
} else if(color == 3) {
chrbuf[r] |= 1UL << (7 - px);
chrbuf[r + 8] |= 1UL << (7 - px);
}
}
void
newchr(void)
{
int i;
for(i = 0; i < SZ; ++i)
chrbuf[i] = 0x00;
}
int
jagg(int x, int y)
{
int n = getchr(x, y + 1);
int e = getchr(x + 1, y);
int s = getchr(x, y - 1);
int w = getchr(x - 1, y);
int h = getchr(x, y);
if(h == n && h == e && h != s && h != w)
return 1;
if(h == e && h == s && h != w && h != n)
return 1;
if(h == s && h == w && h != n && h != e)
return 1;
if(h == w && h == n && h != e && h != s)
return 1;
return 0;
}
int
patt(int x, int y, int mode, int size)
{
if(mode == 1)
return ((x + y) % 4) == 0 && ((y - x) % 4) == 0;
if(mode == 2)
return ((x + y) % 2) == 0 && ((y - x) % 2) == 0;
if(mode == 3)
return 1;
if(mode == 4)
return y % size == 0;
if(mode == 5)
return x % size == 0;
if(mode == 6)
return (x + y) % size == 0 || (x - y) % size == 0;
return 0;
}
void
fill(int x, int y, int mode, int size, int color)
{
int ox, oy;
for(ox = x - (size / 2); ox < x + size; ++ox)
for(oy = y - (size / 2); oy < y + size; ++oy)
if(mode == 7 && jagg(ox, oy))
putchr(ox, oy, 0);
else if(patt(ox, oy, mode, size) && distance(x, y, ox, oy) < size)
putchr(ox, oy, color);
}
void
line(int ax, int ay, int bx, int by, int color)
{
int dx = abs(bx - ax), sx = ax < bx ? 1 : -1;
int dy = -abs(by - ay), sy = ay < by ? 1 : -1;
int err = dx + dy, e2;
for(;;) {
putchr(ax, ay, color);
if(ax == bx && ay == by)
break;
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
ax += sx;
}
if(e2 <= dx) {
err += dx;
ay += sy;
}
}
}
/* draw */
void
drawtile(Uint32 *dst, int x, int y, int id)
{
int v, h, offset = id * 16, sz = WIDTH * HEIGHT;
for(v = 0; v < 8; v++)
for(h = 0; h < 8; h++) {
int px = (x * 8) + (8 - h);
int py = (y * 8) + v;
int ch1 = chrbuf[offset + v];
int ch2 = chrbuf[offset + v + 8];
int clr = ((ch1 >> h) & 0x1) + (((ch2 >> h) & 0x1) << 1);
int key = (py + PAD) * WIDTH + (px + PAD);
if(key >= 0 && key <= sz - 1)
dst[key] = colors[clr];
}
}
void
draw(Uint32 *dst)
{
int x, y;
for(y = 0; y < VER; ++y)
for(x = 0; x < HOR; ++x)
drawtile(dst, x, y, x + y * HOR);
SDL_UpdateTexture(gTexture, NULL, dst, WIDTH * sizeof(Uint32));
SDL_RenderClear(gRenderer);
SDL_RenderCopy(gRenderer, gTexture, NULL, NULL);
SDL_RenderPresent(gRenderer);
}
void
update(Brush *b)
{
if(b->edit)
SDL_SetWindowTitle(gWindow, "Nasu*");
else
SDL_SetWindowTitle(gWindow, "Nasu");
printf("%s %d:%d [%d:%dx%d]\n",
modes[b->mode],
b->size,
b->color,
HOR,
VER,
ZOOM);
}
/* options */
int
error(char *msg, const char *err)
{
printf("Error %s: %s\n", msg, err);
return 0;
}
void
loadchr(char *path)
{
FILE *f = fopen(path, "rb");
if(f == NULL)
error("Load", "Invalid input file");
if(!fread(chrbuf, sizeof(chrbuf), 1, f))
error("Load", "Invalid input size");
fclose(f);
}
void
loadbmp(char *path)
{
FILE *f = fopen(path, "rb");
int i, width = HOR * 8, height = VER * 8, size = 3 * width * height;
unsigned char header[54];
unsigned char data[4096 * 256];
if(!fread(header, sizeof(unsigned char), 54, f))
error("Load", "Invalid bmp header");
if(!fread(data, sizeof(unsigned char), size, f))
error("Load", "Invalid bmp body");
for(i = 0; i < size; i += 3)
putchr((i / 3) % width, height - (i / 3) / width - 1, getclr(data[i + 2], data[i + 1], data[i]));
fclose(f);
}
void
tochr(Brush *b)
{
FILE *f = fopen("nasu-export.chr", "wb");
if(!fwrite(chrbuf, sizeof(chrbuf), 1, f))
error("Save", "Invalid output file");
fclose(f);
b->edit = 0;
}
void
tobmp(void)
{
SDL_Surface *surface = SDL_GetWindowSurface(gWindow);
GUIDES = 0;
draw(pixels);
SDL_RenderReadPixels(gRenderer,
NULL,
SDL_PIXELFORMAT_ARGB8888,
surface->pixels,
surface->pitch);
SDL_SaveBMP(surface, "nasu-render.bmp");
SDL_FreeSurface(surface);
}
void
quit(void)
{
free(pixels);
SDL_DestroyTexture(gTexture);
gTexture = NULL;
SDL_DestroyRenderer(gRenderer);
gRenderer = NULL;
SDL_DestroyWindow(gWindow);
gWindow = NULL;
SDL_Quit();
exit(0);
}
void
domouse(SDL_Event *event, Brush *b)
{
switch(event->type) {
case SDL_MOUSEBUTTONUP:
if(event->button.button == SDL_BUTTON_LEFT) {
b->down = 0;
b->px = 0;
b->py = 0;
}
if(event->button.button == SDL_BUTTON_RIGHT)
b->erase = 0;
b->edit = 1;
update(b);
break;
case SDL_MOUSEBUTTONDOWN:
if(event->button.button == SDL_BUTTON_LEFT) {
b->down = 1;
}
if(event->button.button == SDL_BUTTON_RIGHT)
b->erase = 1;
if(event->button.button == SDL_BUTTON_MIDDLE) {
b->erase = 0;
if(b->px != 0 && b->py != 0) {
b->x = (event->motion.x - (PAD * ZOOM)) / ZOOM;
b->y = (event->motion.y - (PAD * ZOOM)) / ZOOM;
line(b->px, b->py, b->x, b->y, b->erase ? 0 : b->color);
draw(pixels);
}
}
b->px = (event->motion.x - (PAD * ZOOM)) / ZOOM;
b->py = (event->motion.y - (PAD * ZOOM)) / ZOOM;
if(b->down) {
putchr(b->px, b->py, b->color);
draw(pixels);
}
break;
case SDL_MOUSEMOTION:
if(b->down) {
b->x = (event->motion.x - (PAD * ZOOM)) / ZOOM;
b->y = (event->motion.y - (PAD * ZOOM)) / ZOOM;
if(b->mode == 0)
line(b->px, b->py, b->x, b->y, b->erase ? 0 : b->color);
else
fill(b->x, b->y, b->mode, b->size, b->erase ? 0 : b->color);
draw(pixels);
b->px = b->x;
b->py = b->y;
}
break;
}
}
void
dokey(SDL_Event *event, Brush *b)
{
switch(event->key.keysym.sym) {
case SDLK_ESCAPE: quit(); break;
case SDLK_e: tochr(b); break;
case SDLK_r: tobmp(); break;
case SDLK_TAB: b->color = b->color > 2 ? 0 : b->color + 1; break;
case SDLK_BACKQUOTE: b->mode = 7; break;
case SDLK_1: b->mode = 0; break;
case SDLK_2: b->mode = 1; break;
case SDLK_3: b->mode = 2; break;
case SDLK_4: b->mode = 3; break;
case SDLK_5: b->mode = 4; break;
case SDLK_6: b->mode = 5; break;
case SDLK_7: b->mode = 6; break;
case SDLK_h:
GUIDES = !GUIDES;
draw(pixels);
break;
case SDLK_n:
newchr();
draw(pixels);
break;
case SDLK_z:
if(b->size > 1)
b->size -= 1;
break;
case SDLK_x:
if(b->size < 30)
b->size += 1;
break;
}
update(b);
}
int
init(void)
{
int i, j;
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return error("Init", SDL_GetError());
gWindow = SDL_CreateWindow("Nasu",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
WIDTH * ZOOM,
HEIGHT * ZOOM,
SDL_WINDOW_SHOWN);
if(gWindow == NULL)
return error("Window", SDL_GetError());
gRenderer = SDL_CreateRenderer(gWindow, -1, 0);
if(gRenderer == NULL)
return error("Renderer", SDL_GetError());
gTexture = SDL_CreateTexture(gRenderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STATIC,
WIDTH,
HEIGHT);
if(gTexture == NULL)
return error("Texture", SDL_GetError());
pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32));
if(pixels == NULL)
return error("Pixels", "Failed to allocate memory");
for(i = 0; i < HEIGHT; i++)
for(j = 0; j < WIDTH; j++)
pixels[i * WIDTH + j] = color1;
return 1;
}
int
main(int argc, char **argv)
{
int ticknext = 0;
Brush brush;
brush.down = 0;
brush.color = 1;
brush.edit = 0;
brush.size = 10;
brush.mode = 0;
if(!init())
return error("Init", "Failure");
if(argc > 1 && spos(argv[1], ".bmp") > -1)
loadbmp(argv[1]);
else if(argc > 1 && spos(argv[1], ".chr") > -1)
loadchr(argv[1]);
else
newchr();
draw(pixels);
update(&brush);
while(1) {
int tick = SDL_GetTicks();
SDL_Event event;
if(tick < ticknext)
SDL_Delay(ticknext - tick);
ticknext = tick + (1000 / FPS);
while(SDL_PollEvent(&event) != 0) {
if(event.type == SDL_QUIT)
quit();
else if(event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEMOTION) {
domouse(&event, &brush);
} else if(event.type == SDL_KEYDOWN)
dokey(&event, &brush);
else if(event.type == SDL_WINDOWEVENT)
if(event.window.event == SDL_WINDOWEVENT_EXPOSED)
draw(pixels);
}
}
quit();
return 0;
}