Duda con PictureBox y Saturación de Color

Iniciado por FJDA, 16 Septiembre 2016, 16:45 PM

0 Miembros y 1 Visitante están viendo este tema.

FJDA

Buenas amigos,  estoy intentando saturar una imagen y lo he conseguido pero el programa tarda mucho en procesar los cambios. Estoy orgulloso de lo que he conseguido porque no ha sido fácil averiguarlo, pero no va bien. He usado los miembro ColorMatrix y SetColorMatrix y he conseguido crear una matriz que realiza la saturación de una imagen, pero al usar un TrackBar el proceso de los cambios es demasiado lento.

    S = (TrackBar1.Value) + 100

       Dim Rojo As Single = CSng(299 * (100 - S) / 100000)
       Dim Verde As Single = CSng(587 * (100 - S) / 100000)
       Dim Azul As Single = CSng(114 * (100 - S) / 100000)

       S = CSng(S / 100)
       Dim colorMatrixVal As Single()() = {
       New Single() {Rojo + S, Rojo, Rojo, 0, 0}, _
       New Single() {Verde, Verde + S, Verde, 0, 0}, _
       New Single() {Azul, Azul, Azul + S, 0, 0}, _
       New Single() {0, 0, 0, 1, 0}, _
       New Single() {0, 0, 0, 0, 1}}



Este lo he hecho como ejemplo para ponerlo aquí:
Código (vbnet) [Seleccionar]

Imports System.IO
Imports System.Drawing.Imaging



Public Class Form1
   Private originalBitmap As Bitmap = Nothing
   Private previewBitmap As Bitmap = Nothing
   Private resultBitmap As Bitmap = Nothing
   Dim PictureBox1 As New PictureBox With {.Location = New Point(10, 10),
                                           .Size = New Size(400, 240),
                                           .SizeMode = PictureBoxSizeMode.StretchImage,
                                           .BackColor = Color.DarkGray}
   Dim Button1 As New Button With {.Location = New Point(PictureBox1.Width + 20, 10), .Text = "Cargar"}
   Dim Button2 As New Button With {.Location = New Point(PictureBox1.Width + 20, 40), .Text = "Guardar"}
   Dim TrackBar1 As New TrackBar With {.Location = New Point(10, PictureBox1.Height + 20),
                                       .AutoSize = True,
                                       .Size = New Size(PictureBox1.Width, 10),
                                       .TickStyle = TickStyle.TopLeft,
                                       .BackColor = Color.DarkGray,
                                       .TickFrequency = 50,
                                       .Maximum = 100,
                                       .Minimum = -100,
                                       .Value = 0,
                                       .Orientation = Orientation.Horizontal}
   Dim Label1 As New Label With {.Location = New Point(TrackBar1.Width + 20, TrackBar1.Top + 10),
                                 .AutoSize = True,
                                 .Text = "0",
                                .Font = New Font("Arial", 20, FontStyle.Bold)}

   Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
       Me.Controls.Add(PictureBox1)
       Me.Controls.Add(Button1)
       Me.Controls.Add(Button2)
       Me.Controls.Add(TrackBar1)
       Me.Controls.Add(Label1)

       Label1.BringToFront()
       Me.Size = New Size(520, 400)

       AddHandler TrackBar1.ValueChanged, AddressOf TrackBar1_ValueChanged
       AddHandler Button1.Click, AddressOf Button1_Click
       AddHandler Button2.Click, AddressOf Button2_Click
   End Sub

   Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs)

       Label1.Text = TrackBar1.Value.ToString()
       AplicarSaturacion(True)
   End Sub


   Private Sub Button1_Click(sender As Object, e As EventArgs)
       Dim ofd As New OpenFileDialog()
       ofd.Title = "Abrir imagen"
       ofd.Filter = "Jpeg Images(*.jpg)|*.jpg|Png Images(*.png)|*.png"
       ofd.Filter += "|Bitmap Images(*.bmp)|*.bmp"

       If ofd.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
           Dim streamReader As New StreamReader(ofd.FileName)
           originalBitmap = DirectCast(Bitmap.FromStream(streamReader.BaseStream), Bitmap)
           streamReader.Close()
           previewBitmap = Image.FromFile(ofd.FileName)
           PictureBox1.Image = previewBitmap

           AplicarSaturacion(True)
       End If
   End Sub

   Private Sub Button2_Click(sender As Object, e As EventArgs)
       AplicarSaturacion(False)


       If resultBitmap IsNot Nothing Then
           Dim sfd As New SaveFileDialog()
           sfd.Title = "Guardar imagen"
           sfd.Filter = "Jpeg Images(*.jpg)|*.jpg|Png Images(*.png)|*.png"
           sfd.Filter += "|Bitmap Images(*.bmp)|*.bmp"

           If sfd.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
               Dim fileExtension As String = Path.GetExtension(sfd.FileName).ToUpper()
               Dim imgFormat As ImageFormat = ImageFormat.Png

               If fileExtension = "BMP" Then
                   imgFormat = ImageFormat.Bmp
               ElseIf fileExtension = "JPG" Then
                   imgFormat = ImageFormat.Jpeg
               End If

               Dim streamWriter As New StreamWriter(sfd.FileName, False)
               resultBitmap.Save(streamWriter.BaseStream, imgFormat)
               streamWriter.Flush()
               streamWriter.Close()

               resultBitmap = Nothing
           End If
       End If
   End Sub
   Public Sub AplicarSaturacion(Byval  preview As Boolean)
       If previewBitmap Is Nothing Then
           Return
       End If
       Dim Imagen As Image = previewBitmap
       Dim Pic As PictureBox = PictureBox1
       Dim Grafico As Graphics
       Dim Rectangulo As Rectangle
       Pic.Image = New Bitmap(Pic.Width, Pic.Height, PixelFormat.Format32bppArgb)
       Grafico = Graphics.FromImage(Pic.Image)
       Rectangulo = New Rectangle(0, 0, Pic.Width, Pic.Height)
       Grafico.DrawImage(Imagen, Rectangulo)
       Dim S As Single

       S = (TrackBar1.Value) + 100

       Dim Rojo As Single = CSng(299 * (100 - S) / 100000)
       Dim Verde As Single = CSng(587 * (100 - S) / 100000)
       Dim Azul As Single = CSng(114 * (100 - S) / 100000)

       S = CSng(S / 100)
       Dim colorMatrixVal As Single()() = {
       New Single() {Rojo + S, Rojo, Rojo, 0, 0}, _
       New Single() {Verde, Verde + S, Verde, 0, 0}, _
       New Single() {Azul, Azul, Azul + S, 0, 0}, _
       New Single() {0, 0, 0, 1, 0}, _
       New Single() {0, 0, 0, 0, 1}}

       Dim colorMatrix As New ColorMatrix(colorMatrixVal)
       Dim ImagenAtributos As New ImageAttributes

       ImagenAtributos.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap)
       Grafico.DrawImage(Imagen, Rectangulo, 0, 0, Imagen.Width, Imagen.Height, GraphicsUnit.Pixel, ImagenAtributos)
       Pic.Refresh()
       resultBitmap = Pic.Image
   End Sub
