Desenvolvimento

19 ago, 2013

Utilização de novos formatos de imagem na web

Publicidade

O tamanho médio de uma página é hoje de 1200kB, e 60% disso são imagens. Com todo esse foco em desempenho e velocidade na indústria da web, seria de se pensar que inovação em novos formatos de imagem estivesse no topo das prioridades. Nem tanto. Na verdade, estamos vivendo em uma escassez autoimposta de formatos, o que efetivamente nos limita a gifs, pngs e jpegs.

Na prática, fazer uso de novos formatos tem sido algo penoso – recorde apenas a saga do png. Mas esperávamos que o png não fosse o último. De fato, se quisermos realmente impactar o desempenho da web, o formato das imagens é o lugar em que você deve atuar. Não há razão alguma para não termos dúzias de formatos especializados, cada um otimizado para um caso e um tipo específico de imagem. Mas antes de chegarmos lá, precisamos acertar alguns ponteiros…

Fazendo uso do novo “Magic Image Format”

Como exemplo prático, vamos imaginar que inventamos um novo “magic image format” (mif). Como utilizá-lo? Conforme um bug recente da W3C aponta, nosso markup não fornece nenhuma facilidade para especificar diferentes formatos para uma única imagem. Vamos assumir que acrescentamos tal mecanismo. A sintaxe não importa. Vou apenas inventá-la para fins de exemplificação:

[html]

<img srcset="awesome.jpeg 1x, awesome.mif 2x" alt="Use awesome MIF for retina screens!">

[/html]

Até agora, tudo bem. O navegador lê a página e decide carregar o arquivo .mif enquanto renderiza a página. O usuário ama a nossa imagem e decide compartilhá-la em sua rede social favorita: clica com o botão direito, copia a URL ou simplesmente arrasta a imagem para um bookmark ou extensão. Nesse ponto, temos um problema. Nossos amigos podem utilizar um navegador diferente, o qual pode não suportar arquivos .mif. Em vez de uma imagem maravilhosa, eles veem um elemento quebrado.

Por que isso aconteceu? Ao ser apresentado a todas as opções de imagens disponíveis, o navegador foi capaz de negociar o formato para o usuário, mas quando o usuário fez a escolha para todos os seus amigos, quebrou o processo para eles. Negociação no lado do cliente deixa de funcionar no momento em que os recursos deixam a página onde todas as representações estão disponíveis.

Agora, na teoria, os navegadores poderiam resolver isso ao assegurar que toda vez que eles pedissem a URL da imagem, fosse através de clicar e arrastar, de um clique com o botão direito ou mesmo por interação Javascript, uma URL “segura” fosse então retornada. Entretanto, no longo prazo, acho que essa não é a solução certa.

Humanos não são escaláveis

london-photo

Rápido, qual é o formato da imagem acima? Pergunta difícil. O arquivo é salvo como .png no meu servidor original, mas dependendo do seu navegador ela pode estar vindo tanto como .jpeg como quanto .webp ao tentar visualizar a página. O PageSpeed Service proxy testou vários formatos e decidiu recodificar a imagem para atingir uma melhor compressão (veja as configurações).

De fato, já sabemos, baseado em resultados empíricos, que nós humanos somos terríveis para otimizar imagens: nos esquecemos de redimensioná-las, escolhemos o formato errado, e trata-se de um trabalho entediante. Você poderia pensar que com três formatos de imagens a serem escolhidos, tal problema não existiria. Não é verdade. Há muito tempo parei de me preocupar com otimização manual de imagens. Computadores são muito melhores para essa tarefa do que nós, e eles não ligam para trabalho chato.

O formato não importa

Por conta do argumento, vamos dizer que otimizamos manualmente cada imagem. Em seguida, enumeramos cada variação:

[html]

<img srcset="path/awesome.jpeg, path/awesome.png, path/awesome.webp,
path/awesome.svg, path/awesome.mif, path/awesome.mif2"
alt="How many times do I need to repeat myself?">

[/html]

Isso é idiota, chato e um desperdício de bytes no markup. Precisamos de ferramentas de automatização que simplifiquem a tarefa de gerar o modelo repetido – não é improvável que o CSS existente cause tristeza. Por conta disso, a extensão do arquivo de imagem não importa – podemos viver sem ela.

Imagine que em vez disso inventamos um formato .img abstrato que atua como um formato substituto para o formato ideal. O que seria ideal? Seria uma função de conteúdo de imagem e de preferências e capacidades de user agent no momento da requisição. Quem realiza a otimização? O servidor, é claro. Dada uma única imagem, ele é capaz de recodificar, recomprimir, redimensionar e  retirar metadados desnecessários… e entregar o formato ideal.

