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 :)

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). 


lunes, 20 de enero de 2020

Filtrado E Histograma De Una Imagen Para Matlab 6.5 ó 7

Otra entrada migrada de la vieja web :)


Una imagen digitalizada no es más que una matriz de números, donde cada número representa el valor de un píxel. Se diferencia de una señal digitalizada en que una señal es un vector, o varios vectores separados (como el caso de la música estéreo, compuesta por dos vectores de números que representan los canales derecho e izquierdo).

Para el procesamiento de una imagen existen varias técnicas, las cuales se usan para obtener información no visible, resaltar bordes, suavizarlos, quitar detalles irrelevantes, agrupar objetos, eliminar el fondo, etc.

Una técnica muy usada es el filtrado, el cual puede ser lineal o no lineal. El filtrado lineal es simplemente convolucionar la imagen con una matriz predefinida. (la convolución es una operación de sumas y multiplicaciones que se usa tanto en señales como en imágenes). Un filtro lineal es el "Filtro de Promedio" o "Mean Filter". Se usan para suavizar, detectar o resaltar bordes, eliminación de ruido, etc.

El filtrado no lineal se compone de técnicas más complicadas. El más básico es el Filtrado de Mediana o "Median Filter", que consiste en coger una pequeña porción de la imagen (generalmente 3x3 píxeles), evaluar el valor de los píxeles y cambiar sus valores al de aquél que tenga un valor central. Es útil para eliminar ruido impulsivo.

Otra técnica muy usada es la "Ecualización del Histograma". El Histograma de una imagen es el ploteo de los valores de sus píxeles. Una imagen en blanco tendrá todos sus valores iguales a 255, si la mitad es negra, en la gráfica del histograma aparecerán dos líneas iguales a ambos extremos: en los valores correpondientes al 0 y al 255. Una imagen de escala de grises tendrá en su histograma "x" píxeles con el valor 0, "y" píxeles con el valor 1, etc.

Así, el histograma es la representación de la densidad de probabilidad de cada valor de gris para esa imagen.

Tanto el histograma, como el histograma ecualizado, son vectores.

Ecualizar el Histograma es hacerlo lo más llano y separado posible. Esto hace que los píxeles se distribuyan más ampliamente por todo el rango de valores (del 0 al 255) y que en la imagen ecualizada se resaltarán detalles que antes no eran evidentes.

Para generar una imagen con el histograma ecualizado se requieren varios pasos:

1. Calcular el histograma de la imagen

2. Normalizar el histograma (dividirlo entre el número total de píxeles)

3. Calcular el histograma acumulado (ir sumando los píxeles desde el valor 0 al 255, esto originará una gráfica creciente)

4. Se aplica el algoritmo (como se trata de matrices, debe estar dentro de dos bucles anidados):

Para valores de la imagen original distintos de cero:
imagen_ecualizada(i,j) = histograma_acumulado(imagen_original(i,j))

Para valores de la imagen original iguales a cero:
imagen_ecualizada(i,j) = histograma_acumulado(imagen_original(i,j)+1)

Donde lo que va entre paréntesis es el índice, o índices, de cada píxel de la imagen o de cada valor del histograma. Así  "imagen_original(i,j)" viene a indicar el índice del vector del histograma acumulado correspondiente. De esta manera se le asigna a cada píxel de la imagen ecualizada (o imagen con el histograma ecualizado) la densidad de probabilidad acumulada correspondiente al valor del píxel de la imagen original.
Como este algoritmo está hecho para Matlab, y como Matlab no maneja índices iguales a cero, se considera que el histograma va de 1 a 256, en lugar de 0 a 255. Así la densidad de probabilidad del valor cero será la que está en el índice 1 en el vector del histograma.

El algoritmo que presento akí está considerado para matrices con valores tanto positivos como negativos. Es un archivo zip que incluye ejemplos de filtraje, ecualización y eliminación de ruido.

Link de donde aprendí la teoría para poder hacer esto:

http://homepages.inf.ed.ac.uk/rbf/HIPR2/filtops.htm

miércoles, 11 de diciembre de 2019

Comprimiendo y descomprimiendo archivos en formato rar en Ubuntu

Estoy usando Xubuntu 16 y necesité descomprimir un archivo rar. Luego de una intensa búsqueda de veinte segundos en google llegué a esta web donde explican cómo instalar rar y unrar y poder lidiar con este tipo de archivos. A continuación un resumen:

