Comunitat d'usuaris .NET del pais dels pirineus

Hace tiempo que no posteo regularmente, y sinceramente (aunque suene a broma) creo que el porqué no lo hago se merece un post aparte. Sin embargo en esta ocasión quiero dejar un post cortito, sólo un apunte para mi mismo y para todos aquellos que estéis ‘trasteando’ con los helpers de WebMatrix desde MVC3.

Esta librería de helpers incorpora características muy atractivas para interactual con redes sociales (Twitter, Facebook), analizar estadísticas con Google o Yahoo, realizar búsquedas con Bing y unas cuantas cosas más. Tenéis un artículo estupendo del colega Javier Torrecilla, que profundiza más y al cual os recomiendo dar un buen vistazo.

Mi post se va a centrar en un pequeño error que se produce (al menos a fecha de hoy = 20/04/2011) al descargar e instalar el paquete NuGet. Creo que el problema es que o bien no resuelve bien las dependéncias o bien es incapaz de instalarlas, y eso provoca que una vez instalado, al ejecutar nuestra aplicación se produzca este feo error:

The type or namespace name ‘SimpleMembershipProvider’ could not be found

 

mvcfbhelpererror

El error se produce dentro del fichero AppCode/Facebook.cshtml y es muy claro: No encuentra el tipo SimpleMembershipProvider, porque forma parte de la librería WebMatrix.WebData y ésta no está referenciada en nuestro proyecto.

Para solucionarlo basta con agregar no una, sino 2 referencias. Concretamente a las librerías WebMatrix.Data.dll and WebMatrix.WebData.dll, las cuales por el motivo que sea no se han agregado correctamente a nuestro proyecto. Tip: Tal y como se dice en el artículo original, lo más recomendable es establecer el atributo ‘Copy Local’ a True para no tener problemas con la distribución.

Thx al colega jtorrecilla por la ayuda ;-)

Estamos de enhorabuena! :-)

El próximo viernes 15 de Abril a las 18h00, Javier Torrecilla mostrará sus dotes culinarias ‘cocinando’ una sesión de programación sobre WinForms. Veremos aplicaciones Winforms controles personalizados, metodos extensores y plantillas entre otras cosas.

La sesión durará un par de horas y como es habitual, al terminar iremos a tomar una cerveza y hablar de nuestras cosas, así de paso arreglaremos alguno de los problemas del mundo ;-)

Importante: Si hay suerte y llegan los paquetes a tiempo sortearemos agunas cosas, entre ellas una suscripción MSDN ¡valorada en más de 15.000 euros! :-)

El evento es gratuito (como todos los que hacemos) y se realizará en la sala del segundo piso del edificio de ‘La Llacuna’. Al lado de la ‘Plaça de les arcades’.

La Llacuna Centre Cultural
Carrer Mossèn Cinto Verdaguer 4 (*)

(*) Al costat de la “plaça de les arcades”

Ubicación ‘La Llacuna Centre Cultural’ en Google Maps

MAP Logo

Acerca de Javier Torrecilla:

Javier es analista funcional at Avalon Tecnologias de la Informacion, y destacado miembro de la comunidad online de Microsoft. Es moderador en los foros MSDN y recientemente ha sido nombrado Microsoft Active Professional.

Visita su blog:

http://geeks.ms/blogs/jtorrecilla/default.aspx

Perfil LinkedIn:

http://www.linkedin.com/in/jtorrecilla

Saludos y nos vemos en el evento!

Hola de nou,
Avui un post rapidet, volía haver-ho publicat ja fa uns dies… però porto unes setmanes una mica estressants :-S

Aquest divendres 11 a les 18h00, a AndorraDotNet hem organitzat una sessió gratuita d’Introducció a la Programació Orientada a Objectes.

La idea es fer una mica d’introducció a la OOP + Generics + LINQ fins on arribem, si veiem que hi ha material per continuar farem més…

L’event és gratuït i es farà el proper 11 de març, a les 18h00 al segon pis de l’edifici del centre cultural de la Llacuna. Al costat de la plaça de les arcades a Andorra la Vella.

La Llacuna Centre Cultural
Carrer Mossèn Cinto Verdaguer 4 (*)

(*) Al costat de la “plaça de les arcades”

Ubicació ‘La Llacuna Centre Cultural’ a Google Maps

És possible (si els paquets arriben a temps) que fins i tot tingui alguna coseta per sortejar.

Y com és habitual… després cerveses y sopar :-D

