O gráfico animado dos sorteios (howto)

Alguns poucos leitores me perguntaram como foi que eu fiz aquele gráfico com os pontos “subindo” a cada 100 ms… Bem… existem várias formas de fazer. Delas, vou mostrar a que usei e explicar sobre uma alternativa:

Podemos criar uma série de imagens e depois sequenciá-las usando o ffmpeg. Essas imagens podem ser criadas num formato puramente textual, chamado PPM. O programinha que lê cada um dos sorteios e gera as imagens pode ser escrito assim:

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

/* WIDTH e HEIGHT */
#define W 60
#define H 228

static unsigned char buffer[H][W];
static int accum[61] = {};

static void draw(FILE *);

int main(void)
{
  int n, i;
  int v[6];
  char path[16];
  FILE *fin, *fout;

  if (!(fin = fopen("sorteios.lst", "rt")))
  {
    fputs("Erro tetando ler o arquivo com os sorteios!\n",
          stderr);
    return EXIT_FAILURE;
  }

  for (n=1;;n++)
  {
    // Se não conseguiu ler uma linha completa,
    // não temos mais linhas para ler.
    if (fscanf(fin, "%d %d %d %d %d %d",
          v, v+1, v+2, v+3, v+4, v+5) != 6)
      break;

    // Calcula a acumulação...
    for (i = 0; i < 6; i++)
      accum[v[i]]++;

    // Este será o nome do novo arquivo.
    sprintf(path, "%05d.ppm", n);

    // Cria o arquivo, preenche o "frame buffer"
    // e cria o arquivo PPM.
    printf("Amostra %d\r", n);
    fout = fopen(path, "wt");
    draw(fout);
    fclose(fout);
  }

  putchar('\n');
  fclose(fin);

  return EXIT_SUCCESS;
}

void draw(FILE *f)
{
  int x, y;

  memset(buffer, 255, sizeof buffer); // fundo branco.
  for (x = 0; x < 60; x++)
    buffer[226 - accum[x+1]][x] = 0;  // pixel preto.

  // Header to arquivo PPM.
  fprintf(f, "P3\n%d %d\n255\n", W, H);
  for (y = 0; y < H; y++)
  {
    for (x = 0; x < W; x++)
      if (buffer[y][x])
        fputs("255 255 255 ", f);
      else
        fputs("0 0 0 ", f);

    fputc('\n', f);
  }
}

Claro, eu escalonei um cadinho mais para ficar mais “visível”, mas, em essência, é esse código ai…

Depois desse programinha teremos 1960 arquivos, nomeados de 00001.ppm até 01960.ppm, bastando, agora, usar o ffmpeg para juntá-los e criar a animação. Jogaremos os arquivos PPM no lixo, depois disso:

$ ffmpeg -i %05d.ppm -qmax 11 -r 10 anim.gif && rm *.ppm

A outra maneira de criar a animação é usando uma biblioteca dedicada. Por exemplo, libgd. No caso, cada “quadro” é formado preenchendo um retângulo branco em todo o frame e plotando os pixels nas coordenadas correspondentes, mais ou menos como fiz na rotina acima, só que usando gdImageFilledRectangle e gdImageSetPixel.

A biblioteca libgd já grava a saída diretamente num arquivo GIF. Tudo o que devemos fazer é criar uma imagem com gdImageCreate, iniciar uma animação com a função gdImageGifAnimBegin e ir adicionando os quadros com gdImageGifAnimAdd… No final dos 1960 quadros chamamos gdImageGifAnimEnd e, antes de fechar o stream de saída, gdImageGif, para terminar o processo… Ahhh, o delay, em milissegundos, é ajustado em gdImageGifAnimAdd

OBS: A função gdImageGifAnimAdd permite o uso de uma referência ao quadro anterior, para tentar realizar quadros “diferenciais”, tornando o GIF menor. Isso exige que o quadro anterior seja “clonado” antes de criarmos o novo quadro.

A vantagem de usar libgd é que não temos que nos preocupar com a degradação da qualidade da imagem que fatalmente ocorrerá ao usarmos o ffmpeg para converter os vários PPM para GIF (qualidade que tentei manter ao forçar a barra com a opção -qmax 11).

E, se você está curioso… eu usei o primeiro método porque não estava com paciência de depurar o programinha, usando libgd