Ejecutar los siguientes comandos:

sudo apt-get install rar
sudo apt-get install unrar

Para descomprimir un archivo rar en la misma carpeta donde está se ejecuta el comando:

unrar x EjerciciosEjemplo.rar

Pero lo que no dice la web es que, una vez que se instala rar y unrar, el sistema ya reconoce este formato de archivos y no es necesario usar la consola ni instalar nada más. Para comprimir un archivo en formato rar basta hacerle clic derecho e ir a la opción "Crear un archivador":



 Y luego se escoge el formato deseado, en este caso rar, y se presiona el botón "crear":



Para descomprimir, basta hacerle doble clic al archivo rar y el Gestor de Archivadores lo abrirá como si fuera el mismísimo winrar:


viernes, 8 de noviembre de 2019

Matlab: Resolviendo la ecuación de onda bidimensional en el caso de una membrana de forma piramidal

El código fuente para resolver una ecuación de onda bidimensional está dado en el archivo twodimwavedirbc.m disponible en esta web: MATH 5343 Numerical Solution for Partial Differential Equations, Spring 2011.

La idea es resolver el ejercicio 12a del capítulo 12.2 del libro "Introduction to Numerical Ordinary and Partial Differential Equations Using MATLAB" de Alexander Stanoyevitch (de este libro proviene el scrip twodimwavedirbc.m):


Es decir, es una ecuación de onda cuya forma inicial es una pirámide. La dificultad aquí es dibujar la pirámide, yo la deduje como explico a continuacion:

La pirámide mostrada:
está formada por cuatro planos limitados por las rectas Y=X y Y = -X/2+4:

Las regiones donde Z es una función de X o de Y (dependiendo del plano donde se proyecta el eje Z) se muestran a continuación:


Para X<2, Y<2 y la región donde X<Y, la variable Z está definida por la función Z= Y/2.
Esto se hace para cada región de cada uno de los cuatro planos que definen la pirámide.

El script resultante es:

function z = pyramid(x, y)
z=0;

if y<=2 && x <= 2
   if y < x
       z=y/2;
   else
       z=x/2;
   end
end


if y<=2 && x > 2 && x <= 4
   if y < 4-x
       z=y/2;
   else
       z=-x/2+2;
   end
end


if y > 2 && x <= 2 && y <= 4
   if y < 4-x
       z=x/2;
   else
       z=-y/2+2;
   end
end


if y > 2 && y <= 4 && x > 2 && x <= 4
   if y < x
       z=-x/2+2;
   else
       z=-y/2+2;
   end
end

end
 


Modifiqué un poco el archivo twodimwavedirbc.m para hacerlo más flexible:

function [x, y, t, U] = twodimwavedirbc2(phi, nu, a1, a, b1, b, T, h, c)
% a1: punto inicial del eje x
% b1: punto inicial del eje y

% solves the two-dimensional wave problem u_tt = c^2(u_xx+u_yy)
% on the rectangle {a1<= x <= a,  b1<= y <= b}, with u(x,y)=0
% on the boundary of the rectangle.

% variables de entrada: 
% phi=phi(x,y) = función de la forma inicial de la onda
% nu=nu(x,y) = función inicial de la velocidad onda

% a= right endpoint of x, b = right endpoint of y, 
% T = final time solution will be computed.  h=common gap on
% x, y-grids,   k= t-grid gap, c = speed of wave.
% wave.
% Output variables: x = row vector for first space variable, y =
% row vector for second space variable,  t = time grid row vector
%(starts at t=0, ends at t=T, has Nt equally spaced values), 
% U = (Nx)by(Ny)by(Nt) matrix of solution approximations at
% corresponding grid points (where Ny = number of y-grid points)
% x grid will correspond to first entries of U, y
% grid values to second entries of U, and t grid to third entries of U.
% CAUTION:  This method will only work if h is chosen so that the x
% and y grids can have a common gap size, i.e., if h = a/h and
% b/h must be integers.
% The time grid gap is chosen so that mu = 1/2, this guarantees the
% Courant-Friedrichs-Levy condition holds and simplifies the
% main finite difference formula.


k=h/sqrt(2)/c; %k is determined from mu = 1/2

if (b/h-floor(round(b/h))>10*eps)||(a/h-floor(round(a/h))>10*eps)
    fprintf('Space grid gap h must divide evenly into both a and b \r')
    fprintf(' Either try another input or modify the algorithm')
    error('M-file will exit')