Ens veiem allà!

…skies are blue :-)

Si, se que el título del post está ‘pillado’ con pinzas, pero de lo que trata este post es de “cómo utilizar una brocha para pintar el interior de un control con varios niveles de degradado“, o sea “pintar un control Label con los colores del arco iris” :-D

Que? Ya habéis terminado de reíros?

Pues ahora os cuento el porqué se me ha ocurrido todo esto: Resulta que un proyecto en el que estoy trabajando, se muestran unas barras de progreso de color azul que indican el % de realización de unos presupuestos. Después de mirarlas un rato y ver lo ‘sosas’ que quedaban, he pensado que le podía dar un toque de color, aprovechando que pronto va a ser el día del orgullo friki.

mojo1

A que es bonito, verdad? :-)

Vale, pues hacer esto con WPF es trivial mediante GradientStops, pero, oooohhhh… este proyecto es WinForms. Pero tranquilos, el viejo y bueno namespace ‘System.Drawing.Drawing2D’ dispone de alguna sorpresa, como la clase ColorBlend, que permite definir un array de colores y posiciones para realizar degradados de colores. Anda mira, me ha salido un pareado! :-D

Total, que partiendo del evento Paint de cualquier control (en el que se obtienen como argumentos el contexto gráfico y el rectángulo del área a pintar), he creado una pequeña clase que permite especificar los distintos colores de degradado para el mismo:


public class MultiGradientRectangle
{
    public Graphics Graphics { get; set; }
    public Rectangle Rectangle { get; set; }
    public List<Color> ColorPoints { get; set; }
    public int PercentCompleted { get; set; }

    public MultiGradientRectangle(Graphics graphics, Rectangle rectangle)
    {
        Graphics = graphics;
        Rectangle = rectangle;
        ColorPoints = new List<Color>();
    }

    public void DrawMultiGradientRectangle()
    {
        if (PercentCompleted == 0) return;
        if (ColorPoints.Count == 0) return;
        List<float> myPositions = new List<float>();
        float colordistance = 1f / (ColorPoints.Count - 1f);
        for (int i = 0; i < ColorPoints.Count; i++)
        {
            myPositions.Add(colordistance * i);
        }
        ColorBlend blend = new ColorBlend();
        blend.Colors = ColorPoints.ToArray();
        blend.Positions = myPositions.ToArray();
        using (LinearGradientBrush brush = new LinearGradientBrush(
            Rectangle, Color.Black, Color.White, 0, false))
        {
            brush.InterpolationColors = blend;
            Graphics.FillRectangle(brush, Rectangle);
        }
        //algo falta aquí... .-)
    }
}

Ahora creamos un formulario con un control pictureBox (en realidad puede ser cualquiera) y nos suscribimos a su evento Paint.


public Form1()
{
    InitializeComponent();
    pictureBox1.Paint += pictureBox1_Paint;
}

void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    drawMultiGradient(e.Graphics, e.ClipRectangle, 50);
}

private static void drawMultiGradient(Graphics g, Rectangle rect, int percent)
{
    MultiGradientRectangle mgrect = new MultiGradientRectangle(g, rect);
    mgrect.PercentCompleted = percent;
    mgrect.ColorPoints.Add(Color.Red);
    mgrect.ColorPoints.Add(Color.Orange);
    mgrect.ColorPoints.Add(Color.Yellow);
    mgrect.ColorPoints.Add(Color.LightGreen);
    mgrect.ColorPoints.Add(Color.Green);
    mgrect.DrawMultiGradientRectangle();
}

Así, podemos crear tantos puntos intermedios como queramos, sólo tenemos la limitación del buen gusto.

Sin embargo falta algo, verdad? Efectivamente, en la captura de pantalla puede verse perfectamente que las barras de progreso se ‘rellenan’ sólo hasta el porcentaje alcanzado. Si es un 0% no hay degradado, si es un 50% sólo hasta la mitad, y así…

Como veis, falta un fragmento de código en el método DrawMultiGradientRectangle. Os dejo como ejercicio mostrar únicamente el relleno hasta el % especificado mediante la propiedad ‘PercentCompleted’. A ver que solución es la más chula ;-)

Un saludo desde Andorra, nos vemos!

PD – Podéis decir lo que queráis acerca del diseño, se admiten todo tipo de críticas (otra cosa es que os haga caso :-P )

arturo_y_caballeros

Hola a tots,

