442 lines
15 KiB
C
442 lines
15 KiB
C
/*
|
|
* Pruebas sin pantalla del renderizador 2D por software.
|
|
* Los círculos se comparan píxel a píxel con una referencia calculada por
|
|
* fuerza bruta a partir de la definición documentada en draw2d.h.
|
|
*/
|
|
#include "check.h"
|
|
|
|
#include <fnix/fnix.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
static FnixCanvas *new_canvas(int width, int height)
|
|
{
|
|
FnixCanvas *canvas = fnix_canvas_create(width, height);
|
|
CHECK(canvas != NULL);
|
|
fnix_set_target(canvas);
|
|
return canvas;
|
|
}
|
|
|
|
static bool is_black(const FnixCanvas *c, int x, int y)
|
|
{
|
|
const FnixColor p = fnix_canvas_get_pixel(c, x, y);
|
|
return p.r == 0 && p.g == 0 && p.b == 0;
|
|
}
|
|
|
|
static bool pixel_is(const FnixCanvas *c, int x, int y, FnixColor expected)
|
|
{
|
|
const FnixColor p = fnix_canvas_get_pixel(c, x, y);
|
|
return p.r == expected.r && p.g == expected.g && p.b == expected.b;
|
|
}
|
|
|
|
static int count_drawn(const FnixCanvas *c)
|
|
{
|
|
int n = 0;
|
|
for (int y = 0; y < fnix_canvas_height(c); y++) {
|
|
for (int x = 0; x < fnix_canvas_width(c); x++) {
|
|
if (!is_black(c, x, y)) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static void test_clear(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(8, 6);
|
|
fnix_clear(FNIX_BLUE);
|
|
bool all_blue = true;
|
|
for (int y = 0; y < 6; y++) {
|
|
for (int x = 0; x < 8; x++) {
|
|
all_blue = all_blue && pixel_is(c, x, y, FNIX_BLUE);
|
|
}
|
|
}
|
|
CHECK(all_blue);
|
|
|
|
/* clear ignora el alfa y deja el píxel opaco. */
|
|
fnix_clear(FNIX_RGBA(10, 20, 30, 5));
|
|
CHECK(pixel_is(c, 3, 3, FNIX_RGB(10, 20, 30)));
|
|
CHECK(fnix_canvas_get_pixel(c, 3, 3).a == 255);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_pixel(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(10, 10);
|
|
fnix_draw_pixel(3, 4, FNIX_RED);
|
|
CHECK(pixel_is(c, 3, 4, FNIX_RED));
|
|
CHECK(count_drawn(c) == 1);
|
|
|
|
/* Fuera del lienzo: se ignora. */
|
|
fnix_draw_pixel(-1, 0, FNIX_RED);
|
|
fnix_draw_pixel(0, -1, FNIX_RED);
|
|
fnix_draw_pixel(10, 0, FNIX_RED);
|
|
fnix_draw_pixel(0, 10, FNIX_RED);
|
|
fnix_draw_pixel(2000000000, -2000000000, FNIX_RED);
|
|
CHECK(count_drawn(c) == 1);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_rect(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(8, 8);
|
|
|
|
fnix_draw_rect(2, 3, 4, 2, FNIX_GREEN);
|
|
CHECK(count_drawn(c) == 8);
|
|
CHECK(pixel_is(c, 2, 3, FNIX_GREEN));
|
|
CHECK(pixel_is(c, 5, 4, FNIX_GREEN));
|
|
CHECK(is_black(c, 1, 3) && is_black(c, 6, 3) && is_black(c, 2, 2) && is_black(c, 2, 5));
|
|
|
|
/* Recortado por la esquina superior izquierda: solo 2x2 visibles. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect(-2, -2, 4, 4, FNIX_RED);
|
|
CHECK(count_drawn(c) == 4);
|
|
|
|
/* Totalmente fuera, o tamaño nulo/negativo: no dibuja. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect(20, 20, 5, 5, FNIX_RED);
|
|
fnix_draw_rect(-20, 0, 5, 5, FNIX_RED);
|
|
fnix_draw_rect(1, 1, 0, 5, FNIX_RED);
|
|
fnix_draw_rect(1, 1, 5, -3, FNIX_RED);
|
|
CHECK(count_drawn(c) == 0);
|
|
|
|
/* Rectángulo mucho mayor que el lienzo: lo cubre entero. */
|
|
fnix_draw_rect(-100, -100, 1000, 1000, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 64);
|
|
|
|
/* Valores extremos dentro del rango permitido. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect(-(1 << 27), -(1 << 27), 1 << 28, 1 << 28, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 64);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_rect_outline(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(12, 12);
|
|
|
|
fnix_draw_rect_outline(1, 1, 6, 5, FNIX_RED);
|
|
CHECK(count_drawn(c) == 2 * 6 + 2 * (5 - 2)); /* 18 */
|
|
CHECK(pixel_is(c, 1, 1, FNIX_RED) && pixel_is(c, 6, 5, FNIX_RED));
|
|
CHECK(is_black(c, 3, 3)); /* interior vacío */
|
|
|
|
/* Sin interior: equivale a un rectángulo lleno. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect_outline(2, 2, 2, 5, FNIX_RED);
|
|
CHECK(count_drawn(c) == 10);
|
|
|
|
/* Parcialmente fuera: solo se ven los lados dentro del lienzo. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect_outline(-3, 4, 8, 5, FNIX_RED); /* x de -3 a 4 */
|
|
CHECK(pixel_is(c, 4, 6, FNIX_RED)); /* lado derecho */
|
|
CHECK(is_black(c, 0, 6)); /* el lado izquierdo quedó fuera */
|
|
CHECK(pixel_is(c, 0, 4, FNIX_RED) && pixel_is(c, 4, 8, FNIX_RED));
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
/* Referencia por fuerza bruta: ¿pertenece (dx, dy) al disco de radio r? */
|
|
static bool in_disc(long dx, long dy, long r)
|
|
{
|
|
return r >= 0 && dx * dx + dy * dy <= r * r + r;
|
|
}
|
|
|
|
static void test_circles_match_reference(void)
|
|
{
|
|
enum { W = 48, H = 36 };
|
|
FnixCanvas *c = new_canvas(W, H);
|
|
const int centers[][2] = {{24, 18}, {2, 3}, {46, 34}, {-5, -5}, {60, 40}, {24, -3}};
|
|
|
|
for (size_t k = 0; k < sizeof centers / sizeof centers[0]; k++) {
|
|
for (int r = 0; r <= 20; r++) {
|
|
for (int ring = 0; ring < 2; ring++) {
|
|
fnix_clear(FNIX_BLACK);
|
|
if (ring) {
|
|
fnix_draw_circle_outline(centers[k][0], centers[k][1], r, FNIX_WHITE);
|
|
} else {
|
|
fnix_draw_circle(centers[k][0], centers[k][1], r, FNIX_WHITE);
|
|
}
|
|
|
|
int mismatches = 0;
|
|
for (int y = 0; y < H; y++) {
|
|
for (int x = 0; x < W; x++) {
|
|
const long dx = x - centers[k][0];
|
|
const long dy = y - centers[k][1];
|
|
bool expected = in_disc(dx, dy, r);
|
|
if (ring && r >= 1) {
|
|
expected = expected && !in_disc(dx, dy, r - 1);
|
|
}
|
|
if (expected == is_black(c, x, y)) {
|
|
mismatches++;
|
|
}
|
|
}
|
|
}
|
|
CHECK(mismatches == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Radio negativo: no dibuja. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_circle(10, 10, -1, FNIX_WHITE);
|
|
fnix_draw_circle_outline(10, 10, -7, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 0);
|
|
|
|
/* Radio 0: un píxel. */
|
|
fnix_draw_circle(10, 10, 0, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 1 && pixel_is(c, 10, 10, FNIX_WHITE));
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_huge_circles(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(40, 30);
|
|
|
|
/* Disco gigante que cubre el lienzo: termina rápido y lo llena. */
|
|
fnix_draw_circle(0, 0, 1 << 28, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 40 * 30);
|
|
|
|
/* Anillo gigante lejos del lienzo: no se ve nada. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_circle_outline(0, 0, 1 << 28, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 0);
|
|
|
|
/* Borde izquierdo de un disco enorme que roza la columna 0: sin desbordes
|
|
* en ninguna de las dos mitades (filas por encima y por debajo del centro). */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_circle_outline(1 << 28, 10, 1 << 28, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 30);
|
|
bool column_zero = true;
|
|
for (int y = 0; y < 30; y++) {
|
|
column_zero = column_zero && pixel_is(c, 0, y, FNIX_WHITE);
|
|
}
|
|
CHECK(column_zero);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_alpha_blends_once(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(40, 40);
|
|
const FnixColor half = FNIX_RGBA(255, 0, 0, 128);
|
|
|
|
/* Sobre negro: (255*128 + 127) / 255 = 128. Una doble mezcla daría 192. */
|
|
fnix_draw_rect(2, 2, 5, 5, half);
|
|
CHECK(pixel_is(c, 3, 3, FNIX_RGB(128, 0, 0)));
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_circle(20, 20, 15, half);
|
|
fnix_draw_circle_outline(20, 20, 8, half);
|
|
fnix_draw_line(0, 0, 39, 17, half);
|
|
fnix_draw_rect_outline(1, 1, 38, 38, half);
|
|
bool single_blend = true;
|
|
for (int y = 0; y < 40; y++) {
|
|
for (int x = 0; x < 40; x++) {
|
|
const FnixColor p = fnix_canvas_get_pixel(c, x, y);
|
|
/* Cada píxel pudo recibir 1 o 2 figuras distintas; los que recibieron
|
|
* una sola valen exactamente 128. Ninguno puede valer 192 por la
|
|
* misma figura: lo comprobamos de forma aislada abajo. */
|
|
single_blend = single_blend && (p.r == 0 || p.r >= 128);
|
|
}
|
|
}
|
|
CHECK(single_blend);
|
|
|
|
/* Aislado: cada primitiva por separado mezcla cada píxel una sola vez. */
|
|
for (int shape = 0; shape < 5; shape++) {
|
|
fnix_clear(FNIX_BLACK);
|
|
switch (shape) {
|
|
case 0: fnix_draw_circle(20, 20, 15, half); break;
|
|
case 1: fnix_draw_circle_outline(20, 20, 15, half); break;
|
|
case 2: fnix_draw_line(0, 0, 39, 17, half); break;
|
|
case 3: fnix_draw_rect_outline(1, 1, 38, 38, half); break;
|
|
default: fnix_draw_circle_outline(-3, 20, 12, half); break;
|
|
}
|
|
bool all_128 = true;
|
|
for (int y = 0; y < 40; y++) {
|
|
for (int x = 0; x < 40; x++) {
|
|
const FnixColor p = fnix_canvas_get_pixel(c, x, y);
|
|
all_128 = all_128 && (p.r == 0 || p.r == 128);
|
|
}
|
|
}
|
|
CHECK(all_128);
|
|
CHECK(count_drawn(c) > 0);
|
|
}
|
|
|
|
/* Mezcla sobre blanco: R = (255*128 + 255*127 + 127)/255 = 255,
|
|
* G = B = (0 + 255*127 + 127)/255 = 127. */
|
|
fnix_clear(FNIX_WHITE);
|
|
fnix_draw_rect(0, 0, 4, 4, half);
|
|
CHECK(pixel_is(c, 1, 1, FNIX_RGB(255, 127, 127)));
|
|
|
|
/* Redondeo al entero más cercano (el truncado daría otros valores):
|
|
* 10*200/255 = 7,84 -> 8 y 100*254/255 = 99,6 -> 100. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect(0, 0, 2, 2, FNIX_RGBA(10, 10, 10, 200));
|
|
CHECK(pixel_is(c, 0, 0, FNIX_RGB(8, 8, 8)));
|
|
fnix_clear(FNIX_RGB(100, 100, 100));
|
|
fnix_draw_rect(0, 0, 2, 2, FNIX_RGBA(0, 0, 0, 1));
|
|
CHECK(pixel_is(c, 0, 0, FNIX_RGB(100, 100, 100)));
|
|
|
|
/* Alfa 0 no dibuja; alfa 255 reemplaza. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_rect(0, 0, 10, 10, FNIX_RGBA(255, 255, 255, 0));
|
|
fnix_draw_circle(10, 10, 5, FNIX_TRANSPARENT);
|
|
fnix_draw_line(0, 0, 30, 30, FNIX_TRANSPARENT);
|
|
CHECK(count_drawn(c) == 0);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_lines(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(40, 30);
|
|
|
|
/* Horizontal, vertical y diagonal exactas. */
|
|
fnix_draw_line(2, 3, 9, 3, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 8 && pixel_is(c, 2, 3, FNIX_WHITE) && pixel_is(c, 9, 3, FNIX_WHITE));
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(5, 20, 5, 4, FNIX_WHITE); /* hacia arriba */
|
|
CHECK(count_drawn(c) == 17 && pixel_is(c, 5, 20, FNIX_WHITE) && pixel_is(c, 5, 4, FNIX_WHITE));
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(0, 0, 7, 7, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 8);
|
|
bool diagonal = true;
|
|
for (int i = 0; i < 8; i++) {
|
|
diagonal = diagonal && pixel_is(c, i, i, FNIX_WHITE);
|
|
}
|
|
CHECK(diagonal);
|
|
|
|
/* Posición exacta de cada píxel: y = redondeo(3*x/10), empate hacia arriba.
|
|
* Valores calculados con aritmética exacta: 0 0 1 1 1 2 2 2 2 3 3. */
|
|
{
|
|
const int expected_minor[11] = {0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3};
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(0, 0, 10, 3, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 11);
|
|
bool exact = true;
|
|
for (int i = 0; i <= 10; i++) {
|
|
exact = exact && pixel_is(c, i, expected_minor[i], FNIX_WHITE);
|
|
}
|
|
CHECK(exact);
|
|
|
|
/* Lo mismo con los ejes intercambiados (línea pronunciada). */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(0, 0, 3, 10, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 11);
|
|
exact = true;
|
|
for (int i = 0; i <= 10; i++) {
|
|
exact = exact && pixel_is(c, expected_minor[i], i, FNIX_WHITE);
|
|
}
|
|
CHECK(exact);
|
|
}
|
|
|
|
/* Punto. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(11, 12, 11, 12, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 1 && pixel_is(c, 11, 12, FNIX_WHITE));
|
|
|
|
/* Pendientes suaves y pronunciadas: un píxel por paso del eje mayor, con
|
|
* ambos extremos, y la línea inversa también (otro sentido, mismos pasos). */
|
|
const int segments[][4] = {{2, 2, 30, 9}, {5, 1, 9, 28}, {38, 28, 1, 2}, {3, 25, 35, 3}};
|
|
for (size_t k = 0; k < sizeof segments / sizeof segments[0]; k++) {
|
|
const int *s = segments[k];
|
|
const int adx = s[2] > s[0] ? s[2] - s[0] : s[0] - s[2];
|
|
const int ady = s[3] > s[1] ? s[3] - s[1] : s[1] - s[3];
|
|
const int expected = (adx > ady ? adx : ady) + 1;
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(s[0], s[1], s[2], s[3], FNIX_WHITE);
|
|
CHECK(count_drawn(c) == expected);
|
|
CHECK(pixel_is(c, s[0], s[1], FNIX_WHITE) && pixel_is(c, s[2], s[3], FNIX_WHITE));
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(s[2], s[3], s[0], s[1], FNIX_WHITE);
|
|
CHECK(count_drawn(c) == expected);
|
|
CHECK(pixel_is(c, s[0], s[1], FNIX_WHITE) && pixel_is(c, s[2], s[3], FNIX_WHITE));
|
|
}
|
|
fnix_canvas_destroy(c);
|
|
|
|
/* Líneas que atraviesan el lienzo desde muy lejos: se recortan sin recorrer
|
|
* los millones de pasos exteriores. */
|
|
c = new_canvas(16, 16);
|
|
fnix_draw_line(-100000, 4, 100000, 4, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 16);
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(-1000, -1000, 1000, 1000, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 16);
|
|
bool diagonal16 = true;
|
|
for (int i = 0; i < 16; i++) {
|
|
diagonal16 = diagonal16 && pixel_is(c, i, i, FNIX_WHITE);
|
|
}
|
|
CHECK(diagonal16);
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(3, -50, 3, 50, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 16);
|
|
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(-100000, -50000, 100000, 50000, FNIX_WHITE); /* pendiente 1/2 */
|
|
CHECK(count_drawn(c) == 16);
|
|
CHECK(pixel_is(c, 0, 0, FNIX_WHITE) && pixel_is(c, 10, 5, FNIX_WHITE));
|
|
|
|
/* Totalmente fuera del lienzo. */
|
|
fnix_clear(FNIX_BLACK);
|
|
fnix_draw_line(-50, -50, -10, 90, FNIX_WHITE);
|
|
fnix_draw_line(30, 0, 90, 20, FNIX_WHITE);
|
|
CHECK(count_drawn(c) == 0);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
static void test_range_and_no_target(void)
|
|
{
|
|
FnixCanvas *c = new_canvas(16, 16);
|
|
|
|
/* Valores fuera de [-2^28, 2^28]: no dibuja y registra el error. */
|
|
fnix_clear_error();
|
|
fnix_draw_line(1 << 29, 0, 0, 0, FNIX_WHITE);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_clear_error();
|
|
fnix_draw_rect(0, 0, 1 << 29, 5, FNIX_WHITE);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_clear_error();
|
|
fnix_draw_circle(0, 0, 2000000000, FNIX_WHITE);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_clear_error();
|
|
fnix_draw_circle_outline(-2000000000, 0, 5, FNIX_WHITE);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_clear_error();
|
|
fnix_draw_rect_outline(0, 0, 5, -(1 << 29), FNIX_WHITE); /* alto <= 0: no es error */
|
|
CHECK(count_drawn(c) == 0);
|
|
|
|
/* Sin lienzo de destino: no hace nada y lo informa. */
|
|
fnix_set_target(NULL);
|
|
fnix_clear_error();
|
|
fnix_clear(FNIX_RED);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_clear_error();
|
|
fnix_draw_pixel(1, 1, FNIX_RED);
|
|
CHECK(fnix_last_error_code() == FNIX_ERROR_INVALID_ARGUMENT);
|
|
fnix_draw_line(0, 0, 5, 5, FNIX_RED);
|
|
fnix_draw_rect(0, 0, 5, 5, FNIX_RED);
|
|
fnix_draw_rect_outline(0, 0, 5, 5, FNIX_RED);
|
|
fnix_draw_circle(5, 5, 3, FNIX_RED);
|
|
fnix_draw_circle_outline(5, 5, 3, FNIX_RED);
|
|
CHECK(count_drawn(c) == 0);
|
|
fnix_canvas_destroy(c);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_clear();
|
|
test_pixel();
|
|
test_rect();
|
|
test_rect_outline();
|
|
test_circles_match_reference();
|
|
test_huge_circles();
|
|
test_alpha_blends_once();
|
|
test_lines();
|
|
test_range_and_no_target();
|
|
return CHECK_REPORT("test_draw2d");
|
|
}
|