end

Nx = round((a-a1)/h)+1; %number of points on x-grid
Ny = round((b-b1)/h)+1; %number of points on y-grid
Nt = floor(T/k)+1; %number of points on t-grid
U=zeros(Nx, Ny, Nt);
x=a1:h:a;
y=b1:h:b;
t=0:k:T;
% Recall matrix indices must start at 1.  Thus the indices of the
% matrix will always be one more than the corresponding indices that
% were used in theoretical development.


% Note that by default, zero boundary values have been assigned to all
% grid points on the edges of the rectangle (and, for the time being,
% at all grid points).
%Assign initial time t=0 values and next step t=k values.

for i=2:(Nx-1)
     for j=2:(Ny-1)
    U(i,j,1)=feval(phi,x(i),y(j));
    U(i,j,2)=.25*(feval(phi,x(i-1),y(j))+...
      feval(phi,x(i+1),y(j))+feval(phi,x(i),y(j-1))+...
      feval(phi,x(i),y(j+1)))+ k*feval(nu,x(i),y(j));     
     end
end

%Assign values at interior grid points
for ell=3:Nt %letter ell looks too much like number one
    U(2:(Nx-1),2:(Ny-1), ell) = ...
     +.5*(U(3:Nx,2:(Ny-1), ell-1)+ U(1:(Nx-2),2:(Ny-1), ell-1)...
     + U(2:(Nx-1),3:Ny, ell-1) + U(2:(Nx-1),1:(Ny-2), ell-1))...
     - U(2:(Nx-1),2:(Ny-1), ell-2);
end


Y el script principal es:

clc; clear all; close all;
phi = @(x, y)sin(2*x) *sin(2*y);
nu=@(x, y)0;
[x, y, t, U] = twodimwavedirbc2('pyramid', nu, 0, 4, 0, 4, 4, 0.1, 1);
[a b c] = size(U);
%ans = 26 26 46
for ell=1:c
     surf(x,y,U(:, :,ell));
     axis([0, 4, 0, 4 -1, 1]);
     M(:,:,ell) = getframe;
end
movie(M, 2, 4)



Lo he probado en Matlab, y acá está corriendo lo mejor que puede en Octave 4.4.1 a pesar de que me dice que la función Movie no está implementada:

viernes, 25 de octubre de 2019

Resolviendo una ecuación parabólica unidimensional en Matlab/Octave

El siguiente ejercicio se encuentra en el libro "Computational Partial Differential Equations Using MATLAB" by Yi-Tung Chen, Jichun Li, capítulo 2, ejercicio 3:


Una pista de cómo resolver este ejercicio está en la sección 2.21:


Los autores dan un código base sobre el qué trabajar. Luego de recibir un poco de ayuda de la gentita de Stackoverflow, este es el código para resolver el problema:


 %---------------------------------------------------------------
% para1d.m:
%    use the explicit scheme to solve the parabolic equation
%    u_t(x,t) = u_{xx}(x,t),          xl < x < xr, 0 < t < tf
%    u(x,0) = f(x),                   xl < x < xr
%    u_x(0,t) = gl(t), u_x(1,t) = gr(t),  0  < t < tf
%---------------------------------------------------------------

clc;
close all;
clear all;  
                % clear all variables in memory

xl=0; xr=1;                 % x domain [xl,xr]
J = 31;                     % J: number of division for x
dx = (xr-xl) / J;           % dx: mesh size
tf = 0.1;                   % final simulation time
Nt = 51;                    % Nt: number of time steps
dt = tf/Nt/4;

mu = dt/(dx)^2;

if mu > 0.5         % make sure dt satisy stability condition
    error('mu should < 0.5!')
end

% Evaluate the initial conditions
x = xl : dx : xr;              % generate the grid point
f = x;

% store the solution at all grid points for all time steps
u = zeros(J+1,Nt);
u_ex = zeros(J+1,Nt);

% boundary condition at left side

gl = 0;
% boundary condition at right side
gr = 0;

