Basic Color Adjustment in 3ds Max without Photoshop

Mar 17, 2009 by     No Comments    Posted under: DotNet, Imaging, Maxscript

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 5×5 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.

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.

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)

  • Adjust Red Balance
  • Adjust Green Balance
  • Adjust Blue Balance
  • Saturation
  • Contrast
  • Brightness
  • Invert
  • Grayscale
  • Sepia
  • Red Channel Only
  • Green Channel Only
  • Blue Channel Only

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.


Using Base64 encoding in 3dsMax

Dec 17, 2008 by     8 Comments    Posted under: 3dsMax, DotNet, Imaging, Maxscript

If you have ever received an email and instead of your normal information for pharmaceutical-related special offers and personal member enhancement, you get a jumble of nonsense, you’re probably already aware of what a Base64 encoded string looks like. Email clients use MIME to transfer messages and attachments, and one way to break up things like images so that it can be sent is Base64 encoding.


Despite looking like the sound you make when trapping your plums in the fridge door, Base64 encoded strings can be used to represent images and sounds and deployed with scripts to avoid the need for external linked dependencies.

DotNet Provides some easy methods to do this within the framework, so here are some functions for converting images to Base64 within 3dsmax below.

fn ConvertImageToBase64String filename =
if (doesfileexist filename) do
memstream = dotnetobject "System.IO.MemoryStream"
ImgLoaded = ImageClass.fromfile filename memstream ImgLoaded.rawformat
Base64string = ConvertClass.ToBase64String (memstream.ToArray())
return Base64String
fn ConvertBase64StringToImage string =
bytearr = convertclass.FromBase64String string
memstream = dotnetobject "System.IO.MemoryStream" bytearr
DecodedImg = ImageClass.fromstream memstream
return DecodedImg

I’ve added these into a utility with a few extras (namely functions to convert and play Wav files using Base64 encoding) You can download this script at the bottom of the page.

Grimlock says “SSdtIGdvbm5hIG9wZW4gYSBjYW4gb2YgV0hPT1BBU1Mgb24geW91IQ==”

If if you have ever read the “Bitmap Values” topic in the MXSHelp, you will be aware of this script –

