# Render your first animation
Follow this tutorial to load a Lottie animation in C, render it into a pixel buffer with the software renderer, and save frames to an image file.

In this tutorial you will write a complete C program that loads a Lottie animation, renders it into a pixel buffer, and saves a frame to an image file you can open. It needs no window system and no graphics library — just the runtime and a C compiler.

## Prerequisites

- A built library and `dotlottie_player.h` for your machine. If you don't have them yet, run through [Build for your target architecture](/en/runtimes/distributions/native/v0.x/building) with `make native`
- Working [include and link flags](/en/runtimes/distributions/native/v0.x/linking)
- A `.lottie` or Lottie `.json` file to play

## Step 1: Create a player

Every operation goes through a player handle. Create one with `dotlottie_new_player()`, and release it with `dotlottie_destroy()` when you're done.

```c
#include "dotlottie_player.h"

DotLottiePlayer *player = dotlottie_new_player(0);
if (!player) {
  return 1;
}
```

The argument is the number of rendering worker threads. Pass `0` to render on the calling thread.

<Callout type="warning" title="The thread count is fixed for the whole process">
  The renderer's thread pool is created once, when the first player is constructed, and later players reuse it. Passing
  a different number to a second `dotlottie_new_player()` call has no effect. Threading also requires the `tvg-threads`
  feature to be compiled in.
</Callout>

## Step 2: Attach a render target

The player does not allocate a canvas for you. With the software renderer, you supply a buffer of `width × height` 32-bit pixels and the player draws into it. The buffer belongs to you, so it has to stay alive for as long as the player uses it.

```c
#include <stdlib.h>

#define WIDTH  512
#define HEIGHT 512

uint32_t *buffer = malloc(WIDTH * HEIGHT * sizeof(uint32_t));

if (dotlottie_set_sw_target(player, buffer, WIDTH, HEIGHT, ABGR8888) != DOTLOTTIE_SUCCESS) {
  return 1;
}
```

`ABGR8888` packs each pixel as `0xAABBGGRR` — alpha in the high byte, red in the low byte — with colors alpha-premultiplied.

## Step 3: Configure and load

Set your playback configuration before loading, then load the animation from a path:

```c
dotlottie_set_background(player, 255, 255, 255, 255);   /* opaque white */
dotlottie_set_loop(player, true);
dotlottie_set_autoplay(player, true);

if (dotlottie_load_animation_path(player, "animation.lottie") != DOTLOTTIE_SUCCESS) {
  return 1;
}
```

With `autoplay` enabled the animation begins playing as soon as it loads, so you don't need to call `dotlottie_play()`.

Every fallible function returns a `dotlottieDotLottieResult`, where `DOTLOTTIE_SUCCESS` (`0`) means success. Checking it on load is important: a missing file, an unsupported feature, or malformed animation data all surface here.

## Step 4: Render a frame and save it

Jump to a specific frame and render it. `dotlottie_render()` draws the current frame without advancing time, which is exactly what you want for a still image:

```c
float total = 0.0f;
dotlottie_get_total_frames(player, &total);

dotlottie_set_frame(player, total / 2.0f);   /* halfway through */
dotlottie_render(player);
```