Aquest divendres 18/02/2011 organitzarem la segona taula rodona a AndorraDotNet. En que consisteix una taula rodona? Doncs bàsicament no hi ha un temari gaire definit, i l’objectiu consisteix en departir informalment sobre temes genèrics d’actualitat, o de la situació de la professió al país, o simplement explicar les penes que comporta ‘per se’ la nostra professió :-)

L’any passat es va organitzar la primera i va ser tot un èxit, ens ho vàrem passar molt bé parlant de les nostres misèries, de la situació actual i l’entorn tecnològic d’Andorra, i també vàrem aprofitar per planificar algunes de les properes xerrades.

En aquesta ocasió ja us avanço que tinc un parell de bones notícies de casa al 2011, però si voleu saber-ne els detalls haureu de venir divendres.

L’event és gratuït i es farà el proper 18 de febrer, a les 18h00 al segon pis de l’edifici del centre cultural de la Llacuna. Al costat de la plaça de les arcades a Andorra la Vella.

La Llacuna Centre Cultural
Carrer Mossèn Cinto Verdaguer 4 (*)

(*) Al costat de la “plaça de les arcades”

Ubicació ‘La Llacuna Centre Cultural’ a Google Maps

Y com és habitual… després cerveses y sopar :-D

Ens veiem allà!

PD – Si voleu proposar algun tema a tractar, deixeu un comentari…

El motivo

A raíz de una pregunta que me lanzó ayer mi colega Ricardo, hoy vamos a ver cómo obtener el valor de una propiedad de un usuario del directorio activo de la organización. Esto de por si, no tiene ningún secreto, basta con utilizar las clases contenidas en el namespace System.DirectoryServices. Lo que sí tenemos que tener muy claro, es el nombre de esa propiedad dentro del esquema del LDAP de nuestra organización.

GetPropADUser

Por ejemplo, para obtener el mail, la propiedad a utilizar es mail, obvio, no? Sin embargo en otras ocasiones no siempre es así. Por ejemplo, el nombre de la propiedad que nos devuelve el teléfono de un usuario es telephoneNumber, el código postal es postalCode, pero por ejemplo la propiedad que nos devuelve los apellidos, no es surname, sino sn, o el nombre del usuario está definido como sAMAccountName. En resumen, muchas de las propiedades están definidas con unos nombres… como decirlo? Muy cachondos :-P

Obtener los atributos de una clase

A todo esto ¿Cómo podemos obtener los nombres de los atributos de un usuario del LDAP en tiempo de ejecución? Easy –> Buscando la definición de la clase user en el esquema de nuestro LDAP y obteniendo su catálogo de propiedades (básicas + extendidas):

public static List<string> GetUserLDAPProperties(string LDAPUrl)
{
    List<string> properties = new List<string>();
    ActiveDirectorySchema adSchema = ActiveDirectorySchema.GetCurrentSchema();
    ActiveDirectorySchemaClass userSchema =
        default(ActiveDirectorySchemaClass);
    ActiveDirectorySchemaPropertyCollection propertiesCollection =
        default(ActiveDirectorySchemaPropertyCollection);
    userSchema = adSchema.FindClass("user");
    propertiesCollection = userSchema.MandatoryProperties;
    foreach (ActiveDirectorySchemaProperty prop in propertiesCollection)
    {
        properties.Add(prop.Name);
    }
    propertiesCollection = userSchema.OptionalProperties;
    foreach (ActiveDirectorySchemaProperty prop in propertiesCollection)
    {
        properties.Add(prop.Name);
    }
    properties.Sort();
    return properties;
}

De este modo obtenemos los nombres de las propiedades, con lo que ya sabemos los nombres de los atributos de un usuario de nuestro LDAP. Así pues, a partir del nombre de la propiedad que deseamos obtener, del identificador de seguridad de un usuario, y del nombre del dominio, ahora si vamos a ser capaces de obtener el valor de ese atributo para un usuario de nuestro LDAP (siempre que tenga ese atributo definido, claro):

