grayscale in 8bit mode (the way div does it)

Started by handsource-dyko, February 09, 2013, 03:13:18 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

handsource-dyko

I am creating an automated tool that produces an fpg archive with 17 maps in it. The first one is the colored foreground, and the other ones are 50% size scale and should become grayscale. unfortunatly for me, I can't use the standard mod_effects grayscale function because my maps are 8 bit. I used to use DIV to this repetitive manual job, but I figured that making an automated tool for this job saves me a lot of manual work and time.

DIV has a very interessting approach to grayscale, it changes the pixels in an image to other color indexes (it picks about 8 gray colors from the palette, found out by checking some sample graphics).

I basically want to recreate this, but so far my results don't look as nice, so I wonder if DIV uses a specific formula to change a colored pixel to a good grayscale equivilant.

Here's a bit from my code that changes the pixels:


// so far so good, we can create an fpg archive
   exportfile = fpg_new ();
   
   
   // apply the malvado.pal color palette to the loaded png/map file.
   pal_status = pal_map_assign (0, color_input_map, game.palette); 
   say("pal_status: "+pal_status);
   
   // create a monochrome copy of the map (changes the colors but preserves the palette) and scale it to 50%.
   
   // create a new map, 50% the size of the original map.
   grayscale_copy_map = map_new ((map_info (0, color_input_map , G_WIDTH) / 2),
                                 (map_info (0, color_input_map , G_HEIGHT) / 2),;
   
   say("grayscale_copy_map: "+grayscale_copy_map);   
                                 
   // blit the orignal map on the new map, with a 50% scale
   xput_status = map_xput (0, grayscale_copy_map, color_input_map,
                           map_info (0, grayscale_copy_map , G_X_CENTER), // x posistion
                           map_info (0, grayscale_copy_map , G_y_CENTER), // y position
                           0, 50, 0);
       
   say("xput_status: "+xput_status);

   // change all the colors to grayscale, preserving the palette.
   // because the grayscale() function from mod_effects doesn't work with 8 bit maps we'll have to
   // covert every pixel manually to the right color. wich is a bummer.
   // therefore, we'll have to change every pixel that is not color value 0, line by line. basically
   // it's a pixel swap. we'll reduce the colors to index value 21-31 all of these have r,g,b : 0,0,b
   // where b is: 168, 176, 184, 192, 200, 208, 216, 224, 232, 240 and 248. the "b" value for color 21
   // is 168, and for color 31 it is 248. this applies only for malvado.pal.
   // the trick is to do this in such way that it will look nice. we'll convert every group of 8 colors
   // from the 256 color palette to one of these, in the order that they appear. when a pixel has one of
   // these colors in the first placem then that pixel stays the way it is. of course a pixel with color 0
   // will stay color 0.
   
   // colors found with grapx2 that are used by a grayscale map generated by div with malvado.pal:
   
   // - color 22 : r,g,b, = 0,0,176
   // - color 24 : r,g,b, = 0,0,192
   // - color 31 : r,g,b, = 0,0,248
   // - color 179 : r,g,b, = 0,20,152
   // - color 180 : r,g,b, = 0,20,160
   // - color 182 : r,g,b, = 0,20,176
   // - color 184 : r,g,b, = 0,20,192
   // - color 191 : r,g,b, = 0,20,248
   
   // it looks like that these 8 colors are used and they seem to have a pattern. what I noticed when looking
   // at malvado.pal, is that when you look at each horizontal line of 16, the darkest color has a small value
   // for "b", and the light color the highest value. This apllies for all 16 lines of 16 colors. actually, it
   // would be more logical to use 16 grey colors from the palette instead of 8......
   
   // scan the lines from top to botton
   FOR (line_count = 0; line_count < map_info (0, grayscale_copy_map , G_HEIGHT); line_count ++)
     
      FOR (row_count = 0; row_count < map_info (0, grayscale_copy_map , G_WIDTH); row_count ++)
         
         say("line_count: "+line_count+" ; row_count: "+row_count);         
   
         // change the color of a pixel to one of the 8 grayscale colors, but keep color 0 intact.
         IF (map_get_pixel (0, grayscale_copy_map, row_count, line_count)  == 0)
            map_put_pixel (0, grayscale_copy_map, row_count, line_count, 0);
         ELSE   
            tempcolor = map_get_pixel (0, grayscale_copy_map, row_count, line_count);         
            rgb_get (map_get_pixel (0, grayscale_copy_map, row_count, line_count), &my_r, &my_g, &my_b);
            say("tempcolor (the detected index): "+tempcolor+" ; r="+my_r+"; g="+my_g+" ; b="+my_b);
           
            // compare the g and b values of against the 8 grayscale ones. a bigger value for b or g means that the
            // color is lighter, a smaller value means darker. so, for each pixel we compare how dark/light it is
            // relative to one of the 8 grayscale colors.
           
            SWITCH (my_b)
           
               CASE 0..20:
                  SWITCH (my_g)
                     
                     CASE 0..176:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 22); //22
                     END
                     
                     CASE 177..192:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 24); //24
                     END
                     
                     CASE 193..248:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 31); //31
                     END                 
                  END
               END
               
               CASE 21..255:
                 
                  SWITCH (my_g)
                     
                     CASE 0..152:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 179); //179
                     END
                     
                     CASE 153..160:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 180); //180
                     END
                     
                     CASE 161..176:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 182); //182
                     END
                     
                     CASE 177..192:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 184); //184
                     END
                     
                     CASE 193..248:
                        map_put_pixel (0, grayscale_copy_map, row_count, line_count, 191); //191
                     END
                  END
               END
            END
           
         END   
      END
   END
   