The buffer now holds the rendered frame. Write it out as a [PPM↗](https://netpbm.sourceforge.net/doc/ppm.html) file, a format simple enough to produce in a few lines and openable by most image viewers:

```c
#include <stdio.h>

static void save_ppm(const char *path, const uint32_t *pixels, int width, int height) {
  FILE *f = fopen(path, "wb");
  fprintf(f, "P6\n%d %d\n255\n", width, height);

  for (int i = 0; i < width * height; i++) {
    uint32_t px = pixels[i];
    fputc(px & 0xFF, f);          /* red   */
    fputc((px >> 8) & 0xFF, f);   /* green */
    fputc((px >> 16) & 0xFF, f);  /* blue  */
  }

  fclose(f);
}
```

## Step 5: Clean up

Destroy the player before freeing the buffer. The player holds the pointer you handed it in step 2, so releasing the memory first would leave it with a dangling reference:

```c
dotlottie_destroy(player);
free(buffer);
```

## The complete program

```c
#include "dotlottie_player.h"

#include <stdio.h>
#include <stdlib.h>

#define WIDTH  512
#define HEIGHT 512

static void save_ppm(const char *path, const uint32_t *pixels, int width, int height) {
  FILE *f = fopen(path, "wb");
  if (!f) return;

  fprintf(f, "P6\n%d %d\n255\n", width, height);
  for (int i = 0; i < width * height; i++) {
    uint32_t px = pixels[i];
    fputc(px & 0xFF, f);
    fputc((px >> 8) & 0xFF, f);
    fputc((px >> 16) & 0xFF, f);
  }
  fclose(f);
}

int main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "usage: %s <animation.lottie|animation.json>\n", argv[0]);
    return 1;
  }

  DotLottiePlayer *player = dotlottie_new_player(0);
  if (!player) {
    fprintf(stderr, "could not create player\n");
    return 1;
  }

  uint32_t *buffer = malloc(WIDTH * HEIGHT * sizeof(uint32_t));
  if (!buffer) {
    dotlottie_destroy(player);
    return 1;
  }

  int status = 1;

  if (dotlottie_set_sw_target(player, buffer, WIDTH, HEIGHT, ABGR8888) != DOTLOTTIE_SUCCESS) {
    fprintf(stderr, "could not set render target\n");
    goto cleanup;
  }

  dotlottie_set_background(player, 255, 255, 255, 255);
  dotlottie_set_loop(player, true);
  dotlottie_set_autoplay(player, true);

  if (dotlottie_load_animation_path(player, argv[1]) != DOTLOTTIE_SUCCESS) {
    fprintf(stderr, "could not load %s\n", argv[1]);
    goto cleanup;
  }

  float total = 0.0f;
  float duration = 0.0f;
  dotlottie_get_total_frames(player, &total);
  dotlottie_get_duration(player, &duration);
  printf("loaded %.0f frames, %.2f seconds\n", total, duration);

  dotlottie_set_frame(player, total / 2.0f);
  if (dotlottie_render(player) != DOTLOTTIE_SUCCESS) {
    fprintf(stderr, "render failed\n");
    goto cleanup;
  }

  save_ppm("frame.ppm", buffer, WIDTH, HEIGHT);
  printf("wrote frame.ppm\n");
  status = 0;

cleanup:
  dotlottie_destroy(player);
  free(buffer);
  return status;
}
```

Build and run it:

```bash
DOTLOTTIE_DIR=release/native/dotlottie-player

cc main.c -I$DOTLOTTIE_DIR/include -L$DOTLOTTIE_DIR/lib -ldotlottie_player -o player
LD_LIBRARY_PATH=$DOTLOTTIE_DIR/lib ./player animation.lottie
```

Open `frame.ppm` to confirm it rendered.

## Step 6: Animate over time

For continuous playback, replace the single render with a loop driven by `dotlottie_tick()`. It advances the animation by an elapsed time and renders only when the displayed frame actually changed, so you can skip presenting a frame that would be identical to the last one:

```c
#include <time.h>

static double now_seconds(void) {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return ts.tv_sec + ts.tv_nsec / 1e9;
}

double last = now_seconds();

while (!dotlottie_is_complete(player)) {
  double current = now_seconds();
  float dt = (float)(current - last);
  last = current;

  bool rendered = false;
  dotlottie_tick(player, dt, &rendered);

  if (rendered) {
    present(buffer, WIDTH, HEIGHT);   /* blit to your window or texture */
  }
}
```

<Callout type="warning" title="dt is in seconds, not milliseconds">
  `dotlottie_tick()` expects the elapsed time in **seconds**. The example inside the generated `dotlottie_player.h`
  computes `dt` from a millisecond clock without converting, which advances the animation 1000× too fast — don't copy
  it. If your timer reports milliseconds, divide by 1000 before passing the value.
</Callout>

Because looping is enabled in this program, `dotlottie_is_complete()` never returns true and the loop runs until you break out of it. Set `dotlottie_set_loop(player, false)` to have the animation finish on its own.

## Next steps

- [How the C API works](/en/runtimes/distributions/native/v0.x/how-the-c-api-works) — the design behind handles, results, and features
- [Choose a render target](/en/runtimes/distributions/native/v0.x/render-targets) — render with OpenGL or WebGPU instead of software
- [Control playback](/en/runtimes/distributions/native/v0.x/playback-control) — speed, direction, segments, and markers
- [C API reference](/en/runtimes/distributions/native/v0.x/api-reference) — every function and type