Em um mundo com dúzias de formatos de imagens, a solução humana não escala – leia-se: markup não escala. Ao passo que computadores são fantásticos para fazer exatamente o tipo de trabalho de otimização necessário para resolver esse problema.

Negociação de tipo de conteúdo

Boas notícias, o HTTP 1.1 já antecipou todos os mecanismos para fazer com que isso funcione a partir de negociação por servidor (server-driven negociation):

  • O user agent indica quais tipos de arquivo que ele suporta ou está disposto a aceitar, através do cabeçalho de requisição Accept.
  • O servidor seleciona o formato e indica o tipo retornado através do cabeçalho de resposta Content-Type.

Perceba que a extensão no arquivo de imagem na URL não importa. A mesma URL pode retornar diferentes representações baseada em negociação de preferências cliente-servidor, e o navegador usa então o Content-Type para interpretar de forma apropriada a resposta. Não é necessário markup extra, nem ajustar manualmente cada imagem. Além disso, usuários não se importam com qual formato é feita a negociação, desde que a imagem funcione e seja recebida rapidamente.

Solução em linhas gerais

Se a negociação de conteúdo já está por aí, por que estamos tendo essa discussão? No papel, temos tudo que precisamos, mas, na prática, há alguns pontos de implementação para serem resolvidos. Primeiro, vamos ver o cabeçalho Accept enviado pelos navegadores modernos ao fazer uma requisição para uma imagem:

Chrome */*
Safari */*
Firefox image/png,image/*;q=0.8,*/*;q=0.5
Internet Explorer image/png,image/svg+xml,image/*;q=0.8, */*;q=0.5
Opera text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1

Os cabeçalhos do Chrome e do Safari são absolutamente inúteis: aceitamos qualquer coisa! Firefox e IE não fazem muito melhor. O Opera é o único que explicitamente enumera os tipos de arquivos suportados, que é o comportamento que queremos, apesar de também acrescentar alguns tipos desnecessários no início. Se queremos que a negociação acionada pelo servidor funcione, então a primeira tarefa é fazer com que os navegadores enviem cabeçalhos Accept que sejam úteis – um cabeçalho que realmente enumere os tipos suportados.

Entretanto, consertar o cabeçalho Accept é apenas metade do problema. O fato que a mesma URL pode ter múltiplas representações significa que todos os caches intermediários precisam ter uma forma de diferenciar as várias respostas. Felizmente, o HTTP 1.1 também possui um mecanismo para isso, o cabeçalho Vary.

(client)  >  Accept: image/jpeg, image/png, image/mif
  (server) >  Content-Type: image/mif
              >  Vary: Accept
              >  (object)

O servidor indica para os clientes que os recursos devem variar baseados nos valores do cabeçalho Accept do cliente ao retornar Vary: Accept. No exemplo acima, dadas as três opções de formatos, o servidor escolheu mif como a mais otimizada. Qualquer cache upstream pode armazenar de forma segura o arquivo em cache e servir o objeto mif para qualquer usuário que forneça o mesmo cabeçalho Accept. Se outro user agent enviar um cabeçalho com valor diferente, sem o image/mif, por exemplo, então um formato diferente será servido e armazenado em cache. Para o usuário, essa negociação é transparente.

Mas, mas, mas…

Mas isso coloca mais bytes de cabeçalho nas redes! Sim, coloca. Se enumerarmos explicitamente todos os formatos de imagens, então o cabeçalho será 50-100 bytes para envio. Metade das requisições (aproximadamente 40) em uma página padrão são requisições de imagens, o que significa aproximadamente de 2 a 4kB no total. Para download, essas imagens custam aproximadamente 600kB. Assumindo que podemos conseguir uma compressão de 10% a 30% melhor e um retorno do investimento da ordem de 30 a 45 vezes. E espero sei que poderemos fazer ainda melhor no futuro.

Além disso, para o HTTP 2.0, teremos compressão de cabeçalhos, o que amortizaria o custo de envio de cabeçalhos de imagem para uma única transferência de 50-100 bytes, em vez dos 2-4kb de hoje. Assim como no HTTP 1.1, podemos ser espertos e providenciar um mecanismo de escolha por parte do site, de forma que apenas sites que suportam a nova negociação receberão dos navegadores o cabeçalho atualizado – um mecanismo desse tipo merece sua própria discussão em separado.

Mas isso fragmentaria o cache! Pequenas mudanças no cabeçalho Accept têm o potencial de criar entradas duplicadas no cache. Isso não é nada de novo, e a especificação documenta formas de normalizar os cabeçalhos: letra minúscula, a ordem não importa etc. A proposta de usar “Key” foi projetada para resolver essa questão de uma forma genérica e que beneficie os caches.

