Clase para generar efectos de transición entre imágenes con GDI+
 

Fecha: 13/Dic/2004 (12/Dic/04)
Autor: Aníbal Svarcas.  asvarcas @ montevideo.com.uy

 


Programa en Ejecución
Programa en Ejecución

 

Este proyecto muestra como realizar 55 diferentes efectos de transición (movimiento) entre imágenes con GDI+.

Para eso vamos a crear una clase de nombre "EfectosTrans" la cual recibirá en su constructor un control que será

donde se dibujarán los efectos, y una propiedad de tipo "Image" de dicho control donde pondremos una imagen vacía sobre la cual se va a dibujar una imagen mediante un efecto que también hay que pasarle a la clase.

A continuación pongo el código completo de la clase EfectosTrans y del código de ejemplo (también pueden descargar la clase junto al proyecto de ejemplo siguiendo el link que está al final de la página).

 

 

Imports System

Imports System.Windows.Forms

Imports System.Drawing

Imports System.Drawing.Drawing2D

Imports System.Drawing.Imaging

Imports System.ComponentModel

 

 

Friend Class EfectosTrans

   Implements IDisposable

 

   'Evento EffectEnded: se dispara al finalizar un efecto

   Public Event EffectEnded As EventHandler

 

#Region " Enumeraciones "

 

   'Enumeración de Efectos

   Public Enum Efectos

      Abanico_Derecha = 0

      Abanico_Izquierda

      Aparecer

      Barras_Horizontales

      Barras_Verticales

      Barrido_Horizontal

      Barrido_Vertical

      Circulos_Dentro

      Circulos_Fuera

      Desplegar_Centro

      Desplegar_ID

      Desplegar_II

      Desplegar_SD

      Desplegar_SI

      Diagonal

      DivisiónH_Entrante

      DivisiónH_Saliente

      DivisiónV_Entrante

      DivisiónV_Saliente

      Empuja_División_Lados

      Empuja_División_Topes

      Empujar_Abajo

      Empujar_Arriba

      Empujar_Derecha

      Empujar_Diagonal_ID

      Empujar_Diagonal_II

      Empujar_Diagonal_SD

      Empujar_Diagonal_SI

      Empujar_Izquierda

      Estirar_Centro

      Estirar_ID

      Estirar_II

      Estirar_SD

      Estirar_SI

      Girar_Centro

      Girar_Espiral_Abajo

      Girar_Espiral_Arriba

      PersianasH_Abajo

      PersianasH_Arriba

      PersianasV_Derecha

      PersianasV_Izquierda

      Reloj

      Reloj_AntiHorario

      Rodar_DAbajo

      Rodar_DArriba

      Rodar_IAbajo

      Rodar_IArriba

      Rueda_2Ejes

      Rueda_3Ejes

      Rueda_4Ejes

      Rueda_8Ejes

      Simetrico_Adentro

      Simetrico_Afuera

      Simetrico_Derecha

      Simetrico_Izquierda

   End Enum

 

#End Region

 

#Region " Miembros Privados "

   'Velocidad a la que se dibujará el efecto

   Private _Velocidad As Integer = 20

   'Contiene el efecto actual

   Private _Efecto As Efectos = Efectos.Abanico_Derecha

   'Control donde se mostrarán los efectos

   Private _Contenedor As Control

   'Propiedad de _Contenedor donde se colocará una imagen para realizar los efectos

   Private _Propiedad As Reflection.PropertyInfo

   'Color que se usará para rellenar el fondo de la imagen

   Private _Color As Color = Color.Transparent

   'Control Timer que controlará la ejecución de los efectos

   Private _Tiempo As Timer

   'Cuenta las veces que se debe ejecutar el evento Tick para cada efecto

   Private _Contador As Integer = 0

   'Contiene una copia de la imagén pasada a uno de los constructores para realizar los efectos

   Private _bmpTextura As Image

   'Contiene una copia vacia de _bmpTextura donde se dibujará el efecto

   Private _bmpDibujar As Image

   'Creamos un objeto _Graphics para dibujar en _bmpDibujar

   Private _Gr As Graphics

   'Creamos un TextureBrush con la textura de la imágen _bmpTextura

   Private _Brocha As TextureBrush

   'Contienen el Ancho y Alto de _bmpTextura respectivamente

   Private _AnchoImagen, _AltoImagen As Single

   'Indica si actualmente hay un efecto ejecutandose

   Private blnEfectoEjecutandose As Boolean = False

   Private disposed As Boolean = False

 