public static string GetNTAccountProperty(string sid, string domain, string propertyToLoad)
{
    if (string.IsNullOrEmpty(sid)) throw new ArgumentNullException();
    if (string.IsNullOrEmpty(domain)) throw new ArgumentNullException();
    if (string.IsNullOrEmpty(propertyToLoad)) throw new ArgumentNullException();
    string ldapDomainName = GetLDAPDomainName(domain);
    using (DirectoryEntry entries = new DirectoryEntry(ldapDomainName))
    {
        string filter = string.Format(
            "(&(objectCategory=person)(objectClass=user)(objectSID={0}))", sid);
        DirectorySearcher searcher = new DirectorySearcher(entries, filter);
        searcher.PropertiesToLoad.Add(propertyToLoad);
        searcher.PropertiesToLoad.Add("objectSID");
        SearchResult result = searcher.FindOne();
        if (!result.Properties.Contains(propertyToLoad))
            throw new ActiveDirectoryObjectNotFoundException(
                string.Format("Property '{0}' not found on NTAccount '{1}'",
                propertyToLoad, sid));
        return result.Properties[propertyToLoad][0].ToString();
    }
}

Métodos de apoyo

Muchas de éstos métodos utilizan el nombre de nuestro LDAP en el siguiente formato: LDAP://DC=local,DC=miempresa,DC=com

Así que aquí tenéis una función que lo obtiene a partir del nombre del dominio:

public static string GetLDAPDomainName(string domainName)
{
    StringBuilder sb = new StringBuilder();
    if (string.IsNullOrEmpty(domainName)) throw new ArgumentNullException();
    string[] dcItems = domainName.Split(".".ToCharArray());
    sb.Append("LDAP://");
    foreach (string item in dcItems)
    {
        sb.AppendFormat("DC={0},", item);
    }
    return sb.ToString().Substring(0, sb.ToString().Length - 1);
}

Que a su vez se obtiene de este otro método:

public static string GetDomainName()
{
    return IPGlobalProperties.GetIPGlobalProperties().DomainName;
}

Proyecto de ejemplo

Hay algunas cosillas más, como la obtención de los usuarios del LDAP que (para no hacer el post más ‘tocho’ :-P ) no hemos visto en el post. Podéis descargar el proyecto de ejemplo completo desde skydrive en esta ubicación:

http://cid-f3a970280830b5fe.office.live.com/self.aspx/MSDN%20Samples/TestLDAP.zip 

Uffff… al final me ha salido un post más largo de lo que yo quería, pero bueno, espero que os sirva de algo :-)

Saludos desde Andorra,

Hola a tots,

Acabo de publicar un preview del primer dels (al menys) 6 vídeos sobre programació paral·lela amb .NET Framework 4.0.

De moment el podeu trobar aquí a youtube (encara no està publicat a Channel9), ja que encara és possible que canvií alguna cosa.

Com és el primer de la sèrie us agrairia una mica de feedback, que us sembla, coses a millorar, etc.

Merci i espero que us agradi :-)

SAPLogo

Hola a todos!

Un post rapidito: Al intentar distribuir mediante ClickOnce una aplicación Winforms que utiliza Crystal Reports 2010 (si, algunos de nosotros todavía usamos Winforms y el viejo CR), es posible que durante la instalación os encontréis con este error:

Setup has detected that the file ‘C:\…\Crystal Reports for .NET Framework 4.0\CRRuntime_32bit_13_0.msi’ has changed since it was initially published. Click OK to retry the download, or Cancel to exit setup.

El problema es que uno de los ficheros (Product.xml) está firmado con una clave pública incorrecta. Para que no os tengáis que pelear con lo mismo que yo (aunque al fin y al cabo buceando un poco por los foros de SAP lo encuentras rápido), aquí está la solución:

  1. Descargar una nueva versión del fichero Products.xml desde esta ubicación (en realidad es un ZIP).
  2. Descomprimir el ZIP en cualquier carpeta.
  3. Copiar el fichero Product.xml a la siguiente ubicación:

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\Crystal Reports for .NET Framework 4.0

(*) Si no estáis en un entorno x64 debéis utilizar ‘Program Files’ sin el x86.

Luego recompiláis la aplicación, publicáis otra vez, y listos!

Un saludo,

Ayer, a raíz de un post del colega Javier Torrecilla sobre métodos extensores, unos cuantos de nosotros entre los que estaban el propio Javier y Jorge Serrano nos enzarzamos en una discusión en twitter acerca del mejor modo de declarar nuestros métodos extensores.

Pongamos un ejemplo: Supongamos que queremos crear un método extensor para comprobar si un valor está entre dos valores (el clásico between de toda la vida).

Agrego una clase llamada ExtensionMethods a mi proyecto, o a otro proyecto mi solución y agrego este código:


namespace CustomExtensions
{
    public static class ExtensionMethods
    {
        public static bool Between<T>(this T @value, T min, T max) where T : IComparable<T>
        {
            return @value.CompareTo(min) >= 0 && @value.CompareTo(max) <= 0;
        }
    }
}