% Find the approximate solution at each time step
for n = 1:Nt
    t = n*dt;         % current time
     
    if n==1
      for j=2:J
       % first time step
        u(j,1) = f(j) + mu*(f(j+1)-2*f(j)+f(j-1));
      end
    end

  
    if n > 1
        for j=2:J
         % interior nodes
           u(j,n) = u(j,n-1) + mu*(u(j+1,n-1) - 2*u(j,n-1) + u(j-1,n-1));
        end
    end

  
    % hint 2.21 Los valores en la frontera es mejor calcularlos al final
    % pues estoy cogiendo índices que dependen de los nodos interiores

    u(1, n) = u(2, n) - dx * gl;
    u(J+1,n) = u(J, n) + dx * gr;
  
    % calculate the analytic solution
    for j=1:J+1
      xj = xl + (j-1)*dx;    
      suma = zeros(1000 , 1);

      for k= 1:1000
        suma(k) = 4/(((2*k-1)*pi)^2);
        suma(k) = suma(k) * exp(-((2*k-1)^2)*t*pi^2) * cos((2*k-1)*pi*xj);
      end

      u_ex(j, n)= double(0.5 - sum(suma));
    end
end


% Plot the results
tt = dt : dt : Nt*dt;
figure(1)
colormap(cool);     % draw gray figure
surf(x, tt, u');     % 3-D surface plot
xlabel('x')
ylabel('t')
zlabel('u')
title('Numerical solution of 1-D parabolic equation')

figure(2)
surf(x, tt, u_ex');     % 3-D surface plot
xlabel('x')
ylabel('t')
zlabel('u')
title('Analytic solution of 1-D parabolic equation')

maxerr=max(max(abs(u-u_ex))),



El resultado es:


Probado en Octave 4.4.1 y Matlab 2008 R2.

miércoles, 11 de septiembre de 2019

Error de Visual C++ 2017: C3078 se debe especificar el tamaño de la matriz en nuevas expresiones

Este error no me pasaba en Visual Studio 2013. El error C3078 surgió de repente luego de actualizar mi proyecto en Visual C++ a Visual Studio 2017 y al SDK de Windows versión 10.0.17763.0 (para actualizar el SDK al que apunta nuestra solución se hace clic derecho en Solución->Redestinar Solución):



La línea que generaba el error en mi código fuente es la segunda en el siguiente ejemplo:

unsigned char* tempPointer;
tempPointer = new unsigned char[]{'H', 'i', '\0'};

El error no podría ser más fácil de resolver, sólo hay que indicar el tamaño del array:

unsigned char* tempPointer;
tempPointer = new unsigned char[3]{'H', 'i', '\0'};

Algo debe haber cambiado en el estándar de C++.

martes, 16 de julio de 2019

(Tutotial viejito): Creando Un Ocx Para Reproducir Video Con La Api Mcisendstring

Este es otro tutorial migrado de la vieja web:

Sucedió que necesité mucho mucho un ocx para reproducir video, pero no cualquier ocx: no debía depender de componentes instalados con anterioridad, lo que yo quería era un ocx que trabajara directamente con las Apis del windows.
Las Apis son funciones del propio Windows, guardadas dentro de los millones de Dlls en la carpeta de instalación (especialmente en la System32). Las Apis son casi un millar y lo hacen todo: obtienen información de los discos y las particiones, las características del escritorio, la pantalla, los colores, el hardware, los archivos, manejan multimedia, los periféricos, etc etc.

Rebuscando en internet, hallé muchos códigos fuentes que llamaban a las Apis que manejan multimedia: MciExecute, MciSendCommand y MciSendString.

De todas, la mejor es la MciSendString. La primera tiene opciones un tanto limitadas, la segunda requiere que se le pasen parámetros en hexadecimal, mientras que la última permite cadenas fácilmente entendibles.


Cómo funciona MciSendString:

Esta Api devuelve un valor tipo Long que es un código por si ocurre un error. Para saber qué error ha sido se llama a otra Api: mciGetErrorString, la cual no usé para este ocx.


Y fue gracias a estos ejemplos que aprendí a usar la MciSendString:



Lo primero que se debe hacer con esta Api es enviarle la ruta del archivo multimedia que se quiere reproducir:

"vid" es una variable tipo Long, "rutavideo" es una variable tipo String y contiene la ruta del archivo, algo así como:

C:\carpeta\ese_archivo.mpg 


 Lo que primero se hace es ponerle las comillas al principio y al final.


rutavideo = Chr$(34) & rutavideo & Chr$(34) 'para agregarle las comillas
vid = mciSendString("open " & rutavideo & " type mpegvideo alias movie parent " & Picture1.hWnd & " style " & "child ", 0, 0, 0)
vid = mciSendString("put movie window at 0 0 336 240", 0&, 0, 0)

La segunda y tercera líneas le dice que abra el archivo de esa ruta como un video del tipo mpeg (todos los videos que quería reproducir son mpegs) y que lo reconozca como "movie" porque así es más fácil de escribir (¡observad el espacio después de "open"! aquí hay que respetar los espacios!!). 
En esta segunda línea también se puede poner simplemente:



vid = mciSendString("open "& rutavideo & type mpegvideo alias movie", 0&, 0, 0)
 


Pero esto hará que cuando se reproduzca el video
lo haga en una ventana Popup, y yo quería reproducirlo dentro de mi control ocx,
así que por ello le incluí un Picturebox. La propiedad "Picture1.hWnd"
devuelve un número tipo Long que es el identificador del control donde se desea
reproducir el video (puede ser un form también), así se evita que aparezca esa
ventana Popup. 
El resto de las instrucciones es más simple (basta recordar que ya la Api reconoce a nuestro archivo y su ruta como "movie"):

vid = mciSendString("play movie", 0, 0, 0)
vid = mciSendString("pause movie", 0, 0, 0)
vid = mciSendString("resume movie", 0, 0, 0)
vid = mciSendString("stop movie", 0, 0, 0)


Para reproducir desde un tiempo determinado ("tiempo" es una variable tipo Long):
vid = mciSendString("play movie from " & tiempo, 0, 0, 0)

Una muy importante es:
vid = mciSendString("close movie", 0, 0, 0)


Si no se "cierra" nuestro archivo, a la siguiente que se quiera reproducir no aparecerá. Esta instrucción debe estar en el evento Unload del form y cada vez que se quiere reproducir otro video.

Hasta aquí lo básico para hacer un reproductor con MciSendString. Ahora falta meterlo todo en un ocx, al cual llamé "video_rep".
Cómo crear un ocx está en esta web:
http://www.elguruprogramador.com.ar/articulos/creando-un-control-activex-ocx.htm
Lo que hice fué combinar ambas cosas: los comandos del MciSendString dentro de sus respectivas funciones en el ocx para luego "jalarlas" desde una aplicación.
El código fuente de video_rep y unejemplo de cómo usarlo está akí.
Este ocx aún puede mejorarse y adaptarse. Quise hacerlo de modo que pudiera usarse con o sin Slider, bueno, yo sólo lo he probado con Slider.

Y ahora, una excelente web con recopilación de trillones de Apis:


http://allapi.mentalis.org/index2.shtml

Recomiendo mucho bajar el ApiViewer y el Api-Guide

viernes, 14 de junio de 2019

Comandos para Xubuntu que me han servido mucho


Reiniciar Xfce4 porque la barra de tareas no responde a los clicks del ratón:


xfce4-panel -r

Fuente Ask Ubuntu.

Lo malo es que este comando me desconfiguró un par de atajos del teclado:

Configurar la tecla Windows+D (Super+D en el mundo Linux) para abrir el Administrador de Archivos (Explorador de Archivos en el mundo WIndows):

No me funcionó la opción obvia: configurar las teclas mediante Inicio->Configuración->teclado.

Lo que me funcionó fue esto:

xfconf-query --channel xfce4-keyboard-shortcuts --property "/xfwm4/custom/d" --reset

xfconf-query --channel xfce4-keyboard-shortcuts --property "/xfwm4/custom/d" --create --type string --set "show_desktop_key"
xfconf-query --channel xfce4-keyboard-shortcuts  --list -v | grep -i super


Y luego de que se me desconfiguraran los atajos de teclado de nuevo, volví a correr estos comandos y lo volví a arreglar :D

Fuente, este foro.

Para configurar la tecla Super (Windows) y que se muestre el menú de inicio Wiskers es más fácil ir a Configuración->Teclado->Atajos de Aplicación e ingresar el siguiente comando:

xfce4-popup-whiskermenu



Luego presionar Aceptar, aquí pedirá la tecla que deseamos, si presionamos Super (Windows) aparecerá como Super-L, se le da aceptar y luego cerrar.


Montando mi usb de 4 gigas:

sudo mkdir /media/newhd
sudo mount /dev/sdb1 /media/newhd


Fuente, esta web


Abriendo el Administrador de archivos como sudo:

sudo exo-open --launch FileManager

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.

sábado, 4 de mayo de 2019

Reemplazando Windows XP por Xubuntu 16 en una laptop del 2008

Mi vieja laptop Toshiba Satellite A215 tenía windows XP instalado y estaba teniendo problemas de compatibilidad (no podía reproducir videos de youtube, el antivirus se suicidó, y Visual Studio y Java me mostraron el dedo del medio) de modo que decidí darle una nueva vida con un Sistema Operativo nuevo. Escuché que Windows 7 es una buena opción para PCs que corren Windows XP, dicen que optimiza mejor el hardware y es totalmente compatible. He usado Windows 7 por años y lo amo, pero esta vez quise intentar algo diferente. En el trabajo usé Centos, Ubuntu y me enamoré de Linux Mint, pero como esta laptop es más vieja que mis zapatos tenía que buscar un Linux que le cupiera. Al final descargué Xubuntu 16 LTS desktop iso, porque no pude encontrar el iso de la versión 18 para 32 bits. Esta es una distro para computadoras con hardware humilde.

Estos son los pasos que debemos seguir:

0. ¡¡¡BACKUPEAMOS TODA LA DATA!!!
1. Descargamos el iso de Xubuntu (primera opción en el enlace).
2. Quemamos el iso en un DVD (puede ser regrabable) con infraRecorded. No se puede simplemente quemar el iso en el DVD, No va a funcionar. Con este software sólo requiere presionar el botón "Write Image" y hace todo el trabajo de crear un DVD booteable.
3. Reiniciamos la laptop desde el DVD.

4. Esperamos a que cargue:



5. Podemos probar Xubuntu si deseamos, esta opción no cambia nada en el sistema. Yo fui directamente a la instalación después de escoger el idioma:



6. Escogemos Instalar todos los codecs y presionamos "Continuar":





7. Vamos a "Más Opciones".
Como esta laptop tiene 3 particiones C, E y F, yo quise reescribir la partición C (donde el WinXP está) y dejar las otras intactas (si algo va mal, tengo backups).




8. Al presionar "Continuar" nos mostrará las particiones detectadas. La partición C es dev/sda2.




9. Escogemos la partición con Windows XP y le hacemos doble click:

Escogemos formatearla con el tipo de formato ext4 para la partición sda2 seleccionando la opción de "formatear la partición". ¡Cuidado de no tocar las otras dos particiones!



Debe quedar así:

Como la idea es reemplazar Windows XP por Xubuntu, le indico que el cargador de arranque esté también en /dev/sda2.


10. El instalador va a pedir una partición de intercambio. Escogí dev/sda1, era la partición de recuperación de Toshiba, pero para una máquina así de vieja no necesito recuperar nada. Esta opción está en la misma ventana del paso anterior. Finalmente mis particiones quedaron así:





¡Y ahora es el momento de la espera! La instalación copiará los archivos y configurará el sistema.


 De nada :D

Aquí el sistema nos dirá que saquemos el DVD y reiniciemos la máquina.

Funciona como la gloria, hasta reconoce las particiones NTFS:



No tocó mis otras particiones, mis archivos siguen ahí.

Unas cosas más para hacer:

sudo apt-get install gimp
sudo apt-get install firefox
sudo apt-get install firefox-locale-es


Esto es para instalar el editor de imágenes gimp, y actualizar el firefox en español.

:D :D :D :D :D

 El enlace del videíto es éste:


Update dos semanas después:
No me funciona el scanner.

Mi scanner es un CanoScan Lide 110 (debo haberlo comprado allá por el 2012) que funciona perfectamente con Windows 7 sin necesidad de instalar nada (utiliza el protocolo Windows Image Adquisition por defecto) y funcionaba perfectamente con el Windows XP.
Xubuntu 16 me lo reconoce, pero no puede escanear, lo más que logra hacer es intentar arrancar el motor del scanner y nada más.
Probé actualizando sane, xsane, easy-scan, reiniciando, añadiendo ppas raritos, actualizado con apt-get update y apt-get upgrade... y nada.

Bueno, esto queda pendiente, mientras tanto tengo otra laptop (del 2010, tengo la manía de encariñarme con mi hardware) con Windows 7.

Una última recomendación: antes de reemplazar alegremente un Windows por un Linux, es mejor asegurarse que todos los dispositivos, cacharros y demás funcionan con la distro que hemos elegido, nunca se sabe.