Redimensão de imagens é uma tarefa comum do dia-a-dia para quem trabalha com computadores. Redimensionamos imagens em tarefas do trabalho, lazer, e do cotidiano. Esta é uma tarefa que pode ser executada facilmente com um editor de imagens, mas é bastante custosa quando queremos redimensionar um conjunto de imagens.
Por conta do trabalho (e tempo) gasto com a redimensão de múltiplas imagens, e pela vontade de não depender da instalação de nenhum editor de imagens, acabei por criar um programa que faz essa redimensão em massa. Fiz isso quatro anos atrás e, como o programa se tornou “popular” entre amigos e familiares, percebi que era preciso fazer uma nova versão com tecnologias mais novas.
Construí uma versão mais atual com WPF e .Net Framework 4 (nem todo mundo tem o .Net Framework 4.5 instalado em suas máquina).
Achei que seria interessante compartilhar no blog esse aplicativo e comentar os detalhes de sua construção. Provavelmente, detalhar o que foi feito pode ajudar alguém no futuro e isso é bastante válido.
Para fazer download do aplicativo de redimensão de imagens clique neste link: Download ImageResize
Para baixar os fontes deste aplicativo acesse este link: Download dos Fontes
Redimensão de imagens
O algoritmo de redimensão de imagens pode trabalhar de duas maneiras:
1 – Fornecendo uma estrutura Size (System.Drawing) que indica qual a largura e a altura desejada.
2 – Passando um tamanho base (um valor inteiro) cujas proporções serão calculadas e a imagem redimensionada.
Note que estamos utilizando interpolação bicúbica de alta qualidade (InterpolationMode.HighQualityBicubic), isso garante que a redimensão feita produzirá imagens de alta qualidade. Outro ponto importante, veja que estamos utilizando o RawFormat das imagens para produção de sua redimensão.
Outro ponto importante: procuro não deixar nenhum recurso mais tempo do que o necessário em memória. Uso blocos USING em todo o algoritmo para limitar o escopo de uso de instâncias cujas classes implementam a interface IDisposable. Como sabemos, instâncias cujas classes implementam a interface IDisposable demonstram o consumo de recursos não gerenciados ou que podem comprometer o uso da memória heap da sua aplicação. Assim, quando usamos o bloco USING, auxiliamos o Garbage Collector a identificar instâncias que não serão mais utilizadas (devido ao seu bem definido e invoque do método “Dispose”). Ajudar no trabalho do Garbage Collector faz bem e não mata ninguém, fica a dica.
E abaixo a classe responsável por redimensionar imagens:
internal class ImageCompressor { public void ResizeAndSave(string imagePath, int baseSize, string destinyFolder) { using (Image targetImage = Image.FromFile(imagePath)) { byte[] newBytes = Resize(targetImage, baseSize); using (var ms = new MemoryStream(newBytes)) { using (Image imageToSave = Image.FromStream(ms)) { string finalPath = GetNewDestinyPath(imagePath, destinyFolder); imageToSave.Save(finalPath ); } } newBytes = null; } } private static string GetNewDestinyPath(string imagePath, string destinyFolder) { return Path.Combine(destinyFolder, Path.GetFileName(imagePath)); } public byte[] Resize(Image targetImage, float baseSize) { return Resize(targetImage, null, baseSize); } public byte[] Resize(Image targetImage, Size size) { return Resize(targetImage, size, 0); } private static byte[] Resize(Image targetImage, Size? size, float baseSize) { int finalWidth, finalHeight; if (size == null) { float width = (float)targetImage.Width, height = (float)targetImage.Height; float initialWidth, initialHeight = 0F; initialWidth = initialHeight = 0F; initialWidth = baseSize / width; initialHeight = baseSize / height; float baseMultiply = (initialHeight < initialWidth) ? initialHeight : initialWidth; finalWidth = (int)(width * baseMultiply); finalHeight = (int)(height * baseMultiply); } else { finalWidth = size.Value.Width; finalHeight = size.Value.Height; } byte[] returnValue = null; using (Bitmap bitmap = new Bitmap(finalWidth, finalHeight)) { using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(targetImage, 0, 0, finalWidth, finalHeight); } ImageFormat rawFormat = targetImage.RawFormat; using (Image imageToConvert = bitmap) { returnValue = ConvertToByteArray(imageToConvert, rawFormat); } } return returnValue; } private static byte[] ConvertToByteArray(Image imageToConvert, ImageFormat format) { byte[] returnValue = null; using (var stream = new MemoryStream()) { imageToConvert.Save(stream, format); returnValue = stream.ToArray(); } return returnValue; } }
Barras de progresso
Ao utilizar o aplicativo percebe-se que existem duas barras de progresso: uma delas dentro do formulário e outra na barra do Windows.
Acredito que a maioria de nós está familiarizada com o uso de barras de progresso em formulários, mas essa barra de progresso na barra do Windows é um diferencial.
Para causar este efeito devemos utilizar de um recuso do WPF chamado TaskBarItemInfo (System.Windows.Shell). A classe TaskBarItemInfo reúne um conjunto de membros que permitem a interação com o ícone de nossa aplicação na barra de tarefas do Windows. Com esta classe podemos definir o texto que será exibido, valores de progresso (como apresentado acima), estado do progresso (i.e. normal, indeterminado, erro, pausado… existe uma enumeração com estes estados, vide TaskbarItemInfo.ProgressState), e outros recursos menos populares.
E na minha opinião, o mais legal deste componente é a possibilidade de utilizarmos o binding do XAML com ele. Veja o trecho abaixo, ele foi extraído do MainWindows.xaml.
<Window.TaskbarItemInfo> <TaskbarItemInfo ProgressValue="{Binding Progress}"/> </Window.TaskbarItemInfo>
E abaixo o ProgressBar do formulário fazendo binding para a mesma propriedade:
<ProgressBar Grid.Column="0" Grid.Row="6" Grid.ColumnSpan="3" Name="ProgressBar1" Margin="3" Maximum="1" Value="{Binding Progress}"/>
Observação: a barra de progresso do TaskBarItemInfo não trabalha com valores de 0 a 100, essa barra trabalha com valores entre 0 e 1. Vale a pena ficar atento a este detalhe.
MVVM
Neste aplicativo fiz uso do padrão MVVM (Model-View-ViewModel). O MVVM é um padrão arquitetura que favorece a separação da interface gráfica da camada lógica da aplicação.
A camada View-Model é responsável por expor os dados da camada Model de forma que eles possam ser facilmente consumidos e manipulados pela camada View. O MVVM foi desenvolvido para que os recursos de data binding do XAML fossem melhor aproveitados, garantindo a separação da camada View da origem dos dados.
Existem frameworks que auxiliam na implementação do padrão MVVM, como o Prism e o MVVM Light. Nesta aplicação não fiz uso de nenhum dos dois, mas recomendo seu estudo e uso para projetos maiores.
Dica: em projetos com o padrão MVVM eu costumo reutilizar três classes que encontrei na comunidade, são elas:
– ViewModelBase: classe base para criação de ViewModels, e que pode ser encontrada aqui: Die optimale Implementierung des INotifyPropertyChanged – Interfaces
– DelegateCommand: classe própria para associação de propriedades do tipo ICommand com suas implementações concretas, e que pode ser vista neste post: Simplified MVVM Commanding with DelegateCommand
– AssyncObservableCollection<T>: classe genérica para criação de coleções que sofrem modificações assíncronas, que pode ser vista aqui: Binding to an asynchronous collection
BackgroundWorker
Depois que se começa a trabalhar com XAML fica difícil não fazer uso de recursos assíncronos. Costumo utilizar o BackgroundWorker, que nada mais é do que uma classe que fornece recursos para execução de tarefas assíncronas a partir de uma background thread (para saber mais sobre background threads veja este post Foreground threads e Background threads). O BackgroundWorker também fornece recursos para acompanharmos a execução e o progresso do trabalho em execução.
Captura de exceções não tratadas
Para garantir que a aplicação não será fechada quando uma exceção não tratada for encontrada (apesar de toda a atenção investida para que não ocorram erros), existe um controle de captura de exceções não tratadas.
Existe um evento na classe Application que é disparado sempre que uma exceção não tratada é encontrada, este evento se chama “DispatcherUnhandledException”. Para que este evento possa ser tratado é preciso que ele seja registrado no arquivo App.xaml e codificado no App.xaml.cs. Abaixo o código do App.xaml e em seguida o código do App.xaml.cs.
<Application x:Class="ImageCompress.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" DispatcherUnhandledException="Application_DispatcherUnhandledException"> <Application.Resources>
public partial class App : Application { private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { } }
Por
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft Most Valuable Professional – Visual C#
Referências:
http://msdn.microsoft.com/pt-br/library/system.windows.shell.taskbariteminfo(v=vs.110).aspx
http://msdn.microsoft.com/pt-br/library/system.windows.shell.taskbaritemprogressstate(v=vs.110).aspx
http://msdn.microsoft.com/pt-br/library/system.windows.shell.taskbariteminfo.progressstate(v=vs.110).aspx
Fernando, mais uma vez parabéns!
Nem sempre damos valor aos utilitários que criamospara facilitar nosso dia-a-dia, e o pior, nem mesmo compartilhamos eles com a comunidade para que juntos consigamos evoluir.
Vou analisar o fonte e conte com meus feedbacks, além é claro, de saber que vou aprender bastante.
Forte abraço!
Vlw, grande Magoo!
[]s!