Mostrando postagens com marcador manipular imagens em .net c#. Mostrar todas as postagens
Mostrando postagens com marcador manipular imagens em .net c#. Mostrar todas as postagens

quarta-feira, setembro 04, 2013

implementar os métodos GetHashCode(), Equals(Object o), ToString() e CompareTo(Object o) em c#

Um desabafo sobre implementar os métodos GetHashCode(), Equals(Object other), String ToString() e CompareTo(Object other) em c#.

Não sei por que causa tanto espanto aos programadores do universo .Net (c# no meu caso) a sobrecarga de métodos de System.Object, como GetHashCode(), Equals(Object other), String ToString() e CompareTo(Object other) e o básico conhecimento de serialização de objetos.
Quando eu falo sobre esses métodos ou exibo uma classe que os sobrescrevam sempre há uma pergunta de para que eles servem ou se estão no lugar certo(?).
Anos atrás, quando eu era um padawan hehehe, fiz uma classe em Java que deveria retornar uma lista com os elementos ordenados dentro dela. E eu não entendia porque a lista não exibia ordenadamente os dados. Então um colega de trabalho me falou "se você não implementar a tua regra de comparação a lista não irá saber como ordenar os dados". Lá fui eu implementar a minha regra de comparação (método compareTo). E voilà!
Em c# se estivermos tentando chamar Short() em uma coleção em que a classe de conteúdo dessa coleção não realiza a interface IComparable é lançada uma exceção em tempo de execução do tipo System.InvalidOperationException. Em Java não acontece isso, simplesmente é chamado o método compareTo da classe Object e a ordenação não vai acontecer como você previa.
Então para ordenar o conteúdo de uma coleção em c# temos que realizar a interface IComparable na classe de conteúdo da nossa coleção e sobrescrever o método CompareTo. Imaginemos uma coleção de objetos do tipo país:

List<Country> countries = new List<Country>();
 
countries.Add(new Country("Switzerland"));
countries.Add(new Country("Italy"));
countries.Add(new Country("France"));
countries.Add(new Country("Brazil"));

Para podermos chamar o método Sort() de List a classe Country deve implementar o método CompareTo. Exemplo:

/// <summary>
/// Compares two objects for ordering.
/// </summary>
/// <param name="other">The object to be compared.</param>
/// <returns>Compares most representatives object's fields
/// lexicographically, this method has to return one of the
/// following alternatives:
///    0 if this instance is equal to the object argument;
///   -1 if this instance is lexicographically less than the object
///      argument;
///    1 if this instance is lexicographically greater than the object
///      argument.
/// </returns>
public int CompareTo(Object other)
{
    if (other == null)
    {
        return 1;
    }
   
    try
    {
        Country cOther = (Country)other;
        return this.Name.CompareTo(cOther.Name);
    }
    catch (Exception e)
    {
        throw new ArgumentException("Object is not a Country", e);
    }
}

Agora sim se fizermos isso:

List<Country> countries = new List<Country>();
 
countries.Add(new Country("Switzerland"));
countries.Add(new Country("Italy"));
countries.Add(new Country("France"));
countries.Add(new Country("Brazil"));
 
countries.Sort()
 
foreach (Country c in countries) 
    Console.WriteLine(c.Name);

Iremos ter o resultado:

Brazil
France
Italy
Switzerland

Só vou comentar um detalhe do método ToString(). Já conversei com colegas que achavam que o mesmo não serve para nada, pois não é este o método que irá exibir em tela os dados do objeto, etc. Claro que não. Mas este método nos poderá dar uma resposta rápida de quem a instância do objeto se trata.
Eu, por exemplo, implemento o ToString com a seguinte regra:

namespace.Classe [<campos mais importantes da classe>]

Para a minha classe Country:

/// <summary>
/// Returns a string representation of this instance.
/// The format of the string is:
/// namespace + class name + [ + the most representative class fields + ].
/// </summary>
/// <returns>Returns a string representation of this instance.</returns>
public override String ToString()
{
    StringBuilder sb = new StringBuilder();
    
    sb.Append("com.blogspot.jeanjmichel.examples.model.Country [");
    sb.Append(this.Name);
    sb.Append("]");
    
    return sb.ToString();
}

Os campos mais importantes são os mesmos que eu utilizo para Equals, HashCode, etc.
Uma das aplicações mais úteis desta implementação está na hora de debugar. Olhe a coluna Value:

Debug de objetos que implementam ToString()

Ou até mesmo na hora de logar alguma exceção eu poderia logar o estado do objeto fazendo:

log.Error("Object ToString(): " + c.ToString());

O método Equals(Object other) é a regra de comparação não para ordenação, mas para qualquer finalidade. Eu costumo não incluir como campos importantes Id e campos que podem ser nulos. Então a comparação entre duas instâncias do objeto Country só levará em conta o campo Name:

/// <summary>
/// Compares this instance to the specified object.
/// The result is true if and only if the argument is not null, is of
/// the same type of this instance and the rest of the most
/// representative fields of the object has the same value.
/// </summary>
/// <param name="other">The object to compare this country against.</param>
/// <returns>true if the given object represents an equivalent object to
/// this instance, false otherwise</returns>
public bool Equals(Country other)
{
    if (other == null)
    {
        return false;
    }
 
    if (!this.Name.Equals(other.Name))
    {
        return false;
    }
    
    return true;
}

Para o método HashCode() é a mesma coisa:

/// <summary>
/// This method generates a hash code of this instance. To calculate this
/// code is used object's most representative fields.
/// </summary>
/// <returns>Country's hashcode.</returns>
public override int GetHashCode()
{
    int hashCode;
    int prime = 31;
    
    hashCode = String.IsNullOrEmpty(this.Name) ? prime : this.Name.GetHashCode() * prime;
    
    return hashCode;
}

Na trajetória básica de um padawan para virar Jedi em Java, o Java Tutorial passa pela lição falando da classe Object e seus métodos, passando pelos que citei aqui. Me estranha muito a galera que trabalha com c# (ou .Net como um todo) torcer o nariz para esses métodos. Enfim... na minha migração de plataforma procurei por esses métodos em c# assim que comecei a modelar minhas primeiras classes ;)

terça-feira, setembro 03, 2013

Manipulando imagens em .Net c#

Uma das coisas que me decepciona muito no .Net é a falta do senso de comunidade.
Calma aí quem for dizer que há comunidade no universo .Net. Se você pensou isso todo empolgado, entre para o mundo Java e verás a gritante diferença!

Mas mesmo chorando a falta de boas listas e grupos de usuários como temos em Java, ainda há outra coisa que me decepciona muito que é a falta de bons componentes gratuitos para fazer determinadas coisas.

Se em Java você despacha para Pai Google “como fazer tal coisa em Java” e encontra 4 milhões de posts/artigos/papers/tutoriais e mais uns 1,5K componentes gratuitos para fazer tal coisa, em .Net...
Quando tu achas alguma coisa é um componente pago ou alguns poucos posts no stackoverflow.com.

Então eu estava querendo fazer algumas coisas com imagens e me debrucei no balcão de informações Google.com a procura de respostas para:

Recuperar um array de bytes a partir de um arquivo.
Gerar uma String base 64 deste array de bytes.
Redimensionar uma imagem via código.

Primeiro de tudo deixe-me explicar porque disso tudo.

Eu estou gravando imagens em um banco de dados direto do código c# da minha aplicação. Mas não quero gravar o binário do arquivo da imagem no banco e depois ter que recuperar o binário, jogar para algum lado interpretar isso para eu ter uma tag <img /> com o source apontando para um programa que vai ler o binário e me devolver um response do tipo image/{png, jpg, bmp}.

Então eu pego uma imagem, leio os bytes dela e transformo esses bytes em uma String de base 64 que eu posso utilizar diretamente na tag <img />:

<img alt="Embedded Image" src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA8Q1... />

Então criei uma classe para a famigerada namespace útil, chamada ImageUtil:

using System; 
using System.Drawing; 
using System.Drawing.Drawing2D; 
using System.Drawing.Imaging; 
using System.IO; 
 
namespace com.blogspot.jeanjmichel.util 
{ 
    /// <summary> 
    /// This class summarizes all possible operations applicable to an image. 
    /// </summary> 
    public class ImageUtil 
    { 
        /// <summary> 
        /// This method convert an image's bytes array to a based 64 String. 
        /// </summary> 
        /// <param name="bytesArray">Image's bytes in an array.</param> 
        /// <returns>Based 64 String.</returns> 
        public static String ConvertArrayOfBytesToStringBase64(byte[] bytesArray) 
        { 
            return System.Convert.ToBase64String(bytesArray, 0, bytesArray.Length); 
        } 
 
        /// <summary> 
        /// This method loads an image to an array of bytes. 
        /// </summary> 
        /// <param name="fileFullPath">Full path (path + file name) of the image.</param> 
        /// <returns>Image's bytes in an array.</returns> 
        public static byte[] ConvertImageToArrayOfBytes(String fileFullPath) 
        { 
            FileStream fS = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); 
             
            byte[] imgBytes = new byte[fS.Length]; 
            fS.Read(imgBytes, 0, (int)fS.Length); 
             
            fS.Close(); 
             
            return imgBytes; 
        } 
 
        /// <summary> 
        /// This method returns the extension of the image's file. 
        /// </summary> 
        /// <param name="fileFullPath">Full path (path + file name) of the image.</param> 
        /// <returns>The extension of the image's file.</returns> 
        public static String GetImageExtension(String fileFullPath) 
        { 
            return Path.GetExtension(fileFullPath); 
        } 
 
        /// <summary> 
        /// This method generates an HTML <img /> tag with the embedded image data. 
        /// </summary> 
        /// <param name="fileExtension">The extension of the image's file.</param> 
        /// <param name="imgInBase64">The image's bytes in a based 64 String.</param> 
        /// <returns>An HTML <img /> tag with embedded image data.</returns> 
        public static String GenerateHTMLImgTag(String fileExtension, String imgInBase64) 
        { 
            return "<img alt=\"Embedded ImageUtil\" src=\"data:image/" + fileExtension + ";base64," + imgInBase64 + "\" />"; 
        } 
 
        /// <summary> 
        /// This method resizes the image to a specified size. 
        /// </summary> 
        /// <param name="inFileFullPath">Full path (path + file name) of the original image.</param> 
        /// <param name="outFileFullPath">Full path (path + file name) of the resized image.</param> 
        /// <param name="newImagSize">Wanted size.</param> 
        /// <returns></returns> 
        public static Boolean ResizeImageTo(String inFileFullPath, String outFileFullPath, Size newImagSize) 
        { 
            System.Drawing.Image originalImage = new Bitmap(inFileFullPath); 
 
            int originalWidth = originalImage.Width; 
            int originalHeight = originalImage.Height; 
 
            if (originalWidth > newImagSize.Width || originalHeight > newImagSize.Height) 
            { 
                float nPercent = 0; 
                float nPercentW = 0; 
                float nPercentH = 0; 
                 
                nPercentW = ((float)newImagSize.Width / (float)originalWidth); 
                nPercentH = ((float)newImagSize.Height / (float)originalHeight); 
                 
                if (nPercentH < nPercentW) 
                    nPercent = nPercentH; 
                else 
                    nPercent = nPercentW; 
                 
                int destWidth = (int)(originalWidth * nPercent); 
                int destHeight = (int)(originalHeight * nPercent); 
                 
                Bitmap b = new Bitmap(destWidth, destHeight); 
                Graphics g = Graphics.FromImage(b); 
                g.InterpolationMode = InterpolationMode.HighQualityBicubic; 
                 
                g.DrawImage(originalImage, 0, 0, destWidth, destHeight); 
                g.Dispose(); 
                b.Save(outFileFullPath, ImageFormat.Jpeg); 
                 
                b.Dispose(); 
 
                return true; 
            } 
             
            return false; 
        } 
    } 
} 

