Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)
Mostrando entradas con la etiqueta .Net Visual Studio. Mostrar todas las entradas
Mostrando entradas con la etiqueta .Net Visual Studio. Mostrar todas las entradas

jueves, 28 de abril de 2022

"Curlicue Fractals" con Visual C#

Otra entrada migrada de la vieja web, la cual ha perecido con dignidad. De a pocos voy a ir copiando mis viejos tutoriales a este blog, ¡hay que tener paciencia!

Rod Stephens, autor de http://csharphelper.com/ tenía un tutorial sobre cómo dibujar estos fractales, pero él también ha tenido problemas con su web y el tutorial ya no está disponible. Voy a poner aquí lo que yo tengo, mi código está basado en el de Rod, así que los créditos van para él.

Al revisar la documentación en http://mathworld.wolfram.com/CurlicueFractal.html noté que sólo menciona dos ecuaciones cuando en el código fuente de Rod (ya no disponible) se usan cuatro:


Al seguir examinando se puede ver que las ecuaciones de la documentación de Mathworld generan ángulos, y lo que se dibujan son líneas que necesitan las coordenadas de los puntos inicial y final. 

La traducción del algoritmo es la siguiente: Se tiene una línea de longitud 1, paralela al eje x. Se calcula un número theta con la ecuación, luego, con la ecuación se calcula el nuevo ángulo de la siguiente línea de longitud 1, cuyo punto inicial será el extremo final de la línea anterior.
Al final resulta un ángulo. Su módulo es 1. Para calcular las coordenadas se multiplica por el coseno (coordenada x) y el seno (coordenada y).


Mi programa está basado en el algoritmo de Rob, pero con algunas cosas añadidas, como zoom y poder posicionar el fractal, además emplea el objeto Graphics de una forma diferente para dibujar el fractal, también añadí un número aleatorio para jugar con los colores:


El fractal se genera sólo a partir de un número irracional. Si se utiliza un número racional sólo aparecerá una línea horizontal.

La aplicación se puede descargar de aquí (para Visual C# 2008).

miércoles, 22 de enero de 2020

Ecualización del Histograma de una Imagen en C#

Otra entrada migrada de la vieja web :)


Jugando con el Visual Studio Express, cogí mi anterior algoritmo en Matlab para ecualizar el histograma de una imagen y lo quise adaptar para C# (y de paso lo optimicé un poco). La función para cargar la imagen de prueba (en el evento Load del formulario) es espantosamente complicada, esto se puede mejorar usando la clase Path de System.IO (lo que se hace es cargar una imagen que esté en la misma ruta de la aplicación).


El programa (para Visual Studio 2008) se puede descargar de aquí. El algoritmo para ecualizar el histograma está dentro del evento click del ratón. Está pensado para valores negativos también, para el caso en que la imagen a ecualizar haya tenido algún tratamiento intermedio que genere valores negativos (los objetos Bitmap sólo aceptan valores entre 0 y 255). 


miércoles, 5 de junio de 2019

Tutorial De Win32 Y Ejemplo Del Uso De Las Apis Bitblt, Getdibits Y Setdibits En Visual C++ 2008 Express


Este es otro tutorial viejito migrado desde la web Electrónica a Martillazos :D

En este tutorial la idea es manipular imágenes y mostrarlas en una ventana usando solamente apis de Windows. La manera más fácil es empleando el Visual C++ de Microsoft, el cual ya instala los archivos de encabezado necesarios y genera automáticamente el código para crear una ventana vacía.

Primero abrimos el Visual C++ (yo uso la versión 2008 Express), elegimos Nuevo Proyecto->Win32->Proyecto de Win32. A mi proyecto le he puesto el nombre de "FirstTry".

 

Como tipo de aplicación elegimos "Aplicación para Windows". Por ser la versión Express, no nos permite usar las librerías MFC ó ATL, pero no importa porque no las vamos a necesitar (y yo tampoco sé usarlas :P)