The comments in the code are just a part of my thought process, so I would like to know if someone has any ideas on this or knows how DIV actually does this.
In the attached image you'll see two grayscale images, the left one was produced by DIV, and the right one by my code. Both use the exact same palette.

SplinterGU

you are mixing C and PRG?

please, show a capture of the palettes that you use...
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

handsource-dyko

No, I am not mixing C and PRG, I am simply trying to substitute non-gray colors for gray colors, that all exist within the the same palette.

SplinterGU

Download Lastest BennuGD Release: http://www.bennugd.org/node/2

handsource-dyko

#4
I have solved it!  :) It's really not that hard after reading a wikipedia article on the subject and using the weigt factors for the r,g,b components.
I've fiddled a bit with a spreadsheet and selecting some gray colors from the palette. This way, I created a conversion table (it's only for malvado.pal) I still have to create this in a more algorithmic form but this it the code for now:


// scan the lines from top to botton
   FOR (line_count = 0; line_count < map_info (0, grayscale_copy_map , G_HEIGHT); line_count ++)
     
      FOR (row_count = 0; row_count < map_info (0, grayscale_copy_map , G_WIDTH); row_count ++)
 
         // change the colors, based on an weight value calculated with a spreadsheet for malvado.pal
         // the methode used by the spreadsheet is: there are 28 grayscale colors in the palette, so
         // there are about (255-28) / 28 = 8.107 ( colors per one gray color. index 0 is not changed.   
         // the weight value is based on this formula: (R*0,3) + (G*0,59) + (G*0,11).
         // for specic details see these files: src/editor-editart/colorpalette-grayscale-weigtvalues.txt
         SWITCH ( map_get_pixel (0, grayscale_copy_map, row_count, line_count) )
           
            CASE 0:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 0);
            END
           
            CASE 144,226,227,228,229,230,243,244:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 21);
            END
            CASE 23,42,61,121,148,204,205,237:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 23);
            END
           
            CASE 11,24,59,150,186,201,202,236:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 24);
            END
           
            CASE 8,9,17,25,39,40,56,78,79,152,153,197,198,221,235,250:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 25);
            END
           
            CASE 7,26,38,77,111,234,253:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 26);
            END
           
            CASE 20,72,92,105,124,135,158,216,249:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 28);
            END
           
            CASE 29,45,67,68,69,85,86,87,101,102,118,125,137,138,180,213:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 29);
            END
           
            CASE 30,46,81,82,99,116,140,211:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 30);
            END
           
            CASE 31,96,114,142,170,171,177,209:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 31);
            END
           
            CASE 112,176:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 127);
            END
           
            CASE 113,127,160,161,162,163:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 127);
            END
           
            CASE 143,164,165,166,167,168,169,208:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 177);
            END
           
            CASE 47,64,65,80,97,98,115,126,141,172,173,174,175,178,210,247:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 178);
            END
           
            CASE 66,83,84,100,117,139,179,212:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 179);
            END
           
            CASE 44,48,88,89,103,119,136,214:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 180);
            END
           
            CASE 1,2,28,32,33,49,50,70,71,90,91,104,159,181,215,248:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 181);
            END
           
            CASE 3,4,19,34,35,51,52,73,93,94,95,106,157,182,217,232:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 182);
            END
           
            CASE 5,6,18,27,36,37,53,54,74,75,76,107,108,109,110,123,134,155,156,183,218,219,233,252:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 183);
            END   
           
            CASE 55,133,154,184,199,220,251,254:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 184);
            END
           
            CASE 43,57,122,132,185,192,193,196:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 185);
            END
           
            CASE 10,41,58,151,194,195,200,222:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 186);
            END
       
            CASE 12, 16,60,131,149,187,203,223:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 187);
            END
           
            CASE 13,22,62,130,146,147,188,206:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 188);
            END
           
            CASE 14,63,119,120,127,145,189,207,238:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 189);
            END
           
            CASE 15,21,190,224,225,240,241,242:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 190);
            END
           
            CASE 128,191,231,239,245,246,255:
               map_put_pixel (0, grayscale_copy_map, row_count, line_count, 191);
            END                       
         END         
      END
   END
   



The relult can be seen in the attached file. I have also attached an txt export of my spreadsheet, I used it to create weight values
for each color with the formula: (R*0,3) + (G*0,59) + (G*0,11) and then sorted some of the tables to create a lookup table.
Stangly, wikipedia http://en.wikipedia.org/wiki/Grayscale mentions several different weight factors for the r,g,b components, but they all have in common that the green component is more dominant because the human eye is the most sensitive to this color.

handsource-dyko

#5
I have updated the program, I have converted the spreadsheet alogorithm into code. It can detect gray tones from the palette based on a formula and mark those colors as grayscale. The code is a bit too long to put up here, so see attached a textfile. It is a part of another progam, so it does not compile on it's own, but it creates an fpg file with 17 maps, and 16 of them have to be grayscale.

You can find the udated spreadsheet and source in the zip archive as well.