Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Matching My Wife's Wet Washcloth


APR89: GRAPHICS PROGRAMMING

My wife and I have an arrangement regarding home decor. We didn't plan it this way, it's just how it happens. She decides when a room needs redecorating and what needs to be done. Then I do all the work. Afterwards she shows it to friends and says, "Look what we did." That plural pronoun always bothers me because, after all, she was probably out shopping most of the time I was toiling. On the other hand, the "we" might be fair inasmuch as, if left solely up to me, nothing would get done around the house beyond replacing dead light bulbs.

Anyway (and there is a point to this), some years ago as she was preparing to put me to work, my wife took a washcloth to the paint store and told the guy, "Wet this and mix a paint that's the same color." Amazingly, he was able to do it. Starting with white, he added two squirts of this pigment, three of that, one of another, then stirred it up. And there it was, a perfect match.

We might not be able to exactly duplicate the color of my wife's wet washcloth on the EGA screen, but we can probably come pretty close. And the process is almost the same as the salesman used to blend the paint. We probably can match it exactly in VGA 256 color mode; however, that's significantly more complicated than EGA colors, so we'll put it off until later. As pointed out in earlier columns, the VGA behaves just like the EGA when in an EGA graphics mode or 640X480 16-color mode, so this discussion applies to both.

Graphics images are made from pixels, which in turn are represented by bit patterns in programs. The color that a pixel assumes on the screen is a function of the bit pattern placed in the video buffer's corresponding location. Therefore a pixel's data representation value determines its color, right?

Well, not really. There's a data structure between the video buffer and the screen itself, the contents of which governs the mapping of pixel values to actual colors. This data structure is called the color palette.

The name no doubt derives from an analogy to the artist's palette, a board containing an array of colors from which the artist selects as he or she paints. That's apt, because the EGA palette is an electronic equivalent. It holds an array of 16 colors in elements called registers. A pixel value is simply an index into this array. When you stuff pixel value 2 into the video buffer, in effect you say, "I want this pixel to have the color contained in palette register 2."

As it refreshes the screen, the video controller grabs a pixel value from the video buffer, then uses that value to locate the actual color in the palette. That's how color mapping occurs, and why EGA pixel values must be in the range 0 - 15.

So how does a palette register control the actual color? Through a rather oddly arranged 6-bit number that governs the intensities of the three basic colors --red, green, and blue --from which all displayable hues are derived.

Because a 6-bit number can represent 64 different values, the EGA is capable of displaying 64 colors. However, there can be a maximum of only 16 at a time due to the size of the palette. You can instantly change the color of all instances of a given pixel value by changing the contents of its corresponding palette register, but you can't do anything on an EGA to get more than 16 out of 64 colors at one time; it's a hardware limitation. If you gotta have more, get a VGA, which does 256 colors at a time from a total selection of 262,144. Still, 16 out of 64 colors at a resolution of 640x350 is enough to do some pretty respectable graphics.

The EGA initializes its palette to the same color sequence as the CGA's fixed text colors. This provides the default pixel value-to-color mapping shown in Table 1. The program COLORS.C in Listing One, page 138, displays the default EGA palette. The program must be linked with GRAFIX.LIB developed in earlier installments of this column.

Table 1: Default color palette for the EGA and VGA

  Value   Color
  -----------------------

     0     Black
     1     Blue
     2     Green
     3     Cyan
     4     Red
     5     Magenta
     6     Brown
     7     Light gray
     8     Dark gray
     9     Light blue
    10     Light green
    11     Light cyan
    12     Light red
    13     Light magenta
    14     Yellow
    15     White

If you've done much color programming in text, you've probably got the default values so engraved in your mind that 14 and yellow have become synonymous. The ability to change palette registers to any color you want means you'll have to break an old habit. From now on, pixel value 14 refers to whatever is in palette register 14 at the time.

Squirts of Pigment

The guy that matched my wife's washcloth made the color by squirting various quantities of one pigment and another into a white base. The process of creating EGA colors is similar, except that we start from a base of black.

Display colors are hues blended by combining various measures of three primary colors: red, green, and blue. These measures are actually intensities ranging from none (black) through bright. There are four intensities of each primary color, which is how we get 64 possible colors on the EGA (43). Here's how it works.

A byte contains eight bits, but a palette register is a 6-bit value. Consequently the two high-order bits are "don't care" and the remaining low six bits convey color information. It doesn't take a degree in math to figure out that, with three colors and six bits, there are two bits per primary color, and that each set of two bits can represent any of four intensities.

