Files
Tahgibu/src/editor/tools/tool_raster.c
T
2026-10-24 12:01:52 -03:00

175 lines
7.5 KiB
C

/* Primitivas de dibujo sobre un lienzo: pincel, línea, formas, curvas. */
#include <math.h>
#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;
}
}