533 lines
15 KiB
C
533 lines
15 KiB
C
/**
|
|
* Copyright (c) 2016 rxi
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify it
|
|
* under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <pc.h>
|
|
#include "palette.h"
|
|
#include "image.h"
|
|
#include "font.h"
|
|
#include "quad.h"
|
|
#include "vga.h"
|
|
#include "luaobj.h"
|
|
|
|
image_t *graphics_screen;
|
|
font_t *graphics_defaultFont;
|
|
|
|
image_t *graphics_canvas;
|
|
font_t *graphics_font;
|
|
pixel_t graphics_backgroundColor;
|
|
int graphics_backgroundColor_rgb[3];
|
|
pixel_t graphics_color;
|
|
int graphics_color_rgb[3];
|
|
int graphics_blendMode;
|
|
int graphics_flip;
|
|
|
|
|
|
static int getColorFromArgs(lua_State *L, int *rgb, const int *def) {
|
|
int r, g, b;
|
|
if ( lua_isnoneornil(L, 1) ) {
|
|
r = def[0];
|
|
g = def[1];
|
|
b = def[2];
|
|
} else {
|
|
r = luaL_checkint(L, 1);
|
|
g = luaL_checkint(L, 2);
|
|
b = luaL_checkint(L, 3);
|
|
}
|
|
int idx = palette_colorToIdx(r, g, b);
|
|
if (idx < 0) {
|
|
luaL_error(L, "color palette exhausted: use fewer unique colors");
|
|
}
|
|
if (rgb) {
|
|
rgb[0] = r;
|
|
rgb[1] = g;
|
|
rgb[2] = b;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
|
|
static int pushColor(lua_State *L, int *rgb) {
|
|
lua_pushinteger(L, rgb[0]);
|
|
lua_pushinteger(L, rgb[1]);
|
|
lua_pushinteger(L, rgb[2]);
|
|
return 3;
|
|
}
|
|
|
|
|
|
int l_graphics_getDimensions(lua_State *L) {
|
|
lua_pushinteger(L, graphics_screen->width);
|
|
lua_pushinteger(L, graphics_screen->height);
|
|
return 2;
|
|
}
|
|
|
|
|
|
int l_graphics_getWidth(lua_State *L) {
|
|
lua_pushinteger(L, graphics_screen->width);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_getHeight(lua_State *L) {
|
|
lua_pushinteger(L, graphics_screen->height);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_getBackgroundColor(lua_State *L) {
|
|
return pushColor(L, graphics_backgroundColor_rgb);
|
|
}
|
|
|
|
|
|
int l_graphics_setBackgroundColor(lua_State *L) {
|
|
static const int def[] = { 0, 0, 0 };
|
|
int idx = getColorFromArgs(L, graphics_backgroundColor_rgb, def);
|
|
graphics_backgroundColor = idx;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_getColor(lua_State *L) {
|
|
return pushColor(L, graphics_color_rgb);
|
|
}
|
|
|
|
|
|
int l_graphics_setColor(lua_State *L) {
|
|
static const int def[] = { 0xff, 0xff, 0xff };
|
|
graphics_color = getColorFromArgs(L, graphics_color_rgb, def);
|
|
image_setColor(graphics_color);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_getBlendMode(lua_State *L) {
|
|
switch (graphics_blendMode) {
|
|
default:
|
|
case IMAGE_NORMAL : lua_pushstring(L, "normal"); break;
|
|
case IMAGE_FAST : lua_pushstring(L, "fast"); break;
|
|
case IMAGE_AND : lua_pushstring(L, "and"); break;
|
|
case IMAGE_OR : lua_pushstring(L, "or"); break;
|
|
case IMAGE_COLOR : lua_pushstring(L, "color"); break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_setBlendMode(lua_State *L) {
|
|
const char *str = lua_isnoneornil(L, 1) ? "normal" : luaL_checkstring(L, 1);
|
|
#define SET_BLEND_MODE(str, e)\
|
|
do {\
|
|
if (!strcmp(str, str)) {\
|
|
graphics_blendMode = e;\
|
|
image_setBlendMode(graphics_blendMode);\
|
|
return 0;\
|
|
}\
|
|
} while (0)
|
|
|
|
switch (*str) {
|
|
case 'n' : SET_BLEND_MODE("normal", IMAGE_NORMAL); break;
|
|
case 'f' : SET_BLEND_MODE("fast", IMAGE_FAST); break;
|
|
case 'a' : SET_BLEND_MODE("and", IMAGE_AND); break;
|
|
case 'o' : SET_BLEND_MODE("or", IMAGE_OR); break;
|
|
case 'c' : SET_BLEND_MODE("color", IMAGE_COLOR); break;
|
|
}
|
|
#undef SET_BLEND_MODE
|
|
luaL_argerror(L, 1, "bad blend mode");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_getFont(lua_State *L) {
|
|
lua_pushlightuserdata(L, graphics_font);
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_setFont(lua_State *L) {
|
|
font_t *oldFont = graphics_font;
|
|
if (lua_isnoneornil(L, 1)) {
|
|
/* If no arguments are given we use the default embedded font, grab it
|
|
* from the registry and set it as the first argument */
|
|
lua_pushlightuserdata(L, &graphics_defaultFont);
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
lua_insert(L, 1);
|
|
}
|
|
graphics_font = luaobj_checkudata(L, 1, LUAOBJ_TYPE_FONT);
|
|
/* Remove old font from registry. This is done after we know the args are
|
|
* okay so that the font remains unchanged if an error occurs */
|
|
if (oldFont) {
|
|
lua_pushlightuserdata(L, oldFont);
|
|
lua_pushnil(L);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
}
|
|
/* Add new font to registry */
|
|
lua_pushlightuserdata(L, graphics_font);
|
|
lua_pushvalue(L, 1);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_getCanvas(lua_State *L) {
|
|
lua_pushlightuserdata(L, graphics_canvas);
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_setCanvas(lua_State *L) {
|
|
image_t *oldCanvas = graphics_canvas;
|
|
if (lua_isnoneornil(L, 1)) {
|
|
/* If no arguments are given we use the screen canvas, grab it from the
|
|
* registry and set it as the first argument */
|
|
lua_pushlightuserdata(L, &graphics_screen);
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
lua_insert(L, 1);
|
|
}
|
|
graphics_canvas = luaobj_checkudata(L, 1, LUAOBJ_TYPE_IMAGE);
|
|
/* Remove old canvas from registry. This is done after we know the args are
|
|
* okay so that the canvas remains unchanged if an error occurs */
|
|
if (oldCanvas) {
|
|
lua_pushlightuserdata(L, oldCanvas);
|
|
lua_pushnil(L);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
}
|
|
/* Add new canvas to registry */
|
|
lua_pushlightuserdata(L, graphics_canvas);
|
|
lua_pushvalue(L, 1);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_getFlip(lua_State *L) {
|
|
lua_pushboolean(L, graphics_flip);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int l_graphics_setFlip(lua_State *L) {
|
|
int x = lua_isnoneornil(L, 1) ? 0 : lua_toboolean(L, 1);
|
|
image_setFlip(x);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_reset(lua_State *L) {
|
|
int (*funcs[])(lua_State*) = {
|
|
l_graphics_setBackgroundColor,
|
|
l_graphics_setColor,
|
|
l_graphics_setBlendMode,
|
|
l_graphics_setFont,
|
|
l_graphics_setCanvas,
|
|
l_graphics_setFlip,
|
|
NULL,
|
|
};
|
|
int i;
|
|
for (i = 0; funcs[i]; i++) {
|
|
lua_pushcfunction(L, funcs[i]);
|
|
lua_call(L, 0, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_clear(lua_State *L) {
|
|
int idx = getColorFromArgs(L, NULL, graphics_backgroundColor_rgb);
|
|
int sz = graphics_canvas->width * graphics_canvas->height;
|
|
memset(graphics_canvas->data, idx, sz);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_present(lua_State *L) {
|
|
vga_update(graphics_screen->data);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_draw(lua_State *L) {
|
|
image_t *img = luaobj_checkudata(L, 1, LUAOBJ_TYPE_IMAGE);
|
|
quad_t *quad = NULL;
|
|
int x, y;
|
|
if (!lua_isnone(L, 2) && lua_type(L, 2) != LUA_TNUMBER) {
|
|
quad = luaobj_checkudata(L, 2, LUAOBJ_TYPE_QUAD);
|
|
x = luaL_optint(L, 3, 0);
|
|
y = luaL_optint(L, 4, 0);
|
|
} else {
|
|
x = luaL_optint(L, 2, 0);
|
|
y = luaL_optint(L, 3, 0);
|
|
}
|
|
pixel_t *buf = graphics_canvas->data;
|
|
int bufw = graphics_canvas->width;
|
|
int bufh = graphics_canvas->height;
|
|
if (quad) {
|
|
image_blit(img, buf, bufw, bufh, x, y,
|
|
quad->x, quad->y, quad->width, quad->height);
|
|
} else {
|
|
image_blit(img, buf, bufw, bufh, x, y,
|
|
0, 0, img->width, img->height);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_point(lua_State *L) {
|
|
int x = luaL_checkint(L, 1);
|
|
int y = luaL_checkint(L, 2);
|
|
image_setPixel(graphics_canvas, x, y, graphics_color);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_line(lua_State *L) {
|
|
int argc = lua_gettop(L);
|
|
int lastx = luaL_checkint(L, 1);
|
|
int lasty = luaL_checkint(L, 2);
|
|
int idx = 3;
|
|
while (idx < argc) {
|
|
int x0 = lastx;
|
|
int y0 = lasty;
|
|
int x1 = luaL_checkint(L, idx);
|
|
int y1 = luaL_checkint(L, idx + 1);
|
|
lastx = x1;
|
|
lasty = y1;
|
|
/* Draw line */
|
|
#define SWAP_INT(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))
|
|
int steep = abs(y1 - y0) > abs(x1 - x0);
|
|
if (steep) {
|
|
SWAP_INT(x0, y0);
|
|
SWAP_INT(x1, y1);
|
|
}
|
|
if (x0 > x1) {
|
|
SWAP_INT(x0, x1);
|
|
SWAP_INT(y0, y1);
|
|
}
|
|
#undef SWAP_INT
|
|
int deltax = x1 - x0;
|
|
int deltay = abs(y1 - y0);
|
|
int error = deltax / 2;
|
|
int ystep = (y0 < y1) ? 1 : -1;
|
|
int x, y = y0;
|
|
for (x = x0; x < x1; x++) {
|
|
if (steep) {
|
|
image_setPixel(graphics_canvas, y, x, graphics_color);
|
|
} else {
|
|
image_setPixel(graphics_canvas, x, y, graphics_color);
|
|
}
|
|
error -= deltay;
|
|
if (error < 0) {
|
|
y += ystep;
|
|
error += deltax;
|
|
}
|
|
}
|
|
idx += 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_rectangle(lua_State *L) {
|
|
const char *mode = luaL_checkstring(L, 1);
|
|
int x = luaL_checkint(L, 2);
|
|
int y = luaL_checkint(L, 3);
|
|
int x2 = luaL_checkint(L, 4) + x;
|
|
int y2 = luaL_checkint(L, 5) + y;
|
|
int fill = 0;
|
|
if (!strcmp(mode, "fill")) {
|
|
fill = 1;
|
|
} else if (!strcmp(mode, "line")) {
|
|
fill = 0;
|
|
} else {
|
|
luaL_error(L, "bad mode");
|
|
}
|
|
/* Clip to screen */
|
|
if (x < 0) { x2 += x; x = 0; }
|
|
if (y < 0) { y2 += y; y = 0; }
|
|
if (x2 > graphics_canvas->width) { x2 = graphics_canvas->width; }
|
|
if (y2 > graphics_canvas->height) { y2 = graphics_canvas->height; }
|
|
/* Get width/height and Abort early if we're off screen */
|
|
int width = x2 - x;
|
|
int height = y2 - y;
|
|
if (width <= 0 || height <= 0) return 0;
|
|
/* Draw */
|
|
if (fill) {
|
|
int i;
|
|
for (i = y; i < y2; i++) {
|
|
memset(graphics_canvas->data + x + i * graphics_canvas->width,
|
|
graphics_color, width);
|
|
}
|
|
} else {
|
|
memset(graphics_canvas->data + x + y * graphics_canvas->width,
|
|
graphics_color, width);
|
|
memset(graphics_canvas->data + x + (y2 - 1) * graphics_canvas->width,
|
|
graphics_color, width);
|
|
int i;
|
|
for (i = y; i < y2; i++) {
|
|
graphics_canvas->data[x + i * graphics_canvas->width] =
|
|
graphics_canvas->data[x2 - 1 + i * graphics_canvas->width] =
|
|
graphics_color;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_circle(lua_State *L) {
|
|
const char *mode = luaL_checkstring(L, 1);
|
|
int x = luaL_checkint(L, 2);
|
|
int y = luaL_checkint(L, 3);
|
|
int radius = luaL_checkint(L, 4);
|
|
int fill = 0;
|
|
if (!strcmp(mode, "fill")) {
|
|
fill = 1;
|
|
} else if (!strcmp(mode, "line")) {
|
|
fill = 0;
|
|
} else {
|
|
luaL_error(L, "bad mode");
|
|
}
|
|
/* Draw */
|
|
if (fill) {
|
|
int dx = radius, dy = 0;
|
|
int radiusError = 1-dx;
|
|
while(dx >= dy) {
|
|
#define FILL_ROW(startx, endx, starty)\
|
|
do {\
|
|
int sx = (startx);\
|
|
int ex = (endx);\
|
|
int sy = (starty);\
|
|
if (sy < 0 || sy >= graphics_canvas->height) break;\
|
|
if (sx < 0) sx = 0;\
|
|
if (sx > graphics_canvas->width) sx = graphics_canvas->width;\
|
|
if (ex < 0) ex = 0;\
|
|
if (ex > graphics_canvas->width) ex = graphics_canvas->width;\
|
|
if (sx == ex) break;\
|
|
memset(graphics_canvas->data + sx + sy * graphics_canvas->width,\
|
|
graphics_color, ex - sx);\
|
|
} while (0)
|
|
|
|
FILL_ROW( -dx + x, dx + x, dy + y );
|
|
FILL_ROW( -dx + x, dx + x, -dy + y );
|
|
FILL_ROW( -dy + x, dy + x, dx + y );
|
|
FILL_ROW( -dy + x, dy + x, -dx + y );
|
|
|
|
#undef FILL_ROW
|
|
dy++;
|
|
if(radiusError<0) {
|
|
radiusError+=2*dy+1;
|
|
} else {
|
|
dx--;
|
|
radiusError+=2*(dy-dx+1);
|
|
}
|
|
}
|
|
} else {
|
|
int dx = radius, dy = 0;
|
|
int radiusError = 1-dx;
|
|
while(dx >= dy) {
|
|
image_setPixel(graphics_canvas, dx + x, dy + y, graphics_color);
|
|
image_setPixel(graphics_canvas, -dx + x, dy + y, graphics_color);
|
|
image_setPixel(graphics_canvas, dx + x, -dy + y, graphics_color);
|
|
image_setPixel(graphics_canvas, -dx + x, -dy + y, graphics_color);
|
|
image_setPixel(graphics_canvas, dy + x, dx + y, graphics_color);
|
|
image_setPixel(graphics_canvas, -dy + x, dx + y, graphics_color);
|
|
image_setPixel(graphics_canvas, dy + x, -dx + y, graphics_color);
|
|
image_setPixel(graphics_canvas, -dy + x, -dx + y, graphics_color);
|
|
dy++;
|
|
if(radiusError<0) {
|
|
radiusError+=2*dy+1;
|
|
} else {
|
|
dx--;
|
|
radiusError+=2*(dy-dx+1);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int l_graphics_print(lua_State *L) {
|
|
luaL_checkany(L, 1);
|
|
const char *str = luaL_tolstring(L, 1, NULL);
|
|
int x = luaL_checkint(L, 2);
|
|
int y = luaL_checkint(L, 3);
|
|
font_blit(graphics_font, graphics_canvas->data, graphics_canvas->width,
|
|
graphics_canvas->height, str, x, y);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int l_image_new(lua_State *L);
|
|
int l_image_newCanvas(lua_State *L);
|
|
int l_quad_new(lua_State *L);
|
|
int l_font_new(lua_State *L);
|
|
|
|
int luaopen_graphics(lua_State *L) {
|
|
luaL_Reg reg[] = {
|
|
{ "getDimensions", l_graphics_getDimensions },
|
|
{ "getWidth", l_graphics_getWidth },
|
|
{ "getHeight", l_graphics_getHeight },
|
|
{ "getBackgroundColor", l_graphics_getBackgroundColor },
|
|
{ "setBackgroundColor", l_graphics_setBackgroundColor },
|
|
{ "getColor", l_graphics_getColor },
|
|
{ "setColor", l_graphics_setColor },
|
|
{ "getBlendMode", l_graphics_getBlendMode },
|
|
{ "setBlendMode", l_graphics_setBlendMode },
|
|
{ "getFont", l_graphics_getFont },
|
|
{ "setFont", l_graphics_setFont },
|
|
{ "getCanvas", l_graphics_getCanvas },
|
|
{ "setCanvas", l_graphics_setCanvas },
|
|
{ "getFlip", l_graphics_getFlip },
|
|
{ "setFlip", l_graphics_setFlip },
|
|
{ "reset", l_graphics_reset },
|
|
{ "clear", l_graphics_clear },
|
|
{ "present", l_graphics_present },
|
|
{ "draw", l_graphics_draw },
|
|
{ "point", l_graphics_point },
|
|
{ "line", l_graphics_line },
|
|
{ "rectangle", l_graphics_rectangle },
|
|
{ "circle", l_graphics_circle },
|
|
{ "print", l_graphics_print },
|
|
{ "newImage", l_image_new },
|
|
{ "newCanvas", l_image_newCanvas },
|
|
{ "newQuad", l_quad_new },
|
|
{ "newFont", l_font_new },
|
|
{ 0, 0 },
|
|
};
|
|
luaL_newlib(L, reg);
|
|
|
|
/* Init screen canvas */
|
|
lua_pushcfunction(L, l_image_newCanvas);
|
|
lua_pushinteger(L, VGA_WIDTH);
|
|
lua_pushinteger(L, VGA_HEIGHT);
|
|
lua_call(L, 2, 1);
|
|
graphics_screen = luaobj_checkudata(L, -1, LUAOBJ_TYPE_IMAGE);
|
|
/* Add screen canvas to registry */
|
|
lua_pushlightuserdata(L, &graphics_screen);
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
lua_pop(L, 1); /* Pop the Image object */
|
|
|
|
/* Init default font */
|
|
lua_pushcfunction(L, l_font_new);
|
|
lua_call(L, 0, 1);
|
|
graphics_defaultFont = luaobj_checkudata(L, -1, LUAOBJ_TYPE_FONT);
|
|
/* Add default font to registry */
|
|
lua_pushlightuserdata(L, &graphics_defaultFont);
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, LUA_REGISTRYINDEX);
|
|
lua_pop(L, 1); /* Pop the Font object */
|
|
|
|
/* Reset all state settings to their defaults */
|
|
lua_pushcfunction(L, l_graphics_reset);
|
|
lua_call(L, 0, 0);
|
|
|
|
return 1;
|
|
}
|