Basic Color Adjustment in 3ds Max without Photoshop

 

A fair bit of my MXS/DotNet tinkering leads me on tangents that previously haven't been within the remit of what I started. Normally this means I start writing a script which is half finished before I have an idea that seems far more interesting than the thing I actually started, by which time it's too late and I've disappeared into a swirling abyss of ignorance and subterfuge.

Recently I was adding to another script for a 3D/Sculpture crossover project, when I realized that it would be helpful if I could perform some kind of image tweak in the script in order to adjust the results of what I was generating, without having to load it into Photoshop. Also, I was wondering why people use those white earbuds that came with their swooshy new ipod/phone when they are clearly crap. The other day I clearly identified that someone was listening to Glen Campbell, and that wasn't good for me on two levels. One, that I could hear it, and two, that I knew it was Glen Campbell. Do you see what I mean about tangents?

Much of this article is the result of a post by Ofer Zelichover on CGTalk - When thinking about this I remembered a colormatrix method he posted a little while back. So thanks Ofer, you did much of the hard work already, I've just added a few different matrices to the mix.

 

The ColorMatrix Class

If you are familiar with how 3dsMax performs translation,rotation and scaling in the application, you will be familiar with the Transform Matrix. The Color Matrix is a similar principle, except with a 5x5 matrix with each row containing information about the RGB channels of an image, (with an extra column for the alpha channel). Without having to get into exactly what goes on, (there is plenty of information about this explained by far better qualified people) you can pass different color matrices via DotNet to an image in order to manipulate the pixel colour like the way a Transform matrix manipulates vertices or nodes.

Some of the Matrices are absolute values. I found two examples of a grayscale matrix. The one below seemed to be the most popular, taken from the NTSC guidelines on conversion of a color TV image to a black and white one. However, I also found an alternate grayscale matrix that accounts for linear color space. This seems to keep the highlights of the the original image a little better. I've included both methods in the struct code for comparison.

Code:

GrayscaleMatrix =
#(#(0.299, 0.299, 0.299, 0, 0),
#(0.587, 0.587, 0.587, 0, 0),
#(0.114, 0.114, 0.114, 0, 0),
#(0, 0, 0, 1, 0),
#(0, 0, 0, 0, 1) )

LinearGrayscaleMatrix =
#(#(0.3086, 0.3086, 0.3086, 0, 0),
#(0.6094, 0.6094, 0.6094, 0, 0),
#(0.0820, 0.0820, 0.0820, 0, 0),
#(0, 0, 0, 1, 0),
#(0, 0, 0, 0, 1) ),

Some of the adjustment matrices need the current pixel values in order to base their adjustments, so these are implemented via some functions that pass back the corrected color matrix object. There is one function that handles all of the work, and the methods are commented within the download.

Code:

if image != undefined do
(
g = (dotnetclass "system.drawing.Graphics").FromImage bm

adjustmentmatrix = case matrix of
(
#Red:RedMatrix
#Green:GreenMatrix
#Blue:BlueMatrix
#Contrast:(if level != undefined then SetContrastMatrix level else SetContrastMatrix 1.0)
#Saturation:(if level != undefined then SetSaturationMatrix level else SetSaturationMatrix 1.0)
#Gray:GrayScaleMatrix
#invert:InvertMatrix
#Brightness:(if level != undefined then SetBrightnessMatrix level else SetBrightnessMatrix 0.0)
#ColorCorrection: (if level != undefined then (SetColorAdjustmentMatrix level) else SetColorAdjustmentMatrix)
#Sepia:SepiaMatrix
#LinearGrayScale:LinearGrayscaleMatrix
default:GrayScaleMatrix
)

am = dotnetobject "system.drawing.imaging.colormatrix" adjustmentmatrix
ia = dotNetObject "System.Drawing.Imaging.ImageAttributes"
ia.SetColorMatrix am
rect = dotNetObject "System.Drawing.Rectangle" 0 0 image.Width image.Height
graphicsUnit = (dotNetClass "System.Drawing.GraphicsUnit").Pixel
g.DrawImage image rect 0 0 image.Width image.Height graphicsUnit ia
return bm
image.save()
g.Dispose()

It is performed drawing the adjusted image onto a GDI bitmap. This is just about fast enough to perform the color adjustments within the utility. I had experimented with the lockbits method which is using unmanaged code but 3dsMax seems to have problems with this. It is important to specify the bitmap in the utility outside this function, since you don't want to be continually creating bitmap objects with each slider event, as you would create a big memory problem. (There is a setimage function that creates it when the image is specified)

Filters Featured (Starting From Top Left)

At the moment, the utility passes the image back to a max display bitmap for save, this was to allow for a save in a non-windows image format. This is possible with an external image assembly, but not necessary for this.

The only other thing to note is the UI has a couple of custom controls - most notably the slider component that you can download with the code. This needs to be put in your scripts directory. These are some things i had developed for use in character setups, but were a bit more compact and had some functionality that could be set when the utility opens, rather than hardcoding it all into the script. I like this control because, when focused, it will allow you to scroll the middle mouse button to move the slider position.

If possible, I'd like to add a multiplication function to pass multiple matrices as dotnet doesn't allow for this in the bitmapdata class. One for the future, and another half finished script. Apologies to any Glen Campbell fans, he's not that bad, my Dad used to listen to him when I was young. Before his breakdown.