Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Marn

Does anyone know what formulas would've been used to generate Hexen and Strife's colormaps?

Recommended Posts

Do you mean:

  • The 14 PLAYPAL states (palette flash/pain translucent blends?)
  • The 34 COLORMAP light-diminishing lookups?
  • The invulnerability creation (1 of the 34)?
  • The method used to choose the 256 full-bright colors?

Share this post


Link to post
Posted (edited)

The COLORMAPs, obviously. The PLAYPALs aren't much of an enigma, they used largely the same code as dcolors. SLADE can already replicate them perfectly. The COLORMAPS, however, are generated quite differently.

 

For illustration, here's Hexen's vanilla COLORMAP:

vKRm0jP.png

And here's a COLORMAP generated from Hexen's PLAYPAL using Doom's COLORMAP generation method:

xwrnX1I.png

 

Notice by the way the last column changes from all-black to all-white. In Hexen, palette index 255 is pure white (RGB #FFFFFF) but the colormap makes it pure black at all light levels. I remember this led to a bug report over on the ZDoom forums as a custom texture with white in it got black spots and the modder's first thought was it was a ZDoom rendering bug...

Share this post


Link to post
Posted (edited)
On 5/17/2019 at 1:12 AM, Gez said:

The COLORMAPs, obviously. The PLAYPALs aren't much of an enigma, they used largely the same code as dcolors. SLADE can already replicate them perfectly. The COLORMAPS, however, are generated quite differently.

 

For illustration, here's Hexen's vanilla COLORMAP:

vKRm0jP.png

And here's a COLORMAP generated from Hexen's PLAYPAL using Doom's COLORMAP generation method:

xwrnX1I.png

 

Notice by the way the last column changes from all-black to all-white. In Hexen, palette index 255 is pure white (RGB #FFFFFF) but the colormap makes it pure black at all light levels. I remember this led to a bug report over on the ZDoom forums as a custom texture with white in it got black spots and the modder's first thought was it was a ZDoom rendering bug...

 

It wasn't obvious to me, which is why I asked - they're all kinda interesting to me :)

 

Very interesting point about the last column. I can't look at the tools, or at hexen.wad at the moment. Does the original Hexen COLORMAP map index 255 to black?? I'm sure I never noticed that, but I'm way less familiar with Hexen rendering than Doom. Do you think it's an underflow-type situation (Frame 31 - 32) = -1, clamped to 0 comes to mind. So WAD tools really shouldn't use index 255 in Hexen, huh? (It's always a problem with Doom as well, even though it shouldn't be...you know what I mean.) What other thoughts do you have about this strange remapping?

 

Here's what I empirically discovered about idTech1's palettes and colormaps. At that time, my goals were to understand how all of Doom worked, and to provide tools to let me build new capabilities into Doom, via code and data manipulation. I hope this helps answer Marn's questions:

 

PLAYPAL

Each PLAYPAL state is a full 256-color palette. This is used as the flash(es) upon item pickup, the green nukage-suit view, and the red pain flashes:

* Doom v0.2/0.4: 1 state only

* Doom v0.5: 11 states

  * 0: original

  * 1-7 = n/8 blend with #FF0000

  * 8-10 = (n-7)/8 blend with #D7BA45

 

* Doom Press Release: 13 states

  * 0: original

  * 1-8 = n/9 blend with #FF0000

  * 9-12 = (n-8)/8 blend with #D7BA45

 

* Doom/Heretic/Strife: 14 states

  * same as Press Release, with added state:

  * 13 = 1/8 blend with #00FF00

 

* Hexen: 28 states

  * 0-12 matches Doom

  * 13-20 = (n-12)/10 blend with #2C5C24

  * 21 = 1/2 blend with #0000E0

  * 22 = 1/2 blend with #828282

  * 23 = 1/2 blend with #646464

  * 24 = 1/2 blend with #707070

  * 25 = 1/2 blend with #966E00

  * 26 = 1/2 blend with #7D5C00

  * 27 = 1/2 blend with #644900

 

COLORMAP w/Invulnerability Gen

In my WADExplorer app, I added support for building all manners of complexity into the COLORMAP building process, including inverted ramps (very bizarre), fog effects via pre and post processing, color index exceptions (glowing indexes/primitive brightmaps in software), additive blending, and being able to fine-tune the color-matching process:

CMAP.PNG.73a1513280772b2a0c58ba4665aef034.PNG

 

 

Ignoring all of that complexity reduces the algorithm to:

// COLORMAP generator pseudocode
// kb1 - 2019/05/17
// NOTE: These are my algorithms, empirically generated based on my observations from many
// years ago - I make no claims on them being exact - in fact, I know them to only be
// "extremely close".
// 
// code assumes pre-existing PLAYPAL[255] with float Red, Green, and Blue components

// a very basic closest color match algorithm
char Best_PLAYPAL_Match(double Red, double Green, double Blue)
{
  char   best_inx = 0;
  double best_dif = 1000000f;
  
  char   inx;
  double dif;
  
  for (inx = 0; inx < 256; inx++)
  {
    dif = sqrt(
      (PLAYPAL[inx].Red   - Red)   * (PLAYPAL[inx].Red   - Red)   + 
      (PLAYPAL[inx].Green - Green) * (PLAYPAL[inx].Green - Green) + 
      (PLAYPAL[inx].Blue  - Blue)  * (PLAYPAL[inx].Blue  - Blue)   );
    
    if (best_dif > dif)
    {
      best_dif = dif;
      best_inx = inx;
    }
  }
  
  return best_inx;
}

void BuildColormapLump(int beta_night_vision)
{
  // Allocate space for 34 256-byte frames in new COLORMAP lump.
  // Frame 0    = full bright
  // Frame 1-31 = diminished light
  // Frame 32   = invulnerability
  // Frame 33   = beta night-vision (Press-Release beta only)
  char COLORMAP[34 * 256 - 1];
  int cmap_ptr = 0;

  // fill Frame 0
  for (i = 0; i < 256; i++)
    COLORMAP[cmap_ptr++] = i;

  // fill Frames 1 thru 31
  for (int Frame = 1; Frame < 32; Frame++)
  {
    double blend = ((31 - Frame) / 31);

    for (i = 0; i < 256; i++)
    {
      COLORMAP[cmap_ptr++] = 
        Best_PLAYPAL_Match(
          PLAYPAL[i].Red * blend,
          PLAYPAL[i].Green * blend,
          PLAYPAL[i].Blue * blend);
    }
  }
    
  // fill Invulnerability Frame 32
  // NOTE: May not match IWADs 100%, but very close
  for (i = 0; i < 256; i++)
  {
    double bright = 255 - 
      (PLAYPAL[i].Red   * 0.3125f + 
       PLAYPAL[i].Green * 0.5680f + 
       PLAYPAL[i].Blue  * 0.1195f);
      
    if (bright < 0)
      bright = 0;
      
    COLORMAP[cmap_ptr++] = Best_PLAYPAL_Match(bright, bright, bright);
  }

  if (beta_night_vision)
  {
    // fill Frame 33 like Press-Release Beta
    // NOTE: May not match IWADs 100%, but very close
    for (i = 0; i < 256; i++)
    {
      double bright =  
        (PLAYPAL[i].Red   * 0.3125f + 
         PLAYPAL[i].Green * 0.5680f + 
         PLAYPAL[i].Blue  * 0.1195f);
      
      if (bright > 255)
        bright = 255;
      
      COLORMAP[cmap_ptr++] = Best_PLAYPAL_Match(0, bright, 0);
    }
  }
  
  else
  {
    // fill Frame 33 - all black
    for (i = 0; i < 256; i++)
      COLORMAP[cmap_ptr++] = 0;
  }
}

 

Edited by kb1

Share this post


Link to post
Posted (edited)
2 hours ago, kb1 said:

Very interesting point about the last column. I can't look at the tools, or at hexen.wad at the moment. Does the original Hexen COLORMAP map index 255 to black??

Isn't this what I just wrote?

 

2 hours ago, kb1 said:

So WAD tools really shouldn't use index 255 in Hexen, huh?

Nothing stops you from including a modified COLORMAP in your mod.

 

 

Share this post


Link to post

I was wondering how would doom look like with a colormap like Hexen's. It doesn't look bad but sadly there are some saturated stray orange/red pixels in some sprites textures.

Share this post


Link to post

@kb1 sqrt? why? it doesn't matter there, and can be safely omited.

 

also, i am using the following code instead of simple distance. it is rumored that it gives slightly better visual results:

// see https://www.compuphase.com/cmetric.htm
static inline __attribute__((unused)) __attribute__((const)) vint32 rgbDistanceSquared (vuint8 r0, vuint8 g0, vuint8 b0, vuint8 r1, vuint8 g1, vuint8 b1) {
  const vint32 rmean = ((vint32)r0+(vint32)r1)/2;
  const vint32 r = (vint32)r0-(vint32)r1;
  const vint32 g = (vint32)g0-(vint32)g1;
  const vint32 b = (vint32)b0-(vint32)b1;
  return (((512+rmean)*r*r)/256)+4*g*g+(((767-rmean)*b*b)/256);
}

 

Share this post


Link to post
On 5/17/2019 at 8:31 AM, Gez said:

Isn't this what I just wrote?

 

Nothing stops you from including a modified COLORMAP in your mod.

WTF's eating you?

 

On 5/18/2019 at 9:22 PM, ketmar said:

@kb1 sqrt? why? it doesn't matter there, and can be safely omited.

Yes, in this particular case, it can be omitted, and your one-time COLORMAP build will be a few ms. faster :) I do the final square root for a number of reasons:

