Este artigo é sobre o lado de engenharia dos quebra-cabeças deslizantes de imagem: como uma foto arbitrária se torna um puzzle jogável N×N. É a parte que os jogadores nunca veem e em que os desenvolvedores passam mais tempo do que esperam.
O pipeline
Três etapas, por ordem:
1. Corte quadrado
A maioria das fotos não é quadrada. Os ficheiros HEIC do iPhone são 4:3; as fotos do iPhone em vertical são 3:4. Um tabuleiro de quebra-cabeça deslizante é 1:1. O passo um é escolher que região quadrada da foto se vai jogar.
Duas abordagens comuns:
Corte central. Pegar no maior quadrado centrado que cabe. Rápido, previsível, ocasionalmente errado (sujeito fora do centro).
Corte interativo. Mostrar ao utilizador uma sobreposição quadrada arrastável. Deixá-lo escolher. Mais lento, mas o utilizador fica sempre com o que quer.
O Slide Puzzle usa corte interativo, com o central como predefinição. O corte é não-destrutivo — o ficheiro original na biblioteca de fotos do utilizador nunca é alterado.
2. Redimensionar para a resolução de trabalho
Depois de cortada, a imagem ainda costuma ter 3000×3000 ou mais (conforme a câmara). Para a jogabilidade isto é excessivo. Um tabuleiro 6×6 exibido a 320pt num ecrã de iPhone com fator 3× renderiza cada peça com cerca de 53pt = 160 píxeis de dispositivo. Uma resolução de origem de 1024×1024 dá uma resolução por peça de cerca de 170 píxeis — mais nítida do que o ecrã consegue mostrar.
Redimensionar para 1024 (por vezes 2048) é o padrão. Origens maiores desperdiçam memória e atrasam o carregamento sem melhorar o resultado visível.
3. Renderização de peças sob demanda
Esta é a parte que a maioria erra à primeira. A imagem não é mesmo cortada em 16 ficheiros separados. Isso seria lento, desperdiçado e desnecessário.
Em vez disso, a app renderiza cada peça desenhando a mesma imagem de origem num retângulo do tamanho da peça, com o retângulo de origem deslocado pela posição da peça na imagem-alvo. Em CSS chama-se truque do "background-position"; em iOS chama-se retângulo de clipping de um CGImage; em SwiftUI usa-se um Rectangle().clipped() sobre uma Image.
Em pseudocódigo, ao desenhar a peça na posição-alvo (row, col) de um tabuleiro N×N com uma imagem de lado S:
desenhar imagem em (-col * S/N, -row * S/N)
dentro de um retângulo de recorte de (S/N × S/N)
É isto. Dezasseis peças num 4×4 significam 16 chamadas para desenhar a mesma imagem de origem com 16 deslocamentos diferentes. A app nunca precisa de cortar o ficheiro.
É também por isso que cortar um tabuleiro 6×6 é instantâneo: são 36 chamadas ao mesmo renderizador, não 36 operações de ficheiro.
E a animação
Deslizar uma peça é uma transformação de translação sobre a peça renderizada. O conteúdo da imagem dentro da peça não muda — apenas a posição da moldura do retângulo de recorte no ecrã. Isto significa que a animação do deslize é GPU-trivial e corre a 120 Hz em ecrãs ProMotion.
Três detalhes de implementação que importam:
- O recorte e o conteúdo são irmãos, não pai/filho. Se estiverem encadeados, o conteúdo move-se com o recorte e o deslize parte-se.
- Use transform, não layout. Animar x/y via transform CSS ou offset em SwiftUI é acelerado por GPU; animar left/top não é.
- Pré-rasterize na primeira aparição. O primeiro frame de renderização de uma peça pode ser lento porque a imagem-fonte precisa de ser descodificada. Renderize todas as peças uma vez no início do jogo para as aquecer.
Armazenamento
Onde vive de facto a imagem importada?
Numa app que respeita a privacidade, dentro da sandbox da app, encriptada em repouso pelo iOS. A biblioteca de fotos continua a guardar o original; a app armazena uma cópia de trabalho a 1024×1024 na sua própria pasta Documents. Quando o utilizador apaga a app, a cópia de trabalho é apagada com ela.
Numa app baseada na nuvem, a cópia de trabalho vive num servidor algures. As implicações são muito diferentes — para a privacidade, para o jogo offline, e para o que acontece quando o servidor desaparecer. O Slide Puzzle é do tipo sandbox.
Orçamento de memória
Um quebra-cabeça deslizante de imagem 4×4 típico:
- Imagem de origem: ~3 MB de JPEG.
- Imagem descodificada em memória: 1024 × 1024 × 4 bytes = 4 MB.
- Uma cópia por jogo ativo.
Tudo bem. Em 6×6, a imagem de origem e o buffer descodificado têm o mesmo tamanho. O tabuleiro não precisa de mais memória.
Onde o orçamento de memória aperta é nas bibliotecas de capas — 300 capas × 4 MB = 1,2 GB se todas estiverem descodificadas ao mesmo tempo. As apps evitam isto descodificando sob demanda (só quando uma capa é mostrada) e libertando ao passar para background (apenas a imagem do jogo ativo é mantida em memória).
Casos especiais
Três coisas correm mal na prática:
Fotos com etiquetas de orientação EXIF. Uma foto tirada em vertical mas guardada na horizontal com uma etiqueta de rotação vai aparecer corretamente na biblioteca de fotos e errada no puzzle se a app se esquecer de aplicar a rotação EXIF. A primeira versão de toda a app de quebra-cabeça de foto tem este bug.
Imagens de origem muito grandes. Alguns ficheiros HEIC têm 6000×8000 píxeis. Carregar isto na memória em resolução máxima vai fazer crashar a app em iPhones mais pequenos. A correção é descodificação em streaming para um tamanho-alvo reduzido — o ImageIO da Apple suporta isto. Descodifique a 2048×2048 diretamente do disco; nunca em tamanho original.
Renderização sub-pixel. Tamanhos de peça que não dividem inteiramente a grelha de píxeis do ecrã produzem uma coluna fracionária de píxeis num lado de cada peça. Com clipping puro, isto aparece como uma linha fina. Corrige-se ou ajustando os tamanhos das peças a píxeis inteiros (com tremulação visível em tamanhos de tabuleiro não-padrão) ou deixando as peças sobreporem-se em 0,5 píxel (sem linhas, sem tremulação).
Estes não são problemas profundos, mas são uniformemente esquecidos pelos implementadores de primeira viagem.
Resumo
O pipeline é: cortar → redimensionar → recortar-e-transladar para renderizar peças → transformações de translação para animar deslizes. A imagem nunca sai do aparelho numa app que respeite a privacidade. O pipeline inteiro cabe em cerca de 200 linhas de código, mais outras 50 para o tratamento da rotação EXIF que toda a gente se esquece à primeira.
Os quebra-cabeças deslizantes de imagem são mais simples do que parecem de fora. A imagem permanece uma só imagem; as peças são apenas vistas enquadradas dela.