#Region " Variables especificas de algunos efectos "

 

   'Ángulo de giro

   Private _AnguloGiro As Single

   'Cantidad en que se aumentan las transformaciones de ejes.

   Private _xAumentaPos, _yAumentaPos As Single

   '_Aumento progresivo de los dibujos

   Private _Aumento As Single

 

#End Region

 

#End Region

 

#Region " Métodos Publicos "

   'Constructor

   Public Sub New(ByVal RutaImagen As String, ByVal Contenedor As Control, ByVal strPropiedad As String, Optional ByVal intVelocidad As Integer = 20)

      Try

         _bmpTextura = Image.FromFile(RutaImagen)

         Iniciar(Contenedor, intVelocidad, strPropiedad)

      Catch ex As Exception When Not IO.Directory.Exists(RutaImagen)

         MessageBox.Show("La ruta de la imagen no es válida")

      Catch ex As Exception When _bmpTextura Is Nothing

         MessageBox.Show("La imagen no puede ser nula.")

      End Try

   End Sub

 

   'Constructor

   Public Sub New(ByVal Imagen As Image, ByVal Contenedor As Control, ByVal strPropiedad As String, Optional ByVal intVelocidad As Integer = 20)

      Try

         _bmpTextura = Imagen

         Iniciar(Contenedor, intVelocidad, strPropiedad)

      Catch ex As Exception When _bmpTextura Is Nothing

         MessageBox.Show("La imagen no puede ser nula.")

      End Try

   End Sub

 

   'Ejecuta el efecto en la propiedad "EfectosTrans.EfectoActual"

   Public Overloads Sub Start()

      If Not disposed Then

         'Iniciamos los objetos de dibujo y el Timer

         EstableceObjetos()

      Else

         Throw New ObjectDisposedException("", "Esta instancia de la clase EfectosTrans ha sido desechada previamente y por lo tanto, ya no es accesible.")

      End If

   End Sub

 

   'Ejecuta el efecto pasado en eEfecto

   Public Overloads Sub Start(ByVal eEfecto As Efectos)

      If Not disposed Then

         _Efecto = eEfecto

         'Iniciamos los objetos de dibujo y el Timer

         EstableceObjetos()

      Else

         Throw New ObjectDisposedException("", "Esta instancia de la clase EfectosTrans ha sido desechada previamente y por lo tanto, ya no es accesible.")

      End If

   End Sub

 

#End Region

 

#Region " Propiedades "

 

   'EfectoActual

   <DefaultValue(GetType(Efectos), "Abanico_Derecha"), _

   Category("Comportamiento"), _

   Description("Establece el efecto actual para este objeto.")> _

   Public Property EfectoActual() As Efectos

      Get

         Return _Efecto

      End Get

      Set(ByVal Value As Efectos)

         'Si no se esta ejecutando un efecto en este momento cambiamos el efecto

         If Not Me.blnEfectoEjecutandose Then

            _Efecto = Value

         End If

      End Set

   End Property

 

   'VelocidadEfecto

   <DefaultValue(GetType(Integer), "20"), _

   Category("Comportamiento"), _

   Description("Establece la velocidad de dibujado para este objeto.")> _

   Public Property VelocidadEfecto() As Integer

      Get

         Return _Velocidad

      End Get

      Set(ByVal Value As Integer)

         _Velocidad = ValidaVelocidad(Value)

      End Set

   End Property

 

   'ColorTrans

   <DefaultValue(GetType(Color), "Transparent"), _

   Category("Apariencia"), _

   Description("Establece el color de relleno para este objeto.")> _

   Public Property ColorTrans() As Color

      Get

         Return _Color

      End Get

      Set(ByVal Value As Color)

         _Color = Value

      End Set

   End Property

 

   'Image

   <Category("Apariencia"), _

   Description("Establece la imagen que se usará para realizar el efecto seleccionado.")> _

   Public Property Image() As Image

      Get

         Return _bmpTextura

      End Get

      Set(ByVal Value As Image)

         Try

            _bmpTextura = Value

            _AnchoImagen = _bmpTextura.Width

            _AltoImagen = _bmpTextura.Height

            'Hay imagenes que tienen un formato de pixel

            'que no permite crear un objeto Graphics para dibujar sobre

            'ellas, por eso creamos una copia de la imagen, del mismo tamaño

            'y con un formato de pixel que no de problemas

            _bmpDibujar = New Bitmap(CInt(_AnchoImagen), CInt(_AltoImagen), PixelFormat.Format32bppArgb)

         Catch ex As Exception When Value Is Nothing

            Throw New NullReferenceException("La imagen no puede ser nula.")

         End Try

      End Set

   End Property

 

