#include <SDL2/SDL.h>
#include <stdio.h>
#include <math.h>
#define HOR 32
#define VER 16
#define PAD 8
#define ZOOM 2
#define color1 0x000000
#define color2 0x72DEC2
#define color3 0x888888
#define color4 0xFFFFFF
#define color0 0x222222
#define PLIMIT 256
#define SZ (HOR * VER * 16)
#define PI 3.14159265358979323846
typedef enum {
POINT,
LINE,
ARC,
BEZIER,
RECTANGLE,
ELLIPSE
} LineType;
typedef struct {
int x, y;
} Point2d;
typedef struct {
Point2d points[PLIMIT];
LineType type;
int color, len;
} Path2d;
typedef struct {
Path2d paths[PLIMIT];
int len;
} Shape2d;
Path2d stack;
Shape2d shape;
Point2d* selection;
unsigned char chrbuf[SZ];
int colors[] = {color1, color2, color3, color4, color0};
int WIDTH = 8 * HOR + PAD * 2;
int HEIGHT = 8 * VER + PAD * 2;
int FPS = 30;
int GUIDES = 1;
int COLOR = 3;
SDL_Window* gWindow = NULL;
SDL_Renderer* gRenderer = NULL;
SDL_Texture* gTexture = NULL;
uint32_t* pixels;
/* helpers */
Point2d
Pt2d(int x, int y)
{
Point2d p;
p.x = x;
p.y = y;
return p;
}
Point2d
clampt(Point2d p, int step)
{
p.x = abs((p.x + step / 2) / step) * step;
p.y = abs((p.y + step / 2) / step) * step;
return p;
}
int
equpt(Point2d* a, Point2d* b)
{
return a->x == b->x && a->y == b->y;
}
int
slen(char* s)
{
int n = 0;
while(s[n] != '\0' && s[++n])
;
return n;
}
int
scmp(char* a, char* b)
{
int i, l = slen(a);
if(l != slen(b))
return 0;
for(i = 0; i < l; ++i)
if(a[i] != b[i])
return 0;
return 1;
}
int
cpos(char* s, char c)
{
int i;
for(i = 0; i < slen(s); i++)
if(s[i] == c)
return i;
return -1;
}
int
sint(char* s, int len)
{
int num = 0, i = 0;
while(s[i] && i < len && (s[i] >= '0' && s[i] <= '9')) {
num = num * 10 + (s[i] - '0');
i++;
}
return num;
}
/* chr */
int
rowchr(int x, int y)
{
return (y % 8) + ((x / 8 + y / 8 * HOR) * 16);
}
int
getchr(int x, int y)
{
int r = rowchr(x, y), px = 7 - (x % 8), ch1, ch2;
if(r < 0 || r > SZ - 8)
return 0;
ch1 = (chrbuf[r] >> px) & 1;
ch2 = (chrbuf[r + 8] >> 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), px = 7 - (x % 8);
if(x < 0 || y < 0 || x > 8 * HOR || y > 8 * VER || r > SZ - 8)
return;
if(color == 0) {
chrbuf[r] &= ~(1UL << px);
chrbuf[r + 8] &= ~(1UL << px);
} else if(color == 2) {
chrbuf[r] |= 1UL << px;
chrbuf[r + 8] &= ~(1UL << px);
} else if(color == 1) {
chrbuf[r] &= ~(1UL << px);
chrbuf[r + 8] |= 1UL << px;
} else if(color == 3) {
chrbuf[r] |= 1UL << px;
chrbuf[r + 8] |= 1UL << px;
}
}
void
newchr(void)
{
int i;
for(i = 0; i < SZ; ++i)
chrbuf[i] = 0x00;
}
void
line(int x0, int y0, int x1, int y1, int color)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
for(;;) {
putchr(x0, y0, color);
if(x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
x0 += sx;
}
if(e2 <= dx) {
err += dx;
y0 += sy;
}
}
}
void
ellipse(int x0, int y0, int x1, int y1, int cadran, int color)
{
int a = abs(x1 - x0), b = abs(y1 - y0), b1 = b & 1;
double dx = 4 * (1 - a) * b * b, dy = 4 * (b1 + 1) * a * a;
double err = dx + dy + b1 * a * a, e2;
if(x0 == x1 || y0 == y1)
return;
if(x0 > x1) {
x0 = x1;
x1 += a;
}
if(y0 > y1)
y0 = y1;
y0 += (b + 1) / 2;
y1 = y0 - b1;
a *= 8 * a;
b1 = 8 * b * b;
do {
if(cadran == -1 || cadran == 1)
putchr(x1, y0, color);
if(cadran == -1 || cadran == 2)
putchr(x0, y0, color);
if(cadran == -1 || cadran == 3)
putchr(x0, y1, color);
if(cadran == -1 || cadran == 0)
putchr(x1, y1, color);
e2 = 2 * err;
if(e2 <= dy) {
y0++;
y1--;
err += dy += a;
}
if(e2 >= dx || 2 * err > dy) {
x0++;
x1--;
err += dx += b1;
}
} while(x0 <= x1);
}
void
arc(int x0, int y0, int x1, int y1, int x2, int y2, int color)
{
int cadran, col = (y1 - y0) * (x2 - x1) - (y2 - y1) * (x1 - x0);
x1 = x2;
y1 = y2;
if(x2 > x0 && y2 > y0) {
if(col < 0) {
x0 = x0 - (x2 - x0);
y1 = y2 + (y2 - y0);
cadran = 0;
} else {
y0 = y0 - (y2 - y0);
x1 = x2 + (x2 - x0);
cadran = 2;
}
} else if(x2 < x0 && y2 > y0) {
if(col < 0) {
y0 = y0 - (y2 - y0);
x1 = x2 + (x2 - x0);
cadran = 1;
} else {
x0 = x0 - (x2 - x0);
y1 = y2 + (y2 - y0);
cadran = 3;
}
} else if(x2 < x0 && y2 < y0) {
if(col < 0) {
x0 = x0 - (x2 - x0);
y1 = y2 + (y2 - y0);
cadran = 2;
} else {
y0 = y0 - (y2 - y0);
x1 = x2 + (x2 - x0);
cadran = 0;
}
} else {
if(col < 0) {
y0 = y0 - (y2 - y0);
x1 = x2 + (x2 - x0);
cadran = 3;
} else {
x0 = x0 - (x2 - x0);
y1 = y2 + (y2 - y0);
cadran = 1;
}
}
ellipse(x0, y0, x1, y1, cadran, color);
}
void
bezier(int x0, int y0, int x1, int y1, int x2, int y2, int color)
{
int sx = x2 - x1, sy = y2 - y1;
long xx = x0 - x1, yy = y0 - y1, xy;
double dx, dy, err, cur = xx * sy - yy * sx;
if(xx * sx > 0 || yy * sy > 0)
return;
if(sx * (long)sx + sy * (long)sy > xx * xx + yy * yy) {
x2 = x0;
x0 = sx + x1;
y2 = y0;
y0 = sy + y1;
cur = -cur;
}
if(cur != 0) {
xx += sx;
xx *= sx = x0 < x2 ? 1 : -1;
yy += sy;
yy *= sy = y0 < y2 ? 1 : -1;
xy = 2 * xx * yy;
xx *= xx;
yy *= yy;
if(cur * sx * sy < 0) {
xx = -xx;
yy = -yy;
xy = -xy;
cur = -cur;
}
dx = 4.0 * sy * cur * (x1 - x0) + xx - xy;
dy = 4.0 * sx * cur * (y0 - y1) + yy - xy;
xx += xx;
yy += yy;
err = dx + dy + xy;
do {
putchr(x0, y0, color);
if(x0 == x2 && y0 == y2)
return;
y1 = 2 * err < dx;
if(2 * err > dy) {
x0 += sx;
dx -= xy;
err += dy += yy;
}
if(y1) {
y0 += sy;
dy -= xy;
err += dx += xx;
}
} while(dy < dx);
}
line(x0, y0, x2, y2, color);
}
void
rectangle(int x0, int y0, int x1, int y1, int color)
{
line(x0, y0, x0, y1, color);
line(x0, y1, x1, y1, color);
line(x1, y1, x1, y0, color);
line(x1, y0, x0, y0, color);
}
void
handle(int ax, int ay, int r, int color)
{
line(ax, ay - r, ax + r, ay, color);
line(ax + r, ay, ax, ay + r, color);
line(ax, ay + r, ax - r, ay, color);
line(ax - r, ay, ax, ay - r, color);
}
/* draw */
int
guide(int x, int y)
{
if(!GUIDES)
return 0;
if(x % 32 == 0 && y % 32 == 0)
return 3;
else if(x % 8 == 0 && y % 8 == 0)
return 4;
return 0;
}
void
draw(uint32_t* dst)
{
int b, i, j, id = 0;
for(b = 0; b < SZ; b += 16)
for(i = 0; i < 8; i++)
for(j = 7; j >= 0; j--) {
int ch1 = chrbuf[b + i];
int ch2 = chrbuf[b + i + 8];
int color = ((ch1 >> j) & 0x1) + (((ch2 >> j) & 0x1) << 1);
int ti = id / 64;
int px = (ti / (HOR * VER)) * (8 * HOR) + (ti % HOR) * 8 + (id % 8);
int py = ((ti / HOR) * 8) + ((id % 64) / 8);
dst[(py + PAD) * WIDTH + (px + PAD)] = colors[color == 0 ? guide(px, py) : color];
id++;
}
SDL_UpdateTexture(gTexture, NULL, dst, WIDTH * sizeof(uint32_t));
SDL_RenderClear(gRenderer);
SDL_RenderCopy(gRenderer, gTexture, NULL, NULL);
SDL_RenderPresent(gRenderer);
}
void
update(void)
{
printf("[%d:%dx%d]\n",
HOR,
VER,
ZOOM);
}
void
renderpath(Path2d* path, int guides)
{
int j;
/* Draw control points */
for(j = 0; j < path->len; ++j)
if(guides)
handle(path->points[j].x, path->points[j].y, 2, 1);
/* Draw paths */
for(j = 0; j < path->len; ++j) {
if(j < path->len - 1) {
if(path->type == LINE)
line(path->points[j].x, path->points[j].y, path->points[j + 1].x, path->points[j + 1].y, path->color);
else if(path->type == RECTANGLE)
rectangle(path->points[j].x, path->points[j].y, path->points[j + 1].x, path->points[j + 1].y, path->color);
else if(path->type == ELLIPSE)
ellipse(path->points[j].x, path->points[j].y, path->points[j + 1].x, path->points[j + 1].y, -1, path->color);
}
if(j < path->len - 2) {
if(path->type == ARC)
arc(path->points[j].x, path->points[j].y, path->points[j + 1].x, path->points[j + 1].y, path->points[j + 2].x, path->points[j + 2].y, path->color);
else if(path->type == BEZIER)
bezier(path->points[j].x, path->points[j].y, path->points[j + 1].x, path->points[j + 1].y, path->points[j + 2].x, path->points[j + 2].y, path->color);
if(path->type == ARC || path->type == BEZIER)
j++;
}
if(path->type == POINT)
putchr(path->points[j].x, path->points[j].y, path->color);
}
}
void
render(void)
{
int i, j;
newchr();
/* draw shape */
for(i = 0; i < shape.len; i++) {
renderpath(&shape.paths[i], GUIDES && i == shape.len - 1);
}
/* draw stack */
for(j = 0; j < stack.len; ++j) {
handle(stack.points[j].x,
stack.points[j].y,
2, 1);
if(j < stack.len - 1)
line(stack.points[j].x, stack.points[j].y, stack.points[j + 1].x, stack.points[j + 1].y, 1);
}
draw(pixels);
}
/* options */
void
addpoint(Point2d touch)
{
if(stack.len > 0 && equpt(&touch, &stack.points[stack.len - 1]))
return;
stack.points[stack.len] = touch;
stack.len++;
render();
}
void
selectpoint(Point2d* touch)
{
int i, j;
selection = NULL;
for(i = 0; i < shape.len; ++i) {
Path2d* p = &shape.paths[i];
for(j = 0; j < p->len; ++j)
if(equpt(touch, &p->points[j]))
selection = &p->points[j];
}
}
void
dragpoint(Point2d* touch)
{
if(equpt(touch, selection))
return;
selection->x = touch->x;
selection->y = touch->y;
stack.len = 0;
render();
}
void
copypath(Path2d* src, Path2d* dst)
{
int i;
dst->len = src->len;
for(i = 0; i < dst->len; ++i) {
dst->points[i].x = src->points[i].x;
dst->points[i].y = src->points[i].y;
}
}
int
cancast(LineType type)
{
if(type == POINT)
return 1;
else if(type == LINE || type == RECTANGLE || type == ELLIPSE) {
if(stack.len > 1)
return 1;
} else if(type == ARC || type == BEZIER)
if(stack.len > 2)
return 1;
return 0;
}
void
cast(LineType type)
{
if(!cancast(type))
return;
copypath(&stack, &shape.paths[shape.len]);
shape.paths[shape.len].color = COLOR;
shape.paths[shape.len].type = type;
shape.len++;
stack.len = 0;
render();
}
int
error(char* msg, const char* err)
{
printf("Error %s: %s\n", msg, err);
return 0;
}
void
totxt(void)
{
int i, j, c = -1;
FILE* f = fopen("dotgrid-shape.txt", "w");
for(i = 0; i < shape.len; ++i) {
Path2d* p = &shape.paths[i];
if(c != p->color) {
fprintf(f, "%d setcolor\n", p->color);
c = p->color;
}
for(j = 0; j < p->len; ++j)
fprintf(f, "%d,%d ", p->points[j].x, p->points[j].y);
fprintf(f, "%s\n", p->type == POINT ? "point" : p->type == LINE ? "line" : p->type == ARC ? "arc" : p->type == RECTANGLE ? "rectangle" : p->type == ELLIPSE ? "ellipse" : "bezier");
}
fclose(f);
}
void
tochr(void)
{
FILE* f = fopen("dotgrid-export.chr", "wb");
if(!fwrite(chrbuf, sizeof(chrbuf), 1, f))
error("Save", "Invalid output file");
fclose(f);
}
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, "dotgrid-render.bmp");
SDL_FreeSurface(surface);
}
void
loadtxt(FILE* f)
{
char line[256], query[256];
int i = 0, querylen = 0, setting = 0;
if(!f)
return;
while(fgets(line, 256, f)) {
if(line[0] == ';')
continue;
i = 0;
while(line[i]) {
if(line[i] == ' ' || line[i] == '\n' || line[i] == '\0') {
int c = cpos(query, ',');
if(c >= 0)
addpoint(Pt2d(
sint(query, c),
sint(query + c + 1, slen(query) - c - 1)));
else if(i > 0 && scmp(query, "setcolor"))
COLOR = setting < 0 ? 0 : setting > 3 ? 3 : setting;
else if(scmp(query, "line"))
cast(LINE);
else if(scmp(query, "arc"))
cast(ARC);
else if(scmp(query, "bezier"))
cast(BEZIER);
else if(scmp(query, "point"))
cast(POINT);
else if(scmp(query, "rectangle"))
cast(RECTANGLE);
else if(scmp(query, "ellipse"))
cast(ELLIPSE);
else if(slen(query) == 1)
setting = query[0] - '0';
querylen = 0;
query[0] = '\0';
} else {
query[querylen++] = line[i];
query[querylen] = '\0';
}
i++;
}
}
fclose(f);
}
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)
{
Point2d touch = clampt(
Pt2d(
(event->motion.x - (PAD * ZOOM)) / ZOOM,
(event->motion.y - (PAD * ZOOM)) / ZOOM),
8);
switch(event->type) {
case SDL_MOUSEBUTTONUP:
if(selection && !equpt(&touch, selection))
dragpoint(&touch);
else
addpoint(touch);
selection = NULL;
break;
case SDL_MOUSEBUTTONDOWN:
selectpoint(&touch);
break;
case SDL_MOUSEMOTION:
break;
}
}
void
dokey(SDL_Event* event)
{
switch(event->key.keysym.sym) {
case SDLK_ESCAPE:
stack.len = 0;
render();
break;
case SDLK_BACKSPACE:
stack.len = 0;
if(shape.len > 0)
shape.len--;
render();
break;
case SDLK_e:
tochr();
break;
case SDLK_r:
tobmp();
break;
case SDLK_t:
totxt();
break;
case SDLK_a:
cast(LINE);
break;
case SDLK_s:
cast(ARC);
break;
case SDLK_d:
cast(BEZIER);
break;
case SDLK_z:
cast(POINT);
break;
case SDLK_x:
cast(RECTANGLE);
break;
case SDLK_c:
cast(ELLIPSE);
break;
case SDLK_1:
COLOR = 1;
break;
case SDLK_2:
COLOR = 2;
break;
case SDLK_3:
COLOR = 3;
break;
case SDLK_TAB:
break;
case SDLK_h:
GUIDES = !GUIDES;
render();
break;
case SDLK_n:
newchr();
shape.len = 0;
stack.len = 0;
draw(pixels);
break;
}
/* update(); */
}
int
init(void)
{
int i, j;
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return error("Init", SDL_GetError());
gWindow = SDL_CreateWindow("Dotgrid",
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_t*)malloc(WIDTH * HEIGHT * sizeof(uint32_t));
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;
if(!init())
return error("Init", "Failure");
newchr();
if(argc > 1)
loadtxt(fopen(argv[1], "r"));
draw(pixels);
update();
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);
} else if(event.type == SDL_KEYDOWN)
dokey(&event);
else if(event.type == SDL_WINDOWEVENT)
if(event.window.event == SDL_WINDOWEVENT_EXPOSED)
draw(pixels);
}
}
quit();
return 0;
}
— Found a mistake? Submit an edit to dotgrid.c.txt (797 lines)