/* Primitivas de dibujo sobre un lienzo: pincel, línea, formas, curvas. */ #include #include "../../core/internal.h" #define COORD_LIMIT 20000.0f tgb_brush tgb_brush_pencil(tgb_color color, int size) { tgb_brush b = {(float)(size < 1 ? 1 : size), 1.0f, false, color, TGB_BRUSH_PAINT}; return b; } tgb_brush tgb_brush_soft(tgb_color color, float size, float hardness) { tgb_brush b = {size < 1.0f ? 1.0f : size, hardness, true, color, TGB_BRUSH_PAINT}; return b; } tgb_brush tgb_brush_eraser(float size, float hardness) { tgb_color k = {0, 0, 0, 255}; tgb_brush b = {size < 1.0f ? 1.0f : size, hardness, true, k, TGB_BRUSH_ERASE}; return b; } static float clampf(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); } static float sanitize(float v) { if (!isfinite(v)) return 0.0f; return clampf(v, -COORD_LIMIT, COORD_LIMIT); } static void plot(tgb_canvas *c, const tgb_brush *b, int x, int y, float cov) { if (cov <= 0.0f) return; if (cov > 1.0f) cov = 1.0f; if (b->mode == TGB_BRUSH_ERASE) { float a = cov * (b->color.a ? (float)b->color.a / 255.0f : 1.0f); tgb_canvas_erase_px(c, x, y, (uint8_t)(a * 255.0f + 0.5f)); } else { tgb_color col = b->color; col.a = (uint8_t)((float)col.a * cov + 0.5f); if (col.a == 255 && !b->antialias) tgb_canvas_set(c, x, y, col); else tgb_canvas_blend(c, x, y, col); } } void tgb_draw_dab(tgb_canvas *c, const tgb_brush *b, float x, float y) { if (!c || !b) return; float size = clampf(b->size, 1.0f, 512.0f); float r = size * 0.5f; x = sanitize(x); y = sanitize(y); if (!b->antialias) { /* lápiz: píxeles exactos, sin suavizado */ float cx = floorf(x) + 0.5f, cy = floorf(y) + 0.5f; int x0 = (int)floorf(cx - r), x1 = (int)ceilf(cx + r), y0 = (int)floorf(cy - r), y1 = (int)ceilf(cy + r); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > tgb_canvas_width(c)) x1 = tgb_canvas_width(c); if (y1 > tgb_canvas_height(c)) y1 = tgb_canvas_height(c); for (int py = y0; py < y1; py++) for (int px = x0; px < x1; px++) { float dx = (float)px + 0.5f - cx, dy = (float)py + 0.5f - cy; if (dx * dx + dy * dy <= r * r) plot(c, b, px, py, 1.0f); } return; } float h = clampf(b->hardness, 0.0f, 1.0f); int x0 = (int)floorf(x - r - 1), x1 = (int)ceilf(x + r + 1), y0 = (int)floorf(y - r - 1), y1 = (int)ceilf(y + r + 1); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > tgb_canvas_width(c)) x1 = tgb_canvas_width(c); if (y1 > tgb_canvas_height(c)) y1 = tgb_canvas_height(c); for (int py = y0; py < y1; py++) for (int px = x0; px < x1; px++) { float dx = (float)px + 0.5f - x, dy = (float)py + 0.5f - y; float d = sqrtf(dx * dx + dy * dy); float edge = clampf(r + 0.5f - d, 0.0f, 1.0f); float soft = h >= 1.0f ? 1.0f : clampf((r - d) / (r * (1.0f - h)), 0.0f, 1.0f); plot(c, b, px, py, edge < soft ? edge : soft); } } void tgb_draw_line(tgb_canvas *c, const tgb_brush *b, float x0, float y0, float x1, float y1) { if (!c || !b) return; x0 = sanitize(x0); y0 = sanitize(y0); x1 = sanitize(x1); y1 = sanitize(y1); if (!b->antialias) { /* Bresenham sobre píxeles */ int ix0 = (int)floorf(x0), iy0 = (int)floorf(y0), ix1 = (int)floorf(x1), iy1 = (int)floorf(y1); int dx = abs(ix1 - ix0), dy = -abs(iy1 - iy0), sx = ix0 < ix1 ? 1 : -1, sy = iy0 < iy1 ? 1 : -1; int err = dx + dy; for (;;) { tgb_draw_dab(c, b, (float)ix0 + 0.5f, (float)iy0 + 0.5f); if (ix0 == ix1 && iy0 == iy1) break; int e2 = 2 * err; if (e2 >= dy) { err += dy; ix0 += sx; } if (e2 <= dx) { err += dx; iy0 += sy; } } return; } float dx = x1 - x0, dy = y1 - y0, len = sqrtf(dx * dx + dy * dy); float spacing = clampf(b->size * 0.1f, 0.5f, 8.0f); int steps = (int)ceilf(len / spacing); if (steps < 1) steps = 1; for (int i = 0; i <= steps; i++) { float t = (float)i / (float)steps; tgb_draw_dab(c, b, x0 + dx * t, y0 + dy * t); } } static void fill_cov(tgb_canvas *c, const tgb_brush *b, int x, int y) { plot(c, b, x, y, 1.0f); } void tgb_draw_rect(tgb_canvas *c, const tgb_brush *b, tgb_rect r, bool filled) { if (!c || !b || r.w < 1 || r.h < 1) return; if (filled) { tgb_rect bounds = {0, 0, tgb_canvas_width(c), tgb_canvas_height(c)}; tgb_rect a = tgb_rect_intersect(r, bounds); for (int y = a.y; y < a.y + a.h; y++) for (int x = a.x; x < a.x + a.w; x++) fill_cov(c, b, x, y); return; } float inset = clampf(b->size, 1.0f, 512.0f) * 0.5f; float l = (float)r.x + inset, t = (float)r.y + inset, rr = (float)(r.x + r.w) - inset, bb = (float)(r.y + r.h) - inset; if (rr < l) rr = l = ((float)r.x + (float)(r.x + r.w)) * 0.5f; if (bb < t) bb = t = ((float)r.y + (float)(r.y + r.h)) * 0.5f; tgb_draw_line(c, b, l, t, rr, t); tgb_draw_line(c, b, rr, t, rr, bb); tgb_draw_line(c, b, rr, bb, l, bb); tgb_draw_line(c, b, l, bb, l, t); } void tgb_draw_ellipse(tgb_canvas *c, const tgb_brush *b, tgb_rect bd, bool filled) { if (!c || !b || bd.w < 1 || bd.h < 1) return; float cx = (float)bd.x + (float)bd.w * 0.5f, cy = (float)bd.y + (float)bd.h * 0.5f; float rx = (float)bd.w * 0.5f, ry = (float)bd.h * 0.5f; if (filled) { int ymin = bd.y < 0 ? 0 : bd.y, ymax = bd.y + bd.h > tgb_canvas_height(c) ? tgb_canvas_height(c) : bd.y + bd.h; for (int y = ymin; y < ymax; y++) { float ny = ((float)y + 0.5f - cy) / ry; if (fabsf(ny) > 1.0f) continue; float hw = rx * sqrtf(1.0f - ny * ny); int xa = (int)ceilf(cx - hw - 0.5f), xb = (int)floorf(cx + hw - 0.5f); if (xa < 0) xa = 0; if (xb >= tgb_canvas_width(c)) xb = tgb_canvas_width(c) - 1; for (int x = xa; x <= xb; x++) fill_cov(c, b, x, y); } return; } float inset = clampf(b->size, 1.0f, 512.0f) * 0.5f; float arx = rx - inset, ary = ry - inset; if (arx < 0.5f) arx = 0.5f; if (ary < 0.5f) ary = 0.5f; int n = (int)ceilf(6.2831853f * (arx > ary ? arx : ary) / 2.0f); if (n < 24) n = 24; if (n > 4096) n = 4096; float px = cx + arx, py = cy; for (int i = 1; i <= n; i++) { float t = 6.2831853f * (float)i / (float)n; float qx = cx + arx * cosf(t), qy = cy + ary * sinf(t); tgb_draw_line(c, b, px, py, qx, qy); px = qx; py = qy; } } void tgb_draw_bezier(tgb_canvas *c, const tgb_brush *b, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) { if (!c || !b) return; x0 = sanitize(x0); y0 = sanitize(y0); x1 = sanitize(x1); y1 = sanitize(y1); x2 = sanitize(x2); y2 = sanitize(y2); x3 = sanitize(x3); y3 = sanitize(y3); float poly = hypotf(x1 - x0, y1 - y0) + hypotf(x2 - x1, y2 - y1) + hypotf(x3 - x2, y3 - y2); int n = (int)ceilf(poly / 2.0f); if (n < 8) n = 8; if (n > 2048) n = 2048; float px = x0, py = y0; for (int i = 1; i <= n; i++) { float t = (float)i / (float)n, u = 1.0f - t; float w0 = u * u * u, w1 = 3 * u * u * t, w2 = 3 * u * t * t, w3 = t * t * t; float qx = w0 * x0 + w1 * x1 + w2 * x2 + w3 * x3, qy = w0 * y0 + w1 * y1 + w2 * y2 + w3 * y3; tgb_draw_line(c, b, px, py, qx, qy); px = qx; py = qy; } }