Al presionar el botón de "Finalizar", el Visual C++ nos crea varios archivos, con extensión .cpp y extensión .h. Donde debemos mirar es el código del archivo cpp que tiene el mismo nombre de nuestro proyecto. Para este caso es FirstTry.cpp. Con lo primero que nos encontramos es que el Visual C++ ha generado casi doscientas líneas de código. Todo ese código es necesario para mostrar una sola ventana, simple y vacía. Para entender en detalle qué significa, recomiendo este tutorial: The Forger's Win32 Api Tutorial.


Como resumen, voy a explicar un poco qué hace este código:


Hay cuatro funciones a las qué prestar atención: MyRegisterClass, InitInstance, WndProc y _tWinMain.


MyRegisterClass: En esta función se establecen las características de la ventana: colores, íconos, etc. También se le envía el nombre de la función (WndProc) donde se procesarán los eventos que ocurran en la ventana (al redimensionarla, maximizarla, hacerle click, cerrarla, etc).


InitInstance: Nuestro programa en ejecución (que por ahora sólo muestra una ventana vacía) se le conoce como instancia. Una aplicación puede tener varias instancias: por ejemplo, dos ventanas del Block de Notas abiertas son dos instancias de la aplicación Block de Notas. La función InitInstance inicializa la instancia actual de nuestra aplicación, crea la ventana llamando a la api CreateWindow y retorna un valor Verdadero o Falso dependiendo si la ventana pudo crearse con éxito o no.


WndProc: Aquí se procesa todo lo que ocurre en la ventana. Ese "todo lo que ocurre" se conoce como Eventos, y pueden ser desde entradas del teclado o clicks del ratón, hasta el cierre de la ventana y el final de la instancia de la aplicación.


_tWinMain: Función principal y desde dónde arranca nuestra aplicación. Esta función llama a MyRegisterClass y a InitInstance. Si todo sale bien, entrará al bucle de mensajes. Como en MyRegisterClass ya se asoció a la función WndProc con la ventana actual, no es necesario llamarla desde _tWinMain, de esto se encarga automáticamente el bucle de mensajes. Cuando ocurre un evento, la ejecución del programa "salta" a WndProc junto con un número que identifica al evento ocurrido.





Dentro de WndProc siempre hay un "switch" que evalúa el valor que acompaña a los eventos lanzados, de esta forma se puede identificar al evento y ejecutar el código correspondiente:




¿Complicado? ¡Claro que sí! Comparado con el Form_Load de Visual Basic o C#, esto es una pesadilla. Pero es así como el sistema operativo Windows maneja las ventanas de TODAS las aplicaciones. En Windows, no importa si se programó en .Net, VB6, Java o Borland C++, Delphi... las librerías que usan estos lenguajes no son más que "envolturas" (wrappers) de la api de Windows. En sus profundidades, todas las aplicaciones de Windows que tengan ventanas terminan en un bucle de mensajes.


Nota: Nótese que repito "Windows" muchas muchas veces, pues este tutorial es exclusivamente para este sistema operativo. En Linux las ventanas se crean con librerías como GTK, las cuales también poseen un bucle, pero las funciones y la lógica son totalmente distintas.


Para los ejemplos en este tutorial el evento que nos interesa es WM_PAINT, el cual se lanza cada vez que se maximiza o minimiza la ventana, cuando se la redimensiona, o se la vuelve a mostrar luego de estar tapada por otra ventana, en otras palabras, cada vez que se la "repinta".



Primer Ejemplo: Mostrar en la ventana una imagen en formato BMP.

Para cargar una imagen en el proyecto, debemos ir a donde dice Archivos de Recursos, hacer click derecho->Agregar->Elemento existente. Se abrirá una ventana desde la que elegiremos una imagen en formato BMP. En este caso he elegido un archivo llamado p1.bmp.




Luego le hacemos click derecho al archivo FirstTry.rc->Ver Código y añadimos la siguiente línea donde se declara el nombre con el que identificaremos a la imagen p1.bmp:




Luego vamos a Resources.h y definimos un valor único para el nombre que identifica a la imagen p1.bmp:


