#include "teensy_renderer.h" #include #include #include #include #include "teensy_context.h" #include "teensy_list.h" #define BLEND_COLOR ty_color(255, 0, 255) #define screenspace() ((ty_Recti){0, 0, r.target.width, r.target.height}) 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(ty_Image img, 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 } static ty_Vec2i clamp_point(ty_Vec2i p, ty_Recti rect) { p.x = fmin(fmax(p.x, rect.x), rect.x + rect.w); p.y = fmin(fmax(p.y, rect.y), rect.y + rect.h); return p; } void ty_init_renderer(void) { r.screen = ty_create_image( ctx.hints.scr_width, ctx.hints.scr_height, NULL ); r.target = r.screen; ty_draw_set_clip(TY_CLIP_NONE); } void ty_deinit_renderer(void) { assert(is_renderer_init()); ty_free_image(r.screen); } ty_Image ty_create_image(int w, int h, const ty_Color* data) { ty_Image img; img.width = w; img.height = h; size_t size = sizeof(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(ty_Image img) { ty_free(img.data); } ty_Color ty_img_get_pixel(ty_Image img, ty_Vec2i pos) { image_bounds_check(img, pos); return img.data[pos.y * img.width + pos.x]; } void ty_img_set_pixel( ty_Image img, ty_Vec2i pos, 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(ty_Font *font, uint8_t c, ty_Image img) { font->glyphs[c] = img; } int ty_font_width(const 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; } int ty_font_height(const ty_Font *font) { assert(font->glyphs[' '].height > 0); return font->glyphs[' '].height; } void ty_draw_set_target(const ty_Image *img) { if (!img) { r.target = r.screen; return; } memcpy(&r.target, img, sizeof(r.target)); } void ty_draw_set_clip(ty_Recti clip) { if (clip.w <= 0 || clip.h <= 0) clip = screenspace(); ty_Vec2i start = clamp_point(ty_recti_start(clip), screenspace()); ty_Vec2i end = clamp_point(ty_recti_end(clip), screenspace()); r.clip = ty_recti( start.x, start.y, end.x - start.x, end.y - start.y ); } void ty_draw_clear(ty_Color col) { assert(is_renderer_init()); for (int i = 0; i < r.target.width * r.target.height; i++) { r.target.data[i] = col; } } void ty_draw_image(ty_Image img, ty_Vec2i pos) { ty_Vec2i p1 = clamp_point(ty_vec2i(pos.x, pos.y), r.clip); ty_Vec2i p2 = clamp_point( ty_vec2i(pos.x + img.width, pos.y + img.height), r.clip ); for (int dy = p1.y; dy < p2.y; dy++) { for (int dx = p1.x; dx < p2.x; dx++) { ty_Color px = ty_img_get_pixel( img, ty_vec2i(dx - pos.x, dy - pos.y) ); if (memcmp(&px, &BLEND_COLOR, sizeof(px)) == 0) continue; ty_img_set_pixel( r.target, ty_vec2i(dx, dy), px ); } } } void ty_draw_image_ex( ty_Image img, ty_Recti src, ty_Recti dst ) { ty_Vec2i p1 = clamp_point(ty_recti_start(dst), r.clip); ty_Vec2i p2 = clamp_point(ty_recti_end(dst), r.clip); for (int dy = p1.y; dy < p2.y; dy++) { for (int dx = p1.x; dx < p2.x; dx++) { int img_x = ((dx - dst.x) * src.w) / dst.w + src.x; int img_y = ((dy - dst.y) * src.h) / dst.h + src.y; 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.target, ty_vec2i(dx, dy), px ); } } } void ty_draw_image_rot( ty_Image img, ty_Vec2i pos, ty_Vec2i offset, double rot ) { double s = sin(-rot); double c = cos(-rot); int blit_width = ceil(img.width * fabs(c) + img.height * fabs(s)); int blit_height = ceil(img.width * fabs(s) + img.height * fabs(c)); int startx = -blit_width / 2; int starty = -blit_height / 2; int endx = (blit_width + 1) / 2; int endy = (blit_height + 1) / 2; for (int img_y = starty; img_y < endy; img_y++) { for (int img_x = startx; img_x < endx; img_x++) { int dx = floor(img_x * c - img_y * s - offset.x); int dy = floor(img_x * s + img_y * c - offset.y); if (dx < 0 || dx >= img.width || dy < 0 || dy >= img.height) continue; ty_Color px = ty_img_get_pixel(img, ty_vec2i(dx, dy)); if (memcmp(&px, &BLEND_COLOR, sizeof(px)) == 0) continue; ty_img_set_pixel(r.target, ty_vec2i(pos.x + img_x, pos.y + img_y), px); } } } void ty_draw_rect(ty_Recti rect, ty_Color color) { ty_Vec2i p1 = clamp_point(ty_vec2i(rect.x, rect.y), r.clip); ty_Vec2i p2 = clamp_point(ty_recti_end(rect), r.clip); for (int dy = p1.y; dy < p2.y; dy++) { for (int dx = p1.x; dx < p2.x; dx++) { ty_img_set_pixel( r.target, ty_vec2i(dx, dy), color ); } } } void ty_draw_line( ty_Vec2i start, ty_Vec2i end, 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.target, 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.target, ty_vec2i(x, y), color ); } } } void ty_draw_text(const ty_Font *font, ty_Vec2i pos, const char *text) { for (const uint8_t *c = (const uint8_t*)text; *c != '\0'; c++) { ty_Image glyph = font->glyphs[*c]; ty_draw_image(glyph, pos); pos.x += glyph.width; } } void ty_draw_text_args( const ty_Font *font, ty_Vec2i pos, const char *fmt, va_list args ) { char *text = ty_format_args(fmt, args); ty_draw_text(font, pos, text); } void ty_draw_text_fmt( const ty_Font *font, ty_Vec2i pos, const char *fmt, ... ) { va_list args; va_start(args, fmt); ty_draw_text_args(font, pos, fmt, args); va_end(args); } void ty_draw_end(void) { assert(is_renderer_init()); }