#End Region

 

#Region " Destructores "

 

   Public Overloads Sub Dispose() Implements System.IDisposable.Dispose

      Dispose(True)

   End Sub

 

   Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)

      If Not Me.disposed Then

         If disposing Then

            'Acá, limpiamos recursos administrados de esta clase

            'En este caso no hay ningúno (que no haya sido descargado)

         End If

         'Acá, limpiamos recursos no administrados de esta clase

         If Not _bmpTextura Is Nothing Then

            _bmpTextura.Dispose()

         End If

         _bmpDibujar.Dispose()

         Me.disposed = True

      End If

   End Sub

 

   ' Este metodo Finalize se ejecutará (automaticamente) solamente

   ' si el metodo Dispose no es usado por el usuario.

   Protected Overrides Sub Finalize()

      'Limpiamos solamente los recursos no manejados pasando False

      Dispose(False)

   End Sub

 

#End Region

 

#Region " Métodos Privados "

 

   'Establecemos las variables de nivel de modulo

   Private Sub Iniciar(ByVal Contenedor As Control, ByVal intVelocidad As Integer, _

                       ByVal strPropiedad As String)

      _Contenedor = Contenedor

      _Velocidad = ValidaVelocidad(intVelocidad)

 

      _AnchoImagen = _bmpTextura.Width

      _AltoImagen = _bmpTextura.Height

      'Hay imagenes que tienen un formato de pixel

      'que no permite crear un objeto Graphics para dibujar sobre

      'ellas, por eso creamos una copia de la imagen, del mismo tamaño

      'y con un formato de pixel que no de problemas

      _bmpDibujar = New Bitmap(CInt(_AnchoImagen), CInt(_AltoImagen), PixelFormat.Format32bppArgb)

 

      'Obtenemos y utilizamos la propiedad de tipo Image a partir de strPropiedad

      ObtenerPropiedad(strPropiedad)

      _Tiempo = New Timer

      _Tiempo.Interval = 70

      'Controlador del evento Tick del objeto Timer

      AddHandler _Tiempo.Tick, AddressOf TiempoTick

   End Sub

 

   'El siguiente código me fue proporcionado por Eduardo A. Morcillo [MS MVP VB]

   Private Sub ObtenerPropiedad(ByVal strPropiedad As String)

      ' Obtengo el tipo de control

      Dim t As Type = _Contenedor.GetType

      ' Obtengo la propiedad

      _Propiedad = t.GetProperty(strPropiedad)

      ' Verifico que se devolvio la propiedad

      If _Propiedad Is Nothing Then

         Throw New ArgumentException("El control no posee la propiedad")

      End If

      ' Verifico que la propiedad devuelva

      ' el tipo Image o una subclase de el

      If Not GetType(Drawing.Image).IsAssignableFrom(_Propiedad.PropertyType) Then

         Throw New ArgumentException("La propiedad no es de tipo Image")

      End If

   End Sub

 

   '(Re)Establece objetos y variables globales a su estado original antes de ejecutar un efecto

   Private Sub EstableceObjetos()

      Try

         'Si el efecto anterior todabia no terminó de ejecutarse salimos

         If blnEfectoEjecutandose Then Exit Sub

         'Iniciamos el objeto Graphics

         _Gr = Graphics.FromImage(_bmpDibujar)

 

         Select Case _Efecto

            Case Efectos.Aparecer, Efectos.Estirar_Centro To Efectos.Girar_Espiral_Arriba, Efectos.Rodar_DAbajo, Efectos.Rodar_DArriba

               'Para estos efectos no se necesita el objeto _Brocha (se usa DrawImage)

               'En el caso de 'Rodar_DArriba' y 'Rodar_DAbajo',

               'el código de estos efectos inicializa el objeto

            Case Else

               'Iniciamos el objeto TextureBrush

               IniTextureBrush()

         End Select

 

         '******* Aquí se modifica el contenedor de la imagen *******

         'Asignamos _bmpDibujar a la propiedad del Contenedor de la imagen

         _Propiedad.SetValue(_Contenedor, _bmpDibujar, Nothing)

         '******* Aquí se modifica el contenedor de la imagen *******

 

         'Limpiamos el contenido de _bmpDibujar

         _Gr.Clear(_Color)

         'Reiniciamos variables

         _Contador = 0

         _Aumento = 0

         _AnguloGiro = 0

         _xAumentaPos = 0

         _yAumentaPos = 0

         'True paar indicar que el efecto se va a iniciar

         blnEfectoEjecutandose = True

         'Iniciamos el objeto Timer

         _Tiempo.Start()

      Catch ex As Exception When _bmpDibujar Is Nothing

         Throw New NullReferenceException("No se ha establecido la imagen donde se dibujará el efecto")

      End Try

   End Sub

 

   'Inicializa el objeto TextureBrush

   Private Sub IniTextureBrush()

      _Brocha = New TextureBrush(_bmpTextura)

   End Sub

 

   'Controlador del evento Tick del control _Timer

   Private Sub TiempoTick(ByVal sender As System.Object, ByVal e As System.EventArgs)

      Try

         'Ejecutamos cada 80 milesimas de segundo, el efecto selecionado

         Select Case _Efecto

            Case Efectos.Abanico_Derecha, Efectos.Abanico_Izquierda

               Abanico(_Efecto)

            Case Efectos.Aparecer

               Aparecer()

            Case Efectos.Barras_Horizontales, Efectos.Barras_Verticales

               Barras(_Efecto)

            Case Efectos.Barrido_Horizontal, Efectos.Barrido_Vertical

               Barrido(_Efecto)

            Case Efectos.Circulos_Fuera

               Circulos_Fuera()

            Case Efectos.Circulos_Dentro

               Circulos_Dentro()

            Case Efectos.Desplegar_Centro To Efectos.Desplegar_SI, Efectos.Estirar_Centro To Efectos.Estirar_SI

               Desplegar_Estirar(_Efecto)

            Case Efectos.Diagonal

               Diagonal()

            Case Efectos.DivisiónH_Entrante, Efectos.DivisiónH_Saliente

               DivisionH(_Efecto)

            Case Efectos.DivisiónV_Entrante, Efectos.DivisiónV_Saliente

               DivisionV(_Efecto)

            Case Efectos.Empujar_Abajo To Efectos.Empujar_Izquierda

               Empujar(_Efecto)

            Case Efectos.Girar_Centro To Efectos.Girar_Espiral_Arriba

               Girar(_Efecto)

            Case Efectos.PersianasH_Abajo, Efectos.PersianasH_Arriba

               PersianasH(_Efecto)

            Case Efectos.PersianasV_Derecha, Efectos.PersianasV_Izquierda

               PersianasV(_Efecto)

            Case Efectos.Reloj, Efectos.Reloj_AntiHorario

               Reloj(_Efecto)

            Case Efectos.Simetrico_Adentro To Efectos.Simetrico_Izquierda

               Simetrico(_Efecto)

            Case Efectos.Rueda_2Ejes To Efectos.Rueda_8Ejes

               RuedaMultiple(_Efecto)

            Case Efectos.Empuja_División_Lados, Efectos.Empuja_División_Topes

               EmpujaDivision(_Efecto)

            Case Efectos.Rodar_DAbajo To Efectos.Rodar_IArriba

               Rodar(_Efecto)

         End Select

      Catch ex As System.Exception

         Throw New System.Exception(ex.ToString)

      End Try

   End Sub

 

   'Descarga objetos y detiene el Timer

   Private Sub DescargaObjetos()

      _Gr.Dispose()

      If Not (_Brocha Is Nothing) Then

         _Brocha.Dispose()

      End If

      _Tiempo.Stop()

      'False para indicar que el efecto acaba de terminar

      blnEfectoEjecutandose = False

      'El efecto terminó, disparamos un evento para indicarlo

      RaiseEvent EffectEnded(Me, New EventArgs)

   End Sub

 

   'Valida la propiedad VelocidadEfecto

   Private Function ValidaVelocidad(ByVal intValor As Integer) As Integer

      If intValor <= 0 Then

         Return _Velocidad ' Valor por defecto (20)

      ElseIf intValor > 100 Then

         Return 100

      Else : Return intValor

      End If

   End Function

 

#Region " Efectos de Transición "

 

   'Genera los efecto de abanico

   Private Sub Abanico(ByVal Efecto As Efectos)

      'Calculamos por Pitagoras el radio de la circunferencia con centro en (_AnchoImagen / 2, _AltoImagen)

      'que dibujará la imagen.

      Dim Radio As Single = CSng(Math.Pow(((_AnchoImagen / 2) ^ 2) + (_AltoImagen ^ 2), 1 / 2))

      'Negativo: dirección del efecto

      Dim AnguloInicio, Negativo As Integer

 

      'iniciamos variables según el efecto

      If Efecto = Efectos.Abanico_Derecha Then

         AnguloInicio = 180