Suponiendo que estamos en un proyecto de tipo WinForms, si queremos utilizar este método extensor sobre un valor de tipo int basta con ir a cualquier formulario y llamar al método between sobre un valor de este tipo. Por ejemplo:

EM1

Oops! Que pasa? Por que no aparece el método extensor? Bueno, como ya os habréis dado cuenta el método extensor está declarado dentro de un namespce llamado ‘CustomExtensions’, que es distinto al namespace del formulario en el que lo estoy probando, con lo que no podemos usarlo directamente si previamente no hacemos un using:

Vale, ahora si que aparece:

using CustomExtensions;

EM2

Bien. Esto en si no es nada del otro mundo, pero la cuestión es que si deseamos evitar declarar el using (tenéis que pensar que este método extensor lo podéis reutilizar en 1000 proyectos distintos), no tenemos otra opción que:

  1. Declarar el método extensor en un namespace que se llame igual que el namespace en el que se va a usar.
  2. Declarar el método extensor en un namespace que se llame igual que el namespace del tipo que estamos extendiendo.
  3. Pasar de todo y llamarlo al namespace como queramos, y que a la hora de usarlo debamos usar un using para agregarlo.

Particularmente soy partidario del segundo punto, de modo que si vamos a extender elementos de tipo IComparable, en lugar de usar el namespace ‘CustomExtensions’ prefiero usar el nombre del namespace que contiene la definición de este tipo, o sea ‘System’:


namespace System
{
    public static class ExtensionMethods
    {
        public static bool Between<T>(this T @value, T min, T max) where T : IComparable<T>
        {
            return @value.CompareTo(min) >= 0 && @value.CompareTo(max) <= 0;
        }
    }
}

Pero ese es mi punto de vista, tu que opinas? Twitteros manifestaos! :-)

El título del post es algo largo, pero resume un problema que me volvía de cabeza desde hace un tiempo, y que no era capaz de resolver… hasta hoy.

Cuando trabajamos con imágenes en una aplicación suele ser muy común almacenarlas en una base de datos. En el caso que me ocupa, al ser imágenes con una resolución bastante alta, un requisito es que éstas deben almacenarse a distintas resoluciones. Sin embargo, antes de continuar con el tema permitidme un paréntesis:

<PARENTESIS MODE = “on”>

Sé que existen bastantes detractores de ésta práctica, que suelen preferir guardar las imágenes en disco, pero esto a mi juicio conlleva una serie de inconvenientes:

  1. Pérdida de atomicidad: Mezclamos un sistema transaccional con un sistema de ficheros no transaccional (y no, de momento no recomiendo usar transacciones en el sistema de ficheros, al menos si no queréis quedaros calvos en el proceso). De modo que como no disponemos de un mecanismo transaccional, debemos implementar mecanismos de sincronización ‘a manija’ entre la base de datos y el sistema de ficheros, con todo lo que conlleva.
  2. Problemas al hacer copias de seguridad: Ya que mediante el SQL Server agent podemos planificar copias periódicas de la base de datos, pero no de los ficheros asociados. Así pues, hay que copiar los ficheros manualmente o lanzando un proceso desde nuestra aplicación.
  3. También suele argumentarse que si guardamos las imágenes en la base de datos, el tamaño de la base de datos puede incrementarse mucho y degradarse el rendimiento (recordar que SQL Server Express ‘sólo’ admite bases de datos de hasta 10GB). Esto no es cierto, ya que desde la versión 2008 existe la posibilidad de utilizar FILESTREAM, que permite almacenar los datos de un campo en el sistema de ficheros, obteniendo así lo mejor de ambos mundos.

<PARENTESIS MODE= “off”>

Vale, sigamos con el tema.

Como os decía, en el proyecto que me ocupa actualmente un requisito muy importante es almacenar distintas resoluciones de una imagen en la base de datos mediante FILESTREAM. Para ello, hay que redimensionar cada una de las imágenes y convertirlas en un array de bytes, para luego almacenarlas en un campo de tipo BLOB, concretamente varbinary(MAX). Posteriormente cuando queremos recuperar una imagen, se lee el array de bytes y se transforma otra vez en imagen para visualizarla por pantalla, imprimirla, o lo que sea…

Redimensionar imagenes

Cuál es el problema entonces? Existen multitud de ejemplos en Internet acerca de cómo redimensionar imágenes:

