Redimensão de imagens (C# + WPF + MVVM + Asynchronism + Comentários)

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.

app-resizeme-512

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.

clip_image002

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.

clip_image004

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

Anúncios

2 Responses to Redimensão de imagens (C# + WPF + MVVM + Asynchronism + Comentários)

  1. 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!

  2. Vlw, grande Magoo!
    []s!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: