09 abril, 2008

Gerando Thumbnails com HttpHandler

Como prometi em um post anterior, vou detalhar aqui alguns dos conceitos e motivações por trás do gerador de thumbnails que disponibilizei no CodePlex como parte integrante de uma biblioteca para agilizar desenvolvimento ASP.NET chamada CNZK Web Library. O código que está no CodePlex não contém ainda uma documentação formal mas está bem comentado. Vou mostrar o motivo de algumas das decisões que tomei no desenvolvimento dessa classe e tentar explicar o funcionamento de alguns trechos de código.

Para começar: As Motivações

Eu coordeno um time de desenvolvimento responsável por diversos projetos dos nossos clientes. Para poder dar conta de todos os projetos de desenvolvimento ou manutenção de sites com eficiência e agilidade foi necessário desenvolver e aplicar metodologias e padrões. Durante algumas reuniões nós tentamos identificar pontos em comum entre os projetos que pudessem ser padronizados e transformados em uma biblioteca para evitar que o trabalho fosse refeitos todas as vezes. Um ponto que era muito importante para mim era que a utilização dessa biblioteca fosse o mais simples possível, para encorajar o seu uso e aperfeiçoamento.

A geração de thumbnails de imagens pode não parecer muito óbvia quando se analisa os pontos em comum entre sites, até o momento em que você começa a olhar o código das áreas de administração do site e vê a complexidade que é embutida no código para fazer upload de 2 ou 3 imagens para cada produto ou item de uma página. Além disso, tem mais outros 2 fatores que foram muito importantes na decisão de fazer o gerador de thumbnails: Gerenciamento de muitos arquivos em disco no webserver; Facilidade e agilidade de uso da área administrativa do site.

Essa biblioteca do Codeplex não contemplará apenas o gerador de thumbnails. Há diversas outras classes que já tenho prontas ou parcialmente prontas que estão apenas passando por uma limpeza de código antes de fazerem parte da biblioteca. A classe de thumbnail foi escolhida para ser a primeira por ser a que estava em estado mais maduro e por ter sido a melhor testada e mais utilizada da biblioteca até agora em nossos projetos.

Segundo passo: Decisões Técnicas e Arquitetura

A idéia inicial era fazer uma classe que tornasse possível redimensionar facilmente as imagens no momento do upload, para que o usuário não precisasse fazer upload de2 ou 3 imagens diferente apenas por causa do tamanho. Após fazer uma primeira versão eu notei que isso não agilizaria muito o processo de desenvolvimento e também não seria um componente muito reutilizável.

Então eu encontrei um capítulo em um livro que mostrava como fazer um HttpHandler para redimensionamento de imagem. O código no livro trabalhava de forma semelhante ao código que eu gerei, mas o tamanho final das imagens não era parametrizável e as miniaturas geradas eram armazenadas em disco. Essas duas características ainda não eram exatamente o que eu queria, então eu fiz a minha versão.

HttpHandlers são um recurso extremamente interessante do ASP.NET, pois nos possibilitam criar a nossa própria extensão de arquivo e tratá-la da forma que precisarmos, como se fosse um novo tipo de arquivo. O único problema em se criar sua própria extensão de arquivo é que será necessário regitrar essa extensão no IIS e informar que ela deverá ser processada pelo ASP.NET para que o código do seu handler seja executado (aliás, é isso que o ASP.NET faz com as extensões aspx, ascx e axd). A extensão axd já é utilizada pelo ASP.NET justamente para processamento de handlers (um exemplo disso é o trace.axd).

Então, o que eu fiz foi implementar uma classe extendendo IHttpHandler e registrar a minha classe como sendo o handler para qualquer chamada que for feita para arquivos com a extensão ".thumb.axd". Para o IIS, o que interessa é a extensão axd, que indica que o ASP.NET deve processar a requisição. Eu poderia ter usado apenas a extensão ".thumb", mas teria que registrá-la no IIS do servidor do site, o que não seria muito prático, principalmente levando em consideração a quantidade de ambiente que precisariam ser configurados (desenvolvimento, homologação, produção, etc...).

Como Funciona

Eu tentei tornar a utilização simples, rápida e pouco intrusiva. Uma das vantagens de utilizar a extensão .axd, é que o IIS está configurado para simplesmente repassar qualquer requisição feita à arquivos com essa extensão para o ASP.NET, sem sequer verificar se o arquivo realmente existe. Isso tornou possível que eu utilizasse a extensão que escolhi (.thumb.axd) como sufixo das imagens originais. Assim eu poderia chamar um thumb para uma imagem usando a seguinte URI: "/img/foto.jpg.thumb.axd".

