****************************************************************************** ** Color Emphasis For Emulator Authors ** ** Author: Hyde ** ** E-Mail: _hyde_@programmer.net ** ** Website: http://hydesprojects.cjb.net/ ** ****************************************************************************** Well, here is a little something I have put together for all of you emulator authors having problems with color emphasis bits. Enjoy it while you can... Thanks go out there to Marty, for creating NEStopia and making it an open-source project. First of all, you must understand what the color emphasis bits do. One can readily infer from their name that they are used to control emphasis on specific colors. There are three of such bits, which allow 8 possible combinations of emaphasis. To learn more about these check out the source code of Chris Covell's "Wall" demo. Now, how do they affect regular palette colors? I can't give you a hardware-based explanation on how it happens, but I surely can give you a small portion of code that will handle it quite well. If you already have a decent working emulator, then you must be using a palette for output (could be Matrixz's, FCE's, Loopy's, Matt's, whatever). For example, Matrixz's palette looks like this (may be a little outdated): unsigned char bMatrixzPalette [64][3] = { {0x6D,0x6D,0x6D}, {0x00,0x00,0xB3}, {0x45,0x00,0xBD}, {0x5C,0x00,0xA1}, {0x79,0x00,0x73}, {0x7E,0x00,0x00}, {0x79,0x28,0x00}, {0x61,0x39,0x00}, {0x44,0x50,0x00}, {0x00,0x5C,0x00}, {0x00,0x5C,0x00}, {0x00,0x50,0x50}, {0x00,0x44,0x89}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0xB3,0xB3,0xB3}, {0x00,0x56,0xFF}, {0x50,0x00,0xFF}, {0x7E,0x00,0xF8}, {0xAD,0x00,0xBE}, {0xBD,0x00,0x56}, {0xBD,0x44,0x00}, {0xA1,0x67,0x00}, {0x7E,0x7E,0x00}, {0x00,0x90,0x00}, {0x00,0x95,0x00}, {0x00,0x8A,0x68}, {0x00,0x79,0xBD}, {0x22,0x22,0x22}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0xFF,0xFF,0xFF}, {0x00,0xA1,0xFF}, {0x84,0x84,0xFF}, {0xAC,0x61,0xFF}, {0xE7,0x67,0xFF}, {0xFF,0x67,0xBE}, {0xFF,0x79,0x55}, {0xEC,0x9C,0x00}, {0xD0,0xBE,0x00}, {0x90,0xD5,0x00}, {0x00,0xE0,0x00}, {0x00,0xDB,0x84}, {0x00,0xCF,0xE1}, {0x56,0x56,0x56}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0xFF,0xFF,0xFF}, {0xBD,0xE0,0xFF}, {0xCF,0xCF,0xFF}, {0xE0,0xC4,0xFF}, {0xF8,0xBE,0xFF}, {0xFF,0xBE,0xEC}, {0xFF,0xCF,0xBE}, {0xFF,0xDB,0xA1}, {0xEC,0xE7,0x90}, {0xD5,0xF3,0x95}, {0xBE,0xF8,0xAD}, {0xAD,0xF8,0xD0}, {0xAD,0xF1,0xF3}, {0xB8,0xB8,0xB8}, {0x00,0x00,0x00}, {0x00,0x00,0x00} }; This array has a total of 64 RGB entries. Now, instead of modifying these entries every time a color emaphasis bit gets changed, we will calculate 8 different versions of this original palette when the emulator is first initialized (or whenever the user picks a different palette). Why eight? Because there are eight possible combinations for the color emphasis bits, thus implying that one would need 8 different palettes to handle every one of them properly. So, the modified palette will have a size of 8 * 64 = 512. Hence, we have int iNESPalette [512]; Now, we need a procedure that will generate the palette for us. The following lines of code were taken from NEStopia's source: -------------------------------------------------------------------------------------- #define PDX_MIN(x,y) ((x) < (y) ? (x) : (y)) #define PDX_MAX(x,y) ((x) < (y) ? (y) : (x)) #define PDX_CLAMP(x,min_,max_) PDX_MAX(PDX_MIN(x,max_),min_) //Tadaah... These will determine color intensities of every palette static const double emphasis[8][3] = { {1.000, 1.000, 1.000}, {1.239, 0.915, 0.743}, {0.794, 1.086, 0.882}, {1.019, 0.980, 0.653}, {0.905, 1.026, 1.277}, {1.023, 0.908, 0.979}, {0.741, 0.987, 1.001}, {0.750, 0.750, 0.750} }; //Converts from RGB to Hue Saturation Value void ToHSV(double r,double g,double b,double& h,double& s,double& v) { const double min = PDX_MIN( r, PDX_MIN( g, b )); const double max = PDX_MAX( r, PDX_MAX( g, b )); v = max; if (max != 0) { const double delta = max - min; s = delta / max; if (r == max) h = 0 + (g - b) / delta; else if (g == max) h = 2 + (b - r) / delta; else h = 4 + (r - g) / delta; h *= 60; if (h < 0) h += 360; } else { s = 0; h = -1; } } //Does the opposite void ToRGB(double h,double s,double v,double& r,double& g,double& b) { if (s == 0) { r = g = b = v; } else { h /= 60; const int i = (int)(floor (h)); const double f = h - i; const double p = v * ( 1 - s ); const double q = v * ( 1 - s * f ); const double t = v * ( 1 - s * ( 1 - f ) ); switch (i) { case 0: r = v; g = t; b = p; return; case 1: r = q; g = v; b = p; return; case 2: r = p; g = v; b = t; return; case 3: r = p; g = q; b = v; return; case 4: r = t; g = p; b = v; return; default: r = v; g = p; b = q; return; } } } //Compute the modified palette void computePalette (void) { unsigned char brightness = 128, saturation = 128, hue = 128; const double bri = (brightness - 128) / 255.0; const double sat = (((saturation - 128) / 255.0) * 2) + 1; const int hof = (hue - 128) / 4; const unsigned char (*const from)[3] = bMatrixzPalette; //Change this //So that it points to your //array. for (unsigned short int i=0; i < 8; ++i) //Eight 64-color palettes... { for (unsigned short int j=0; j < 64; ++j) { double r = from [j][0] / 255.0; double g = from [j][1] / 255.0; double b = from [j][2] / 255.0; double h,s,v; ToHSV (r,g,b,h,s,v); s *= sat; v += bri; h -= hof; if (h >= 360) h -= 360; else if (h < 0) h += 360; ToRGB (h,s,v,r,g,b); //Magic takes place here... r *= emphasis[i][0]; g *= emphasis[i][1]; b *= emphasis[i][2]; unsigned char rr = unsigned char (PDX_CLAMP(r * 255,0,255)), gg = unsigned char (PDX_CLAMP(g * 255,0,255)), bb = unsigned char (PDX_CLAMP(b * 255,0,255)); //Write modified values to the 512-int palette. Notice that makecol converts //hardware-independent RGB values to an integer that depends on the color //depth we are using (RGB_16 for 16-bit color mode, RGB_32 for 32-bit mode). //I'm guessing that Win32 API / DirectX provide macros/functions that will //perform these conversions for ya... iNESPalette [(i * 64) + j] = makecol (rr, gg, bb); } } } -------------------------------------------------------------------------------------- This is it for the palette calculations. Now you gotta give these colors a good use. First, declare a variable that will hold information regarding color emphasis settings: int colorEmphasis; On $2001 writes, modify this variable as follows: void handle_2001_writes (unsigned char data) { colorEmphasis = (bData >> 5) << 6; } colorEmphasis will then hold a value that indexes NES palette colors into the modified emulator palette. For example: bData colorEmphasis Detail 00000000 0 Using no color emphasis 00100000 64 Using color emphasis #1 01000000 128 Using color emphasis #2 . . . Finally, the conversion of NES palette colors to your emulator's palette colors can be done as follows: int NESColor (int uiIndex) { return iNESPalette [colorEmphasis + VRAM.readByte (uiIndex)]; } Where uiIndex >= 0x3f00 && uiIndex <= 0x3f2f. I hope this all makes sense. If not, feel free to ask me questions.