|
HueRotateMatrix() takes 0 to 360 (0 being normal)
SaturateMatrix() takes 0 to 2 (1 being normal)
ColourScaleMatrix() (Brightness) takes 0 to 255 (I think, havn't tried it yet)
Check this link for more information
http://www.sgi.com/misc/grafica/matrix/[^]
|
|
|
|
|
Sub OffsetMatrix change:
Dim mmat as ColorMatrix = _
New ColorMatrix(New Single()() _
{New Single() {1.0, 0.0, 0.0, 0.0, 0.0}, _
New Single() {0.0, 1.0, 0.0, 0.0, 0.0}, _
New Single() {0.0, 0.0, 1.0, 0.0, 0.0}, _
New Single() {0.0, 0.0, 0.0, 1.0, 0.0}, _
New Single() {roffset, goffset, boffset, 0.0, 1.0}})
|
|
|
|
|
Public Function Translucent(ByVal img As Image) As Boolean
Dim cm As New Imaging.ColorMatrix(New Single()() { _
New Single() {1.0F, 0.0F, 0.0F, 0.0F, 0.0F}, _
New Single() {0.0F, 1.0F, 0.0F, 0.0F, 0.0F}, _
New Single() {0.0F, 0.0F, 1.0F, 0.0F, 0.0F}, _
New Single() {0.0F, 0.0F, 0.0F, 0.0F, 0.0F}, _
New Single() {0.0F, 0.0F, 0.0F, 0.4F, 1.0F}})
Return draw_adjusted_image(img, cm)
End Function
Thanks,
Michaelas10;)
|
|
|
|
|
Hay There,
This article looks to have what i need. I copied the code to get started and now i get an error Type expected
My code looks like:
Imports System.Drawing.Imaging
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<system.diagnostics.debuggerstepthrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container
Me.Text = "Form1"
End Sub
#End Region
Public Function translate(ByVal img As Image, ByVal red As Single, _
ByVal green As Single, ByVal blue As Single, _
Optional ByVal alpha As Single = 0) As Boolean
Dim sr, sg, sb, sa As Single
' noramlize the color components to 1
sr = red / 255
sg = green / 255
sb = blue / 255
sa = alpha / 255
' create the color matrix
Dim cm As New ColorMatrix(New Single()() _
{New Single() {1, 0, 0, 0, 0}, _
New Single() {0, 1, 0, 0, 0}, _
New Single() {0, 0, 1, 0, 0}, _
New Single() {0, 0, 0, 1, 0}, _
New Single() {sr, sg, sb, sa, 1}})
' apply the matrix to the image
Return draw_adjusted_image(img, cm)
End Function
End Class
VB.NET tells me that the the type expected error is under ColorMatrix
Can anyone tell me what i'am doing wrong?
|
|
|
|
|
Hi BigBertB,
you're missing the path to ColorMatrix. Either use an imports-statement (Imports System.Drawing.Drawing2D) at the top of your class or precede the full path when declaring your cm-variable.
BTW - in cases like this, hit F2 (Object Browser) and locate an object. Use the "Find Symbol"-command to search for the object you don't know the path to (either by Edit/Find and Replace/Find Symbol or using the commandbar's button on the RHS)
Cheers,
Olaf
|
|
|
|
|
Imports System.Drawing.Imaging.ColorMatrix
|
|
|
|
|
Michael,
This is a great (if not the better!) article for color adjustment. Thank You a lot for share it with us.
Best ragards
Khe ps
Merry Christmas
|
|
|
|
|
Good article, I understand what's going on, but how on earth do you impliment it. I've been playing with it for about an hour and I still can't get it to display the image with the new Colormatrix...
|
|
|
|
|
Michael,
Thank you for the excellent article.
I've noticed a little issue with the way you define your negative matrix. With your definition of the matrix pure black colors (0,0,0,x) will not be inverted. Some other strange color "artifacts" could be produced as well.
I believe that a better negative could be produced by subtracting current color components from 255 (from 1 in the matrix world). To achieve this you should scale components by -1 and then translate them by 1. Following matrix should do the trick:
{-1, 0, 0, 0, 0}
{ 0, -1, 0, 0, 0}
{ 0, 0, -1, 0, 0}
{ 0, 0, 0, 1, 0}
{ 1, 1, 1, 0, 1}
Thank you.
|
|
|
|
|
Yeah, I came up with the same matrix you did. It does produce a better looking negative image.
|
|
|
|
|
With the original definition all the values where out by 1, so when a color should have been 255 it was 0. For some reason when the values in the last row are 0 the results from the matrix are rolled over (eg 256 becomes 0) but when the values in the last row are no zero the results are clamped (eg 256 becomes 255). I'm not sure if this is a bug or feature but the original example shouldn't work at all because the colors all come out to be negative so should really be clamped at zero.
|
|
|
|
|
Hi
i have tried this code. its working okay. i have a question. when i change the color of picture by giving red, green, blue color values. it displays the new picture on the picturebox. but when i minimize the form and then maximize it, the picture is no more there.
how can i save the changed picture because i need to store this new changed picture in database.
thanks a lot in advance.
bye
|
|
|
|
|
It really depends on how you are using the code. It sounds like you are updating an image using the image property of a picturebox. I would suggest loading the image from disk into an image object and then making modifications to that object. You can then resave the image to disk, display the results in a picturebox, etc. Otherwise, I would suggest reading up on the paint related events and properties of a picturebox control.
-Michael Combs
|
|
|
|
|
hey Michael
sorry, actually i m new to VB.NET. i have worked much on Java. now i m switching to VB.NET just to change taste
anyway, can u write a small code to save this picture.
i hope u will not mind it
thanks a lot in advance
|
|
|
|
|
The Bitmap class inherits from Image, and Image has a Save method. That should be enough.
Basic example:
<br />
Public Sub SaveImage(ByVal img As System.Drawing.Image, ByVal filename As String)<br />
img.Save(filename)<br />
End Sub<br />
|
|
|
|
|
Is there way, using matrix functions, to compute the average R, G and B values for a image. I have 10s or 1000s of images that I need to calculate avg RGB for and looping through each pixel is very slow.
|
|
|
|
|
Not as a whole. You can change each individual pixel to the average of r, g and b
|
|
|
|
|
Have you looked into the lockbits function? This is very fast. If you're using getpixel you will notice a huge speed improvement.
|
|
|
|
|
Hi,
I had try the code n it works fine. However, is there a way to manipulate the pixel/images ( as in change a 24 bit image to 8 bit image or select all the even number of pixel bit...etc) in Vb.net and how??
|
|
|
|
|
|
I would also be interested in this.
I tried to find some articles about this topic, but without usable results ;(
|
|
|
|
|
Option Explicit On
Option Strict On
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Public Class Colourise
' Provide public access to the picture's byte data.
Public ImageBytes() As Byte
Public RowSizeBytes As Integer
Public Const PixelDataSize As Integer = 32
' Save a reference to the bitmap.
Public Sub New(ByVal bm As Bitmap)
m_Bitmap = bm
End Sub
' A reference to the Bitmap.
Private m_Bitmap As Bitmap
' Bitmap data.
Private m_BitmapData As BitmapData
' Lock the bitmap's data.
Private Sub LockBitmap()
' Lock the bitmap data.
Dim bounds As Rectangle = New Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height)
m_BitmapData = m_Bitmap.LockBits(bounds, Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
RowSizeBytes = m_BitmapData.Stride
' Allocate room for the data.
Dim total_size As Integer = m_BitmapData.Stride * m_BitmapData.Height
ReDim ImageBytes(total_size)
' Copy the data into the ImageBytes array.
Marshal.Copy(m_BitmapData.Scan0, ImageBytes, 0, total_size)
End Sub
' Copy the data back into the Bitmap
' and release resources.
Private Sub UnlockBitmap()
' Copy the data back into the bitmap.
Dim total_size As Integer = m_BitmapData.Stride * m_BitmapData.Height
Marshal.Copy(ImageBytes, 0, m_BitmapData.Scan0, total_size)
' Unlock the bitmap.
m_Bitmap.UnlockBits(m_BitmapData)
' Release resources.
ImageBytes = Nothing
m_BitmapData = Nothing
End Sub
Private Sub RGBToHLS(ByVal r As Long, ByVal g As Long, ByVal b As Long, ByRef h As Single, ByRef s As Single, ByRef l As Single)
Dim Max As Single
Dim Min As Single
Dim delta As Single
Dim rR As Single, rG As Single, rB As Single
rR = CSng(r / 255) : rG = CSng(g / 255) : rB = CSng(b / 255)
'{Given: rgb each in [0,1].
' Desired: h in [0,360] and s in [0,1], except if s=0, then h=UNDEFINED.}
Max = Maximum(rR, rG, rB)
Min = Minimum(rR, rG, rB)
l = (Max + Min) / 2 '{This is the lightness}
'{Next calculate saturation}
If Max = Min Then
'begin {Acrhomatic case}
s = 0
h = 0
'end {Acrhomatic case}
Else
'begin {Chromatic case}
'{First calculate the saturation.}
If l <= 0.5 Then
s = (Max - Min) / (Max + Min)
Else
s = (Max - Min) / (2 - Max - Min)
End If
'{Next calculate the hue.}
delta = Max - Min
If rR = Max Then
h = (rG - rB) / delta '{Resulting color is between yellow and magenta}
ElseIf rG = Max Then
h = 2 + (rB - rR) / delta '{Resulting color is between cyan and yellow}
ElseIf rB = Max Then
h = 4 + (rR - rG) / delta '{Resulting color is between magenta and cyan}
End If
'Debug.Print h
'h = h * 60
'If h < 0# Then
' h = h + 360 '{Make degrees be nonnegative}
'End If
'end {Chromatic Case}
End If
'end {RGB_to_HLS}
End Sub
Private Sub HLSToRGB(ByVal h As Single, ByVal s As Single, ByVal l As Single, ByRef r As Long, ByRef g As Long, ByRef b As Long)
Dim rR As Single, rG As Single, rB As Single
Dim Min As Single, Max As Single
If s = 0 Then
' Achromatic case:
rR = l : rG = l : rB = l
Else
' Chromatic case:
' delta = Max-Min
If l <= 0.5 Then
's = (Max - Min) / (Max + Min)
' Get Min value:
Min = l * (1 - s)
Else
's = (Max - Min) / (2 - Max - Min)
' Get Min value:
Min = l - s * (1 - l)
End If
' Get the Max value:
Max = 2 * l - Min
' Now depending on sector we can evaluate the h,l,s:
If (h < 1) Then
rR = Max
If (h < 0) Then
rG = Min
rB = rG - h * (Max - Min)
Else
rB = Min
rG = h * (Max - Min) + rB
End If
ElseIf (h < 3) Then
rG = Max
If (h < 2) Then
rB = Min
rR = rB - (h - 2) * (Max - Min)
Else
rR = Min
rB = (h - 2) * (Max - Min) + rR
End If
Else
rB = Max
If (h < 4) Then
rR = Min
rG = rR - (h - 4) * (Max - Min)
Else
rG = Min
rR = (h - 4) * (Max - Min) + rG
End If
End If
End If
r = CLng(rR * 255) : g = CLng(rG * 255) : b = CLng(rB * 255)
End Sub
Private Function Maximum(ByVal rR As Single, ByVal rG As Single, ByVal rB As Single) As Single
If (rR > rG) Then
If (rR > rB) Then
Maximum = rR
Else
Maximum = rB
End If
Else
If (rB > rG) Then
Maximum = rB
Else
Maximum = rG
End If
End If
End Function
Private Function Minimum(ByVal rR As Single, ByVal rG As Single, ByVal rB As Single) As Single
If (rR < rG) Then
If (rR < rB) Then
Minimum = rR
Else
Minimum = rB
End If
Else
If (rB < rG) Then
Minimum = rB
Else
Minimum = rG
End If
End If
End Function
Public Sub ColouriseBitmap(ByVal lHue As Long, ByVal lSaturation As Long)
' Perform colourisation
Dim x As Long
Dim y As Long
Dim xEnd As Long
Dim yEnd As Integer
Dim Jump As Integer
Dim h As Single
Dim s As Single
Dim l As Single
Dim lR As Long
Dim lG As Long
Dim lB As Long
Dim hDN As Single
Dim sDN As Single
' Calculate denormalized Hue, Saturation & Intensity:
hDN = CSng(((lHue * 6.0#) / 255.0#) - 1.0#)
sDN = CSng(lSaturation / 255.0#)
' Lock the bitmap.
LockBitmap()
xEnd = RowSizeBytes
yEnd = m_Bitmap.Height
Jump = PixelDataSize \ 8
For x = 0 To xEnd - 1 Step Jump
For y = 0 To yEnd - 1
' Obtain the luminance:
RGBToHLS(ImageBytes(CInt(x + 2 + (y * xEnd))), ImageBytes(CInt(x + 1 + (y * xEnd))), ImageBytes(CInt(x + (y * xEnd))), h, s, l)
' Now get the new colour using the input hue and saturation
HLSToRGB(hDN, sDN, l, lR, lG, lB)
ImageBytes(CInt(x + 2 + (y * xEnd))) = CByte(lR)
ImageBytes(CInt(x + 1 + (y * xEnd))) = CByte(lG)
ImageBytes(CInt(x + (y * xEnd))) = CByte(lB)
Next y
Next x
UnlockBitmap()
End Sub
Public Sub RotateHue(ByVal incHue As Single)
' Saturation only applies to grey scale images. Otherwise saturation
' is taken from the colour.
Dim x As Long, y As Long
Dim xEnd As Long, yEnd As Long
Dim lB As Long, lG As Long, lR As Long
Dim h As Single, s As Single, l As Single
Dim Jump As Integer
Dim hDN As Single
' Lock the bitmap.
LockBitmap()
xEnd = RowSizeBytes
yEnd = m_Bitmap.Height
Jump = PixelDataSize \ 8
For x = 0 To xEnd - 1 Step Jump
For y = 0 To yEnd - 1
RGBToHLS(ImageBytes(CInt(x + 2 + (y * xEnd))), ImageBytes(CInt(x + 1 + (y * xEnd))), ImageBytes(CInt(x + (y * xEnd))), h, s, l)
h = h + incHue
If (h > 5) Then h = h - 6
If (h < -1) Then h = h + 6
HLSToRGB(h, s, l, lR, lG, lB)
ImageBytes(CInt(x + 2 + (y * xEnd))) = CByte(lR)
ImageBytes(CInt(x + 1 + (y * xEnd))) = CByte(lG)
ImageBytes(CInt(x + (y * xEnd))) = CByte(lB)
Next y
Next x
UnlockBitmap()
End Sub
End Class
|
|
|
|
|
Maybe I'm missing something here, but the colormatrix method described here only works as a 'display transformation', the actual data in the bitmap is not modified but just applied when rendering the image to screen. This means (if I am correct) that for real image processing you still need to modify image data pixel by pixel, and that in that case you cannot use the colormatrix ... That's what I did using unmanaged pointers because setPixel and GetPixel are soooo slooow, maybe I will post an article about it.
Regarding color, again a very shortsighted implementation of MS:
-the only color data that can be stored in 8-bit integers per color channel are gamma-corrected values; you need about 12-13 bits for linear light values. This means basically image processing is performed on non-linear values (R*G*B*), with no provisions to perform it on linear values (RGB), e.g. needed when doing tranformations from one color space to another.
-the NTSC luminosity you are speaking of is just one possibility, as there are literally thousands of RGB spaces out there, and only a very small chance your image is using the NTSC primaries, which are quite old and certainly not representative of a modern computer monitor. sRGB primaries are probably much more suitable (ITU Rec. 709). To compute lightness L* from that you have to perform quite a number of operations: sR*G*B* -> sRGB -> CIE XYZ -> CIE L*a*b*. Again, very little is foreseen for such operations in GDI+, at least that I know of. Is this correct?
If both my assumptions are correct, although your article is well written, the GDI+ implementation seems flawed in my eyes for real (physics based or vision based) image processing. It seems MS wasn't concerned with the basics, it just had to look good (and go fast, although thats a joke using managed datatypes).
Yves Vander Haeghen
|
|
|
|
|
Actually the data is modified during the render process. If you render to another image then the color modified data is saved to the new image.
I don't quite understand your comments about gamma correction. Altering color with the ColorMatrix is equivalent to direct pixel manipulation with pointers, although less versatile. Gamma correction may occur during the output of the image to a display device, but this has nothing to do with the raw image data (unless you actually apply a gamma correction to the data directly).
The NTSC standards, although old, are based on the sensitivity of the eye to color. It's a widely used standard by which to calculate luminosity for gray scale images. I'm sure there are a variety of ways to compute lightness. However, changing the color space to L*a*b* is an arbitrary adjustment providing an arbitrary representation of lightness. You have to have a representation of lightness in the format of the data, which is RGB (as saved in the bitmap).
GDI+ does not handle color space transformations directly. Color space transformation is an arbitrary calculation which simply changes the representation of the numbers for ease of use. As such there are an infinite number of color spaces which can be devised. Attempting to accomodate all of these with the .NET framework would be excessive. There is nothing within the colormatrix class which prevents it from being used for image processing (physics, or otherwise).
|
|
|
|
|
Hello,
great to know that the colormatrix can be used to render into another image (I still wrote a class that does the same using unmanaged pointers in C# that gives more flexibility).
Regarding gamma-correction, most of the time it is INCLUDED in the data, and the OS does nothing during rendering. This is bizarre in that it assumes that you are displaying with a CRT monitor with gamma of about 2.2. The reason for this is that gamma-corrected RGB values (R*G*B*) are more 'compact', and need less bits to store data accurately: about 8 bits suffice. Linear light RGB values (no gamma correction) need at least 12-14 bits per color channel in order to avoid visual artefacts in the darker colors, and just aren't practical to work with. This means that many images on the net are actually already output-rendered (Mac images maybe pre-rendered with an gamma of 1.8 I think, because there is an additional gamma factor in the MAc OS!).
Reagarding the NTSC standards, they are indeed based on the human visual system, but ALSO on the NTSC primaries which are NOT relevant for modern computer monitors! The formula is also very approximative. CIE L*a*b* is NOT an arbitrary adjustment at all. You are probably thinking about the generic RGB to CIE L*a*b* tansform given in many image processing textbooks, which is misguiding if not wrong: each RGB color space has a well-defined transform to CIE XYZ, which can then be transformed to CIE L*a*b*. Thus, NTSC RGB should have a transform to CIE XYZ and thus to lightness L*. However, most of the time the actual RGB space used to encode an image is unknown, and in that sense ANY lightness formula will do. I, however advise to use one based on sRGB, as many printers, monitors and digital cameras are starting to use this color space.
I do not agreee with your comments about color space transformations in GDI+. It is not necessary to include inside GDI+ all the possible transformations, only to allow the possibility to perform transformations on linear-light values and not only on gamma-corrected values! Practically this means that when doing a color space transformation one needs to transform a gamma-corrected integer pixel value to a floating point linear light representation, then apply the transformation, and then recode into a integer
(gamma-corrected) value. At first sight this isn't possible in GDI+ with the colormatrix as it operates directly on the gamma-corrected raw data, and it is this fact that prevents physics (=linear-light!!!!) or human vision (=CIE L*a*b* or other perceptually uniform color space) based image processing to be implemented without first writing extra classes. A workaround maybe to transform the input image into a 16-bit per channel linear-light or L*a*b* integer representation, and let the colormatrix operate on that ....
Yves
|
|
|
|
|