Criptografando dados com C# e MD5
O exemplo abaixo demonstra como criptografar dados utilizando C# e o algorítmo MD5. O Namespace System.Security.Cryptography oferece os recursos necessários para esta implementação.
O exemplo abaixo demonstra como criptografar dados utilizando C# e o algorítmo MD5. O Namespace System.Security.Cryptography oferece os recursos necessários para esta implementação.
Introdução
Muitas aplicações .NET utilizam o Namespace System.Drawing.Printing para criar documentos de impressão (PrintDocument) e enviá-los para impressora ou para janela de PrintPreview.
Um cenário comum é encontrado em aplicações WindowsForms. Imagine um ponto de venda que utiliza uma aplicação WindowsForms para impressão de cupons, comandas entre outros jobs. Considere que o operador desta aplicação utiliza o teclado para executar todos os comandos e entradas de dados (o uso do mouse é minimizado). Em cenários como este, é comum que a tecla [ENTER] seja pressionada repetidas vezes para completar um processo de entrada de um novo pedido por exemplo.
Tive um cenário semelhante implementado no módulo de caixa de um restaurante, o volume de comandas entregues pelos garçons ao caixa é grande e o procedimento do caixa para inserir os pedidos no sistema é rápido e agilizado pelo uso do teclado. Ao final do processe de inserir uma nova comanda, o sistema verifica os itens inseridos e envia um job para diferentes impressoras dependendo dos itens, por exemplo, os itens “pratos quentes” são impressos na cozinha, enquanto as bebidas são enviadas para a impressora do bar para que o pedido seja atendido.
O problema é que com o tempo, o usuário do caixa, tende a digitar as entradas cada vez mais rápido. Isso faz com que a tecla [ENTER] seja pressionada repetidas vezes, algumas para avançar para o próximo campo e outras para acionar os botões de comando. Contudo, é comum que o usuário pressione esta tecla pelo menos 1 vez mais além do necessário. Se isso ocorre quando a janela de diálogo “Generating Previews” está sendo exibida, o job de impressão é cancelado.