Mas não há suporte para Vary por parte dos caches! Acontece que a maioria dos CDN (sistemas de entrega de conteúdo, em tradução do inglês) não colocarão em cache nada que use Vary: Accept. Isso é um triste estado das coisas e deveria ser considerado um bug. A boa notícia é que não se trata de algo extremamente complexo; na verdade, é uma questão de incentivos comerciais. Melhor otimização de imagens se traduz em menos bytes nas redes e melhor desempenho – o que está alinhado com o que qualquer fornecedor de conteúdo está tentando vender para você. Se o suporte do lado do cliente já existe, então suportar Vary: Accept é uma vantagem competitiva para todo cache e provedor de conteúdo.

Mas clientes antigos não funcionarão com Vary: Accept! O suporte para Vary tem sido instável em clientes antigos: o IE6 não colocará em cache nenhum elemento com Vary, o IE 7 coloca em cache mas faz uma requisição condicional, e assim por diante. Uma abordagem prática é fazer com que isso seja um a otimização futura que é ativada apenas em clientes que aceitem esse novo comportamento.

Mas agora eu preciso de um novo software no servidor para otimizar as imagens! Sim, você precisa. Essa é, mais uma vez, uma questão de incentivos para provedores de hosting e conteúdo. De fato, muitos provedores de conteúdo já realizam otimização de imagem experimentalmente. De forma parecida, projetos de software livre como mod_pagespeed e ngx_pagespeed são módulos que realizarão todo o trabalho necessário para que isso funcione.

Mas isso significa mais carga no servidor! Otimização dinâmica de imagens não existe sem uma contrapartida, mas seu tempo é mais valioso do que isso. O servidor pode otimizar o elemento, colocá-lo em cache, e esquecê-lo. Não há uma escassez global de ciclos de CPU e uma vez que haja incentivo, esse fluxo de otimização de imagens cairá no esquecimento.

Exemplo prático com WebP

O WebP é um novo formato de imagem que fornece compressão com e sem perdas para imagens na web. Imagens WebP sem perda são 26% menores quando comparadas com PNGs. Imagens WebP com perdas são de 25-34% menores quando comparadas com imagens JPEG com índice SSIM equivalentes. WebP suporta transparência sem perdas (também conhecidas como canal alfa) com apenas 22% de bytes a mais. Transparências também estão disponíveis com compressões lossy (com perdas) e normalmente resultam em arquivos 3x menores do que PNGs quando compressões do tipo lossy são aceitáveis para os canais de cores vermelho/verde/azul.

Economia de 25-35% para PNG e JPEG e até 60% para PNGs com canais alfa (transparência). Isso significa algumas centenas de kilobytes de economia para a maior parte das páginas. Algo que vale a briga.

Tanto o Chrome quanto o Opera suportam o WebP, assim como alguns proxies de otimização, tal como o mod_pagespeed, o serviço PageSpeed, Torbit, e alguns outros. Entretanto, por falta de contexto nos cabeçalhos Accept existentes, cada um deles é forçado a marcar recursos .webp com cabeçalhos Cache-Control: private. Isso faz com que cada requisição seja forçada a rotear para o servidor de otimização, que realiza a detecção do user agent e serve o tipo de conteúdo apropriado.

Como muitos apontaram, este método não é escalável: cada requisição é roteada para o servidor de origem, e marcar o recurso como privado “dribla” todos os caches intermediários. Apenas isso é suficiente para explicar por que não vemos uma adoção significativa do WebP ou de qualquer outro formato experimental na web moderna.

O plano de ação

Como primeiro passo, precisamos consertar e normatizar os cabeçalhos Accept. O cabeçalho do Opera é o mais próximo do que nós queremos, apesar de podermos restringir o content-type para image, ao requisitar imagens. Talvez algo como:

Accept: image/webp, image/png, image/jpeg, image/gif, image/svg+xml, image/bitmap

Com isso no lugar certo, haverá um incentivo para que provedores de conteúdo, proxies e servidores realizem a negociação para entregar o formato de imagem ideal. Finalmente, uma vez que o servidor puder escolher o formato, o cache do cliente terá um incentivo para fazer com que o Vary funcione. No final, cortamos centenas de kilobytes de dados de imagens, automatizamos a tarefa manual de selecionar os formatos ideias e teremos mecanismos de negociação preparados para um futuro no qual eles poderão escalar para dúzias de formatos de imagens. Todos ganham… até compensarmos os ganhos ao inserir mais imagens de gatos em nossas páginas…

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.igvita.com/2012/12/18/deploying-new-image-formats-on-the-web/