Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - turning palettes monochromatic

Reply to topic
Author Message
  • Joined: 01 Feb 2014
  • Posts: 643
Reply with quote
turning palettes monochromatic
Post Posted: Sun Aug 01, 2021 10:17 am
This may be a weird question, but I'm going to ask it anyway.

Does anyone have a good idea how it might be possible to transform a given sprite and/or background palette into a monochromatic version of itself while preserving the internal colour ramps?

Like for example if you want to turn a colour picture into a black and white one.

The easiest way I can think of would involve using a lookup table, but maybe there's a more elegant way that doesn't require precalculating all the colour values?
  View user's profile Send private message
  • Joined: 04 Jul 2010
  • Posts: 368
  • Location: Angers, France
Reply with quote
Post Posted: Sun Aug 01, 2021 12:58 pm
Last edited by ichigobankai on Tue Aug 03, 2021 9:33 am; edited 2 times in total
for speed and size i will only use LUT.

edit. remove stupid things ^^
  View user's profile Send private message
  • Joined: 09 Jun 2014
  • Posts: 97
Reply with quote
Post Posted: Sun Aug 01, 2021 1:35 pm
Beware that the SMS has only 4 greyscale colors, so a lot of shades will be lost:
- Black (00)
- Dark grey (15)
- Light grey (2a)
- White (3f)

So you might wanna use blue for more shades.

The Game gear has more colors available than SMS though.

https://www.smspower.org/maxim/HowToProgram/Palette
  View user's profile Send private message
  • Joined: 29 Mar 2012
  • Posts: 696
  • Location: Spain
Reply with quote
Post Posted: Mon Aug 02, 2021 7:17 am
Just for curiosity, this are my dinamic way to create bw, sepia or blue palettes:

void createBWPaletteVersion(unsigned char *original_palette, unsigned char *bw_palette, unsigned char numcolors) {
    unsigned char j,redComponent, greenComponent, blueComponent, addedColor;
    for(j = 0;j<numcolors;j++) {
        redComponent = getRedFromRGB(original_palette[j]);
        greenComponent = getGreenFromRGB(original_palette[j]);
        blueComponent = getBlueFromRGB(original_palette[j]);
        addedColor = redComponent + greenComponent + blueComponent;
        switch (addedColor)
        {
            case 0:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 0;
                break;
            case 1:
            case 2:
            case 3:
                redComponent = 1;
                greenComponent = 1;
                blueComponent = 1;
                break;
            case 4:
            case 5:
            case 6:
                redComponent = 2;
                greenComponent = 2;
                blueComponent = 2;
                break; 
            case 7: 
            case 8:
            case 9:
                redComponent = 3;
                greenComponent = 3;
                blueComponent = 3;
                break;
            default:
                redComponent = 3;
                greenComponent = 3;
                blueComponent = 3;
            break;
        }
        bw_palette[j] = RGB(redComponent,greenComponent,blueComponent);
    }
}

void createSepiaPaletteVersion(unsigned char *original_palette, unsigned char *bw_palette, unsigned char numcolors) {
    unsigned char j,redComponent, greenComponent, blueComponent, addedColor;
    for(j = 0;j<numcolors;j++) {
        redComponent = getRedFromRGB(original_palette[j]);
        greenComponent = getGreenFromRGB(original_palette[j]);
        blueComponent = getBlueFromRGB(original_palette[j]);
        addedColor = redComponent + greenComponent + blueComponent;
        switch (addedColor)
        {
            case 0:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 0;
                break;
            case 1:
            case 2:
            case 3:
                redComponent = 1;
                greenComponent = 1;
                blueComponent = 0;
                break;
            case 4:
            case 5:
            case 6:
                redComponent = 2;
                greenComponent = 2;
                blueComponent = 1;
                break; 
            case 7: 
            case 8:
            case 9:
                redComponent = 3;
                greenComponent = 3;
                blueComponent = 2;
                break;
            default:
                redComponent = 3;
                greenComponent = 3;
                blueComponent = 3;
            break;
        }
        bw_palette[j] = RGB(redComponent,greenComponent,blueComponent);
    }
}

void createBluePaletteVersion(unsigned char *original_palette, unsigned char *bw_palette, unsigned char numcolors) {
    unsigned char j,redComponent, greenComponent, blueComponent, addedColor;
    for(j = 0;j<numcolors;j++) {
        redComponent = getRedFromRGB(original_palette[j]);
        greenComponent = getGreenFromRGB(original_palette[j]);
        blueComponent = getBlueFromRGB(original_palette[j]);
        addedColor = redComponent + greenComponent + blueComponent;
        switch (addedColor)
        {
            case 0:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 0;
                break;
            case 1:
            case 2:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 1;
                break;
            case 3:
            case 4:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 2;
                break;
           
            case 5:
            case 6:
                redComponent = 0;
                greenComponent = 0;
                blueComponent = 3;
                break; 
            case 7: 
            case 8:
                redComponent = 0;
                greenComponent = 1;
                blueComponent = 3;
            case 9:
                redComponent = 0;
                greenComponent = 2;
                blueComponent = 3; 
                break;
            default:
                redComponent = 0;
                greenComponent = 3;
                blueComponent = 3;
            break;
        }
        bw_palette[j] = RGB(redComponent,greenComponent,blueComponent);
    }
}
  View user's profile Send private message
  • Joined: 23 Aug 2009
  • Posts: 146
  • Location: Seattle, WA