What is not so obvious is how those bits are arranged. The designers of the EGA no doubt had their reasons for doing it as they did, but I don't know what those reasons were. And whether it makes sense or not, it's how it is.

The palette bits are arranged in the order rgbRGB, where lower case indicates lower intensity. Thus the palette value xx100000 (binary) is a very dark shade of red, and palette value xx000011 is light cyan (a combination of bright blue and bright green). Turning all bits on yields pure white, while resetting all bits produces black, or the absence of any color component.

The default palette colors are grouped according to "normal" and "intense" hues. Thus registers 0 - 7 hold a sequence of colors with normal intensity, and 8 - 15 contain their brighter counterparts in the same sequence. The primary colors (red, green, and blue) in the lower range are actually of intensity level 2, or one level away from brightest. The default palette doesn't display any low-intensity primary colors, which tend to be quite dim on the display and are thus mostly useful for mixing to create composite colors.

An example is brown. It's composed of red level 2 and green level 1. To get this mixture, set the bits as follows:

  rgbRGB 
  ------
  10100
  

This yields the value 14h. Notice that brown contains no blue component, so bits b and B are both 0. This corresponds to blue level 0, or in other words the absence of blue.

Adding blue level 1 to the mix (011100b) is the same as squirting a little blue pigment into tan paint. The outcome is mauve, which is a sort of washed-out purple. If instead we take brown and "promote" both its colors upward one level (to green 2 and red 3, represented as 100110b) we get bright brown, or in other words yellow.

Because each of the primary color levels is represented by a fixed bit pattern, we can give them symbolic names to improve program readability. Table 2 shows the mapping of names to bit patterns. Using them, we can construct color palette values with expressions such as

  brown = GRN1 | RED2;

My colleagues in the publishing business have a name for this technique of forming hues by mixing measures of three primary colors: They call it color separation. To me that's backwards. It's really color combination. Whatever you call it, it works for the painter, the printer, and the programmer. Later in this article we'll look at a program that you can use to experiment with color mixing on the EGA.

Table 2: Symbolic names for the primary color intensities

  Name  Binary  Hex
  ------------------

  RED0  000000  0x00
  RED1  100000  0x20
  RED2  000100  0x04
  RED3  100100  0x24
  GRN0  000000  0x00
  GRN1  010000  0x10
  GRN2  000010  0x02
  GRN3  010010  0x12
  BLU0  000000  0x00
  BLU1  001000  0x08
  BLU2  000001  0x01
  BLU3  001001  0x09

Adding EGA Colors to the GRAFIX Library

In order to support EGA colors, we need to modify the GRAFIX.H header file that we're building and add some functions to the library.

Each month we'll be adding something to the header file, so I'll list that entire file with the additions set apart and labeled by a heading indicating the month they appeared. As you can see from Listing Two, page 138, this month we're adding color constants and the definition of a byte type to the top of GRAFIX.H, and four functions to the bottom.

The functions themselves are defined in EGAPALET.C (Listing Three, page 138). You should compile this file to an .OBJ, then add it to the GRAFIX library with the DOS command

  LIB GRAFIX +EGAPALET;

Let's look at what this program module does.

Because it's declared outside the scope of any function, the ega_palette array is a static visible to the four functions in this compile unit. Furthermore, it's initialized to the default colors of the real palette. Note that this is our copy of what's in the palette known to the video controller; it's not the palette. Because it carries the same values at initialization as the video controller's, and because the palette-setting function maintains it, we can safely assume that the local copy always accurately reflects the colors appearing on the display.

The ega_palreg( ) function returns the color value in a given palette register. Pass it the register number (that is, pixel value) and the function returns the 6-bit composite color pattern from that register.

Note that the ROM BIOS video services furnish function 10h, subfunction 7, to read a hardware palette register directly. If you put 10h in register AH, 7 in register AL, and the register number in BL, then execute interrupt 10h, you get the color pattern back in register BH. This is an alternative that is slightly safer than returning a value from the phantom software palette, but it incurs the heavy overhead of calling the ROM BIOS. Consequently we use the method given here in the interest of performance.

The next function, ega_blend( ), is a color pattern constructor. It combines the primary components into a byte that you can then load into a palette register or use for other purposes. For example, to make brown, write

  brown = ega_blend (RED2, GRN1,
                          BLU0);

