DWolf
DWolf

Reputation: 725

Opengl wrong colors when applying textures to objects

So I am having a rough go of things when it comes to applying ppm textures to a quad shape. My issue is the color is not "correct."

colors of a few planets

Here is a few planets from mercury towards jupiter.

The only color that is correct is the earth.

I downloaded the textures online (jpegs) and then converted them from the linux commandline

jpegtopnm Mercury.jpg > Mercury.ppm

I did this for all of the images that you see above.

At first the colors were all inverted so I changed

 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ImgWidth, ImgHeight, 0, GL_RGB,
                GL_UNSIGNED_BYTE, TexBits);

to

 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ImgWidth, ImgHeight, 0, GL_BGR,
                GL_UNSIGNED_BYTE, TexBits);

and that fixed the inversed colors (r instead of b ... ect). Now i am stuck here.. how do I correct the textures to show the correct colors for the planets?

here is the mercury texture file enter image description here

and here is the code that handles the textures

void read_file(const char * filename)
{
    FILE *infile;
    char buf[80];
    char string[256];
    unsigned char *texImage;
    int i, temp;
    GLubyte *sp;
    if ( (infile = fopen(filename, "rb")) == NULL) 
    {
        printf("File open error\n");
        exit(1);
    }
    fgets(string, 256, infile);


     fgets(string, 256, infile);
    while (string[0] == '#')
            fgets(string, 256, infile);

    sscanf(string, "%d %d", &ImgWidth, &ImgHeight);


    if (TexBits != 0) 
        free(TexBits);

    TexBits = (GLubyte *) calloc(ImgWidth * ImgHeight * 3, sizeof(GLubyte));

    for (i = ImgHeight - 1; i >= 0; i--)
    {
        sp = TexBits + i * ImgWidth * 3;
            fread (sp, sizeof(GLubyte), ImgWidth * 3, infile);
    } 
    fclose(infile);
 }
void bindTexture()
{
     glGenTextures(10, texName);

     glBindTexture(GL_TEXTURE_2D, texName[1]);
     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     read_file("Mercury.ppm");
         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ImgWidth, ImgHeight, 0, GL_BGR,
                GL_UNSIGNED_BYTE, TexBits);

    ///// rest of planets in order.. ( same code for each of them )
 }

 void drawCircle(GLfloat size, GLfloat offset, GLint r)
 {
     quadratic=gluNewQuadric();

     gluQuadricDrawStyle(quadratic, GLU_FILL);
     gluQuadricTexture(quadratic, GL_TRUE);
     gluSphere(quadratic, size, r, r);
 }

 void display(void)
 {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();

     /* mercury */
     glEnable(GL_TEXTURE_2D);
     glBindTexture(GL_TEXTURE_2D, texName[1]);
     glColor4f(1.0, 1.0, 1.0, 1.0);
     DrawPlanet(-17.0f, -4.0f, -9.0f, zAxis, .7f, .3f, 25);
     glBindTexture(GL_TEXTURE_2D, 0);
     glDisable(GL_TEXTURE_2D);


         // rest of code is similar to the one above
     }

I am finding it hard to figure out why the colors are a bright color of green instead of the actual color of each planet.. it seems that the program is having a problem with brown.. any ideas?

Upvotes: 1

Views: 1618

Answers (1)

Joe Z
Joe Z

Reputation: 17956

P6 PPM files have in their header 3 numbers: A width, a height and a maxval. The width and height give the dimensions of the image, and the maxval gives the dynamic range of the image.

In your code above, you scan for the image dimensions, but not the maxval. The maxval is not guaranteed to be on the same line as the image dimensions.

As a result, at the point where you start reading the image, the P6's maxval gets read in as part of the image information, shifting all the bytes, and rotating the apparent R, G and B values.

The exact definition of the P6 header and payload is as follows (courtesy of man ppm):

  • A "magic number" for identifying the file type. A ppm image's magic number is the two characters "P6".

  • Whitespace (blanks, TABs, CRs, LFs).

  • A width, formatted as ASCII characters in decimal.

  • Whitespace.

  • A height, again in ASCII decimal.

  • Whitespace.

  • The maximum color value (Maxval), again in ASCII decimal. Must be less than 65536.

  • Newline or other single whitespace character.

  • A raster of Width * Height pixels, proceeding through the image in normal English reading order. Each pixel is a triplet of red, green, and blue samples, in that order. Each sample is represented in pure binary by either 1 or 2 bytes. If the Maxval is less than 256, it is 1 byte. Otherwise, it is 2 bytes. The most significant byte is first.

  • Characters from a "#" to the next end-of-line, before the maxval line, are comments and are ignored.

This suggests that rather than using fgets and sscanf (which, honestly, is often the better idea for parsing, especially with line-oriented input), you should consider using a loop with fgetc() and a small state machine to ensure you're fully robust against any P6 file you might encounter.

Something like this may work:

int get_pnm_header(FILE *f)
{
    int ch;

    if ((ch = fgetc(f)) != 'P')
        return -1;                 // not a PNM file

    if ((ch = fgetc(f)) < '1' || ch > '6')
        return -1;                 // not a PNM file

    return ch - '0';
}

int get_ppm_integer(FILE *f)
{
    int in_comment = 0;
    int in_value   = 0;
    int value      = 0;
    int ch;

    while ((ch = fgetc(f)) != EOF)
    {
        if (ch == '#')
            in_comment = 1;

        if (in_comment)
        {
            in_comment = ch != '\n'; 
            continue;
        }

        if (isdigit(ch))
            in_value = 1;

        if (in_value)
        {
            if (!isdigit(ch))
            { 
                if (!isspace(ch))  /* consume first WS after value */
                    ungetc(ch, f); /* If not WS, put it back (might be '#' for a comment) */
                return value; 
            }

            value = (value * 10) + ch - '0';
            continue;
        }

        if (!isspace(ch))
        {
            fprintf(stderr, "Warning: unexpected character '%c' in P6 header\n", ch);
        }
    }
    fprintf(stderr,  "Warning: EOF encountered reading P6 header\n");
    return -1;
} 

and then in your code further down:

int pnm_type = get_pnm_header(infile);

if (pnm_type != 6)
    // report an error about unexpected file type

ImgWidth  = get_ppm_integer(infile);
ImgHeight = get_ppm_integer(infile);
ImgMaxval = get_ppm_integer(infile);

if (ImgMaxval != 255)
    // report an error about unsupported maxval

int r = fread((void *)TexBits, 3, ImgHeight * ImgWidth, infile);

if (r != ImgHeight * ImgWidth)
    // report an error about a short file.

If you modify get_ppm_integer to just return value on EOF, I believe that function will also work correctly for reading the bytes in the body of P3 files, if you need to support those.

Upvotes: 4

Related Questions