- Como o GitHub Pages não oferece suporte a Brotli, este é um experimento que codifica HTML como uma imagem WebP sem perdas e o restaura com o decodificador de imagens do navegador e JavaScript para reduzir o volume de dados transferidos
- Um decodificador Brotli em WASM adiciona um custo extra de
71 KiB~200 KB, e a Compression Streams API só suportagzip,deflateedeflate-raw, o que dificulta usá-la como atalho para decodificação de Brotli - O WebP sem perdas VP8L pode gerar resultados menores que gzip até para dados textuais graças à transformação preditiva, ao reuso de árvores de Huffman por blocos de 16x16 e ao color cache
- Um HTML de teste com
439,478bytes foi reduzido para94,683bytes com gzip e até43,182bytes com WebP; ainda maior que os37 KiBdo Brotli, mas cerca de 2,2x menor que gzip - Por causa do ruído anti-fingerprinting do Canvas 2D, da mudança no
readPixelsdo WebGL, da tela em branco inicial e de problemas na restauração de rolagem, isso ficou mais próximo de um hack que revela limitações do navegador do que de algo aplicável na prática
O problema de não poder usar Brotli no GitHub Pages
- Ao reduzir o tempo de carregamento de uma página, a compressão HTTP tem efeito maior do que minificar o HTML
- O HTTP oferece suporte a gzip e Brotli por meio do cabeçalho
Content-Encoding- gzip é barato, então costuma vir ativado por padrão
- Brotli normalmente comprime melhor que gzip, mas é muito mais lento
- Como o GitHub Pages não oferece suporte a Brotli, o texto mais longo do site,
Recovering garbled Bitcoin addresses, fica com92 KiBem gzip em vez de poder ter37 KiBcom Brotli - Essa diferença aumenta desnecessariamente o tempo de carregamento em 2,5x
As primeiras alternativas imaginadas e onde elas emperraram
- Se o GitHub permitisse enviar e servir arquivos Brotli pré-comprimidos, o problema poderia ser evitado, mas esse recurso não existe
- Descompactar no cliente diretamente com JavaScript perde vantagem por causa do tamanho do decodificador WASM
- brotli-dec-wasm tem cerca de
200 KB - tiny-brotli-dec-wasm tem
71 KiB - A comparação passa a ser entre gzip com
92 KiBe Brotli com37 KiB + 71 KiB, e o ganho desaparece
- brotli-dec-wasm tem cerca de
- A pilha HTTP do navegador já tem um decodificador Brotli, mas o DecompressionStream da Compression Streams API só aceita
gzip,deflateedeflate-raw - Mesmo pré-comprimindo
gzipcom Zopfli, o resultado foi86 KiB, ainda maior que Brotli
Usando um formato de imagem como contêiner de compressão
- Como o navegador já consegue decodificar imagens, é possível colocar os dados nos pixels da imagem e lê-los de volta com a Canvas API sem adicionar uma lógica nova de descompressão
- GIF organiza os dados em ordem row-major e depois aplica LZW, mas o DEFLATE do gzip foi projetado justamente para substituir o LZW, então é difícil esperar vantagem
- PNG usa DEFLATE, mas antes aplica uma transformação preditiva que comprime não os pixels originais, e sim a diferença em relação aos pixels vizinhos
- Ex.: compactar
[a, b, c, d]em vez de[a, b-a, c-b, d-c] - Quanto menor a diferença entre o valor previsto e o real, mais favorável fica a compressão com Huffman
- Ex.: compactar
- O núcleo do experimento é usar o VP8L, a variante sem perdas do WebP, para comprimir dados genéricos de bytes
Em que o VP8L difere do gzip
- O WebP tem variantes com e sem perdas, e aqui só entra o formato sem perdas VP8L
- Assim como PNG, o VP8L usa transformação preditiva, mas no lugar do DEFLATE usa um método parecido, criado pelo Google
- O DEFLATE divide o arquivo em vários trechos e pode usar árvores de Huffman adequadas a cada um deles
- Se um HTML mistura JavaScript, SVG e markup, árvores diferentes podem ser vantajosas
- O VP8L define uma tabela potencialmente grande de árvores de Huffman e pode usar árvores diferentes para cada bloco de 16x16 pixels
- Quando há JavaScript, depois CSS, depois JavaScript de novo, o DEFLATE pode acabar codificando árvores parecidas várias vezes
- O VP8L consegue reutilizar árvores, então pode trocá-las com mais frequência e a custo menor
- O color cache do VP8L permite representar valores de forma curta, como “copie um pixel recente com certa propriedade”
Primeiro experimento de compressão com WebP
- O arquivo de teste era o HTML de
Recovering garbled Bitcoin addresses- Tamanho original:
439,478bytes gzip --best:94,683bytes
- Tamanho original:
- Com a crate webp do Rust, os bytes foram convertidos em uma imagem RGB em tons de cinza e comprimidos em WebP sem perdas
- O motivo para usar grayscale foi a transformação
subtract greendo WebP- Em grayscale, ao subtrair o canal G de R/B, R/B viram praticamente 0
- Como o WebP codifica os três canais com árvores de Huffman separadas, canais com valor fixo ocupam espaço próximo de
O(1)
- A ideia inicial era criar uma imagem
1xN, mas o WebP só suporta até16383x16383, então ocorreu o erroVP8_ENC_ERROR_BAD_DIMENSION - Ajustando para o formato
16383xN, o resultado ficou em45,604bytes, 2x menor que gzip e até menor que49,764bytes do bzip2
Ajustes específicos para WebP
- Em uma imagem larga, usar ordem row-major mistura dentro de um bloco 16x16 bytes que estavam distantes entre si na entrada
- Ao mudar o formato da imagem para um
27x16383alto e estreito, a compressão caiu para43,232bytes - Foram comparadas as configurações method do
cwebp, de0~6- method 0:
48,902 - method 1:
43,546 - method 2:
43,442 - method 3:
43,292 - method 4:
43,232 - method 5:
43,182 - method 6:
43,182
- method 0:
- O method
5foi escolhido por produzir o mesmo tamanho do method6, mas ser mais rápido - Nesse ponto, o WebP ficou 2,2x menor que gzip e 1,2x maior que Brotli
Benchmark com vários arquivos
- Os alvos de comparação foram o snappy testdata, o Canterbury Corpus e Large Corpus e 2 arquivos SVG
- Os formatos comparados foram
gzip --best,brotli --best,bzip2 --beste o script de compressão com WebP - Tirando arquivos muito pequenos como
grammar.lsp,xargs.1e algumas exceções, o WebP quase sempre foi melhor que gzip - As exceções foram
kennedy.xlsepaper-100k.pdf- Em
paper-100k.pdf, há19 KBde XML seguidos por dados já comprimidos, então na prática o teste mede um volume pequeno de dados kennedy.xlstambém tem desempenho estranho em relação a Brotli e bzip2 e pode ser um arquivo difícil para compressores, com muitos dados heterogêneos em posições próximas
- Em
- O WebP tende a ser um pouco pior que bzip2, embora em alguns casos consiga superar
- O WebP sempre foi pior que Brotli, exceto em casos como
fireworks.jpeg, que é quase um blob aleatório uniforme - Em grandes volumes de texto puro, ele mostrou melhora mensurável em relação ao gzip
- Houve melhora também nos arquivos SVG
- Em
html_x_4, o WebP atingiu taxa de compressão de3.3%, pior que os2.8%do Brotli, mas muito melhor que os13%do gzip
Restaurando com JavaScript
- A decodificação do WebP em si pode ser implementada com
fetch,createImageBitmap,OffscreenCanvas,getImageDataeTextDecoder - A estrutura usa o canal R dos pixels como bytes do HTML original, decodifica isso em UTF-8 e depois injeta o resultado em
document.documentElement.innerHTML - A Canvas API é muito usada para fingerprinting, então alguns navegadores adicionam ruído ao resultado de
getImageData- No modo strict tracking protection do Firefox, menos de 1% dos pixels podem ser afetados
- No HTML, esse ruído aparece como se fossem erros de digitação
- Usando
readPixelsdo WebGL, na época isso funcionava sem ruído- O WebGL só oferece suporte confiável a texturas de até
2048x2048, então foi preciso ajustar novamente os limites de tamanho - Esse código de restauração, após minificação, tinha cerca de
550bytes
- O WebGL só oferece suporte confiável a texturas de até
- Somando WebP e código, o total ficou em
44 KiB, comparado com92 KiBdo gzip e37 KiBdo Brotli - Depois de 15 de abril de 2026, o Firefox passou a aplicar anti-fingerprinting também ao
readPixels, então esse método deixou de funcionar como antes
Cintilação de tela e problema de rolagem
- Como
awaitfunciona com promises, o navegador entende que a execução do script terminou antes do fim do download do WebP - Como o DOM ainda está vazio, o usuário vê por um instante uma tela branca vazia
- Como mitigação, o estilo e cerca de
8 KiBdo topo da página podem permanecer no HTML em gzip, deixando só o conteúdo abaixo da viewport comprimido em WebP - Restaurar a posição da rolagem ao recarregar também vira um problema
- Por exemplo, se a página for recarregada em
Y = 5000px, mas a altura da página ainda for0px, a posição é resetada - Adicionar uma
divtemporária muito alta ajuda
- Por exemplo, se a página for recarregada em
- É preciso atribuir o HTML a
document.documentElement.innerHTMLem vez de usardocument.write, para atualizar o documento atual sem trocá-lo por um novo
Colocando o WebP diretamente no JavaScript
- Para reduzir ainda mais a latência, é possível embutir o WebP diretamente no JavaScript
- A forma mais simples é uma data URL em base64
- O base64 aumenta o tamanho original em
1,33x, mas o gzip praticamente compensa esse aumento- Ao converter
compressed.webppara base64, ele passa a ter57,576bytes - Com
gzip --best, isso cai para43,519bytes
- Ao converter
- Um blob comprimido como WebP é quase um conjunto uniforme de dados aleatórios, e a conversão de 8 bits para 6 bits do base64 faz com que a árvore de Huffman do gzip atue quase como uma transformação inversa
- Também daria para usar Unicode e UTF-16, mas o base64 já serve como primeira solução suficiente
Aplicação real e situação atual
- No momento em que o texto foi escrito, a própria página estava comprimida em WebP a partir da seção “Fool me twice”, a menos que o navegador fosse antigo ou o JavaScript estivesse desativado
- A imagem WebP da página real, no código, era alta e estreita, mas também foi fornecido um exemplo quadrado de WebP para facilitar a visualização
- Na imagem, as áreas claras no topo e na base eram texto e código, a região hachurada perto de 1/5 da altura era um diagrama, e a maior parte das áreas escuras era texto dentro do diagrama
- O ganho real acabou sendo limitado
- Página original com gzip:
88 KiB - Página com WebP aplicada, ainda em gzip:
83 KiB - Estimativa com Brotli:
69 KiB
- Página original com gzip:
- Depois de 15 de abril de 2026, para evitar que visitantes do Firefox vissem conteúdo corrompido, a página voltou a ficar sem essa compressão
- O código em Rust, os corpora e outros arquivos estão disponíveis no GitHub
1 comentários
Opiniões no Hacker News
Se você ignorar a latência, até faria sentido, mas na prática parece algo como um aumento de 0,001% no tempo de carregamento
O aumento de tamanho é insignificante em comparação com a latência de ida e volta, e o tempo gasto na descompressão pode até ser maior do que o tempo economizado ao transmitir 55KiB a menos
É um experimento interessante, mas neste caso a experiência do usuário provavelmente ficaria pior; a velocidade seria quase a mesma e só a compatibilidade cairia
O problema é que há gente, como o autor do TFA, se esforçando para reduzir 100KB para 50KB, mas também há lugares que me enviam dezenas de MB em imagens sem pensar duas vezes quando eu só quero ver o horário de funcionamento de um restaurante usando dados em roaming
A consciência de recursos existe, mas infelizmente está distribuída de forma muito desigual
Se a conexão cair, você perde tudo, e nem consegue ler apenas a parte que foi baixada
Em conexões comuns, a diferença não é significativa; em conexões muito lentas ou instáveis, nas quais 50KB importam, essa abordagem é claramente pior. É um experimento interessante, mas eu preferiria que não fosse aplicado a sites
Symbols-2048-em%20Nerd%20Font%20Complete.woff2de 850K, o que praticamente anula essa diferençaNão será uma diferença de 2,5x, mas também não é 0,001%
Em redes com perdas talvez faça diferença, mas não tenho certeza
Não entendo por que
readPixelsnão é alvo de proteção contra fingerprinting. Para mim tudo bem, já que não costumo espalhar erros de digitação quase invisíveis pela página inteiraA parte que diz que no HTML gzipado ficam apenas os estilos e cerca de 8KiB do topo da página, enquanto só o conteúdo abaixo da viewport é comprimido em WebP, explica por que o texto foi cortado de repente depois de uma frase aleatória e uma página em branco continuou em seguida
Estou usando LibreWolf, então o WebGL está desativado, e uso Chromium para jogos aleatórios da web que precisam de WebGL. Ao ativar o WebGL, o texto funcionou corretamente e, sinceramente, é uma técnica bem elegante
A WWW deve ser universalmente acessível, começando com HTML simples e sendo aprimorada progressivamente
Também é possível usar Brotli diretamente em navegadores web, mas obviamente há limitações
Acredito que a submissão ao JS1024 de 2022 [1] tenha sido a primeira demonstração desse conceito, e também há um código de prova de conceito para compressão arbitrária. Infelizmente, ele não serviu para o objetivo original, que era code golfing de tamanho
A limitação central é que, na prática, fica restrito a caracteres ASCII e, por motivos óbvios, é muito sensível à pilha de renderização. No momento parece não funcionar no Firefox
[1] https://js1024.fun/demos/2022/18/readme
[2] https://gist.github.com/lifthrasiir/1c7f9c5a421ad39c1af19a9c...
Só é possível usar o formato de arquivo de fonte WOFF2, para o qual o Brotli foi originalmente projetado, mas para aproveitá-lo é preciso criar um arquivo de fonte inteiro
Navegadores recentes geralmente higienizam fontes com o OpenType Sanitizer (OTS), porque colocar diretamente no sistema arquivos de fonte não confiáveis é muito perigoso; por isso, é necessário criar um arquivo WOFF2 que seja normal o bastante para o OTS aceitar, mas que ainda contenha internamente a sequência de bytes desejada e permita extraí-la
Depois de muitos fracassos, a ideia de se fixar nos advance widths, isto é, nas larguras dos glifos, que são codificadas como uma sequência de inteiros com sinal de 2 bytes quase sem restrições, é fantástica
O lado do Chromium bloqueou isso por muito tempo, mas o zstd também está chegando à web. Agora que finalmente entrou no Chrome, só falta o Safari acompanhar
Estou criando o Batch Compress (https://batchcompress.com/en) e, pouco depois de adicionar suporte a WebP, recentemente mudei para que ele fosse o padrão
Pelo que eu sei, eu já estava gerando os JPEGs menores entre as ferramentas de compressão para a web, mas o WebP saía com apenas cerca de 50% do tamanho do JPEG. Depois de adicionar o suporte, foi uma decisão fácil torná-lo o padrão
Como o site tem bastante usuários, achei que haveria algumas reclamações depois de mudar o padrão para WebP, mas agora, cerca de um mês depois, só houve uma pergunta ou reclamação relacionada a WebP
Parece que hoje quase todas as ferramentas e navegadores já têm suporte a WebP. Só vi recentemente um site que não conseguia lidar direito com uploads de imagens WebP, bloqueando a próxima etapa; fora isso, hoje em dia quase tudo dá suporte bem
Se você comprimir e otimizar bem o JPEG, ele não deveria ficar muito atrás do WebP
Você sempre pode reduzir o tamanho do arquivo criando um WebP que pareça quase igual ao JPEG, mas o mesmo vale se recomprimir como um JPEG que pareça quase igual
Isso é uma característica de todos os codecs de compressão com perdas e, como o tamanho do arquivo cresce exponencialmente conforme a qualidade aumenta, as pessoas sempre se surpreendem com o quanto uma perda de qualidade mínima, quase imperceptível, pode mudar bastante o tamanho do arquivo
O WebP tinha fama de ter padrões horríveis que destruíam detalhes em áreas escuras
Olhando o código-fonte, vi que está faltando um espaço na declaração do doctype. A forma atual está errada; deveria haver um espaço
O espaço omitido em
DOCTYPEpode deixá-lo mais curtoA rigor, não é HTML válido, mas ainda assim aciona com sucesso o modo padrão
Referência: https://GitHub.com/kangax/html-minifier/pull/970 / https://HTML.spec.WHATWG.org/multipage/parsing.html#parse-er...
Eu também uso esse truque em https://FreeSolitaire.win
Parece resultado de um minifier que remove o máximo possível [0]
0: https://github.com/KTibow/KTibow/issues/3#issuecomment-23367...
Já usei esse truque antes. Curiosamente não me lembro em quê, mas acho que era só para ver se era possível, e também deixei um comentário aqui: https://gist.github.com/gasman/2560551?permalink_comment_id=...
Também encontrei um protótipo antigo, que acho que era só um teste: https://retr0.id/stuff/bee_movie.webp.html
Hoje esse tipo de injeção de script é chamado de extensão, não de add-on, mas, de todo modo, achei interessante a abordagem de entregar “lixo” no começo e anexar JS depois para devolver à página
O nerd de segurança dentro de mim fica se perguntando se isso poderia viabilizar um ataque quando houver dados fornecidos pelo usuário, como um formulário de comentários
Talvez alguém conseguisse descobrir a sequência de bytes a colocar em um comentário para que, depois da compressão, ela virasse uma tag
scriptposicionada antes do meu script e fosse executadaA maior parte do que o WebP lossless acrescenta ao PNG diz respeito à modelagem, não à codificação, e esse tipo de compressão de texto acaba usando apenas a parte de codificação do WebP
Remover o Google Fonts também melhoraria um pouco o tempo de carregamento da página, pois ele é carregado de um servidor remoto e exige um handshake adicional
Esta página quebra pelo menos no navegador do Sailfish OS. Há um grande espaço em branco depois do seguinte parágrafo
“Alright, so we’re dealing with 92 KiB for gzip vs 37 + 71 KiB for Brotli. Umm…”
Ainda assim, o overhead da compressão HTML com gzip e Brotli não é nada comparado à quantidade de JS, imagens e vídeo que os sites usam hoje
Pessoalmente, não gosto muito desse formato. Quando salvo uma imagem e ela é salva como WebP, quase nada além do navegador oferece suporte, então preciso convertê-la antes de editar ou usar de forma significativa
Parece apenas uma etapa extra sendo imposta
Ainda assim, se o suporte aumentar, acho aceitável. Dá para tolerar um novo formato surgindo uma vez a cada 20 anos
.webmpode desaparecer