Agora tudo que eu precisava era passar alguns parâmetros para que o handler soubesse qual deveria ser o novo tamanho da imagem. Como eu não queria que a url ficasse poluída, fiz com que os parâmetros de tamanho (que são obrigatórios) fossem passados como se fizessem parte do nome do arquivo, da seguinte forma: "/img/foto.jpg.200x150.thumb.axd". Pronto, agora posso redimensionar qualquer imagem nos meus sites. Outra vantagem é que os profissionais de interface podem utilizar esse controle sem nenhuma interferência dos programadores, poupando trabalho dos 2 lados.

Superficialmente, a lógica que o código segue é a seguinte:

  • Verifica se a URI solicitada está no formato correto, utilizando Expressão Regular.
  • Obtém a imagem que será redimensionada. Verifica se a imagem original existe.
    • Se existir, abre o arquivo e obtém suas dimensões. Nesse momento é configurado o cache, vinculado ao arquivo original.
    • Se não existir, verifica se está configurado um arquivo padrão para imagem não disponível e o abre.
    • Se não houver arquivo padrão, cria uma imagem em memória com os dizeres "Imagem não disponível".
  • Faz o cálculo das proporções da imagem, para verificar se o tamanho solicitado é proporcional ao original.
    • Se o tamanho solicitado for proporcional, redimensiona a imagem para o novo tamanho.
    • Se não for proporcional, calcula o redimensionamento da imagem para que caiba no novo tamanho.
  • Cria uma nova imagem em memória com o tamanho solicitado.
  • Redimensiona a imagem obtida (original ou de aviso) para o tamanho proporcional calculado.
  • Coloca a imagem redimensionada centralizada dentro da nova imagem.
  • Define qual será o tipo de output (se o original for gif, a saída será gif. se não for gif, a saída será jpg).
  • Faz o output da imagem, codificada no formato correto.

Alguns pontos interessantes desse handler:

Os thumbnails gerados são armazenados em cache para evitar sobrecarga de processamento no servidor. Esse cache está vinculado ao arquivo da imagem original, ou seja, se a imagem original for alterada o cache expira.

Sempre será retornada uma imagem, existindo ou não a original. Isso poupa bastante código de verificação nas páginas e problemas de layout.

A imagem redimensionada não será distorcida. Se a imagem original não estiver nas mesmas proporções da solicitada, ela será redimensionada proporcionalmente para caber no tamanho solicitado.

O peso das páginas vai diminuir, poupando banda dos usuários do site e tornando o carregamento da página mais rápido.

Outros parâmetros

Esse controle não faz apenas redimensionamento de imagens. Conforme as necessidades foram surgindo, novas funcionalidades foram desenvolvidas. Abaixo tem uma lista do que o controle faz, além de redimensionar imagens:

  • Definição da cor de fundo. É possível definir a cor de fundo utilizando o parâmetro "bg" na URI. Essa cor será visível quando a imagem for de proporções diferentes da solicitada ou quando for gerada a "imagem não diponível".
  • Definição da cor de frente. Parâmetro "fg". É a cor que será utilizada no texto de "imagem não disponível".
  • Definição de texto. Parâmetro "txt". É o texto que aparecerá em "Imagem não disponível".
  • Definição do método do cálculo de proporção. Parâmetro "inside". Define se a imagem deve ser redimensionada para caber inteira dentro da nova proporção ou se haverá cortes nas bordas. Os cortes são feitos em apenas 2 lados, no caso de inside=false.
  • Definição de máscara. Parâmetro "mask". É possível colocar uma máscara translúcida sobre a imagem. A cor e opacidade da máscara são fornecidas no formato ARGB, em um hexadecimal de 8 caracteres. Ex.: mask=88FF0000 é igual a uma máscara vermelha com 50% de opacidade.
  • Definições globais no web.config. É possível definir valores padrão para todos os parâmetros acima, exceto a máscara, no web.config da aplicação.

Documentação

Estou finalizando uma documentação completa do código, com exemplos e detalhes de todos os parâmetros. Essa documentação será disponibilizada no Codeplex ainda nesta semana.

Resumo

Expliquei um pouco sobre o motivo da criação dessa biblioteca e detalhei melhor o funcionamento do gerador de thumbnails. Achei melhor não colocar nenhum código neste post pois o objetivo aqui não era ser técnico. Quem quiser mais detalhes, pode baixar o código fonte no projeto CNZK Web Library no Codeplex e dar uma olhada. Se houver qualquer dúvida ou sugestão, basta postar um comentário no Codeplex neste blog.

3 comentários:

Wagner Tadeu disse...

Parabéns pela explicação, obrigado! Era justamente o que eu procurava.

Abraço!

Kelps Leite de Sousa disse...

Wagner,
Essa é apenas uma versão preliminar. Continue acompanhando pois em breve eu devo atualizar o projeto com novas funcionalidades e uma documantação adequada.

Luis disse...

Parabéns pelo artigo!
Você já tem um exemplo em aspnet c# ?

Abraços
Luis