Note that the order of arguments with respect to color sequence doesn't matter. The function simply combines bits. Thus,

  brown = ega_blend (BLU0, GRN1,
                          RED2);

produces exactly the same result.

The get_ega_colormix( ) function is the opposite of ega_blend( ). It explodes the content of a given palette register into its three primary color intensities. Here the sequence of color arguments is significant, and additionally the arguments must be addresses of receiving variables (since a function can return only one value directly). The function derives the bit patterns by ANDing the register contents with each of the most intense primary values, thus, isolating the appropriate bits.

The final function is set ega_palreg( ), which places a new color pattern into the given palette register. The function updates the local copy of the palette, then uses the ROM BIOS video services-unavoidable in this case-to change the hardware setting. The effect of this function is to instantly change all instances of the corresponding pixel to the new color on the display.

Listing Four, page 138, is a utility program that you can use to experiment with EGA color combinations. To get it up and running, compile MIX.C, then link with GRAFIX.LIB.

This program displays a 4x3 array of filled rectangles showing the four intensities each of red, green, and blue (the leftmost is black, the lowest possible intensity corresponding to RED0, GRN0, and BLU0). A large filled rectangle at the bottom of the screen contains the current color mixture; it starts out bright white, because the most intense of all three primary shades is selected.

A white rectangle surrounds one box in each color row. This is an indicator showing which intensity is currently a component of the color at the bottom. To move the indicator for red to a lower intensity, type r. The indicator shifts left and the color bar at the bottom immediately changes to pale cyan. To move the red indicator to its brighter neighbor, type uppercase R. You can similarly control green with g and G, and blue with b and B. Quit by pressing Esc; the program then shows the breakdown of the color most recently constructed.

The local functions in MIX.C reveal how it works. The setup_palette( ) function loads reds into palette registers 1 - 4, greens into 5 - 8, and blues into 9 - 12. Palette register 13 drives the color sample at the bottom of the screen. setup_screen( ) constructs the work display.

Most of the program's work is done by mix_colors( ), a loop that repeats until you press Esc. The loop gets a keystroke, then acts on it via a switch( ) whose cases vary depending on the keystroke. The RAISE and LOWER macros ensure that the indicator box remains within the four possible intensity selections. After the switch( ) completes, the new color mixture is computed and this value updates palette register 13, instantly changing the color sample box.

The main( ) function itself does little besides reporting the results after mix_colors( ) completes. The results make MIX more than just a toy or an example of how to use the graphics library we're constructing. Use it as a utility for designing the colors you want to use elsewhere, and the results report to code your program.

Who knows? One of the colors you mix might match my wife's wet washcloth.

_GRAPHICS PROGRAMMING COLUMN_ by Kent Porter

[LISTING ONE]

<a name="00d4_0009">


/* COLORS.C: Shows all colors in default palette */

#include "grafix.h"
#include <conio.h>

void main ()
{
int r, c, color;

  if (init_video (EGA)) {
    for (r = 0; r < 4; r++)
      for (c = 0; c < 4; c++) {
        color = (r * 4) + c;       /* next color */
        set_color1 (color);
        fill_rect ((c*160), (r*80), 158, 79);
      }
    getch();                /* wait for keypress */
  }
}





<a name="00d4_000a"><a name="00d4_000a">
<a name="00d4_000b">
[LISTING TWO]
<a name="00d4_000b">


/* Include file for GRAFIX.LIB                */
/* EGA/VGA graphics subsystem                 */
/* K. Porter, DDJ Graphics Programming Column */
/* ------------------------------------------ */

/* Color constants from April, 89 */
#define Black     0        /* standard colors */
#define Blue      1
#define Green     2
#define Cyan      3
#define Red       4
#define Magenta   5
#define Brown     0x14
#define LtGray    7
#define DkGray    0x38
#define LtBlue    0x39
#define LtGreen   0x3A
#define LtCyan    0x3B
#define LtRed     0x3C
#define LtMagenta 0x3D
#define Yellow    0x3E
#define White     0x3F

#define RED0      0x00  /* basic hues for mixing */
#define RED1      0x20
#define RED2      0x04
#define RED3      0x24
#define GRN0      0x00
#define GRN1      0x10
#define GRN2      0x02
#define GRN3      0x12
#define BLU0      0x00
#define BLU1      0x08
#define BLU2      0x01
#define BLU3      0x09

