Palettes

3DS Max Plug-In SDK

Palettes

See Also: Class GPort, Class Quantizer, Class ColorPacker.

Overview

When operating in 8-bit mode, 3ds max uses a 256 color palette. Plug-ins should avoid changing the system palette because this can cause the quality of the 3ds max display to degrade. The palette used by 3ds max reserves the bottom 10 slots and top 10 slots of the palette for Windows colors. It also contains a 6x6x6 RGB color cube, and a 16 gray level ramp. This palette should be adequate for most purposes. When a plug-in wants to output bitmaps and colored areas with dither, so as to get good approximations of 24 bit colors in 8-bit modes, it should make use of the GPort class. The GPort also provides access to 8 "animated palette slots" to allow dynamically altering system palette entries.

Support Classes

This section discusses several class that are helpful in working with 3ds max palettes and paletted images.

Class GPort - This class has several purposes. It maintain the default 3ds max palette for doing 256 color graphics. It also provides a mechanism for allocating "animated color slots" in the default palette for use in the user interface. Additionally it provide various functions for doing dithered graphics using the default 3ds max palette.

There is one instance of GPort that is shared globally. It is accessed by the function:

GPort* GetGPort();

Here is an example of a typical use of this class. In this case the developer wants to put up a dialog box with two color swatches in it, and have these be painted using an "animated color slot" in the default 3ds max palette. To set things up, the following is done in the WM_INITDIALOG code:

// Plug the standard MAX palette into the hdc

HDC hdc = GetDC(hwndDlg);

hOldPal = GetGPort()->PlugPalette(hdc);

// Get one anim palette slot

animSlot[0] = GetGPort()->GetAnimPalSlot();

// Get another anim palette slot

animSlot[1] = GetGPort()->GetAnimPalSlot();

The routine GetAnimPalSlot() is used to get an animatable palette slot. There are a total of 8 slots available. This method may return -1. This indicates that GPort has no more remaining animatable palette slots. In this case, using the routine GPort::PaintColorSwatch() will do the proper thing, that is dithering the swatch instead of trying to paint it with a single palette index.

Next, in the WM_PAINT code before drawing the following is done:

// Just to be safe: if the palette is there, unchanged, this is cheap.

GetGPort()->PlugPalette(hdc);

GetGPort()->SetAnimPalEntry(animSlot[0], rgb1); // set an rgb into slot

GetGPort()->SetAnimPalEntry(animSlot[1], rgb2); // set an rgb into slot

GetGPort()->AnimPalette(hdc); // update the palette with these colors

PaintColorSwatch(hdc, rgb1, 0, 10,10,40,20); // see below

PaintColorSwatch(hdc, rgb2, 0, 10,30,40,40);

To finish up, in WM_DESTROY you should do the following:

GetGPort()->ReleaseAnimPalSlot(animSlot[0]); // Release palette slot

GetGPort()->ReleaseAnimPalSlot(animSlot[1]); // Release palette slot

GetGPort()->RestorePalette(hdc,hOldPal); // Restore palette

The code for PaintColorSwatch() is also included below to illustrate in more detail how GPort works. This is a routine to draw a color swatch using an animated palette entry, if available.

void PaintColorSwatch(HDC hdc, DWORD col, int slot,

 int left, int top, int right, int bottom) {

 HPEN oldPen = (HPEN) SelectObject(hdc, GetStockObject(NULL_PEN));

 HBRUSH oldBrush;

 if (slot>=0) {

  HBRUSH brush = GetGPort()->MakeAnimBrush(slot,col);

  oldBrush = (HBRUSH)SelectObject (hdc, brush);

  Rectangle (hdc, left,top,right,bottom);

  DeleteObject(brush);

  }

 else {

  oldBrush = (HBRUSH)SelectObject (hdc,

   GetStockObject( NULL_BRUSH ));

  Rectangle (hdc, left, top, right,bottom); // Paint the border

  Rect rect;

  rect.left = left++;

  rect.top = top++;

  rect.right = right--;

  rect.bottom = bottom--;

  GetGPort()->DitherColorSwatch(hdc, rect , col);

  }

 SelectObject (hdc, oldPen);

 SelectObject (hdc, oldBrush);

 }

Class Quantizer - This class is used for dithering output to files. This class computes a palette when going from 24-bit to 8-bit color.

Class ColorPacker - This class is used to pack the pixels down using the Quantizer computed palette.

The following code shows how the methods of Quantizer and ColorPacker may be used to compute a palette for an image.

/* Make a palette for the given image */

int BitmapIO::CalcOutputPalette(int palsize, BMM_Color_48 *pal) {

 Quantizer *q = BMMNewQuantizer();

 if (!q->AllocHistogram()) {

bail_out:

  q->DeleteThis();

  return(0);

  }

 PixelBuf line(map->Width());

 if (!line.Ptr())

  goto bail_out;

 int y;

 for(y=0; y<map->Height(); ++y) {

  // call GetOutputPixels to get fileOutputGamma corrected values.

  if (!GetOutputPixels(0,y,map->Width(),line.Ptr()))

   goto bail_out;

  q->AddToHistogram(line.Ptr(),map->Width());

  }

 // Reserve the background color (assuming the upper left pixel is a

 // representative) and make a palette.

 BMM_Color_64 bgpix;

 GetOutputPixels(0,0,1,&bgpix);

 int n = q->Partition(pal, palsize, &bgpix);

 q->DeleteThis();

 return n;

 }