Regiones del formulario en C# y VB (GDI+)

Capturar la ventana completa del formulario con o sin decoración (área cliente) o una región que se corresponde con un control PictureBox usando GDI+ mediante objetos Graphics de origen y destino y la función BitBlt que combina 2 mapas de bits en uno solo, en C# y VB

En un artículo anterior se mostraba cómo aplicar un recorte circular a la imagen de un control PictureBox para que la imagen se vea redondeada. He ampliado el ejercicio continuando con GDI (Graphics Device Interface). GDI es es uno de los tres componentes de la interfaz de usuario de Microsoft Windows. Trabaja junto con el núcleo y la API de Windows.

Este ejercicio añade la funcionalidad de captura de pantalla usando métodos de las clases de GDI+ explicando cómo hacer para capturar la ventana de la aplicación completa (con bordes y decoraciones), la ventana sin decoración (lo que se llama área cliente del formulario) o una región específica de la ventana, la de un control PictureBox de imagen redondeada, y guardar los resultados como imagen JPG en el directorio del ensamblado. Estas imágenes JPG se muestran en un formulario nuevo.

Esquema del código

Se definen 3 funciones (imagenFormA, imagenFormB y imagenFormC) que:

  • crean un objeto Graphics de origen (desde la ventana o región)
  • crean un Bitmap para contener la imagen (con la anchura y la altura del formulario o de la región)
  • crean otro objeto Graphics de destino (desde el Bitmap)
  • usan el tipo IntPtr (entero específico de la plataforma – sistema operativo) para obtener el Hdc (device context handle – enlace al contexto de dispositivo gráfico) de los 2 objetos Graphics generados
  • usan la función BitBlt (BitBlast) para copiar la imagen en el Bitmap
  • esta imagen es guardada en disco con el formato JPG.

Las funciones tienen un esqueleto común y las diferencias se enfocan en la región específica que han de capturar:

  • imagenFormA: captura la ventana con decoración y la guarda como Ventana1.jpg
  • imagenFormB: captura la ventana sin decoración y la guarda como Ventana2.jpg
  • imagenFormC: captura la región PictureBox y la guarda como Ventana3.jpg.

Es necesario invocar funciones que radican en librerías externas de la API de Windows.

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

// Importación de librerías externas de la API de  Windows
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
	public static  extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, System.Int32 dwRop);

// GetWindowDC devuelve el contexto de dispositivo (DC) correspondiente a la ventana entera, con bordes, barras de herramientas, de menús y de scroll, etc.
// Un DC de la ventana permite pintar sobre la ventana ya que el origen del DC es la esquina superior izda. de la ventana y no la esquina superior izda. del área cliente
[System.Runtime.InteropServices.DllImport("User32.dll")]
	public static  extern System.IntPtr GetWindowDC(System.IntPtr hwnd);

// Constante para usar como último parámetro de la función BitBlt
	private const int SRCCOPY = 0xCC0020; //destino = origen

Código en C#

Como ejemplo, expondré una de las 3 funciones de la clase, la que captura la ventana pero sin la decoración, lo que se llama el área cliente del formulario (el código es casi idéntico en las 3 funciones). El paso final es instanciar un Bitmap obtenido desde la función y guardarlo como JPG. Este fragmento final de código es el mismo para las 3 capturas.

//GUARDAR LA VENTANA SIN BORDES COMO JPG
private void btWin2_Click(System.Object sender, System.EventArgs e)
{
	try
	{
		Bitmap bm = imagenFormB();
		bm.Save(Application.StartupPath + "\\Ventana2.jpg", ImageFormat.Jpeg);
	}
	catch (Exception pollo)
	{
		MessageBox.Show("ERROR: " + pollo.Message, "Aviso");
	}
}

La función imagenFormB es la que contiene código relacionado con GDI+. Crea un objeto Graphics de origen para tener área de dibujo y un Bitmap para contener la imagen (anchura y altura del área del formulario) y también crea otro objeto Graphics de destino generado desde el Bitmap. Obtiene el DC (device context) de ambos objetos Graphics y con la función BitBlt se combinan los dos mapas de bits en uno solo.

//OBTENER UN BITMAP CON LA IMAGEN DE LA VENTANA SIN DECORACIÓN (ÁREA CLIENTE)
private Bitmap imagenFormB()
{
	try
	{
		// Objeto Graphics de origen para tener área de dibujo y Bitmap para contener la imagen (anchura y altura del área cliente del formulario)
		Graphics g1 = this.CreateGraphics();
		Bitmap bm = new Bitmap(this.ClientSize.Width, this.ClientSize.Height, g1);
		// Objeto Graphics de destino (desde el Bitmap bm generado a partir del primer objeto Graphics)
		Graphics g2 = Graphics.FromImage(bm);
		// Un device context (DC) es una estructura  que define un conjunto de objetos gráficos y sus atributos asociados
		// IntPtr es un entero específico de la plataforma (específico del sistema operativo de 32 o de 64 bits)
		// Hdc es device context handle (enlace al contexto de dispositivo del objeto g2)
		IntPtr bm_Hdc = g2.GetHdc();
		// Hdc de toda la ventana, se debe hacer después de crear el Bitmap (utiliza g1)
		IntPtr me_Hdc = g1.GetHdc();
		// Copiar la imagen del área del formulario en el Bitmap, rectángulos origen y destino tienen coordenadas x.y = 0
		// BitBlt deriva de la instrucción BitBLT (bit block transfer, transferencia de bloque de bits) y consiste en la transferencia
		// de un bloque de bits que se corresponden con un rectángulo de píxeles desde un DC origen a un DC destino
		// En resumen se trata de que dos mapas de bit son combinados en uno solo
		BitBlt(bm_Hdc, 0, 0, this.ClientSize.Width, this.ClientSize.Height, me_Hdc, 0, 0, SRCCOPY);
		// Liberar recursos, se puede liberar tambión la ventana
		g2.ReleaseHdc(bm_Hdc);
		g1.ReleaseHdc(me_Hdc);
		// Devolver el resultado
		return bm;
	}
	catch (Exception pollo)
	{
		MessageBox.Show("ERROR: " + pollo.Message, "Aviso");
	}
	return default(Bitmap);
}