It keeps the result within the same range as the RGB numbers, and it's consistent with the standard distance formula. The squares cause a difference in the same primary color to have more weight than the same difference spread across primaries. In other words, a difference of 20 red holds much more weight than a difference of 10 red, and 10 blue, for example. The square root at the end just converts it back to 20, vs. 400, allowing the formula to be used recursively, if desired.

 

On 5/18/2019 at 9:22 PM, ketmar said:

also, i am using the following code instead of simple distance. it is rumored that it gives slightly better visual results:


// see https://www.compuphase.com/cmetric.htm
static inline __attribute__((unused)) __attribute__((const)) vint32 rgbDistanceSquared (vuint8 r0, vuint8 g0, vuint8 b0, vuint8 r1, vuint8 g1, vuint8 b1) {
  const vint32 rmean = ((vint32)r0+(vint32)r1)/2;
  const vint32 r = (vint32)r0-(vint32)r1;
  const vint32 g = (vint32)g0-(vint32)g1;
  const vint32 b = (vint32)b0-(vint32)b1;
  return (((512+rmean)*r*r)/256)+4*g*g+(((767-rmean)*b*b)/256);
}

Interesting. I'm always looking for a better color match algorithm. As you probably know, the standard RGB ratios used in monitors is not linear - it's more in tune with how sensitive the human eye supposedly is to changes in the primary colors.