End Class


Va bien  pero al deslizar el TrackBar se producen trompicones al moverlo.


He conseguido convertir por mi cuenta un código de VB viejo a NET que usa API. Que no sé, seré el único que lo he hecho porque puse en google -SetColorAdjustment VB.NET-  y ni rastro, las declaraciones que encontraba eran como en VB6.

Conseguí crear un código basado en API con SetColorAdjustment y GetColorAdjustment , y funciona bien. Los cambios son suaves pero tiene un problema y es que no puedo guardar los cambios y no consigo refrescar el PictureBox correctamente.

Este es el código:
Código (vbnet) [Seleccionar]

Imports System.IO
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports WindowsApplication1.NativeMethods



Public Class Form1
   Private originalBitmap As Bitmap = Nothing
   Private previewBitmap As Bitmap = Nothing
   Private resultBitmap As Bitmap = Nothing
   Dim picLoad As Boolean
   Dim PictureBox1 As New PictureBox With {.Location = New Point(10, 10),
                                           .Size = New Size(400, 240),
                                           .SizeMode = PictureBoxSizeMode.StretchImage,
                                           .BackColor = Color.DarkGray}
   Dim Button1 As New Button With {.Location = New Point(PictureBox1.Width + 20, 10), .Text = "Cargar"}
   Dim Button2 As New Button With {.Location = New Point(PictureBox1.Width + 20, 40), .Text = "Guardar"}
   Dim TrackBar1 As New TrackBar With {.Location = New Point(10, PictureBox1.Height + 20),
                                       .AutoSize = True,
                                       .Size = New Size(PictureBox1.Width, 10),
                                       .TickStyle = TickStyle.TopLeft,
                                       .BackColor = Color.DarkGray,
                                       .TickFrequency = 50,
                                       .Maximum = 100,
                                       .Minimum = -100,
                                       .Value = 0,
                                       .Orientation = Orientation.Horizontal}
   Dim Label1 As New Label With {.Location = New Point(TrackBar1.Width + 20, TrackBar1.Top + 10),
                                 .AutoSize = True,
                                 .Text = "0",
                                .Font = New Font("Arial", 20, FontStyle.Bold)}

   Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
       Me.Controls.Add(PictureBox1)
       Me.Controls.Add(Button1)
       Me.Controls.Add(Button2)
       Me.Controls.Add(TrackBar1)
       Me.Controls.Add(Label1)

       Label1.BringToFront()
       Me.Size = New Size(520, 400)

       AddHandler TrackBar1.ValueChanged, AddressOf TrackBar1_ValueChanged
       AddHandler Button1.Click, AddressOf Button1_Click
       AddHandler Button2.Click, AddressOf Button2_Click
       AddHandler PictureBox1.Paint, AddressOf PictureBox1_Paint
   End Sub
   Public Sub PictureBox1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)
       If picLoad = True Then
           AplicarSaturacion(True)
       End If

   End Sub
   Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs)
       Label1.Text = TrackBar1.Value.ToString()
       PictureBox1.Refresh()
       PictureBox1_Paint(1, Nothing)
   End Sub


   Private Sub Button1_Click(sender As Object, e As EventArgs)
       Dim ofd As New OpenFileDialog()
       ofd.Title = "Guardar imagen"
       ofd.Filter = "Jpeg Images(*.jpg)|*.jpg|Png Images(*.png)|*.png"
       ofd.Filter += "|Bitmap Images(*.bmp)|*.bmp"

       If ofd.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
           Dim streamReader As New StreamReader(ofd.FileName)
           originalBitmap = DirectCast(Bitmap.FromStream(streamReader.BaseStream), Bitmap)
           streamReader.Close()
           previewBitmap = Image.FromFile(ofd.FileName)
           PictureBox1.Image = previewBitmap
           picLoad = True
           AplicarSaturacion(True)
       End If
   End Sub

   Private Sub Button2_Click(sender As Object, e As EventArgs)
       AplicarSaturacion(False)


       If resultBitmap IsNot Nothing Then
           Dim sfd As New SaveFileDialog()
           sfd.Title = "Abrir Imagen"
           sfd.Filter = "Jpeg Images(*.jpg)|*.jpg|Png Images(*.png)|*.png"
           sfd.Filter += "|Bitmap Images(*.bmp)|*.bmp"

           If sfd.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
               Dim fileExtension As String = Path.GetExtension(sfd.FileName).ToUpper()
               Dim imgFormat As ImageFormat = ImageFormat.Png

               If fileExtension = "BMP" Then
                   imgFormat = ImageFormat.Bmp
               ElseIf fileExtension = "JPG" Then
                   imgFormat = ImageFormat.Jpeg
               End If

               Dim streamWriter As New StreamWriter(sfd.FileName, False)
               resultBitmap.Save(streamWriter.BaseStream, imgFormat)
               streamWriter.Flush()
               streamWriter.Close()

               resultBitmap = Nothing
           End If
       End If
   End Sub
   Public Sub AplicarSaturacion(ByVal preview As Boolean)


       Dim ca As COLORADJUSTMENT
       ca.caSize = CType(Marshal.SizeOf(ca), Short)
       Dim HDcPic As IntPtr = CType(PictureBox1.CreateGraphics.GetHdc, IntPtr) 'GetDC(PictureBox1.Handle)


       SetStretchBltMode(HDcPic, HALFTONE)
       GetColorAdjustment(HDcPic, ca)
       ca.caColorfulness = TrackBar1.Value
       SetColorAdjustment(HDcPic, ca)

       StretchBlt(HDcPic, 0, 0, PictureBox1.Image.Width, PictureBox1.Image.Height, _
       HDcPic, 0, 0, PictureBox1.Image.Width, PictureBox1.Image.Height, TernaryRasterOperations.SRCCOPY)

       resultBitmap = PictureBox1.Image

   End Sub




NativeMethods > GetColorAdjustment y SetColorAdjustment  para VB.NET

Código (vbnet) [Seleccionar]