Estos pasos se deben repetir para cada imagen que se carga al proyecto. Para este ejemplo he cargado una segunda imagen llamada "IDI_CMC" y cuyo valor en Resources.h es 100.


A continuación vamos a FirstTry.cpp y, en la función WndProc agregamos el siguiente código en el "case" de WM_PAINT:



Este código crea un "handle" a la imagen ya definida en los archivos de recursos. Luego se crea un "Device Context" llamado hdcMem el cual se "enlaza" al handle de la imagen. Los "Device Context" (Dispositivos de Contexto) son áreas de memoria donde podemos dibujar y copiar imágenes. Luego se llama a la api GetObject para obtener las dimensiones de la imagen, éstos valores se guardarán en el objeto BITMAP llamado bm.


La imagen está cargada en algún lugar de la memoria RAM. Para que aparezca en la ventana debemos copiarla. Eso se hace con la api BitBlt cuyos parámetros son: destino (el handle del Device Context de la ventana, y que es el valor devuelto por la api BeginPaint y guardado en la variable hdc), coordenada "x" donde se ubicará la esquina superior izquierda de la imagen copiada, coordenada "y" donde se ubicará la esquina superior izquierda de la imagen copiada, ancho de la imagen, alto de la imagen, handle del Device Conext con la imagen a copiar, coordenada "x" de la esquina superior izquierda de la imagen a copiar, coordenada "y" de la esquina superior izquierda de la imagen a copiar, operación de copiado. Más información de esta api aquí.


Luego de terminar de utilizar los handles, bitmaps y demás objetos, se los debe borrar de la memoria. Al programar directamente con la Api de Windows usamos código no administrado. Es decir: no hay un recolector de basura que libere la memoria. Si una función en código no administrado no libera todos los recursos utilizados tendrá "fugas de memoria" (memory leaks) las cuales se irán acumulando cada vez que se llame a la función. En el peor de los casos terminará agotando la memoria asignada a nuestra aplicación y haciendo que se cuelgue.

Al ejecutar la aplicación se mostrará la imagen en la ventana. Cada vez que ésta se "repinte" ejecutará el código que vuelve a cargar y copiar la imagen. Si no fuera así, la imagen desaparecería si se minimiza y maximiza la ventana, se la redimensiona, etc.

El resultado final es:




Segundo Ejemplo: Copiar una imagen de forma que cubra toda la ventana.

Para tener el código de forma más ordenada, vamos a crear un nuevo archivo cpp y llamar sus funciones desde el "case" WM_PAINT en WndProc. Para ello hacemos click derecho en "Archivos de Código Fuente" y elegimos la opción Agregar->Nuevo Elemento:



En la ventana de "Agregar Nuevo Elemento" elegimos la opción Código->Archivo C++: 




Esto creará un archivo con extensión cpp en blanco. Primero le agregamos las referencias a los archivos de encabezado stafx.h y el que lleva el nombre del proyecto: 



Luego, en el archivo de encabezado con el nombre del proyecto, se añaden las declaraciones de las funciones que irán en el nuevo archivo cpp (para este caso lo he llamado Image.cpp). La función que dibujará y copiará una imagen en toda la ventana se llamará ImagenCopiar. En general, las funciones que dibujan en una ventana necesitan 3 parámetros: La instancia actual de la aplicación (para cargar la imagen dentro de un recurso), el handle de la ventana (hWnd) y el handle del Device Context (hdc) de la ventana (donde copiaremos la imagen).



A continuación muestro el código de ImagenCopiar. Buena parte del código simplemente se copió del ejemplo anterior, mas se han añadido la declaración de un objeto RECT y la llamada a la api GetWindowsRect; esto es para poder obtener las dimensiones de la ventana. Las dimensiones de la imagen se guardan en el objeto BITMAP llamado bm. Para este ejemplo he usado la imagen llamada p1.bmp declarada como IDB_BITMAP1.



