Files
Tahgibu/tests/test_canvas_draw.c
T
2026-10-24 11:52:16 -03:00

193 lines
8.1 KiB
C

/* Lienzo, herramientas de dibujo, relleno y transformaciones. */
#include <math.h>
#include "tgb_test.h"
TEST(canvas_basics) {
CHECK(tgb_canvas_create(0, 10) == NULL);
CHECK(tgb_canvas_create(10, -1) == NULL);
CHECK(tgb_canvas_create(TGB_MAX_DIMENSION + 1, 10) == NULL);
tgb_canvas *c = tgb_canvas_create(8, 6);
CHECK(c != NULL);
CHECK_EQ(tgb_canvas_width(c), 8);
CHECK_EQ(tgb_canvas_bytes(c), 8 * 6 * 4);
CHECK(tgb_canvas_is_empty(c));
tgb_canvas_set(c, 2, 3, RED);
CHECK(is_px(c, 2, 3, RED));
CHECK(!tgb_canvas_is_empty(c));
CHECK_EQ(tgb_canvas_get(c, -1, 0).a, 0); /* fuera del lienzo = transparente */
CHECK_EQ(tgb_canvas_get(c, 8, 0).a, 0);
tgb_canvas_set(c, 100, 100, RED); /* fuera: se ignora sin fallar */
tgb_canvas *d = tgb_canvas_clone(c);
CHECK(tgb_canvas_equal(c, d));
tgb_canvas_set(d, 0, 0, BLUE);
CHECK(!tgb_canvas_equal(c, d));
CHECK_EQ(tgb_canvas_refs(c), 1);
tgb_canvas_retain(c);
CHECK_EQ(tgb_canvas_refs(c), 2);
tgb_canvas_release(c);
tgb_canvas_release(c);
tgb_canvas_release(d);
tgb_canvas_release(NULL); /* aceptar NULL */
}
TEST(canvas_blend_math) {
tgb_canvas *c = tgb_canvas_create(2, 1);
tgb_color half_red = {255, 0, 0, 128};
tgb_canvas_blend(c, 0, 0, half_red); /* sobre transparente: queda igual */
CHECK(is_px(c, 0, 0, half_red));
tgb_canvas_set(c, 1, 0, BLUE);
tgb_canvas_blend(c, 1, 0, half_red); /* 50 % rojo sobre azul opaco */
tgb_color p = tgb_canvas_get(c, 1, 0);
CHECK_EQ(p.a, 255);
CHECK(abs(p.r - 128) <= 1 && abs(p.b - 127) <= 1 && p.g == 0);
tgb_canvas_erase_px(c, 1, 0, 255); /* borrado total: forma canónica 0,0,0,0 */
CHECK(is_px(c, 1, 0, (tgb_color){0, 0, 0, 0}));
tgb_canvas_release(c);
}
TEST(canvas_crop_blit_dirty) {
tgb_canvas *c = tgb_canvas_create(10, 10);
CHECK_EQ(tgb_canvas_dirty(c).w, 0);
tgb_canvas_set(c, 3, 4, RED);
tgb_canvas_set(c, 6, 8, RED);
tgb_rect d = tgb_canvas_dirty(c);
CHECK(d.x == 3 && d.y == 4 && d.w == 4 && d.h == 5);
tgb_canvas_reset_dirty(c);
CHECK_EQ(tgb_canvas_dirty(c).w, 0);
tgb_canvas *crop = NULL;
CHECK_OK(tgb_canvas_crop(c, (tgb_rect){3, 4, 4, 5}, &crop));
CHECK(is_px(crop, 0, 0, RED) && is_px(crop, 3, 4, RED));
tgb_canvas *bad = NULL;
CHECK_STATUS(tgb_canvas_crop(c, (tgb_rect){8, 8, 5, 5}, &bad), TGB_ERR_RANGE);
tgb_canvas *dst = tgb_canvas_create(10, 10);
tgb_canvas_blit(dst, crop, 1, 1, 128, true); /* mitad de opacidad */
CHECK_EQ(tgb_canvas_get(dst, 1, 1).a, 128);
tgb_canvas_blit(dst, crop, 20, 20, 255, true); /* totalmente fuera: no falla */
tgb_canvas_release(crop); tgb_canvas_release(dst); tgb_canvas_release(c);
}
TEST(pencil_and_brush) {
tgb_canvas *c = tgb_canvas_create(20, 20);
tgb_brush pen = tgb_brush_pencil(RED, 1);
tgb_draw_line(c, &pen, 0, 0, 9, 9);
CHECK_EQ(count_alpha(c), 10); /* diagonal exacta de 10 píxeles */
for (int i = 0; i < 10; i++) CHECK(is_px(c, i, i, RED));
tgb_canvas_clear(c);
tgb_draw_line(c, &pen, 2, 5, 12, 5); /* horizontal */
CHECK_EQ(count_alpha(c), 11);
tgb_canvas_clear(c);
tgb_brush fat = tgb_brush_pencil(BLUE, 3); /* lápiz grueso sin suavizado: todo opaco */
tgb_draw_dab(c, &fat, 10.5f, 10.5f);
bool all_opaque = true;
for (int y = 0; y < 20; y++) for (int x = 0; x < 20; x++) { tgb_color p = tgb_canvas_get(c, x, y); if (p.a && p.a != 255) all_opaque = false; }
CHECK(all_opaque);
CHECK(count_alpha(c) >= 5 && count_alpha(c) <= 9);
tgb_canvas_clear(c);
tgb_brush soft = tgb_brush_soft(BLACK, 10.0f, 0.5f);
tgb_draw_dab(c, &soft, 10.0f, 10.0f);
CHECK_EQ(tgb_canvas_get(c, 10, 10).a, 255); /* centro opaco */
int a = tgb_canvas_get(c, 14, 10).a;
CHECK(a > 0 && a < 255); /* borde suave */
CHECK_EQ(tgb_canvas_get(c, 17, 10).a, 0);
tgb_brush er = tgb_brush_eraser(6.0f, 1.0f);
tgb_draw_dab(c, &er, 10.0f, 10.0f);
CHECK_EQ(tgb_canvas_get(c, 10, 10).a, 0); /* el borrador quita opacidad */
tgb_canvas_release(c);
}
TEST(pencil_robust_inputs) {
tgb_canvas *c = tgb_canvas_create(16, 16);
tgb_brush pen = tgb_brush_pencil(RED, 1);
tgb_draw_line(c, &pen, NAN, 0, 5, 5); /* no debe colgarse ni escribir fuera */
tgb_draw_line(c, &pen, -1e30f, -1e30f, 1e30f, 1e30f);
tgb_draw_dab(c, &pen, INFINITY, 3);
tgb_draw_line(c, NULL, 0, 0, 1, 1);
tgb_draw_dab(NULL, &pen, 0, 0);
CHECK(c != NULL);
tgb_canvas_release(c);
}
TEST(shapes) {
tgb_canvas *c = tgb_canvas_create(30, 30);
tgb_brush pen = tgb_brush_pencil(RED, 1);
tgb_draw_rect(c, &pen, (tgb_rect){2, 2, 4, 3}, true);
CHECK_EQ(count_alpha(c), 12);
tgb_canvas_clear(c);
tgb_draw_rect(c, &pen, (tgb_rect){0, 0, 5, 5}, false);
CHECK_EQ(count_alpha(c), 16); /* perímetro de 5x5 */
CHECK(!is_px(c, 2, 2, RED));
tgb_canvas_clear(c);
tgb_draw_ellipse(c, &pen, (tgb_rect){5, 5, 9, 9}, true);
CHECK(is_px(c, 9, 9, RED));
CHECK(!is_px(c, 5, 5, RED));
bool sym = true; /* simetría horizontal */
for (int y = 5; y < 14; y++) for (int x = 5; x < 14; x++) if (is_px(c, x, y, RED) != is_px(c, 18 - x, y, RED)) sym = false;
CHECK(sym);
tgb_canvas_clear(c);
tgb_draw_ellipse(c, &pen, (tgb_rect){5, 5, 15, 9}, false);
CHECK(count_alpha(c) > 20);
CHECK(!is_px(c, 12, 9, RED)); /* hueco en el centro */
tgb_canvas_clear(c);
tgb_draw_bezier(c, &pen, 2, 2, 10, 25, 20, 25, 27, 2);
CHECK(is_px(c, 2, 2, RED) && is_px(c, 27, 2, RED));
CHECK(count_alpha(c) > 25);
tgb_canvas_release(c);
}
TEST(flood_fill_and_eyedropper) {
tgb_canvas *c = tgb_canvas_create(10, 10);
for (int y = 0; y < 10; y++) tgb_canvas_set(c, 5, y, BLACK); /* muro vertical */
size_t n = tgb_flood_fill(c, 1, 1, RED, 0);
CHECK_EQ(n, 50);
CHECK(is_px(c, 0, 9, RED) && is_px(c, 4, 0, RED));
CHECK(is_px(c, 5, 5, BLACK));
CHECK_EQ(tgb_canvas_get(c, 7, 7).a, 0); /* el otro lado no se toca */
CHECK_EQ(tgb_flood_fill(c, 1, 1, RED, 0), 0); /* mismo color: no hace nada (y no se cuelga) */
CHECK_EQ(tgb_flood_fill(c, -1, 0, RED, 0), 0);
tgb_color e = tgb_eyedropper(c, 5, 3);
CHECK(e.r == 0 && e.a == 255);
/* tolerancia: gris casi igual se rellena con tolerancia alta */
tgb_canvas *g = tgb_canvas_create(4, 1);
for (int x = 0; x < 4; x++) { tgb_color p = {(uint8_t)(100 + x * 3), 100, 100, 255}; tgb_canvas_set(g, x, 0, p); }
CHECK_EQ(tgb_flood_fill(g, 0, 0, GREEN, 0), 1);
tgb_canvas_set(g, 0, 0, (tgb_color){100, 100, 100, 255});
CHECK_EQ(tgb_flood_fill(g, 0, 0, GREEN, 10), 4);
tgb_canvas_release(g); tgb_canvas_release(c);
}
TEST(regions) {
tgb_canvas *c = tgb_canvas_create(10, 10);
tgb_canvas_fill_rect(c, (tgb_rect){1, 1, 2, 2}, RED, false);
CHECK_OK(tgb_region_move(c, (tgb_rect){0, 0, 4, 4}, 3, 2));
CHECK(is_px(c, 4, 3, RED) && is_px(c, 5, 4, RED));
CHECK_EQ(tgb_canvas_get(c, 1, 1).a, 0);
CHECK_EQ(count_alpha(c), 4);
CHECK_STATUS(tgb_region_move(c, (tgb_rect){50, 50, 2, 2}, 1, 1), TGB_ERR_RANGE);
tgb_canvas_clear(c);
tgb_canvas_set(c, 1, 1, RED);
CHECK_OK(tgb_region_flip(c, (tgb_rect){0, 0, 4, 4}, true));
CHECK(is_px(c, 2, 1, RED) && count_alpha(c) == 1);
CHECK_OK(tgb_region_flip(c, (tgb_rect){0, 0, 4, 4}, false));
CHECK(is_px(c, 2, 2, RED) && count_alpha(c) == 1);
tgb_canvas_clear(c);
tgb_canvas_fill_rect(c, (tgb_rect){0, 0, 2, 2}, BLUE, false);
CHECK_OK(tgb_region_scale(c, (tgb_rect){0, 0, 2, 2}, 4, 4));
CHECK_EQ(count_alpha(c), 16);
CHECK_STATUS(tgb_region_scale(c, (tgb_rect){0, 0, 2, 2}, 0, 4), TGB_ERR_RANGE);
CHECK_OK(tgb_region_clear(c, (tgb_rect){0, 0, 10, 10}));
CHECK(tgb_canvas_is_empty(c));
tgb_canvas_release(c);
}
int main(void) {
RUN(canvas_basics);
RUN(canvas_blend_math);
RUN(canvas_crop_blit_dirty);
RUN(pencil_and_brush);
RUN(pencil_robust_inputs);
RUN(shapes);
RUN(flood_fill_and_eyedropper);
RUN(regions);
TEST_SUMMARY();
}