/* Composición de capas, Onion Skin y reproductor. */ #include #include "tgb_test.h" static tgb_context *g_ctx; static char g_root[200]; static tgb_document *new_doc(int w, int h, int fps) { tgb_document *d = NULL; CHECK_OK(tgb_document_create(g_ctx, w, h, fps, &d)); return d; } TEST(flatten_layers) { tgb_document *d = new_doc(8, 8, 12); CHECK_OK(tgb_layer_add(d, 1, "Arriba", NULL)); CHECK_OK(paint_px(d, 0, 0, 1, 1, RED)); CHECK_OK(paint_px(d, 0, 1, 1, 1, BLUE)); tgb_canvas *out = tgb_canvas_create(8, 8); CHECK_OK(tgb_render_flatten(d, 0, out)); CHECK(is_px(out, 1, 1, BLUE)); /* la capa de arriba tapa a la de abajo */ CHECK_OK(tgb_layer_set_visible(d, 1, false)); CHECK_OK(tgb_render_flatten(d, 0, out)); CHECK(is_px(out, 1, 1, RED)); /* capa oculta: no cuenta */ CHECK_OK(tgb_layer_set_visible(d, 1, true)); CHECK_OK(tgb_layer_set_opacity(d, 1, 128)); CHECK_OK(tgb_render_flatten(d, 0, out)); tgb_color p = tgb_canvas_get(out, 1, 1); CHECK(p.a == 255 && abs(p.r - 127) <= 2 && abs(p.b - 128) <= 2); /* mezcla 50 % */ CHECK_OK(tgb_layer_move(d, 1, 0)); CHECK_OK(tgb_layer_set_opacity(d, 0, 255)); CHECK_OK(tgb_render_flatten(d, 0, out)); CHECK(is_px(out, 1, 1, RED)); /* cambiar el orden cambia el resultado */ CHECK_OK(tgb_render_flatten(d, 0, out)); CHECK_EQ(tgb_canvas_dirty(out).w, 0); tgb_canvas *small = tgb_canvas_create(4, 4); CHECK_STATUS(tgb_render_flatten(d, 0, small), TGB_ERR_RANGE); CHECK_STATUS(tgb_render_flatten(d, 5, out), TGB_ERR_RANGE); CHECK_STATUS(tgb_render_flatten(NULL, 0, out), TGB_ERR_INVALID_ARG); tgb_canvas_release(small); tgb_canvas_release(out); tgb_document_destroy(d); } TEST(onion_skin) { tgb_document *d = new_doc(8, 8, 12); CHECK_OK(tgb_frame_add(d, 1, NULL)); CHECK_OK(tgb_frame_add(d, 2, NULL)); CHECK_OK(paint_px(d, 0, 0, 2, 2, RED)); /* fotograma 0: rojo */ CHECK_OK(paint_px(d, 2, 0, 5, 5, GREEN)); /* fotograma 2: verde */ tgb_onion_settings on; tgb_onion_defaults(&on); CHECK(!on.enabled); on.tint = false; on.opacity_before = 0.4f; on.opacity_after = 0.5f; on.falloff = 0.5f; tgb_canvas *out = tgb_canvas_create(8, 8); tgb_color none = {0, 0, 0, 0}; /* desactivado: no se ve nada del resto */ CHECK_OK(tgb_render_view(d, 1, &on, none, out)); CHECK(tgb_canvas_is_empty(out)); on.enabled = true; CHECK_OK(tgb_render_view(d, 1, &on, none, out)); tgb_color pr = tgb_canvas_get(out, 2, 2), pg = tgb_canvas_get(out, 5, 5); CHECK(pr.r == 255 && pr.g == 0 && abs(pr.a - 102) <= 1); /* anterior: 40 % */ CHECK(pg.g == 255 && abs(pg.a - 128) <= 1); /* posterior: 50 % */ /* tinte: el color original se mezcla con el tinte */ on.tint = true; on.tint_before = BLUE; CHECK_OK(tgb_render_view(d, 1, &on, none, out)); pr = tgb_canvas_get(out, 2, 2); CHECK(pr.b > 100 && pr.r < 255); /* dos fotogramas hacia atrás con atenuación */ on.tint = false; on.frames_before = 2; on.frames_after = 0; CHECK_OK(tgb_render_view(d, 2, &on, none, out)); CHECK(abs(tgb_canvas_get(out, 2, 2).a - 51) <= 1); /* 0,4 * 0,5 */ CHECK(is_px(out, 5, 5, GREEN)); /* el fotograma actual va encima, sin transparencia */ /* sin fotogramas anteriores al primero */ CHECK_OK(tgb_render_view(d, 0, &on, none, out)); CHECK(is_px(out, 2, 2, RED)); CHECK_EQ(count_alpha(out), 1); /* fondo opaco */ tgb_color white = {255, 255, 255, 255}; on.frames_after = 2; on.frames_before = 0; CHECK_OK(tgb_render_view(d, 1, &on, white, out)); CHECK(is_px(out, 0, 0, white)); CHECK(tgb_canvas_get(out, 5, 5).g == 255 && tgb_canvas_get(out, 5, 5).r > 100); /* validación de ajustes */ tgb_onion_settings bad = on; bad.frames_before = 99; bad.frames_after = -3; bad.opacity_before = 7.0f; bad.opacity_after = NAN; bad.falloff = -1; CHECK_OK(tgb_onion_validate(&bad)); CHECK(bad.frames_before == 8 && bad.frames_after == 0 && bad.opacity_before == 1.0f && bad.opacity_after == 0.0f && bad.falloff == 0.0f); CHECK_STATUS(tgb_onion_validate(NULL), TGB_ERR_INVALID_ARG); tgb_canvas_release(out); tgb_document_destroy(d); } TEST(player_loop_once_pingpong) { tgb_document *d = new_doc(8, 8, 10); /* 10 fps = 100 ms por fotograma */ CHECK_OK(tgb_frame_add(d, 1, NULL)); CHECK_OK(tgb_frame_add(d, 2, NULL)); tgb_player *p = tgb_player_create(d); CHECK(p != NULL); CHECK_EQ(tgb_player_update(p, 500), 0); /* parado: no avanza */ tgb_player_play(p); CHECK(tgb_player_is_playing(p)); CHECK_EQ(tgb_player_update(p, 50), 0); CHECK_EQ(tgb_player_update(p, 60), 1); CHECK_EQ(tgb_player_update(p, 100), 2); CHECK_EQ(tgb_player_update(p, 100), 0); /* bucle */ tgb_player_pause(p); CHECK_EQ(tgb_player_update(p, 1000), 0); tgb_player_stop(p); CHECK(!tgb_player_is_playing(p)); /* una vez */ tgb_player_set_mode(p, TGB_PLAY_ONCE); tgb_player_play(p); CHECK_EQ(tgb_player_update(p, 100), 1); CHECK_EQ(tgb_player_update(p, 100), 2); CHECK_EQ(tgb_player_update(p, 100), 2); CHECK(!tgb_player_is_playing(p)); tgb_player_play(p); /* volver a dar a play reinicia */ CHECK(tgb_player_is_playing(p) && tgb_player_frame(p) == 0); /* ping-pong: 0,1,2,1,0,1,2 */ tgb_player_stop(p); tgb_player_set_mode(p, TGB_PLAY_PINGPONG); tgb_player_play(p); size_t expect[] = {1, 2, 1, 0, 1, 2}; for (int i = 0; i < 6; i++) CHECK_EQ(tgb_player_update(p, 100), expect[i]); /* velocidad doble */ tgb_player_stop(p); tgb_player_set_mode(p, TGB_PLAY_LOOP); tgb_player_set_speed(p, 2.0f); tgb_player_play(p); CHECK_EQ(tgb_player_update(p, 50), 1); tgb_player_destroy(p); CHECK_OK(0); tgb_document_destroy(d); } TEST(player_durations_range_and_edge_cases) { tgb_document *d = new_doc(8, 8, 10); CHECK_OK(tgb_frame_add(d, 1, NULL)); CHECK_OK(tgb_frame_add(d, 2, NULL)); CHECK_OK(tgb_frame_set_duration(d, 1, 300)); /* duraciones: 100, 300, 100 */ tgb_player *p = tgb_player_create(d); tgb_player_play(p); CHECK_EQ(tgb_player_update(p, 150), 1); CHECK_EQ(tgb_player_update(p, 240), 1); /* 390 ms: aún en el fotograma de 300 ms (100..400) */ CHECK_EQ(tgb_player_update(p, 20), 2); /* 410 ms: ya en el tercero */ /* un dt gigante no cuelga ni sale del rango */ size_t f = tgb_player_update(p, 1e12); CHECK(f < 3); CHECK_EQ(tgb_player_update(p, -5), f); /* dt negativo se ignora */ /* rango 1..2 */ CHECK_STATUS(tgb_player_set_range(p, 2, 1), TGB_ERR_RANGE); CHECK_STATUS(tgb_player_set_range(p, 0, 9), TGB_ERR_RANGE); CHECK_OK(tgb_player_set_range(p, 1, 2)); tgb_player_stop(p); CHECK_EQ(tgb_player_frame(p), 1); tgb_player_play(p); CHECK_EQ(tgb_player_update(p, 300), 2); CHECK_EQ(tgb_player_update(p, 100), 1); /* vuelve al inicio del rango, no al 0 */ tgb_player_clear_range(p); CHECK_STATUS(tgb_player_seek(p, 7), TGB_ERR_RANGE); CHECK_OK(tgb_player_seek(p, 2)); CHECK_EQ(tgb_player_frame(p), 2); /* el documento cambia mientras se reproduce */ CHECK_OK(tgb_frame_delete(d, 2)); CHECK(tgb_player_update(p, 10) < 2); tgb_player_set_speed(p, -1.0f); tgb_player_set_speed(p, 1000.0f); /* se recortan sin fallar */ tgb_player_destroy(p); tgb_player_destroy(NULL); CHECK(tgb_player_create(NULL) == NULL); tgb_document_destroy(d); } int main(void) { g_ctx = tgb_test_context(g_root, sizeof g_root); RUN(flatten_layers); RUN(onion_skin); RUN(player_loop_once_pingpong); RUN(player_durations_range_and_edge_cases); tgb_context_destroy(g_ctx); tgb_test_rmtree(g_root); TEST_SUMMARY(); }