El código incluye dos bucles anidados, los que recorren el ancho y el alto de la ventana en incrementos iguales al ancho y al alto de la imagen a copiar, la cual es mucho más pequeña que la ventana donde va a mostrarse. Dentro de los bucles se va alternado una llamada a la api BitBlt que copia exactamente la imagen a la ventana, y otra llamada a la misma api que copia la imagen pero con los colores invertidos. El resultado es:





Tercer Ejemplo: Manipular los píxeles de una imagen. Uso de las apis GetDIBits y SetDIBits.

Para este ejemplo declaramos una segunda función llamada ImagenManipular, la cual recibe los mismos parámetros que ImagenCopiar, convertirá la imagen p1.bmp a escala de grises y la mostrará en la ventana de la aplicación.




Para poder procesar individualmente cada píxel de p1.bmp, debemos extraerlos, guardarlos temporalmente en un array, realizar las operaciones necesarias con ellos, y devolverlos a una nueva imagen la cual se mostrará en la ventana.

Cada píxel de una imagen está compuesto de 3 ó 4 bytes que corresponden a los colores rojo, verde y azul, más el canal alpha que indica la transparencia, el cual no es usado en todos los formatos de imagen. En este ejemplo sólo se trabajará con los valores de los colores. Para el canal alpha existen otras apis como TransparentBlt ó AlphaBlend (la última es la más recomendable de usar) pero no nos ocuparemos de ellas en este ejemplo.


El código de ImagenManipular es el siguiente:




Usando la api GetDIBits extraemos los píxeles de la imagen, como esta api también necesita de una "cabecera" donde esté la información de la imagen (bits por píxel, dimensiones, compresión, etc), esta información la guardamos en las variables tipo BITMAPINFO, una para la imagen original y otra para la imagen ya convertida a escala de grises.. Las dimensiones de p1.bmp las obtenemos con la api GetObject. Leyendo tutoriales y otros códigos de ejemplo, supe que a las variables BITMAPINFO se les establece la altura de la imagen como un valor negativo ya que su sistema de coordenadas para la altura está invertido.


Las dos imágenes (la original y la que estará en escala de grises) necesitan su propio handle y su propio Device Context.

Para recorrer los píxeles de la imagen uso dos punteros: uno que siempre apuntará al inicio del array (buf2), y otro (buf) que lo recorrerá incrementando su valor. Luego coloco los píxeles en otra imagen mediante la api SetDIBits, la cual requiere también otra variable tipo BITMAPINFO para "construir" la nueva imagen en escala de grises.

SetDIBits crea (o "dibuja") la imagen en escala de grises en un espacio en memoria. Aún falta colocarla en la ventana para poder verla. Esto se hace con una simple copia usando la api BitBlt.


El resultado es:





Si llamo a la función ImagenCopiar y luego a ImagenManipular el resultado será:





Esto es porque ImagenManipular copia a p1.bmp encima de lo que ha sido dibujado por ImagenCopiar.


Una imagen cualquiera posee mucha más información que sólo los píxeles que la componen. Todos los archivos digitales poseen una cabecera que le dice al sistema operativo o a una aplicación qué tipo de archivo es. En el caso de imágenes, la cabecera almacena información de los bits por píxel, dimensiones, formato, tipo de compresión etc. E el caso de archivos jpg, también se almacena la Data Exif, que contiene información del dispositivo de captura (como una cámara digital), el software de edición, la fecha de creación, etc.  Las apis GetDIBits y SetDIBits nos permiten obtener sólo los píxeles de una imagen y guardarlos en un array para después procesarlos.


El proyecto con los tres ejemplos se puede descargar de aquí. Se compila con Visual C++ Express 2008.


Un detalle más: el código de estos ejemplos no está optimizado. Lo ideal es que las imágenes procesadas se guarden en un Device Context global y simplemente copiarlo al Device Context de la ventana cada vez que ésta se repinta.

viernes, 23 de junio de 2017

Excepción no controlada del tipo 'System.Configuration.ConfigurationErrorsException' en System.Configuration.dll

