5.7. Compondo imagens

As primitivas de desenho da página anterior pintam marcas geométricas sobre uma imagem – uma linha, um retângulo, um trecho de texto. Isso cobre a maioria das anotações que um algoritmo precisa tornar visíveis, mas não todas. Às vezes a anotação é, ela mesma, uma imagem: um quadro de referência capturado para exibir lado a lado com o atual, uma miniatura de uma captura anterior mostrada em um canto da pré-visualização, um modelo previamente armazenado visualizado sobre um quadro ao vivo para calibração. O mecanismo para desenhar uma imagem sobre outra é um único método – draw_image() – com parâmetros suficientes para lidar com a posição, o escalonamento, a paleta de cores e a transparência que uma composição real exige.

5.7.1. A chamada básica

Em sua forma mais simples, draw_image recebe outra Image e uma posição para desenhá-la:

reference = image.Image("/sdcard/reference.bmp")
img.draw_image(reference, x=10, y=10)

O destino é img; a origem é reference; o pixel superior esquerdo da origem cai em (10, 10) de img, e o restante dos pixels da origem segue para a direita e para baixo a partir daí. Os pixels do destino que a origem cobre são sobrescritos pelos pixels correspondentes da origem; os pixels fora da área coberta pela origem permanecem intactos.

Se a origem se estender além da borda do destino, as partes que ficam de fora são silenciosamente recortadas – o mesmo comportamento tolerante que set_pixel apresenta para posições fora do intervalo. O código da aplicação não precisa limitar a posição às dimensões da imagem com antecedência; ele pode passar a posição desejada e deixar que o método cuide do recorte.

5.7.2. Carregando um arquivo diretamente

draw_image aceita um caminho de arquivo no lugar do argumento Image e carrega o arquivo antes de compô-lo:

img.draw_image("/sdcard/reference.bmp", x=10, y=10)

Isso parece uma conveniência – uma linha em vez de duas – e é, mas a diferença vai além da sintaxe. Construir uma Image a partir de um arquivo aloca um buffer para armazenar os pixels decodificados, e esse buffer permanece até que a coleta de lixo o libere. Passar o caminho diretamente para draw_image permite que o módulo decodifique o arquivo em um buffer temporário, faça a composição a partir dele e libere o buffer quando a chamada retorna, sem que o código da aplicação tenha que manter uma referência a uma Image separada entre os quadros.

5.7.3. Escalonamento

Quando a origem e o destino têm tamanhos diferentes – uma captura de baixa resolução sendo composta sobre uma tela de resolução mais alta, ou uma miniatura que precisa ser dimensionada para uma fração específica do quadro – dois parâmetros de escala cuidam de redimensionar a origem conforme ela é desenhada:

img.draw_image(reference, x=10, y=10, x_scale=2.0, y_scale=2.0)

x_scale e y_scale são floats independentes; passar ambos com o mesmo valor escalona uniformemente, e passar valores diferentes estica ou encolhe a origem ao longo de um eixo. O escalonamento acontece no momento do desenho; a origem reference não é modificada.

Uma máscara de bits de flags de dica (hint) decide como o escalonamento realmente interpola entre os pixels. image.BILINEAR produz resultados mais suaves ao custo de mais processamento; image.BICUBIC produz resultados ainda mais suaves e custa mais ainda; o padrão usa o vizinho mais próximo, que é o mais barato e a escolha certa quando a origem já está na resolução de pixels do destino. As flags de tratamento de proporção – SCALE_ASPECT_KEEP, SCALE_ASPECT_EXPAND, SCALE_ASPECT_IGNORE – decidem o que fazer quando a proporção da origem não corresponde ao retângulo em que ela está sendo desenhada.

5.7.4. Mistura alfa (alpha blending)

Por padrão, draw_image substitui os pixels do destino pelos pixels da origem. Quando o objetivo é uma sobreposição translúcida – de modo que o destino apareça através da origem – o parâmetro alpha controla como os dois são misturados. alpha=0 mostra apenas o destino (nenhuma origem); alpha=255 é o padrão e mostra apenas a origem (substituição total); valores intermediários misturam os dois proporcionalmente:

img.draw_image(overlay, x=0, y=0, alpha=128)

Um argumento alpha_palette separado é o único mecanismo de alfa por pixel do módulo. Ele recebe uma imagem GRAYSCALE cujos valores são usados como alfa na posição correspondente da origem – um mapa de calor cujo alfa varia com sua intensidade, por exemplo. O alfa precisa ser fornecido como esse argumento separado em escala de cinza; uma imagem de origem que carrega seu próprio canal alfa (um PNG com transparência, digamos) não o transfere automaticamente.

5.7.5. ROI e paleta da origem

Dois parâmetros adicionais completam o mecanismo de composição:

  • roi=(x, y, w, h) restringe a origem a um sub-retângulo dela mesma, de modo que apenas esse retângulo seja composto sobre o destino. Útil para recortar dentro da mesma chamada, sem preparar um intermediário recortado.

  • color_palette substitui o valor de cada pixel da origem por uma consulta em uma tabela de lookup antes de desenhar – o mesmo mecanismo que to_rainbow() e to_ironbow() usam, exposto aqui para que uma sobreposição possa ser paletizada a caminho do destino sem um passo de conversão separado.

Ambos se combinam com tudo o mais na chamada: o escalonamento, o alfa, o argumento mask do lado do destino e o parâmetro roi do lado do destino que limita a escrita a um retângulo do destino.