Reply with quote
Post Posted: Mon Aug 02, 2021 8:56 pm
I think if you take the highest value of any given RGB triad, that will give you the corresponding grey value.

For example, if you have (170, 85, 85), use a grey value of (170, 170, 170).
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 13785
  • Location: London
Reply with quote
Post Posted: Mon Aug 02, 2021 10:37 pm
Technically you need to weight the three colour channels differently and that means a lookup to do a decent job. Many colours will map to the two greys though.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 696
  • Location: Spain
Reply with quote
Post Posted: Tue Aug 03, 2021 6:43 am
Why differently? the median of the three channels is more or less working well to me.
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 13785
  • Location: London
Reply with quote
Post Posted: Tue Aug 03, 2021 9:03 am
The weights are there because the perceived brightness of red, green and blue are different.

I made a quick JS page to show the different greys from a few algorithms, see attached. The grey (perfect) column is applying the NTSC weights for greyscale, somewhat as a monochrome TV would handle the SMS signal. Grey (SMS) is the nearest SMS greys to this. The "average" column is the unweighted version. Both median and max algorithms seen to fare badly, picking black and white inappropriately.

It would be interesting to add more algorithms, some palette mapping (e.g. using blues) and some demo images, but that would be rather more work.
2021-08-03 10.22.43 98e1c7d65ee8.png (47.01 KB)
Screenshot
2021-08-03 10.22.43  98e1c7d65ee8.png
colours.zip (1.04 KB)
Code

  View user's profile Send private message Visit poster's website
  • Joined: 04 Jul 2010
  • Posts: 368
  • Location: Angers, France
Reply with quote
Post Posted: Tue Aug 03, 2021 9:33 am
Funny, i've made the same thing but by hand ^^
colors.png (31.52 KB)
colors.png

  View user's profile Send private message
  • Joined: 01 Feb 2014
  • Posts: 643
Reply with quote
Post Posted: Tue Aug 03, 2021 12:39 pm
Highly interesting, thanks.

I had actually considered the median approach, but had dismissed it eventually due to the massive amount of bit shifting and masking operations necessary to separate the RGB channels.

SavagePencil's and ichigo's threshold approaches seem like a tolerable compromise between speed and accuracy, even though they tend to over-represent either white or black.

Maxim's and ichigo's graphical charts, however, convince me that a LUT is most likely the way to go. It would indeed be interesting to apply them to some example images.

@kusfo: I really like your sepia and blue palettes. I will keep them in mind for future projects.
  View user's profile Send private message
  • Joined: 29 Mar 2012
  • Posts: 696
  • Location: Spain
Reply with quote
Post Posted: Tue Aug 03, 2021 3:12 pm
@Maxim: Thanks for the explanation! I'll do some more experiments!

@Kagesan: You're welcome!
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 13785
  • Location: London
Reply with quote
Post Posted: Wed Aug 04, 2021 3:54 pm
Here's an updated version with automatic colour mapping based on brightness. It's somewhat easy to add in additional colour ramps; I extended the "blues" to 11 levels here.
2021-08-04 16.52.52 d978b3db35a8.png (168.33 KB)
Screenshot
2021-08-04 16.52.52  d978b3db35a8.png
colours.zip (1.38 KB)
Code

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 2968
Reply with quote
Post Posted: Wed Aug 04, 2021 6:09 pm
sorry I'm late to the party, but I would suggest using one of these formulas then carefully handpick the three thresholds for the conversion, then fix anything that you don't like and create a LUT after that.
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 13785
  • Location: London
Reply with quote
Post Posted: Wed Aug 04, 2021 8:43 pm
I used the NTSC ones for mine; there is a minefield of colour space representation issue you can get into here, but for 6-bit RGB it's a bit unnecessary.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 2968
Reply with quote
Post Posted: Thu Aug 05, 2021 10:25 am
also, I thought you might not even have to use a generic LUT for color conversions to grayscale - you might just have an alternative grayscale palette for each (color) palette you load in the game.
This way you can have absolute control.
  View user's profile Send private message Visit poster's website
Reply to topic



Back to the top of this page

Back to SMS Power!