#include "teensy_renderer.h" #include #include #include #include #include "teensy_context.h" #include "teensy_list.h" #define BLEND_COLOR ty_color(255, 0, 255) struct ty_renderer r; static bool is_renderer_init(void) { return r.screen.data != NULL && r.screen.width * r.screen.height != 0; } static void image_bounds_check(struct ty_image img, struct ty_vec2i pos) { #ifdef TEENSY_DEBUG if ( pos.x < 0 || pos.x >= img.width || pos.y < 0 || pos.y >= img.height ) { ty_log_fatal( TY_ERR_RENDER_OOB, "OOB (%d, %d). bounds are (%d, %d)", pos.x, pos.y, img.width, img.height ); } #else (void)img; (void)pos; #endif } void ty_init_renderer(void) { r.screen = ty_create_image( ctx.hints.scr_width, ctx.hints.scr_height, NULL ); } void ty_deinit_renderer(void) { assert(is_renderer_init()); ty_free_image(r.screen); } struct ty_image ty_create_image(int w, int h, const struct ty_color* data) { struct ty_image img; img.width = w; img.height = h; size_t size = sizeof(struct ty_color) * w * h; img.data = ty_alloc(size); if (data) memcpy(img.data, data, size); else memset(img.data, 0, size); return img; } void ty_free_image(struct ty_image img) { ty_free(img.data); } struct ty_color ty_img_get_pixel(struct ty_image img, struct ty_vec2i pos) { image_bounds_check(img, pos); return img.data[pos.y * img.width + pos.x]; } void ty_img_set_pixel( struct ty_image img, struct ty_vec2i pos, struct ty_color color ) { image_bounds_check(img, pos); img.data[pos.y * img.width + pos.x] = color; } // This function technically is not needed, but I may want unicode support in // the future, when an abstraction like this is needed. void ty_font_add_glyph(struct ty_font *font, uint8_t c, struct ty_image img) { font->glyphs[c] = img; } int ty_font_width(struct ty_font *font, const char *fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); char *text = ty_talloc(sizeof(char) * (len + 1)); va_end(args); va_start(args, fmt); vsnprintf(text, len + 1, fmt, args); va_end(args); int width = 0; for (uint8_t *c = (uint8_t*)text; *c != '\0'; c++) width += font->glyphs[*c].width; return width; } void ty_draw_clear(struct ty_color col) { assert(is_renderer_init()); for (int i = 0; i < r.screen.width * r.screen.height; i++) { r.screen.data[i] = col; } } void ty_draw_image(struct ty_image img, struct ty_vec2i pos) { int x1 = fmin(fmax(pos.x, 0), r.screen.width); int y1 = fmin(fmax(pos.y, 0), r.screen.height); int x2 = fmin(fmax(pos.x + img.width, 0), r.screen.width); int y2 = fmin(fmax(pos.y + img.height, 0), r.screen.height); for (int dy = y1; dy < y2; dy++) { for (int dx = x1; dx < x2; dx++) { struct ty_color px = ty_img_get_pixel( img, ty_vec2i(dx - x1, dy - y1) ); if (memcmp(&px, &BLEND_COLOR, sizeof(px)) == 0) continue; ty_img_set_pixel( r.screen, ty_vec2i(dx, dy), px ); } } } void ty_draw_image_ex( struct ty_image img, struct ty_recti src, struct ty_recti dst ) { int x1 = fmin(fmax(dst.x, 0), r.screen.width); int y1 = fmin(fmax(dst.y, 0), r.screen.height); int x2 = fmin(fmax(dst.x + dst.w, 0), r.screen.width); int y2 = fmin(fmax(dst.y + dst.h, 0), r.screen.height); for (int dy = y1; dy < y2; dy++) { for (int dx = x1; dx < x2; dx++) { int img_x = ((dx - x1) * src.w) / dst.w + src.x; int img_y = ((dy - y1) * src.h) / dst.h + src.y; struct ty_color px = ty_img_get_pixel(img, ty_vec2i(img_x, img_y)); if (memcmp(&px, &BLEND_COLOR, sizeof(px)) == 0) continue; ty_img_set_pixel( r.screen, ty_vec2i(dx, dy), px ); } } } struct ty_vec2i clamp_point(struct ty_vec2i p, struct ty_recti rect) { p.x = fmin(fmax(p.x, rect.x), rect.w); p.y = fmin(fmax(p.y, rect.y), rect.h); return p; } void ty_draw_image_rot( struct ty_image img, struct ty_vec2i pos, struct ty_vec2i offset, double rot ) { double s = sin(rot); double c = cos(rot); // x1 = c*x - s*y // y1 = s*x + c*y // FIXME: the corners of the rotated image get cut off. for (int img_y = 0; img_y < img.height; img_y++) { for (int img_x = 0; img_x < img.width; img_x++) { double pixel_x = img_x + offset.x; double pixel_y = img_y + offset.y; int dx = floor(pixel_x * c - pixel_y * s - offset.x); int dy = floor(pixel_x * s + pixel_y * c - offset.y); if (dx < 0 || dx >= img.width || dy < 0 || dy >= img.height) continue; struct ty_color px = ty_img_get_pixel(img, ty_vec2i(dx, dy)); ty_img_set_pixel(r.screen, ty_vec2i(pos.x + img_x, pos.y + img_y), px); } } } void ty_draw_rect(struct ty_recti rect, struct ty_color color) { int x1 = fmin(fmax(rect.x, 0), r.screen.width); int y1 = fmin(fmax(rect.y, 0), r.screen.height); int x2 = fmin(fmax(rect.x + rect.w, 0), r.screen.width); int y2 = fmin(fmax(rect.y + rect.h, 0), r.screen.height); for (int dy = y1; dy < y2; dy++) { for (int dx = x1; dx < x2; dx++) { ty_img_set_pixel( r.screen, ty_vec2i(dx, dy), color ); } } } void ty_draw_line( struct ty_vec2i start, struct ty_vec2i end, struct ty_color color ) { int dx = end.x - start.x; int dy = end.y - start.y; if (abs(dx) > abs(dy)) { if (start.x > end.x) { int tmp = start.x; start.x = end.x; end.x = tmp; tmp = start.y; start.y = end.y; end.y = tmp; } for (int x = start.x; x < end.x; x++) { int y = start.y + dy * (x - start.x) / dx; ty_img_set_pixel( r.screen, ty_vec2i(x, y), color ); } } else { if (start.y > end.y) { int tmp = start.x; start.x = end.x; end.x = tmp; tmp = start.y; start.y = end.y; end.y = tmp; } for (int y = start.y; y < end.y; y++) { int x = start.x + dx * (y - start.y) / dy; ty_img_set_pixel( r.screen, ty_vec2i(x, y), color ); } } } void ty_draw_text(struct ty_font *font, struct ty_vec2i pos, const char *text) { for (const uint8_t *c = (const uint8_t*)text; *c != '\0'; c++) { struct ty_image glyph = font->glyphs[*c]; ty_draw_image(glyph, pos); pos.x += glyph.width; } } void ty_draw_text_fmt( struct ty_font *font, struct ty_vec2i pos, const char *fmt, ... ) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); char *text = ty_talloc(sizeof(char) * (len + 1)); va_end(args); va_start(args, fmt); vsnprintf(text, len + 1, fmt, args); va_end(args); ty_draw_text(font, pos, text); } void ty_draw_end(void) { assert(is_renderer_init()); }