Bit Block Transfers (BitBlt)

Deixe-me mostrar, caso a caso, como a transferência de pixels de uma superfície para outra pode ser feito. A ideia é criar uma função que faça isso “sozinha”, com base em 5 argumentos:

int BitBltCopy(struct surface *dest,
               int x, int y,
               struct surface *src,
               struct rect *rc);

Aqui, queremos trasferir pixels da região do retângulo rc da superfície src para a posição (x,y) da superfície dest. A rotina deverá levar em conta o formato de pixels das duas superfícies, seus tamanhos (e o da região) e os casos especiais citados no artigo anterior (aqui). Para facilitar, vou considerar apenas o formato PELFMT_RGBA aqui, mas, no final do artigo, farei considerações sobre os outros formatos.

O endereço do primeiro pixel do retângulo, considerando-o totalmente dentro da superfície src pode ser facilmente calculado como:

static size_t surface_getpelsize(struct surface *surfp)
{
  switch (surfp->pelfmt)
  {
  case PELFMT_INT: return sizeof(int);
  case PELFMT_RGB: return 3;
  case PELFMT_RGBA: return 4;  
  }
  return 1;
}
void *surface_getptr(struct surface *surfp, uint32_t x, uint32_t y)
{
  return surfp->buffp + (y * surfp->width + x) * surface_getpelsize(surfp);
}

Claro, o caso onde temos PELFMT_1BPP é um caso especial e não deve ser considerado aqui.
Considerando que a região “fonte” esteja totalmente dentro da superfície “fonte” e a posição (x,y) “destino” esteja dentro da superfície “destino”. e que ambas as superfícies tenham o mesmo formato, a função BitBltCopy fica bem simples:

int BitBltCopy(struct surface *dest,
               int x, int y,
               struct surface *src,
               struct rect *rc)
{
  char *srcptr, *destptr;
  size_t row,
         pixel_size, 
         src_scanline_size, dest_scanline_size;

  srcptr = surface_getptr(src, rc->left, rc->top);
  destptr = surface_getptr(dest, x, y);
  pixel_size = surface_getpelsize(src);
  src_scanline_size = src->width * pixel_size;
  dest_scanline_size = dest->width * pixel_size;

  for (row = rc->top; row < row->bottom; row++)
  {
    _memcpy(destptr, srcptr, (rc->right - rc->left) * pixel_size);
    srcptr += src_scanline_size;
    destptr += dest_scanline_size;
  }

  return 1;
}

Um ponto importante aqui é o uso da função _memcpy. Lembre sempre: A ideia aqui é fazer transferência de blocos, nunca (ou quase nunca) bytes ou bits individuais… Dito isso, a rotina acima faz o trabalho nas condições ideais citadas, no entanto, temos os casos especiais…

A posição (x,y) pode não estar dentro da surperfície destino:

Se (x,y) estiver além da área da superfície, podemos desconsiderar a cópia. E mais: Se o retângulo fonte estiver totalmente fora da superfície destino, também desconsideraremos a cópia:

 ...
  if (x >= dest->width || y >= dest->height) return 0;
  if (x <= (int)src->width || y <= (int)src->height) return 0;

  /* ... o resto da rotina ... */
  srcptr = surface_getptr(src, rc->left, rc->top);
  destptr = surface_getptr(dest, x, y);

Este é apenas um exemplo dos testes de casos especiais que podemos colocar na rotina. Deixo os detalhes para você, dadas as explicações deste artigo…

A posição (x,y) pode estar “parcialmente” dentro da surperfície destino:

É o caso, por exemplo, das regiões periféricas no diagrama abaixo. Note que temos 8 casos especiais onde as áreas em verde-escuro da região fonte serão copiadas para a superfície destino. Isso significa que teremos que mudar o cálculo dos ponteiros iniciais e do tamanho da linha de varredura para esses casos.

Casos de uso

A rgião pode estar, também, “parcialmente” dentro da superfície fonte:

Este caso é análogo ao diagrama acima, mas ao invés de considerarmos os bits copiados temos que considerar os bits que iremos copiar. Por exemplo, considere o caso do canto superior direito… Apenas os pixels verde-escuros devem ser copiados para a superfície destino… Isso poderia ser feito recalculando o retângulo fonte, mas os pixels “invisíveis” devem permanecer invisíveis na superfície destino.

O caso acima pode ser exemplificado com o caso citado acima, em relação à superfície fonte e o caso central para a superfície destino. O diagrama abaixo demonstra o problema… A região em vermelho contem os pixels que serão copiados na superfície destino, a área verde dessa superfície não deve ser alterada.

Claro que teremos que modificar o retângulo a ser copiado, mas temos que modificar também as coordenadas (x,y) para onde o retângulo efetivo será copiado na superfície destino… Isso faz com que a cópia seja bem mais rápida…

Considere, também o caso onde, na superfície destino, apenas um pedaço do regângulo efetivo tenha que ser copiado, como abaixo:

Aqui, apenas a área “vermelha mais escura” será copiada, todo o resto deve ser ignorado (incluindo o “vermelho menos escuro”).

BitBltCopy() não considera retângulos de tamanhos diferentes entre fonte e destino!

Você pode querer fazer algo como copiar uma região fonte de tamanho definido para uma região destino com outro tamanho definido, mas diferente do tamanho da origem… Ou seja, pode querer mudar a escala (para criar um efeito de zoom in ou zoon out). Isso não é feito pela função BitBltCopy, que já deve ser complicada o suficiente com todos esses casos especiais. A assinatura da função seria similar à BitBltCopy, com uma diferença. Ao invés de especificarmos a posição (x,y), na superfície destino, especificamos o retângulo destino:

int BitBltStrech(struct surface *dest,
               struct rect *rcDest,
               struct surface *src,
               struct rect *rcSrc);

Todos os casos especiais acima continuam válidos, exceto pelo fato de que teremos uma superfície intermediária que conterá a região efetiva, da superfície fonte, interpolada que, depois, será copiada para o retângulo destino da superfície destino.

Essa função, obviamente, é mais lenta que BitBltCopy, já que essa superfície intermediária precisa ser calculada, possivelmente usando ponto flutuante.

Não se esqueça dos formatos de pixels:

As explicações acima não levam em conta a diferença de formatos de pixels entre superfícies. Podemos separar as funções naquelas que não levam em conta essa distinção e outras que serial “independentes de dispositivo”, que as levariam. Essa diferenciação tem um custo alto: Não podemos, simplesmente, copiar blocos via _memcpy, mas teríamos que lidar com pixels individuais, implicando em um loop interno para cada posição X que queiramos copiar.

Outra solução viável, mas também um pouco custosa, é converter as regiões fonte para uma superfície intermediária com o mesmo formato da superfície destino. Essas conversões podem ser especializadas, do tipo funções nomeadas surface_create_rgba_from_rgb() ou surface_create_rgb_from_8bpp(), por exemplo. Uma função mais genérica como surface_create_intermediary() poderia fazer a mágica verificando os formatos de pixel das superfícies fonte e destino e criando a superfície intermediária com base nelas e usaríamos essa nova superfície num BitBltCopy para finalizar o processo. A vantagem é que teremos uma superfície intermediária menor que a fonte (supostamente)…

Anúncios