Your formula reminds me of the way color television signals were transmitted and interpreted. The green signal was not sent like the red and blue - instead is was derived from the intensity, as the remainder after the red and blue were considered. Please look it up for details: I am not describing it accurately.

Another method I've been considering is matching based on converting the RGB to hue, saturation, and intensity. Mathematically, not a lot different, but, supposedly, visually, this produces good results too.

 

If you look carefully at my screenshot, in the Conversion frame, you'll see some sliders for red, green, blue, hue, saturation, and intensity. Also, there's a dropdown that allows you to choose the matching method. This has allowed me to produce some nice colormaps, regardless of accuracy. These controls are much more useful when importing images and converting them into the game's 256-color palette. In fact, I only added them to the colormap build tool to let me produce some neat color effects, without being very concerned about accuracy.

 

I'm going to have to give your conversion code a try - thanks!

 

 

Share this post


Link to post
Posted (edited)
23 minutes ago, kb1 said:

matching based on converting the RGB to hue, saturation, and intensity. Mathematically, not a lot different, but, supposedly, visually, this produces good results too.

you may be surprised a little in the end. ;-) i found that for limited palettes simple distance (or the formula given above) usually works much better than any "visually correct" method. you may end up fine-tuning colorspace conversions, distance calculations, gamma coeffs, and the final result will be almost as good as simple distance.

 