#if !defined byte
#define byte unsigned char
#endif

/* Supported video modes */
#define  EGA       0x10              /* EGA 640 x 350, 16/64 colors */
#define  VGA16     0x11              /* VGA 640 x 480, 16/64 colors */

/* Function prototypes */
/* From February, '89 */
/* ------------------ */
int far init_video (int mode);        /* init display in video mode */

void far pc_textmode (void);                        /* PC text mode */

void far draw_point (int x, int y);        /* write pixel in color1 */

void far set_color1 (int palette_reg);      /* set foreground color */

/* From March, '89 */
/* --------------- */
void far draw_line (int x1, int y1, int x2, int y2);
                                /* Bresenham line drawing algorithm */

void far draw_rect (int left, int top, int width, int height);
                             /* draw rectangle from top left corner */

void far polyline (int edges, int vertices[]);     /* draw polyline */

void far hline (int x, int y, int len);          /* horizontal line */

void far fill_rect (int left, int top, int width, int height);
      /* draw solid rectangle in color1 starting at top left corner */

/* From April, '89 */
/* --------------- */
byte far ega_palreg (int preg);         /* color in EGA palette reg */

void far set_ega_palreg (int reg, int color);    /* set palette reg */

byte far ega_blend (byte c1, byte c2, byte c3);  /* blend primaries */

void far get_ega_colormix (int preg, int *r, int *g, int *b);
        /* get mix of red, green, and blue in EGA pal register preg */





<a name="00d4_000c"><a name="00d4_000c">
<a name="00d4_000d">
[LISTING THREE]
<a name="00d4_000d">

/* EGAPALET.C: Palette control for EGA/VGA 16-color modes      */
/* Compile separately, then add to GRAFIX.LIB                  */
/* K. Porter, DDJ Graphics Programming Column, April '89       */
/* ----------------------------------------------------------- */

#include "grafix.h"
#include <dos.h>

byte ega_palette [16] = {          /* current palette contents */
   Black, Blue, Green, Cyan, Red, Magenta, Brown, LtGray,
   DkGray, LtBlue, LtGreen, LtCyan, LtRed, LtMagenta, Yellow,
   White
};
extern int grafixmode;                        /* from GRAFIX.C */
/* ----------------------------------------------------------- */

byte far ega_palreg (int preg)
    /* return contents of palette register preg */
{
  return ega_palette [preg];
} /* --------------------------------------------------------- */

byte far ega_blend (byte c1, byte c2, byte c3)
    /* blend three primary colors, return color pattern */
{
  return c1 | c2 | c3;
} /* --------------------------------------------------------- */

void far get_ega_colormix (int preg, int *r, int *g, int *b)
    /* break color in palette reg preg into its components */
{
  *r = ega_palette [preg] & RED3;
  *g = ega_palette [preg] & GRN3;
  *b = ega_palette [preg] & BLU3;
} /* --------------------------------------------------------- */

void far set_ega_palreg (int preg, int color)
    /* change palette register preg to new color */
    /* use only for EGA 16-color mode */
{
union REGS r;

  if (grafixmode == EGA) {
    ega_palette [preg] = color;  /* update our copy of palette */
    r.h.ah = 0x10;              /* use ROM BIOS video services */
    r.h.al = 0;                  /* to update the real palette */
    r.h.bh = color;
    r.h.bl = preg;
    int86 (0x10, &r, &r);
  }
} /* --------------------------------------------------------- */





<a name="00d4_000e"><a name="00d4_000e">
<a name="00d4_000f">
[LISTING FOUR]
<a name="00d4_000f">

/* MIX.C: Utility for mixing colors with EGA */

#include "grafix.h"
#include <conio.h>
#include <stdio.h>

#define ESC   27     /* Esc char */
#define ERASE  0
#define SHOW  15

int col[] = {30, 190, 350, 510};    /* box horiz locations */
int row[] = {30, 110, 190};         /* box vert locations  */
int rr = 1, gr = 5, br = 9;     /* reg index for color set */
int base_red[] = {RED0, RED1, RED2, RED3};
int base_grn[] = {GRN0, GRN1, GRN2, GRN3};
int base_blu[] = {BLU0, BLU1, BLU2, BLU3};