public Image ResizeImage(Image oldImage, int targetHeightSize)
{
    Size newSize = calculateDimensions(oldImage.Size, targetHeightSize);
    using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
        PixelFormat.Format24bppRgb))
    {
        using (Graphics canvas = Graphics.FromImage(newImage))
        {
            canvas.SmoothingMode = SmoothingMode.AntiAlias;
            canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
            canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
            canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
            using (MemoryStream m = new MemoryStream())
            {
                newImage.Save(m, ImageFormat.Jpeg);
                return (Image)newImage.Clone();
            }
        }
    }
}

El código anterior funciona bien en casi todos los casos, pero no cuando la imagen a redimensionar contiene partes transparentes, ya que las partes transparentes aparecen en negro. Esto es así porque la información de transparencia de una imagen se almacena en el canal alfa, y en el código anterior al crear el nuevo Bitmap estamos usando explícitamente el valor ‘Format24bppRgb’ de la enumeración PixelFormat, que almacena 8 bits para cada color primario.

images_fail

En su lugar, debemos utilizar el valor ‘Format32bppRgb’ que almacena 8 bits para cada color primario más 8 bits para el canal alfa. También podemos omitir el formato en el constructor y pasar sólo el ancho y alto, ya que por defecto se usará el valor ‘Format32bppRgb’ en caso que no sea suministrado.

De todos modos, el código anterior es sólo a efectos de ilustrar el ejemplo, ya que para redimensionar una imagen es mucho más sencillo usar el método ‘GetThumbnailImage’ de la clase ‘Image’:

public static Image ResizeImage(this Image oldImage, int targetSize)
{
    Size newSize = calculateDimensions(oldImage.Size, targetSize);
    return oldImage.GetThumbnailImage(newSize.Width, newSize.Height, () => false, IntPtr.Zero);
}

Convirtiendo imágenes a bytes y viceversa

También existen multitud de ejemplos acerca de convertir imágenes a matrices y a la inversa. Veamos algunos ejemplos:

1) Mediante un MemoryStream: en este ejemplo se vuelca la imagen en un stream en memoria, y posteriormente se transforma en un array.

public static byte[] ConvertImageToByteArray(System.Drawing.Image imageIn)
{
    using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
    {
        imageIn.Save(ms, ImageFormat.Jpeg);
        return ms.ToArray();
    }
}
public static Image ConvertByteArrayToImage(byte[] byteArrayIn)
{
    using (System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArrayIn))
    {
        Image returnImage = Image.FromStream(ms);
        return returnImage;
    }
}

Resultaría muy sencillo si no fuese porque al convertir la imagen a un array volvemos a tener el problema de las transparencias.

2) Otro método es utilizar código unsafe para copiar literalmente los bits de la imagen a un array:

private unsafe byte[] BmpToBytes_Unsafe(Bitmap bmp)
{
    BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size),
        ImageLockMode.ReadOnly,
        PixelFormat.Format32bppArgb);
    int byteCount = bData.Stride * bmp.Height;
    byte[] bmpBytes = new byte[byteCount];
    Marshal.Copy(bData.Scan0, bmpBytes, 0, byteCount);
    bmp.UnlockBits(bData);
    return bmpBytes;
}
       
private unsafe Bitmap BytesToBmp_Unsafe(byte[] bmpBytes, Size imageSize)
{
    Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);
    BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size),
        ImageLockMode.WriteOnly,
        PixelFormat.Format32bppArgb);
    Marshal.Copy(bmpBytes, 0, bData.Scan0, bmpBytes.Length);
    bmp.UnlockBits(bData);
    return bmp;
}

Sin duda éste método ofrece un mayor rendimiento, y además al especificar el formato ‘Format32bppArgb’ nos soluciona el problema de las transparencias, pero resulta que nos crea otro problema: Para posteriormente poder revertir el array a imagen necesitamos conocer el tamaño de la imagen original, y eso no es demasiado práctico.

AL final la solución ha sido mucho más simple y porque no, mucho más elegante: Usando un simple TypeConverter.ConvertTo:

public static byte[] ConvertImageToByteArray(System.Drawing.Image imageIn)
{
    return (byte[])TypeDescriptor.GetConverter(imageIn).ConvertTo(imageIn, typeof(byte[]));
}

images_success

En fin, espero que si alguien ha estado en la misma situación que yo, al menos este post le resuelva un poco la vida :-)

Saludos desde Andorra a punto de cerrar el año,

Follow

Get every new post delivered to your Inbox.

Join 100 other followers