b=selectbitmap() -- open image file browser
bname="bitmap_"+(getfilenamefile b.filename) -- build name from filename
w=b.width -- get properties of bitmap
format "----------nfn load_% = (n" bname -- start defining function
format "local %=bitmap % %n" bname w h -- create bitmap in function
-- write out a function that unpacks an integer into a pixel color
format "fn unpack val = for p in val collect (r=p/256^2; g=p/256-r*256; b=mod p 256; color r g b)n"
for r=0 to h-1 do -- for each row in the bitmap
-- have function write the column of pixels to the bitmap
( format "setpixels % [0,%] (unpack #(" bname r
pixels=getpixels b [0,r] w -- read in the column of pixels
for c=1 to w do -- loop through each pixel
( p=pixels[c] -- get the pixel
-- pack the pixel into an integer and write it out
format "%" (((p.r as integer)*256+(p.g as integer))*256+(p.b as integer))
if c != w then -- if not at end of data
format ", " -- write a comma
format "))n" -- else close out the line
format "return %n" bname -- function returns the bitmap
format ")n----------n" -- finish off function definition

Base64 is a DotNet method of performing the same thing, and instead of returning a max bitmap, it returns a Dotnet image.

This could probably benefit from being moved into a dedicated dotnet assembly, as I found with the color control a few weeks back, similar functions are much slower within max. Therefore if you are converting large images you might find the UI snagged up for a while.

Finally, to convert text to and from Base64, here’s a great site I found that will do it for you! –

download script

Building a DotNet Colour Chart (Not for Elephants)

Nov 24, 2008 by     No Comments    Posted under: DotNet, Imaging, Maxscript

If you’re anything like me then you have a mind like a sieve. So much so, I can barely remember the names of the dotnet colours, let alone get their pesky American spelling right. It’s colour alright???!!! 🙂

Anyhoo, despite this, and not knowing what “GoldenRod” is, I decided to make a script that built an interface by retrieving the colors via the enumeration class. I’ve talked about enums last article but this introduces another way of using them. This certainly isn’t the only way to do something like this, but it was the way I worked out so it’ll do for me. The DotNetControl is a FlowLayoutPanel, which is really useful for building dynamic interfaces, (I use one in my HitchHiker control) as you can pass it an array of controls to build it.

It’s quite easy to get the brightness of the colour when creating the labels. Having a cutoff where if the color has a brightness value of less than 0.3 will change the forecolor to a light color. I’ve changed the code to reflect this. Also, i’ve split the array into two, to make the differentiation between systemcolors and named colors. (System colors are the colors used on the current windows color scheme)

Update –

  • Added copying to clipboard of dotnet string
  • Added DotNet color dialog to pick color
  • Added RGB swatches

To Do –

I am going to see if Reflection can be used to make this script faster. At the moment it takes a few seconds I cant work out if it is setting up the button array or the color property retrieval.

I will also try sorting the colors by Hue (Thanks to Dave Stewart for the suggestion) It will be an interesting test to see if an IComparer class can work in 3dsMax.

download script

GDI+ Methods – The DotNet Paint event

Oct 28, 2008 by     2 Comments    Posted under: 3dsMax, DotNet, Imaging

In order to use GDI+ to draw vector based shapes, outlines and objects in DotNet, one option available is to use the paint event.

I have been working on some GDI+ functions that can dynamically generate icons without the need for bitmaps. The idea of this came from a post on CGTalk some time ago by Dave Stewart who previewed a similar idea based around MXS and setpixel methods if i remember rightly. I wanted to write a dotnet equivalent. I am trying to include the sort of icons you use on dialogs for generic actions like arrows, numbers, pickbuttons etc.

One nice thing is using translatetransform() within the graphics surface, that way you can draw the icon in one position and then pass an angle in the function and it draws it pointing the correct direction. this simplifies things greatly and means you have one function for all arrow icons.

With the same thing in mind, the plus icon also becomes the cross icon with translatetransform, as does the square when it becomes the diamond. The numbers adjusts the font size to the largest possible according to the number of digits.

Each paint event function passes an outline colour and a fill colour along with the graphics surface so you can adapt the icons to whatever scheme you like, including the current 3dsmax one.

There are a few options with a GDI+ drawing surface when it comes to how you perform the drawing itself. I decided to opt for the graphicspath object method as you are able to treat it as a closed entity. This means you can fill it with colors, stroke the outline and apply linestyles and edge joins. Constructing the shape from individual lines always treats them as individual lines and not the overall shape. In order to make the corners sharp on a graphicspath object, one option in this heavilly overloaded function is to pass a byte array. I’m still not sure why this is neccesary, except for the fact that it doesn’t work properly without it. I think it must be something to do with how the corners are rendered.

I set up a loop to build the dotnet arrays in max, remember that dotnet arrays start at 0, not at 1 like in Maxscript.

-- specify an array of points

PlusGDIArray = dotNetObject "System.drawing.point[]" 12

-- and a byte array of the same size

ByteArray = dotNetObject "System.byte[]" 12

-- store the points you want to place in the array

local pointloop = #((pt1 = dotNetObject PointClass 8 2),
(pt2= dotNetObject PointClass 8 8),
(pt3= dotNetObject PointClass 2 8),
(pt4 = dotNetObject PointClass 2 16),
(pt5 = dotNetObject PointClass 8 16),
(pt6 = dotNetObject PointClass 8 22),
(pt7= dotNetObject PointClass 16 22),
(pt8 = dotNetObject PointClass 16 16),
(pt9 = dotNetObject PointClass 22 16),
(pt10 = dotNetObject PointClass 22 8),
(pt11 = dotNetObject PointClass 16 8),
(pt12 = dotNetObject PointClass 16 2))

-- and apply them into the dotnetarray
for i = 1 to pointloop.count do PlusGDIArray.setvalue pointloop[i] (i-1)

--repeat for the byte array
local byteloop = #((Byte1 = dotNetObject ByteClass 0),
(Byte2= dotNetObject ByteClass 1),
(Byte3= dotNetObject ByteClass 1),
(Byte4 = dotNetObject ByteClass 1),
(Byte5 = dotNetObject ByteClass 1),
(Byte6 = dotNetObject ByteClass 1),
(Byte7= dotNetObject ByteClass 1),
(Byte8 = dotNetObject ByteClass 1),
(Byte9 = dotNetObject ByteClass 1),
(Byte10 = dotNetObject ByteClass 1),
(Byte11 = dotNetObject ByteClass 1),
(Byte12 = dotNetObject ByteClass 129))
for i = 1 to byteloop.count do ByteArray.setvalue Byteloop[i] (i-1)
-- finally add both arrays to a graphicspath object
ClosedPath = dotnetobject "System.Drawing.Drawing2D.GraphicsPath" PlusGDIArray ByteArray

download script