void main ()
{
int r, g, b;

void setup_palette (void), setup_screen (void),
     mix_colors (void);

  if (init_video (EGA)) {
    setup_palette();
    setup_screen();
    mix_colors();
    pc_textmode();
  }

  /* Report results */
  get_ega_colormix (13, &r, &g, &b);
  printf ("\nMixed color has value 0x%02X", ega_palreg (13));
  printf ("\nComponent breakdown:");
  printf ("\n    Red:   0x%02X  ", r);
  switch (r) {
    case RED0: puts ("(RED0)"); break;
    case RED1: puts ("(RED1)"); break;
    case RED2: puts ("(RED2)"); break;
    case RED3: puts ("(RED3)"); break;
  }
  printf ("    Green: 0x%02X  ", g);
  switch (g) {
    case GRN0: puts ("(GRN0)"); break;
    case GRN1: puts ("(GRN1)"); break;
    case GRN2: puts ("(GRN2)"); break;
    case GRN3: puts ("(GRN3)"); break;
  }
  printf ("    Blue:  0x%02X  ", b);
  switch (b) {
    case BLU0: puts ("(BLU0)"); break;
    case BLU1: puts ("(BLU1)"); break;
    case BLU2: puts ("(BLU2)"); break;
    case BLU3: puts ("(BLU3)"); break;
  }
} /* ----------------------------------------------------- */

void setup_palette (void)   /* put basic colors in palette */
{
  set_ega_palreg ( 1, RED0); set_ega_palreg ( 2, RED1);
  set_ega_palreg ( 3, RED2); set_ega_palreg ( 4, RED3);

  set_ega_palreg ( 5, GRN0); set_ega_palreg ( 6, GRN1);
  set_ega_palreg ( 7, GRN2); set_ega_palreg ( 8, GRN3);

  set_ega_palreg ( 9, BLU0); set_ega_palreg (10, BLU1);
  set_ega_palreg (11, BLU2); set_ega_palreg (12, BLU3);

  set_ega_palreg (13, White);     /* used for mixed colors */
} /* ----------------------------------------------------- */

void draw_box (int reg, int c, int r)  /* box around color */
{
  set_color1 (reg);
  draw_rect (col[c]-5, row[r]-5, 110, 70);
} /* ----------------------------------------------------- */

void setup_screen (void)          /* construct work screen */
{
int r, c, reg = 1;

  for (r = 0; r < 3; r++)
    for (c = 0; c < 4; c++) {
      set_color1 (reg++);              /* select color reg */
      fill_rect (col[c], row[r], 100, 60);         /* fill */
    }
  set_color1 (13);
  fill_rect (col[0], 280, 580, 60);    /* mixed color area */

  for (r = 0; r < 3; r++)       /* rubberband most intense */
    draw_box (SHOW, 3, r);
} /* ----------------------------------------------------- */

void mix_colors (void)              /* interactive portion */
{
#define RAISE(col) col = (col < 3) ? ++col : 3
#define LOWER(col) col = (col > 0) ? --col : 0

char reply;
int  r, rc = 3, gc = 3, bc = 3, mixture;

  do {
    reply = getch();
    switch (reply) {
      case ESC : break;                    /* quit program */
      case 'r' : r = 0;                       /* lower red */
                 draw_box (ERASE, rc, r);
                 LOWER (rc);
                 draw_box (SHOW, rc, r);
                 break;
      case 'R' : r = 0;                       /* raise red */
                 draw_box (ERASE, rc, r);
                 RAISE (rc);
                 draw_box (SHOW, rc, r);
                 break;
      case 'g' : r = 1;                     /* lower green */
                 draw_box (ERASE, gc, r);
                 LOWER (gc);
                 draw_box (SHOW, gc, r);
                 break;
      case 'G' : r = 1;                     /* raise green */
                 draw_box (ERASE, gc, r);
                 RAISE (gc);
                 draw_box (SHOW, gc, r);
                 break;
      case 'b' : r = 2;                      /* lower blue */
                 draw_box (ERASE, bc, r);
                 LOWER (bc);
                 draw_box (SHOW, bc, r);
                 break;
      case 'B' : r = 2;                      /* raise blue */
                 draw_box (ERASE, bc, r);
                 RAISE (bc);
                 draw_box (SHOW, bc, r);
                 break;
    }
    mixture = ega_blend (base_red[rc], base_grn[gc],
                          base_blu[bc]);
    set_ega_palreg (13, mixture);      /* update color mix */
  } while (reply != ESC);
}












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.