Ventana inicial del programa

La ventana inicial del programa cuyas diferentes regiones son capturadas por las 3 funciones presenta un PictureBox cuya imagen se muestra con forma redondeada- Este código se ejecuta en el el evento Paint del formulario (se produce cada vez que se dibuja el formulario o cualquier otro control): se rellena el PictureBox con la imagen elegida dibujando sobre ella un área de recorte circular.

  //ACTUAMOS EN EL EVENTO PAINT DEL FORMULARIO
// El evento Paint se produce cada vez que se dibuja el formulario o cualquier otro control
// Aquí se rellena el PictureBox con la imagen elegida recortando sobre ella un círculo
// para crear la ventana del formulario que será copiada de formas diferentes y guardada como archivos JPG
private void Captura_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
	// Podemos mejorar el aspecto del borde redondeado aplicando antialias
	// Graphics.SmoothingMode obtiene o establece la calidad de la representación del objeto Graphics
	e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
	// Creamos un objeto de la clase GraphicsPath que representa una serie de líneas y curvas conectadas
	GraphicsPath curra = new GraphicsPath();
	// Manipulando las variables que se corresponden con los puntos x.y, ancho y alto de la figura podemos variar su aspecto
	int x = 0;
	int y = 0;
	int ancho = 0;
	int alto = 0;
	// Posiciones x.y del objeto curra (las del control PictureBox)
	x = PBox1.Left;
	y = PBox1.Top;
	// Anchura y altura del objeto curra, un poco menores que las del PictureBox para que se vea bien ajustado al control
	ancho = PBox1.Width - 24;
	alto = PBox1.Height - 24;
	// Usamos el método AddEllipse para agregar la forma de un círculo o elipse al trazado actual (el control PictureBox)
	curra.AddEllipse(new Rectangle(x, y, ancho, alto));
	// En el PictureBox creamos una región que se corresponde con la forma del objeto GraphicsPath (círculo)
	// y se dibuja en el PictureBox asignando el objeto curra a la región
	// Region de System.Drawing describe el interior de una forma gráfica formada por rectángulos y trazados
	Region reg = default(Region);
	reg = new Region(curra);
	PBox1.Region = reg;
	// Otra forma de realizarlo
	//pBox.Region = New Region(curra)
}

Abrir un segundo formulario con las capturas

El código siguiente abre el segundo formulario para mostrar juntas las 3 capturas diferentes (imagen inicial del artículo):

// Abrir una instancia del formulario Ventanas en el que se muestran las 3 imágenes capturadas (formulario con bordes,
// formulario sin bordes o área cliente y control de imagen) en 3 PictureBox diferentes adaptados al tamaño de las imágenes
private void BtCapturas_Click(object sender, EventArgs e)
{
	Ventanas F1 = new Ventanas();
	F1.Show();
	this.Close();
}

Función BitBlt

En este ejercicio se necesita que la imagen de la región del formulario seleccionada se copie a un Bitmap teniendo en cuenta que los rectángulos origen y destino tienen coordenadas x.y idénticas (x= 0, y=0). Para ello se emplea la función BitBlt.

BitBlt deriva de la instrucción del mismo nombre (bit block transfer, transferencia de bloque de bits) del ordenador Xerox Alto. Consiste en la transferencia de un bloque de bits que se corresponden con un rectángulo de píxeles desde un DC origen a un DC destino (DC = device context, contexto de dispositivo). En resumen se trata de que dos mapas de bit son combinados en uno solo.

BitBlt recibe 9 parámetros:

  • hdcDest: apunta al contexto de dispositivo (DC) destino
  • nXDest: coordenada x de la esquina superior izquierda del rectángulo destino
  • nYDest: coordenada y de la esquina superior izquierda del rectángulo destino
  • nWidth: anchura del rectángulo destino (ha de ser igual al origen)
  • nHeight: altura del rectángulo destino (ha de ser igual al origen)
  • hdcSrc: apunta al contexto de dispositivo (DC) origen
  • nXSrc: coordenada x de la esquina superior izquierda del rectángulo origen
  • nYSrc: coordenada y de la esquina superior izquierda del rectángulo origen
  • dwRop: código de operación de rastreo, define la manera en que los datos del origen se combinan con los del destino, cómo se combinan los colores en el rectángulo de salida para obtener el color final. El parámetro dwRop puede tener diferentes valores que implican distintas formas de combinar los 2 mapas de bits. En el ejercicio se usa la constante SRCCOPY que copia el rectángulo de origen directamente en el rectángulo de destino.

Puedes descargar un archivo ZIP con las aplicaciones C# y VB.