p.s.: even converting from sRGB to linear space, and use distance on linear values, which supposedly should give better visual matches, doesn't really producing better results. sometimes much worser than sRGB distance, almost never better.

 

colors are hard! ;-)

Share this post


Link to post
On 5/16/2019 at 4:46 PM, Marn said:

Thx

Did this help?

 

On 5/18/2019 at 10:11 AM, Kore said:

I was wondering how would doom look like with a colormap like Hexen's. It doesn't look bad but sadly there are some saturated stray orange/red pixels in some sprites textures.

Doom's palette is pretty highly specialized for Doom's art. Theoretically, Hexen's palette should be superior to all the others, and, in some ways it is. Hexen's palette *is* superior, as far as providing coverage for random images, as it provides an even spread amongst all possible colors...but that's also its weakness - it's the jack of all trades.

 

You start out with a space of 256 colors, which is not a lot. But, it's even worse than that, because you don't really have 256 unique colors - you have some very bright colors, but you also have to have darkened versions of those colors. In fact, that's what COLORMAP does - for each of the 256 PLAYPAL colors, COLORMAP provides a remapping of those colors to 32 darker levels of those colors, picked from that original 256. It's difficult to explain.

 

When you have smoothly-darker versions of your bright colors, things look pretty good in dark rooms. Doom's COLORMAP does pretty good with some of the colors, and pretty bad with others. Pinkies turn brown very quickly as the room gets darker, for example. But, one of Doom's strong points is that it doesn't try to support the whole spectrum of colors possible. Instead, it's specialized to Doom's specific art: Lots of dark reds, army greens, tans, lot's of grey, etc. Hexen's palette, on the other hand, has an even spread across the color spectrum, meaning that it can good fairly well with any arbitrary image, but it cannot handle diminshed light quite as well as Doom can (with Doom art). Now, if you adjusted Doom's resources to use Hexen's colors, it can probably end up looking pretty good.

 

1 hour ago, ketmar said:

you may be surprised a little in the end. ;-) i found that for limited palettes simple distance (or the formula given above) usually works much better than any "visually correct" method. you may end up fine-tuning colorspace conversions, distance calculations, gamma coeffs, and the final result will be almost as good as simple distance.

 

p.s.: even converting from sRGB to linear space, and use distance on linear values, which supposedly should give better visual matches, doesn't really producing better results. sometimes much worser than sRGB distance, almost never better.

 

colors are hard! ;-)

Colors *are* hard - you're right about that!

 

And, I've experienced the same ambiguity with color matching. However, there's one area where proper color matching can make a huge improvement with COLORMAP generation: Per-color range, specialized diminished light calculations. What I mean is this:

Using a different formula for each of Doom's color ranges: One for reds, one for greens, etc. I've found that Doom colors *always* go wrong when you try to use one single formula for calculating diminished light: When you get the greens right, the blues fail. Fix the blues, and yellow starts to look green. It's always a mess.

 

But, using one formula for blues, one for reds, one for greens, etc.? Honestly, I haven't put in enough research yet, but I know that I can tweak the formula to get some of the colors to look good, and then, I can use a different formula and fix the other colors.

 

When I get some time, I am going to experiment with this. I cannot prove it, but I *know* that there's a lot of room for improvement, and I believe using multiple formulas is the way to go.

 

Now, I'm talking about a COLORMAP that is *identical* to vanilla, all the way down to, say, 128 light. Then, from 128 down to 0, per-color range formulas, specialized to keep the color hues in the same range, as much as possible, all the way to black. We should be able to do a whole lot better.

 

I think it hasn't really been done before, because:

  • Using multiple formulas is complicated, and it takes a long time to get it right.
  • Attempts to manually fix the colors involves manually adjusting *a lot* of colors. It must be done with software.
  • Doom's palette is not arranged perfectly, which complicates the definition of "color range".

I guess I'm going to have to devote some time and produce a demo or two.

 

 

 

 

Share this post


Link to post

i didn't tried it, it is just a thought. but just subtracting from sRGB already gives results with wrong gamma, so maybe doing subtractions in linear color space will get better results? i.e. convert sRGB to linear, darken it, convert back to sRGB, and use color cube distance to find a match.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×