Essa classe tem métodos para retornar o array de bytes da imagem, a String base 64, gerar a tag HTML utilizando está String base 64 e ainda posso redimensiona a imagem.

Rodando um teste com a classe, onde eu tenho uma imagem em resolução 720x540 e irei:

Gerar um arquivo com a tag HTML embutindo os dados da imagem no source;
Gerar uma cópia da imagem em resolução de até 335x180 para utilizar neste post;
E gerar um arquivo com a tag HTML embutindo os dados da imagem redimensionada no source.

Eis a classe teste:

using System; 
using System.IO; 
using com.blogspot.jeanjmichel.util; 
using System.Drawing; 
 
namespace com.blogspot.jeanjmichel 
{ 
    public class Program 
    { 
        static void Main(string[] args) 
        { 
            String imagesDirectoryPath = @"C:\\WorkingWithImages\images\"; 
 
            String originalImageName = "Anita.jpg"; 
            String originalImageFullPath = imagesDirectoryPath + originalImageName; 
            String originalImageExtension = ImageUtil.GetImageExtension(imagesDirectoryPath + originalImageName).Replace(".", "").ToLower(); 
            String originalImageOutTextFile = imagesDirectoryPath + originalImageName.Replace("." + originalImageExtension, ".txt"); 
             
            String resizedImageName = originalImageName.Replace("." + originalImageExtension, " - resized.jpg"); 
            String resizedImageFullPath = imagesDirectoryPath + resizedImageName; 
            String resizedImageOutTextFile = imagesDirectoryPath + resizedImageName.Replace(".jpg", ".txt"); 
             
            Console.WriteLine("Generating HTML <img /> tag with embedded image data..."); 
             
            byte[] originalImageBytes = ImageUtil.ConvertImageToArrayOfBytes(originalImageFullPath); 
            String originalImageInStringBase64 = ImageUtil.ConvertArrayOfBytesToStringBase64(originalImageBytes); 
            StreamWriter txtFile = new StreamWriter(originalImageOutTextFile); 
            txtFile.Write(ImageUtil.GenerateHTMLImgTag(originalImageExtension, originalImageInStringBase64)); 
            txtFile.Flush(); 
            txtFile.Dispose(); 
             
            Console.WriteLine("Done!"); 
             
            Console.WriteLine("Resizing the image to 335x180 resolution..."); 
             
            if (ImageUtil.ResizeImageTo(originalImageFullPath, resizedImageFullPath, new Size(640, 480))) 
            { 
                Console.WriteLine("Done!"); 
                Console.WriteLine("Generating HTML <img /> tag with embedded resized image data ..."); 
                 
                byte[] resizedImageBytes = ImageUtil.ConvertImageToArrayOfBytes(resizedImageFullPath); 
                String resizedImageInStringBase64 = ImageUtil.ConvertArrayOfBytesToStringBase64(resizedImageBytes); 
                StreamWriter txtFile2 = new StreamWriter(resizedImageOutTextFile); 
                txtFile2.Write(ImageUtil.GenerateHTMLImgTag("jpg", resizedImageInStringBase64)); 
                txtFile2.Flush(); 
                txtFile2.Dispose(); 
 
                Console.WriteLine("Done!"); 
            } 
            else 
            { 
                Console.WriteLine("The image is smaller than the new resolution!"); 
            } 
             
            Console.ReadKey(); 
        } 
    } 
} 

E a saída no console:

C:\WorkingWithImages\bin\Debug>WorkingWithImages.exe
Generating HTML <img /> tag with embedded image data...
Done!
Resizing the image to 335x180 resolution...
Done!
Generating HTML <img /> tag with embedded resized image data ...
Done!


Pronto, a imagem foi redimensionada e salva em disco, além de ter sido gerados dois arquivos txt com as tags HTML <img /> no mesmo diretório de imagens.

Original:

Embedded ImageUtil

Redimensionada:

Embedded ImageUtil

Está é a Anita, minha labradora ;)

Bom proveito para quem mais for útil ou possa interessar manipular imagens em c#. Lembrando que o código está no Git Hub também.