<Security.SuppressUnmanagedCodeSecurity>
Friend Class NativeMethods
   Inherits Attribute
   Private Sub New()
   End Sub
   <DllImport("gdi32.dll")> _
   Public Shared Function BitBlt(hObject As IntPtr,
                                 nXDest As Integer,
                                 nYDest As Integer,
                                 nWidth As Integer,
                                 nHeight As Integer,
                                 hObjSource As IntPtr,
                                 nXSrc As Integer,
                                 nYSrc As Integer,
                                 dwRop As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function
   <DllImport("gdi32.dll")>
   Shared Function SetColorAdjustment(hdc As IntPtr,
                                      <MarshalAs(UnmanagedType.Struct)>
                                      ByRef lpca As COLORADJUSTMENT) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("gdi32.dll")>
   Shared Function GetColorAdjustment(ByVal hdc As IntPtr,
                                      <MarshalAs(UnmanagedType.Struct)>
                                      ByRef lpca As COLORADJUSTMENT) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("user32.dll")>
   Shared Function GetDC(ByVal hWnd As IntPtr) As IntPtr
   End Function

   <DllImport("gdi32.dll")> _
   Public Shared Function StretchBlt(hdc As IntPtr, _
                                     x As Integer,
                                    y As Integer,
                                    nHeight As Integer,
                                    hSrcDC As Integer,
                                    hObjSource As IntPtr, _
                                   xSrc As Integer,
                                   ySrc As Integer,
                                   nSrcWidth As Integer,
                                   nSrcHeight As Integer,
                                   dwRop As TernaryRasterOperations) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("gdi32.dll")> _
   Public Shared Function SetStretchBltMode(ByVal hObject As IntPtr,
                                            ByVal nStretchMode As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <StructLayout(LayoutKind.Sequential)>
   Public Structure COLORADJUSTMENT
       Public caSize As Short
       Public caFlags As Short
       Public caIlluminantIndex As Short
       Public caRedGamma As Short
       Public caGreenGamma As Short
       Public caBlueGamma As Short
       Public caReferenceBlack As Short
       Public caReferenceWhite As Short
       Public caContrast As Short
       Public caBrightness As Short
       Public caColorfulness As Short
       Public caRedGreenTint As Short
   End Structure

  Public Enum caIlluminantIndex
       ILLUMINANT_DEVICE_DEFAULT = 0 'Device's default. Standard used by output devices.
       ILLUMINANT_A = 1 'Tungsten lamp.
       ILLUMINANT_B = 2 'Noon sunlight.
       ILLUMINANT_C = 3 'NTSC daylight.
       ILLUMINANT_D50 = 4 'Normal print.
       ILLUMINANT_D55 = 5 'Bond paper print.
       ILLUMINANT_D65 = 6 'Standard daylight. Standard for CRTs and pictures.
       ILLUMINANT_D75 = 7 'Northern daylight.
       ILLUMINANT_F2 = 8 'Cool white lamp.
       ILLUMINANT_DAYLIGHT = ILLUMINANT_C 'Same as ILLUMINANT_C.
       ILLUMINANT_FLUORESCENT = ILLUMINANT_F2 'Same as ILLUMINANT_F2.
       ILLUMINANT_MAX_INDEX = ILLUMINANT_F2 'Same as ILLUMINANT_F2.
       ILLUMINANT_NTSC = ILLUMINANT_C 'Same as ILLUMINANT_C.
       ILLUMINANT_TUNGSTEN = ILLUMINANT_A 'Same as ILLUMINANT_A.
   End Enum


   Public Enum TernaryRasterOperations
       SRCCOPY = &HCC0020 ' dest = source
       SRCPAINT = &HEE0086 ' dest = source OR dest
       SRCAND = &H8800C6 ' dest = source AND dest
       SRCINVERT = &H660046 ' dest = source XOR dest
       SRCERASE = &H440328 ' dest = source AND (NOT dest)
       NOTSRCCOPY = &H330008 ' dest = (NOT source)
       NOTSRCERASE = &H1100A6 ' dest = (NOT src) AND (NOT dest)
       MERGECOPY = &HC000CA ' dest = (source AND pattern)
       MERGEPAINT = &HBB0226 ' dest = (NOT source) OR dest
       PATCOPY = &HF00021 ' dest = pattern
       PATPAINT = &HFB0A09 ' dest = DPSnoo
       PATINVERT = &H5A0049 ' dest = pattern XOR dest
       DSTINVERT = &H550009 ' dest = (NOT dest)
       BLACKNESS = &H42 ' dest = BLACK
       WHITENESS = &HFF0062 ' dest = WHITE
   End Enum
   Public Const HALFTONE = 4

   <DllImport("gdi32.dll")> _
   Public Shared Function SetBkColor(hdc As IntPtr, crColor As Integer) As Integer
   End Function

End Class



Se ve muy largo el código pero tenía que ponerlo para que puedan analizar.

Espero que me puedan ayudar. No puedo darles nada solo mi agradecimiento. gracias




He conseguido redibujar correctamente el PictureBox en la versión para API metiendo la saturación en el evento PAINT, pero sigo sin poder guardar los cambios

Añadí la configuración al código anterior.






Eleкtro

lo estás complicando todo demasiado.

en un rato despues de cenar editaré este mensaje para mostrarte una solución completa y funcional (sin ningún tipo de "retraso" o "bloqueo") e indicarte (algunos de) los fallos del código actual que tienes.

hasta ahora!








FJDA

#2
Cita de: Eleкtro en 16 Septiembre 2016, 21:01 PM
lo estás complicando todo demasiado.

en un rato despues de cenar editaré este mensaje para mostrarte una solución completa y funcional (sin ningún tipo de "retraso" o "bloqueo") e indicarte (algunos de) los fallos del código actual que tienes.

hasta ahora!

Cambie el COLORADJUSTMENT todo a Short. Porque  hace una hora edité mi mensaje y cambié caBlueGamma, caRedGamma, caGreenGamma, a Integer por que ví que como Short BlueGamma no funcionaba, pero si hago esto no funcionan los demás ajustes.  :-\

Intenté usar  funciones que PictureBox tiene en C# que simplifican mucho todo pero no están disponibles en VB

El código del principio lo saqué de aquí, solo cambié la matriz para conseguir el efecto de saturación
https://onedrive.live.com/?authkey=%21AGLMrn20g0JGtX0&id=C9DBA2BC5A16373B%21115&cid=C9DBA2BC5A16373B

Son códigos de muestra, solo necesito saber que falla, gracias. No hace falta que modifique todo, no quisiera dar demasiado trabajo, pero vi en otras preguntas que se quejan que no ponen código.

el 80% del código es sólo para que lo puedan ejecutar, sin tener que crear nada.

Eleкtro

#3
Bueno, como te prometí, vamos al tema...

Desconozco cual es el código original que estarás usando, pero el código de ejemplo que has compartido tiene varios problemas, e imagino que los mismos problemas estarán replicados en el código original...

Por orden de importancia:




Cita de: FJDA en 16 Septiembre 2016, 16:45 PMestoy intentando saturar una imagen
...
el programa tarda mucho en procesar los cambios.
...
no va bien.
...
al usar un TrackBar el proceso de los cambios es demasiado lento.

El problema principal (y el más grave) por el cual se aprecia una elevada lentitud de respuesta al mover el TrackBar, es por que estás manipulando la imagen a tamaño real cada vez que mueves la posición del TrackBar, y eso causa un gran impacto negativo en el rendimiento de la aplicación.

Si tenemos un PictureBox donde quieres previsualizar la imagen, entonces lo primero que debemos hacer para evitar problemas de rendimiento es redimensaionar la imagen original al tamaño de ese PictureBox, eso lo puedes hacer "manualmente" creando un objeto Bitmap del tamaño deseando y dibujando la imagen, o simplemente llamando a la función Image.GetThumbnailImage(), y entonces, manipular la imagen redimensionada.




Cita de: FJDA en 16 Septiembre 2016, 16:45 PMDim Grafico As Graphics
...
Grafico.DrawImage(...)

El segundo problema es que estás utilizando los parámetros por defecto de calidad de imagen al dibujar la imagen modificada y, al tratarse de una previsualización, logicamente deberiamos personalizar esos parámetros para crear una imagen de baja calidad, es decir, más rápida de procesar.

Los parámetros (o propiedades) que debes ajustar son las siguientes:
  • Graphics.CompositingQuality = CompositingQuality.HighSpeed
  • Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
  • Graphics.PixelOffsetMode = PixelOffsetMode.None
  • Graphics.SmoothingMode = SmoothingMode.None




Cita de: FJDA en 16 Septiembre 2016, 16:45 PMDim PictureBox1 As New PictureBox With {...
                                       .SizeMode = PictureBoxSizeMode.StretchImage,
                                       ...}

Si a todo lo anterior le sumamos que cada vez que asignas la imagen modificada a tamaño real, ésta debe ser vuelta a modificar para ser estrechada, esto logicamente disminuirá todavía más el rendimiento ...de hecho el estrechamiento de cualquier imagen es expensivo de por si.

La solución es sencilla y obvia, debemos restaurar el valor por defecto (PictureBoxSizeMode.Normal) para no forzar otra redimensión adicional de la imagen, y en su lugar utilizar una imagen del tamaño del PictureBox, como ya expliqué más arriba.




El último problema (pero no menos importante), es que todo el procedimiento o algoritmo del código que has publicado se lleva a cabo de forma sincrónica, y esto supone que cada vez que mueves el TrackBar, el hilo de la interfáz de usuario debe esperar a que la imagen se procese y la modificación de imagen se genere para poder procesar el siguiente evento de "mover" del TrackBar, y esto se aprecia en forma de desagradables congelamientos de respuesta o "trompicones" al intentar mover el TrackBar demasiado rápido, puesto que se necesita mucho más tiempo para procesar todo lo que haces con esa imagen a tamaño real, que para mover el TrackBar.

Tanto en C#/VB.NET como en cualquier otro lenguaje, a mayor cantidad de datos que procesar (y una imagen cotidiana contiene muchos datos que procesar) necesitará mayor tiempo de procesado, y hay que esperar ese tiempo, es así de simple. Dicho de otro modo: estás saturando el hilo de la interfáz de usuario.




También cabe mencionar que no estás haciendo un buen uso, o mejor dicho directamente no haces ningún uso de la correcta administración del Garbage Collector para ayudarle a recolectar los recursos de los objetos que ya no sean necesarios por tu aplicación.

Debes asegurarte siempre, sin excepción, de liberar los objetos que sean deshechables llamando al método Object.Dispose(), incluso con los objetos que se liberen automáticamente al liberar un Form, no pierdas la costumbre.

Esto no afecta en el rendimiento de la interfáz de usuario (al menos no en el código de ejemplo que has publicado), pero si que afectará al consumo de memoria, y mucho. Literalmente hablando hay una fuga muy grave de memoria en el código que has publicado, basta con cargar una imagen de varios megabytes (+/-10 mb) y mover la posición del trackbar un par de veces para ver como el espacio reservado va subiendo, y subiendo, y subiendo... y nunca baja, puesto que nunca liberas los recursos no-administrados (Win32) de las imágenes llamando al método Dispose.




Cita de: FJDA en 16 Septiembre 2016, 21:06 PMIntenté usar funciones que PictureBox tiene en C# que simplifican mucho todo pero no están disponibles en VB

No se a que te estarás refiriendo pero eso no es así.

C# y VB.NET son dos lenguajes que trabajan bajo el mismo framework de Microsoft, por ende, ambos pueden utilizar todas las funciones que estén definidas en la librería de clases de .NET Framework. Ambos lenguajes son idénticos en esencia, excepto algunas peculiaridades muy concretas de cada lenguaje.








Eleкtro

#4
Para resumir brevemente todas las explicaciones que di en el post de arriba, lo que debes hacer para solucionar este tipo de problemas es:
1. Manipular una imagen en miniatura de la imagen a tamaño real.
2. Ajustar los parámetros de calidad de GDI+ al manipular la imagen, para reducir la calidad del resultado (solo en la previsualización, claro).
3. Utilizar técnicas de multi-threading para evitar bloquear el thread de la UI con instrucciones o metodologías expensivas que resulten en congelamientos o "trompicones" al manejar la UI.

Y... en lo último que debes pensar al manipular gráficos en .NET es en recurrir a la API de Windows, los gráficos 2D/3D es una temática que requiere mucha experiencia profesional, no solo de gráficos en general, sino de los tipos de datos que maneja Windows (y la forma especial de utilizar cada uno y de liberar los recursos de cada uno), así que por muy cuidadoso que intentes ser siempre te vas a dejar alguna imperfección por el camino, y una imperfección utilizando código no administrado puede traernos serios problemas y ser muy dificil de localizar la raíz del problema, además, con la inmensa cantidad de miembros administrados que de por si ya expone la libreria de clases de .NET Framework para la manipulación de gráficos e imágenes, es que simplemente no es necesario usar la WinAPI.




He escrito el siguiente código para mostrarte como puedes corregir esos problemas, pero recuerda que esto también es solo un código de ejemplo (no me he esmerado como lo haría para un trabajo).
La matriz de color no la he cambiado, he dejado la misma que estabas usando tú.

Código (vbnet) [Seleccionar]
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging

Public NotInheritable Class Form1 : Inherits Form

   Friend WithEvents PcbPreview As PictureBox
   Friend WithEvents BtLoad As Button
   Friend WithEvents BtSave As Button
   Friend WithEvents TrckHue As TrackBar
   Friend WithEvents LblHue As Label

   Private srcImg As Image
   Private thumbnail As Image

   Public Sub New()

       MyClass.InitializeComponent()

       Me.Size = New Size(520, 400)
       Me.DoubleBuffered = True

       Me.PcbPreview = New PictureBox With {
           .Location = New Point(10, 10),
           .Size = New Size(400, 240),
           .SizeMode = PictureBoxSizeMode.Normal,
           .BackColor = Color.DarkGray
       }

       Me.BtLoad = New Button With {
           .Location = New Point(Me.PcbPreview.Width + 20, 10),
           .Text = "Load from..."
       }

       Me.BtSave = New Button With {
           .Location = New Point(Me.PcbPreview.Width + 20, 40),
           .Text = "Save to..."
       }

       Me.TrckHue = New TrackBar With {
           .Location = New Point(10, Me.PcbPreview.Height + 20),
           .AutoSize = True,
           .Size = New Size(Me.PcbPreview.Width, 10),
           .TickStyle = TickStyle.TopLeft,
           .BackColor = Color.DarkGray,
           .TickFrequency = 50,
           .Maximum = 100,
           .Minimum = -100,
           .Value = 0,
           .Orientation = Orientation.Horizontal
       }

       Me.LblHue = New Label With {
           .Location = New Point(Me.TrckHue.Width + 20, Me.TrckHue.Top + 10),
           .AutoSize = True,
           .Text = "0",
           .Font = New Font("Arial", 20, FontStyle.Bold)
       }
       Me.LblHue.BringToFront()

   End Sub

   Private Sub Form1_Shown(ByVal sender As Object, ByVal e As EventArgs) _
   Handles Me.Shown

       Me.SuspendLayout()
       Me.Controls.AddRange({Me.PcbPreview, Me.BtLoad, Me.BtSave, Me.TrckHue, Me.LblHue})
       Me.ResumeLayout()

   End Sub

   Private Sub BtLoad_Click(ByVal sender As Object, ByVal e As EventArgs) _
   Handles BtLoad.Click

       Using ofd As New OpenFileDialog()
           ofd.Title = "Open image"
           ofd.Filter = "Image files|*.jpg;*.png;*.bmp"

           If (ofd.ShowDialog() = DialogResult.OK) Then
               If (Me.srcImg IsNot Nothing) Then
                   Me.srcImg.Dispose()
               End If
               Me.srcImg = Image.FromFile(ofd.FileName)

               If (Me.thumbnail IsNot Nothing) Then
                   Me.thumbnail.Dispose()
               End If
               Dim thumbSize As Size = Me.PcbPreview.Size
               Me.thumbnail = Me.srcImg.GetThumbnailImage(thumbSize.Width, thumbSize.Heigth, Nothing, Nothing)

               ' Force TrackBar.OnValueChanged event to trigger.
               Me.TrckHue.Value = 1
               Me.TrckHue.Value = 0

           End If
       End Using ' ofd

   End Sub

   Private Sub BtSave_Click(ByVal sender As Object, ByVal e As EventArgs) _
   Handles BtSave.Click


       Using sfd As New SaveFileDialog()
           sfd.Title = "Save image"
           sfd.Filter = "Bitmap image|*.bmp|Jpeg image|*.jpg|PNG image|*.png"
           sfd.AddExtension = True

           If (sfd.ShowDialog() = DialogResult.OK) Then
               Dim file As New FileInfo(sfd.FileName)
               Dim ext As String = file.Extension.ToLower()
               Dim format As ImageFormat

               Select Case ext
                   Case ".png"
                       format = ImageFormat.Png
                   Case ".bmp"
                       format = ImageFormat.Bmp
                   Case Else ' jpg
                       format = ImageFormat.Jpeg
               End Select

               Dim factor As Single = (Me.TrckHue.Value + 100)
               Using img As Image = Me.srcImg.SetImageSaturation(factor, highQuality:=True)
                   img.Save(file.FullName, format)
               End Using ' img

           End If
       End Using ' sfd

   End Sub

   Private Sub TrckHue_ValueChanged(ByVal sender As Object, ByVal e As EventArgs) _
   Handles TrckHue.ValueChanged

       Dim trck As TrackBar = DirectCast(sender, TrackBar)
       Dim value As Integer = trck.Value
       Dim factor As Single = (trck.Value + 100)

       Dim pcbUpdate As Action =
           Sub()
               Me.LblHue.Invoke(Sub()
                                    Me.LblHue.Text = CStr(value)
                                    Me.Update()
                                End Sub)
               Dim lockImg As Image = Me.thumbnail
               SyncLock lockImg
                   lockImg = lockImg.SetImageSaturation(factor, highQuality:=False)
                   Me.PcbPreview.Image = lockImg
               End SyncLock
           End Sub

       Task.Factory.StartNew(pcbUpdate)

   End Sub

End Class


Código (vbnet) [Seleccionar]
Public Module ImageExtensions

   <DebuggerStepThrough>
   <Extension>
   <EditorBrowsable(EditorBrowsableState.Always)>
   Public Function SetImageSaturation(ByVal srcImg As Image, ByVal factor As Single,
                                      ByVal highQuality As Boolean) As Image

       Dim r As Single = (299 * (100 - factor) / 100000.0F)
       Dim g As Single = (587 * (100 - factor) / 100000.0F)
       Dim b As Single = (114 * (100 - factor) / 100000.0F)

       factor = (factor / 100.0F)
       Dim bmp As New Bitmap(srcImg.Width, srcImg.Height, srcImg.PixelFormat)
       Dim matrix As Single()() = {
               New Single() {(r + factor), r, r, 0.0F, 0.0F},
               New Single() {g, (g + factor), g, 0.0F, 0.0F},
               New Single() {b, b, (b + factor), 0.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 1.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 0.0F, 1.0F}
       } ' Esta matriz de color la ideó el usuario @FJDA con el propósito de saturar una imagen.

       Using ia As New ImageAttributes
           ia.ClearColorMatrix()
           ia.SetColorMatrix(New ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap)
           ia.SetGamma(1.0F, ColorAdjustType.Bitmap)

           Using gdi As Graphics = Graphics.FromImage(bmp)
               With gdi
                   If highQuality Then
                       .CompositingQuality = CompositingQuality.HighQuality
                       .InterpolationMode = InterpolationMode.HighQualityBicubic
                       .PixelOffsetMode = PixelOffsetMode.HighQuality
                       .SmoothingMode = SmoothingMode.HighQuality

                   Else
                       .CompositingQuality = CompositingQuality.HighSpeed
                       .InterpolationMode = InterpolationMode.NearestNeighbor
                       .PixelOffsetMode = PixelOffsetMode.None
                       .SmoothingMode = SmoothingMode.None

                   End If
                   .DrawImage(srcImg, New Rectangle(0, 0, bmp.Width, bmp.Height),
                                                    0, 0, srcImg.Width, srcImg.Height,
                                                    GraphicsUnit.Pixel, ia)
               End With

           End Using ' gdi
       End Using ' ia

       Return bmp

   End Function

End Module


Como puedes comprobar, al igual que en tu código de ejemplo, yo también utilizo un único método para manipular ambas imágenes, sin embargo lo hago de una forma muy distinta, en base a un factor booleano de calidad gráfica (parámetro highQuality) que es lo que nos ayuda a aumentar el rendimiento produciendo una imagen de baja calidad al generar la imagen de previsualización, y una imagen de máxima calidad al guardar la imagen de tamaño real en el disco.

La extensión de método la extraí de mi API gratuita ElektroKit y posteriormente la adapté a tus necesidades.


En mi librería gratuita ElektroKit puedes encontrar muchas otras utilidades interesantes que te podrían ayudar a manipular imágenes de forma general, y también para ayudarte en otras muchas diversas tareas de la programación cotidiana en .NET que nada tengan que ver con la manipulación de imágenes.

Si quieres conocer un poco más sobre ElektroKit, lee este tema:




Aquí te dejo una demostración visual para que se aprecie la mejora de la modificación que le hice al código que publicaste.

En la demo primero cargo y manipulo una imagen de un tigre, a una resolución de 5184x3456 px. y de 18 mb, y luego una imagen de un cachorro, a 1920x1080 px. y de 3 mb. El único tiempo de espera apreciable sería al cargar y/o guardar la imagen, puesto que para el resto como ya expliqué se manipula una imagen a tamaño redimensionado.

[youtube=640,360]https://www.youtube.com/watch?v=2VxoHrHWoxA[/youtube]

Espero que todo esto te haya servido de algo.

Saludos








FJDA

#5
Cita de: Eleкtro en 17 Septiembre 2016, 00:21 AM
Bueno, como te prometí, vamos al tema...

Desconozco cual es el código original que estarás usando, pero el código de ejemplo que has compartido tiene varios problemas, e imagino que los mismos problemas estarán replicados en el código original...

Por orden de importancia:




El problema principal (y el más grave) por el cual se aprecia una elevada lentitud de respuesta al mover el TrackBar, es por que estás manipulando la imagen a tamaño real cada vez que mueves la posición del TrackBar, y eso causa un gran impacto negativo en el rendimiento de la aplicación.

Si tenemos un PictureBox donde quieres previsualizar la imagen, entonces lo primero que debemos hacer para evitar problemas de rendimiento es redimensaionar la imagen original al tamaño de ese PictureBox, eso lo puedes hacer "manualmente" creando un objeto Bitmap del tamaño deseando y dibujando la imagen, o simplemente llamando a la función Image.GetThumbnailImage(), y entonces, manipular la imagen redimensionada.




El segundo problema es que estás utilizando los parámetros por defecto de calidad de imagen al dibujar la imagen modificada y, al tratarse de una previsualización, logicamente deberiamos personalizar esos parámetros para crear una imagen de baja calidad, es decir, más rápida de procesar.

Los parámetros (o propiedades) que debes ajustar son las siguientes:
  • Graphics.CompositingQuality = CompositingQuality.HighSpeed
  • Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
  • Graphics.PixelOffsetMode = PixelOffsetMode.None
  • Graphics.SmoothingMode = SmoothingMode.None




Si a todo lo anterior le sumamos que cada vez que asignas la imagen modificada a tamaño real, ésta debe ser vuelta a modificar para ser estrechada, esto logicamente disminuirá todavía más el rendimiento ...de hecho el estrechamiento de cualquier imagen es expensivo de por si.

La solución es sencilla y obvia, debemos restaurar el valor por defecto (PictureBoxSizeMode.Normal) para no forzar otra redimensión adicional de la imagen, y en su lugar utilizar una imagen del tamaño del PictureBox, como ya expliqué más arriba.




El último problema (pero no menos importante), es que todo el procedimiento o algoritmo del código que has publicado se lleva a cabo de forma sincrónica, y esto supone que cada vez que mueves el TrackBar, el hilo de la interfáz de usuario debe esperar a que la imagen se procese y la modificación de imagen se genere para poder procesar el siguiente evento de "mover" del TrackBar, y esto se aprecia en forma de desagradables congelamientos de respuesta o "trompicones" al intentar mover el TrackBar demasiado rápido, puesto que se necesita mucho más tiempo para procesar todo lo que haces con esa imagen a tamaño real, que para mover el TrackBar.

Tanto en C#/VB.NET como en cualquier otro lenguaje, a mayor cantidad de datos que procesar (y una imagen cotidiana contiene muchos datos que procesar) necesitará mayor tiempo de procesado, y hay que esperar ese tiempo, es así de simple. Dicho de otro modo: estás saturando el hilo de la interfáz de usuario.




También cabe mencionar que no estás haciendo un buen uso, o mejor dicho directamente no haces ningún uso de la correcta administración del Garbage Collector para ayudarle a recolectar los recursos de los objetos que ya no sean necesarios por tu aplicación.

Debes asegurarte siempre, sin excepción, de liberar los objetos que sean deshechables llamando al método Object.Dispose(), incluso con los objetos que se liberen automáticamente al liberar un Form, no pierdas la costumbre.

Esto no afecta en el rendimiento de la interfáz de usuario (al menos no en el código de ejemplo que has publicado), pero si que afectará al consumo de memoria, y mucho. Literalmente hablando hay una fuga muy grave de memoria en el código que has publicado, basta con cargar una imagen de varios megabytes (+/-10 mb) y mover la posición del trackbar un par de veces para ver como el espacio reservado va subiendo, y subiendo, y subiendo... y nunca baja, puesto que nunca liberas los recursos no-administrados (Win32) de las imágenes llamando al método Dispose.




No se a que te estarás refiriendo pero eso no es así.

C# y VB.NET son dos lenguajes que trabajan bajo el mismo framework de Microsoft, por ende, ambos pueden utilizar todas las funciones que estén definidas en la librería de clases de .NET Framework. Ambos lenguajes son idénticos en esencia, excepto algunas peculiaridades muy concretas de cada lenguaje.


Vaya es usted una auténtico maestro. Ahora mismo no tengo tiempo de probar los cambios y la información que me facilita más tarde lo haré y le comentaré cualquier problema o circunstancia.

Ya pedí libros en otra pregunta pero no me respondió, nadie. Me interesa mucho sobre todo trabajar con imágenes pero es difícil encontrar. Ya imaginé que había que tratar la imagen adecuadamente para poder modificar de forma eficiente pero es difícil saber sin la información adecuada.

En cuanto a C# y VB, he comprobado que en C# PictureBox tiene filtros en sus propiedades y miembros que no veo que aparezcan en VB. A eso me refería.

hasta luego y gracias por su amabilidad.




No he podido evitar curiosear a falta de tiempo su modificación. Le agradecería  cambie el nombramiento de esta función a SetImageSaturation:

Código (vbnet) [Seleccionar]
Public Function SetImageSaturation(ByVal srcImg As Image, ByVal factor As Single,
                                      ByVal highQuality As Boolean) As Image
        factor+= 100
       Dim r As Single = (299 * (100 - factor) / 100000.0F)
       Dim g As Single = (587 * (100 - factor) / 100000.0F)
       Dim b As Single = (114 * (100 - factor) / 100000.0F)

       factor = (factor / 100.0F)
       Dim bmp As New Bitmap(srcImg.Width, srcImg.Height, srcImg.PixelFormat)
       Dim matrix As Single()() = {
               New Single() {(r + factor), r, r, 0.0F, 0.0F},
               New Single() {g, (g + factor), g, 0.0F, 0.0F},
               New Single() {b, b, (b + factor), 0.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 1.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 0.0F, 1.0F}
       }


Esta matriz la he desarrollado yo y es única. Imagino habrán otras pero yo no encontré ninguna buscando que funcionara igual que en API. Existen otras configuraciones, para brillo, escala de grises, gamma, etc.  Es importante recalcar que esta matriz es exclusivamente para Saturación. Si lo nombra  SetImageCorrection no es posible saber a simple vista para que se ha implementado dicha función.

Es posible añadir 'If factor > 0 then', para multiplicar  por diez el efecto cuando sea mayor que cero para conseguir una mayor saturación de color.

hasta luego y gracias de nuevo

Eleкtro

#6
Cita de: FJDA en 17 Septiembre 2016, 11:03 AMLe agradecería  cambie el nombramiento de esta función a SetImageSaturation:

Como dije en el post, solo es un código de ejemplo, un ejemplo que se debe adaptar a las necesidades que tengas, de todas formas aceptando tu petición le cambié el nombre a la función y también especifiqué que la matriz de color es una idea original tuya.

Saludos!








FJDA

#7
Cita de: Eleкtro en 17 Septiembre 2016, 13:51 PM
Como dije en el post, solo es un código de ejemplo, un ejemplo que se debe adaptar a las necesidades que tengas, de todas formas aceptando tu petición le cambié el nombre a la función y también especifiqué que la matriz de color es una idea original tuya.

Saludos!
Esta tarde he podido ver más tranquilamente su código y probarlo. He visto un problema que miraré mas tarde no quiero darle trabajo pues ya a aportado una respuesta excelente. Lo único que no he visto correcto es sacar la operación  factor+= 100 de la función pues forma parte de la misma. De todos modos he conseguido sintentizar las operaciones, y ya no hace falta el 'factor+= 100', quedando así:


Código (vbnet) [Seleccionar]
       Dim r As Single = CSng(factor * (299 * -10 ^ -5))
       Dim g As Single = CSng(factor * (587 * -10 ^ -5))
       Dim b As Single = CSng(factor * (114 * -10 ^ -5))
       factor = (factor / 100.0F) + 1

       Dim matrix As Single()() = {
               New Single() {(r + factor), r, r, 0.0F, 0.0F},
               New Single() {g, (g + factor), g, 0.0F, 0.0F},
               New Single() {b, b, (b + factor), 0.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 1.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 0.0F, 1.0F}
       }


o también:


       Dim r As Single = CSng(factor * -(299 * 10 ^ -5))
       Dim g As Single = CSng(factor * -(587 * 10 ^ -5))
       Dim b As Single = CSng(factor * -(114 * 10 ^ -5))
...



       Dim r As Single = CSng(factor * -(299 / 100000.0F))
       Dim g As Single = CSng(factor * -(587 / 100000.0F))
       Dim b As Single = CSng(factor * -(114 / 100000.0F))






Conseguí eliminar otra ecuación más  factor = (factor / 100.0F)


Código (vbnet) [Seleccionar]
 
   ''' <summary>
   ''' Función para saturación de color //por @Elektro y @FJDA
   ''' </summary>
   ''' <param name="srcImg">Objeto de tipo System.Drawing.Image</param>
   ''' <param name="Factor">Valor de saturación de color; de -100 a 100</param>
   ''' <param name="highQuality">Composición de calidad</param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function SetImageSaturation(ByVal srcImg As Image, ByVal Factor As Single, ByVal highQuality As Boolean) As Image
       Dim F As Single = Factor
       Dim R As Single = CSng(F * -(299 / 100000))
       Dim G As Single = CSng(F * -(587 / 100000))
       Dim B As Single = CSng(F * -(114 / 100000))

       Dim bmp As New Bitmap(srcImg.Width, srcImg.Height, srcImg.PixelFormat)
       Dim matrix As Single()() = {
               New Single() {CSng((0.00701 * F) + 1), R, R, 0.0F, 0.0F},
               New Single() {G, CSng((0.00413 * F) + 1), G, 0.0F, 0.0F},
               New Single() {B, B, CSng((0.00886 * F) + 1), 0.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 1.0F, 0.0F},
               New Single() {0.0F, 0.0F, 0.0F, 0.0F, 1.0F}
       }

       Using ia As New ImageAttributes
           ia.ClearColorMatrix()
           ia.SetColorMatrix(New ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap)
           ia.SetGamma(1.0F, ColorAdjustType.Bitmap)

           Using gdi As Graphics = Graphics.FromImage(bmp)
               With gdi
                   If highQuality Then
                       .CompositingQuality = CompositingQuality.HighQuality
                       .InterpolationMode = InterpolationMode.HighQualityBicubic
                       .PixelOffsetMode = PixelOffsetMode.HighQuality
                       .SmoothingMode = SmoothingMode.HighQuality

                   Else
                       .CompositingQuality = CompositingQuality.HighSpeed
                       .InterpolationMode = InterpolationMode.NearestNeighbor
                       .PixelOffsetMode = PixelOffsetMode.None
                       .SmoothingMode = SmoothingMode.None

                   End If
                   .DrawImage(srcImg, New Rectangle(0, 0, bmp.Width, bmp.Height),
                                                    0, 0, srcImg.Width, srcImg.Height,
                                                    GraphicsUnit.Pixel, ia)
               End With

           End Using ' gdi
       End Using ' ia

       Return bmp

   End Function






Buenas de nuevo.

@Elektro. he estado analizando su código y lo he ejecutado. Como comenté había  algún problema y es que a pesar de que  introdujo el trabajo en modo asincrónico seguía habiendo una ligera lentitud para mostrar el cambio si se movía muy rápido el TrackBar, especialmente se percibía al mostrar el valor de saturación en el Label.

Finalmente hemos conseguido eficiencia cien por cien, gracias a usted. ¡¡Buen trabajo!!

Tan solo tuve que realizar pequeños cambios. sin importancia. También mediante matemáticas eliminar ecuaciones innecesarias.

Aquí muestro los cambios y he reducido el código para que sea más fácil de entender y sea más corto.

*Deben crear un PictureBox, un TrackBar1 y un Label
* Coloquen el directorio para cargar la imagen en:
.ImagenPrevia = Image.FromFile(".\Imagen.jpg")


Código (vbnet) [Seleccionar]

Imports System.Runtime.CompilerServices
Imports System.ComponentModel
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D


Public NotInheritable Class Form1
   Inherits Form
   Private ImagenPrevia As Image
   Private ImagenDeTrabajo As Image
   Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
       With Me
           'Propiedades para el TrackBar1
           With .TrackBar1
               .TickFrequency = 10
               .TickStyle = TickStyle.TopLeft
               .Maximum = 100
               .Minimum = -100
               .Value = 0
           End With
           'Propiedades para el PictureBox1
           With .PictureBox1
               .BackColor = Color.DarkGray
               .SizeMode = PictureBoxSizeMode.Normal
           End With

           'Carga la imagen desde un directorio
           .ImagenPrevia = Image.FromFile(".\Imagen.jpg")

           'Tamaño de la imagen para trabajar
           Dim TamañoImagenDeTrabajo As Size = .PictureBox1.Size

           'Obtiene imagen previa para trabajar
           .ImagenDeTrabajo = .ImagenPrevia.GetThumbnailImage(TamañoImagenDeTrabajo.Width, _
                                                               TamañoImagenDeTrabajo.Height, _
                                                               Nothing, Nothing)
           .PictureBox1.Image = .ImagenDeTrabajo
       End With
   End Sub
   Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar1.ValueChanged
       'Ejecutar de forma asincrónica
       Task.Factory.StartNew(Sub()
                                 Dim lockImg As Image = ImagenDeTrabajo
                                 SyncLock lockImg 'Bloqueo de instrucción antes de procesar
                                     Me.Invoke(Sub()
                                                   Dim vSat As Single = TrackBar1.Value 'Obtiene el valor de saturación de color
                                                   Label1.Text = CStr(vSat) 'Muestra el valor de saturación de color
                                                   lockImg = lockImg.SetImageSaturation(sngSatVal:=vSat,
                                                                                       highQuality:=False)
                                                   Me.PictureBox1.Image = lockImg
                                               End Sub) 'Me.Invoke
                                 End SyncLock
                             End Sub) 'Task
   End Sub

End Class
Public Module ImageExtensions
   ''' <summary>
   ''' Función para saturación de color //por @Elektro y @FJDA
   ''' </summary>
   ''' <param name="srcImg">Objeto de tipo System.Drawing.Image</param>
   ''' <param name="sngSatVal">Valor de saturación de -100 a 100</param>
   ''' <param name="highQuality">Composición de calidad</param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   <DebuggerStepThrough> <Extension>
   <EditorBrowsable(EditorBrowsableState.Always)>
   Public Function SetImageSaturation(ByVal srcImg As Image, ByVal sngSatVal As Single, ByVal highQuality As Boolean) As Image

       Dim R As Single = CSng(sngSatVal * -(299 / 100000))
       Dim G As Single = CSng(sngSatVal * -(587 / 100000))
       Dim B As Single = CSng(sngSatVal * -(114 / 100000))
       Dim FactR As Single = CSng(((0.00701F * sngSatVal) + 1))
       Dim FactG As Single = CSng(((0.00413F * sngSatVal) + 1))
       Dim FactB As Single = CSng(((0.00886F * sngSatVal) + 1))

       Dim matrix As Single()() = {
               New Single() {FactR, R, R, 0, 0},
               New Single() {G, FactG, G, 0, 0},
               New Single() {B, B, FactB, 0, 0},
               New Single() {0, 0, 0, 1, 0},
               New Single() {0, 0, 0, 0, 1}
       }

       Dim bmp As New Bitmap(srcImg.Width, srcImg.Height, srcImg.PixelFormat)
       Using ia As New ImageAttributes
           ia.ClearColorMatrix()
           ia.SetColorMatrix(New ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap)
           ia.SetGamma(1.0F, ColorAdjustType.Bitmap)

           Using gdi As Graphics = Graphics.FromImage(bmp)
               With gdi
                   If highQuality Then
                       .CompositingQuality = CompositingQuality.HighQuality
                       .InterpolationMode = InterpolationMode.HighQualityBicubic
                       .PixelOffsetMode = PixelOffsetMode.HighQuality
                       .SmoothingMode = SmoothingMode.HighQuality

                   Else
                       .CompositingQuality = CompositingQuality.HighSpeed
                       .InterpolationMode = InterpolationMode.NearestNeighbor
                       .PixelOffsetMode = PixelOffsetMode.None
                       .SmoothingMode = SmoothingMode.None

                   End If
                   .DrawImage(srcImg, New Rectangle(0, 0, bmp.Width, bmp.Height),
                                                    0, 0, srcImg.Width, srcImg.Height,
                                                    GraphicsUnit.Pixel, ia)
               End With

           End Using ' gdi
       End Using ' ia

       Return bmp

   End Function

End Module




Hoy he modificado un poco la matriz para que sea más ordenado y legible. Ya que el uso del punto fijo 'F' junto con la variable single F (mal puesta por mi parte), lo hacía confuso. No veo necesario su uso en valores 0 o 1, así que lo he suprimido en estos valores. También he sacado las ecuaciones de la matriz pasándolas a FactR, FactG y FactB, nombrandolos   Fact(Letra color) porque son los verdaderos factores de rojo, verde y azul.


       Dim R As Single = CSng(sngSatVal * -(299 / 100000))
       Dim G As Single = CSng(sngSatVal * -(587 / 100000))
       Dim B As Single = CSng(sngSatVal * -(114 / 100000))
       Dim FactR As Single = CSng(((0.00701F * sngSatVal) + 1))
       Dim FactG As Single = CSng(((0.00413F * sngSatVal) + 1))
       Dim FactB As Single = CSng(((0.00886F * sngSatVal) + 1))

       Dim matrix As Single()() = {
               New Single() {FactR, R, R, 0, 0},
               New Single() {G, FactG, G, 0, 0},
               New Single() {B, B, FactB, 0, 0},
               New Single() {0, 0, 0, 1, 0},
               New Single() {0, 0, 0, 0, 1}
       }





y recuerden que para guardar la imagen deben usar la imagen previa (la original en tamaño y calidad) y modificar la saturación en alta calidad en el momento de guardar. Como bien ha hecho @Elektro, para realizar los cambios previos se trabaja con una imagen de bajo tamaño y calidad para acelerar el trabajo, pero a la hora de guardar hay que trabajar con la imagen original en tamaño y calidad que hemos cargado.


Código (vbnet) [Seleccionar]
Using img As Image = SetImageSaturation(ImagenPrevia, TrackBar1.Value, highQuality:=True)
           img.Save(".\Cambios.jpg", ImageFormat.Jpeg)
       End Using



Aquí dejo finalizado el tema, creo que se ha conseguido y espero sea de utilidad para otros programadores.

Agradecimientos de nuevo a @Elektro por su gran ayuda.

Hasta luego.