Esta excepción me saltó en un proyecto de C# compilado en Visual Studio Express 2013, el cual tiene configurado un un archivo .Settings. Inicialmente Visual Studio me puso el proyecto para .Net 4, yo lo cambié a .Net 3.5, y luego lo volví a cambiar a .Net 4, y luego de vuelta a .Net 3.5
Y cuando intenté depurarlo saltó la excepción:

Excepción no controlada del tipo 'System.Configuration.ConfigurationErrorsException' en System.Configuration.dll

Información adicional: Se ha producido un error al crear el controlador de sección de configuración para userSettings/miApp.Settings1: No se puede cargar el archivo o ensamblado 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' ni una de sus dependencias. El sistema no puede encontrar el archivo especificado.



Luego de rebuscar, mirar y furstrarme un rato, hallé el origen del problema.
El archivo App.Config:


       
Para .Net 4 la versión es 4.0.0.0. Para .Net 3.5 debe ser 2.0.0.0. Sólo debí cambiar a la versión correcta para corregir la excepción.
       

lunes, 28 de noviembre de 2016

Imprimir caracteres en consola con Visual C++

Al crear una aplicación de consola con Visual C++ Express encontré que no es tan intuituvo como creí (hay que importar más librerías de las que pensé, especialmente si se trabaja con arreglos de caracteres que se convierten a string). Al final, éste es el código que funcionó:


#include "stdafx.h"
#include
<iostream>
#include
<ostream>
#include
<string>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
    unsigned char* chararr = new unsigned char[256];

    // ... asigno valores al array de caracteres...   

    string str;
    str.assign((char*)chararr);   

  
    cout << "Look, I'm writting string values"
    cout << str << "\n";
   
    cout << "Look, I'm writting hexadecimal values";   

    for (unsigned long i = 0; i < 256; i++)
         printf("%02X ", chararr[i]);


    printf("\n");

}

sábado, 19 de diciembre de 2015

Cómo mostrar la fecha completa en un DataGridView cuando es exactamente medianoche

Hice la pregunta originalmente en StackOverflow.

Tengo este DataGridView:


El problema está en la fila seleccionada: en realidad tiene el valor "07/10/2015 12:00:00 am", pero en el DataGridView sólo se muestra "07/10/2015". La siguiente fila tiene el valor de " 07/10/2015 12:00:26 am ". Este problema sólo ocurre cuando los valores DateTime son exactamente medianoche. Cuando paso ese valor al de texto a la izquierda con el siguiente código, el valor DateTime se muestra correctamente (los valores DateTime están en la columna con índice 2):

text1.Text = dgView.SelectedRows[0].Cells[2].Value.ToString();

No quiero cambiar el valor de DefaultCellStyle.Format porque quiero mostrar los valores tipo DateTime en el formato de la configuración regional del sistema operativo (en mi caso es es-pe).

La solución me la dio Jon Skeet. Si se está usando Visual Studio, se debe seleccionar el DataGridView e ir a la ventana Propiedades->DefaultCellStyle e ingresar "G" en "Format":

 
Con esto el DataGridView mostrará las horas exactamente a medianoche y sin cambair la configuración regional.

domingo, 23 de agosto de 2009

Limpiar un Combobox (para Visual Studio 2008)

Si se tiene un control tipo combobox y se lo desea limpiar (borrar todos los elementos de su lista) en VB6 bastaba hacer:
Combobox.Clear

En .Net se debe añadir la propiedad "Items":
Combobox.Items.Clear()

Si el combobox tiene la propiedad "DataSource" enlazada a una base de datos, antes se deberá "romper" el enlace a ésta. Una manera es asignarle un valor nulo:

En VB:
Combobox.DataSource = Nothing

En C#:
Combobox.DataSource = null;

Si no se rompe el enlace, el compilador no nos permitirá limpiarlo. "Nothing" es el valor nulo en VB, "null" es el valor nulo para C#, C++, C o Java. Ambas son palabras reservadas en sus respectivos lenguajes.