Imagine a situação, você está na mesa, solicitou um prato, o garçon emitiu a comanda, entregou ao caixa, o caixa registrou o pedido, mas, sem perceber, pressionou a tecla [ENTER] mais uma vez cancelando o envio da impressão para a cozinha. Seu prato demora, você reclama para o garçon, ele reclama para o caixa, o caixa culpa a cozinha e no final de tudo a cupla recai sobre o sistema que devia ter previsto este cenário e utilizado uma implementação diferente.
A solução: A solução é simples, porém os exemplos de impressão na documentação do MSDN não aborda nenhum cenário semelhante. Basicamente o que você precisa é mudar o PrintController do seu objeto PrintDocument. Por padrão, o PrintController é um objeto do tipo PrintControllerWithStatusDialog, assim toda vez que o método Print() é invocado, o PrintControllerWithStatusDialog exibe a janela Generating Previews. Basta substituí-lo pelo objeto StandardPrintController que a janela não será mais exibida. A sintaxe correta para esta construção é:
pd.PrintController = new StandardPrintController();
pd.Print();
Simples assim!
Introdução
Periodicamente recebo e-mails de desenvolvedores pedindo ajuda para implementar funções do Microsoft Win32 em suas aplicações. Nos idos do Visual Basic 6.0, a procura por funções de API era ainda maior. Isso mudou porque o .NET Framework, desde sua versão 1.0, implementou diversas funções que antes só conseguíamos utilizar através da API Microsoft Win32. O Microsoft Win32 expõe diversas funções através de um conjunto de bibliotecas. As bibliotecas mais utilizadas e suas finalidades são apresentadas na Tabela 1.
| Biblioteca | Descrição |
| Gdi32.dll | Graphics Device Interface (GDI). Esta biblioteca implementa funções para dispositivos de saída, gráficos e gerenciamento de fontes. |
| Kernel32.dll | Funções de baixo nível, como gerenciamento de memória e manipulação de recursos e dispositivos do sistema. |
| User32.dll | Funções de gerenciamento do Windows como tratamento de mensagens, timers, menus, etc. |
Tabela 1: Bibliotecas do Microsoft Win32
Microsoft Win32 API e o .NET Framework
Muitos desenvolvedores perguntam: “Por que temos que codificar funções que já estão implementadas no Windows? Por que o .NET Framework não provê essas funcionalidades?”. Na verdade uma boa parte das funções do Win32, foi portada para o .NET, mas o conjunto completo dessas funções é muito grande, e muitas funções foram deixadas de fora. A Microsoft disponibilizou um documento que estabelece o mapeamento das funções do Win32 com as classes do .NET, esse documento está disponível na seguinte URL: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/win32map.asp.
Platform Invoke (P/Invoke)
Platform Invocation Services (PInvoke) permite que uma aplicação, implementada com código gerenciado (Manage Code), possa fazer chamadas para funções encapsuladas em bibliotecas (DLL ) implementadas com código não-gerenciado (Unmanage Code). Através do P/Invoke, com o uso correto de alguns atributos e parâmetros, podemos invocar qualquer função do Microsoft Win32 ou de outra biblioteca. O fragmento de código abaixo apresenta uma chamada para a função externa CascadeWindows, armazenada na biblioteca User32.dll.
[DllImport("user32.dll")]
static extern ushort CascadeWindows(IntPtr hwndParent, uint wHow, IntPtr lpRect, uint cKids, IntPtr [] lpKids);
Observe que o atributo contendo a declaração DllImport(“biblioteca.dll”) deve preceder a declaração do método ou função que deve ser obrigatoriamente definido como static extern.
Não perca tempo, Seja produtivo!
É muito importante que o desenvolvedor utilize funções externas somente quando necessário. Sugiro uma boa pesquisa nas classes do .NET Framework antes de optar pela API do Win32. Não perca seu tempo tentando implementar uma função de API quando ela já está disponível no .NET Framework.
Unmanage Code vs. Segurança
Outro ponto a ser considerado quando se planeja utilizar funções de código não-gerenciado, refere-se à segurança. Você deve certificar-se de que o usuário da sua aplicação ou o seu componente possui os privilégios necessários para executar código não-gerenciado.
Exemplos Práticos
Neste artigo, vou apresentar quatro funções do Microsoft Win32. Estas funções não foram portadas para o .NET Framework e sua aplicabilidade prática é relativamente simples. A Tabela 2 apresenta as funções que serão utilizadas no aplicativo de exemplo.
| Função | Biblioteca | Descrição |
| SetLocalTime | Kernel32.dll | Esta função permite que você altere a data e hora do Windows. |
| Beep | Kernel32.dll | Esta função emite um Beep sonoro que pode ter sua freqüência e duração definidas programaticamente. |
| MessageBeep | User32.dll | Esta função executa os sons associados aos tipos de mensagem do Windows (Exclamação, Pergunta, Erro, etc). |
| GetDiskFreeSpace | Kernel32.dll | Esta função permite obter informações específicas sobre uma unidade de disco (setores, clusters, espaço total e espaço livre). Existe uma função bastante próxima desta chamada GetDiskFreeSpaceEx que retorna apenas o espaço livre de uma unidade de disco, porém esta função já foi incorporada ao .NET Framework através do Namespace System.IO. |
Tabela 2: Funções do Win32 utilizadas no exemplo.
Referência ao Namespace
Para utilizarmos funções externas, como as funções do Microsoft Win32, precisamos estabelecer uma referência ao Namespace System.Runtime.InteropServices. A linha de código abaixo apresenta a referência necessária.
using System.Runtime.InteropServices;
Alterando Data e Hora do Sistema com a função SetLocalTime
O Quadro 1 apresenta o código necessário para implementar a função SetLocalTime. Esta função é disponibilizada através da biblioteca kernel32.dll. Observe que a declaração do método externo é acompanhada do atributo DllImport(“kernel32.dll”). Para esta função o desenvolvedor precisa criar a estrutura SystemTime. Note que esta estrutura também possui um atributo chamado StructLayout. Este atributo controla o layout de um objeto quando é passado para o ambiente não-gerenciado, o parâmetro LayoutKind é representado por uma Enumeração. Os membros desta Enumeração são apresentados na Tabela 3.
| Membro | Descrição |
| Auto | O runtime escolhe automaticamente o layout apropriado para os objetos quando são expostos. Contudo este método não deve ser utilizado para expor objetos ao ambiente não-gerenciado, isso geraria uma Exception. |
| Explicit | A posição precisa de cada membro do objeto em ambiente não-gerenciado deve ser explicitamente especificada através da propriedade FieldOffsetAttribute. |
| Sequential | Os membros do objeto são apresentados sequencialmente, na mesma ordem que serão expostos ao ambiente não-gerenciado. |
Tabela 3: A Enumeração LayoutKind.
#region SetLocalTime Function
[DllImport("kernel32.dll")]
public static extern bool SetLocalTime(ref SystemTime sysTime);
public static void SetNewDateTime(DateTime dt)
{
SystemTime sysTime = new SystemTime();
sysTime.wYear = (ushort)dt.Year;
sysTime.wMonth = (ushort)dt.Month;
sysTime.wDay = (ushort)dt.Day;
sysTime.wHour = (ushort)dt.Hour;
sysTime.wMinute = (ushort)dt.Minute;
sysTime.wSecond = (ushort)dt.Second;
sysTime.wMiliseconds = (ushort)dt.Millisecond;
SetLocalTime(ref sysTime);
}
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMiliseconds;
}
private void cmdSetLocalTime_Click(object sender, EventArgs e)
{
DateTime myDate = new DateTime(dtpDate.Value.Year,
dtpDate.Value.Month,
dtpDate.Value.Day,
dtpTime.Value.Hour,
dtpTime.Value.Minute,
dtpTime.Value.Second);
SetNewDateTime(myDate);
}
private void frmSetLocalTime_Load(object sender, EventArgs e)
{
dtpDate.Value = System.DateTime.Today;
dtpTime.Value = System.DateTime.Now;
}
private void tmrLocal_Tick(object sender, EventArgs e)
{
lblDateTime.Text = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString();
}
#endregion
Quadro 1: Implementando a função SetLocalTime
Emitindo Beep Sonoros no C#
Esta função é bastante simples e útil em algumas situações. Com a função Beep exposta pela biblioteca kernel32.dll, você pode emitir um aviso sonoro, controlando sua freqüência e duração. Semelhante à função Beep encontrada no Visual Basic. O Quadro 2 apresenta o código necessário para sua implementação.
#region Beep Function
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
private void cmdBeep_Click(object sender, EventArgs e)
{
Beep((int)upFrequency.Value, (int)upDuration.Value);
}
public enum BeepType
{
SimpleBeep = -1,
IconAsterisk = 0x00000040,
IconExclamation = 0x00000030,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
Ok = 0x00000000,
}
#endregion
Quadro 2: A função Beep
Sons do Windows com a função MessageBeep
Semelhante ao exemplo anterior, a função MessageBeep, exposta pela bilbioteca user32.dll, emite avisos sonoros de acordo com o tipo de mensagem que você quer exibir. Os mesmos tipos que são definidos quando você codifica uma Caixa de Mensagem. Você pode executar um aviso sonoro simples, um aviso de exclamação ou de interrogação, e assim por diante. Os sons acionados por esta função são os mesmos configurados pelo usuário nas propriedades de som do Painel de Controle. Desta forma, se não houver som configurado para interrogação, por exemplo, a função não será executada. Observe o código desta implementação no Quadro 3.
#region MessageBeep Function
[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);
private void cmdMessageBeep_Click(object sender, EventArgs e)
{
switch (cboBeepType.Text)
{
case "Ok":
MessageBeep(BeepType.Ok);
break;
case "SimpleBeep":
MessageBeep(BeepType.SimpleBeep);
break;
case "IconAsterisk":
MessageBeep(BeepType.IconAsterisk);
break;
case "IconExclamation":
MessageBeep(BeepType.IconExclamation);
break;
case "IconQuestion":
MessageBeep(BeepType.IconQuestion);
break;
case "IconHand":
MessageBeep(BeepType.IconHand);
break;
default:
MessageBeep(BeepType.Ok);
break;
}
}
#endregion
Quadro 3: A função MessageBeep
Verificando o Espaço em Disco com a função GetDiskFreeSpace
Esta função, exposta pela biblioteca kernel32.dll, permite ao desenvolvedor verificar o espaço total e espaço livre de uma unidade de disco. As informações fornecidas por esta função incluem: Número de Setores por Cluster, Número de Bytes por Setor, Número de Clusters Livres e o Número Total de Clusters. Agora, se você quer saber apenas o espaço livre em uma unidade de disco, você pode utilizar a classe System.Io.DriveInfo do .NET Framework 2.0 para obter essa informação. Essa classe provê a mesma funcionalidade exposta pela função semelhante chamada GetDiskFreeSpaceEx do Win32. O Quadro 4 apresenta o código para implementação desta função.
#region GetDiskFreeSpace Function
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetDiskFreeSpace(string lpRootPathName,
out uint lpSectorsPerCluster,
out uint lpBytesPerSector,
out uint lpNumberOfFreeClusters,
out uint lpTotalNumberOfClusters);
private void cmdGetDiskFreeSpace_Click(object sender, EventArgs e)
{
uint SectorsPerCluster;
uint BytesPerSector;
uint NumberOfFreeClusters;
uint TotalNumberOfClusters;
GetDiskFreeSpace("C:\\", out SectorsPerCluster, out BytesPerSector,
out NumberOfFreeClusters, out TotalNumberOfClusters);
txtSectorsPerCluster.Text = SectorsPerCluster.ToString();
txtBytesPerSector.Text = BytesPerSector.ToString();
txtNumberOfFreeClusters.Text = NumberOfFreeClusters.ToString();
txtTotalNumberOfClusters.Text = TotalNumberOfClusters.ToString();
long Bytes = (long)NumberOfFreeClusters * SectorsPerCluster * BytesPerSector;
txtFreeSpaceInBytes.Text = Bytes.ToString();
decimal KiloBytes = (decimal)Bytes / 1024;
txtFreeSpaceInKilobytes.Text = KiloBytes.ToString();
decimal MegaBytes = (decimal)KiloBytes / 1024;
txtFreeSpaceInMegabytes.Text = MegaBytes.ToString();
decimal GigaBytes = (decimal)MegaBytes / 1024;
txtFreeSpaceInGigabytes.Text = GigaBytes.ToString();
}
#endregion
Quadro 4: A função GetDiskFreeSpace
Conclusão
Neste artigo você conheceu um pouco mais sobre o Microsoft Win32 e aprendeu como utilizar suas funções através do Microsoft Visual C# e o P/Invoke. Visite os links de referência. Consulte as classes do .NET Framework antes de recorrer ao uso de funções externas, não tente re-inventar a roda! Boa programação!
Introdução
Recentemente trabalhando num projeto ASP.NET, deparei-me com um cenário bastante específico que levou-me à buscar uma solução dinâmica que gostaria de compartilhar com vocês neste artigo. Você aprenderá como utilizar os Namespaces System.Drawing e System.Drawing.Imaging para converter imagens no formato TIFF Multiframes para JPEG, e ainda como inserir marcas d’água na imagem final.
O cenário
Nossa solução Web deveria permitir a consulta de documentos digitalizados no formato TIFF e permitir que o usuário navegasse através das frames (páginas) contidas no arquivo de imagem. Todos sabemos que o formato TIFF não é suportado naturalmente pelos browsers. Encontrei diversos componentes para suportar a exibição de imagens TIFF em páginas web, alguns pagos outros gratuitos, mas nenhum deles oferecia a flexibilidade necessária para nossa solução.
Outro problema que tínhamos referia-se à proteção das imagens dos documentos, por tratarem-se de documentos legais, a instituição que nos contratou fez uma exigência de que todas as imagens deveriam ser modificadas para incluir uma marca d’água que indicasse claramente que aquela imagem não poderia ser utilizada como um documento oficial.
Tínhamos ainda outros fatores complicadores. O número de arquivos de imagens que deveríamos converter e publicar estava próximo de 130.000 (cerca de 15GB) e crescendo a cada dia. Além disso, as imagens já existentes poderiam ser modificadas para receber uma nova página ou alterar uma página do documento original, desta forma nossa solução deveria prover um mecanismo para processar as imagens alteradas. Mais um detalhe importante, o sistema de gestão responsável por gerar as imagens digitalizadas em TIFF, por razões de performance, distribuia os arquivos gerados em pastas contendo no máximo 1000 arquivos em cada pasta, isto representava mais um fator a ser considerado, a solução deveria percorrer todas as pastas criadas pelo sistema de gestão, localizar as novas imagens e as imagens que sofreram alterações para processá-las e disponibilizá-las para publicação.
Analisando o problema
Após uma análise inicial, descartamos duas hipóteses:
a) Converter as imagens durante o processo de pesquisa, ou seja, o usuário entra na página de pesquisa informa o critério de busca e a aplicação localiza a imagem em seu estado original, realiza a conversão para JPEG, adiciona a marca d’água e exibe a imagem resultante para o usuário. Esta hipótese foi descartada porque a performance para consulta de cada imagem apresentou-se excessivamente prejudicada.
b) Outra hipótese era convertermos todas as imagens e inserir as marcas d’água diariamente num processo executado em horários extraordinários, esta hipótese não exigiria a identificação de imagens modificadas, uma vez que o processo seria aplicado para todas as imagens existentes mantendo-as atualizadas para publicação. Esta hipótese também foi descartada porque o processo de conversão e inserção das marcas d’água nas 130.000 imagens exigia várias horas para processamento, o que era enviável além de representar um fator de risco, pois processos longos executados sem interação do usuário estão sujeitos à diversos fatores que podem interromper o processamento e tornar o conjunto de imagens inconsistente.
A solução proposta
Após descartarmos as hipóteses anteriores, nossa solução foi implementada considerando as seguintes etapas:
a) Um processamento completo, ou seja, todas as 130.000 imagens, foi realizado no momento de implantação da solução. As imagens resultantes, já no formato JPEG e com a marca d’água foram disponibilizadas no servidor Web para consultas.
b) Criamos um componente para rastrear o pasta principal onde o sistema de gestão da instituição gera as imagens no formato original (TIFF). Esse processo de rastreamento é executado diariamente em horário agendado pelo usuário. O processo consiste em percorrer todas as subpastas em busca de novos arquivos de imagens ou arquivos modificados após a última publicação. Esta busca foi implementada utilizando o Namespace System.IO com o uso das classes FileInfo e DirectoryInfo. A configuração deste componente foi facilitada com uma interface criada com Windows Forms. Esta interface permite ao usuário definir o horário de execução, periodicidade (diária, semanal, mensal), a pasta raiz que deverá ser rastreada, além de permitir outras configurações como a definição do texto que é inserido como marca d’água e a taxa de compactação do arquivo JPEG gerado (necessário para que o arquivo tenha tamanho adequado para Web).
c) Os testes de performance desta solução apresentaram resultados positivos. O processo de rastreamento, conversão das imagens e inserção das marcas d’água (em arquivos novos e modificados) foi concluído pelo componente em exatos 9,7 minutos. Para realizarmos os testes, reproduzimos o cenário da instituição, criando 130 pastas, cada uma contendo 1.000 arquivos TIFF em diversos tamanhos (diferentes números de frames). Depois acrescentamos 40 novos arquivos (com data de criação diferente dos demais), esta quantidade (40) foi determinada pela média de novos arquivos gerados diariamente na instituição. Também modificamos outros 8 arquivos escolhidos aleatoriamente dentro de diferentes pastas, esta quantidade de arquivos modificados (8) também foi determinada pela média apurada diária apurada pela instituição.
Nota: Uma observação importante refere-se ao código apresentado neste exemplo. Ele inclui algumas linhas utilizadas para manipular dois controles ProgressBar, um para indicar as pastas e subpastas processadas e outro para indicar os arquivos processados. Contudo, o código apresentado neste exemplo é apenas um fragmento da aplicação principal. A implementação dos controles ProgressBar feita dentro da mesma thread prejudica sensivelmente a performance do processo. No nosso caso, o controle ProgressBar foi utilizado apenas durante os testes realizados na fase de desenvolvimento. O componente responsável pelo processamento, apesar de prover interface para configuração, ele próprio é executado sem interação do usuário através de um serviço do Windows e dispensa o uso de controles para notificar o usuário do sobre o processamento.
Os quadros a seguir apresentam o código necessário para esta implementação. Observe os comentários adicionados ao código para falicitar a compreensão dos procedimentos.
//Namespaces necessários para esta implementação
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace WD5ImageTools
{
public class ImageTools
{
///
/// Este procedimento varre a pasta principal e
/// subpastas contidas no diretório root informado
/// em busca de arquivos TIFF para convertê-los
/// em JPEG. Para cada frame contida no arquivo TIFF
/// será gerado um arquivo JPEG.
///
///
/// Este parâmetro indica o diretório onde o
/// processo será iniciado.
///
private void ConvertImages(string root_path)
{
// variável utilizada para determinar o tempo de processamento
DateTime start = DateTime.Now;
// A classe DirectoryInfo permite o rastreamento de todas
// as subpastas contidas no diretório raiz.
DirectoryInfo di = new System.IO.DirectoryInfo(root_path);
// Notifica o usuário que o processamento está em andamento
Cursor.Current = Cursors.WaitCursor;
// Cria um array com as subpastas
DirectoryInfo[] dirs = di.GetDirectories();
// Contadores utilizados para registrar os arquivos processados
int iFolderCount = 0;
int iFilesRead = 0;
int iFilesConverted = 0;
// inicia o loop através das subpastas
foreach (DirectoryInfo diNext in dirs)
{
// define as propriedades para o controle ProgressBar
// este controle foi utilizado durante a fase de testes
// contudo a performance é sensivelmente prejudicada
// quando a implementação é feita de forma simples como
// neste exemplo. O correto neste caso seria a criação de
// threads separadas uma para o processo das imagens e outra
// para a atualização dos controles de feedback para o usuário
pbFolders.Minimum = 0;
pbFolders.Maximum = dirs.Length;
lblFolderCount.Text = dirs.Length.ToString();
// incrementa o contador de pastas
iFolderCount++;
// atualiza o controle ProgressBar
pbFolders.Value = iFolderCount;
// inicia o loop através de todos os arquivos contidos na
// pasta selecionada. A busca é filtrada pela extensão TIF
foreach (System.IO.FileInfo fi in diNext.GetFiles(”*.tif”))
{
// incrementa o contador de arquivos lidos
iFilesRead++;
// define as propriedades do controle ProgressBar
pbFiles.Minimum = 0;
pbFiles.Maximum = diNext.GetFiles().Length;
lblFileCount.Text = diNext.GetFiles().Length.ToString();
// atualiza o controle ProgressBar
pbFiles.Value = iFilesRead;
// teste lógico para verificar se o arquivo encontrado foi
// alterado/modificado após o último processamento.
// A propriedade LastWriteTime foi utilizada para realizar
// esta comparação. Neste exemplo, somente os arquivos
// modificados serão processados.
if (fi.LastWriteTime.ToShortDateString() ==
DateTime.Today.ToShortDateString())
{
// incrementa o contador de arquivos convertidos
iFilesConverted++;
// cria um objeto Bitmap a partir do arquivo TIFF
Bitmap tifImage = new Bitmap(fi.FullName);
// obtém o número de frames (páginas) contidas no TIFF
int count = tifImage.GetFrameCount(FrameDimension.Page);
// inicia o loop através das frames
for (int i = 0; i < count; i++)
{
// armazena o caminho e nome completo do novo
// arquivo de imagem no formato JPEG que será criado
// para cada frame encontrada. O nome do arquivo
// será constituido do nome original do arquivo TIFF
// + um número sequencial.
string sFile = fi.FullName.Substring(0,
fi.FullName.Length - 4) + "_" + i + ".jpg";
// seleciona uma frame por vez utilizando a variável
// i como indexador
tifImage.SelectActiveFrame(FrameDimension.Page, i);
// salva a imagem no formato JPEG, utilizando a
// propriedade ImageFormat
tifImage.Save(sFile, ImageFormat.Jpeg);
// invoca o procedimento InsertWatermark para
// adicionar a marca d'água passa como parâmetro o
// texto da marca d'água e o nome do novo arquivo
// JPEG que será compactado neste procedimento
InsertWatermark("DOCUMENTO NÃO É VÁLIDO", sFile);
}
// libera os recursos utilizados pelo objeto Bitmap
tifImage.Dispose();
tifImage = null;
}
}
}
// restaura o estado original do cursor
Cursor.Current = Cursors.Default;
// define o término do processamento
DateTime finish = DateTime.Now;
// exibe mensagem informando as estatísticas do processamento
MessageBox.Show("Conversão concluída! \nInício: [" +
start.ToString() + "] \nTérmino: [" + finish.ToString() + "]
\nPastas: [" + iFolderCount.ToString() + "] \nArquivos Processados:
[" + iFilesRead.ToString() + "] \nArquivos Convertidos: [" +
iFilesConverted.ToString() + "]" , "WD5 Image Converter",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
///
/// Procedimento de apoio utilizado para construir um
/// array com os encoders do arquivo de imagem
///
///
tipo de imagem “image/jpeg”
///
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
///
/// Procedimento utilizado para adicionar a marca d’água ao arquivo
/// JPEG. Neste caso a marca d’água é constituida de um texto informado
/// através do parâmetro sLine.
///
///
Texto para marca d’água
///
nome do arquivo que será processado
private void InsertWatermark(string sLine, string ImageFile)
{
// variáveis que definirão o tamanho da imagem
int width = 0;
int height = 0;
// define os brushes e fonts utilizados construir a marca d’água
SolidBrush letterBrush = new SolidBrush(Color.FromArgb(100, 255,
255, 255));
SolidBrush shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0,
0));
Font fontTitle = new Font(”Times New Roman”, 72, FontStyle.Bold);
// cria um objeto Image (System.Drawing.Imaging)
Image img = Image.FromFile(ImageFile);
// define o tamanho da imagem
width = img.Width;
height = img.Height;
// cria um novo objeto Bitmap que será utilizado para
// gerar a nova imagem com a marca d’água
Bitmap baseMap = new Bitmap(width,height);
// cria um objeto Graphics a partir do novo Bitmap
Graphics myGraphic = Graphics.FromImage(baseMap);
// o objeto StringFormat é utilizado para definir o posicionamento
// vertical da marca d’água
StringFormat stringFormat = new StringFormat();
stringFormat.FormatFlags = StringFormatFlags.DirectionVertical;
// cria a nova imagem com base nos parâmetros informados
myGraphic.DrawImage(img, 0, 0, width, height);
myGraphic.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// propriedade RotateTransform usada para rotacionar a marca d’água
// -45º para que ela fique posicionada na diagonal da imagem
myGraphic.RotateTransform(-45);
// cria a marca d’água com sombreamento usando o parâmetro sLine
myGraphic.DrawString(sLine, fontTitle, shadowBrush, -850, 1000);
myGraphic.DrawString(sLine, fontTitle, letterBrush, -850, 1000);
// libera os recursos utilizados pelo objeto Image
img.Dispose();
img = null;
// cria um objeto encoder que será utilizado para compactar
// o arquivo JPEG. o objeto EncoderParameter recebe dois
// parâmetros (o objeto myEncoder e o percentual de compactação
// que pode variar entre 0 e 100
Encoder myEncoder = Encoder.Quality;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
myEncoderParameters = new EncoderParameters(1);
// compacta o arquivo JPEG com qualidade de 5%. Adequado neste caso
// pois trata-se de documento de texto digitalizado.
myEncoderParameter = new EncoderParameter(myEncoder, 5L);
myEncoderParameters.Param[0] = myEncoderParameter;
// obtém a coleção de encoders do arquivo de imagem
ImageCodecInfo myImageCodecInfo;
myImageCodecInfo = GetEncoderInfo(”image/jpeg”);
// salva o arquivo sobrescrevendo o original com a nova imagem
// acrescida da marca d’água e compactada
baseMap.Save(ImageFile, myImageCodecInfo, myEncoderParameters);
// libera os recursos utilizados pelo objeto Bitmap
baseMap.Dispose();
baseMap = null;
}
}
}
Conclusão
Você pode acompanhar todos os passos necessários para utilização dos Namespaces System.Drawing e System.Drawing.Imaging para converter imagens TIFF Multiframes em arquivos JPEG. Como compactar as imagens JPEG utilizando os objetos Encoders. Também aprendeu como acrescentar marcas d’água aos seus arquivos de imagens. Não pare por aí, existem inúmeras funcionalidades associadas aos mesmos Namespaces. A conversão de imagens pode ser realizada para diversos formatos utilizando a propriedade ImageFormat. Consulte os links do artigo e aprenda um pouco mais sobre a manipulação de imagens com o Visual C# 2005.